This fixes a potential[^1] security vulnerability -- if bot shows errors
from teloxide to the user & for some reason network error happened[^2]
the url of the request would be included in the error. Since TBA
includes bot token in the error this may lead to token leakage.
This commit fixes that issue by removing the token from the urls of
`reqwest::Error`, we try to only replace the token, but if we fail we
remove the whole url.
This can be tested by using a very low timeout value for the http
reqwest client:
```rust
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_millis(1))
.build()
.unwrap();
let bot = Bot::from_env_with_client(client).auto_send();
// see if the token is redacted when network error (timeout) happens
// while sending common requests
let _ = dbg!(bot.get_me().await);
// see if the token is redacted when network error (timeout) happens
// while downloading files ("path" is unimportant as the timeout is so
// low the request probably won't even be sent)
let _ = dbg!(bot.download_file_stream("path").next().await);
```
For me this gives the following result:
```text
[t.rs:26] bot.get_me().await = Err(
Network(
reqwest::Error {
kind: Request,
url: Url {
scheme: "https",
cannot_be_a_base: false,
username: "",
password: None,
host: Some(
Domain(
"api.telegram.org",
),
),
port: None,
path: "/token:redacted/GetMe",
query: None,
fragment: None,
},
source: TimedOut,
},
),
)
[t.rs:31] bot.download_file_stream("path").next().await = Some(
Err(
reqwest::Error {
kind: Request,
url: Url {
scheme: "https",
cannot_be_a_base: false,
username: "",
password: None,
host: Some(
Domain(
"api.telegram.org",
),
),
port: None,
path: "/file/token:redacted/path",
query: None,
fragment: None,
},
source: TimedOut,
},
),
)
```
Note that this commits parent is `d0be260` and not the current master
the master branch currently contains breaking changes (we'll need to
make a release from this brach directly).
[^1]: Note that there are recorded cases where the token got exposed.
[^2]: Note that this can be theoretically be controlled by the user when
sending/downloading bigger files.
This commit adds `InputFile::read` constructor that creates `InputFile`
from an `impl AsyncRead + Send + Unpin + 'static`.
Internally this requires quite a bit of work, since we need to support
cloning `InputFile`s but the `AsyncRead` trait only allows us reading it
once.
To support this, if `InputFile` detects that it's shared, it reads the
contents of the `AsyncRead` into a buffer and then shares the buffer
(or an error if it has occured).
This removes the logic in the multipart serializer that unserialized
`InputFile`s from serde. Now `InputFile`s are serialized either as
their value (for `FileId` and `Url`) or as an `attach://<id>` string
where `<id>` is replaced with some id unique for the file. The file data
itself is acquired through `MultipartPayload` trait.
Since the `<id>` must be the same while serializing the file with serde
and while acquiring data through `MultipartPayload` trait, `InputFile`
needs to store said id. As such, `InputFile` is now a structure with
private fields and it's structure can't be observed by users. The only
things that `InputFile` provides are
- Constructors (`url`, `file_id`, `file`, `memory`)
- File name setter
- `Clone` and `Debug` implementations