Merge branch 'master' into fix3782

This commit is contained in:
Sebastian Klähn
2023-01-05 18:55:47 +01:00
committed by GitHub
49 changed files with 4199 additions and 307 deletions

View File

@@ -69,7 +69,7 @@ jobs:
strategy:
matrix:
include:
# Currently used Rust version, same as in `rust-toolchain` file.
# Currently used Rust version.
- os: ubuntu-latest
rust: 1.64.0
python: 3.9

View File

@@ -59,6 +59,7 @@ jobs:
npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: '--force-node-api-uncaught-exceptions-policy=true'
- name: Run tests on Windows, except lint
timeout-minutes: 10
if: runner.os == 'Windows'
@@ -67,3 +68,4 @@ jobs:
npm run test:mocha
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: '--force-node-api-uncaught-exceptions-policy=true'

View File

@@ -16,7 +16,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.50.0
toolchain: 1.66.0
override: true
- name: build

View File

@@ -13,7 +13,6 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat --no-deps

View File

@@ -13,7 +13,6 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat_ffi --no-deps

View File

@@ -6,6 +6,12 @@
- Validate signatures in try_decrypt() even if the message isn't encrypted #3859
- Don't parse the message again after detached signatures validation #3862
- Move format=flowed support to a separate crate #3869
- cargo: bump quick-xml from 0.23.0 to 0.26.0 #3722
- Add fuzzing tests #3853
- Add mappings for some file types to Viewtype / MIME type #3881
- Buffer IMAP client writes #3888
- move `DC_CHAT_ID_ARCHIVED_LINK` to the top of chat lists
and make `dc_get_fresh_msg_cnt()` work for `DC_CHAT_ID_ARCHIVED_LINK` #3918
### API-Changes
- jsonrpc: add python API for webxdc updates #3872
@@ -15,6 +21,10 @@
- Do not add an error if the message is encrypted but not signed #3860
- Do not strip leading spaces from message lines #3867
- Don't always rebuild group member lists #3872
- Fix uncaught exception in JSON-RPC tests #3884
- Fix STARTTLS connection and add a test for it #3907
- Trigger reconnection when failing to fetch existing messages #3911
- Do not retry fetching existing messages after failure, prevents infinite reconnection loop #3913
## 1.104.0

82
Cargo.lock generated
View File

@@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "addr2line"
version = "0.17.0"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
"gimli",
]
@@ -86,9 +86,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.66"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "ascii_utils"
@@ -340,15 +340,15 @@ dependencies = [
[[package]]
name = "backtrace"
version = "0.3.66"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.5.3",
"miniz_oxide 0.6.2",
"object",
"rustc-demangle",
]
@@ -861,9 +861,9 @@ dependencies = [
[[package]]
name = "data-encoding"
version = "2.3.2"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "deltachat"
@@ -1598,9 +1598,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.26.2"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
[[package]]
name = "h2"
@@ -1752,9 +1752,9 @@ dependencies = [
[[package]]
name = "humansize"
version = "2.1.2"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e682e2bd70ecbcce5209f11a992a4ba001fea8e60acf7860ce007629e6d2756"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
@@ -2030,9 +2030,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.137"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libm"
@@ -2100,9 +2100,9 @@ dependencies = [
[[package]]
name = "mailparse"
version = "0.13.8"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cae768a50835557749599277fc59f7c728118724eb34185e8feb633ef266a32"
checksum = "6b56570f5f8c0047260d1c8b5b331f62eb9c660b9dd4071a8c46f8c7d3f280aa"
dependencies = [
"charset",
"data-encoding",
@@ -2336,28 +2336,28 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.14.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi 0.1.19",
"hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "object"
version = "0.29.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "oorandom"
@@ -2739,27 +2739,27 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.23.0"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9279fbdacaad3baf559d8cabe0acc3d06e30ea14931af31af79578ac0946decc"
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "quoted_printable"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f"
checksum = "20f14e071918cbeefc5edc986a7aa92c425dae244e003a35e1cdddb5ca39b5cb"
[[package]]
name = "r2d2"
@@ -3413,9 +3413,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.105"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
@@ -3482,18 +3482,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
@@ -3538,9 +3538,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.22.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
"autocfg",
"bytes",
@@ -3553,7 +3553,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
"windows-sys 0.42.0",
]
[[package]]
@@ -3641,9 +3641,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.9"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
]

View File

@@ -44,16 +44,16 @@ kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
log = {version = "0.4.16", optional = true }
mailparse = "0.13"
mailparse = "0.14"
native-tls = "0.2"
num_cpus = "1.14"
num_cpus = "1.15"
num-derive = "0.3"
num-traits = "0.2"
once_cell = "1.16.0"
once_cell = "1.17.0"
percent-encoding = "2.2"
pgp = { version = "0.9", default-features = false }
pretty_env_logger = { version = "0.4", optional = true }
quick-xml = "0.23"
quick-xml = "0.26"
r2d2 = "0.8"
r2d2_sqlite = "0.20"
rand = "0.8"

View File

@@ -115,6 +115,29 @@ use the `--ignored` argument to the test binary (not to cargo itself):
$ cargo test -- --ignored
```
### Fuzzing
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
```sh
$ cargo install cargo-bolero
```
Run fuzzing tests with
```sh
$ cd fuzz
$ cargo bolero test fuzz_mailparse --release=false -s NONE
```
Corpus is created at `fuzz/fuzz_targets/corpus`,
you can add initial inputs there.
For `fuzz_mailparse` target corpus can be populated with
`../test-data/message/*.eml`.
To run with AFL instead of libFuzzer:
```sh
$ cargo bolero test fuzz_format_flowed --release=false -e afl -s NONE
```
## Features
- `vendored`: When using Openssl for TLS, this bundles a vendored version.

BIN
assets/icon-archive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

60
assets/icon-archive.svg Normal file
View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="60"
height="60"
viewBox="0 0 60 60"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-archive"
version="1.1"
id="svg8"
sodipodi:docname="icon-archive.svg"
inkscape:version="1.2.2 (b0a84865, 2022-12-01)"
inkscape:export-filename="icon-archive.png"
inkscape:export-xdpi="409.60001"
inkscape:export-ydpi="409.60001"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="6.4597151"
inkscape:cx="24.459283"
inkscape:cy="32.509174"
inkscape:window-width="1457"
inkscape:window-height="860"
inkscape:window-x="55"
inkscape:window-y="38"
inkscape:window-maximized="0"
inkscape:current-layer="svg8" />
<g
id="g846"
transform="translate(0.558605,0.464417)">
<path
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
d="M 38.749006,25.398867 V 38.843194 H 20.133784 V 25.398867"
id="path847" />
<path
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
d="m 18.065427,20.227972 h 22.751936 v 5.170894 H 18.065427 Z"
id="path845" />
<path
style="fill:#ff0000;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
d="m 27.373036,29.535581 h 4.136718"
id="line6" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -24,7 +24,7 @@ tokio = { version = "1", features = ["rt-multi-thread"] }
anyhow = "1"
thiserror = "1"
rand = "0.7"
once_cell = "1.16.0"
once_cell = "1.17.0"
[features]
default = ["vendored"]

View File

@@ -1227,7 +1227,11 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch
* Get the number of _fresh_ messages in a chat.
* Typically used to implement a badge with a number in the chatlist.
*
* If the specified chat is muted,
* As muted archived chats are not unarchived automatically,
* a similar information is needed for the @ref dc_get_chatlist() "archive link" as well:
* here, the number of archived chats containing fresh messages is returned.
*
* If the specified chat is muted or the @ref dc_get_chatlist() "archive link",
* the UI should show the badge counter "less obtrusive",
* e.g. using "gray" instead of "red" color.
*
@@ -4763,7 +4767,7 @@ char* dc_contact_get_verifier_addr (dc_contact_t* contact);
* we verified the contact ourself. If it is 0, we don't have verifier information or
* the contact is not verified.
*/
int dc_contact_get_verifier_id (dc_contact_t* contact);
uint32_t dc_contact_get_verifier_id (dc_contact_t* contact);
/**

View File

@@ -3983,10 +3983,10 @@ pub unsafe extern "C" fn dc_contact_get_verifier_addr(
}
#[no_mangle]
pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t) -> libc::c_int {
pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t) -> u32 {
if contact.is_null() {
eprintln!("ignoring careless call to dc_contact_get_verifier_id()");
return 0 as libc::c_int;
return 0;
}
let ffi_contact = &*contact;
let ctx = &*ffi_contact.context;
@@ -3995,7 +3995,7 @@ pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t)
.unwrap_or_default()
.unwrap_or_default();
contact_id.to_u32() as libc::c_int
contact_id.to_u32()
}
// dc_lot_t

View File

@@ -23,7 +23,7 @@ futures = { version = "0.3.25" }
serde_json = "1.0.89"
yerpc = { version = "^0.3.1", features = ["anyhow_expose"] }
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
tokio = { version = "1.22.0" }
tokio = { version = "1.23.0" }
sanitize-filename = "0.4"
walkdir = "2.3.2"
@@ -32,7 +32,7 @@ axum = { version = "0.6.1", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
tokio = { version = "1.22.0", features = ["full", "rt-multi-thread"] }
tokio = { version = "1.23.0", features = ["full", "rt-multi-thread"] }
[features]

View File

@@ -27,12 +27,17 @@ class ACFactory:
async def get_unconfigured_bot(self) -> Bot:
return Bot(await self.get_unconfigured_account())
async def new_configured_account(self) -> Account:
async def new_preconfigured_account(self) -> Account:
"""Make a new account with configuration options set, but configuration not started."""
credentials = await get_temp_credentials()
account = await self.get_unconfigured_account()
assert not await account.is_configured()
await account.set_config("addr", credentials["email"])
await account.set_config("mail_pw", credentials["password"])
assert not await account.is_configured()
return account
async def new_configured_account(self) -> Account:
account = await self.new_preconfigured_account()
await account.configure()
assert await account.is_configured()
return account

View File

@@ -41,6 +41,16 @@ async def test_acfactory(acfactory) -> None:
print("Successful configuration")
@pytest.mark.asyncio
async def test_configure_starttls(acfactory) -> None:
account = await acfactory.new_preconfigured_account()
# Use STARTTLS
await account.set_config("mail_security", "2")
await account.configure()
assert await account.is_configured()
@pytest.mark.asyncio
async def test_account(acfactory) -> None:
alice, bob = await acfactory.get_online_accounts(2)

View File

@@ -21,5 +21,5 @@ futures-lite = "1.12.0"
log = "0.4"
serde_json = "1.0.89"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.22.0", features = ["io-std"] }
tokio = { version = "1.23.0", features = ["io-std"] }
yerpc = { version = "0.3.1", features = ["anyhow_expose"] }

View File

@@ -182,6 +182,12 @@ mod tests {
let text = " Foo bar baz";
assert_eq!(format_flowed(text), " Foo bar baz");
let text =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAA";
let expected =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \r\nAAAAAA";
assert_eq!(format_flowed(text), expected);
}
#[test]

3441
fuzz/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

36
fuzz/Cargo.toml Normal file
View File

@@ -0,0 +1,36 @@
[package]
name = "deltachat-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[dev-dependencies]
bolero = "0.8"
[dependencies]
mailparse = "0.13"
deltachat = { path = ".." }
format-flowed = { path = "../format-flowed" }
[workspace]
members = ["."]
[[test]]
name = "fuzz_dateparse"
path = "fuzz_targets/fuzz_dateparse.rs"
harness = false
[[test]]
name = "fuzz_simplify"
path = "fuzz_targets/fuzz_simplify.rs"
harness = false
[[test]]
name = "fuzz_mailparse"
path = "fuzz_targets/fuzz_mailparse.rs"
harness = false
[[test]]
name = "fuzz_format_flowed"
path = "fuzz_targets/fuzz_format_flowed.rs"
harness = false

View File

@@ -0,0 +1,10 @@
use bolero::check;
fn main() {
check!().for_each(|data: &[u8]| match std::str::from_utf8(data) {
Ok(input) => {
mailparse::dateparse(input).ok();
}
Err(_err) => {}
});
}

View File

@@ -0,0 +1,25 @@
use bolero::check;
use format_flowed::{format_flowed, unformat_flowed};
fn round_trip(input: &str) -> String {
let mut input = format_flowed(input);
input.retain(|c| c != '\r');
unformat_flowed(&input, false)
}
fn main() {
check!().for_each(|data: &[u8]| {
if let Ok(input) = std::str::from_utf8(data.into()) {
let mut input = input.to_string();
// Only consider inputs that don't contain quotes.
input.retain(|c| c != '>');
// Only consider inputs that are the result of unformatting format=flowed text.
// At least this means that lines don't contain any trailing whitespace.
let input = round_trip(&input);
let output = round_trip(&input);
assert_eq!(input, output);
}
});
}

View File

@@ -0,0 +1,7 @@
use bolero::check;
fn main() {
check!().for_each(|data: &[u8]| {
mailparse::parse_mail(data).ok();
});
}

View File

@@ -0,0 +1,13 @@
use bolero::check;
use deltachat::fuzzing::simplify;
fn main() {
check!().for_each(|data: &[u8]| match String::from_utf8(data.to_vec()) {
Ok(input) => {
simplify(input.clone(), true);
simplify(input, false);
}
Err(_err) => {}
});
}

View File

@@ -11,6 +11,7 @@ import { mkdtempSync, statSync } from 'fs'
import { tmpdir } from 'os'
import { Context } from '../dist/context'
chai.use(chaiAsPromised)
chai.config.truncateThreshold = 0; // Do not truncate assertion errors.
async function createTempUser(url) {
const fetch = require('node-fetch')
@@ -121,7 +122,7 @@ describe('JSON RPC', function () {
const promises = {}
dc.startJsonRpcHandler((msg) => {
const response = JSON.parse(msg)
promises[response.id](response)
if (response.hasOwnProperty('id')) promises[response.id](response)
delete promises[response.id]
})
const call = (request) => {

2
python/tests/data/r Normal file
View File

@@ -0,0 +1,2 @@
hello

View File

@@ -450,24 +450,25 @@ class TestOfflineChat:
assert msg.filemime == "image/png"
@pytest.mark.parametrize(
"typein,typeout",
"fn,typein,typeout",
[
(None, "application/octet-stream"),
("text/plain", "text/plain"),
("image/png", "image/png"),
("r", None, "application/octet-stream"),
("r.txt", None, "text/plain"),
("r.txt", "text/plain", "text/plain"),
("r.txt", "image/png", "image/png"),
],
)
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
def test_message_file(self, ac1, chat1, data, lp, fn, typein, typeout):
lp.sec("sending file")
fn = data.get_path("r.txt")
msg = chat1.send_file(fn, typein)
fp = data.get_path(fn)
msg = chat1.send_file(fp, typein)
assert msg
assert msg.id > 0
assert msg.is_file()
assert os.path.exists(msg.filename)
assert msg.filename.endswith(msg.basename)
assert msg.filemime == typeout
msg2 = chat1.send_file(fn, typein)
msg2 = chat1.send_file(fp, typein)
assert msg2 != msg
assert msg2.filename != msg.filename

View File

@@ -1 +0,0 @@
1.64.0

View File

@@ -782,17 +782,35 @@ impl ChatId {
// the times are average, no matter if there are fresh messages or not -
// and have to be multiplied by the number of items shown at once on the chatlist,
// so savings up to 2 seconds are possible on older devices - newer ones will feel "snappier" :)
let count = context
.sql
.count(
"SELECT COUNT(*)
let count = if self.is_archived_link() {
context
.sql
.count(
"SELECT COUNT(DISTINCT(m.chat_id))
FROM msgs m
LEFT JOIN chats c ON m.chat_id=c.id
WHERE m.state=10
and m.hidden=0
AND m.chat_id>9
AND c.blocked=0
AND c.archived=1
",
paramsv![],
)
.await?
} else {
context
.sql
.count(
"SELECT COUNT(*)
FROM msgs
WHERE state=?
AND hidden=0
AND chat_id=?;",
paramsv![MessageState::InFresh, self],
)
.await?;
paramsv![MessageState::InFresh, self],
)
.await?
};
Ok(count)
}
@@ -1216,6 +1234,10 @@ impl Chat {
if !image_rel.is_empty() {
return Ok(Some(get_abs_path(context, image_rel)));
}
} else if self.id.is_archived_link() {
if let Ok(image_rel) = get_archive_icon(context).await {
return Ok(Some(get_abs_path(context, image_rel)));
}
} else if self.typ == Chattype::Single {
let contacts = get_chat_contacts(context, self.id).await?;
if let Some(contact_id) = contacts.first() {
@@ -1710,6 +1732,21 @@ pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
Ok(icon)
}
pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
if let Some(icon) = context.sql.get_raw_config("icon-archive").await? {
return Ok(icon);
}
let icon = include_bytes!("../assets/icon-archive.png");
let blob = BlobObject::create(context, "icon-archive.png", icon).await?;
let icon = blob.as_name().to_string();
context
.sql
.set_raw_config("icon-archive", Some(&icon))
.await?;
Ok(icon)
}
async fn update_special_chat_name(
context: &Context,
contact_id: ContactId,

View File

@@ -92,8 +92,6 @@ impl Chatlist {
let flag_no_specials = 0 != listflags & DC_GCL_NO_SPECIALS;
let flag_add_alldone_hint = 0 != listflags & DC_GCL_ADD_ALLDONE_HINT;
let mut add_archived_link_item = false;
let process_row = |row: &rusqlite::Row| {
let chat_id: ChatId = row.get(0)?;
let msg_id: Option<MsgId> = row.get(1)?;
@@ -123,7 +121,7 @@ impl Chatlist {
//
// The query shows messages from blocked contacts in
// groups. Otherwise it would be hard to follow conversations.
let mut ids = if let Some(query_contact_id) = query_contact_id {
let ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
"SELECT c.id, m.id
@@ -216,7 +214,7 @@ impl Chatlist {
} else {
ChatId::new(0)
};
let ids = context.sql.query_map(
let mut ids = context.sql.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
@@ -236,19 +234,15 @@ impl Chatlist {
process_row,
process_rows,
).await?;
if !flag_no_specials {
add_archived_link_item = true;
if !flag_no_specials && get_archived_cnt(context).await? > 0 {
if ids.is_empty() && flag_add_alldone_hint {
ids.push((DC_CHAT_ID_ALLDONE_HINT, None));
}
ids.insert(0, (DC_CHAT_ID_ARCHIVED_LINK, None));
}
ids
};
if add_archived_link_item && get_archived_cnt(context).await? > 0 {
if ids.is_empty() && flag_add_alldone_hint {
ids.push((DC_CHAT_ID_ALLDONE_HINT, None));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, None));
}
Ok(Chatlist { ids })
}

View File

@@ -579,10 +579,10 @@ async fn try_imap_one_param(
let mut imap = match Imap::new(param, socks5_config.clone(), addr, provider_strict_tls, r) {
Err(err) => {
info!(context, "failure: {}", err);
info!(context, "failure: {:#}", err);
return Err(ConfigurationError {
config: inf,
msg: err.to_string(),
msg: format!("{:#}", err),
});
}
Ok(imap) => imap,
@@ -590,10 +590,10 @@ async fn try_imap_one_param(
match imap.connect(context).await {
Err(err) => {
info!(context, "failure: {}", err);
info!(context, "failure: {:#}", err);
Err(ConfigurationError {
config: inf,
msg: err.to_string(),
msg: format!("{:#}", err),
})
}
Ok(()) => {
@@ -634,7 +634,7 @@ async fn try_smtp_one_param(
info!(context, "failure: {}", err);
Err(ConfigurationError {
config: inf,
msg: err.to_string(),
msg: format!("{:#}", err),
})
} else {
info!(context, "success: {}", inf);

View File

@@ -62,7 +62,7 @@ fn parse_server<B: BufRead>(
reader: &mut quick_xml::Reader<B>,
server_event: &BytesStart,
) -> Result<Option<Server>, quick_xml::Error> {
let end_tag = String::from_utf8_lossy(server_event.name())
let end_tag = String::from_utf8_lossy(server_event.name().as_ref())
.trim()
.to_lowercase();
@@ -70,12 +70,17 @@ fn parse_server<B: BufRead>(
.attributes()
.find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
.map(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.to_lowercase()
== "type"
})
.unwrap_or_default()
})
.map(|typ| {
typ.unwrap()
.unescape_and_decode_value(reader)
.decode_and_unescape_value(reader)
.unwrap_or_default()
.to_lowercase()
})
@@ -89,25 +94,23 @@ fn parse_server<B: BufRead>(
let mut tag_config = MozConfigTag::Undefined;
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref event) => {
tag_config = String::from_utf8_lossy(event.name())
tag_config = String::from_utf8_lossy(event.name().as_ref())
.parse()
.unwrap_or_default();
}
Event::End(ref event) => {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == end_tag {
break;
}
}
Event::Text(ref event) => {
let val = event
.unescape_and_decode(reader)
.unwrap_or_default()
.trim()
.to_owned();
let val = event.unescape().unwrap_or_default().trim().to_owned();
match tag_config {
MozConfigTag::Hostname => hostname = Some(val),
@@ -150,9 +153,11 @@ fn parse_xml_reader<B: BufRead>(
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref event) => {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "incomingserver" {
if let Some(incoming_server) = parse_server(reader, event)? {

View File

@@ -59,12 +59,18 @@ fn parse_protocol<B: BufRead>(
let mut current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref event) => {
current_tag = Some(String::from_utf8_lossy(event.name()).trim().to_lowercase());
current_tag = Some(
String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase(),
);
}
Event::End(ref event) => {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "protocol" {
break;
}
@@ -73,7 +79,7 @@ fn parse_protocol<B: BufRead>(
}
}
Event::Text(ref e) => {
let val = e.unescape_and_decode(reader).unwrap_or_default();
let val = e.unescape().unwrap_or_default();
if let Some(ref tag) = current_tag {
match tag.as_str() {
@@ -115,9 +121,9 @@ fn parse_redirecturl<B: BufRead>(
reader: &mut quick_xml::Reader<B>,
) -> Result<String, quick_xml::Error> {
let mut buf = Vec::new();
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Text(ref e) => {
let val = e.unescape_and_decode(reader).unwrap_or_default();
let val = e.unescape().unwrap_or_default();
Ok(val.trim().to_string())
}
_ => Ok("".to_string()),
@@ -131,9 +137,11 @@ fn parse_xml_reader<B: BufRead>(
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref e) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(e.name().as_ref())
.trim()
.to_lowercase();
if tag == "protocol" {
if let Some(protocol) = parse_protocol(reader)? {

View File

@@ -88,18 +88,30 @@ fn dehtml_quick_xml(buf: &str) -> String {
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
match reader.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
dehtml_starttag_cb(e, &mut dehtml, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => dehtml_endtag_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::Text(ref e)) => dehtml_text_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::CData(e)) => dehtml_text_cb(&e.escape(), &mut dehtml),
Ok(quick_xml::events::Event::CData(e)) => match e.escape() {
Ok(e) => dehtml_text_cb(&e, &mut dehtml),
Err(e) => {
eprintln!(
"CDATA escape error at position {}: {:?}",
reader.buffer_position(),
e,
);
}
},
Ok(quick_xml::events::Event::Empty(ref e)) => {
// Handle empty tags as a start tag immediately followed by end tag.
// For example, `<p/>` is treated as `<p></p>`.
dehtml_starttag_cb(e, &mut dehtml, &reader);
dehtml_endtag_cb(&BytesEnd::borrowed(e.name()), &mut dehtml);
dehtml_endtag_cb(
&BytesEnd::new(String::from_utf8_lossy(e.name().as_ref())),
&mut dehtml,
);
}
Err(e) => {
eprintln!(
@@ -121,7 +133,7 @@ fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
if dehtml.get_add_text() == AddText::YesPreserveLineEnds
|| dehtml.get_add_text() == AddText::YesRemoveLineEnds
{
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
let last_added = escaper::decode_html_buf_sloppy(event as &[_]).unwrap_or_default();
if dehtml.get_add_text() == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
@@ -135,7 +147,9 @@ fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
}
fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
match tag.as_str() {
"p" | "table" | "td" | "style" | "script" | "title" | "pre" => {
@@ -176,7 +190,9 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
dehtml: &mut Dehtml,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
match tag.as_str() {
"p" | "table" | "td" => {
@@ -206,10 +222,15 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
if let Some(href) = event
.html_attributes()
.filter_map(|attr| attr.ok())
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "href")
.find(|attr| {
String::from_utf8_lossy(attr.key.as_ref())
.trim()
.to_lowercase()
== "href"
})
{
let href = href
.unescape_and_decode_value(reader)
.decode_and_unescape_value(reader)
.unwrap_or_default()
.to_lowercase();
@@ -258,7 +279,7 @@ fn maybe_push_tag(
fn tag_contains_attr(event: &BytesStart, reader: &Reader<impl BufRead>, name: &str) -> bool {
event.attributes().any(|r| {
r.map(|a| {
a.unescape_and_decode_value(reader)
a.decode_and_unescape_value(reader)
.map(|v| v == name)
.unwrap_or(false)
})

View File

@@ -1,7 +1,5 @@
//! # Download large messages manually.
#![allow(missing_docs)]
use anyhow::{anyhow, Result};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
@@ -33,6 +31,7 @@ const MIN_DOWNLOAD_LIMIT: u32 = 32768;
/// `MIN_DELETE_SERVER_AFTER` increases the timeout in this case.
pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
/// Download state of the message.
#[derive(
Debug,
Display,
@@ -49,9 +48,16 @@ pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
)]
#[repr(u32)]
pub enum DownloadState {
/// Message is fully downloaded.
Done = 0,
/// Message is partially downloaded and can be fully downloaded at request.
Available = 10,
/// Failed to fully download the message.
Failure = 20,
/// Full download of the message is in progress.
InProgress = 1000,
}

12
src/fuzzing.rs Normal file
View File

@@ -0,0 +1,12 @@
//! # Fuzzing module.
//!
//! This module exposes private APIs for fuzzing.
/// Fuzzing target for simplify().
///
/// Calls simplify() and panics if simplify() panics.
/// Does not return any vaule to avoid exposing internal crate types.
#[cfg(fuzzing)]
pub fn simplify(input: String, is_chat_message: bool) {
crate::simplify::simplify(input, is_chat_message);
}

View File

@@ -297,6 +297,7 @@ impl Imap {
let oauth2 = self.config.lp.oauth2;
info!(context, "Connecting to IMAP server");
let connection_res: Result<Client> = if self.config.lp.security == Socket::Starttls
|| self.config.lp.security == Socket::Plain
{
@@ -304,22 +305,23 @@ impl Imap {
let imap_server: &str = config.lp.server.as_ref();
let imap_port = config.lp.port;
let connection = if let Some(socks5_config) = &config.socks5_config {
Client::connect_insecure_socks5((imap_server, imap_port), socks5_config.clone())
if let Some(socks5_config) = &config.socks5_config {
if config.lp.security == Socket::Starttls {
Client::connect_starttls_socks5(
imap_server,
imap_port,
socks5_config.clone(),
config.strict_tls,
)
.await
} else {
Client::connect_insecure_socks5((imap_server, imap_port), socks5_config.clone())
.await
}
} else if config.lp.security == Socket::Starttls {
Client::connect_starttls(imap_server, imap_port, config.strict_tls).await
} else {
Client::connect_insecure((imap_server, imap_port)).await
};
match connection {
Ok(client) => {
if config.lp.security == Socket::Starttls {
client.secure(imap_server, config.strict_tls).await
} else {
Ok(client)
}
}
Err(err) => Err(err),
}
} else {
let config = &self.config;
@@ -328,8 +330,8 @@ impl Imap {
if let Some(socks5_config) = &config.socks5_config {
Client::connect_secure_socks5(
(imap_server, imap_port),
imap_server,
imap_port,
config.strict_tls,
socks5_config.clone(),
)
@@ -345,6 +347,7 @@ impl Imap {
let imap_pw: &str = config.lp.password.as_ref();
let login_res = if oauth2 {
info!(context, "Logging into IMAP server with OAuth 2");
let addr: &str = config.addr.as_ref();
let token = get_oauth2_access_token(context, addr, imap_pw, true)
@@ -356,6 +359,7 @@ impl Imap {
};
client.authenticate("XOAUTH2", auth).await
} else {
info!(context, "Logging into IMAP server with LOGIN");
client.login(imap_user, imap_pw).await
};
@@ -371,6 +375,7 @@ impl Imap {
"IMAP-LOGIN as {}",
self.config.lp.user
)));
info!(context, "Successfully logged into IMAP server");
Ok(())
}
@@ -378,7 +383,7 @@ impl Imap {
let imap_user = self.config.lp.user.to_owned();
let message = stock_str::cannot_login(context, &imap_user).await;
warn!(context, "{} ({})", message, err);
warn!(context, "{} ({:#})", message, err);
let lock = context.wrong_pw_warning_mutex.lock().await;
if self.login_failed_once
@@ -386,7 +391,7 @@ impl Imap {
&& context.get_config_bool(Config::NotifyAboutWrongPw).await?
{
if let Err(e) = context.set_config(Config::NotifyAboutWrongPw, None).await {
warn!(context, "{}", e);
warn!(context, "{:#}", e);
}
drop(lock);
@@ -396,13 +401,13 @@ impl Imap {
chat::add_device_msg_with_importance(context, None, Some(&mut msg), true)
.await
{
warn!(context, "{}", e);
warn!(context, "{:#}", e);
}
} else {
self.login_failed_once = true;
}
Err(format_err!("{}\n\n{}", message, err))
Err(format_err!("{}\n\n{:#}", message, err))
}
}
}
@@ -672,7 +677,10 @@ impl Imap {
return Ok(false);
}
let new_emails = self.select_with_uidvalidity(context, folder).await?;
let new_emails = self
.select_with_uidvalidity(context, folder)
.await
.with_context(|| format!("failed to select folder {}", folder))?;
if !new_emails && !fetch_existing_msgs {
info!(context, "No new emails in folder {}", folder);
@@ -832,9 +840,15 @@ impl Imap {
}
self.prepare(context).await.context("could not connect")?;
add_all_recipients_as_contacts(context, self, Config::ConfiguredSentboxFolder).await;
add_all_recipients_as_contacts(context, self, Config::ConfiguredMvboxFolder).await;
add_all_recipients_as_contacts(context, self, Config::ConfiguredInboxFolder).await;
add_all_recipients_as_contacts(context, self, Config::ConfiguredSentboxFolder)
.await
.context("failed to get recipients from the sentbox")?;
add_all_recipients_as_contacts(context, self, Config::ConfiguredMvboxFolder)
.await
.context("failed to ge recipients from the movebox")?;
add_all_recipients_as_contacts(context, self, Config::ConfiguredInboxFolder)
.await
.context("failed to get recipients from the inbox")?;
if context.get_config_bool(Config::FetchExistingMsgs).await? {
for config in &[
@@ -843,17 +857,18 @@ impl Imap {
Config::ConfiguredSentboxFolder,
] {
if let Some(folder) = context.get_config(*config).await? {
info!(
context,
"Fetching existing messages from folder \"{}\"", folder
);
self.fetch_new_messages(context, &folder, false, true)
.await
.context("could not fetch messages")?;
.context("could not fetch existing messages")?;
}
}
}
info!(context, "Done fetching existing messages.");
context
.set_config_bool(Config::FetchedExistingMsgs, true)
.await?;
Ok(())
}
}
@@ -1202,8 +1217,10 @@ impl Imap {
/// Prefetch all messages greater than or equal to `uid_next`. Returns a list of fetch results
/// in the order of ascending delivery time to the server (INTERNALDATE).
async fn prefetch(&mut self, uid_next: u32) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
let session = self.session.as_mut();
let session = session.context("fetch_after(): IMAP No Connection established")?;
let session = self
.session
.as_mut()
.context("no IMAP connection established")?;
// fetch messages with larger UID than the last one seen
let set = format!("{}:*", uid_next);
@@ -1228,7 +1245,6 @@ impl Imap {
}
}
}
drop(list);
Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
}
@@ -2301,49 +2317,58 @@ impl std::fmt::Display for UidRange {
}
}
}
async fn add_all_recipients_as_contacts(context: &Context, imap: &mut Imap, folder: Config) {
let mailbox = if let Ok(Some(m)) = context.get_config(folder).await {
async fn add_all_recipients_as_contacts(
context: &Context,
imap: &mut Imap,
folder: Config,
) -> Result<()> {
let mailbox = if let Some(m) = context.get_config(folder).await? {
m
} else {
return;
info!(
context,
"Folder {} is not configured, skipping fetching contacts from it.", folder
);
return Ok(());
};
if let Err(e) = imap.select_with_uidvalidity(context, &mailbox).await {
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
warn!(context, "Could not select {}: {:#}", mailbox, e);
return;
}
match imap.get_all_recipients(context).await {
Ok(contacts) => {
let mut any_modified = false;
for contact in contacts {
let display_name_normalized = contact
.display_name
.as_ref()
.map(|s| normalize_name(s))
.unwrap_or_default();
imap.select_with_uidvalidity(context, &mailbox)
.await
.with_context(|| format!("could not select {}", mailbox))?;
match Contact::add_or_lookup(
context,
&display_name_normalized,
&contact.addr,
Origin::OutgoingTo,
)
.await
{
Ok((_, modified)) => {
if modified != Modifier::None {
any_modified = true;
}
}
Err(e) => warn!(context, "Could not add recipient: {}", e),
let contacts = imap
.get_all_recipients(context)
.await
.context("could not get recipients")?;
let mut any_modified = false;
for contact in contacts {
let display_name_normalized = contact
.display_name
.as_ref()
.map(|s| normalize_name(s))
.unwrap_or_default();
match Contact::add_or_lookup(
context,
&display_name_normalized,
&contact.addr,
Origin::OutgoingTo,
)
.await
{
Ok((_, modified)) => {
if modified != Modifier::None {
any_modified = true;
}
}
if any_modified {
context.emit_event(EventType::ContactsChanged(None));
}
Err(e) => warn!(context, "Could not add recipient: {}", e),
}
Err(e) => warn!(context, "Could not add recipients: {}", e),
};
}
if any_modified {
context.emit_event(EventType::ContactsChanged(None));
}
Ok(())
}
#[cfg(test)]

View File

@@ -8,13 +8,13 @@ use anyhow::{Context as _, Result};
use async_imap::Client as ImapClient;
use async_imap::Session as ImapSession;
use tokio::net::{self, TcpStream};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
use tokio::io::BufWriter;
use tokio::net::ToSocketAddrs;
use super::capabilities::Capabilities;
use super::session::Session;
use crate::login_param::build_tls;
use crate::net::connect_tcp;
use crate::socks::Socks5Config;
use super::session::SessionStream;
@@ -24,7 +24,6 @@ pub(crate) const IMAP_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug)]
pub(crate) struct Client {
is_secure: bool,
inner: ImapClient<Box<dyn SessionStream>>,
}
@@ -93,108 +92,131 @@ impl Client {
}
pub async fn connect_secure(hostname: &str, port: u16, strict_tls: bool) -> Result<Self> {
let tcp_stream = timeout(IMAP_TIMEOUT, TcpStream::connect((hostname, port))).await??;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(IMAP_TIMEOUT));
timeout_stream.set_read_timeout(Some(IMAP_TIMEOUT));
let timeout_stream = Box::pin(timeout_stream);
let tcp_stream = connect_tcp((hostname, port), IMAP_TIMEOUT).await?;
let tls = build_tls(strict_tls);
let tls_stream: Box<dyn SessionStream> =
Box::new(tls.connect(hostname, timeout_stream).await?);
let mut client = ImapClient::new(tls_stream);
let tls_stream = tls.connect(hostname, tcp_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: true,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn connect_insecure(addr: impl net::ToSocketAddrs) -> Result<Self> {
let tcp_stream = timeout(IMAP_TIMEOUT, TcpStream::connect(addr)).await??;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(IMAP_TIMEOUT));
timeout_stream.set_read_timeout(Some(IMAP_TIMEOUT));
let timeout_stream = Box::pin(timeout_stream);
let stream: Box<dyn SessionStream> = Box::new(timeout_stream);
let mut client = ImapClient::new(stream);
pub async fn connect_insecure(addr: impl ToSocketAddrs) -> Result<Self> {
let tcp_stream = connect_tcp(addr, IMAP_TIMEOUT).await?;
let buffered_stream = BufWriter::new(tcp_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: false,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn connect_starttls(hostname: &str, port: u16, strict_tls: bool) -> Result<Self> {
let tcp_stream = connect_tcp((hostname, port), IMAP_TIMEOUT).await?;
// Run STARTTLS command and convert the client back into a stream.
let session_stream: Box<dyn SessionStream> = Box::new(tcp_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")??;
client
.run_command_and_check_ok("STARTTLS", None)
.await
.context("STARTTLS command failed")?;
let tcp_stream = client.into_inner();
let tls = build_tls(strict_tls);
let tls_stream = tls
.connect(hostname, tcp_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let client = ImapClient::new(session_stream);
Ok(Client { inner: client })
}
pub async fn connect_secure_socks5(
target_addr: impl net::ToSocketAddrs,
domain: &str,
port: u16,
strict_tls: bool,
socks5_config: Socks5Config,
) -> Result<Self> {
let socks5_stream: Box<dyn SessionStream> =
Box::new(socks5_config.connect(target_addr, IMAP_TIMEOUT).await?);
let socks5_stream = socks5_config.connect((domain, port), IMAP_TIMEOUT).await?;
let tls = build_tls(strict_tls);
let tls_stream: Box<dyn SessionStream> =
Box::new(tls.connect(domain, socks5_stream).await?);
let mut client = ImapClient::new(tls_stream);
let tls_stream = tls.connect(domain, socks5_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: true,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn connect_insecure_socks5(
target_addr: impl net::ToSocketAddrs,
target_addr: impl ToSocketAddrs,
socks5_config: Socks5Config,
) -> Result<Self> {
let socks5_stream: Box<dyn SessionStream> =
Box::new(socks5_config.connect(target_addr, IMAP_TIMEOUT).await?);
let mut client = ImapClient::new(socks5_stream);
let socks5_stream = socks5_config.connect(target_addr, IMAP_TIMEOUT).await?;
let buffered_stream = BufWriter::new(socks5_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: false,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn secure(self, domain: &str, strict_tls: bool) -> Result<Self> {
if self.is_secure {
Ok(self)
} else {
let Client { mut inner, .. } = self;
let tls = build_tls(strict_tls);
inner.run_command_and_check_ok("STARTTLS", None).await?;
pub async fn connect_starttls_socks5(
hostname: &str,
port: u16,
socks5_config: Socks5Config,
strict_tls: bool,
) -> Result<Self> {
let socks5_stream = socks5_config
.connect((hostname, port), IMAP_TIMEOUT)
.await?;
let stream = inner.into_inner();
let ssl_stream = tls.connect(domain, stream).await?;
let boxed: Box<dyn SessionStream> = Box::new(ssl_stream);
// Run STARTTLS command and convert the client back into a stream.
let session_stream: Box<dyn SessionStream> = Box::new(socks5_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")??;
client
.run_command_and_check_ok("STARTTLS", None)
.await
.context("STARTTLS command failed")?;
let socks5_stream = client.into_inner();
Ok(Client {
is_secure: true,
inner: ImapClient::new(boxed),
})
}
let tls = build_tls(strict_tls);
let tls_stream = tls
.connect(hostname, socks5_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let client = ImapClient::new(session_stream);
Ok(Client { inner: client })
}
}

View File

@@ -6,6 +6,7 @@ use async_imap::types::Mailbox;
use async_imap::Session as ImapSession;
use async_native_tls::TlsStream;
use fast_socks5::client::Socks5Stream;
use tokio::io::BufWriter;
use tokio::net::TcpStream;
use tokio_io_timeout::TimeoutStream;
@@ -33,12 +34,17 @@ pub(crate) trait SessionStream:
fn set_read_timeout(&mut self, timeout: Option<Duration>);
}
impl SessionStream for TlsStream<Box<dyn SessionStream>> {
impl SessionStream for Box<dyn SessionStream> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.as_mut().set_read_timeout(timeout);
}
}
impl<T: SessionStream> SessionStream for TlsStream<T> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_mut().set_read_timeout(timeout);
}
}
impl SessionStream for TlsStream<Pin<Box<TimeoutStream<TcpStream>>>> {
impl<T: SessionStream> SessionStream for BufWriter<T> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_mut().set_read_timeout(timeout);
}
@@ -48,7 +54,7 @@ impl SessionStream for Pin<Box<TimeoutStream<TcpStream>>> {
self.as_mut().set_read_timeout_pinned(timeout);
}
}
impl SessionStream for Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>> {
impl<T: SessionStream> SessionStream for Socks5Stream<T> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_socket_mut().set_read_timeout(timeout)
}

View File

@@ -100,6 +100,7 @@ mod dehtml;
mod authres;
mod color;
pub mod html;
mod net;
pub mod plaintext;
mod ratelimit;
pub mod summary;
@@ -117,3 +118,6 @@ pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG";
mod test_utils;
#[cfg(test)]
mod tests;
#[cfg(fuzzing)]
pub mod fuzzing;

View File

@@ -1,7 +1,5 @@
//! Location handling.
#![allow(missing_docs)]
use std::convert::TryFrom;
use std::time::Duration;
@@ -20,32 +18,63 @@ use crate::mimeparser::SystemMessage;
use crate::stock_str;
use crate::tools::{duration_to_str, time};
/// Location record
/// Location record.
#[derive(Debug, Clone, Default)]
pub struct Location {
/// Row ID of the location.
pub location_id: u32,
/// Location latitude.
pub latitude: f64,
/// Location longitude.
pub longitude: f64,
/// Nonstandard `accuracy` attribute of the `coordinates` tag.
pub accuracy: f64,
/// Location timestamp in seconds.
pub timestamp: i64,
/// Contact ID.
pub contact_id: ContactId,
/// Message ID.
pub msg_id: u32,
/// Chat ID.
pub chat_id: ChatId,
/// A marker string, such as an emoji, to be displayed on top of the location.
pub marker: Option<String>,
/// Whether location is independent, i.e. not part of the path.
pub independent: u32,
}
impl Location {
/// Creates a new empty location.
pub fn new() -> Self {
Default::default()
}
}
/// KML document.
///
/// See <https://www.ogc.org/standards/kml/> for the standard and
/// <https://developers.google.com/kml> for documentation.
#[derive(Debug, Clone, Default)]
pub struct Kml {
/// Nonstandard `addr` attribute of the `Document` tag storing the user email address.
pub addr: Option<String>,
/// Placemarks.
pub locations: Vec<Location>,
/// Currently parsed XML tag.
tag: KmlTag,
/// Currently parsed placemark.
pub curr: Location,
}
@@ -62,10 +91,12 @@ bitflags! {
}
impl Kml {
/// Creates a new empty KML document.
pub fn new() -> Self {
Default::default()
}
/// Parses a KML document.
pub fn parse(to_parse: &[u8]) -> Result<Self> {
ensure!(to_parse.len() <= 1024 * 1024, "kml-file is too large");
@@ -78,7 +109,7 @@ impl Kml {
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf).with_context(|| {
match reader.read_event_into(&mut buf).with_context(|| {
format!(
"location parsing error at position {}",
reader.buffer_position()
@@ -86,7 +117,7 @@ impl Kml {
})? {
quick_xml::events::Event::Start(ref e) => kml.starttag_cb(e, &reader),
quick_xml::events::Event::End(ref e) => kml.endtag_cb(e),
quick_xml::events::Event::Text(ref e) => kml.text_cb(e, &reader),
quick_xml::events::Event::Text(ref e) => kml.text_cb(e),
quick_xml::events::Event::Eof => break,
_ => (),
}
@@ -96,9 +127,9 @@ impl Kml {
Ok(kml)
}
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
fn text_cb(&mut self, event: &BytesText) {
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let val = event.unescape().unwrap_or_default();
let val = val.replace(['\n', '\r', '\t', ' '], "");
@@ -127,7 +158,9 @@ impl Kml {
}
fn endtag_cb(&mut self, event: &BytesEnd) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "placemark" {
if self.tag.contains(KmlTag::PLACEMARK)
@@ -147,14 +180,20 @@ impl Kml {
event: &BytesStart,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "document" {
if let Some(addr) = event
.attributes()
.filter_map(|a| a.ok())
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "addr")
{
self.addr = addr.unescape_and_decode_value(reader).ok();
if let Some(addr) = event.attributes().filter_map(|a| a.ok()).find(|attr| {
String::from_utf8_lossy(attr.key.as_ref())
.trim()
.to_lowercase()
== "addr"
}) {
self.addr = addr
.decode_and_unescape_value(reader)
.ok()
.map(|a| a.into_owned());
}
} else if tag == "placemark" {
self.tag = KmlTag::PLACEMARK;
@@ -172,12 +211,17 @@ impl Kml {
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
if let Some(acc) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
.map(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.to_lowercase()
== "accuracy"
})
.unwrap_or_default()
}) {
let v = acc
.unwrap()
.unescape_and_decode_value(reader)
.decode_and_unescape_value(reader)
.unwrap_or_default();
self.curr.accuracy = v.trim().parse().unwrap_or_default();
@@ -259,6 +303,7 @@ pub async fn is_sending_locations_to_chat(
Ok(exists)
}
/// Sets current location of the user device.
pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
if latitude == 0.0 && longitude == 0.0 {
return true;
@@ -306,6 +351,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
continue_streaming
}
/// Searches for locations in the given time range, optionally filtering by chat and contact IDs.
pub async fn get_range(
context: &Context,
chat_id: Option<ChatId>,
@@ -396,6 +442,7 @@ pub async fn delete_all(context: &Context) -> Result<()> {
Ok(())
}
/// Returns `location.kml` contents.
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)> {
let mut last_added_location_id = 0;
@@ -481,6 +528,7 @@ fn get_kml_timestamp(utc: i64) -> String {
.to_string()
}
/// Returns a KML document containing a single location with the given timestamp and coordinates.
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
@@ -498,6 +546,7 @@ pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String
)
}
/// Sets the timestamp of the last time location was sent in the chat.
pub async fn set_kml_sent_timestamp(
context: &Context,
chat_id: ChatId,
@@ -513,6 +562,7 @@ pub async fn set_kml_sent_timestamp(
Ok(())
}
/// Sets the location of the message.
pub async fn set_msg_location_id(context: &Context, msg_id: MsgId, location_id: u32) -> Result<()> {
context
.sql

View File

@@ -1168,6 +1168,7 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"3gp" => (Viewtype::Video, "video/3gpp"),
"aac" => (Viewtype::Audio, "audio/aac"),
"avi" => (Viewtype::Video, "video/x-msvideo"),
"avif" => (Viewtype::File, "image/avif"), // supported since Android 12 / iOS 16
"doc" => (Viewtype::File, "application/msword"),
"docx" => (
Viewtype::File,
@@ -1176,6 +1177,8 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"epub" => (Viewtype::File, "application/epub+zip"),
"flac" => (Viewtype::Audio, "audio/flac"),
"gif" => (Viewtype::Gif, "image/gif"),
"heic" => (Viewtype::File, "image/heic"), // supported since Android 10 / iOS 11
"heif" => (Viewtype::File, "image/heif"), // supported since Android 10 / iOS 11
"html" => (Viewtype::File, "text/html"),
"htm" => (Viewtype::File, "text/html"),
"ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
@@ -1200,10 +1203,15 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"oga" => (Viewtype::Audio, "audio/ogg"),
"ogg" => (Viewtype::Audio, "audio/ogg"),
"ogv" => (Viewtype::File, "video/ogg"),
"opus" => (Viewtype::File, "audio/ogg"), // not supported eg. on Android 4
"opus" => (Viewtype::File, "audio/ogg"), // supported since Android 10
"otf" => (Viewtype::File, "font/otf"),
"pdf" => (Viewtype::File, "application/pdf"),
"png" => (Viewtype::Image, "image/png"),
"ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
"pptx" => (
Viewtype::File,
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
),
"rar" => (Viewtype::File, "application/vnd.rar"),
"rtf" => (Viewtype::File, "application/rtf"),
"spx" => (Viewtype::File, "audio/ogg"), // Ogg Speex Profile
@@ -1212,6 +1220,7 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"tiff" => (Viewtype::File, "image/tiff"),
"tif" => (Viewtype::File, "image/tiff"),
"ttf" => (Viewtype::File, "font/ttf"),
"txt" => (Viewtype::File, "text/plain"),
"vcard" => (Viewtype::File, "text/vcard"),
"vcf" => (Viewtype::File, "text/vcard"),
"wav" => (Viewtype::File, "audio/wav"),
@@ -1221,11 +1230,12 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"wmv" => (Viewtype::Video, "video/x-ms-wmv"),
"xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
"xhtml" => (Viewtype::File, "application/xhtml+xml"),
"xls" => (Viewtype::File, "application/vnd.ms-excel"),
"xlsx" => (
Viewtype::File,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
"xml" => (Viewtype::File, "application/vnd.ms-excel"),
"xml" => (Viewtype::File, "application/xml"),
"zip" => (Viewtype::File, "application/zip"),
_ => {
return None;

View File

@@ -799,6 +799,7 @@ impl<'a> MimeFactory<'a> {
})
}
/// Returns MIME part with a `message.kml` attachment.
fn get_message_kml_part(&self) -> Option<PartBuilder> {
let latitude = self.msg.param.get_float(Param::SetLatitude)?;
let longitude = self.msg.param.get_float(Param::SetLongitude)?;
@@ -818,6 +819,7 @@ impl<'a> MimeFactory<'a> {
Some(part)
}
/// Returns MIME part with a `location.kml` attachment.
async fn get_location_kml_part(&mut self, context: &Context) -> Result<PartBuilder> {
let (kml_content, last_added_location_id) =
location::get_kml(context, self.msg.chat_id).await?;

33
src/net.rs Normal file
View File

@@ -0,0 +1,33 @@
///! # Common network utilities.
use std::pin::Pin;
use std::time::Duration;
use anyhow::{Context as _, Result};
use tokio::net::{TcpStream, ToSocketAddrs};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
/// Returns a TCP connection stream with read/write timeouts set
/// and Nagle's algorithm disabled with `TCP_NODELAY`.
///
/// `TCP_NODELAY` ensures writing to the stream always results in immediate sending of the packet
/// to the network, which is important to reduce the latency of interactive protocols such as IMAP.
pub(crate) async fn connect_tcp(
addr: impl ToSocketAddrs,
timeout_val: Duration,
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
let tcp_stream = timeout(timeout_val, TcpStream::connect(addr))
.await
.context("connection timeout")?
.context("connection failure")?;
// Disable Nagle's algorithm.
tcp_stream.set_nodelay(true)?;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(timeout_val));
timeout_stream.set_read_timeout(Some(timeout_val));
let pinned_stream = Box::pin(timeout_stream);
Ok(pinned_stream)
}

View File

@@ -141,8 +141,19 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
match ctx.get_config_bool(Config::FetchedExistingMsgs).await {
Ok(fetched_existing_msgs) => {
if !fetched_existing_msgs {
// Consider it done even if we fail.
//
// This operation is not critical enough to retry,
// especially if the error is persistent.
if let Err(err) =
ctx.set_config_bool(Config::FetchedExistingMsgs, true).await
{
warn!(ctx, "Can't set Config::FetchedExistingMsgs: {:#}", err);
}
if let Err(err) = connection.fetch_existing_msgs(&ctx).await {
warn!(ctx, "Failed to fetch existing messages: {:#}", err);
connection.trigger_reconnect(&ctx);
}
}
}
@@ -198,8 +209,8 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder_config: Config)
.await
.context("prepare IMAP connection")
{
connection.trigger_reconnect(ctx);
warn!(ctx, "{:#}", err);
connection.trigger_reconnect(ctx);
return connection.fake_idle(ctx, Some(watch_folder)).await;
}

View File

@@ -1,13 +1,13 @@
//! # Simplify incoming plaintext.
// protect lines starting with `--` against being treated as a footer.
// for that, we insert a ZERO WIDTH SPACE (ZWSP, 0x200B);
// this should be invisible on most systems and there is no need to unescape it again
// (which won't be done by non-deltas anyway)
//
// this escapes a bit more than actually needed by delta (eg. also lines as "-- footer"),
// but for non-delta-compatibility, that seems to be better.
// (to be only compatible with delta, only "[\r\n|\n]-- {0,2}[\r\n|\n]" needs to be replaced)
/// Protects lines starting with `--` against being treated as a footer.
/// for that, we insert a ZERO WIDTH SPACE (ZWSP, 0x200B);
/// this should be invisible on most systems and there is no need to unescape it again
/// (which won't be done by non-deltas anyway).
///
/// This escapes a bit more than actually needed by delta (e.g. also lines as "-- footer"),
/// but for non-delta-compatibility, that seems to be better.
/// (to be only compatible with delta, only "[\r\n|\n]-- {0,2}[\r\n|\n]" needs to be replaced)
pub fn escape_message_footer_marks(text: &str) -> String {
if let Some(text) = text.strip_prefix("--") {
"-\u{200B}-".to_string() + &text.replace("\n--", "\n-\u{200B}-")
@@ -74,6 +74,7 @@ pub(crate) fn split_lines(buf: &str) -> Vec<&str> {
/// Simplified text and some additional information gained from the input.
#[derive(Debug, Default)]
pub(crate) struct SimplifiedText {
/// The text itself.
pub text: String,
/// True if the message is forwarded.

View File

@@ -4,10 +4,10 @@ use std::fmt;
use std::pin::Pin;
use std::time::Duration;
use anyhow::{Context as _, Result};
use crate::net::connect_tcp;
use anyhow::Result;
pub use async_smtp::ServerAddress;
use tokio::net::{self, TcpStream};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
use crate::context::Context;
@@ -59,14 +59,7 @@ impl Socks5Config {
target_addr: impl net::ToSocketAddrs,
timeout_val: Duration,
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
let tcp_stream = timeout(timeout_val, TcpStream::connect(target_addr))
.await
.context("connection timeout")?
.context("connection failure")?;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(timeout_val));
timeout_stream.set_read_timeout(Some(timeout_val));
let timeout_stream = Box::pin(timeout_stream);
let tcp_stream = connect_tcp(target_addr, timeout_val).await?;
let authentication_method = if let Some((username, password)) = self.user_password.as_ref()
{
@@ -78,8 +71,7 @@ impl Socks5Config {
None
};
let socks_stream =
Socks5Stream::use_stream(timeout_stream, authentication_method, Config::default())
.await?;
Socks5Stream::use_stream(tcp_stream, authentication_method, Config::default()).await?;
Ok(socks_stream)
}

View File

@@ -1,7 +1,5 @@
//! # Message summary for chatlist.
#![allow(missing_docs)]
use crate::chat::Chat;
use crate::constants::Chattype;
use crate::contact::{Contact, ContactId};
@@ -54,6 +52,8 @@ pub struct Summary {
}
impl Summary {
/// Constucts chatlist summary
/// from the provided message, chat and message author contact snapshots.
pub async fn new(
context: &Context,
msg: &Message,