Compare commits

..

7 Commits

Author SHA1 Message Date
holger krekel
3e05015028 remove all mentioning of clippy and checks 2020-06-17 18:53:09 +02:00
holger krekel
6aa0291dfe remove one more 2020-06-17 18:14:05 +02:00
holger krekel
bb9112811a and another one bites 2020-06-17 17:48:43 +02:00
holger krekel
9574c09613 another try 2020-06-17 17:04:49 +02:00
holger krekel
486c9f6655 another try 2020-06-17 16:28:38 +02:00
holger krekel
65aec0c965 try a different way 2020-06-17 16:24:32 +02:00
holger krekel
e0750a04e8 try fix #1623 the wrong warnings 2020-06-17 16:09:07 +02:00
53 changed files with 4161 additions and 4187 deletions

View File

@@ -5,8 +5,6 @@ on:
push:
branches:
- master
- staging
- trying
jobs:
@@ -20,25 +18,12 @@ jobs:
profile: minimal
toolchain: 1.43.1
override: true
- run: rustup component add rustfmt
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.43.1
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
build_and_test:
name: Build and test
@@ -75,12 +60,6 @@ jobs:
path: target
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.toml') }}
- name: check
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:

View File

@@ -1,52 +1,5 @@
# Changelog
## 1.39.0
- fix handling of `mvbox_watch`, `sentbox_watch`, `inbox_watch` #1654 #1658
- fix potential panics, update dependencies #1650 #1655
## 1.38.0
- fix sorting, esp. for multi-device
## 1.37.0
- improve ndn heuristics #1630
- get oauth2 authorizer from provider-db #1641
- removed linebreaks and spaces from generated qr-code #1631
- more fixes #1633 #1635 #1636 #1637
## 1.36.0
- parse ndn (network delivery notification) reports
and report failed messages as such #1552 #1622 #1630
- add oauth2 support for gsuite domains #1626
- read image orientation from exif before recoding #1619
- improve logging #1593 #1598
- improve python and bot bindings #1583 #1609
- improve imap logout #1595
- fix sorting #1600 #1604
- fix qr code generation #1631
- update rustcrypto releases #1603
- refactorings #1617
## 1.35.0
- enable strict-tls from a new provider-db setting #1587

510
Cargo.lock generated
View File

@@ -2,9 +2,9 @@
# It is not intended for manual editing.
[[package]]
name = "addr2line"
version = "0.12.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c"
checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543"
dependencies = [
"gimli",
]
@@ -102,17 +102,11 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
[[package]]
name = "aho-corasick"
version = "0.7.13"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
dependencies = [
"memchr",
]
@@ -244,9 +238,9 @@ dependencies = [
[[package]]
name = "async-std"
version = "1.6.2"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d"
checksum = "b93c583a035d21e6d6f09adf48abfc55277bf48886406df370e5db6babe3ab98"
dependencies = [
"async-attributes",
"async-task",
@@ -287,24 +281,15 @@ checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]]
name = "async-trait"
version = "0.1.36"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92"
checksum = "89cb5d814ab2a47fd66d3266e9efccb53ca4c740b7451043b8ffcf9a6208f3f8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atoi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0afb7287b68575f5ca0e5c7e40191cbd4be59d325781f46faa603e176eaef47"
dependencies = [
"num-traits",
]
[[package]]
name = "atty"
version = "0.2.14"
@@ -474,19 +459,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c98bfd7c112b6399fef97cc0614af1cd375b27a112e552ce60f94c1b5f13cb74"
[[package]]
name = "blocking"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d17efb70ce4421e351d61aafd90c16a20fb5bfe339fcdc32a86816280e62ce0"
dependencies = [
"futures-channel",
"futures-util",
"once_cell",
"parking",
"waker-fn",
]
[[package]]
name = "blowfish"
version = "0.5.0"
@@ -564,21 +536,6 @@ dependencies = [
"iovec",
]
[[package]]
name = "bytes"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b"
dependencies = [
"loom",
]
[[package]]
name = "cache-padded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24508e28c677875c380c20f4d28124fab6f8ed4ef929a1397d7b1a31e92f1005"
[[package]]
name = "cargo_metadata"
version = "0.6.4"
@@ -676,15 +633,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
[[package]]
name = "concurrent-queue"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83c06aff61f2d899eb87c379df3cbf7876f14471dcab474e0b6dc90ab96c080"
dependencies = [
"cache-padded",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@@ -739,15 +687,31 @@ dependencies = [
]
[[package]]
name = "crossbeam-channel"
version = "0.4.2"
name = "crossbeam-deque"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
"maybe-uninit",
]
[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [
"autocfg 1.0.0",
"cfg-if",
"crossbeam-utils",
"lazy_static",
"maybe-uninit",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
@@ -838,12 +802,6 @@ dependencies = [
"syn",
]
[[package]]
name = "data-encoding"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69"
[[package]]
name = "deflate"
version = "0.8.4"
@@ -856,7 +814,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.39.0"
version = "1.35.0"
dependencies = [
"ansi_term 0.12.1",
"anyhow",
@@ -886,7 +844,6 @@ dependencies = [
"lazy_static",
"lettre_email",
"libc",
"libsqlite3-sys",
"log",
"mailparse",
"native-tls",
@@ -898,8 +855,11 @@ dependencies = [
"pretty_env_logger",
"proptest",
"quick-xml",
"r2d2",
"r2d2_sqlite",
"rand 0.7.3",
"regex",
"rusqlite",
"rustyline",
"sanitize-filename",
"serde",
@@ -907,7 +867,6 @@ dependencies = [
"sha2 0.9.0",
"smallvec",
"smol",
"sqlx",
"stop-token",
"strum",
"strum_macros",
@@ -927,7 +886,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.39.0"
version = "1.35.0"
dependencies = [
"anyhow",
"async-std",
@@ -1016,17 +975,11 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
[[package]]
name = "ed25519-dalek"
@@ -1204,6 +1157,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fast_chemail"
version = "0.9.6"
@@ -1213,12 +1178,6 @@ dependencies = [
"ascii_utils",
]
[[package]]
name = "fastrand"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64b0126b293b050395b37b10489951590ed024c03d7df4f249d219c8ded7cbf"
[[package]]
name = "flate2"
version = "1.0.14"
@@ -1371,19 +1330,6 @@ dependencies = [
"tokio-io",
]
[[package]]
name = "generator"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
dependencies = [
"cc",
"libc",
"log",
"rustc_version",
"winapi",
]
[[package]]
name = "generic-array"
version = "0.12.3"
@@ -1458,16 +1404,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "hashbrown"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9b7860757ce258c89fd48d28b68c41713e597a7b09e793f6c6a6e2ea37c827"
dependencies = [
"ahash",
"autocfg 1.0.0",
]
[[package]]
name = "heck"
version = "0.3.1"
@@ -1553,9 +1489,9 @@ dependencies = [
[[package]]
name = "http-types"
version = "2.2.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca4221cd1c7cedf275cd0ad3c9dfe58b5acc93cdd5511c7e020a102e1995fe99"
checksum = "9a89eaaf43f3700e78c01cb8165d1bd05155065637d26ee2f49800c95e7b62ee"
dependencies = [
"anyhow",
"async-std",
@@ -1565,7 +1501,6 @@ dependencies = [
"rand 0.7.3",
"serde",
"serde_json",
"serde_qs",
"serde_urlencoded",
"url",
]
@@ -1619,9 +1554,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.23.6"
version = "0.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4"
checksum = "d534e95ad8b9d5aa614322d02352b4f1bf962254adcf02ac6f2def8be18498e8"
dependencies = [
"bytemuck",
"byteorder",
@@ -1664,9 +1599,12 @@ dependencies = [
[[package]]
name = "infer"
version = "0.1.7"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6854dd77ddc4f9ba1a448f487e27843583d407648150426a30c2ea3a2c39490a"
checksum = "d55c406a76164eb346a829ed4b97b73cb06259078eca01adeb12e8ca308d4123"
dependencies = [
"byteorder",
]
[[package]]
name = "iovec"
@@ -1700,9 +1638,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "jpeg-decoder"
@@ -1839,17 +1777,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "loom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
dependencies = [
"cfg-if",
"generator",
"scoped-tls 0.1.2",
]
[[package]]
name = "lru-cache"
version = "0.1.2"
@@ -1876,12 +1803,6 @@ dependencies = [
"quoted_printable",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "match_cfg"
version = "0.1.0"
@@ -1917,6 +1838,15 @@ version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "memoffset"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
dependencies = [
"autocfg 1.0.0",
]
[[package]]
name = "mime"
version = "0.3.16"
@@ -2064,9 +1994,9 @@ dependencies = [
[[package]]
name = "num-rational"
version = "0.3.0"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg 1.0.0",
"num-integer",
@@ -2180,12 +2110,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "parking"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a"
[[package]]
name = "parking_lot"
version = "0.10.2"
@@ -2275,50 +2199,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand 0.7.3",
]
[[package]]
name = "phf_macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "0.4.22"
@@ -2351,6 +2231,18 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01608bfa680dafb103f9207fa944facf572e4e3e708d10de19a0d0c3d36e5f18"
dependencies = [
"crossbeam-utils",
"futures-io",
"futures-sink",
"futures-util",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
@@ -2487,6 +2379,27 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
[[package]]
name = "r2d2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "r2d2_sqlite"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed60ebe88b27ac28c0563bc0fbeaecd302ff53e3a01e5ddc2ec9f4e6c707d929"
dependencies = [
"r2d2",
"rusqlite",
]
[[package]]
name = "rand"
version = "0.4.6"
@@ -2512,7 +2425,6 @@ dependencies = [
"rand_chacha",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -2558,15 +2470,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.2.0"
@@ -2693,6 +2596,22 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rusqlite"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45d0fd62e1df63d254714e6cb40d0a0e82e7a1623e7a27f679d851af092ae58b"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"libsqlite3-sys",
"lru-cache",
"memchr",
"smallvec",
"time 0.1.43",
]
[[package]]
name = "rust-argon2"
version = "0.7.0"
@@ -2791,16 +2710,19 @@ dependencies = [
]
[[package]]
name = "scoped-tls"
version = "0.1.2"
name = "scheduled-thread-pool"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6"
dependencies = [
"parking_lot",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
name = "scoped-tls-hkt"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63"
[[package]]
name = "scopeguard"
@@ -2855,18 +2777,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
[[package]]
name = "serde"
version = "1.0.114"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.114"
version = "1.0.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57"
dependencies = [
"proc-macro2",
"quote",
@@ -2884,18 +2806,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_qs"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f3acf84e23ab27c01cb5917551765c01c50b2000089db8fa47fe018a3260cf"
dependencies = [
"data-encoding",
"error-chain",
"percent-encoding",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.6.1"
@@ -2974,12 +2884,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]]
name = "skeptic"
version = "0.13.4"
@@ -3010,23 +2914,23 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "smol"
version = "0.1.18"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5"
checksum = "afcf8beb4aa23cc616e3351e49585153b462637c8fec929163b54d88e522b3b0"
dependencies = [
"async-task",
"blocking",
"concurrent-queue",
"fastrand",
"crossbeam-deque",
"crossbeam-queue",
"crossbeam-utils",
"futures-io",
"futures-util",
"libc",
"once_cell",
"scoped-tls 1.0.0",
"piper",
"scoped-tls-hkt",
"slab",
"socket2",
"wepoll-sys-stjepang",
"winapi",
"wepoll-binding",
]
[[package]]
@@ -3047,88 +2951,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "sqlformat"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce64a4576e1720a2e511bf3ccdb8c0f6cfed0fc265bcbaa0bd369485e02c631"
dependencies = [
"lazy_static",
"maplit",
"regex",
]
[[package]]
name = "sqlx"
version = "0.4.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"sqlx-core",
"sqlx-macros",
]
[[package]]
name = "sqlx-core"
version = "0.4.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"atoi",
"bitflags",
"byteorder",
"bytes 0.5.5",
"crossbeam-channel",
"crossbeam-queue",
"crossbeam-utils",
"either",
"futures-channel",
"futures-core",
"futures-util",
"hashbrown",
"hex",
"itoa",
"libc",
"libsqlite3-sys",
"log",
"memchr",
"once_cell",
"parking_lot",
"percent-encoding",
"phf",
"smallvec",
"sqlformat",
"sqlx-rt",
"stringprep",
"thiserror",
"url",
"whoami",
]
[[package]]
name = "sqlx-macros"
version = "0.4.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"async-std",
"dotenv",
"futures 0.3.5",
"heck",
"proc-macro2",
"quote",
"sqlx-core",
"syn",
"url",
]
[[package]]
name = "sqlx-rt"
version = "0.1.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"async-native-tls",
"async-std",
"native-tls",
]
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
@@ -3218,16 +3040,6 @@ dependencies = [
"generic-array 0.14.2",
]
[[package]]
name = "stringprep"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "strsim"
version = "0.9.3"
@@ -3286,9 +3098,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.33"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
dependencies = [
"proc-macro2",
"quote",
@@ -3417,19 +3229,13 @@ dependencies = [
"syn",
]
[[package]]
name = "tinyvec"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"bytes",
"futures 0.1.29",
"log",
]
@@ -3527,11 +3333,11 @@ dependencies = [
[[package]]
name = "unicode-normalization"
version = "0.1.13"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
dependencies = [
"tinyvec",
"smallvec",
]
[[package]]
@@ -3548,9 +3354,9 @@ checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "universal-hash"
@@ -3621,12 +3427,6 @@ dependencies = [
"libc",
]
[[package]]
name = "waker-fn"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7"
[[package]]
name = "walkdir"
version = "2.3.1"
@@ -3721,19 +3521,23 @@ dependencies = [
]
[[package]]
name = "wepoll-sys-stjepang"
version = "1.0.6"
name = "wepoll-binding"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694"
checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7"
dependencies = [
"cc",
"bitflags",
"wepoll-sys",
]
[[package]]
name = "whoami"
version = "0.8.2"
name = "wepoll-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18bb55566f6f3f8440914233cf63df1eb60b801b5007d376cc46212cb8a9287c"
checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24"
dependencies = [
"cc",
]
[[package]]
name = "widestring"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.39.0"
version = "1.35.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL-2.0"
@@ -36,6 +36,9 @@ indexmap = "1.3.0"
kamadak-exif = "0.5"
lazy_static = "1.4.0"
regex = "1.1.6"
rusqlite = { version = "0.23", features = ["bundled"] }
r2d2_sqlite = "0.16.0"
r2d2 = "0.8.5"
strum = "0.18.0"
strum_macros = "0.18.0"
backtrace = "0.3.33"
@@ -57,22 +60,20 @@ anyhow = "1.0.28"
async-trait = "0.1.31"
url = "2.1.1"
async-std-resolver = "0.19.5"
sqlx = { git = "https://github.com/launchbadge/sqlx", branch = "master", features = ["runtime-async-std", "sqlite", "macros"] }
libsqlite3-sys = { version = "0.18", features = ["bundled", "min_sqlite_version_3_7_16"] }
pretty_env_logger = { version = "0.4.0", optional = true }
log = { version = "0.4.8", optional = true }
log = {version = "0.4.8", optional = true }
rustyline = { version = "4.1.0", optional = true }
ansi_term = { version = "0.12.1", optional = true }
[dev-dependencies]
tempfile = "3.0"
pretty_assertions = "0.6.1"
pretty_env_logger = "0.4.0"
proptest = "0.10"
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
smol = "0.1.11"
log = "0.4.8"
smol = "0.1.10"
[workspace]
members = [
@@ -97,3 +98,4 @@ internals = []
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"]
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"]
nightly = ["pgp/nightly"]

View File

@@ -123,7 +123,6 @@ Language bindings are available for:
- [Node.js](https://www.npmjs.com/package/deltachat-node)
- [Python](https://py.delta.chat)
- [Go](https://github.com/hugot/go-deltachat/)
- [Free Pascal](https://github.com/deltachat/deltachat-fp/)
- **Java** and **Swift** (contained in the Android/iOS repos)
The following "frontend" projects make use of the Rust-library

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.39.0"
version = "1.35.0"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -262,25 +262,17 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `selfavatar` = File containing avatar. Will immediately be copied to the
* `blobdir`; the original image will not be needed anymore.
* NULL to remove the avatar.
* As for `displayname` and `selfstatus`, also the avatar is sent to the recipients.
* To save traffic, however, the avatar is attached only as needed
* and also recoded to a reasonable size.
* It is planned for future versions
* to send this image together with the next messages.
* - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
* - `mdns_enabled` = 0=do not send or request read receipts,
* 1=send and request read receipts (default)
* - `bcc_self` = 0=do not send a copy of outgoing messages to self (default),
* 1=send a copy of outgoing messages to self.
* Sending messages to self is needed for a proper multi-account setup,
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
* - `inbox_watch` = 1=watch `INBOX`-folder for changes (default),
* 0=do not watch the `INBOX`-folder,
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* 0=do not watch the `INBOX`-folder
* - `sentbox_watch`= 1=watch `Sent`-folder for changes (default),
* 0=do not watch the `Sent`-folder,
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* 0=do not watch the `Sent`-folder
* - `mvbox_watch` = 1=watch `DeltaChat`-folder for changes (default),
* 0=do not watch the `DeltaChat`-folder,
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* 0=do not watch the `DeltaChat`-folder
* - `mvbox_move` = 1=heuristically detect chat-messages
* and move them to the `DeltaChat`-folder,
* 0=do not move chat-messages
@@ -4315,7 +4307,7 @@ void dc_event_unref(dc_event_t* event);
*/
/**
* Provider works out-of-the-box.
* Prover works out-of-the-box.
* This provider status is returned for provider where the login
* works by just entering the name or the email-address.
*

View File

@@ -126,7 +126,7 @@ pub unsafe extern "C" fn dc_set_config(
// When ctx.set_config() fails it already logged the error.
// TODO: Context::set_config() should not log this
Ok(key) => block_on(async move {
ctx.set_config(key, to_opt_string_lossy(value).as_deref())
ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str()))
.await
.is_ok() as libc::c_int
}),
@@ -285,7 +285,7 @@ pub unsafe extern "C" fn dc_start_io(context: *mut dc_context_t) {
}
let ctx = &*context;
block_on(ctx.start_io())
block_on({ ctx.start_io() })
}
#[no_mangle]
@@ -295,7 +295,7 @@ pub unsafe extern "C" fn dc_is_io_running(context: *mut dc_context_t) -> libc::c
}
let ctx = &*context;
block_on(ctx.is_io_running()) as libc::c_int
block_on({ ctx.is_io_running() }) as libc::c_int
}
#[no_mangle]
@@ -349,7 +349,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| Event::MsgDelivered { chat_id, .. }
| Event::MsgFailed { chat_id, .. }
| Event::MsgRead { chat_id, .. }
| Event::ChatModified(chat_id) => chat_id.to_u32().unwrap_or_default() as libc::c_int,
| Event::ChatModified(chat_id) => chat_id.to_u32() as libc::c_int,
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
let id = id.unwrap_or_default();
id as libc::c_int
@@ -396,7 +396,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| Event::IncomingMsg { msg_id, .. }
| Event::MsgDelivered { msg_id, .. }
| Event::MsgFailed { msg_id, .. }
| Event::MsgRead { msg_id, .. } => msg_id.to_u32().unwrap_or_default() as libc::c_int,
| Event::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int,
Event::SecurejoinInviterProgress { progress, .. }
| Event::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
}
@@ -554,7 +554,14 @@ pub unsafe extern "C" fn dc_get_chatlist(
let qi = if query_id == 0 { None } else { Some(query_id) };
block_on(async move {
match chatlist::Chatlist::try_load(&ctx, flags as usize, qs.as_deref(), qi).await {
match chatlist::Chatlist::try_load(
&ctx,
flags as usize,
qs.as_ref().map(|x| x.as_str()),
qi,
)
.await
{
Ok(list) => {
let ffi_list = ChatlistWrapper { context, list };
Box::into_raw(Box::new(ffi_list))
@@ -745,9 +752,13 @@ pub unsafe extern "C" fn dc_add_device_msg(
};
block_on(async move {
chat::add_device_msg(&ctx, to_opt_string_lossy(label).as_deref(), msg)
.await
.unwrap_or_log_default(&ctx, "Failed to add device message")
chat::add_device_msg(
&ctx,
to_opt_string_lossy(label).as_ref().map(|x| x.as_str()),
msg,
)
.await
.unwrap_or_log_default(&ctx, "Failed to add device message")
})
.to_u32()
}
@@ -833,8 +844,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
let arr = dc_array_t::from(
chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag)
.await
.unwrap_or_log_default(ctx, "failed get_chat_msgs")
.into_iter()
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
@@ -899,7 +909,7 @@ pub unsafe extern "C" fn dc_get_fresh_msgs(
let arr = dc_array_t::from(
ctx.get_fresh_msgs()
.await
.into_iter()
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
@@ -976,8 +986,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
or_msg_type3,
)
.await
.unwrap_or_log_default(ctx, "failed get_chat_media")
.into_iter()
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
@@ -1021,9 +1030,8 @@ pub unsafe extern "C" fn dc_get_next_media(
or_msg_type3,
)
.await
.unwrap_or_log_default(ctx, "failed get_next_media")
.map(|msg_id| msg_id.to_u32())
.unwrap_or_else(|| 0)
.unwrap_or(0)
})
}
@@ -1089,10 +1097,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
let ctx = &*context;
block_on(async move {
let list = chat::get_chat_contacts(&ctx, ChatId::new(chat_id))
.await
.unwrap_or_log_default(ctx, "failed get_chat_contacts");
let arr = dc_array_t::from(list);
let arr = dc_array_t::from(chat::get_chat_contacts(&ctx, ChatId::new(chat_id)).await);
Box::into_raw(Box::new(arr))
})
}
@@ -1113,7 +1118,7 @@ pub unsafe extern "C" fn dc_search_msgs(
let arr = dc_array_t::from(
ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query))
.await
.into_iter()
.iter()
.map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(),
);
@@ -1302,12 +1307,7 @@ pub unsafe extern "C" fn dc_get_msg_info(
}
let ctx = &*context;
block_on(async move {
message::get_msg_info(ctx, MsgId::new(msg_id))
.await
.unwrap_or_log_default(ctx, "failed get_msg_info")
})
.strdup()
block_on(message::get_msg_info(&ctx, MsgId::new(msg_id))).strdup()
}
#[no_mangle]
@@ -1829,11 +1829,7 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
}
let ctx = &*context;
block_on(location::send_locations_to_chat(
&ctx,
ChatId::new(chat_id),
seconds as i64,
));
block_on({ location::send_locations_to_chat(&ctx, ChatId::new(chat_id), seconds as i64) });
}
#[no_mangle]
@@ -1866,11 +1862,7 @@ pub unsafe extern "C" fn dc_set_location(
}
let ctx = &*context;
block_on(async move {
location::set(ctx, latitude, longitude, accuracy)
.await
.unwrap_or_log_default(ctx, "failed location::set")
}) as _
block_on(location::set(&ctx, latitude, longitude, accuracy)) as _
}
#[no_mangle]
@@ -2270,12 +2262,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let ctx = &*ffi_chat.context;
block_on(async move {
match ffi_chat
.chat
.get_profile_image(&ctx)
.await
.unwrap_or_log_default(ctx, "failed get_profile_image")
{
match ffi_chat.chat.get_profile_image(&ctx).await {
Some(p) => p.to_string_lossy().strdup(),
None => ptr::null_mut(),
}
@@ -2291,13 +2278,7 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 {
let ffi_chat = &*chat;
let ctx = &*ffi_chat.context;
block_on(async move {
ffi_chat
.chat
.get_color(&ctx)
.await
.unwrap_or_log_default(ctx, "failed dc_chat_get_color")
})
block_on(ffi_chat.chat.get_color(&ctx))
}
#[no_mangle]
@@ -2831,7 +2812,7 @@ pub unsafe extern "C" fn dc_msg_set_file(
let ffi_msg = &mut *msg;
ffi_msg.message.set_file(
to_string_lossy(file),
to_opt_string_lossy(filemime).as_deref(),
to_opt_string_lossy(filemime).as_ref().map(|x| x.as_str()),
)
}

View File

@@ -8,33 +8,36 @@ use quote::quote;
// data. If this assumption is violated, compiler error will point to
// generated code, which is not very user-friendly.
#[proc_macro_derive(Sqlx)]
pub fn sqlx_derive(input: TokenStream) -> TokenStream {
#[proc_macro_derive(ToSql)]
pub fn to_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl<'q> sqlx::encode::Encode<'q, sqlx::sqlite::Sqlite> for #name {
fn encode_by_ref(&self, buf: &mut Vec<sqlx::sqlite::SqliteArgumentValue<'q>>) -> sqlx::encode::IsNull {
num_traits::ToPrimitive::to_i32(self).expect("invalid type").encode(buf)
impl rusqlite::types::ToSql for #name {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let num = *self as i64;
let value = rusqlite::types::Value::Integer(num);
let output = rusqlite::types::ToSqlOutput::Owned(value);
std::result::Result::Ok(output)
}
}
};
gen.into()
}
#[proc_macro_derive(FromSql)]
pub fn from_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl rusqlite::types::FromSql for #name {
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
let inner = rusqlite::types::FromSql::column_result(col)?;
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
}
}
impl<'de> sqlx::decode::Decode<'de, sqlx::sqlite::Sqlite> for #name {
fn decode(value: sqlx::sqlite::SqliteValueRef) -> std::result::Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
let raw: i32 = sqlx::decode::Decode::decode(value)?;
Ok(num_traits::FromPrimitive::from_i32(raw).unwrap_or_default())
}
}
impl sqlx::types::Type<sqlx::sqlite::Sqlite> for #name {
fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
<i32 as sqlx::types::Type<_>>::type_info()
}
}
};
gen.into()
}

View File

@@ -28,7 +28,7 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 1 {
context
.sql()
.execute("DELETE FROM jobs;", paramsx![])
.execute("DELETE FROM jobs;", paramsv![])
.await
.unwrap();
println!("(1) Jobs reset.");
@@ -36,7 +36,7 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 2 {
context
.sql()
.execute("DELETE FROM acpeerstates;", paramsx![])
.execute("DELETE FROM acpeerstates;", paramsv![])
.await
.unwrap();
println!("(2) Peerstates reset.");
@@ -44,7 +44,7 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 4 {
context
.sql()
.execute("DELETE FROM keypairs;", paramsx![])
.execute("DELETE FROM keypairs;", paramsv![])
.await
.unwrap();
println!("(4) Private keypairs reset.");
@@ -52,35 +52,35 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 8 {
context
.sql()
.execute("DELETE FROM contacts WHERE id>9;", paramsx![])
.execute("DELETE FROM contacts WHERE id>9;", paramsv![])
.await
.unwrap();
context
.sql()
.execute("DELETE FROM chats WHERE id>9;", paramsx![])
.execute("DELETE FROM chats WHERE id>9;", paramsv![])
.await
.unwrap();
context
.sql()
.execute("DELETE FROM chats_contacts;", paramsx![])
.execute("DELETE FROM chats_contacts;", paramsv![])
.await
.unwrap();
context
.sql()
.execute("DELETE FROM msgs WHERE id>9;", paramsx![])
.execute("DELETE FROM msgs WHERE id>9;", paramsv![])
.await
.unwrap();
context
.sql()
.execute(
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
paramsx![],
paramsv![],
)
.await
.unwrap();
context
.sql()
.execute("DELETE FROM leftgrps;", paramsx![])
.execute("DELETE FROM leftgrps;", paramsv![])
.await
.unwrap();
println!("(8) Rest but server config reset.");
@@ -118,7 +118,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
.await
.unwrap();
} else {
let rs = context.sql().get_raw_config("import_spec").await;
let rs = context.sql().get_raw_config(context, "import_spec").await;
if rs.is_none() {
error!(context, "Import: No file or folder given.");
return false;
@@ -276,7 +276,7 @@ async fn log_contactlist(context: &Context, contacts: &[u32]) {
}
);
let peerstate = Peerstate::from_addr(context, &addr).await;
if peerstate.is_ok() && contact_id != 1 as libc::c_uint {
if peerstate.is_some() && contact_id != 1 as libc::c_uint {
line2 = format!(
", prefer-encrypt={}",
peerstate.as_ref().unwrap().prefer_encrypt
@@ -484,11 +484,10 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
context.maybe_network().await;
}
"housekeeping" => {
sql::housekeeping(&context).await?;
sql::housekeeping(&context).await;
}
"listchats" | "listarchived" | "chats" => {
let listflags = if arg0 == "listarchived" { 0x01 } else { 0 };
let time_start = std::time::SystemTime::now();
let chatlist = Chatlist::try_load(
&context,
listflags,
@@ -496,9 +495,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
None,
)
.await?;
let time_needed = std::time::SystemTime::now()
.duration_since(time_start)
.unwrap_or_default();
let cnt = chatlist.len();
if cnt > 0 {
@@ -557,7 +553,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
println!("Location streaming enabled.");
}
println!("{} chats", cnt);
println!("{:?} to create this list", time_needed);
}
"chat" => {
if sel_chat.is_none() && arg1.is_empty() {
@@ -573,8 +568,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(sel_chat.is_some(), "Failed to select chat");
let sel_chat = sel_chat.as_ref().unwrap();
let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await?;
let members = chat::get_chat_contacts(&context, sel_chat.id).await?;
let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await;
let members = chat::get_chat_contacts(&context, sel_chat.id).await;
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
@@ -594,7 +589,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
} else {
""
},
match sel_chat.get_profile_image(&context).await? {
match sel_chat.get_profile_image(&context).await {
Some(icon) => match icon.to_str() {
Some(icon) => format!(" Icon: {}", icon),
_ => " Icon: Err".to_string(),
@@ -691,7 +686,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(sel_chat.is_some(), "No chat selected.");
let contacts =
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?;
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await;
println!("Memberlist:");
log_contactlist(&context, &contacts).await;
@@ -762,7 +757,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let latitude = arg1.parse()?;
let longitude = arg2.parse()?;
let continue_streaming = location::set(&context, latitude, longitude, 0.).await?;
let continue_streaming = location::set(&context, latitude, longitude, 0.).await;
if continue_streaming {
println!("Success, streaming should be continued.");
} else {
@@ -858,7 +853,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
Viewtype::Gif,
Viewtype::Video,
)
.await?;
.await;
println!("{} images or videos: ", images.len());
for (i, data) in images.iter().enumerate() {
if 0 == i {
@@ -892,7 +887,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"msginfo" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = MsgId::new(arg1.parse()?);
let res = message::get_msg_info(&context, id).await?;
let res = message::get_msg_info(&context, id).await;
println!("{}", res);
}
"listfresh" => {

View File

@@ -349,7 +349,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
if hasattr(acc, "_configtracker"):
acc._configtracker.wait_finish()
del acc._configtracker
acc.set_config("bcc_self", "0")
if acc.is_configured() and not acc.is_started():
acc.start_io()
print("{}: {} account was successfully setup".format(

View File

@@ -723,9 +723,9 @@ class TestOnlineAccount:
def test_move_works_on_self_sent(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True)
ac1.set_config("bcc_self", "1")
ac2 = acfactory.get_online_configuring_account()
acfactory.wait_configure_and_start_io()
ac1.set_config("bcc_self", "1")
chat = acfactory.get_accepted_chat(ac1, ac2)
chat.send_text("message1")

View File

@@ -6,15 +6,13 @@ use std::collections::BTreeMap;
use std::str::FromStr;
use std::{fmt, str};
use deltachat_derive::*;
use crate::contact::*;
use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{DcKey, SignedPublicKey};
/// Possible values for encryption preference
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive, Sqlx)]
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)]
#[repr(u8)]
pub enum EncryptPreference {
NoPreference = 0,

View File

@@ -497,7 +497,7 @@ mod tests {
#[async_std::test]
async fn test_create() {
let t = TestContext::new().await;
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo", b"hello").await.unwrap();
let fname = t.ctx.get_blobdir().join("foo");
let data = fs::read(fname).await.unwrap();
@@ -508,7 +508,7 @@ mod tests {
#[async_std::test]
async fn test_lowercase_ext() {
let t = TestContext::new().await;
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello")
.await
.unwrap();
@@ -517,7 +517,7 @@ mod tests {
#[async_std::test]
async fn test_as_file_name() {
let t = TestContext::new().await;
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
@@ -526,7 +526,7 @@ mod tests {
#[async_std::test]
async fn test_as_rel_path() {
let t = TestContext::new().await;
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
@@ -535,7 +535,7 @@ mod tests {
#[async_std::test]
async fn test_suffix() {
let t = TestContext::new().await;
let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
@@ -546,7 +546,7 @@ mod tests {
#[async_std::test]
async fn test_create_dup() {
let t = TestContext::new().await;
let t = dummy_context().await;
BlobObject::create(&t.ctx, "foo.txt", b"hello")
.await
.unwrap();
@@ -570,7 +570,7 @@ mod tests {
#[async_std::test]
async fn test_double_ext_preserved() {
let t = TestContext::new().await;
let t = dummy_context().await;
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello")
.await
.unwrap();
@@ -595,7 +595,7 @@ mod tests {
#[async_std::test]
async fn test_create_long_names() {
let t = TestContext::new().await;
let t = dummy_context().await;
let s = "1".repeat(150);
let blob = BlobObject::create(&t.ctx, &s, b"data").await.unwrap();
let blobname = blob.as_name().split('/').last().unwrap();
@@ -604,7 +604,7 @@ mod tests {
#[async_std::test]
async fn test_create_and_copy() {
let t = TestContext::new().await;
let t = dummy_context().await;
let src = t.dir.path().join("src");
fs::write(&src, b"boo").await.unwrap();
let blob = BlobObject::create_and_copy(&t.ctx, &src).await.unwrap();
@@ -620,7 +620,7 @@ mod tests {
#[async_std::test]
async fn test_create_from_path() {
let t = TestContext::new().await;
let t = dummy_context().await;
let src_ext = t.dir.path().join("external");
fs::write(&src_ext, b"boo").await.unwrap();
@@ -638,7 +638,7 @@ mod tests {
}
#[async_std::test]
async fn test_create_from_name_long() {
let t = TestContext::new().await;
let t = dummy_context().await;
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).await.unwrap();
@@ -660,40 +660,8 @@ mod tests {
#[test]
fn test_sanitise_name() {
let (stem, ext) =
let (_, ext) =
BlobObject::sanitise_name("Я ЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯ.txt");
assert_eq!(ext, ".txt");
assert!(!stem.is_empty());
// the extensions are kept together as between stem and extension a number may be added -
// and `foo.tar.gz` should become `foo-1234.tar.gz` and not `foo.tar-1234.gz`
let (stem, ext) = BlobObject::sanitise_name("wot.tar.gz");
assert_eq!(stem, "wot");
assert_eq!(ext, ".tar.gz");
let (stem, ext) = BlobObject::sanitise_name(".foo.bar");
assert_eq!(stem, "");
assert_eq!(ext, ".foo.bar");
let (stem, ext) = BlobObject::sanitise_name("foo?.bar");
assert!(stem.contains("foo"));
assert!(!stem.contains("?"));
assert_eq!(ext, ".bar");
let (stem, ext) = BlobObject::sanitise_name("no-extension");
assert_eq!(stem, "no-extension");
assert_eq!(ext, "");
let (stem, ext) = BlobObject::sanitise_name("path/ignored\\this: is* forbidden?.c");
assert_eq!(ext, ".c");
assert!(!stem.contains("path"));
assert!(!stem.contains("ignored"));
assert!(stem.contains("this"));
assert!(stem.contains("forbidden"));
assert!(!stem.contains("/"));
assert!(!stem.contains("\\"));
assert!(!stem.contains(":"));
assert!(!stem.contains("*"));
assert!(!stem.contains("?"));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -105,6 +105,17 @@ impl Chatlist {
let mut add_archived_link_item = false;
let process_row = |row: &rusqlite::Row| {
let chat_id: ChatId = row.get(0)?;
let msg_id: MsgId = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
};
let skip_id = if flag_for_forwarding {
chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
.await
@@ -131,23 +142,24 @@ impl Chatlist {
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_rows(
context.sql.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND (hidden=0 OR state=?1))
WHERE c.id>9
AND c.blocked=0
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2)
GROUP BY c.id
ORDER BY c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsx![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
process_row,
process_rows,
).await?
} else if flag_archived_only {
// show archived chats
@@ -156,23 +168,24 @@ impl Chatlist {
// and adapting the number requires larger refactorings and seems not to be worth the effort)
context
.sql
.query_rows(
.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND (hidden=0 OR state=?))
WHERE c.id>9
AND c.blocked=0
AND c.archived=1
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsx![MessageState::OutDraft],
paramsv![MessageState::OutDraft],
process_row,
process_rows,
)
.await?
} else if let Some(query) = query {
@@ -188,23 +201,24 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query);
context
.sql
.query_rows(
.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?1)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND (hidden=0 OR state=?1))
WHERE c.id>9 AND c.id!=?2
AND c.blocked=0
AND c.name LIKE ?3
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsx![MessageState::OutDraft, skip_id, str_like_cmd],
paramsv![MessageState::OutDraft, skip_id, str_like_cmd],
process_row,
process_rows,
)
.await?
} else {
@@ -217,25 +231,25 @@ impl Chatlist {
} else {
ChatId::new(0)
};
let mut ids = context.sql.query_rows(
let mut ids = context.sql.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
AND m.timestamp=(
SELECT MAX(timestamp)
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?1)
ORDER BY timestamp DESC, id DESC LIMIT 1)
AND (hidden=0 OR state=?1))
WHERE c.id>9 AND c.id!=?2
AND c.blocked=0
AND NOT c.archived=?3
GROUP BY c.id
ORDER BY c.id=?4 DESC, c.archived=?5 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsx![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
paramsv![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
process_row,
process_rows,
).await?;
if !flag_no_specials {
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await
{
@@ -369,15 +383,15 @@ impl Chatlist {
/// Returns the number of archived chats
pub async fn dc_get_archived_cnt(context: &Context) -> u32 {
let v: i32 = context
context
.sql
.query_value(
.query_get_value(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
paramsx![],
paramsv![],
)
.await
.unwrap_or_default();
v as u32
.unwrap_or_default()
}
async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
@@ -385,21 +399,21 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
// sufficient as there are typically only few fresh messages.
context
.sql
.query_value(
r#"
SELECT m.id
FROM msgs m
LEFT JOIN chats c
ON c.id=m.chat_id
WHERE m.state=10
AND m.hidden=0
AND c.blocked=2
ORDER BY m.timestamp DESC, m.id DESC;
"#,
paramsx![],
.query_get_value(
context,
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN chats c",
" ON c.id=m.chat_id",
" WHERE m.state=10",
" AND m.hidden=0",
" AND c.blocked=2",
" ORDER BY m.timestamp DESC, m.id DESC;"
),
paramsv![],
)
.await
.ok()
}
#[cfg(test)]
@@ -410,7 +424,7 @@ mod tests {
#[async_std::test]
async fn test_try_load() {
let t = TestContext::new().await;
let t = dummy_context().await;
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
.await
.unwrap();
@@ -458,7 +472,7 @@ mod tests {
#[async_std::test]
async fn test_sort_self_talk_up_on_forward() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx.update_device_chats().await.unwrap();
create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
.await
@@ -483,7 +497,7 @@ mod tests {
#[async_std::test]
async fn test_search_special_chat_names() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx.update_device_chats().await.unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
@@ -516,7 +530,7 @@ mod tests {
#[async_std::test]
async fn test_get_summary_unwrap() {
let t = TestContext::new().await;
let t = dummy_context().await;
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
.await
.unwrap();

View File

@@ -11,7 +11,7 @@ use crate::dc_tools::*;
use crate::events::Event;
use crate::message::MsgId;
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::stock::StockMessage;
use crate::{scheduler::InterruptInfo, stock::StockMessage};
/// The available configuration keys.
#[derive(
@@ -120,21 +120,17 @@ pub enum Config {
}
impl Context {
pub async fn config_exists(&self, key: Config) -> bool {
self.sql.get_raw_config(key).await.is_some()
}
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
pub async fn get_config(&self, key: Config) -> Option<String> {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_raw_config(key).await;
let rel_path = self.sql.get_raw_config(self, key).await;
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_raw_config(key).await,
_ => self.sql.get_raw_config(self, key).await,
};
if value.is_some() {
@@ -189,7 +185,7 @@ impl Context {
match key {
Config::Selfavatar => {
self.sql
.execute("UPDATE contacts SET selfavatar_sent=0;", paramsx![])
.execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![])
.await?;
self.sql
.set_raw_config_bool(self, "attach_selfavatar", true)
@@ -205,6 +201,22 @@ impl Context {
None => self.sql.set_raw_config(self, key, None).await,
}
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_inbox(InterruptInfo::new(false, None)).await;
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_sentbox(InterruptInfo::new(false, None))
.await;
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_mvbox(InterruptInfo::new(false, None)).await;
ret
}
Config::Selfstatus => {
let def = self.stock_str(StockMessage::StatusLine).await;
let val = if value.is_none() || value.unwrap() == def {
@@ -274,7 +286,7 @@ mod tests {
#[async_std::test]
async fn test_selfavatar_outside_blobdir() {
let t = TestContext::new().await;
let t = dummy_context().await;
let avatar_src = t.dir.path().join("avatar.jpg");
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
File::create(&avatar_src)
@@ -303,7 +315,7 @@ mod tests {
#[async_std::test]
async fn test_selfavatar_in_blobdir() {
let t = TestContext::new().await;
let t = dummy_context().await;
let avatar_src = t.ctx.get_blobdir().join("avatar.png");
let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png");
File::create(&avatar_src)
@@ -329,7 +341,7 @@ mod tests {
#[async_std::test]
async fn test_selfavatar_copy_without_recode() {
let t = TestContext::new().await;
let t = dummy_context().await;
let avatar_src = t.dir.path().join("avatar.png");
let avatar_bytes = include_bytes!("../test-data/image/avatar64x64.png");
File::create(&avatar_src)
@@ -353,7 +365,7 @@ mod tests {
#[async_std::test]
async fn test_media_quality_config_option() {
let t = TestContext::new().await;
let t = dummy_context().await;
let media_quality = t.ctx.get_config_int(Config::MediaQuality).await;
assert_eq!(media_quality, 0);
let media_quality = constants::MediaQuality::from_i32(media_quality).unwrap_or_default();

View File

@@ -1,7 +1,5 @@
//! Email accounts autoconfiguration process module
#![forbid(clippy::indexing_slicing)]
mod auto_mozilla;
mod auto_outlook;
mod read_url;
@@ -37,7 +35,7 @@ macro_rules! progress {
impl Context {
/// Checks if the context is already configured.
pub async fn is_configured(&self) -> bool {
self.sql.get_raw_config_bool("configured").await
self.sql.get_raw_config_bool(self, "configured").await
}
/// Configures this account with the currently set parameters.
@@ -70,20 +68,16 @@ impl Context {
async fn inner_configure(&self) -> Result<()> {
info!(self, "Configure ...");
let was_configured_before = self.is_configured().await;
let mut param = LoginParam::from_database(self, "").await;
let success = configure(self, &mut param).await;
if let Some(provider) = provider::get_provider_info(&param.addr) {
if let Some(config_defaults) = &provider.config_defaults {
for def in config_defaults.iter() {
if !self.config_exists(def.key).await {
if !was_configured_before {
if let Some(config_defaults) = &provider.config_defaults {
for def in config_defaults.iter() {
info!(self, "apply config_defaults {}={}", def.key, def.value);
self.set_config(def.key, Some(def.value)).await?;
} else {
info!(
self,
"skip already set config_defaults {}={}", def.key, def.value
);
}
}
}
@@ -627,7 +621,7 @@ mod tests {
#[async_std::test]
async fn test_no_panic_on_bad_credentials() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.await
@@ -641,7 +635,7 @@ mod tests {
#[async_std::test]
async fn test_get_offline_autoconfig() {
let context = TestContext::new().await.ctx;
let context = dummy_context().await.ctx;
let mut params = LoginParam::new();
params.addr = "someone123@example.org".to_string();

View File

@@ -1,6 +1,7 @@
//! # Constants
#![allow(dead_code)]
use deltachat_derive::*;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
@@ -24,11 +25,12 @@ const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
Serialize,
Deserialize,
sqlx::Type,
)]
#[repr(i32)]
#[repr(u8)]
pub enum Blocked {
Not = 0,
Manually = 1,
@@ -41,8 +43,8 @@ impl Default for Blocked {
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
#[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum ShowEmails {
Off = 0,
AcceptedContacts = 1,
@@ -55,8 +57,8 @@ impl Default for ShowEmails {
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
#[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum MediaQuality {
Balanced = 0,
Worse = 1,
@@ -68,7 +70,7 @@ impl Default for MediaQuality {
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum KeyGenType {
Default = 0,
@@ -125,12 +127,13 @@ pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
IntoStaticStr,
Serialize,
Deserialize,
sqlx::Type,
)]
#[repr(i32)]
#[repr(u32)]
pub enum Chattype {
Undefined = 0,
Single = 100,
@@ -240,9 +243,10 @@ pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3;
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
Serialize,
Deserialize,
sqlx::Type,
)]
#[repr(i32)]
pub enum Viewtype {

View File

@@ -1,14 +1,10 @@
//! Contacts module
#![forbid(clippy::indexing_slicing)]
use std::convert::TryFrom;
use async_std::path::PathBuf;
use async_std::prelude::*;
use deltachat_derive::*;
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use sqlx::Row;
use crate::aheader::EncryptPreference;
use crate::chat::ChatId;
@@ -74,7 +70,7 @@ pub struct Contact {
/// Possible origins of a contact.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, sqlx::Type,
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
)]
#[repr(i32)]
pub enum Origin {
@@ -135,31 +131,6 @@ impl Default for Origin {
Origin::Unknown
}
}
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Contact {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
let contact = Self {
id: u32::try_from(row.try_get::<i32, _>("id")?)
.map_err(|err| sqlx::Error::Decode(err.into()))?,
name: row
.try_get::<Option<String>, _>("name")?
.unwrap_or_default(),
authname: row
.try_get::<Option<String>, _>("authname")?
.unwrap_or_default(),
addr: row.try_get::<String, _>("addr")?,
blocked: row
.try_get::<Option<i32>, _>("blocked")?
.unwrap_or_default()
!= 0,
origin: row.try_get("origin")?,
param: row
.try_get::<String, _>("param")?
.parse()
.unwrap_or_default(),
};
Ok(contact)
}
}
impl Origin {
/// Contacts that are known, i. e. they came in via accepted contacts or
@@ -190,18 +161,27 @@ pub enum VerifiedStatus {
impl Contact {
pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
let mut res: Contact = context
let mut res = context
.sql
.query_row(
r#"
SELECT id, name, addr, origin, blocked, authname, param
FROM contacts
WHERE id=?;
"#,
paramsx![contact_id as i32],
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
FROM contacts c
WHERE c.id=?;",
paramsv![contact_id as i32],
|row| {
let contact = Self {
id: contact_id,
name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?,
addr: row.get::<_, String>(1)?,
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
origin: row.get(2)?,
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
};
Ok(contact)
},
)
.await?;
if contact_id == DC_CONTACT_ID_SELF {
res.name = context.stock_str(StockMessage::SelfMsg).await.to_string();
res.addr = context
@@ -287,11 +267,8 @@ SELECT id, name, addr, origin, blocked, authname, param
if context
.sql
.execute(
r#"
UPDATE msgs SET state=?
WHERE from_id=? AND state=?;
"#,
paramsx![MessageState::InNoticed, id as i32, MessageState::InFresh],
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh],
)
.await
.is_ok()
@@ -326,15 +303,15 @@ WHERE from_id=? AND state=?;
if addr_cmp(addr_normalized, addr_self) {
return DC_CONTACT_ID_SELF;
}
let v: i32 = context.sql.query_value(
"SELECT id FROM contacts WHERE addr=? COLLATE NOCASE AND id>? AND origin>=? AND blocked=0;",
paramsx![
context.sql.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
paramsv![
addr_normalized,
DC_CONTACT_ID_LAST_SPECIAL as i32,
min_origin as i32
min_origin as u32,
],
).await.unwrap_or_default();
v as u32
).await.unwrap_or_default()
}
/// Lookup a contact and create it if it does not exist yet.
@@ -405,32 +382,39 @@ WHERE from_id=? AND state=?;
let mut update_authname = false;
let mut row_id = 0;
let res: Result<(i32, String, String, Origin, String), _> = context.sql.query_row(
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row(
"SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;",
paramsx![addr.to_string()],
)
.await;
paramsv![addr.to_string()],
|row| {
let row_id = row.get(0)?;
let row_name: String = row.get(1)?;
let row_addr: String = row.get(2)?;
let row_origin: Origin = row.get(3)?;
let row_authname: String = row.get(4)?;
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = res {
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if (origin >= row_origin || row_name == row_authname)
&& name.as_ref() != row_name
{
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if (origin >= row_origin || row_name == row_authname)
&& name.as_ref() != row_name
{
update_name = true;
}
} else {
update_name = true;
}
} else {
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
} else if origin == Origin::ManuallyCreated && !row_authname.is_empty() {
// no name given on manual edit, this will update the name to the authname
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
} else if origin == Origin::ManuallyCreated && !row_authname.is_empty() {
// no name given on manual edit, this will update the name to the authname
update_name = true;
}
row_id = id as u32;
Ok((row_id, row_name, row_addr, row_origin, row_authname))
},
)
.await {
row_id = id;
if origin as i32 >= row_origin as i32 && addr != row_addr {
update_addr = true;
}
@@ -449,13 +433,9 @@ WHERE from_id=? AND state=?;
.sql
.execute(
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
paramsx![
&new_name,
if update_addr {
addr.to_string()
} else {
row_addr
},
paramsv![
new_name,
if update_addr { addr.to_string() } else { row_addr },
if origin > row_origin {
origin
} else {
@@ -466,7 +446,7 @@ WHERE from_id=? AND state=?;
} else {
row_authname
},
row_id as i32
row_id
],
)
.await
@@ -477,7 +457,7 @@ WHERE from_id=? AND state=?;
// This is one of the few duplicated data, however, getting the chat list is easier this way.
context.sql.execute(
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
paramsx![&new_name, Chattype::Single, row_id as i32]
paramsv![new_name, Chattype::Single, row_id]
).await.ok();
}
sth_modified = Modifier::Modified;
@@ -491,17 +471,20 @@ WHERE from_id=? AND state=?;
.sql
.execute(
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
paramsx![
name.as_ref(),
&addr,
paramsv![
name.as_ref().to_string(),
addr,
origin,
if update_authname { name.as_ref() } else { "" }
if update_authname { name.as_ref().to_string() } else { "".to_string() }
],
)
.await
.is_ok()
{
row_id = context.sql.get_rowid("contacts", "addr", &addr).await?;
row_id = context
.sql
.get_rowid(context, "contacts", "addr", &addr)
.await?;
sth_modified = Modifier::Created;
info!(context, "added contact id={} addr={}", row_id, &addr);
} else {
@@ -588,32 +571,35 @@ WHERE from_id=? AND state=?;
.map(|s| s.as_ref().to_string())
.unwrap_or_default()
);
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(
r#"
SELECT c.id FROM contacts c
LEFT JOIN acpeerstates ps ON c.addr=ps.addr
WHERE c.addr!=?
AND c.id>?
AND c.origin>=?
AND c.blocked=0
AND (c.name LIKE ? OR c.addr LIKE ?)
AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0)
ORDER BY LOWER(c.name||c.addr),c.id
"#,
)
.bind(&self_addr)
.bind(DC_CONTACT_ID_LAST_SPECIAL as i32)
.bind(Origin::IncomingReplyTo)
.bind(&s3str_like_cmd)
.bind(&s3str_like_cmd)
.bind(if flag_verified_only { 0i32 } else { 1i32 })
.fetch(&pool);
while let Some(id) = rows.next().await {
let (id,): (i32,) = id?;
ret.push(id as u32);
}
context
.sql
.query_map(
"SELECT c.id FROM contacts c \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
WHERE c.addr!=?1 \
AND c.id>?2 \
AND c.origin>=?3 \
AND c.blocked=0 \
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
ORDER BY LOWER(c.name||c.addr),c.id;",
paramsv![
self_addr,
DC_CONTACT_ID_LAST_SPECIAL as i32,
Origin::IncomingReplyTo,
s3str_like_cmd,
s3str_like_cmd,
if flag_verified_only { 0i32 } else { 1i32 },
],
|row| row.get::<_, i32>(0),
|ids| {
for id in ids {
ret.push(id? as u32);
}
Ok(())
},
)
.await?;
let self_name = context
.get_config(Config::Displayname)
@@ -634,15 +620,17 @@ SELECT c.id FROM contacts c
} else {
add_self = true;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(
"SELECT id FROM contacts WHERE addr!=? AND id>? AND origin>=? AND blocked=0 ORDER BY LOWER(name || addr), id;"
).bind(self_addr).bind(DC_CONTACT_ID_LAST_SPECIAL as i32).bind(0x100).fetch(&pool);
while let Some(id) = rows.next().await {
let (id,): (i32,) = id?;
ret.push(id as u32);
}
context.sql.query_map(
"SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;",
paramsv![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100],
|row| row.get::<_, i32>(0),
|ids| {
for id in ids {
ret.push(id? as u32);
}
Ok(())
}
).await?;
}
if flag_add_self && add_self {
@@ -653,30 +641,32 @@ SELECT c.id FROM contacts c
}
pub async fn get_blocked_cnt(context: &Context) -> usize {
let v: i32 = context
context
.sql
.query_value(
.query_get_value::<isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
)
.await
.unwrap_or_default();
v as usize
.unwrap_or_default() as usize
}
/// Get blocked contacts.
pub async fn get_all_blocked(context: &Context) -> Vec<u32> {
context
.sql
.query_values(
.query_map(
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;",
paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|row| row.get::<_, u32>(0),
|ids| {
ids.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await
.unwrap_or_default()
.into_iter()
.map(|id: i32| id as u32)
.collect()
}
/// Returns a textual summary of the encryption state for the contact.
@@ -691,10 +681,9 @@ SELECT c.id FROM contacts c
let peerstate = Peerstate::from_addr(context, &contact.addr).await;
let loginparam = LoginParam::from_database(context, "configured_").await;
if peerstate.is_ok()
if peerstate.is_some()
&& peerstate
.as_ref()
.ok()
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
.is_some()
{
@@ -763,9 +752,10 @@ SELECT c.id FROM contacts c
let count_contacts: i32 = context
.sql
.query_value(
.query_get_value(
context,
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
paramsx![contact_id as i32],
paramsv![contact_id as i32],
)
.await
.unwrap_or_default();
@@ -773,9 +763,10 @@ SELECT c.id FROM contacts c
let count_msgs: i32 = if count_contacts > 0 {
context
.sql
.query_value(
.query_get_value(
context,
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
paramsx![contact_id as i32, contact_id as i32],
paramsv![contact_id as i32, contact_id as i32],
)
.await
.unwrap_or_default()
@@ -788,7 +779,7 @@ SELECT c.id FROM contacts c
.sql
.execute(
"DELETE FROM contacts WHERE id=?;",
paramsx![contact_id as i32],
paramsv![contact_id as i32],
)
.await
{
@@ -826,7 +817,7 @@ SELECT c.id FROM contacts c
.sql
.execute(
"UPDATE contacts SET param=? WHERE id=?",
paramsx![self.param.to_string(), self.id as i32],
paramsv![self.param.to_string(), self.id as i32],
)
.await?;
Ok(())
@@ -934,7 +925,7 @@ SELECT c.id FROM contacts c
pub async fn is_verified_ex(
&self,
context: &Context,
peerstate: Option<&Peerstate>,
peerstate: Option<&Peerstate<'_>>,
) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
@@ -949,7 +940,7 @@ SELECT c.id FROM contacts c
}
let peerstate = Peerstate::from_addr(context, &self.addr).await;
if let Ok(ps) = peerstate {
if let Some(ps) = peerstate {
if ps.verified_key.is_some() {
return VerifiedStatus::BidirectVerified;
}
@@ -984,15 +975,15 @@ SELECT c.id FROM contacts c
return 0;
}
let v: i32 = context
context
.sql
.query_value(
.query_get_value::<isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>?;",
paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
)
.await
.unwrap_or_default();
v as usize
.unwrap_or_default() as usize
}
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
@@ -1004,7 +995,7 @@ SELECT c.id FROM contacts c
.sql
.exists(
"SELECT id FROM contacts WHERE id=?;",
paramsx![contact_id as i32],
paramsv![contact_id as i32],
)
.await
.unwrap_or_default()
@@ -1015,7 +1006,7 @@ SELECT c.id FROM contacts c
.sql
.execute(
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
paramsx![origin, contact_id as i32, origin],
paramsv![origin, contact_id as i32, origin],
)
.await
.is_ok()
@@ -1038,10 +1029,10 @@ pub fn addr_normalize(addr: &str) -> &str {
let norm = addr.trim();
if norm.starts_with("mailto:") {
norm.get(7..).unwrap_or(norm)
} else {
norm
return &norm[7..];
}
norm
}
fn sanitize_name_and_addr(name: impl AsRef<str>, addr: impl AsRef<str>) -> (String, String) {
@@ -1051,15 +1042,11 @@ fn sanitize_name_and_addr(name: impl AsRef<str>, addr: impl AsRef<str>) -> (Stri
if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
(
if name.as_ref().is_empty() {
captures
.get(1)
.map_or("".to_string(), |m| normalize_name(m.as_str()))
normalize_name(&captures[1])
} else {
name.as_ref().to_string()
},
captures
.get(2)
.map_or("".to_string(), |m| m.as_str().to_string()),
captures[2].to_string(),
)
} else {
(name.as_ref().to_string(), addr.as_ref().to_string())
@@ -1077,7 +1064,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
.sql
.execute(
"UPDATE contacts SET blocked=? WHERE id=?;",
paramsx![new_blocking as i32, contact_id as i32],
paramsv![new_blocking as i32, contact_id as i32],
)
.await
.is_ok()
@@ -1087,23 +1074,13 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...)
if context
.sql
.execute(
r#"
UPDATE chats
SET blocked=?
WHERE type=? AND id
IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);
"#,
paramsx![new_blocking, 100i32, contact_id as i32],
)
.await
.is_ok()
{
Contact::mark_noticed(context, contact_id).await;
context.emit_event(Event::ContactsChanged(None));
}
if context.sql.execute(
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
paramsv![new_blocking, 100, contact_id as i32],
).await.is_ok() {
Contact::mark_noticed(context, contact_id).await;
context.emit_event(Event::ContactsChanged(None));
}
}
}
}
@@ -1136,21 +1113,38 @@ pub(crate) async fn set_profile_image(
/// Normalize a name.
///
/// - Remove quotes (come from some bad MUA implementations)
/// - Convert names as "Petersen, Björn" to "Björn Petersen"
/// - Trims the resulting string
///
/// Typically, this function is not needed as it is called implicitly by `Contact::add_address_book`.
pub fn normalize_name(full_name: impl AsRef<str>) -> String {
let full_name = full_name.as_ref().trim();
let mut full_name = full_name.as_ref().trim();
if full_name.is_empty() {
return full_name.into();
}
match full_name.as_bytes() {
[b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => full_name
.get(1..full_name.len() - 1)
.map_or("".to_string(), |s| s.trim().into()),
_ => full_name.to_string(),
let len = full_name.len();
if len > 1 {
let firstchar = full_name.as_bytes()[0];
let lastchar = full_name.as_bytes()[len - 1];
if firstchar == b'\'' && lastchar == b'\''
|| firstchar == b'\"' && lastchar == b'\"'
|| firstchar == b'<' && lastchar == b'>'
{
full_name = &full_name[1..len - 1];
}
}
if let Some(p1) = full_name.find(',') {
let (last_name, first_name) = full_name.split_at(p1);
let last_name = last_name.trim();
let first_name = (&first_name[1..]).trim();
return format!("{} {}", first_name, last_name);
}
full_name.trim().into()
}
fn cat_fingerprint(
@@ -1241,6 +1235,7 @@ mod tests {
#[test]
fn test_normalize_name() {
assert_eq!(&normalize_name("Doe, John"), "John Doe");
assert_eq!(&normalize_name(" hello world "), "hello world");
assert_eq!(&normalize_name("<"), "<");
assert_eq!(&normalize_name(">"), ">");
@@ -1275,7 +1270,7 @@ mod tests {
#[async_std::test]
async fn test_get_contacts() {
let context = TestContext::new().await;
let context = dummy_context().await;
let contacts = Contact::get_all(&context.ctx, 0, Some("some2"))
.await
.unwrap();
@@ -1299,10 +1294,10 @@ mod tests {
#[async_std::test]
async fn test_is_self_addr() -> Result<()> {
let t = TestContext::new().await;
let t = test_context().await;
assert!(t.ctx.is_self_addr("me@me.org").await.is_err());
let addr = t.configure_alice().await;
let addr = configure_alice_keypair(&t.ctx).await;
assert_eq!(t.ctx.is_self_addr("me@me.org").await?, false);
assert_eq!(t.ctx.is_self_addr(&addr).await?, true);
@@ -1312,7 +1307,7 @@ mod tests {
#[async_std::test]
async fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book()
let t = TestContext::new().await;
let t = dummy_context().await;
let book = concat!(
" Name one \n one@eins.org \n",
"Name two\ntwo@deux.net\n",
@@ -1405,10 +1400,10 @@ mod tests {
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Wonderland, Alice");
assert_eq!(contact.get_display_name(), "Wonderland, Alice");
assert_eq!(contact.get_name(), "Alice Wonderland");
assert_eq!(contact.get_display_name(), "Alice Wonderland");
assert_eq!(contact.get_addr(), "alice@w.de");
assert_eq!(contact.get_name_n_addr(), "Wonderland, Alice (alice@w.de)");
assert_eq!(contact.get_name_n_addr(), "Alice Wonderland (alice@w.de)");
// check SELF
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF)
@@ -1425,7 +1420,7 @@ mod tests {
#[async_std::test]
async fn test_remote_authnames() {
let t = TestContext::new().await;
let t = dummy_context().await;
// incoming mail `From: bob1 <bob@example.org>` - this should init authname and name
let (contact_id, sth_modified) = Contact::add_or_lookup(
@@ -1488,7 +1483,7 @@ mod tests {
#[async_std::test]
async fn test_remote_authnames_create_empty() {
let t = TestContext::new().await;
let t = dummy_context().await;
// manually create "claire@example.org" without a given name
let contact_id = Contact::create(&t.ctx, "", "claire@example.org")
@@ -1535,7 +1530,7 @@ mod tests {
#[async_std::test]
async fn test_remote_authnames_edit_empty() {
let t = TestContext::new().await;
let t = dummy_context().await;
// manually create "dave@example.org"
let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org")
@@ -1579,7 +1574,7 @@ mod tests {
#[async_std::test]
async fn test_name_in_address() {
let t = TestContext::new().await;
let t = dummy_context().await;
let contact_id = Contact::create(&t.ctx, "", "<dave@example.org>")
.await
@@ -1592,7 +1587,7 @@ mod tests {
.await
.unwrap();
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Mueller, Dave");
assert_eq!(contact.get_name(), "Dave Mueller");
assert_eq!(contact.get_addr(), "dave@example.org");
let contact_id = Contact::create(&t.ctx, "name1", "name2 <dave@example.org>")

View File

@@ -76,11 +76,7 @@ pub struct RunningState {
pub fn get_info() -> BTreeMap<&'static str, String> {
let mut res = BTreeMap::new();
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
let version =
String::from_utf8(libsqlite3_sys::SQLITE_VERSION.to_vec()).expect("invalid version");
res.insert("sqlite_version", version);
res.insert("sqlite_version", rusqlite::version().to_string());
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
res.insert("level", "awesome".into());
res
@@ -89,6 +85,8 @@ pub fn get_info() -> BTreeMap<&'static str, String> {
impl Context {
/// Creates new context.
pub async fn new(os_name: String, dbfile: PathBuf) -> Result<Context> {
// pretty_env_logger::try_init_timed().ok();
let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default());
blob_fname.push("-blobs");
@@ -129,8 +127,10 @@ impl Context {
let ctx = Context {
inner: Arc::new(inner),
};
ctx.sql.open(&ctx, &ctx.dbfile, false).await?;
ensure!(
ctx.sql.open(&ctx, &ctx.dbfile, false).await,
"Failed opening sqlite database"
);
Ok(ctx)
}
@@ -263,29 +263,27 @@ impl Context {
let is_configured = self.get_config_int(Config::Configured).await;
let dbversion = self
.sql
.get_raw_config_int("dbversion")
.get_raw_config_int(self, "dbversion")
.await
.unwrap_or_default();
let journal_mode = self
.sql
.query_value("PRAGMA journal_mode;", paramsx![])
.query_get_value(self, "PRAGMA journal_mode;", paramsv![])
.await
.unwrap_or_else(|_| "unknown".to_string());
.unwrap_or_else(|| "unknown".to_string());
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await;
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await;
let bcc_self = self.get_config_int(Config::BccSelf).await;
let prv_key_cnt: Option<i32> = self
let prv_key_cnt: Option<isize> = self
.sql
.query_value("SELECT COUNT(*) FROM keypairs;", paramsx![])
.await
.ok();
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![])
.await;
let pub_key_cnt: Option<i32> = self
let pub_key_cnt: Option<isize> = self
.sql
.query_value("SELECT COUNT(*) FROM acpeerstates;", paramsx![])
.await
.ok();
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
.await;
let fingerprint_str = match SignedPublicKey::load_self(self).await {
Ok(key) => key.fingerprint().hex(),
Err(err) => format!("<key failure: {}>", err),
@@ -297,7 +295,7 @@ impl Context {
let mvbox_move = self.get_config_int(Config::MvboxMove).await;
let folders_configured = self
.sql
.get_raw_config_int("folders_configured")
.get_raw_config_int(self, "folders_configured")
.await
.unwrap_or_default();
@@ -358,22 +356,30 @@ impl Context {
pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
let show_deaddrop: i32 = 0;
self.sql
.query_values(
r#"
SELECT m.id
FROM msgs m
LEFT JOIN contacts ct
ON m.from_id=ct.id
LEFT JOIN chats c
ON m.chat_id=c.id
WHERE m.state=?
AND m.hidden=0
AND m.chat_id>?
AND ct.blocked=0
AND (c.blocked=0 OR c.blocked=?)
ORDER BY m.timestamp DESC,m.id DESC;
"#,
paramsx![10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
.query_map(
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" LEFT JOIN chats c",
" ON m.chat_id=c.id",
" WHERE m.state=?",
" AND m.hidden=0",
" AND m.chat_id>?",
" AND ct.blocked=0",
" AND (c.blocked=0 OR c.blocked=?)",
" ORDER BY m.timestamp DESC,m.id DESC;"
),
paramsv![10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get::<_, MsgId>(0),
|rows| {
let mut ret = Vec::new();
for row in rows {
ret.push(row?);
}
Ok(ret)
},
)
.await
.unwrap_or_default()
@@ -389,36 +395,47 @@ SELECT m.id
let strLikeBeg = format!("{}%", real_query);
let query = if !chat_id.is_unset() {
r#"
SELECT m.id
FROM msgs
LEFT JOIN contacts ct
ON m.from_id=ct.id
WHERE m.chat_id=?
AND m.hidden=0
AND ct.blocked=0
AND (txt LIKE ? OR ct.name LIKE ?)
ORDER BY m.timestamp,m.id;
"#
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" WHERE m.chat_id=?",
" AND m.hidden=0",
" AND ct.blocked=0",
" AND (txt LIKE ? OR ct.name LIKE ?)",
" ORDER BY m.timestamp,m.id;"
)
} else {
r#"
SELECT m.id
FROM msgs m
LEFT JOIN contacts ct
ON m.from_id=ct.id
LEFT JOIN chats c
ON m.chat_id=c.id
WHERE m.chat_id>9
AND m.hidden=0
AND (c.blocked=0 OR c.blocked=?)
AND ct.blocked=0
AND (m.txt LIKE ? OR ct.name LIKE ?)
ORDER BY m.timestamp DESC,m.id DESC;
"#
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" LEFT JOIN chats c",
" ON m.chat_id=c.id",
" WHERE m.chat_id>9",
" AND m.hidden=0",
" AND (c.blocked=0 OR c.blocked=?)",
" AND ct.blocked=0",
" AND (m.txt LIKE ? OR ct.name LIKE ?)",
" ORDER BY m.timestamp DESC,m.id DESC;"
)
};
self.sql
.query_values(query, paramsx![chat_id, strLikeInText, strLikeBeg])
.query_map(
query,
paramsv![chat_id, strLikeInText, strLikeBeg],
|row| row.get::<_, MsgId>("id"),
|rows| {
let mut ret = Vec::new();
for id in rows {
ret.push(id?);
}
Ok(ret)
},
)
.await
.unwrap_or_default()
}
@@ -523,7 +540,7 @@ mod tests {
#[async_std::test]
async fn test_get_fresh_msgs() {
let t = TestContext::new().await;
let t = dummy_context().await;
let fresh = t.ctx.get_fresh_msgs().await;
assert!(fresh.is_empty())
}
@@ -578,13 +595,13 @@ mod tests {
#[async_std::test]
async fn no_crashes_on_context_deref() {
let t = TestContext::new().await;
let t = dummy_context().await;
std::mem::drop(t.ctx);
}
#[async_std::test]
async fn test_get_info() {
let t = TestContext::new().await;
let t = dummy_context().await;
let info = t.ctx.get_info().await;
assert!(info.get("database_dir").is_some());

View File

@@ -1,9 +1,9 @@
use async_std::prelude::*;
use itertools::join;
use mailparse::SingleInfo;
use num_traits::FromPrimitive;
use sha2::{Digest, Sha256};
use mailparse::SingleInfo;
use crate::chat::{self, Chat, ChatId};
use crate::config::Config;
use crate::constants::*;
@@ -45,14 +45,13 @@ pub async fn dc_receive_imf(
) -> Result<()> {
info!(
context,
"Receiving message {}/{}, seen={}...",
"Receiving message {}/{}...",
if !server_folder.as_ref().is_empty() {
server_folder.as_ref()
} else {
"?"
},
server_uid,
seen
);
if std::env::var(crate::DCC_MIME_DEBUG).unwrap_or_default() == "2" {
@@ -622,13 +621,7 @@ async fn add_parts(
// correct message_timestamp, it should not be used before,
// however, we cannot do this earlier as we need from_id to be set
let rcvd_timestamp = time();
let sort_timestamp = calc_sort_timestamp(
context,
*sent_timestamp,
*chat_id,
state == MessageState::InFresh,
)
.await;
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, *chat_id, !seen).await;
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
// unarchive chat
@@ -652,6 +645,9 @@ async fn add_parts(
let icnt = mime_parser.parts.len();
let subject = mime_parser.get_subject().unwrap_or_default();
let mut parts = std::mem::replace(&mut mime_parser.parts, Vec::new());
let server_folder = server_folder.as_ref().to_string();
let location_kml_is = mime_parser.location_kml.is_some();
let is_system_message = mime_parser.is_system_message;
let mime_headers = if save_mime_headers {
@@ -660,43 +656,51 @@ async fn add_parts(
None
};
let sent_timestamp = *sent_timestamp;
let is_hidden = *hidden;
let chat_id = *chat_id;
let is_mdn = !mime_parser.mdn_reports.is_empty();
for part in &mut mime_parser.parts {
let mut txt_raw = "".to_string();
// TODO: can this clone be avoided?
let rfc724_mid = rfc724_mid.to_string();
let is_location_kml =
location_kml_is && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty());
let (new_parts, ids, is_hidden) = context
.sql
.with_conn(move |mut conn| {
let mut ids = Vec::with_capacity(parts.len());
let mut is_hidden = is_hidden;
if is_mdn || is_location_kml {
*hidden = true;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
}
for part in &mut parts {
let mut txt_raw = "".to_string();
let mut stmt = conn.prepare_cached(
"INSERT INTO msgs \
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
bytes, hidden, mime_headers, mime_in_reply_to, mime_references, error) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?, ?);",
)?;
if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
txt_raw = format!("{}\n\n{}", subject, msg_raw);
}
if is_system_message != SystemMessage::Unknown {
part.param.set_int(Param::Cmd, is_system_message as i32);
}
let is_location_kml = location_kml_is
&& icnt == 1
&& (part.msg == "-location-" || part.msg.is_empty());
context
.sql
.execute(
r#"
INSERT INTO msgs (
rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp,
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param,
bytes, hidden, mime_headers, mime_in_reply_to, mime_references)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
"#,
paramsx![
rfc724_mid.to_owned(),
server_folder.as_ref().to_owned(),
if is_mdn || is_location_kml {
is_hidden = true;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
}
if part.typ == Viewtype::Text {
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
txt_raw = format!("{}\n\n{}", subject, msg_raw);
}
if is_system_message != SystemMessage::Unknown {
part.param.set_int(Param::Cmd, is_system_message as i32);
}
stmt.execute(paramsv![
rfc724_mid,
server_folder,
server_uid as i32,
chat_id,
from_id as i32,
@@ -707,30 +711,38 @@ INSERT INTO msgs (
part.typ,
state,
is_dc_message,
part.msg.clone(),
part.msg,
// txt_raw might contain invalid utf8
txt_raw,
part.param.to_string(),
part.bytes as i64,
*hidden,
mime_headers.clone(),
mime_in_reply_to.clone(),
mime_references.clone(),
],
)
.await?;
part.bytes as isize,
is_hidden,
mime_headers,
mime_in_reply_to,
mime_references,
part.error,
])?;
let msg_id = MsgId::new(
context
.sql
.get_rowid("msgs", "rfc724_mid", &rfc724_mid)
.await?,
);
drop(stmt);
ids.push(MsgId::new(crate::sql::get_rowid(
&mut conn,
"msgs",
"rfc724_mid",
&rfc724_mid,
)?));
}
Ok((parts, ids, is_hidden))
})
.await?;
*insert_msg_id = msg_id;
created_db_entries.push((chat_id, msg_id));
if let Some(id) = ids.iter().last() {
*insert_msg_id = *id;
}
*hidden = is_hidden;
created_db_entries.extend(ids.iter().map(|id| (chat_id, *id)));
mime_parser.parts = new_parts;
info!(
context,
"Message has {} parts and is assigned to chat #{}.", icnt, chat_id,
@@ -848,17 +860,18 @@ async fn calc_sort_timestamp(
// get newest non fresh message for this chat
// update sort_timestamp if less than that
if is_fresh_msg {
let last_msg_time: Result<i32, _> = context
let last_msg_time: Option<i64> = context
.sql
.query_value(
.query_get_value(
context,
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?",
paramsx![chat_id, MessageState::InFresh],
paramsv![chat_id, MessageState::InFresh],
)
.await;
if let Ok(last_msg_time) = last_msg_time {
if last_msg_time as i64 > sort_timestamp {
sort_timestamp = last_msg_time as i64;
if let Some(last_msg_time) = last_msg_time {
if last_msg_time > sort_timestamp {
sort_timestamp = last_msg_time;
}
}
}
@@ -1135,7 +1148,7 @@ async fn create_or_lookup_group(
.sql
.execute(
"UPDATE chats SET name=? WHERE id=?;",
paramsx![grpname, chat_id],
paramsv![grpname.to_string(), chat_id],
)
.await
.is_ok()
@@ -1171,7 +1184,7 @@ async fn create_or_lookup_group(
.sql
.execute(
"DELETE FROM chats_contacts WHERE chat_id=?;",
paramsx![chat_id],
paramsv![chat_id],
)
.await
.ok();
@@ -1253,10 +1266,10 @@ async fn create_or_lookup_adhoc_group(
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids).await?;
if !chat_ids.is_empty() {
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
let res: Result<(ChatId, Option<Blocked>), _> = context
let res = context
.sql
.query_row(
&format!(
format!(
"SELECT c.id,
c.blocked
FROM chats c
@@ -1268,13 +1281,19 @@ async fn create_or_lookup_adhoc_group(
LIMIT 1;",
chat_ids_str
),
paramsx![],
paramsv![],
|row| {
Ok((
row.get::<_, ChatId>(0)?,
row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(),
))
},
)
.await;
if let Ok((id, id_blocked)) = res {
/* success, chat found */
return Ok((id, id_blocked.unwrap_or_default()));
return Ok((id, id_blocked));
}
}
@@ -1306,7 +1325,7 @@ async fn create_or_lookup_adhoc_group(
// create a new ad-hoc group
// - there is no need to check if this group exists; otherwise we would have caught it above
let grpid = create_adhoc_grp_id(context, &member_ids).await?;
let grpid = create_adhoc_grp_id(context, &member_ids).await;
if grpid.is_empty() {
warn!(
context,
@@ -1346,7 +1365,7 @@ async fn create_group_record(
) -> ChatId {
if context.sql.execute(
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);",
paramsx![
paramsv![
if VerifiedStatus::Unverified != create_verified {
Chattype::VerifiedGroup
} else {
@@ -1355,7 +1374,7 @@ async fn create_group_record(
grpname.as_ref(),
grpid.as_ref(),
create_blocked,
time()
time(),
],
).await
.is_err()
@@ -1370,7 +1389,7 @@ async fn create_group_record(
}
let row_id = context
.sql
.get_rowid("chats", "grpid", grpid.as_ref())
.get_rowid(context, "chats", "grpid", grpid.as_ref())
.await
.unwrap_or_default();
@@ -1385,7 +1404,7 @@ async fn create_group_record(
chat_id
}
async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> Result<String> {
async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
/* algorithm:
- sort normalized, lowercased, e-mail addresses alphabetically
- put all e-mail addresses into a single string, separate the address by a single comma
@@ -1399,20 +1418,30 @@ async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> Result<St
.unwrap_or_else(|| "no-self".to_string())
.to_lowercase();
let query = format!(
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
member_ids_str
);
let mut addrs: Vec<String> = context.sql.query_values(&query, paramsx![]).await?;
addrs.sort();
let members = context
.sql
.query_map(
format!(
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
member_ids_str
),
paramsv![],
|row| row.get::<_, String>(0),
|rows| {
let mut addrs = rows.collect::<std::result::Result<Vec<_>, _>>()?;
addrs.sort();
let mut acc = member_cs.clone();
for addr in &addrs {
acc += ",";
acc += &addr.to_lowercase();
}
Ok(acc)
},
)
.await
.unwrap_or_else(|_| member_cs);
let mut acc = member_cs;
for addr in &addrs {
acc += ",";
acc += &addr.to_lowercase();
}
Ok(hex_hash(&acc))
hex_hash(&members)
}
fn hex_hash(s: impl AsRef<str>) -> String {
@@ -1439,7 +1468,8 @@ async fn search_chat_ids_by_contact_ids(
if !contact_ids.is_empty() {
contact_ids.sort();
let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ",");
let query = format!(
context.sql.query_map(
format!(
"SELECT DISTINCT cc.chat_id, cc.contact_id
FROM chats_contacts cc
LEFT JOIN chats c ON c.id=cc.chat_id
@@ -1448,37 +1478,37 @@ async fn search_chat_ids_by_contact_ids(
AND cc.contact_id!=1
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
contact_ids_str
);
),
paramsv![],
|row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)),
|rows| {
let mut last_chat_id = ChatId::new(0);
let mut matches = 0;
let mut mismatches = 0;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(&query).fetch(&pool);
for row in rows {
let (chat_id, contact_id) = row?;
if chat_id != last_chat_id {
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
last_chat_id = chat_id;
matches = 0;
mismatches = 0;
}
if matches < contact_ids.len() && contact_id == contact_ids[matches] {
matches += 1;
} else {
mismatches += 1;
}
}
let mut last_chat_id = ChatId::new(0);
let mut matches = 0;
let mut mismatches = 0;
while let Some(row) = rows.next().await {
let (chat_id, contact_id): (ChatId, i32) = row?;
let contact_id = contact_id as u32;
if chat_id != last_chat_id {
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
last_chat_id = chat_id;
matches = 0;
mismatches = 0;
Ok(())
}
if matches < contact_ids.len() && contact_id == contact_ids[matches] {
matches += 1;
} else {
mismatches += 1;
}
}
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
).await?;
}
}
@@ -1502,10 +1532,8 @@ async fn check_verified_properties(
if from_id != DC_CONTACT_ID_SELF {
let peerstate = Peerstate::from_addr(context, contact.get_addr()).await;
if peerstate.is_err()
|| contact
.is_verified_ex(context, peerstate.as_ref().ok())
.await
if peerstate.is_none()
|| contact.is_verified_ex(context, peerstate.as_ref()).await
!= VerifiedStatus::BidirectVerified
{
bail!(
@@ -1514,7 +1542,7 @@ async fn check_verified_properties(
);
}
if let Ok(peerstate) = peerstate {
if let Some(peerstate) = peerstate {
ensure!(
peerstate.has_verified_key(&mimeparser.signatures),
"The message was sent with non-verified encryption."
@@ -1531,30 +1559,36 @@ async fn check_verified_properties(
}
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
let query = format!(
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
let rows = context
.sql
.query_map(
format!(
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
to_ids_str
);
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(&query).fetch(&pool);
while let Some(row) = rows.next().await {
let (to_addr, is_verified): (String, i32) = row?;
to_ids_str
),
paramsv![],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))),
|rows| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
for (to_addr, _is_verified) in rows.into_iter() {
info!(
context,
"check_verified_properties: {:?} self={:?}",
to_addr,
context.is_self_addr(&to_addr).await
);
let mut is_verified = is_verified != 0;
let mut is_verified = _is_verified != 0;
let peerstate = Peerstate::from_addr(context, &to_addr).await;
// mark gossiped keys (if any) as verified
if mimeparser.gossipped_addr.contains(&to_addr) {
if let Ok(mut peerstate) = peerstate {
if let Some(mut peerstate) = peerstate {
// if we're here, we know the gossip key is verified:
// - use the gossip-key as verified-key if there is no verified-key
// - OR if the verified-key does not match public-key or gossip-key
@@ -1644,7 +1678,7 @@ async fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
LEFT JOIN chats c ON m.chat_id=c.id \
WHERE m.rfc724_mid=? \
AND m.chat_id>9 AND c.blocked=0;",
paramsx![rfc724_mid],
paramsv![rfc724_mid],
)
.await
.unwrap_or_default()
@@ -1690,7 +1724,7 @@ async fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
.sql
.exists(
"SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;",
paramsx![rfc724_mid],
paramsv![rfc724_mid],
)
.await
.unwrap_or_default()
@@ -1767,7 +1801,7 @@ mod tests {
#[async_std::test]
async fn test_grpid_simple() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"From: hello\n\
Subject: outer-subject\n\
In-Reply-To: <lqkjwelq123@123123>\n\
@@ -1784,7 +1818,7 @@ mod tests {
#[async_std::test]
async fn test_grpid_from_multiple() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"From: hello\n\
Subject: outer-subject\n\
In-Reply-To: <Gr.HcxyMARjyJy.9-qweqwe@asd.net>\n\
@@ -1821,7 +1855,7 @@ mod tests {
#[async_std::test]
async fn test_is_known_rfc724_mid() {
let t = TestContext::new().await;
let t = dummy_context().await;
let mut msg = Message::new(Viewtype::Text);
msg.text = Some("first message".to_string());
let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg))
@@ -1837,7 +1871,7 @@ mod tests {
#[async_std::test]
async fn test_is_msgrmsg_rfc724_mid() {
let t = TestContext::new().await;
let t = dummy_context().await;
let mut msg = Message::new(Viewtype::Text);
msg.text = Some("first message".to_string());
let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg))
@@ -1851,34 +1885,34 @@ mod tests {
assert!(!is_msgrmsg_rfc724_mid(&t.ctx, "nonexistant@message.id").await);
}
static MSGRMSG: &[u8] = b"From: Bob <bob@example.com>\n\
To: alice@example.com\n\
static MSGRMSG: &[u8] = b"From: Bob <bob@example.org>\n\
To: alice@example.org\n\
Chat-Version: 1.0\n\
Subject: Chat: hello\n\
Message-ID: <Mr.1111@example.com>\n\
Message-ID: <Mr.1111@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:55 +0000\n\
\n\
hello\n";
static ONETOONE_NOREPLY_MAIL: &[u8] = b"From: Bob <bob@example.com>\n\
To: alice@example.com\n\
static ONETOONE_NOREPLY_MAIL: &[u8] = b"From: Bob <bob@example.org>\n\
To: alice@example.org\n\
Subject: Chat: hello\n\
Message-ID: <2222@example.com>\n\
Message-ID: <2222@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n";
static GRP_MAIL: &[u8] = b"From: bob@example.com\n\
To: alice@example.com, claire@example.com\n\
static GRP_MAIL: &[u8] = b"From: bob@example.org\n\
To: alice@example.org, claire@example.org\n\
Subject: group with Alice, Bob and Claire\n\
Message-ID: <3333@example.com>\n\
Message-ID: <3333@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
\n\
hello\n";
#[async_std::test]
async fn test_adhoc_group_show_chats_only() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
assert_eq!(t.ctx.get_config_int(Config::ShowEmails).await, 0);
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
@@ -1905,7 +1939,7 @@ mod tests {
#[async_std::test]
async fn test_adhoc_group_show_accepted_contact_unknown() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
t.ctx
.set_config(Config::ShowEmails, Some("1"))
.await
@@ -1921,12 +1955,12 @@ mod tests {
#[async_std::test]
async fn test_adhoc_group_show_accepted_contact_known() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
t.ctx
.set_config(Config::ShowEmails, Some("1"))
.await
.unwrap();
Contact::create(&t.ctx, "Bob", "bob@example.com")
Contact::create(&t.ctx, "Bob", "bob@example.org")
.await
.unwrap();
dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 1, false)
@@ -1941,7 +1975,7 @@ mod tests {
#[async_std::test]
async fn test_adhoc_group_show_accepted_contact_accepted() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
t.ctx
.set_config(Config::ShowEmails, Some("1"))
.await
@@ -1961,32 +1995,14 @@ mod tests {
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Single);
assert_eq!(chat.name, "Bob");
assert_eq!(
chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
1
);
assert_eq!(
chat::get_chat_msgs(&t.ctx, chat_id, 0, None)
.await
.unwrap()
.len(),
1
);
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 1);
assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 1);
// receive a non-delta-message from Bob, shows up because of the show_emails setting
dc_receive_imf(&t.ctx, ONETOONE_NOREPLY_MAIL, "INBOX", 2, false)
.await
.unwrap();
assert_eq!(
chat::get_chat_msgs(&t.ctx, chat_id, 0, None)
.await
.unwrap()
.len(),
2
);
assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 2);
// let Bob create an adhoc-group by a non-delta-message, shows up because of the show_emails setting
dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 3, false)
@@ -2000,18 +2016,12 @@ mod tests {
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Group);
assert_eq!(chat.name, "group with Alice, Bob and Claire");
assert_eq!(
chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
3
);
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3);
}
#[async_std::test]
async fn test_adhoc_group_show_all() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
t.ctx
.set_config(Config::ShowEmails, Some("2"))
.await
@@ -2030,19 +2040,13 @@ mod tests {
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Group);
assert_eq!(chat.name, "group with Alice, Bob and Claire");
assert_eq!(
chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
3
);
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3);
}
#[async_std::test]
async fn test_read_receipt_and_unarchive() {
// create alice's account
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
// create one-to-one with bob, archive one-to-one
let bob_id = Contact::create(&t.ctx, "bob", "bob@exampel.org")
@@ -2062,10 +2066,7 @@ mod tests {
.unwrap();
chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await;
assert_eq!(
chat::get_chat_msgs(&t.ctx, group_id, 0, None)
.await
.unwrap()
.len(),
chat::get_chat_msgs(&t.ctx, group_id, 0, None).await.len(),
0
);
group_id
@@ -2088,14 +2089,14 @@ mod tests {
dc_receive_imf(
&t.ctx,
format!(
"From: alice@example.com\n\
To: bob@example.com\n\
"From: alice@example.org\n\
To: bob@example.org\n\
Subject: foo\n\
Message-ID: <Gr.{}.12345678901@example.com>\n\
Message-ID: <Gr.{}.12345678901@example.org>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: {}\n\
Chat-Group-Name: foo\n\
Chat-Disposition-Notification-To: alice@example.com\n\
Chat-Disposition-Notification-To: alice@example.org\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
\n\
hello\n",
@@ -2108,9 +2109,7 @@ mod tests {
)
.await
.unwrap();
let msgs = chat::get_chat_msgs(&t.ctx, group_id, 0, None)
.await
.unwrap();
let msgs = chat::get_chat_msgs(&t.ctx, group_id, 0, None).await;
assert_eq!(msgs.len(), 1);
let msg_id = msgs.first().unwrap();
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone())
@@ -2126,12 +2125,12 @@ mod tests {
dc_receive_imf(
&t.ctx,
format!(
"From: bob@example.com\n\
To: alice@example.com\n\
"From: bob@example.org\n\
To: alice@example.org\n\
Subject: message opened\n\
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
Chat-Version: 1.0\n\
Message-ID: <Mr.12345678902@example.com>\n\
Message-ID: <Mr.12345678902@example.org>\n\
Content-Type: multipart/report; report-type=disposition-notification; boundary=\"SNIPP\"\n\
\n\
\n\
@@ -2145,9 +2144,9 @@ mod tests {
Content-Type: message/disposition-notification\n\
\n\
Reporting-UA: Delta Chat 1.28.0\n\
Original-Recipient: rfc822;bob@example.com\n\
Final-Recipient: rfc822;bob@example.com\n\
Original-Message-ID: <Gr.{}.12345678901@example.com>\n\
Original-Recipient: rfc822;bob@example.org\n\
Final-Recipient: rfc822;bob@example.org\n\
Original-Message-ID: <Gr.{}.12345678901@example.org>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n\
\n\
@@ -2161,10 +2160,7 @@ mod tests {
)
.await.unwrap();
assert_eq!(
chat::get_chat_msgs(&t.ctx, group_id, 0, None)
.await
.unwrap()
.len(),
chat::get_chat_msgs(&t.ctx, group_id, 0, None).await.len(),
1
);
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone())
@@ -2190,7 +2186,7 @@ mod tests {
// are very rare, however, we have to add them to the database (they go to the
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
let context = &t.ctx;
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
@@ -2198,9 +2194,9 @@ mod tests {
dc_receive_imf(
context,
b"To: bob@example.com\n\
b"To: bob@example.org\n\
Subject: foo\n\
Message-ID: <3924@example.com>\n\
Message-ID: <3924@example.org>\n\
Chat-Version: 1.0\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
\n\
@@ -2219,7 +2215,7 @@ mod tests {
#[async_std::test]
async fn test_escaped_from() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
let contact_id = Contact::create(&t.ctx, "foobar", "foobar@example.com")
.await
.unwrap();
@@ -2229,9 +2225,9 @@ mod tests {
dc_receive_imf(
&t.ctx,
b"From: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= <foobar@example.com>\n\
To: alice@example.com\n\
To: alice@example.org\n\
Subject: foo\n\
Message-ID: <asdklfjjaweofi@example.com>\n\
Message-ID: <asdklfjjaweofi@example.org>\n\
Chat-Version: 1.0\n\
Chat-Disposition-Notification-To: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= <foobar@example.com>\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
@@ -2246,9 +2242,9 @@ mod tests {
.await
.unwrap()
.get_authname(),
"Имя, Фамилия",
"Фамилия Имя", // The name was "Имя, Фамилия" and ("lastname, firstname") and should be swapped to "firstname, lastname"
);
let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap();
let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await;
assert_eq!(msgs.len(), 1);
let msg_id = msgs.first().unwrap();
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone())
@@ -2261,7 +2257,7 @@ mod tests {
#[async_std::test]
async fn test_escaped_recipients() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
Contact::create(&t.ctx, "foobar", "foobar@example.com")
.await
.unwrap();
@@ -2275,10 +2271,10 @@ mod tests {
dc_receive_imf(
&t.ctx,
b"From: Foobar <foobar@example.com>\n\
To: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= alice@example.com\n\
To: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= alice@example.org\n\
Cc: =?utf-8?q?=3Ch2=3E?= <carl@host.tld>\n\
Subject: foo\n\
Message-ID: <asdklfjjaweofi@example.com>\n\
Message-ID: <asdklfjjaweofi@example.org>\n\
Chat-Version: 1.0\n\
Chat-Disposition-Notification-To: <foobar@example.com>\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
@@ -2309,7 +2305,7 @@ mod tests {
#[async_std::test]
async fn test_cc_to_contact() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
Contact::create(&t.ctx, "foobar", "foobar@example.com")
.await
.unwrap();
@@ -2327,10 +2323,10 @@ mod tests {
dc_receive_imf(
&t.ctx,
b"From: Foobar <foobar@example.com>\n\
To: alice@example.com\n\
To: alice@example.org\n\
Cc: Carl <carl@host.tld>\n\
Subject: foo\n\
Message-ID: <asdklfjjaweofi@example.com>\n\
Message-ID: <asdklfjjaweofi@example.org>\n\
Chat-Version: 1.0\n\
Chat-Disposition-Notification-To: <foobar@example.com>\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
@@ -2431,8 +2427,7 @@ mod tests {
raw_ndn: &[u8],
error_msg: &str,
) {
let t = TestContext::new().await;
t.configure_addr(self_addr).await;
let t = configured_offline_context_with_addr(self_addr).await;
dc_receive_imf(
&t.ctx,
@@ -2477,19 +2472,18 @@ mod tests {
#[async_std::test]
async fn test_parse_ndn_group_msg() {
let t = TestContext::new().await;
t.configure_addr("alice@gmail.com").await;
let t = configured_offline_context_with_addr("alice@gmail.com").await;
dc_receive_imf(
&t.ctx,
b"From: alice@gmail.com\n\
To: bob@example.com, assidhfaaspocwaeofi@gmail.com\n\
To: bob@example.org, assidhfaaspocwaeofi@gmail.com\n\
Subject: foo\n\
Message-ID: <CADWx9Cs32Wa7Gy-gM0bvbq54P_FEHe7UcsAV=yW7sVVW=fiMYQ@mail.gmail.com>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: abcde\n\
Chat-Group-Name: foo\n\
Chat-Disposition-Notification-To: alice@example.com\n\
Chat-Disposition-Notification-To: alice@example.org\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
\n\
hello\n",
@@ -2512,9 +2506,7 @@ mod tests {
assert_eq!(msg.state, MessageState::OutFailed);
let msgs = chat::get_chat_msgs(&t.ctx, msg.chat_id, 0, None)
.await
.unwrap();
let msgs = chat::get_chat_msgs(&t.ctx, msg.chat_id, 0, None).await;
let last_msg = Message::load_from_db(&t.ctx, *msgs.last().unwrap())
.await
.unwrap();

View File

@@ -568,6 +568,14 @@ impl FromStr for EmailAddress {
}
}
impl rusqlite::types::ToSql for EmailAddress {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let val = rusqlite::types::Value::Text(self.to_string());
let out = rusqlite::types::ToSqlOutput::Owned(val);
Ok(out)
}
}
/// Utility to check if a in the binary represantion of listflags
/// the bit at position bitindex is 1.
pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool {
@@ -766,7 +774,7 @@ mod tests {
#[async_std::test]
async fn test_file_handling() {
let t = TestContext::new().await;
let t = dummy_context().await;
let context = &t.ctx;
macro_rules! dc_file_exist {
($ctx:expr, $fname:expr) => {
@@ -845,7 +853,7 @@ mod tests {
#[async_std::test]
async fn test_create_smeared_timestamp() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_ne!(
dc_create_smeared_timestamp(&t.ctx).await,
dc_create_smeared_timestamp(&t.ctx).await
@@ -861,7 +869,7 @@ mod tests {
#[async_std::test]
async fn test_create_smeared_timestamps() {
let t = TestContext::new().await;
let t = dummy_context().await;
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await;
let next = dc_smeared_time(&t.ctx).await;

View File

@@ -91,7 +91,7 @@ impl EncryptHelper {
context: &Context,
min_verified: PeerstateVerifiedStatus,
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: Vec<(Option<Peerstate>, &str)>,
peerstates: Vec<(Option<Peerstate<'_>>, &str)>,
) -> Result<String> {
let mut keyring: Keyring<SignedPublicKey> = Keyring::new();
@@ -132,7 +132,7 @@ pub async fn try_decrypt(
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
if message_time > 0 {
peerstate = Peerstate::from_addr(context, &from).await.ok();
peerstate = Peerstate::from_addr(context, &from).await;
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
@@ -143,7 +143,7 @@ pub async fn try_decrypt(
peerstate.save_to_db(&context.sql, false).await?;
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(header, message_time);
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true).await?;
peerstate = Some(p);
}
@@ -155,7 +155,7 @@ pub async fn try_decrypt(
let mut signatures = HashSet::default();
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &from).await.ok();
peerstate = Peerstate::from_addr(&context, &from).await;
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
@@ -327,14 +327,14 @@ mod tests {
#[async_std::test]
async fn test_prexisting() {
let t = TestContext::new().await;
let test_addr = t.configure_alice().await;
let t = dummy_context().await;
let test_addr = configure_alice_keypair(&t.ctx).await;
assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr);
}
#[async_std::test]
async fn test_not_configured() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert!(ensure_secret_key_exists(&t.ctx).await.is_err());
}
}

View File

@@ -3,8 +3,6 @@
//! uses [async-email/async-imap](https://github.com/async-email/async-imap)
//! to implement connect, fetch, delete functionality with standard IMAP servers.
#![forbid(clippy::indexing_slicing)]
use std::collections::BTreeMap;
use async_imap::{
@@ -473,7 +471,7 @@ impl Imap {
folder: S,
) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_raw_config(&key).await {
if let Some(entry) = context.sql.get_raw_config(context, &key).await {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
@@ -734,17 +732,9 @@ impl Imap {
folder: S,
server_uids: &[u32],
) -> (Option<u32>, usize) {
let set = match server_uids {
[] => return (None, 0),
[server_uid] => server_uid.to_string(),
[first_uid, .., last_uid] => {
// XXX: it is assumed that UIDs are sorted and
// contiguous. If UIDs are not contiguous, more
// messages than needed will be downloaded.
debug_assert!(first_uid < last_uid, "uids must be sorted");
format!("{}:{}", first_uid, last_uid)
}
};
if server_uids.is_empty() {
return (None, 0);
}
if !self.is_connected() {
warn!(context, "Not connected");
@@ -760,6 +750,15 @@ impl Imap {
let session = self.session.as_mut().unwrap();
let set = if server_uids.len() == 1 {
server_uids[0].to_string()
} else {
let first_uid = server_uids[0];
let last_uid = server_uids[server_uids.len() - 1];
assert!(first_uid < last_uid, "uids must be sorted");
format!("{}:{}", first_uid, last_uid)
};
let mut msgs = match session.uid_fetch(&set, BODY_FLAGS).await {
Ok(msgs) => msgs,
Err(err) => {
@@ -812,6 +811,7 @@ impl Imap {
Ok(_) => Some(server_uid),
Err(err) => {
warn!(context, "dc_receive_imf error: {}", err);
read_errors += 1;
None
}
}
@@ -1129,7 +1129,10 @@ impl Imap {
context: &Context,
create_mvbox: bool,
) -> Result<()> {
let folders_configured = context.sql.get_raw_config_int("folders_configured").await;
let folders_configured = context
.sql
.get_raw_config_int(context, "folders_configured")
.await;
if folders_configured.unwrap_or_default() >= DC_FOLDERS_CONFIGURED_VERSION {
return Ok(());
}
@@ -1295,7 +1298,7 @@ impl Imap {
.sql
.execute(
"UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?",
paramsx![folder],
paramsv![folder],
)
.await
{

View File

@@ -96,21 +96,21 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result
let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
sql.open(context, &path, true).await?;
let curr_backup_time = sql
.get_raw_config_int("backup_time")
.await
.unwrap_or_default();
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
newest_backup_time = curr_backup_time;
if sql.open(context, &path, true).await {
let curr_backup_time = sql
.get_raw_config_int(context, "backup_time")
.await
.unwrap_or_default();
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
newest_backup_time = curr_backup_time;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close().await;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close().await;
}
}
}
match newest_backup_path {
Some(path) => Ok(path.to_string_lossy().into_owned()),
None => bail!("no backup found in {}", dir_name.display()),
@@ -245,7 +245,7 @@ pub fn create_setup_code(_context: &Context) -> String {
}
async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
if !context.sql.get_raw_config_bool("bcc_self").await {
if !context.sql.get_raw_config_bool(context, "bcc_self").await {
let mut msg = Message::new(Viewtype::Text);
// TODO: define this as a stockstring once the wording is settled.
msg.text = Some(
@@ -396,7 +396,6 @@ async fn imex_inner(
Ok(())
}
Err(err) => {
error!(context, "IMEX FAILED: {}", err);
context.emit_event(Event::ImexProgress(0));
bail!("IMEX FAILED to complete: {}", err);
}
@@ -429,35 +428,51 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
);
/* error already logged */
/* re-open copied database file */
context
.sql
.open(&context, &context.get_dbfile(), false)
.await?;
ensure!(
context
.sql
.open(&context, &context.get_dbfile(), false)
.await,
"could not re-open db"
);
delete_and_reset_all_device_msgs(&context).await?;
let total_files_cnt: i32 = context
let total_files_cnt = context
.sql
.query_value("SELECT COUNT(*) FROM backup_blobs;", paramsx![])
.query_get_value::<isize>(context, "SELECT COUNT(*) FROM backup_blobs;", paramsv![])
.await
.unwrap_or_default();
let total_files_cnt = total_files_cnt as usize;
.unwrap_or_default() as usize;
info!(
context,
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
);
let pool = context.sql.get_pool().await?;
let mut files = sqlx::query_as("SELECT file_name, file_content FROM backup_blobs ORDER BY id;")
.fetch(&pool);
let files = context
.sql
.query_map(
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
paramsv![],
|row| {
let name: String = row.get(0)?;
let blob: Vec<u8> = row.get(1)?;
let mut processed_files_cnt = 0;
while let Some(files_result) = files.next().await {
let (file_name, file_blob): (String, Vec<u8>) = files_result?;
Ok((name, blob))
},
|files| {
files
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
let mut all_files_extracted = true;
for (processed_files_cnt, (file_name, file_blob)) in files.into_iter().enumerate() {
if context.shall_stop_ongoing().await {
all_files_extracted = false;
break;
}
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 {
permille = 10
@@ -472,22 +487,19 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
let path_filename = context.get_blobdir().join(file_name);
dc_write_file(context, &path_filename, &file_blob).await?;
processed_files_cnt += 1;
}
ensure!(
processed_files_cnt == total_files_cnt,
"received stop signal"
);
context
.sql
.execute("DROP TABLE backup_blobs;", paramsx![])
.await?;
context.sql.execute("VACUUM;", paramsx![]).await?;
Ok(())
if all_files_extracted {
// only delete backup_blobs if all files were successfully extracted
context
.sql
.execute("DROP TABLE backup_blobs;", paramsv![])
.await?;
context.sql.execute("VACUUM;", paramsv![]).await.ok();
Ok(())
} else {
bail!("received stop signal");
}
}
/*******************************************************************************
@@ -503,9 +515,9 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let dest_path_filename = dc_get_next_backup_path(dir, now).await?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
sql::housekeeping(context).await?;
sql::housekeeping(context).await;
context.sql.execute("VACUUM;", paramsx![]).await.ok();
context.sql.execute("VACUUM;", paramsv![]).await.ok();
// we close the database during the copy of the dbfile
context.sql.close().await;
@@ -519,7 +531,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
context
.sql
.open(&context, &context.get_dbfile(), false)
.await?;
.await;
if !copied {
bail!(
@@ -529,9 +541,17 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
);
}
let dest_sql = Sql::new();
dest_sql.open(context, &dest_path_filename, false).await?;
ensure!(
dest_sql.open(context, &dest_path_filename, false).await,
"could not open exported database {}",
dest_path_string
);
let res = match add_files_to_export(context, &dest_sql).await {
Err(err) => {
dc_delete_file(context, &dest_path_filename).await;
error!(context, "backup failed: {}", err);
Err(err)
}
Ok(()) => {
dest_sql
.set_raw_config_int(context, "backup_time", now as i32)
@@ -539,11 +559,6 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
context.emit_event(Event::ImexFileWritten(dest_path_filename));
Ok(())
}
Err(err) => {
dc_delete_file(context, &dest_path_filename).await;
error!(context, "backup failed: {}", err);
Err(err)
}
};
dest_sql.close().await;
@@ -556,7 +571,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
if !sql.table_exists("backup_blobs").await? {
sql.execute(
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
paramsx![],
paramsv![],
)
.await?;
}
@@ -568,38 +583,41 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
// scan directory, pass 2: copy files
let mut dir_handle = async_std::fs::read_dir(&dir).await?;
sql.with_conn_async(|conn| async move {
// scan directory, pass 2: copy files
let mut dir_handle = async_std::fs::read_dir(&dir).await?;
let mut processed_files_cnt = 0;
while let Some(entry) = dir_handle.next().await {
let entry = entry?;
if context.shall_stop_ongoing().await {
return Ok(());
}
processed_files_cnt += 1;
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
context.emit_event(Event::ImexProgress(permille));
let mut processed_files_cnt = 0;
while let Some(entry) = dir_handle.next().await {
let entry = entry?;
if context.shall_stop_ongoing().await {
return Ok(());
}
processed_files_cnt += 1;
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
context.emit_event(Event::ImexProgress(permille));
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
continue;
}
info!(context, "EXPORT: copying filename={}", name);
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) = dc_read_file(context, &curr_path_filename).await {
if buf.is_empty() {
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
continue;
}
// bail out if we can't insert
sql.execute(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
paramsx![name.as_ref(), buf],
)
.await?;
info!(context, "EXPORT: copying filename={}", name);
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) = dc_read_file(context, &curr_path_filename).await {
if buf.is_empty() {
continue;
}
// bail out if we can't insert
let mut stmt = conn.prepare_cached(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
)?;
stmt.execute(paramsv![name, buf])?;
}
}
}
Ok(())
})
.await?;
Ok(())
}
@@ -662,19 +680,30 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut export_errors = 0;
let pool = context.sql.get_pool().await?;
let keys = context
.sql
.query_map(
"SELECT id, public_key, private_key, is_default FROM keypairs;",
paramsv![],
|row| {
let id = row.get(0)?;
let public_key_blob: Vec<u8> = row.get(1)?;
let public_key = SignedPublicKey::from_slice(&public_key_blob);
let private_key_blob: Vec<u8> = row.get(2)?;
let private_key = SignedSecretKey::from_slice(&private_key_blob);
let is_default: i32 = row.get(3)?;
let mut keys = sqlx::query_as("SELECT id, public_key, private_key, is_default FROM keypairs;")
.fetch(&pool);
while let Some(keys_result) = keys.next().await {
let (id, public_key_blob, private_key_blob, is_default): (i64, Vec<u8>, Vec<u8>, i32) =
keys_result?;
let public_key = SignedPublicKey::from_slice(&public_key_blob);
let private_key = SignedSecretKey::from_slice(&private_key_blob);
Ok((id, public_key, private_key, is_default))
},
|keys| {
keys.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
for (id, public_key, private_key, is_default) in keys {
let id = Some(id).filter(|_| is_default != 0);
if let Ok(key) = public_key {
if export_key_to_asc_file(context, &dir, id, &key)
.await
@@ -698,7 +727,6 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
}
ensure!(export_errors == 0, "errors while exporting keys");
Ok(())
}
@@ -748,9 +776,9 @@ mod tests {
#[async_std::test]
async fn test_render_setup_file() {
let t = TestContext::new().await;
let t = test_context().await;
t.configure_alice().await;
configure_alice_keypair(&t.ctx).await;
let msg = render_setup_file(&t.ctx, "hello").await.unwrap();
println!("{}", &msg);
// Check some substrings, indicating things got substituted.
@@ -767,12 +795,12 @@ mod tests {
#[async_std::test]
async fn test_render_setup_file_newline_replace() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
.await
.unwrap();
t.configure_alice().await;
configure_alice_keypair(&t.ctx).await;
let msg = render_setup_file(&t.ctx, "pw").await.unwrap();
println!("{}", &msg);
assert!(msg.contains("<p>hello<br>there</p>"));
@@ -780,7 +808,7 @@ mod tests {
#[async_std::test]
async fn test_create_setup_code() {
let t = TestContext::new().await;
let t = dummy_context().await;
let setupcode = create_setup_code(&t.ctx);
assert_eq!(setupcode.len(), 44);
assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
@@ -795,7 +823,7 @@ mod tests {
#[async_std::test]
async fn test_export_key_to_asc_file() {
let context = TestContext::new().await;
let context = dummy_context().await;
let key = alice_keypair().public;
let blobdir = "$BLOBDIR";
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key)

View File

@@ -6,13 +6,13 @@
use std::fmt;
use std::future::Future;
use deltachat_derive::{FromSql, ToSql};
use itertools::Itertools;
use rand::{thread_rng, Rng};
use async_smtp::smtp::response::Category;
use async_smtp::smtp::response::Code;
use async_smtp::smtp::response::Detail;
use async_std::prelude::*;
use deltachat_derive::*;
use itertools::Itertools;
use rand::{thread_rng, Rng};
use crate::blob::BlobObject;
use crate::chat::{self, ChatId};
@@ -37,7 +37,7 @@ use crate::{scheduler::InterruptInfo, sql};
const JOB_RETRIES: u32 = 17;
/// Thread IDs
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(i32)]
pub(crate) enum Thread {
Unknown = 0,
@@ -75,7 +75,17 @@ impl Default for Thread {
}
#[derive(
Debug, Display, Copy, Clone, PartialEq, Eq, PartialOrd, FromPrimitive, ToPrimitive, Sqlx,
Debug,
Display,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
)]
#[repr(i32)]
pub enum Action {
@@ -145,32 +155,6 @@ impl fmt::Display for Job {
}
}
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Job {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let foreign_id: i32 = row.try_get("foreign_id")?;
if foreign_id < 0 {
return Err(sqlx::Error::Decode(
anyhow::anyhow!("invalid foreign_id").into(),
));
}
Ok(Job {
job_id: row.try_get::<i32, _>("id")? as u32,
action: row.try_get("action")?,
foreign_id: foreign_id as u32,
desired_timestamp: row.try_get_unchecked("desired_timestamp")?,
added_timestamp: row.try_get_unchecked("added_timestamp")?,
tries: row.try_get::<i32, _>("tries")? as u32,
param: row
.try_get::<String, _>("param")?
.parse()
.unwrap_or_default(),
pending_error: None,
})
}
}
impl Job {
pub fn new(action: Action, foreign_id: u32, param: Params, delay_seconds: i64) -> Self {
let timestamp = time();
@@ -196,7 +180,7 @@ impl Job {
if self.job_id != 0 {
context
.sql
.execute("DELETE FROM jobs WHERE id=?;", paramsx![self.job_id as i32])
.execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32])
.await?;
}
@@ -216,22 +200,22 @@ impl Job {
.sql
.execute(
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
paramsx![
paramsv![
self.desired_timestamp,
self.tries as i64,
self.param.to_string(),
self.job_id as i32
self.job_id as i32,
],
)
.await?;
} else {
context.sql.execute(
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);",
paramsx![
paramsv![
self.added_timestamp,
thread,
self.action,
self.foreign_id as i32,
self.foreign_id,
self.param.to_string(),
self.desired_timestamp
]
@@ -410,32 +394,40 @@ impl Job {
context: &Context,
contact_id: u32,
) -> sql::Result<(Vec<u32>, Vec<String>)> {
// Extract message IDs from job parameters
let res: Vec<(u32, MsgId)> = context
.sql
.query_map(
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
paramsv![contact_id, self.job_id],
|row| {
let job_id: u32 = row.get(0)?;
let params_str: String = row.get(1)?;
let params: Params = params_str.parse().unwrap_or_default();
Ok((job_id, params))
},
|jobs| {
let res = jobs
.filter_map(|row| {
let (job_id, params) = row.ok()?;
let msg_id = params.get_msg_id()?;
Some((job_id, msg_id))
})
.collect();
Ok(res)
},
)
.await?;
// Load corresponding RFC724 message IDs
let mut job_ids = Vec::new();
let mut rfc724_mids = Vec::new();
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?")
.bind(contact_id as i64)
.bind(self.job_id as i64)
.fetch(&pool);
while let Some(row) = rows.next().await {
let (job_id, params): (i64, String) = row?;
let params: Params = params.parse().unwrap_or_default();
let msg_id = params.get_msg_id().unwrap_or_default();
match Message::load_from_db(context, msg_id).await {
Ok(Message { rfc724_mid, .. }) => {
job_ids.push(job_id as u32);
rfc724_mids.push(rfc724_mid);
}
Err(err) => {
warn!(context, "failed to load mdn job message: {}", err);
}
for (job_id, msg_id) in res {
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await {
job_ids.push(job_id);
rfc724_mids.push(rfc724_mid);
}
}
Ok((job_ids, rfc724_mids))
}
@@ -672,27 +664,21 @@ impl Job {
pub async fn kill_action(context: &Context, action: Action) -> bool {
context
.sql
.execute("DELETE FROM jobs WHERE action=?;", paramsx![action])
.execute("DELETE FROM jobs WHERE action=?;", paramsv![action])
.await
.is_ok()
}
/// Remove jobs with specified IDs.
async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
use sqlx::Arguments;
let mut args = sqlx::sqlite::SqliteArguments::default();
for job_id in job_ids {
args.add(*job_id as i32);
}
context
.sql
.execute(
&format!(
format!(
"DELETE FROM jobs WHERE id IN({})",
job_ids.iter().map(|_| "?").join(",")
),
args,
job_ids.iter().map(|i| i as &dyn crate::ToSql).collect(),
)
.await?;
Ok(())
@@ -701,7 +687,7 @@ async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
pub async fn action_exists(context: &Context, action: Action) -> bool {
context
.sql
.exists("SELECT id FROM jobs WHERE action=?;", paramsx![action])
.exists("SELECT id FROM jobs WHERE action=?;", paramsv![action])
.await
.unwrap_or_default()
}
@@ -710,7 +696,11 @@ async fn set_delivered(context: &Context, msg_id: MsgId) {
message::update_msg_state(context, msg_id, MessageState::OutDelivered).await;
let chat_id: ChatId = context
.sql
.query_value("SELECT chat_id FROM msgs WHERE id=?", paramsx![])
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?",
paramsv![msg_id],
)
.await
.unwrap_or_default();
context.emit_event(Event::MsgDelivered { chat_id, msg_id });
@@ -844,12 +834,12 @@ async fn load_imap_deletion_msgid(context: &Context) -> sql::Result<Option<MsgId
context
.sql
.query_value_optional(
r#"
SELECT id FROM msgs
WHERE timestamp < ? AND server_uid != 0
"#,
paramsx![threshold_timestamp],
.query_row_optional(
"SELECT id FROM msgs \
WHERE timestamp < ? \
AND server_uid != 0",
paramsv![threshold_timestamp],
|row| row.get::<_, MsgId>(0),
)
.await
} else {
@@ -985,9 +975,7 @@ async fn perform_job_action(
Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await,
Action::MoveMsg => job.move_msg(context, connection.inbox()).await,
Action::Housekeeping => {
if let Err(err) = sql::housekeeping(context).await {
error!(context, "housekeeping failed: {}", err);
}
sql::housekeeping(context).await;
Status::Finished(Ok(()))
}
};
@@ -1079,8 +1067,9 @@ pub(crate) async fn load_next(
info!(context, "loading job for {}-thread", thread);
let query;
let params: Box<dyn Fn() -> sqlx::sqlite::SqliteArguments<'static> + 'static + Send>;
let params;
let t = time();
let m;
let thread_i = thread as i64;
if let Some(msg_id) = info.msg_id {
@@ -1091,7 +1080,8 @@ WHERE thread=? AND foreign_id=?
ORDER BY action DESC, added_timestamp
LIMIT 1;
"#;
params = Box::new(move || paramsx![thread_i, msg_id]);
m = msg_id;
params = paramsv![thread_i, m];
} else if !info.probe_network {
// processing for first-try and after backoff-timeouts:
// process jobs in the order they were added.
@@ -1102,7 +1092,7 @@ WHERE thread=? AND desired_timestamp<=?
ORDER BY action DESC, added_timestamp
LIMIT 1;
"#;
params = Box::new(move || paramsx![thread_i, t]);
params = paramsv![thread_i, t];
} else {
// processing after call to dc_maybe_network():
// process _all_ pending jobs that failed before
@@ -1114,12 +1104,27 @@ WHERE thread=? AND tries>0
ORDER BY desired_timestamp, action DESC
LIMIT 1;
"#;
params = Box::new(move || paramsx![thread_i]);
params = paramsv![thread_i];
};
let job: Option<Job> = loop {
let p = params();
let job_res = context.sql.query_row_optional(query, p).await;
let job = loop {
let job_res = context
.sql
.query_row_optional(query, params.clone(), |row| {
let job = Job {
job_id: row.get("id")?,
action: row.get("action")?,
foreign_id: row.get("foreign_id")?,
desired_timestamp: row.get("desired_timestamp")?,
added_timestamp: row.get("added_timestamp")?,
tries: row.get("tries")?,
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
pending_error: None,
};
Ok(job)
})
.await;
match job_res {
Ok(job) => break job,
@@ -1128,13 +1133,15 @@ LIMIT 1;
info!(context, "cleaning up job, because of {}", err);
// TODO: improve by only doing a single query
let p = params();
let id: Result<i32, _> = context.sql.query_value(query, p).await;
match id {
match context
.sql
.query_row(query, params.clone(), |row| row.get::<_, i32>(0))
.await
{
Ok(id) => {
context
.sql
.execute("DELETE FROM jobs WHERE id=?;", paramsx![id])
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id])
.await
.ok();
}
@@ -1184,7 +1191,7 @@ mod tests {
"INSERT INTO jobs
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
VALUES (?, ?, ?, ?, ?, ?);",
paramsx![
paramsv![
now,
Thread::from(Action::MoveMsg),
Action::MoveMsg,
@@ -1202,7 +1209,7 @@ mod tests {
// We want to ensure that loading jobs skips over jobs which
// fails to load from the database instead of failing to load
// all jobs.
let t = TestContext::new().await;
let t = dummy_context().await;
insert_job(&t.ctx, -1).await; // This can not be loaded into Job struct.
let jobs = load_next(
&t.ctx,
@@ -1224,7 +1231,7 @@ mod tests {
#[async_std::test]
async fn test_load_next_job_one() {
let t = TestContext::new().await;
let t = dummy_context().await;
insert_job(&t.ctx, 1).await;

View File

@@ -116,21 +116,22 @@ impl DcKey for SignedPublicKey {
type KeyType = SignedPublicKey;
async fn load_self(context: &Context) -> Result<Self::KeyType> {
let res: std::result::Result<Vec<u8>, _> = context
match context
.sql
.query_value(
.query_row(
r#"
SELECT public_key
FROM keypairs
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
AND is_default=1;
"#,
paramsx![],
paramsv![],
|row| row.get::<_, Vec<u8>>(0),
)
.await;
match res {
.await
{
Ok(bytes) => Self::from_slice(&bytes),
Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => {
Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => {
let keypair = generate_keypair(context).await?;
Ok(keypair.public)
}
@@ -160,21 +161,22 @@ impl DcKey for SignedSecretKey {
type KeyType = SignedSecretKey;
async fn load_self(context: &Context) -> Result<Self::KeyType> {
let res: std::result::Result<Vec<u8>, _> = context
match context
.sql
.query_value(
.query_row(
r#"
SELECT private_key
FROM keypairs
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
AND is_default=1;
"#,
paramsx![],
paramsv![],
|row| row.get::<_, Vec<u8>>(0),
)
.await;
match res {
.await
{
Ok(bytes) => Self::from_slice(&bytes),
Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => {
Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => {
let keypair = generate_keypair(context).await?;
Ok(keypair.secret)
}
@@ -225,25 +227,26 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
let _guard = context.generating_key_mutex.lock().await;
// Check if the key appeared while we were waiting on the lock.
let res: std::result::Result<(Vec<u8>, Vec<u8>), _> = context
match context
.sql
.query_row(
r#"
SELECT public_key, private_key
FROM keypairs
WHERE addr=?
WHERE addr=?1
AND is_default=1;
"#,
paramsx![addr.to_string()],
paramsv![addr],
|row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?)),
)
.await;
match res {
.await
{
Ok((pub_bytes, sec_bytes)) => Ok(KeyPair {
addr,
public: SignedPublicKey::from_slice(&pub_bytes)?,
secret: SignedSecretKey::from_slice(&sec_bytes)?,
}),
Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => {
Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => {
let start = std::time::Instant::now();
let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await)
.unwrap_or_default();
@@ -318,14 +321,14 @@ pub async fn store_self_keypair(
.sql
.execute(
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
paramsx![&public_key, &secret_key],
paramsv![public_key, secret_key],
)
.await
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
if default == KeyPairUse::Default {
context
.sql
.execute("UPDATE keypairs SET is_default=0;", paramsx![])
.execute("UPDATE keypairs SET is_default=0;", paramsv![])
.await
.map_err(|err| SaveKeyError::new("failed to clear default", err))?;
}
@@ -337,7 +340,7 @@ pub async fn store_self_keypair(
let addr = keypair.addr.to_string();
let t = time();
let params = paramsx![addr, is_default, public_key, secret_key, t];
let params = paramsv![addr, is_default, public_key, secret_key, t];
context
.sql
.execute(
@@ -547,8 +550,8 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[async_std::test]
async fn test_load_self_existing() {
let alice = alice_keypair();
let t = TestContext::new().await;
t.configure_alice().await;
let t = dummy_context().await;
configure_alice_keypair(&t.ctx).await;
let pubkey = SignedPublicKey::load_self(&t.ctx).await.unwrap();
assert_eq!(alice.public, pubkey);
let seckey = SignedSecretKey::load_self(&t.ctx).await.unwrap();
@@ -557,7 +560,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[async_std::test]
async fn test_load_self_generate_public() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx
.set_config(Config::ConfiguredAddr, Some("alice@example.com"))
.await
@@ -568,7 +571,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[async_std::test]
async fn test_load_self_generate_secret() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx
.set_config(Config::ConfiguredAddr, Some("alice@example.com"))
.await
@@ -581,7 +584,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
async fn test_load_self_generate_concurrent() {
use std::thread;
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx
.set_config(Config::ConfiguredAddr, Some("alice@example.com"))
.await
@@ -608,17 +611,15 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
async fn test_save_self_key_twice() {
// Saving the same key twice should result in only one row in
// the keypairs table.
let t = TestContext::new().await;
let t = dummy_context().await;
let ctx = Arc::new(t.ctx);
let ctx1 = ctx.clone();
let nrows = || async {
let val: i32 = ctx1
.sql
.query_value("SELECT COUNT(*) FROM keypairs;", paramsx![])
ctx1.sql
.query_get_value::<u32>(&ctx1, "SELECT COUNT(*) FROM keypairs;", paramsv![])
.await
.unwrap();
val as usize
.unwrap()
};
assert_eq!(nrows().await, 0);
store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)

View File

@@ -79,8 +79,8 @@ mod tests {
#[async_std::test]
async fn test_keyring_load_self() {
// new_self() implies load_self()
let t = TestContext::new().await;
t.configure_alice().await;
let t = dummy_context().await;
configure_alice_keypair(&t.ctx).await;
let alice = alice_keypair();
let pub_ring: Keyring<SignedPublicKey> = Keyring::new_self(&t.ctx).await.unwrap();

View File

@@ -6,10 +6,16 @@
extern crate num_derive;
#[macro_use]
extern crate smallvec;
#[macro_use]
extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
pub trait ToSql: rusqlite::ToSql + Send + Sync {}
impl<T: rusqlite::ToSql + Send + Sync> ToSql for T {}
#[macro_use]
pub mod log;
#[macro_use]

View File

@@ -1,6 +1,5 @@
//! Location handling
use async_std::prelude::*;
use bitflags::bitflags;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
@@ -38,34 +37,6 @@ impl Location {
}
}
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Location {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let msg_id = row.try_get::<i32, _>("msg_id")? as u32;
let txt: String = row.try_get("txt")?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = Location {
location_id: row.try_get::<i32, _>("id")? as u32,
latitude: row.try_get("latitude")?,
longitude: row.try_get("longitude")?,
accuracy: row.try_get("accuracy")?,
timestamp: row.try_get("timestamp")?,
independent: row.try_get::<i32, _>("independent")? as u32,
msg_id,
contact_id: row.try_get::<i32, _>("from_id")? as u32,
chat_id: row.try_get("chat_id")?,
marker,
};
Ok(loc)
}
}
#[derive(Debug, Clone, Default)]
pub struct Kml {
pub addr: Option<String>,
@@ -226,16 +197,14 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
if context
.sql
.execute(
r#"
UPDATE chats
SET locations_send_begin=?,
locations_send_until=?
WHERE id=?
"#,
paramsx![
"UPDATE chats \
SET locations_send_begin=?, \
locations_send_until=? \
WHERE id=?",
paramsv![
if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 },
chat_id
chat_id,
],
)
.await
@@ -291,60 +260,53 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) ->
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
paramsx![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
paramsv![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
)
.await
.unwrap_or_default()
}
pub async fn set(
context: &Context,
latitude: f64,
longitude: f64,
accuracy: f64,
) -> Result<bool, Error> {
pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
if latitude == 0.0 && longitude == 0.0 {
return Ok(true);
return true;
}
let mut continue_streaming = false;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT id FROM chats WHERE locations_send_until>?;")
.bind(time())
.fetch(&pool);
while let Some(row) = rows.next().await {
let (chat_id,): (i64,) = row?;
if let Err(err) = context
.sql
.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
paramsx![
latitude,
longitude,
accuracy,
time(),
chat_id,
DC_CONTACT_ID_SELF as i32
],
)
.await
{
warn!(context, "failed to store location {:?}", err);
} else {
continue_streaming = true;
if let Ok(chats) = context
.sql
.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
paramsv![time()],
|row| row.get::<_, i32>(0),
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await
{
for chat_id in chats {
if let Err(err) = context.sql.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
paramsv![
latitude,
longitude,
accuracy,
time(),
chat_id,
DC_CONTACT_ID_SELF,
]
).await {
warn!(context, "failed to store location {:?}", err);
} else {
continue_streaming = true;
}
}
if continue_streaming {
context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
};
schedule_maybe_send_locations(context, false).await;
}
if continue_streaming {
context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
};
schedule_maybe_send_locations(context, false).await;
Ok(continue_streaming)
continue_streaming
}
pub async fn get_range(
@@ -357,21 +319,16 @@ pub async fn get_range(
if timestamp_to == 0 {
timestamp_to = time() + 10;
}
context
.sql
.query_rows(
r#"
SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent,
COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt
FROM locations l
LEFT JOIN msgs m ON l.id=m.location_id
WHERE (? OR l.chat_id=?)
AND (? OR l.from_id=?)
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?))
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;
"#,
paramsx![
.query_map(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt \
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
paramsv![
if chat_id.is_unset() { 1 } else { 0 },
chat_id,
if contact_id == 0 { 1 } else { 0 },
@@ -379,6 +336,36 @@ SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent,
timestamp_from,
timestamp_to,
],
|row| {
let msg_id = row.get(6)?;
let txt: String = row.get(9)?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = Location {
location_id: row.get(0)?,
latitude: row.get(1)?,
longitude: row.get(2)?,
accuracy: row.get(3)?,
timestamp: row.get(4)?,
independent: row.get(5)?,
msg_id,
contact_id: row.get(7)?,
chat_id: row.get(8)?,
marker,
};
Ok(loc)
},
|locations| {
let mut ret = Vec::new();
for location in locations {
ret.push(location?);
}
Ok(ret)
},
)
.await
.unwrap_or_default()
@@ -392,7 +379,7 @@ fn is_marker(txt: &str) -> bool {
pub async fn delete_all(context: &Context) -> Result<(), Error> {
context
.sql
.execute("DELETE FROM locations;", paramsx![])
.execute("DELETE FROM locations;", paramsv![])
.await?;
context.emit_event(Event::LocationChanged(None));
Ok(())
@@ -406,10 +393,16 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
.await
.unwrap_or_default();
let (locations_send_begin, locations_send_until, locations_last_sent): (i64, i64, i64) = context.sql.query_row(
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
paramsx![chat_id]
).await?;
paramsv![chat_id], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
})
.await?;
let now = time();
let mut location_count = 0;
@@ -420,41 +413,40 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
self_addr,
);
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(
r#"
SELECT id, latitude, longitude, accuracy, timestamp
FROM locations
WHERE from_id=?
AND timestamp>=?
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?))
AND independent=0
GROUP BY timestamp
ORDER BY timestamp;
"#,
)
.bind(DC_CONTACT_ID_SELF as i32)
.bind(locations_send_begin)
.bind(locations_last_sent)
.bind(DC_CONTACT_ID_SELF as i32)
.fetch(&pool);
while let Some(row) = rows.next().await {
let (location_id, latitude, longitude, accuracy, timestamp): (i32, f64, f64, f64, i64) =
row?;
let timestamp = get_kml_timestamp(timestamp);
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
timestamp,
accuracy,
longitude,
latitude
);
location_count += 1;
last_added_location_id = location_id as u32;
}
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp \
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
paramsv![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
let longitude: f64 = row.get(2)?;
let accuracy: f64 = row.get(3)?;
let timestamp = get_kml_timestamp(row.get(4)?);
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
timestamp,
accuracy,
longitude,
latitude
);
location_count += 1;
last_added_location_id = location_id as u32;
}
Ok(())
}
).await?;
ret += "</Document>\n</kml>";
}
@@ -496,7 +488,7 @@ pub async fn set_kml_sent_timestamp(
.sql
.execute(
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
paramsx![timestamp, chat_id],
paramsv![timestamp, chat_id],
)
.await?;
Ok(())
@@ -511,7 +503,7 @@ pub async fn set_msg_location_id(
.sql
.execute(
"UPDATE msgs SET location_id=? WHERE id=?;",
paramsx![location_id as i32, msg_id],
paramsv![location_id, msg_id],
)
.await?;
@@ -538,51 +530,48 @@ pub async fn save(
accuracy,
..
} = location;
let exists: Option<i32> = context
context
.sql
.query_value_optional(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
paramsx![timestamp, contact_id as i32],
)
.await?;
.with_conn(move |mut conn| {
let mut stmt_test = conn
.prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?;
let mut stmt_insert = conn.prepare_cached(
"INSERT INTO locations\
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
)?;
if independent || exists.is_none() {
context
.sql
.execute(
r#"
INSERT INTO locations
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent)
VALUES (?,?,?,?,?,?,?);
"#,
paramsx![
let exists = stmt_test.exists(paramsv![timestamp, contact_id as i32])?;
if independent || !exists {
stmt_insert.execute(paramsv![
timestamp,
contact_id as i32,
chat_id,
latitude,
longitude,
accuracy,
independent
],
)
.await?;
independent,
])?;
if timestamp > newest_timestamp {
// okay to drop, as we use cached prepared statements
newest_timestamp = timestamp;
newest_location_id = context
.sql
.get_rowid2(
"locations",
"timestamp",
timestamp,
"from_id",
contact_id as i32,
)
.await?;
}
}
if timestamp > newest_timestamp {
// okay to drop, as we use cached prepared statements
drop(stmt_test);
drop(stmt_insert);
newest_timestamp = timestamp;
newest_location_id = crate::sql::get_rowid2(
&mut conn,
"locations",
"timestamp",
timestamp,
"from_id",
contact_id as i32,
)?;
}
}
Ok(())
})
.await?;
}
Ok(newest_location_id)
@@ -596,75 +585,85 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
let pool = match context.sql.get_pool().await {
Ok(pool) => pool,
Err(err) => {
return job::Status::Finished(Err(err.into()));
}
};
let rows = context
.sql
.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
paramsv![now],
|row| {
let chat_id: ChatId = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = true;
let mut rows = sqlx::query_as(
r#"
SELECT id, locations_send_begin, locations_last_sent
FROM chats
WHERE locations_send_until>?;
"#,
)
.bind(now)
.fetch(&pool);
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
rows.filter_map(|v| v.transpose())
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await;
while let Some(row) = rows.next().await {
let (chat_id, locations_send_begin, locations_last_sent): (ChatId, i64, i64) = match row {
Ok(res) => res,
Err(err) => {
warn!(context, "invalid row: {}", err);
continue;
}
};
continue_streaming = true;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
continue;
}
let exists = context
if rows.is_ok() {
let msgs = context
.sql
.exists(
r#"
SELECT id
FROM locations
WHERE from_id=?
AND timestamp>=?
AND timestamp>?
AND independent=0
ORDER BY timestamp;",
"#,
paramsx![
DC_CONTACT_ID_SELF as i32,
locations_send_begin,
locations_last_sent,
],
)
.with_conn(move |conn| {
let rows = rows.unwrap();
let mut stmt_locations = conn.prepare_cached(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
)?;
let mut msgs = Vec::new();
for (chat_id, locations_send_begin, locations_last_sent) in &rows {
if !stmt_locations
.exists(paramsv![
DC_CONTACT_ID_SELF,
*locations_send_begin,
*locations_last_sent,
])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
} else {
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = Message::new(Viewtype::Text);
msg.hidden = true;
msg.param.set_cmd(SystemMessage::LocationOnly);
msgs.push((*chat_id, msg));
}
}
Ok(msgs)
})
.await
.unwrap_or_default();
if !exists {
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
} else {
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = Message::new(Viewtype::Text);
msg.hidden = true;
msg.param.set_cmd(SystemMessage::LocationOnly);
for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling
chat::send_msg(context, chat_id, &mut msg)
.await
@@ -675,7 +674,6 @@ SELECT id
if continue_streaming {
schedule_maybe_send_locations(context, true).await;
}
job::Status::Finished(Ok(()))
}
@@ -689,12 +687,13 @@ pub(crate) async fn job_maybe_send_locations_ended(
let chat_id = ChatId::new(job.foreign_id);
let (send_begin, send_until): (i64, i64) = job_try!(
let (send_begin, send_until) = job_try!(
context
.sql
.query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
paramsx![chat_id],
paramsv![chat_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
)
.await
);
@@ -706,8 +705,8 @@ pub(crate) async fn job_maybe_send_locations_ended(
if !(send_begin == 0 && send_until == 0) {
// not streaming, device-message already sent
job_try!(context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
paramsx![chat_id],
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
paramsv![chat_id],
).await);
let stock_str = context
@@ -723,11 +722,11 @@ pub(crate) async fn job_maybe_send_locations_ended(
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::TestContext;
use crate::test_utils::dummy_context;
#[async_std::test]
async fn test_kml_parse() {
let context = TestContext::new().await;
let context = dummy_context().await;
let xml =
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";

View File

@@ -56,54 +56,63 @@ impl LoginParam {
let key = format!("{}addr", prefix);
let addr = sql
.get_raw_config(key)
.get_raw_config(context, key)
.await
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_raw_config(key).await.unwrap_or_default();
let mail_server = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_raw_config_int(key).await.unwrap_or_default();
let mail_port = sql
.get_raw_config_int(context, key)
.await
.unwrap_or_default();
let key = format!("{}mail_user", prefix);
let mail_user = sql.get_raw_config(key).await.unwrap_or_default();
let mail_user = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_raw_config(key).await.unwrap_or_default();
let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}imap_certificate_checks", prefix);
let imap_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(key).await {
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else {
Default::default()
};
let key = format!("{}send_server", prefix);
let send_server = sql.get_raw_config(key).await.unwrap_or_default();
let send_server = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_raw_config_int(key).await.unwrap_or_default();
let send_port = sql
.get_raw_config_int(context, key)
.await
.unwrap_or_default();
let key = format!("{}send_user", prefix);
let send_user = sql.get_raw_config(key).await.unwrap_or_default();
let send_user = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_raw_config(key).await.unwrap_or_default();
let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}smtp_certificate_checks", prefix);
let smtp_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(key).await {
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else {
Default::default()
};
let key = format!("{}server_flags", prefix);
let server_flags = sql.get_raw_config_int(key).await.unwrap_or_default();
let server_flags = sql
.get_raw_config_int(context, key)
.await
.unwrap_or_default();
LoginParam {
addr,

View File

@@ -1,4 +1,4 @@
use deltachat_derive::*;
use deltachat_derive::{FromSql, ToSql};
use crate::key::Fingerprint;
@@ -22,7 +22,7 @@ pub struct Lot {
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum Meaning {
None = 0,
Text1Draft = 1,
@@ -67,7 +67,7 @@ impl Lot {
}
#[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum LotState {
// Default
Undefined = 0,

View File

@@ -1,8 +1,7 @@
//! # Messages and their identifiers
use async_std::path::{Path, PathBuf};
use async_std::prelude::*;
use deltachat_derive::*;
use deltachat_derive::{FromSql, ToSql};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
@@ -34,20 +33,7 @@ const SUMMARY_CHARACTERS: usize = 160;
/// This type can represent both the special as well as normal
/// messages.
#[derive(
Debug,
Copy,
Clone,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Serialize,
Deserialize,
ToPrimitive,
FromPrimitive,
Sqlx,
Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
)]
pub struct MsgId(u32);
@@ -106,7 +92,7 @@ impl MsgId {
.sql
.execute(
"UPDATE msgs SET chat_id=?, txt='', txt_raw='' WHERE id=?",
paramsx![chat_id, self],
paramsv![chat_id, self],
)
.await?;
@@ -119,11 +105,11 @@ impl MsgId {
// sure they are not left while the message is deleted.
context
.sql
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsx![self])
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![self])
.await?;
context
.sql
.execute("DELETE FROM msgs WHERE id=?;", paramsx![self])
.execute("DELETE FROM msgs WHERE id=?;", paramsv![self])
.await?;
Ok(())
}
@@ -137,12 +123,10 @@ impl MsgId {
context
.sql
.execute(
r#"
UPDATE msgs
SET server_folder='', server_uid=0
WHERE id=?
"#,
paramsx![self],
"UPDATE msgs \
SET server_folder='', server_uid=0 \
WHERE id=?",
paramsv![self],
)
.await?;
Ok(())
@@ -172,6 +156,41 @@ impl std::fmt::Display for MsgId {
}
}
/// Allow converting [MsgId] to an SQLite type.
///
/// This allows you to directly store [MsgId] into the database.
///
/// # Errors
///
/// This **does** ensure that no special message IDs are written into
/// the database and the conversion will fail if this is not the case.
impl rusqlite::types::ToSql for MsgId {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
if self.0 <= DC_MSG_ID_LAST_SPECIAL {
return Err(rusqlite::Error::ToSqlConversionFailure(Box::new(
InvalidMsgId,
)));
}
let val = rusqlite::types::Value::Integer(self.0 as i64);
let out = rusqlite::types::ToSqlOutput::Owned(val);
Ok(out)
}
}
/// Allow converting an SQLite integer directly into [MsgId].
impl rusqlite::types::FromSql for MsgId {
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
// Would be nice if we could use match here, but alas.
i64::column_result(value).and_then(|val| {
if 0 <= val && val <= std::u32::MAX as i64 {
Ok(MsgId::new(val as u32))
} else {
Err(rusqlite::types::FromSqlError::OutOfRange(val))
}
})
}
}
/// Message ID was invalid.
///
/// This usually occurs when trying to use a message ID of
@@ -182,7 +201,16 @@ impl std::fmt::Display for MsgId {
pub struct InvalidMsgId;
#[derive(
Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive, Serialize, Deserialize, Sqlx,
Debug,
Copy,
Clone,
PartialEq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
Serialize,
Deserialize,
)]
#[repr(u8)]
pub(crate) enum MessengerMessage {
@@ -231,58 +259,6 @@ pub struct Message {
pub(crate) param: Params,
}
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Message {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let id = row.try_get("id")?;
let text;
if let Some(buf) = row.try_get_unchecked::<Option<&[u8]>, _>("txt")? {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
eprintln!(
"dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}",
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
} else {
text = "".to_string();
}
Ok(Message {
id,
rfc724_mid: row.try_get::<String, _>("rfc724mid")?,
in_reply_to: row.try_get::<Option<String>, _>("mime_in_reply_to")?,
server_folder: row.try_get::<Option<String>, _>("server_folder")?,
server_uid: row.try_get_unchecked::<i64, _>("server_uid")? as u32,
chat_id: row.try_get("chat_id")?,
from_id: row.try_get::<i32, _>("from_id")? as u32,
to_id: row.try_get::<i32, _>("to_id")? as u32,
timestamp_sort: row.try_get_unchecked("timestamp")?,
timestamp_sent: row.try_get_unchecked("timestamp_sent")?,
timestamp_rcvd: row.try_get_unchecked("timestamp_rcvd")?,
viewtype: row.try_get("type")?,
state: row.try_get("state")?,
is_dc_message: row.try_get("msgrmsg")?,
text: Some(text),
param: row
.try_get::<String, _>("param")?
.parse()
.unwrap_or_default(),
starred: row.try_get::<i32, _>("starred")? > 0,
hidden: row.try_get::<i32, _>("hidden")? > 0,
location_id: row.try_get_unchecked::<i64, _>("location")? as u32,
chat_blocked: row
.try_get::<Option<Blocked>, _>("blocked")?
.unwrap_or_default(),
error: row.try_get("error")?,
})
}
}
impl Message {
pub fn new(viewtype: Viewtype) -> Self {
let mut msg = Message::default();
@@ -296,7 +272,7 @@ impl Message {
!id.is_special(),
"Can not load special message IDs from DB."
);
let msg: Message = context
let msg = context
.sql
.query_row(
concat!(
@@ -325,7 +301,56 @@ impl Message {
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;"
),
paramsx![id],
paramsv![id],
|row| {
let mut msg = Message::default();
// msg.id = row.get::<_, AnyMsgId>("id")?;
msg.id = row.get("id")?;
msg.rfc724_mid = row.get::<_, String>("rfc724mid")?;
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
msg.server_uid = row.get("server_uid")?;
msg.chat_id = row.get("chat_id")?;
msg.from_id = row.get("from_id")?;
msg.to_id = row.get("to_id")?;
msg.timestamp_sort = row.get("timestamp")?;
msg.timestamp_sent = row.get("timestamp_sent")?;
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
msg.viewtype = row.get("type")?;
msg.state = row.get("state")?;
msg.error = row.get("error")?;
msg.is_dc_message = row.get("msgrmsg")?;
let text;
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw("txt") {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
),
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
} else {
text = "".to_string();
}
msg.text = Some(text);
msg.param = row.get::<_, String>("param")?.parse().unwrap_or_default();
msg.starred = row.get("starred")?;
msg.hidden = row.get("hidden")?;
msg.location_id = row.get("location")?;
msg.chat_blocked = row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default();
Ok(msg)
},
)
.await?;
@@ -629,7 +654,7 @@ impl Message {
.sql
.execute(
"UPDATE msgs SET param=? WHERE id=?;",
paramsx![self.param.to_string(), self.id],
paramsv![self.param.to_string(), self.id],
)
.await
.is_ok()
@@ -644,9 +669,10 @@ impl Message {
Eq,
FromPrimitive,
ToPrimitive,
ToSql,
FromSql,
Serialize,
Deserialize,
sqlx::Type,
)]
#[repr(i32)]
pub enum MessageState {
@@ -818,16 +844,29 @@ impl Lot {
}
}
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Error> {
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
let mut ret = String::new();
let msg = Message::load_from_db(context, msg_id).await?;
let msg = Message::load_from_db(context, msg_id).await;
if msg.is_err() {
return ret;
}
let msg = msg.unwrap_or_default();
let rawtxt: Option<String> = context
.sql
.query_value("SELECT txt_raw FROM msgs WHERE id=?;", paramsx![msg_id])
.await?;
.query_get_value(
context,
"SELECT txt_raw FROM msgs WHERE id=?;",
paramsv![msg_id],
)
.await;
if rawtxt.is_none() {
ret += &format!("Cannot load message {}.", msg_id);
return ret;
}
let rawtxt = rawtxt.unwrap_or_default();
let rawtxt = dc_truncate(rawtxt.trim(), 100_000);
@@ -836,7 +875,8 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Er
let name = Contact::load_from_db(context, msg.from_id)
.await
.map(|contact| contact.get_name_n_addr())?;
.map(|contact| contact.get_name_n_addr())
.unwrap_or_default();
ret += &format!(" by {}", name);
ret += "\n";
@@ -853,28 +893,35 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Er
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
// device-internal message, no further details needed
return Ok(ret);
return ret;
}
let pool = context.sql.get_pool().await?;
if let Ok(rows) = context
.sql
.query_map(
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
paramsv![msg_id],
|row| {
let contact_id: i32 = row.get(0)?;
let ts: i64 = row.get(1)?;
Ok((contact_id, ts))
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await
{
for (contact_id, ts) in rows {
let fts = dc_timestamp_to_str(ts);
ret += &format!("Read: {}", fts);
let mut rows =
sqlx::query_as("SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;")
.bind(msg_id)
.fetch(&pool);
let name = Contact::load_from_db(context, contact_id as u32)
.await
.map(|contact| contact.get_name_n_addr())
.unwrap_or_default();
while let Some(row) = rows.next().await {
let (contact_id, ts): (i32, i32) = row?;
let fts = dc_timestamp_to_str(ts as i64);
ret += &format!("Read: {}", fts);
let name = Contact::load_from_db(context, contact_id as u32)
.await
.map(|contact| contact.get_name_n_addr())?;
ret += &format!(" by {}", name);
ret += "\n";
ret += &format!(" by {}", name);
ret += "\n";
}
}
ret += &format!("State: {}", msg.state);
@@ -931,7 +978,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Er
}
}
Ok(ret)
ret
}
pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
@@ -959,12 +1006,12 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
context
.sql
.query_value(
.query_get_value(
context,
"SELECT mime_headers FROM msgs WHERE id=?;",
paramsx![msg_id],
paramsv![msg_id],
)
.await
.ok()
}
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
@@ -1003,7 +1050,7 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
.sql
.execute(
"DELETE FROM locations WHERE independent = 1 AND id=?;",
paramsx![location_id as i32],
paramsv![location_id as i32],
)
.await
.is_ok()
@@ -1014,40 +1061,56 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> bool {
return false;
}
let mut send_event = false;
for id in msg_ids.into_iter() {
let query_res: Result<Option<(MessageState, Option<Blocked>)>, _> = context
.sql
.query_row_optional(
r#"
SELECT
m.state
c.blocked
FROM msgs m LEFT JOIN chats c ON c.id = m.chat_id
WHERE m.id = ? AND m.chat_id > 9
"#,
paramsx![id],
)
.await;
let msgs = context
.sql
.with_conn(move |conn| {
let mut stmt = conn.prepare_cached(concat!(
"SELECT",
" m.state AS state,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=? AND m.chat_id>9"
))?;
if let Ok(Some((state, blocked))) = query_res {
let blocked = blocked.unwrap_or_default();
if blocked == Blocked::Not {
if state == MessageState::InFresh || state == MessageState::InNoticed {
update_msg_state(context, id, MessageState::InSeen).await;
info!(context, "Seen message {}.", id);
job::add(
context,
job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0),
)
.await;
send_event = true;
let mut msgs = Vec::with_capacity(msg_ids.len());
for id in msg_ids.into_iter() {
let query_res = stmt.query_row(paramsv![id], |row| {
Ok((
row.get::<_, MessageState>("state")?,
row.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default(),
))
});
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
continue;
}
} else if state == MessageState::InFresh {
update_msg_state(context, id, MessageState::InNoticed).await;
let (state, blocked) = query_res.map_err(Into::<anyhow::Error>::into)?;
msgs.push((id, state, blocked));
}
Ok(msgs)
})
.await
.unwrap_or_default();
let mut send_event = false;
for (id, curr_state, curr_blocked) in msgs.into_iter() {
if curr_blocked == Blocked::Not {
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
update_msg_state(context, id, MessageState::InSeen).await;
info!(context, "Seen message {}.", id);
job::add(
context,
job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0),
)
.await;
send_event = true;
}
} else if curr_state == MessageState::InFresh {
update_msg_state(context, id, MessageState::InNoticed).await;
send_event = true;
}
}
@@ -1066,7 +1129,7 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
.sql
.execute(
"UPDATE msgs SET state=? WHERE id=?;",
paramsx![state, msg_id],
paramsv![state, msg_id],
)
.await
.is_ok()
@@ -1076,22 +1139,17 @@ pub async fn star_msgs(context: &Context, msg_ids: Vec<MsgId>, star: bool) -> bo
if msg_ids.is_empty() {
return false;
}
for msg_id in msg_ids.into_iter() {
if context
.sql
.execute(
"UPDATE msgs SET starred=? WHERE id=?;",
paramsx![star as i32, msg_id],
)
.await
.is_err()
{
return false;
}
}
true
context
.sql
.with_conn(move |conn| {
let mut stmt = conn.prepare("UPDATE msgs SET starred=? WHERE id=?;")?;
for msg_id in msg_ids.into_iter() {
stmt.execute(paramsv![star as i32, msg_id])?;
}
Ok(())
})
.await
.is_ok()
}
/// Returns a summary test.
@@ -1182,9 +1240,12 @@ pub async fn exists(context: &Context, msg_id: MsgId) -> bool {
let chat_id: Option<ChatId> = context
.sql
.query_value("SELECT chat_id FROM msgs WHERE id=?;", paramsx![msg_id])
.await
.ok();
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
paramsv![msg_id],
)
.await;
if let Some(chat_id) = chat_id {
!chat_id.is_trash()
@@ -1210,7 +1271,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl
.sql
.execute(
"UPDATE msgs SET state=?, error=? WHERE id=?;",
paramsx![msg.state, error, msg_id],
paramsv![msg.state, error, msg_id],
)
.await
{
@@ -1236,19 +1297,28 @@ pub async fn handle_mdn(
return None;
}
let res: Result<(MsgId, ChatId, Chattype, MessageState), _> = context
let res = context
.sql
.query_row(
r#"
SELECT
m.id AS msg_id,
c.id AS chat_id,
c.type AS type,
m.state AS state
FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id
WHERE rfc724_mid=? AND from_id=1
ORDER BY m.id;"#,
paramsx![rfc724_mid],
concat!(
"SELECT",
" m.id AS msg_id,",
" c.id AS chat_id,",
" c.type AS type,",
" m.state AS state",
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
" WHERE rfc724_mid=? AND from_id=1",
" ORDER BY m.id;"
),
paramsv![rfc724_mid],
|row| {
Ok((
row.get::<_, MsgId>("msg_id")?,
row.get::<_, ChatId>("chat_id")?,
row.get::<_, Chattype>("type")?,
row.get::<_, MessageState>("state")?,
))
},
)
.await;
if let Err(ref err) = res {
@@ -1266,7 +1336,7 @@ SELECT
.sql
.exists(
"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;",
paramsx![msg_id, from_id as i32,],
paramsv![msg_id, from_id as i32,],
)
.await
.unwrap_or_default();
@@ -1274,7 +1344,7 @@ SELECT
if !mdn_already_in_table {
context.sql.execute(
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
paramsx![msg_id, from_id as i32, timestamp_sent],
paramsv![msg_id, from_id as i32, timestamp_sent],
)
.await
.unwrap_or_default(); // TODO: better error handling
@@ -1286,16 +1356,15 @@ SELECT
read_by_all = true;
} else {
// send event about new state
let ist_cnt: i32 = context
let ist_cnt = context
.sql
.query_value(
.query_get_value::<isize>(
context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
paramsx![msg_id],
paramsv![msg_id],
)
.await
.unwrap_or_default();
let ist_cnt = ist_cnt as usize;
.unwrap_or_default() as usize;
/*
Groupsize: Min. MDNs
@@ -1336,18 +1405,25 @@ pub(crate) async fn handle_ndn(
return;
}
let res: Result<(MsgId, ChatId, Chattype), _> = context
let res = context
.sql
.query_row(
r#"
SELECT
m.id AS msg_id,
c.id AS chat_id,
c.type AS type
FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id
WHERE rfc724_mid=? AND from_id=1
"#,
paramsx![&failed.rfc724_mid],
concat!(
"SELECT",
" m.id AS msg_id,",
" c.id AS chat_id,",
" c.type AS type",
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
" WHERE rfc724_mid=? AND from_id=1",
),
paramsv![failed.rfc724_mid],
|row| {
Ok((
row.get::<_, MsgId>("msg_id")?,
row.get::<_, ChatId>("chat_id")?,
row.get::<_, Chattype>("type")?,
))
},
)
.await;
if let Err(ref err) = res {
@@ -1385,13 +1461,12 @@ SELECT
pub async fn get_real_msg_cnt(context: &Context) -> i32 {
match context
.sql
.query_value(
r#"
SELECT COUNT(*)
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;
"#,
paramsx![],
.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
paramsv![],
|row| row.get(0),
)
.await
{
@@ -1404,17 +1479,17 @@ SELECT COUNT(*)
}
pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
let res: Result<i32, _> = context
match context
.sql
.query_value(
r#"
SELECT COUNT(*)
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
WHERE c.blocked=2;"#,
paramsx![],
.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE c.blocked=2;",
paramsv![],
|row| row.get::<_, isize>(0),
)
.await;
match res {
.await
{
Ok(res) => res as usize,
Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
@@ -1434,39 +1509,37 @@ pub async fn estimate_deletion_cnt(
.0;
let threshold_timestamp = time() - seconds;
let cnt: i32 = if from_server {
let cnt: isize = if from_server {
context
.sql
.query_value(
r#"SELECT COUNT(*)
.query_row(
"SELECT COUNT(*)
FROM msgs m
WHERE m.id > ?
AND timestamp < ?
AND chat_id != ?
AND server_uid != 0;"#,
paramsx![
DC_MSG_ID_LAST_SPECIAL as i32,
threshold_timestamp,
self_chat_id
],
AND server_uid != 0;",
paramsv![DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id],
|row| row.get(0),
)
.await?
} else {
context
.sql
.query_value(
r#"SELECT COUNT(*)
.query_row(
"SELECT COUNT(*)
FROM msgs m
WHERE m.id > ?
AND timestamp < ?
AND chat_id != ?
AND chat_id != ? AND hidden = 0;"#,
paramsx![
DC_MSG_ID_LAST_SPECIAL as i32,
AND chat_id != ? AND hidden = 0;",
paramsv![
DC_MSG_ID_LAST_SPECIAL,
threshold_timestamp,
self_chat_id,
ChatId::new(DC_CHAT_ID_TRASH)
],
|row| row.get(0),
)
.await?
};
@@ -1481,9 +1554,10 @@ pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
// check the number of messages with the same rfc724_mid
match context
.sql
.query_value(
.query_row(
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0",
paramsx![rfc724_mid],
paramsv![rfc724_mid],
|row| row.get(0),
)
.await
{
@@ -1504,15 +1578,22 @@ pub(crate) async fn rfc724_mid_exists(
return Ok(None);
}
let res: Option<(Option<String>, i32, MsgId)> = context
let res = context
.sql
.query_row_optional(
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
paramsx![rfc724_mid],
paramsv![rfc724_mid],
|row| {
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
let server_uid = row.get(1)?;
let msg_id: MsgId = row.get(2)?;
Ok((server_folder, server_uid, msg_id))
},
)
.await?;
Ok(res.map(|(a, b, c)| (a.unwrap_or_default(), b as u32, c)))
Ok(res)
}
pub async fn update_server_uid(
@@ -1524,8 +1605,9 @@ pub async fn update_server_uid(
match context
.sql
.execute(
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?",
paramsx![server_folder.as_ref(), server_uid as i32, rfc724_mid],
"UPDATE msgs SET server_folder=?, server_uid=? \
WHERE rfc724_mid=?",
paramsv![server_folder.as_ref(), server_uid, rfc724_mid],
)
.await
{
@@ -1563,7 +1645,7 @@ mod tests {
async fn test_prepare_message_and_send() {
use crate::config::Config;
let d = test::TestContext::new().await;
let d = test::dummy_context().await;
let ctx = &d.ctx;
let contact = Contact::create(ctx, "", "dest@example.com")
@@ -1587,7 +1669,7 @@ mod tests {
#[async_std::test]
async fn test_get_summarytext_by_raw() {
let d = test::TestContext::new().await;
let d = test::dummy_context().await;
let ctx = &d.ctx;
let some_text = Some("bla bla".to_string());

View File

@@ -1,4 +1,3 @@
use async_std::prelude::*;
use chrono::TimeZone;
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
@@ -88,26 +87,30 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
if chat.is_self_talk() {
recipients.push((from_displayname.to_string(), from_addr.to_string()));
} else {
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(
r#"
SELECT c.authname, c.addr
FROM chats_contacts cc
LEFT JOIN contacts c ON cc.contact_id=c.id
WHERE cc.chat_id=? AND cc.contact_id>9;
"#,
)
.bind(msg.chat_id)
.fetch(&pool);
while let Some(row) = rows.next().await {
let (authname, addr): (String, String) = row?;
if !recipients_contain_addr(&recipients, &addr) {
recipients.push((authname, addr));
}
}
context
.sql
.query_map(
"SELECT c.authname, c.addr \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
WHERE cc.chat_id=? AND cc.contact_id>9;",
paramsv![msg.chat_id],
|row| {
let authname: String = row.get(0)?;
let addr: String = row.get(1)?;
Ok((authname, addr))
},
|rows| {
for row in rows {
let (authname, addr) = row?;
if !recipients_contain_addr(&recipients, &addr) {
recipients.push((authname, addr));
}
}
Ok(())
},
)
.await?;
let command = msg.param.get_cmd();
@@ -122,15 +125,18 @@ SELECT c.authname, c.addr
.sql
.query_row(
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
paramsx![msg.id],
paramsv![msg.id],
|row| {
let in_reply_to: String = row.get(0)?;
let references: String = row.get(1)?;
Ok((
render_rfc724_mid_list(&in_reply_to),
render_rfc724_mid_list(&references),
))
},
)
.await
.map(|(in_reply_to, references): (String, String)| {
(
render_rfc724_mid_list(&in_reply_to),
render_rfc724_mid_list(&references),
)
})?;
.await?;
let default_str = context
.stock_str(StockMessage::StatusLine)
@@ -205,7 +211,7 @@ SELECT c.authname, c.addr
Ok(res)
}
async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate>, &str)>, Error> {
async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate<'_>>, &str)>, Error> {
let self_addr = self
.context
.get_config(Config::ConfiguredAddr)
@@ -219,7 +225,7 @@ SELECT c.authname, c.addr
.filter(|(_, addr)| addr != &self_addr)
{
res.push((
Peerstate::from_addr(self.context, addr).await.ok(),
Peerstate::from_addr(self.context, addr).await,
addr.as_str(),
));
}
@@ -1204,6 +1210,7 @@ mod tests {
use crate::chatlist::Chatlist;
use crate::dc_receive_imf::dc_receive_imf;
use crate::mimeparser::*;
use crate::test_utils::configured_offline_context;
use crate::test_utils::TestContext;
#[test]
@@ -1293,10 +1300,10 @@ mod tests {
// 1.: Receive a mail from an MUA or Delta Chat
assert_eq!(
msg_to_subject_str(
b"From: Bob <bob@example.com>\n\
To: alice@example.com\n\
b"From: Bob <bob@example.org>\n\
To: alice@example.org\n\
Subject: Antw: Chat: hello\n\
Message-ID: <2222@example.com>\n\
Message-ID: <2222@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
@@ -1307,10 +1314,10 @@ mod tests {
assert_eq!(
msg_to_subject_str(
b"From: Bob <bob@example.com>\n\
To: alice@example.com\n\
b"From: Bob <bob@example.org>\n\
To: alice@example.org\n\
Subject: Infos: 42\n\
Message-ID: <2222@example.com>\n\
Message-ID: <2222@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
@@ -1322,11 +1329,11 @@ mod tests {
// 2. Receive a message from Delta Chat when we did not send any messages before
assert_eq!(
msg_to_subject_str(
b"From: Charlie <charlie@example.com>\n\
To: alice@example.com\n\
b"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: Chat: hello\n\
Chat-Version: 1.0\n\
Message-ID: <2223@example.com>\n\
Message-ID: <2223@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
@@ -1336,11 +1343,10 @@ mod tests {
);
// 3. Send the first message to a new contact
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
assert_eq!(first_subject_str(t).await, "Message from alice@example.org");
assert_eq!(first_subject_str(t).await, "Message from alice@example.com");
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
t.ctx
.set_config(Config::Displayname, Some("Alice"))
.await
@@ -1349,11 +1355,11 @@ mod tests {
// 4. Receive messages with unicode characters and make sure that we do not panic (we do not care about the result)
msg_to_subject_str(
"From: Charlie <charlie@example.com>\n\
To: alice@example.com\n\
"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: äääää\n\
Chat-Version: 1.0\n\
Message-ID: <2893@example.com>\n\
Message-ID: <2893@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
@@ -1362,11 +1368,11 @@ mod tests {
.await;
msg_to_subject_str(
"From: Charlie <charlie@example.com>\n\
To: alice@example.com\n\
"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: aäääää\n\
Chat-Version: 1.0\n\
Message-ID: <2893@example.com>\n\
Message-ID: <2893@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n"
@@ -1377,7 +1383,7 @@ mod tests {
async fn first_subject_str(t: TestContext) -> String {
let contact_id =
Contact::add_or_lookup(&t.ctx, "Dave", "dave@example.com", Origin::ManuallyCreated)
Contact::add_or_lookup(&t.ctx, "Dave", "dave@example.org", Origin::ManuallyCreated)
.await
.unwrap()
.0;
@@ -1401,7 +1407,7 @@ mod tests {
}
async fn msg_to_subject_str(imf_raw: &[u8]) -> String {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
let new_msg = incoming_msg_to_reply_msg(imf_raw, &t.ctx).await;
let mf = MimeFactory::from_msg(&t.ctx, &new_msg, false)
.await
@@ -1439,15 +1445,15 @@ mod tests {
#[async_std::test]
// This test could still be extended
async fn test_render_reply() {
let t = TestContext::new_alice().await;
let t = configured_offline_context().await;
let context = &t.ctx;
let msg = incoming_msg_to_reply_msg(
b"From: Charlie <charlie@example.com>\n\
To: alice@example.com\n\
b"From: Charlie <charlie@example.org>\n\
To: alice@example.org\n\
Subject: Chat: hello\n\
Chat-Version: 1.0\n\
Message-ID: <2223@example.com>\n\
Message-ID: <2223@example.org>\n\
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
\n\
hello\n",
@@ -1458,7 +1464,7 @@ mod tests {
let mimefactory = MimeFactory::from_msg(&t.ctx, &msg, false).await.unwrap();
let recipients = mimefactory.recipients();
assert_eq!(recipients, vec!["charlie@example.com"]);
assert_eq!(recipients, vec!["charlie@example.org"]);
let rendered_msg = mimefactory.render().await.unwrap();

View File

@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::pin::Pin;
use deltachat_derive::*;
use deltachat_derive::{FromSql, ToSql};
use lazy_static::lazy_static;
use lettre_email::mime::{self, Mime};
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
@@ -64,7 +64,7 @@ pub(crate) enum AvatarAction {
Change(String),
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
#[repr(i32)]
pub enum SystemMessage {
Unknown = 0,
@@ -257,8 +257,7 @@ impl MimeMessage {
self.parts[0].msg = "".to_string();
// swap new with old
self.parts.push(filepart); // push to the end
let _ = self.parts.swap_remove(0); // drops first element, replacing it with the last one in O(1)
std::mem::replace(&mut self.parts[0], filepart);
}
}
}
@@ -904,13 +903,16 @@ impl MimeMessage {
/// If you improve heuristics here you might also have to change prefetch_should_download() in imap/mod.rs.
/// Also you should add a test in dc_receive_imf.rs (there already are lots of test_parse_ndn_* tests).
async fn heuristically_parse_ndn(&mut self, context: &Context) -> Option<()> {
let maybe_ndn = if let Some(from) = self.get(HeaderDef::From_) {
let from = from.to_ascii_lowercase();
from.contains("mailer-daemon") || from.contains("mail-daemon")
} else {
false
};
if maybe_ndn && self.failure_report.is_none() {
if self
.get(HeaderDef::Subject)?
.to_ascii_lowercase()
.contains("fail")
&& self
.get(HeaderDef::From_)?
.to_ascii_lowercase()
.contains("mailer-daemon")
&& self.failure_report.is_none()
{
lazy_static! {
static ref RE: regex::Regex = regex::Regex::new(r"Message-ID:(.*)").unwrap();
}
@@ -989,12 +991,12 @@ async fn update_gossip_peerstates(
.iter()
.any(|info| info.addr == header.addr.to_lowercase())
{
let mut peerstate = Peerstate::from_addr(context, &header.addr).await.ok();
let mut peerstate = Peerstate::from_addr(context, &header.addr).await;
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time);
peerstate.save_to_db(&context.sql, false).await?;
} else {
let p = Peerstate::from_gossip(header, message_time);
let p = Peerstate::from_gossip(context, header, message_time);
p.save_to_db(&context.sql, true).await?;
peerstate = Some(p);
}
@@ -1237,7 +1239,7 @@ mod tests {
#[async_std::test]
async fn test_dc_mimeparser_crash() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
@@ -1249,7 +1251,7 @@ mod tests {
#[async_std::test]
async fn test_get_rfc724_mid_exists() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/mail_with_message_id.txt");
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
@@ -1263,7 +1265,7 @@ mod tests {
#[async_std::test]
async fn test_get_rfc724_mid_not_exists() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
@@ -1321,7 +1323,7 @@ mod tests {
#[async_std::test]
async fn test_parse_first_addr() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"From: hello@one.org, world@two.org\n\
Chat-Disposition-Notification-To: wrong\n\
Content-Type: text/plain\n\
@@ -1342,7 +1344,7 @@ mod tests {
#[async_std::test]
async fn test_mimeparser_with_context() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"From: hello\n\
Content-Type: multipart/mixed; boundary=\"==break==\";\n\
Subject: outer-subject\n\
@@ -1392,7 +1394,7 @@ mod tests {
#[async_std::test]
async fn test_mimeparser_with_avatars() {
let t = TestContext::new().await;
let t = dummy_context().await;
let raw = include_bytes!("../test-data/message/mail_attach_txt.eml");
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
@@ -1435,7 +1437,7 @@ mod tests {
#[async_std::test]
async fn test_mimeparser_message_kml() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"Chat-Version: 1.0\n\
From: foo <foo@example.org>\n\
To: bar <bar@example.org>\n\
@@ -1480,7 +1482,7 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\
#[async_std::test]
async fn test_parse_mdn() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
@@ -1530,7 +1532,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
/// multipart MIME messages.
#[async_std::test]
async fn test_parse_multiple_mdns() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
@@ -1606,7 +1608,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
#[async_std::test]
async fn test_parse_mdn_with_additional_message_ids() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
@@ -1661,7 +1663,7 @@ Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
#[async_std::test]
async fn test_parse_inline_attachment() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = br#"Date: Thu, 13 Feb 2020 22:41:20 +0000 (UTC)
From: sender@example.com
To: receiver@example.com
@@ -1701,7 +1703,7 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg==
#[async_std::test]
async fn parse_inline_image() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = br#"Message-ID: <foobar@example.org>
From: foo <foo@example.org>
Subject: example
@@ -1747,7 +1749,7 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I=
#[async_std::test]
async fn parse_thunderbird_html_embedded_image() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = br#"To: Alice <alice@example.org>
From: Bob <bob@example.org>
Subject: Test subject
@@ -1820,7 +1822,7 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I=
// Outlook specifies filename in the "name" attribute of Content-Type
#[async_std::test]
async fn parse_outlook_html_embedded_image() {
let context = TestContext::new().await;
let context = dummy_context().await;
let raw = br##"From: Anonymous <anonymous@example.org>
To: Anonymous <anonymous@example.org>
Subject: Delta Chat is great stuff!

View File

@@ -9,8 +9,6 @@ use serde::Deserialize;
use crate::context::Context;
use crate::dc_tools::*;
use crate::provider;
use crate::provider::Oauth2Authorizer;
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
// see https://developers.google.com/identity/protocols/OAuth2InstalledApp
@@ -95,7 +93,10 @@ pub async fn dc_get_oauth2_access_token(
// read generated token
if !regenerate && !is_expired(context).await {
let access_token = context.sql.get_raw_config("oauth2_access_token").await;
let access_token = context
.sql
.get_raw_config(context, "oauth2_access_token")
.await;
if access_token.is_some() {
// success
return access_token;
@@ -103,10 +104,13 @@ pub async fn dc_get_oauth2_access_token(
}
// generate new token: build & call auth url
let refresh_token = context.sql.get_raw_config("oauth2_refresh_token").await;
let refresh_token = context
.sql
.get_raw_config(context, "oauth2_refresh_token")
.await;
let refresh_token_for = context
.sql
.get_raw_config("oauth2_refresh_token_for")
.get_raw_config(context, "oauth2_refresh_token_for")
.await
.unwrap_or_else(|| "unset".into());
@@ -116,7 +120,7 @@ pub async fn dc_get_oauth2_access_token(
(
context
.sql
.get_raw_config("oauth2_pending_redirect_uri")
.get_raw_config(context, "oauth2_pending_redirect_uri")
.await
.unwrap_or_else(|| "unset".into()),
oauth2.init_token,
@@ -130,7 +134,7 @@ pub async fn dc_get_oauth2_access_token(
(
context
.sql
.get_raw_config("oauth2_redirect_uri")
.get_raw_config(context, "oauth2_redirect_uri")
.await
.unwrap_or_else(|| "unset".into()),
oauth2.refresh_token,
@@ -272,12 +276,8 @@ impl Oauth2 {
.find('@')
.map(|index| addr_normalized.split_at(index + 1).1)
{
if let Some(provider) = provider::get_provider_info(&addr_normalized) {
match &provider.oauth2_authorizer {
Some(Oauth2Authorizer::Gmail) => Some(OAUTH2_GMAIL),
Some(Oauth2Authorizer::Yandex) => Some(OAUTH2_YANDEX),
None => None, // provider known to not support oauth2, no mx-lookup required
}
if let Some(provider) = Oauth2::lookup_whitelist(domain) {
Some(provider)
} else {
Oauth2::lookup_mx(domain).await
}
@@ -286,6 +286,16 @@ impl Oauth2 {
}
}
fn lookup_whitelist(domain: impl AsRef<str>) -> Option<Self> {
let domain = domain.as_ref();
match domain {
"gmail.com" | "googlemail.com" => Some(OAUTH2_GMAIL),
"yandex.com" | "yandex.by" | "yandex.kz" | "yandex.ru" | "yandex.ua" | "ya.ru"
| "narod.ru" => Some(OAUTH2_YANDEX),
_ => None,
}
}
async fn lookup_mx(domain: impl AsRef<str>) -> Option<Self> {
if let Ok(resolver) = resolver(
config::ResolverConfig::default(),
@@ -354,7 +364,7 @@ impl Oauth2 {
async fn is_expired(context: &Context) -> bool {
let expire_timestamp = context
.sql
.get_raw_config_int64("oauth2_timestamp_expires")
.get_raw_config_int64(context, "oauth2_timestamp_expires")
.await
.unwrap_or_default();
@@ -430,7 +440,7 @@ mod tests {
#[async_std::test]
async fn test_dc_get_oauth2_addr() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code).await;
@@ -440,7 +450,7 @@ mod tests {
#[async_std::test]
async fn test_dc_get_oauth2_url() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let addr = "dignifiedquire@gmail.com";
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri).await;
@@ -450,7 +460,7 @@ mod tests {
#[async_std::test]
async fn test_dc_get_oauth2_token() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false).await;

View File

@@ -414,7 +414,7 @@ mod tests {
#[async_std::test]
async fn test_params_file_fs_path() {
let t = TestContext::new().await;
let t = dummy_context().await;
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() {
assert_eq!(p, Path::new("/foo/bar/baz"));
} else {
@@ -424,7 +424,7 @@ mod tests {
#[async_std::test]
async fn test_params_file_blob() {
let t = TestContext::new().await;
let t = dummy_context().await;
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
assert_eq!(b.as_name(), "$BLOBDIR/foo");
} else {
@@ -435,7 +435,7 @@ mod tests {
// Tests for Params::get_file(), Params::get_path() and Params::get_blob().
#[async_std::test]
async fn test_params_get_fileparam() {
let t = TestContext::new().await;
let t = dummy_context().await;
let fname = t.dir.path().join("foo");
let mut p = Params::new();
p.set(Param::File, fname.to_str().unwrap());

View File

@@ -1,8 +1,9 @@
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module
use std::collections::HashSet;
use std::convert::TryFrom;
use std::fmt;
use anyhow::Result;
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::context::Context;
@@ -24,8 +25,8 @@ pub enum PeerstateVerifiedStatus {
}
/// Peerstate represents the state of an Autocrypt peer.
#[derive(Debug, PartialEq, Eq)]
pub struct Peerstate {
pub struct Peerstate<'a> {
pub context: &'a Context,
pub addr: String,
pub last_seen: i64,
pub last_seen_autocrypt: i64,
@@ -41,49 +42,43 @@ pub struct Peerstate {
pub degrade_event: Option<DegradeEvent>,
}
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Peerstate {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
impl<'a> PartialEq for Peerstate<'a> {
fn eq(&self, other: &Peerstate) -> bool {
self.addr == other.addr
&& self.last_seen == other.last_seen
&& self.last_seen_autocrypt == other.last_seen_autocrypt
&& self.prefer_encrypt == other.prefer_encrypt
&& self.public_key == other.public_key
&& self.public_key_fingerprint == other.public_key_fingerprint
&& self.gossip_key == other.gossip_key
&& self.gossip_timestamp == other.gossip_timestamp
&& self.gossip_key_fingerprint == other.gossip_key_fingerprint
&& self.verified_key == other.verified_key
&& self.verified_key_fingerprint == other.verified_key_fingerprint
&& self.to_save == other.to_save
&& self.degrade_event == other.degrade_event
}
}
let mut res = Self::new(row.try_get("addr")?);
impl<'a> Eq for Peerstate<'a> {}
res.last_seen = row.try_get_unchecked("last_seen")?;
res.last_seen_autocrypt = row.try_get_unchecked("last_seen_autocrypt")?;
res.prefer_encrypt = row.try_get("prefer_encrypted")?;
res.gossip_timestamp = row.try_get_unchecked("gossip_timestamp")?;
res.public_key_fingerprint = row
.try_get::<Option<String>, _>("public_key_fingerprint")?
.map(|fp| fp.parse::<Fingerprint>())
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.gossip_key_fingerprint = row
.try_get::<Option<String>, _>("gossip_key_fingerprint")?
.map(|fp| fp.parse::<Fingerprint>())
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.verified_key_fingerprint = row
.try_get::<Option<String>, _>("verified_key_fingerprint")?
.map(|fp| fp.parse::<Fingerprint>())
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.public_key = row
.try_get::<Option<&[u8]>, _>("public_key")?
.map(|blob| SignedPublicKey::from_slice(blob))
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.gossip_key = row
.try_get::<Option<&[u8]>, _>("gossip_key")?
.map(|blob| SignedPublicKey::from_slice(blob))
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.verified_key = row
.try_get::<Option<&[u8]>, _>("verified_key")?
.map(|blob| SignedPublicKey::from_slice(blob))
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
Ok(res)
impl<'a> fmt::Debug for Peerstate<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Peerstate")
.field("addr", &self.addr)
.field("last_seen", &self.last_seen)
.field("last_seen_autocrypt", &self.last_seen_autocrypt)
.field("prefer_encrypt", &self.prefer_encrypt)
.field("public_key", &self.public_key)
.field("public_key_fingerprint", &self.public_key_fingerprint)
.field("gossip_key", &self.gossip_key)
.field("gossip_timestamp", &self.gossip_timestamp)
.field("gossip_key_fingerprint", &self.gossip_key_fingerprint)
.field("verified_key", &self.verified_key)
.field("verified_key_fingerprint", &self.verified_key_fingerprint)
.field("to_save", &self.to_save)
.field("degrade_event", &self.degrade_event)
.finish()
}
}
@@ -104,9 +99,10 @@ pub enum DegradeEvent {
FingerprintChanged = 0x02,
}
impl Peerstate {
pub fn new(addr: String) -> Self {
impl<'a> Peerstate<'a> {
pub fn new(context: &'a Context, addr: String) -> Self {
Peerstate {
context,
addr,
last_seen: 0,
last_seen_autocrypt: 0,
@@ -123,8 +119,8 @@ impl Peerstate {
}
}
pub fn from_header(header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(header.addr.clone());
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context, header.addr.clone());
res.last_seen = message_time;
res.last_seen_autocrypt = message_time;
@@ -136,8 +132,8 @@ impl Peerstate {
res
}
pub fn from_gossip(gossip_header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(gossip_header.addr.clone());
pub fn from_gossip(context: &'a Context, gossip_header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context, gossip_header.addr.clone());
res.gossip_timestamp = message_time;
res.to_save = Some(ToSave::All);
@@ -147,53 +143,76 @@ impl Peerstate {
res
}
pub async fn from_addr(context: &Context, addr: &str) -> Result<Peerstate> {
let query = r#"
SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key,
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint,
verified_key, verified_key_fingerprint
FROM acpeerstates
WHERE addr=? COLLATE NOCASE;
"#;
Self::from_stmt(context, query, paramsx![addr]).await
pub async fn from_addr(context: &'a Context, addr: &str) -> Option<Peerstate<'a>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;";
Self::from_stmt(context, query, paramsv![addr]).await
}
pub async fn from_fingerprint(
context: &Context,
context: &'a Context,
_sql: &Sql,
fingerprint: &Fingerprint,
) -> Result<Peerstate> {
let query = r#"
SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key,
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint,
verified_key, verified_key_fingerprint
FROM acpeerstates
WHERE public_key_fingerprint=? COLLATE NOCASE
OR gossip_key_fingerprint=? COLLATE NOCASE
ORDER BY public_key_fingerprint=? DESC;
"#;
let fingerprint = fingerprint.hex();
Self::from_stmt(
context,
query,
paramsx![&fingerprint, &fingerprint, &fingerprint],
)
.await
) -> Option<Peerstate<'a>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint \
FROM acpeerstates \
WHERE public_key_fingerprint=? COLLATE NOCASE \
OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;";
let fp = fingerprint.hex();
Self::from_stmt(context, query, paramsv![fp, fp, fp]).await
}
async fn from_stmt<'a, P: sqlx::IntoArguments<'a, sqlx::sqlite::Sqlite> + 'a>(
context: &Context,
query: &'a str,
params: P,
) -> Result<Peerstate> {
/* all the above queries start with this: SELECT
addr, last_seen, last_seen_autocrypt, prefer_encrypted,
public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
gossip_key_fingerprint, verified_key, verified_key_fingerprint
*/
let peerstate = context.sql.query_row(query, params).await?;
async fn from_stmt(
context: &'a Context,
query: &str,
params: Vec<&dyn crate::ToSql>,
) -> Option<Peerstate<'a>> {
context
.sql
.query_row(query, params, |row| {
/* all the above queries start with this: SELECT
addr, last_seen, last_seen_autocrypt, prefer_encrypted,
public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
gossip_key_fingerprint, verified_key, verified_key_fingerprint
*/
let mut res = Self::new(context, row.get(0)?);
Ok(peerstate)
res.last_seen = row.get(1)?;
res.last_seen_autocrypt = row.get(2)?;
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
res.gossip_timestamp = row.get(5)?;
res.public_key_fingerprint = row
.get::<_, Option<String>>(7)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.gossip_key_fingerprint = row
.get::<_, Option<String>>(8)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.verified_key_fingerprint = row
.get::<_, Option<String>>(10)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.public_key = row
.get(4)
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
res.gossip_key = row
.get(6)
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
res.verified_key = row
.get(9)
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
Ok(res)
})
.await
.ok()
}
pub fn recalc_fingerprint(&mut self) {
@@ -305,9 +324,11 @@ SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key,
pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> {
if let Some(key) = self.peek_key(min_verified) {
// TODO: avoid cloning
let public_key = SignedPublicKey::try_from(key.clone()).ok()?;
let header = Aheader::new(
self.addr.clone(),
key.clone(), // TODO: avoid cloning
public_key,
// Autocrypt 1.1.0 specification says that
// `prefer-encrypt` attribute SHOULD NOT be included,
// but we include it anyway to propagate encryption
@@ -385,21 +406,19 @@ SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key,
if create {
sql.execute(
"INSERT INTO acpeerstates (addr) VALUES(?);",
paramsx![&self.addr],
paramsv![self.addr],
)
.await?;
}
if self.to_save == Some(ToSave::All) || create {
sql.execute(
r#"
UPDATE acpeerstates
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?,
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?,
verified_key=?, verified_key_fingerprint=?
WHERE addr=?;
"#,
paramsx![
"UPDATE acpeerstates \
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
verified_key=?, verified_key_fingerprint=? \
WHERE addr=?;",
paramsv![
self.last_seen,
self.last_seen_autocrypt,
self.prefer_encrypt as i64,
@@ -410,17 +429,18 @@ UPDATE acpeerstates
self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
&self.addr
self.addr,
],
).await?;
} else if self.to_save == Some(ToSave::Timestamps) {
sql.execute(
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? WHERE addr=?;",
paramsx![
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
WHERE addr=?;",
paramsv![
self.last_seen,
self.last_seen_autocrypt,
self.gossip_timestamp,
&self.addr
self.addr
],
)
.await?;
@@ -438,20 +458,28 @@ UPDATE acpeerstates
}
}
impl From<crate::key::FingerprintError> for rusqlite::Error {
fn from(_source: crate::key::FingerprintError) -> Self {
Self::InvalidColumnType(0, "Invalid fingerprint".into(), rusqlite::types::Type::Text)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
#[async_std::test]
async fn test_peerstate_save_to_db() {
let ctx = crate::test_utils::TestContext::new().await;
let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com";
let pub_key = alice_keypair().public;
let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(),
last_seen: 10,
last_seen_autocrypt: 11,
@@ -479,19 +507,21 @@ mod tests {
// clear to_save, as that is not persissted
peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new);
let peerstate_new2 = Peerstate::from_fingerprint(&ctx.ctx, &pub_key.fingerprint())
.await
.expect("failed to load peerstate from db");
let peerstate_new2 =
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
.await
.expect("failed to load peerstate from db");
assert_eq!(peerstate, peerstate_new2);
}
#[async_std::test]
async fn test_peerstate_double_create() {
let ctx = crate::test_utils::TestContext::new().await;
let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com";
let pub_key = alice_keypair().public;
let peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(),
last_seen: 10,
last_seen_autocrypt: 11,
@@ -519,12 +549,13 @@ mod tests {
#[async_std::test]
async fn test_peerstate_with_empty_gossip_key_save_to_db() {
let ctx = crate::test_utils::TestContext::new().await;
let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com";
let pub_key = alice_keypair().public;
let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(),
last_seen: 10,
last_seen_autocrypt: 11,
@@ -553,4 +584,11 @@ mod tests {
peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new);
}
// TODO: don't copy this from stress.rs
#[allow(dead_code)]
struct TestContext {
ctx: Context,
dir: TempDir,
}
}

View File

@@ -20,7 +20,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// aol.md: aol.com
@@ -33,22 +32,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// arcor.de.md: arcor.de
static ref P_ARCOR_DE: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/arcor-de",
server: vec![
Server { protocol: IMAP, socket: SSL, hostname: "imap.arcor.de", port: 993, username_pattern: EMAIL },
Server { protocol: SMTP, socket: SSL, hostname: "mail.arcor.de", port: 465, username_pattern: EMAIL },
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// autistici.org.md: autistici.org
@@ -63,7 +46,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// bluewin.ch.md: bluewin.ch
@@ -78,7 +60,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// chello.at.md: chello.at
@@ -93,34 +74,13 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// comcast.md: xfinity.com, comcast.net
static ref P_COMCAST: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/comcast",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// dismail.de.md: dismail.de
static ref P_DISMAIL_DE: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/dismail-de",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// disroot.md: disroot.org
static ref P_DISROOT: Provider = Provider {
@@ -132,25 +92,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// dubby.org.md: dubby.org
static ref P_DUBBY_ORG: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/dubby-org",
server: vec![
],
config_defaults: Some(vec![
ConfigDefault { key: Config::BccSelf, value: "1" },
ConfigDefault { key: Config::SentboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxMove, value: "0" },
]),
strict_tls: true,
oauth2_authorizer: None,
};
// example.com.md: example.com, example.org
@@ -165,7 +106,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// fastmail.md: fastmail.com
@@ -178,7 +118,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// five.chat.md: five.chat
@@ -189,14 +128,8 @@ lazy_static::lazy_static! {
overview_page: "https://providers.delta.chat/five-chat",
server: vec![
],
config_defaults: Some(vec![
ConfigDefault { key: Config::BccSelf, value: "1" },
ConfigDefault { key: Config::SentboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxMove, value: "0" },
]),
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// freenet.de.md: freenet.de
@@ -211,7 +144,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// gmail.md: gmail.com, googlemail.com
@@ -226,7 +158,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: Some(Oauth2Authorizer::Gmail),
};
// gmx.net.md: gmx.net, gmx.de, gmx.at, gmx.ch, gmx.org, gmx.eu, gmx.info, gmx.biz, gmx.com
@@ -242,34 +173,10 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// hey.com.md: hey.com
static ref P_HEY_COM: Provider = Provider {
status: Status::BROKEN,
before_login_hint: "hey.com does not offer the standard IMAP e-mail protocol, so you cannot log in with Delta Chat to hey.com.",
after_login_hint: "",
overview_page: "https://providers.delta.chat/hey-com",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// i.ua.md: i.ua
static ref P_I_UA: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/i-ua",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// icloud.md: icloud.com, me.com, mac.com
static ref P_ICLOUD: Provider = Provider {
@@ -283,47 +190,16 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// kolst.com.md: kolst.com
static ref P_KOLST_COM: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/kolst-com",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// kontent.com.md: kontent.com
static ref P_KONTENT_COM: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/kontent-com",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// mail.ru.md: mail.ru, inbox.ru, bk.ru, list.ru
static ref P_MAIL_RU: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/mail-ru",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// mailbox.org.md: mailbox.org, secure.mailbox.org
static ref P_MAILBOX_ORG: Provider = Provider {
@@ -335,7 +211,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// nauta.cu.md: nauta.cu
@@ -358,7 +233,6 @@ lazy_static::lazy_static! {
ConfigDefault { key: Config::MediaQuality, value: "1" },
]),
strict_tls: false,
oauth2_authorizer: None,
};
// outlook.com.md: hotmail.com, outlook.com, office365.com, outlook.com.tr, live.com
@@ -373,7 +247,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// posteo.md: posteo.de, posteo.af, posteo.at, posteo.be, posteo.ch, posteo.cl, posteo.co, posteo.co.uk, posteo.com.br, posteo.cr, posteo.cz, posteo.dk, posteo.ee, posteo.es, posteo.eu, posteo.fi, posteo.gl, posteo.gr, posteo.hn, posteo.hr, posteo.hu, posteo.ie, posteo.in, posteo.is, posteo.jp, posteo.la, posteo.li, posteo.lt, posteo.lu, posteo.me, posteo.mx, posteo.my, posteo.net, posteo.nl, posteo.no, posteo.nz, posteo.org, posteo.pe, posteo.pl, posteo.pm, posteo.pt, posteo.ro, posteo.ru, posteo.se, posteo.sg, posteo.si, posteo.tn, posteo.uk, posteo.us
@@ -388,7 +261,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// protonmail.md: protonmail.com, protonmail.ch
@@ -401,7 +273,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// riseup.net.md: riseup.net
@@ -414,21 +285,10 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// rogers.com.md: rogers.com
static ref P_ROGERS_COM: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/rogers-com",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// systemli.org.md: systemli.org
static ref P_SYSTEMLI_ORG: Provider = Provider {
@@ -440,7 +300,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// t-online.md: t-online.de, magenta.de
@@ -453,7 +312,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// testrun.md: testrun.org
@@ -467,14 +325,8 @@ lazy_static::lazy_static! {
Server { protocol: IMAP, socket: STARTTLS, hostname: "testrun.org", port: 143, username_pattern: EMAIL },
Server { protocol: SMTP, socket: STARTTLS, hostname: "testrun.org", port: 587, username_pattern: EMAIL },
],
config_defaults: Some(vec![
ConfigDefault { key: Config::BccSelf, value: "1" },
ConfigDefault { key: Config::SentboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxMove, value: "0" },
]),
config_defaults: None,
strict_tls: true,
oauth2_authorizer: None,
};
// tiscali.it.md: tiscali.it
@@ -489,34 +341,13 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// ukr.net.md: ukr.net
static ref P_UKR_NET: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/ukr-net",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// vfemail.md: vfemail.net
static ref P_VFEMAIL: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/vfemail",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// - skipping provider with status OK and no special things to do
// web.de.md: web.de, email.de, flirt.ms, hallo.ms, kuss.ms, love.ms, magic.ms, singles.ms, cool.ms, kanzler.ms, okay.ms, party.ms, pop.ms, stars.ms, techno.ms, clever.ms, deutschland.ms, genial.ms, ich.ms, online.ms, smart.ms, wichtig.ms, action.ms, fussball.ms, joker.ms, planet.ms, power.ms
static ref P_WEB_DE: Provider = Provider {
@@ -531,7 +362,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// yahoo.md: yahoo.com, yahoo.de, yahoo.it, yahoo.fr, yahoo.es, yahoo.se, yahoo.co.uk, yahoo.co.nz, yahoo.com.au, yahoo.com.ar, yahoo.com.br, yahoo.com.mx, ymail.com, rocketmail.com, yahoodns.net
@@ -546,10 +376,9 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// yandex.ru.md: yandex.com, yandex.by, yandex.kz, yandex.ru, yandex.ua, ya.ru, narod.ru
// yandex.ru.md: yandex.ru, yandex.com
static ref P_YANDEX_RU: Provider = Provider {
status: Status::PREPARATION,
before_login_hint: "For Yandex accounts, you have to set IMAP protocol option turned on.",
@@ -559,7 +388,6 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: true,
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
};
// ziggo.nl.md: ziggo.nl
@@ -574,21 +402,15 @@ lazy_static::lazy_static! {
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
pub static ref PROVIDER_DATA: HashMap<&'static str, &'static Provider> = [
("aktivix.org", &*P_AKTIVIX_ORG),
("aol.com", &*P_AOL),
("arcor.de", &*P_ARCOR_DE),
("autistici.org", &*P_AUTISTICI_ORG),
("bluewin.ch", &*P_BLUEWIN_CH),
("chello.at", &*P_CHELLO_AT),
("xfinity.com", &*P_COMCAST),
("comcast.net", &*P_COMCAST),
("dismail.de", &*P_DISMAIL_DE),
("disroot.org", &*P_DISROOT),
("dubby.org", &*P_DUBBY_ORG),
("example.com", &*P_EXAMPLE_COM),
("example.org", &*P_EXAMPLE_COM),
("fastmail.com", &*P_FASTMAIL),
@@ -605,17 +427,9 @@ lazy_static::lazy_static! {
("gmx.info", &*P_GMX_NET),
("gmx.biz", &*P_GMX_NET),
("gmx.com", &*P_GMX_NET),
("hey.com", &*P_HEY_COM),
("i.ua", &*P_I_UA),
("icloud.com", &*P_ICLOUD),
("me.com", &*P_ICLOUD),
("mac.com", &*P_ICLOUD),
("kolst.com", &*P_KOLST_COM),
("kontent.com", &*P_KONTENT_COM),
("mail.ru", &*P_MAIL_RU),
("inbox.ru", &*P_MAIL_RU),
("bk.ru", &*P_MAIL_RU),
("list.ru", &*P_MAIL_RU),
("mailbox.org", &*P_MAILBOX_ORG),
("secure.mailbox.org", &*P_MAILBOX_ORG),
("nauta.cu", &*P_NAUTA_CU),
@@ -676,14 +490,11 @@ lazy_static::lazy_static! {
("protonmail.com", &*P_PROTONMAIL),
("protonmail.ch", &*P_PROTONMAIL),
("riseup.net", &*P_RISEUP_NET),
("rogers.com", &*P_ROGERS_COM),
("systemli.org", &*P_SYSTEMLI_ORG),
("t-online.de", &*P_T_ONLINE),
("magenta.de", &*P_T_ONLINE),
("testrun.org", &*P_TESTRUN),
("tiscali.it", &*P_TISCALI_IT),
("ukr.net", &*P_UKR_NET),
("vfemail.net", &*P_VFEMAIL),
("web.de", &*P_WEB_DE),
("email.de", &*P_WEB_DE),
("flirt.ms", &*P_WEB_DE),
@@ -726,13 +537,8 @@ lazy_static::lazy_static! {
("ymail.com", &*P_YAHOO),
("rocketmail.com", &*P_YAHOO),
("yahoodns.net", &*P_YAHOO),
("yandex.com", &*P_YANDEX_RU),
("yandex.by", &*P_YANDEX_RU),
("yandex.kz", &*P_YANDEX_RU),
("yandex.ru", &*P_YANDEX_RU),
("yandex.ua", &*P_YANDEX_RU),
("ya.ru", &*P_YANDEX_RU),
("narod.ru", &*P_YANDEX_RU),
("yandex.com", &*P_YANDEX_RU),
("ziggo.nl", &*P_ZIGGO_NL),
].iter().copied().collect();
}

View File

@@ -35,13 +35,6 @@ pub enum UsernamePattern {
EMAILLOCALPART = 2,
}
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum Oauth2Authorizer {
Yandex = 1,
Gmail = 2,
}
#[derive(Debug)]
pub struct Server {
pub protocol: Protocol,
@@ -80,7 +73,6 @@ pub struct Provider {
pub server: Vec<Server>,
pub config_defaults: Option<Vec<ConfigDefault>>,
pub strict_tls: bool,
pub oauth2_authorizer: Option<Oauth2Authorizer>,
}
impl Provider {

View File

@@ -103,9 +103,6 @@ def process_data(data, file):
strict_tls = data.get("strict_tls", False)
strict_tls = "true" if strict_tls else "false"
oauth2 = data.get("oauth2", "")
oauth2 = "Some(Oauth2Authorizer::" + camel(oauth2) + ")" if oauth2 != "" else "None"
provider = ""
before_login_hint = cleanstr(data.get("before_login_hint", ""))
after_login_hint = cleanstr(data.get("after_login_hint", ""))
@@ -118,7 +115,6 @@ def process_data(data, file):
provider += " server: vec![\n" + server + " ],\n"
provider += " config_defaults: " + config_defaults + ",\n"
provider += " strict_tls: " + strict_tls + ",\n"
provider += " oauth2_authorizer: " + oauth2 + ",\n"
provider += " };\n\n"
else:
raise TypeError("SMTP and IMAP must be specified together or left out both")
@@ -129,11 +125,11 @@ def process_data(data, file):
# finally, add the provider
global out_all, out_domains
out_all += " // " + file[file.rindex("/")+1:] + ": " + comment.strip(", ") + "\n"
# also add provider with no special things to do -
# eg. _not_ supporting oauth2 is also an information and we can skip the mx-lookup in this case
out_all += provider
out_domains += domains
if status == "OK" and before_login_hint == "" and after_login_hint == "" and server == "" and config_defaults == "None" and strict_tls == "false":
out_all += " // - skipping provider with status OK and no special things to do\n\n"
else:
out_all += provider
out_domains += domains
def process_file(file):

View File

@@ -138,10 +138,10 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let mut lot = Lot::new();
// retrieve known state for this fingerprint
let peerstate = Peerstate::from_fingerprint(context, &fingerprint).await;
let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint).await;
if invitenumber.is_none() || auth.is_none() {
if let Ok(peerstate) = peerstate {
if let Some(peerstate) = peerstate {
lot.state = LotState::QrFprOk;
lot.id = Contact::add_or_lookup(
@@ -383,11 +383,11 @@ fn normalize_address(addr: &str) -> Result<String, Error> {
mod tests {
use super::*;
use crate::test_utils::TestContext;
use crate::test_utils::dummy_context;
#[async_std::test]
async fn test_decode_http() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(&ctx.ctx, "http://www.hello.com").await;
@@ -399,7 +399,7 @@ mod tests {
#[async_std::test]
async fn test_decode_https() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(&ctx.ctx, "https://www.hello.com").await;
@@ -411,7 +411,7 @@ mod tests {
#[async_std::test]
async fn test_decode_text() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(&ctx.ctx, "I am so cool").await;
@@ -423,7 +423,7 @@ mod tests {
#[async_std::test]
async fn test_decode_vcard() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -441,7 +441,7 @@ mod tests {
#[async_std::test]
async fn test_decode_matmsg() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -459,7 +459,7 @@ mod tests {
#[async_std::test]
async fn test_decode_mailto() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -485,7 +485,7 @@ mod tests {
#[async_std::test]
async fn test_decode_smtp() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld").await;
@@ -499,7 +499,7 @@ mod tests {
#[async_std::test]
async fn test_decode_openpgp_group() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -528,7 +528,7 @@ mod tests {
#[async_std::test]
async fn test_decode_openpgp_secure_join() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -556,7 +556,7 @@ mod tests {
#[async_std::test]
async fn test_decode_openpgp_without_addr() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -591,7 +591,7 @@ mod tests {
#[async_std::test]
async fn test_decode_account() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
@@ -613,7 +613,7 @@ mod tests {
#[async_std::test]
async fn test_decode_account_bad_scheme() {
let ctx = TestContext::new().await;
let ctx = dummy_context().await;
let res = check_qr(
&ctx.ctx,
"DCACCOUNT:http://example.org/new_email?t=1w_7wDjgjelxeX884x96v3",

View File

@@ -1,5 +1,3 @@
#![warn(clippy::indexing_slicing)]
use async_std::prelude::*;
use async_std::sync::{channel, Receiver, Sender};
use async_std::task;
@@ -38,6 +36,14 @@ impl Context {
self.scheduler.read().await.interrupt_inbox(info).await;
}
pub(crate) async fn interrupt_sentbox(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_sentbox(info).await;
}
pub(crate) async fn interrupt_mvbox(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_mvbox(info).await;
}
pub(crate) async fn interrupt_smtp(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_smtp(info).await;
}
@@ -76,11 +82,7 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
}
None => {
jobs_loaded = 0;
info = if ctx.get_config_bool(Config::InboxWatch).await {
fetch_idle(&ctx, &mut connection, Config::ConfiguredInboxFolder).await
} else {
connection.fake_idle(&ctx, None).await
};
info = fetch_idle(&ctx, &mut connection, Config::ConfiguredInboxFolder).await;
}
}
}
@@ -241,21 +243,29 @@ impl Scheduler {
let (smtp, smtp_handlers) = SmtpConnectionState::new();
let (inbox, inbox_handlers) = ImapConnectionState::new();
*self = Scheduler::Running {
inbox,
mvbox,
sentbox,
smtp,
inbox_handle: None,
mvbox_handle: None,
sentbox_handle: None,
smtp_handle: None,
};
let (inbox_start_send, inbox_start_recv) = channel(1);
let (mvbox_start_send, mvbox_start_recv) = channel(1);
let mut mvbox_handle = None;
let (sentbox_start_send, sentbox_start_recv) = channel(1);
let mut sentbox_handle = None;
let (smtp_start_send, smtp_start_recv) = channel(1);
let ctx1 = ctx.clone();
let inbox_handle = Some(task::spawn(async move {
inbox_loop(ctx1, inbox_start_send, inbox_handlers).await
}));
if ctx.get_config_bool(Config::MvboxWatch).await {
if let Scheduler::Running { inbox_handle, .. } = self {
let ctx1 = ctx.clone();
mvbox_handle = Some(task::spawn(async move {
*inbox_handle = Some(task::spawn(async move {
inbox_loop(ctx1, inbox_start_send, inbox_handlers).await
}));
}
let (mvbox_start_send, mvbox_start_recv) = channel(1);
if let Scheduler::Running { mvbox_handle, .. } = self {
let ctx1 = ctx.clone();
*mvbox_handle = Some(task::spawn(async move {
simple_imap_loop(
ctx1,
mvbox_start_send,
@@ -264,13 +274,12 @@ impl Scheduler {
)
.await
}));
} else {
mvbox_start_send.send(()).await;
}
if ctx.get_config_bool(Config::SentboxWatch).await {
let (sentbox_start_send, sentbox_start_recv) = channel(1);
if let Scheduler::Running { sentbox_handle, .. } = self {
let ctx1 = ctx.clone();
sentbox_handle = Some(task::spawn(async move {
*sentbox_handle = Some(task::spawn(async move {
simple_imap_loop(
ctx1,
sentbox_start_send,
@@ -279,25 +288,15 @@ impl Scheduler {
)
.await
}));
} else {
sentbox_start_send.send(()).await;
}
let ctx1 = ctx.clone();
let smtp_handle = Some(task::spawn(async move {
smtp_loop(ctx1, smtp_start_send, smtp_handlers).await
}));
*self = Scheduler::Running {
inbox,
mvbox,
sentbox,
smtp,
inbox_handle,
mvbox_handle,
sentbox_handle,
smtp_handle,
};
let (smtp_start_send, smtp_start_recv) = channel(1);
if let Scheduler::Running { smtp_handle, .. } = self {
let ctx1 = ctx.clone();
*smtp_handle = Some(task::spawn(async move {
smtp_loop(ctx1, smtp_start_send, smtp_handlers).await
}));
}
// wait for all loops to be started
if let Err(err) = inbox_start_recv
@@ -387,18 +386,10 @@ impl Scheduler {
smtp_handle,
..
} => {
if let Some(handle) = inbox_handle.take() {
handle.await;
}
if let Some(handle) = mvbox_handle.take() {
handle.await;
}
if let Some(handle) = sentbox_handle.take() {
handle.await;
}
if let Some(handle) = smtp_handle.take() {
handle.await;
}
inbox_handle.take().expect("inbox not started").await;
mvbox_handle.take().expect("mvbox not started").await;
sentbox_handle.take().expect("sentbox not started").await;
smtp_handle.take().expect("smtp not started").await;
*self = Scheduler::Stopped;
}

View File

@@ -114,7 +114,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
Some(format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
fingerprint.hex(),
fingerprint,
self_addr_urlencoded,
&group_name_urlencoded,
&chat.grpid,
@@ -129,11 +129,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
// parameters used: a=n=i=s=
Some(format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
self_name_urlencoded,
&invitenumber,
&auth,
fingerprint, self_addr_urlencoded, self_name_urlencoded, &invitenumber, &auth,
))
};
@@ -152,69 +148,81 @@ async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
}
}
async fn cleanup(
context: &Context,
contact_chat_id: ChatId,
ongoing_allocated: bool,
join_vg: bool,
) -> ChatId {
let mut bob = context.bob.write().await;
bob.expects = 0;
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
if join_vg {
chat::get_chat_id_by_grpid(
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
)
.await
.unwrap_or((ChatId::new(0), false, Blocked::Not))
.0
} else {
contact_chat_id
}
} else {
ChatId::new(0)
};
bob.qr_scan = None;
if ongoing_allocated {
context.free_ongoing().await;
}
ret_chat_id
}
/// Take a scanned QR-code and do the setup-contact/join-group handshake.
/// See the ffi-documentation for more details.
pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
if let Err(err) = context.alloc_ongoing().await {
error!(context, "SecureJoin Error: {}", err);
return ChatId::new(0);
if context.alloc_ongoing().await.is_err() {
return cleanup(&context, ChatId::new(0), false, false).await;
}
match securejoin(context, qr).await {
Ok(id) => id,
Err(err) => {
error!(context, "SecureJoin Error: {}", err);
ChatId::new(0)
}
}
securejoin(context, qr).await
}
/// Bob - the joiner's side
/// Step 2 in "Setup verified contact" protocol
async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, anyhow::Error> {
struct DropGuard<'a> {
context: &'a Context,
}
async fn securejoin(context: &Context, qr: &str) -> ChatId {
/*========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================*/
impl Drop for DropGuard<'_> {
fn drop(&mut self) {
async_std::task::block_on(async {
let mut bob = self.context.bob.write().await;
bob.expects = 0;
bob.qr_scan = None;
self.context.free_ongoing().await;
});
}
}
let mut contact_chat_id = ChatId::new(0);
let mut join_vg: bool = false;
let _guard = DropGuard { context: &context };
info!(context, "Requesting secure-join ...");
info!(context, "Requesting secure-join ...",);
ensure_secret_key_exists(context).await.ok();
let qr_scan = check_qr(context, &qr).await;
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
{
bail!("Unknown QR code.");
error!(context, "Unknown QR code.",);
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
let contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await {
contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await {
Ok(chat_id) => chat_id,
Err(_) => {
bail!("Unknown contact.");
error!(context, "Unknown contact.");
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
};
if context.shall_stop_ongoing().await {
bail!("Interrupted");
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
let join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
{
let mut bob = context.bob.write().await;
bob.status = 0;
bob.qr_scan = Some(qr_scan);
}
let fp_equals_sender = fingerprint_equals_sender(
if fingerprint_equals_sender(
context,
context
.bob
@@ -228,22 +236,21 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, anyhow::Error
.unwrap(),
contact_chat_id,
)
.await?;
if fp_equals_sender {
.await
{
// the scanned fingerprint matches Alice's key,
// we can proceed to step 4b) directly and save two mails
info!(context, "Taking protocol shortcut.");
context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(
context,
chat_id_2_contact_id(context, contact_chat_id).await?,
chat_id_2_contact_id(context, contact_chat_id).await,
400
);
let own_fingerprint = get_self_fingerprint(context).await;
// Bob -> Alice
send_handshake_msg(
if let Err(err) = send_handshake_msg(
context,
contact_chat_id,
if join_vg {
@@ -259,12 +266,16 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, anyhow::Error
"".to_string()
},
)
.await?;
.await
{
error!(context, "failed to send handshake message: {}", err);
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
} else {
context.bob.write().await.expects = DC_VC_AUTH_REQUIRED;
// Bob -> Alice
send_handshake_msg(
if let Err(err) = send_handshake_msg(
context,
contact_chat_id,
if join_vg { "vg-request" } else { "vc-request" },
@@ -272,7 +283,11 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, anyhow::Error
None,
"",
)
.await?;
.await
{
error!(context, "failed to send handshake message: {}", err);
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
}
if join_vg {
@@ -280,23 +295,12 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, anyhow::Error
while !context.shall_stop_ongoing().await {
async_std::task::sleep(Duration::from_millis(50)).await;
}
let bob = context.bob.read().await;
if bob.status == DC_BOB_SUCCESS {
let id = chat::get_chat_id_by_grpid(
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
)
.await?
.0;
Ok(id)
} else {
bail!("Failed to join");
}
cleanup(&context, contact_chat_id, true, join_vg).await
} else {
// for a one-to-one-chat, the chat is already known, return the chat-id,
// the verification runs in background
context.free_ongoing().await;
Ok(contact_chat_id)
contact_chat_id
}
}
@@ -343,12 +347,12 @@ async fn send_handshake_msg(
Ok(())
}
async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> Result<u32, Error> {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await?;
async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await;
if contacts.len() == 1 {
Ok(contacts[0])
contacts[0]
} else {
Ok(0)
0
}
}
@@ -356,24 +360,22 @@ async fn fingerprint_equals_sender(
context: &Context,
fingerprint: &Fingerprint,
contact_chat_id: ChatId,
) -> Result<bool, Error> {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await?;
) -> bool {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await;
if contacts.len() == 1 {
if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await {
if let Ok(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await {
if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await {
if peerstate.public_key_fingerprint.is_some()
&& fingerprint == peerstate.public_key_fingerprint.as_ref().unwrap()
{
return Ok(true);
return true;
}
}
}
}
Ok(false)
false
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum HandshakeError {
#[error("Can not be called with special contact ID")]
@@ -394,8 +396,6 @@ pub(crate) enum HandshakeError {
MsgSendFailed(#[source] Error),
#[error("Failed to parse fingerprint")]
BadFingerprint(#[from] crate::key::FingerprintError),
#[error("{0}")]
Other(#[from] Error),
}
/// What to do with a Secure-Join handshake message after it was handled.
@@ -529,20 +529,20 @@ pub(crate) async fn handle_securejoin_handshake(
"Not encrypted."
},
)
.await?;
.await;
context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing().await;
return Ok(HandshakeMessage::Ignore);
}
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id)
.await?
.await
{
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
)
.await?;
.await;
context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing().await;
return Ok(HandshakeMessage::Ignore);
@@ -585,7 +585,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint not provided.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
};
@@ -595,16 +595,16 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"Auth not encrypted.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await? {
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await {
could_not_establish_secure_connection(
context,
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
info!(context, "Fingerprint verified.",);
@@ -617,13 +617,13 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"Auth not provided.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
};
if !token::exists(context, token::Namespace::Auth, &auth_0).await {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.")
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
if mark_peer_as_verified(context, &fingerprint).await.is_err() {
@@ -632,12 +632,12 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on inviter-side.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await;
info!(context, "Auth verified.",);
secure_connection_established(context, contact_chat_id).await?;
secure_connection_established(context, contact_chat_id).await;
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600);
if join_vg {
@@ -740,7 +740,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"Contact confirm message not encrypted.",
)
.await?;
.await;
context.bob.write().await.status = 0;
return Ok(abort_retval);
}
@@ -754,7 +754,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"Fingerprint mismatch on joiner-side.",
)
.await?;
.await;
return Ok(abort_retval);
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await;
@@ -772,7 +772,7 @@ pub(crate) async fn handle_securejoin_handshake(
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return Ok(abort_retval);
}
secure_connection_established(context, contact_chat_id).await?;
secure_connection_established(context, contact_chat_id).await;
context.bob.write().await.expects = 0;
// Bob -> Alice
@@ -897,7 +897,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
contact_chat_id,
"Message not encrypted correctly.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
@@ -909,7 +909,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
contact_chat_id,
"Fingerprint not provided, please update Delta Chat on all your devices.",
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
};
@@ -919,7 +919,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
contact_chat_id,
format!("Fingerprint mismatch on observing {}.", step).as_ref(),
)
.await?;
.await;
return Ok(HandshakeMessage::Ignore);
}
Ok(if step.as_str() == "vg-member-added" {
@@ -932,11 +932,8 @@ pub(crate) async fn observe_securejoin_on_other_device(
}
}
async fn secure_connection_established(
context: &Context,
contact_chat_id: ChatId,
) -> Result<(), HandshakeError> {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await?;
async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await;
let contact = Contact::get_by_id(context, contact_id).await;
let addr = if let Ok(ref contact) = contact {
@@ -949,16 +946,14 @@ async fn secure_connection_established(
.await;
chat::add_info_msg(context, contact_chat_id, msg).await;
emit_event!(context, Event::ChatModified(contact_chat_id));
Ok(())
}
async fn could_not_establish_secure_connection(
context: &Context,
contact_chat_id: ChatId,
details: &str,
) -> Result<(), HandshakeError> {
let contact_id = chat_id_2_contact_id(context, contact_chat_id).await?;
) {
let contact_id = chat_id_2_contact_id(context, contact_chat_id).await;
let contact = Contact::get_by_id(context, contact_id).await;
let msg = context
.stock_string_repl_str(
@@ -973,11 +968,12 @@ async fn could_not_establish_secure_connection(
chat::add_info_msg(context, contact_chat_id, &msg).await;
error!(context, "{} ({})", &msg, details);
Ok(())
}
async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> {
if let Ok(ref mut peerstate) = Peerstate::from_fingerprint(context, fingerprint).await {
if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint).await
{
if peerstate.set_verified(
PeerstateKeyType::PublicKey,
fingerprint,
@@ -1031,21 +1027,31 @@ fn encrypted_and_signed(
}
}
pub async fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> {
pub async fn handle_degrade_event(
context: &Context,
peerstate: &Peerstate<'_>,
) -> Result<(), Error> {
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
// with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this)
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = context
let contact_id: i32 = match context
.sql
.query_value(
.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?;",
paramsx![&peerstate.addr],
paramsv![peerstate.addr],
)
.await?;
.await
{
None => bail!(
"contact with peerstate.addr {:?} not found",
&peerstate.addr
),
Some(contact_id) => contact_id,
};
if contact_id > 0 {
let (contact_chat_id, _) =
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)

View File

@@ -1,7 +1,5 @@
//! # SMTP transport module
#![forbid(clippy::indexing_slicing)]
pub mod send;
use std::time::{Duration, Instant};

1332
src/sql.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
#[macro_export]
macro_rules! paramsx {
() => {
sqlx::sqlite::SqliteArguments::default()
};
($($param:expr),+ $(,)?) => {{
use sqlx::Arguments;
let mut args = sqlx::sqlite::SqliteArguments::default();
$(args.add($param);)+
args
}};
}

View File

@@ -1,378 +0,0 @@
use super::{Error, Result, Sql};
use crate::constants::ShowEmails;
use crate::context::Context;
/// Executes all migrations required to get from the passed in `dbversion` to the latest.
pub async fn run(
context: &Context,
sql: &Sql,
dbversion: i32,
exists_before_update: bool,
) -> Result<()> {
let migrate = |version: i32, stmt: &'static str| async move {
if dbversion < version {
info!(context, "[migration] v{}", version);
sql.execute_batch(stmt).await?;
sql.set_raw_config_int(context, "dbversion", version)
.await?;
}
Ok::<_, Error>(())
};
migrate(
0,
r#"
CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);
CREATE INDEX config_index1 ON config (keyname);
CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT DEFAULT '',
addr TEXT DEFAULT '' COLLATE NOCASE,
origin INTEGER DEFAULT 0,
blocked INTEGER DEFAULT 0,
last_seen INTEGER DEFAULT 0,
param TEXT DEFAULT '');
CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);
CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);
INSERT INTO contacts (id,name,origin) VALUES
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144),
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144),
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);
CREATE TABLE chats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER DEFAULT 0,
name TEXT DEFAULT '',
draft_timestamp INTEGER DEFAULT 0,
draft_txt TEXT DEFAULT '',
blocked INTEGER DEFAULT 0,
grpid TEXT DEFAULT '',
param TEXT DEFAULT '');
CREATE INDEX chats_index1 ON chats (grpid);
CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);
CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);
INSERT INTO chats (id,type,name) VALUES
(1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'),
(4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'),
(7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');
CREATE TABLE msgs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rfc724_mid TEXT DEFAULT '',
server_folder TEXT DEFAULT '',
server_uid INTEGER DEFAULT 0,
chat_id INTEGER DEFAULT 0,
from_id INTEGER DEFAULT 0,
to_id INTEGER DEFAULT 0,
timestamp INTEGER DEFAULT 0,
type INTEGER DEFAULT 0,
state INTEGER DEFAULT 0,
msgrmsg INTEGER DEFAULT 1,
bytes INTEGER DEFAULT 0,
txt TEXT DEFAULT '',
txt_raw TEXT DEFAULT '',
param TEXT DEFAULT '');
CREATE INDEX msgs_index1 ON msgs (rfc724_mid);
CREATE INDEX msgs_index2 ON msgs (chat_id);
CREATE INDEX msgs_index3 ON msgs (timestamp);
CREATE INDEX msgs_index4 ON msgs (state);
INSERT INTO msgs (id,msgrmsg,txt) VALUES
(1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'),
(4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'),
(8,0,'rsvd'), (9,0,'daymarker');
CREATE TABLE jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
added_timestamp INTEGER,
desired_timestamp INTEGER DEFAULT 0,
action INTEGER,
foreign_id INTEGER,
param TEXT DEFAULT '');
CREATE INDEX jobs_index1 ON jobs (desired_timestamp);
"#,
)
.await?;
migrate(
1,
r#"
CREATE TABLE leftgrps (
id INTEGER PRIMARY KEY,
grpid TEXT DEFAULT '');
CREATE INDEX leftgrps_index1 ON leftgrps (grpid);
"#,
)
.await?;
migrate(
2,
r#"
ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';
"#,
)
.await?;
migrate(
7,
r#"
CREATE TABLE keypairs (
id INTEGER PRIMARY KEY,
addr TEXT DEFAULT '' COLLATE NOCASE,
is_default INTEGER DEFAULT 0,
private_key,
public_key,
created INTEGER DEFAULT 0);
"#,
)
.await?;
migrate(
10,
r#"
CREATE TABLE acpeerstates (
id INTEGER PRIMARY KEY,
addr TEXT DEFAULT '' COLLATE NOCASE,
last_seen INTEGER DEFAULT 0,
last_seen_autocrypt INTEGER DEFAULT 0,
public_key,
prefer_encrypted INTEGER DEFAULT 0);
"#,
)
.await?;
migrate(
12,
r#"
CREATE TABLE msgs_mdns (
msg_id INTEGER,
contact_id INTEGER);
CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);
"#,
)
.await?;
migrate(
17,
r#"
ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
CREATE INDEX chats_index2 ON chats (archived);
ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
CREATE INDEX msgs_index5 ON msgs (starred);
"#,
)
.await?;
migrate(
18,
r#"
ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
ALTER TABLE acpeerstates ADD COLUMN gossip_key;
"#,
)
.await?;
// chat.id=1 and chat.id=2 are the old deaddrops,
// the current ones are defined by chats.blocked=2
migrate(
27,
r#"
DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
34,
r#"
ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
"#,
)
.await?;
migrate(
39,
r#"
CREATE TABLE tokens (
id INTEGER PRIMARY KEY,
namespc INTEGER DEFAULT 0,
foreign_id INTEGER DEFAULT 0,
token TEXT DEFAULT '',
timestamp INTEGER DEFAULT 0);
ALTER TABLE acpeerstates ADD COLUMN verified_key;
ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
"#,
)
.await?;
migrate(
40,
r#"
ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
44,
r#"
ALTER TABLE msgs ADD COLUMN mime_headers TEXT;
"#,
)
.await?;
migrate(
46,
r#"
ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
ALTER TABLE msgs ADD COLUMN mime_references TEXT;
"#,
)
.await?;
migrate(
47,
r#"
ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;
"#,
)
.await?;
// NOTE: move_state is not used anymore
migrate(
48,
r#"
ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;
"#,
)
.await?;
migrate(
49,
r#"
ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;
"#,
)
.await?;
if dbversion < 50 {
info!(context, "[migration] v50");
// installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly;
// keep this default and use DC_SHOW_EMAILS_NO
// only for new installations
if exists_before_update {
sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)
.await?;
}
sql.set_raw_config_int(context, "dbversion", 50).await?;
}
// the messages containing _only_ locations
// are also added to the database as _hidden_.
migrate(
53,
r#"
CREATE TABLE locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL DEFAULT 0.0,
longitude REAL DEFAULT 0.0,
accuracy REAL DEFAULT 0.0,
timestamp INTEGER DEFAULT 0,
chat_id INTEGER DEFAULT 0,
from_id INTEGER DEFAULT 0);
CREATE INDEX locations_index1 ON locations (from_id);
CREATE INDEX locations_index2 ON locations (timestamp);
ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
CREATE INDEX chats_index3 ON chats (locations_send_until);
"#,
)
.await?;
migrate(
54,
r#"
ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
CREATE INDEX msgs_index6 ON msgs (location_id);
"#,
)
.await?;
migrate(
55,
r#"
ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
59,
r#"
CREATE TABLE devmsglabels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT,
msg_id INTEGER DEFAULT 0);
CREATE INDEX devmsglabels_index1 ON devmsglabels (label);
"#,
)
.await?;
// records in the devmsglabels are kept when the message is deleted.
// so, msg_id may or may not exist.
if dbversion < 59 && exists_before_update && sql.get_raw_config_int("bcc_self").await.is_none()
{
sql.set_raw_config_int(context, "bcc_self", 1).await?;
}
migrate(
60,
r#"
ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
61,
r#"
ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
62,
r#"
ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
63,
r#"
UPDATE chats SET grpid='' WHERE type=100;
"#,
)
.await?;
migrate(
64,
r#"
ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';
"#,
)
.await?;
Ok(())
}

View File

@@ -1,706 +0,0 @@
//! # SQLite wrapper
use std::collections::HashSet;
use std::path::Path;
use async_std::prelude::*;
use async_std::sync::RwLock;
use sqlx::sqlite::*;
use crate::chat::{update_device_icon, update_saved_messages_icon};
use crate::constants::DC_CHAT_ID_TRASH;
use crate::context::Context;
use crate::dc_tools::*;
use crate::param::*;
use crate::peerstate::*;
#[macro_use]
mod macros;
mod migrations;
pub use macros::*;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Sqlite: Connection closed")]
SqlNoConnection,
#[error("Sqlite: Already open")]
SqlAlreadyOpen,
#[error("Sqlite: Failed to open")]
SqlFailedToOpen,
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0:?}")]
BlobError(#[from] crate::blob::BlobError),
#[error("{0}")]
Other(#[from] crate::error::Error),
#[error("{0}")]
Sqlx(#[from] sqlx::Error),
#[error("{0}: {1}")]
SqlxWithContext(String, #[source] sqlx::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
/// A wrapper around the underlying Sqlite3 object.
#[derive(Debug)]
pub struct Sql {
xpool: RwLock<Option<sqlx::SqlitePool>>,
}
impl Default for Sql {
fn default() -> Self {
Self {
xpool: RwLock::new(None),
}
}
}
impl Sql {
pub fn new() -> Sql {
Self::default()
}
/// Returns `true` if there is a working sqlite connection, `false` otherwise.
pub async fn is_open(&self) -> bool {
let pool = self.xpool.read().await;
pool.is_some() && !pool.as_ref().unwrap().is_closed()
}
/// Shuts down all sqlite connections.
pub async fn close(&self) {
if let Some(pool) = self.xpool.write().await.take() {
pool.close().await;
}
}
pub async fn open<T: AsRef<Path>>(
&self,
context: &Context,
dbfile: T,
readonly: bool,
) -> crate::error::Result<()> {
if let Err(err) = open(context, self, dbfile, readonly).await {
return match err.downcast_ref::<Error>() {
Some(Error::SqlAlreadyOpen) => Err(err),
_ => {
self.close().await;
Err(err)
}
};
}
Ok(())
}
/// Execute a single query.
pub async fn execute<'a, P>(&self, statement: &'a str, params: P) -> Result<usize>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let count = sqlx::query_with(statement, params).execute(xpool).await?;
Ok(count as usize)
}
/// Execute a list of statements, without any bindings
pub async fn execute_batch(&self, statement: &str) -> Result<()> {
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
sqlx::query(statement.as_ref()).execute(xpool).await?;
Ok(())
}
pub async fn get_pool(&self) -> Result<SqlitePool> {
let lock = self.xpool.read().await;
lock.as_ref().cloned().ok_or_else(|| Error::SqlNoConnection)
}
/// Starts a new transaction.
pub async fn begin(
&self,
) -> Result<sqlx::Transaction<'static, Sqlite, sqlx::pool::PoolConnection<Sqlite>>> {
let lock = self.xpool.read().await;
let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let tx = pool.begin().await?;
Ok(tx)
}
/// Execute a query which is expected to return zero or more rows.
pub async fn query_rows<'a, T, P>(&self, statement: &'a str, params: P) -> Result<Vec<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let rows = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row))
.fetch_all(xpool)
.await?;
Ok(rows)
}
/// Execute a query which is expected to return zero or more rows.
pub async fn query_values<'a, T, P>(&self, statement: &'a str, params: P) -> Result<Vec<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::decode::Decode<'b, Sqlite>,
T: sqlx::Type<Sqlite>,
T: 'static + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let rows = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| {
let (val,): (T,) = sqlx::FromRow::from_row(&row)?;
Ok(val)
})
.fetch_all(xpool)
.await?;
Ok(rows)
}
/// Return `true` if a query in the SQL statement it executes returns one or more
/// rows and false if the SQL returns an empty set.
pub async fn exists<'a, P>(&self, statement: &'a str, params: P) -> Result<bool>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let mut rows = sqlx::query_with(statement, params).fetch(xpool);
match rows.next().await {
Some(Ok(_)) => Ok(true),
None => Ok(false),
Some(Err(sqlx::Error::RowNotFound)) => Ok(false),
Some(Err(err)) => Err(Error::SqlxWithContext(
format!("exists: '{}'", statement),
err,
)),
}
}
/// Execute a query which is expected to return one row.
pub async fn query_row<'a, T, P>(&self, statement: &'a str, params: P) -> Result<T>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let row = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row))
.fetch_one(xpool)
.await?;
Ok(row)
}
/// Execute a query which is expected to return zero or one row.
pub async fn query_row_optional<'a, T, P>(
&self,
statement: &'a str,
params: P,
) -> Result<Option<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let row = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row))
.fetch_optional(xpool)
.await?;
Ok(row)
}
pub async fn query_value_optional<'a, T, P>(
&self,
statement: &'a str,
params: P,
) -> Result<Option<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::decode::Decode<'b, Sqlite>,
T: sqlx::Type<Sqlite>,
T: 'static + Unpin + Send,
{
match self.query_row_optional(statement, params).await? {
Some((val,)) => Ok(Some(val)),
None => Ok(None),
}
}
pub async fn query_value<'a, T, P>(&self, statement: &'a str, params: P) -> Result<T>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::decode::Decode<'b, Sqlite>,
T: sqlx::Type<Sqlite>,
T: 'static + Unpin + Send,
{
let (val,): (T,) = self.query_row(statement, params).await?;
Ok(val)
}
pub async fn table_exists(&self, name: impl AsRef<str>) -> Result<bool> {
self.exists(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name=?",
paramsx![name.as_ref()],
)
.await
}
/// Set private configuration options.
///
/// Setting `None` deletes the value. On failure an error message
/// will already have been logged.
pub async fn set_raw_config(
&self,
_context: &Context,
key: impl AsRef<str>,
value: Option<&str>,
) -> Result<()> {
let key = key.as_ref();
if let Some(ref value) = value {
let exists = self
.exists("SELECT value FROM config WHERE keyname=?;", paramsx![key])
.await?;
if exists {
self.execute(
"UPDATE config SET value=? WHERE keyname=?;",
paramsx![value, key],
)
.await?;
} else {
self.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?);",
paramsx![key, value],
)
.await?;
}
} else {
self.execute("DELETE FROM config WHERE keyname=?;", paramsx![key])
.await?;
}
Ok(())
}
/// Get configuration options from the database.
pub async fn get_raw_config(&self, key: impl AsRef<str>) -> Option<String> {
if !self.is_open().await || key.as_ref().is_empty() {
return None;
}
self.query_row(
"SELECT value FROM config WHERE keyname=?;",
paramsx![key.as_ref().to_string()],
)
.await
.ok()
.map(|(res,)| res)
}
pub async fn set_raw_config_int(
&self,
context: &Context,
key: impl AsRef<str>,
value: i32,
) -> Result<()> {
self.set_raw_config(context, key, Some(&format!("{}", value)))
.await
}
pub async fn get_raw_config_int(&self, key: impl AsRef<str>) -> Option<i32> {
self.get_raw_config(key).await.and_then(|s| s.parse().ok())
}
pub async fn get_raw_config_bool(&self, key: impl AsRef<str>) -> bool {
// Not the most obvious way to encode bool as string, but it is matter
// of backward compatibility.
let res = self.get_raw_config_int(key).await;
res.unwrap_or_default() > 0
}
pub async fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
where
T: AsRef<str>,
{
let value = if value { Some("1") } else { None };
self.set_raw_config(context, key, value).await
}
pub async fn set_raw_config_int64(
&self,
context: &Context,
key: impl AsRef<str>,
value: i64,
) -> Result<()> {
self.set_raw_config(context, key, Some(&format!("{}", value)))
.await
}
pub async fn get_raw_config_int64(&self, key: impl AsRef<str>) -> Option<i64> {
self.get_raw_config(key).await.and_then(|r| r.parse().ok())
}
/// Alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above.
/// the ORDER BY ensures, this function always returns the most recent id,
/// eg. if a Message-ID is split into different messages.
pub async fn get_rowid(
&self,
table: impl AsRef<str>,
field: impl AsRef<str>,
value: impl AsRef<str>,
) -> Result<u32> {
// alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above.
// the ORDER BY ensures, this function always returns the most recent id,
// eg. if a Message-ID is split into different messages.
let query = format!(
"SELECT id FROM {} WHERE {}=? ORDER BY id DESC",
table.as_ref(),
field.as_ref(),
);
let res: i32 = self.query_value(&query, paramsx![value.as_ref()]).await?;
Ok(res as u32)
}
pub async fn get_rowid2(
&self,
table: impl AsRef<str>,
field: impl AsRef<str>,
value: i64,
field2: impl AsRef<str>,
value2: i32,
) -> Result<u32> {
let query = format!(
"SELECT id FROM {} WHERE {}=? AND {}=? ORDER BY id DESC",
table.as_ref(),
field.as_ref(),
field2.as_ref(),
);
let res: i32 = self
.query_value(query.as_ref(), paramsx![value, value2])
.await?;
Ok(res as u32)
}
}
pub async fn housekeeping(context: &Context) -> Result<()> {
let mut files_in_use = HashSet::new();
let mut unreferenced_count = 0usize;
info!(context, "Start housekeeping...");
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;",
Param::File,
)
.await?;
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM jobs;",
Param::File,
)
.await?;
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM chats;",
Param::ProfileImage,
)
.await?;
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM contacts;",
Param::ProfileImage,
)
.await?;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT value FROM config;").fetch(&pool);
while let Some(row) = rows.next().await {
let (row,): (String,) = row?;
maybe_add_file(&mut files_in_use, row);
}
info!(context, "{} files in use.", files_in_use.len(),);
/* go through directory and delete unused files */
let p = context.get_blobdir();
match async_std::fs::read_dir(p).await {
Ok(mut dir_handle) => {
/* avoid deletion of files that are just created to build a message object */
let diff = std::time::Duration::from_secs(60 * 60);
let keep_files_newer_than = std::time::SystemTime::now().checked_sub(diff).unwrap();
while let Some(entry) = dir_handle.next().await {
if entry.is_err() {
break;
}
let entry = entry.unwrap();
let name_f = entry.file_name();
let name_s = name_f.to_string_lossy();
if is_file_in_use(&files_in_use, None, &name_s)
|| is_file_in_use(&files_in_use, Some(".increation"), &name_s)
|| is_file_in_use(&files_in_use, Some(".waveform"), &name_s)
|| is_file_in_use(&files_in_use, Some("-preview.jpg"), &name_s)
{
continue;
}
unreferenced_count += 1;
if let Ok(stats) = async_std::fs::metadata(entry.path()).await {
let recently_created =
stats.created().is_ok() && stats.created().unwrap() > keep_files_newer_than;
let recently_modified = stats.modified().is_ok()
&& stats.modified().unwrap() > keep_files_newer_than;
let recently_accessed = stats.accessed().is_ok()
&& stats.accessed().unwrap() > keep_files_newer_than;
if recently_created || recently_modified || recently_accessed {
info!(
context,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name(),
);
continue;
}
}
info!(
context,
"Housekeeping: Deleting unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name()
);
let path = entry.path();
dc_delete_file(context, path).await;
}
}
Err(err) => {
warn!(
context,
"Housekeeping: Cannot open {}. ({})",
context.get_blobdir().display(),
err
);
}
}
if let Err(err) = prune_tombstones(context).await {
warn!(
context,
"Houskeeping: Cannot prune message tombstones: {}", err
);
}
info!(context, "Housekeeping done.",);
Ok(())
}
fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
let name_to_check = if let Some(namespc) = namespc_opt {
let name_len = name.len();
let namespc_len = namespc.len();
if name_len <= namespc_len || !name.ends_with(namespc) {
return false;
}
&name[..name_len - namespc_len]
} else {
name
};
files_in_use.contains(name_to_check)
}
fn maybe_add_file(files_in_use: &mut HashSet<String>, file: impl AsRef<str>) {
if !file.as_ref().starts_with("$BLOBDIR/") {
return;
}
files_in_use.insert(file.as_ref()[9..].into());
}
async fn maybe_add_from_param(
context: &Context,
files_in_use: &mut HashSet<String>,
query: &str,
param_id: Param,
) -> Result<()> {
info!(context, "maybe_add_from_param: {}", query);
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(query).fetch(&pool);
while let Some(row) = rows.next().await {
let (row,): (String,) = row?;
info!(context, "param: {}", &row);
let param: Params = row.parse().unwrap_or_default();
if let Some(file) = param.get(param_id) {
info!(context, "got file: {:?} {}", param_id, file);
maybe_add_file(files_in_use, file);
}
}
Ok(())
}
#[allow(clippy::cognitive_complexity)]
async fn open(
context: &Context,
sql: &Sql,
dbfile: impl AsRef<Path>,
readonly: bool,
) -> crate::error::Result<()> {
if sql.is_open().await {
error!(
context,
"Cannot open, database \"{:?}\" already opened.",
dbfile.as_ref(),
);
return Err(Error::SqlAlreadyOpen.into());
}
if readonly {
// TODO: readonly mode
}
let xpool = sqlx::SqlitePool::builder()
.min_size(1)
.max_size(4)
.build(&format!("sqlite://{}", dbfile.as_ref().to_string_lossy()))
.await?;
{
*sql.xpool.write().await = Some(xpool)
}
if !readonly {
let mut exists_before_update = false;
let mut dbversion_before_update: i32 = -1;
if sql.table_exists("config").await? {
exists_before_update = true;
if let Some(version) = sql.get_raw_config_int("dbversion").await {
dbversion_before_update = version;
}
}
// (1) update low-level database structure.
// this should be done before updates that use high-level objects that
// rely themselves on the low-level structure.
// --------------------------------------------------------------------
migrations::run(context, &sql, dbversion_before_update, exists_before_update).await?;
// general updates
// (2) updates that require high-level objects
// (the structure is complete now and all objects are usable)
// --------------------------------------------------------------------
let mut recalc_fingerprints = false;
let mut update_icons = false;
if dbversion_before_update < 34 {
recalc_fingerprints = true;
}
if dbversion_before_update < 61 {
update_icons = true;
}
if recalc_fingerprints {
info!(context, "[migration] recalc fingerprints");
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT addr FROM acpeerstates;").fetch(&pool);
while let Some(addr) = rows.next().await {
let (addr,): (String,) = addr?;
if let Ok(ref mut peerstate) = Peerstate::from_addr(context, &addr).await {
peerstate.recalc_fingerprint();
peerstate.save_to_db(sql, false).await?;
}
}
}
if update_icons {
update_saved_messages_icon(context).await?;
update_device_icon(context).await?;
}
}
info!(context, "Opened {:?}.", dbfile.as_ref(),);
Ok(())
}
/// Removes from the database locally deleted messages that also don't
/// have a server UID.
async fn prune_tombstones(context: &Context) -> Result<()> {
context
.sql
.execute(
r#"
DELETE FROM msgs
WHERE (chat_id = ? OR hidden)
AND server_uid = 0
"#,
paramsx![DC_CHAT_ID_TRASH as i32],
)
.await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_maybe_add_file() {
let mut files = Default::default();
maybe_add_file(&mut files, "$BLOBDIR/hello");
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
maybe_add_file(&mut files, "world2.txt");
maybe_add_file(&mut files, "$BLOBDIR");
assert!(files.contains("hello"));
assert!(files.contains("world.txt"));
assert!(!files.contains("world2.txt"));
assert!(!files.contains("$BLOBDIR"));
}
#[test]
fn test_is_file_in_use() {
let mut files = Default::default();
maybe_add_file(&mut files, "$BLOBDIR/hello");
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
maybe_add_file(&mut files, "world2.txt");
assert!(is_file_in_use(&files, None, "hello"));
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
}
}

View File

@@ -360,7 +360,7 @@ impl Context {
// create saved-messages chat;
// we do this only once, if the user has deleted the chat, he can recreate it manually.
if !self.sql.get_raw_config_bool("self-chat-added").await {
if !self.sql.get_raw_config_bool(&self, "self-chat-added").await {
self.sql
.set_raw_config_bool(&self, "self-chat-added", true)
.await?;
@@ -409,7 +409,7 @@ mod tests {
#[async_std::test]
async fn test_set_stock_translation() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
.await
@@ -419,7 +419,7 @@ mod tests {
#[async_std::test]
async fn test_set_stock_translation_wrong_replacements() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert!(t
.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
@@ -434,7 +434,7 @@ mod tests {
#[async_std::test]
async fn test_stock_str() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_eq!(
t.ctx.stock_str(StockMessage::NoMessages).await,
"No messages."
@@ -443,7 +443,7 @@ mod tests {
#[async_std::test]
async fn test_stock_string_repl_str() {
let t = TestContext::new().await;
let t = dummy_context().await;
// uses %1$s substitution
assert_eq!(
t.ctx
@@ -456,7 +456,7 @@ mod tests {
#[async_std::test]
async fn test_stock_string_repl_int() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_eq!(
t.ctx
.stock_string_repl_int(StockMessage::MsgAddMember, 42)
@@ -467,7 +467,7 @@ mod tests {
#[async_std::test]
async fn test_stock_string_repl_str2() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_eq!(
t.ctx
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar")
@@ -478,7 +478,7 @@ mod tests {
#[async_std::test]
async fn test_stock_system_msg_simple() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_eq!(
t.ctx
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)
@@ -489,7 +489,7 @@ mod tests {
#[async_std::test]
async fn test_stock_system_msg_add_member_by_me() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_eq!(
t.ctx
.stock_system_msg(
@@ -505,7 +505,7 @@ mod tests {
#[async_std::test]
async fn test_stock_system_msg_add_member_by_me_with_displayname() {
let t = TestContext::new().await;
let t = dummy_context().await;
Contact::create(&t.ctx, "Alice", "alice@example.com")
.await
.expect("failed to create contact");
@@ -524,7 +524,7 @@ mod tests {
#[async_std::test]
async fn test_stock_system_msg_add_member_by_other_with_displayname() {
let t = TestContext::new().await;
let t = dummy_context().await;
let contact_id = {
Contact::create(&t.ctx, "Alice", "alice@example.com")
.await
@@ -548,7 +548,7 @@ mod tests {
#[async_std::test]
async fn test_stock_system_msg_grp_name() {
let t = TestContext::new().await;
let t = dummy_context().await;
assert_eq!(
t.ctx
.stock_system_msg(
@@ -564,7 +564,7 @@ mod tests {
#[async_std::test]
async fn test_stock_system_msg_grp_name_other() {
let t = TestContext::new().await;
let t = dummy_context().await;
let id = Contact::create(&t.ctx, "Alice", "alice@example.com")
.await
.expect("failed to create contact");
@@ -579,7 +579,7 @@ mod tests {
#[async_std::test]
async fn test_update_device_chats() {
let t = TestContext::new().await;
let t = dummy_context().await;
t.ctx.update_device_chats().await.ok();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 2);

View File

@@ -18,69 +18,44 @@ pub(crate) struct TestContext {
pub dir: TempDir,
}
impl TestContext {
/// Create a new [TestContext].
///
/// The [Context] will be created and have an SQLite database named "db.sqlite" in the
/// [TestContext.dir] directory. This directory is cleaned up when the [TestContext] is
/// dropped.
///
/// [Context]: crate::context::Context
pub async fn new() -> Self {
pretty_env_logger::try_init_timed().ok();
/// Create a new, opened [TestContext] using given callback.
///
/// The [Context] will be opened with the SQLite database named
/// "db.sqlite" in the [TestContext.dir] directory.
///
/// [Context]: crate::context::Context
pub(crate) async fn test_context() -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new("FakeOs".into(), dbfile.into()).await.unwrap();
TestContext { ctx, dir }
}
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new("FakeOS".into(), dbfile.into()).await.unwrap();
let events = ctx.get_event_emitter();
/// Return a dummy [TestContext].
///
/// The context will be opened and use the SQLite database as
/// specified in [test_context] but there is no callback hooked up,
/// i.e. [Context::call_cb] will always return `0`.
pub(crate) async fn dummy_context() -> TestContext {
test_context().await
}
async_std::task::spawn(async move {
while let Some(event) = events.recv().await {
log::info!("{:?}", event);
}
});
pub(crate) async fn configured_offline_context() -> TestContext {
configured_offline_context_with_addr("alice@example.org").await
}
TestContext { ctx, dir }
}
/// Create a new configured [TestContext].
///
/// This is a shortcut which automatically calls [TestContext::configure_alice] after
/// creating the context.
pub async fn new_alice() -> Self {
let t = Self::new().await;
t.configure_alice().await;
t
}
/// Configure with alice@example.com.
///
/// The context will be fake-configured as the alice user, with a pre-generated secret
/// key. The email address of the user is returned as a string.
pub async fn configure_alice(&self) -> String {
let keypair = alice_keypair();
self.configure_addr(&keypair.addr.to_string()).await;
key::store_self_keypair(&self.ctx, &keypair, key::KeyPairUse::Default)
.await
.expect("Failed to save Alice's key");
keypair.addr.to_string()
}
/// Configure as a given email address.
///
/// The context will be configured but the key will not be pre-generated so if a key is
/// used the fingerprint will be different every time.
pub async fn configure_addr(&self, addr: &str) {
self.ctx.set_config(Config::Addr, Some(addr)).await.unwrap();
self.ctx
.set_config(Config::ConfiguredAddr, Some(addr))
.await
.unwrap();
self.ctx
.set_config(Config::Configured, Some("1"))
.await
.unwrap();
}
pub(crate) async fn configured_offline_context_with_addr(addr: &str) -> TestContext {
let t = dummy_context().await;
t.ctx.set_config(Config::Addr, Some(addr)).await.unwrap();
t.ctx
.set_config(Config::ConfiguredAddr, Some(addr))
.await
.unwrap();
t.ctx
.set_config(Config::Configured, Some("1"))
.await
.unwrap();
t
}
/// Load a pre-generated keypair for alice@example.com from disk.
@@ -103,6 +78,20 @@ pub(crate) fn alice_keypair() -> key::KeyPair {
}
}
/// Creates Alice with a pre-generated keypair.
///
/// Returns the address of the keypair created (alice@example.com).
pub(crate) async fn configure_alice_keypair(ctx: &Context) -> String {
let keypair = alice_keypair();
ctx.set_config(Config::ConfiguredAddr, Some(&keypair.addr.to_string()))
.await
.unwrap();
key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default)
.await
.expect("Failed to save Alice's key");
keypair.addr.to_string()
}
/// Load a pre-generated keypair for bob@example.net from disk.
///
/// Like [alice_keypair] but a different key and identity.

View File

@@ -11,7 +11,7 @@ use crate::context::Context;
use crate::dc_tools::*;
/// Token namespace
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
#[repr(i32)]
pub enum Namespace {
Unknown = 0,
@@ -34,7 +34,7 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -
.sql
.execute(
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
paramsx![namespace, foreign_id, &token, time()],
paramsv![namespace, foreign_id, token, time()],
)
.await
.ok();
@@ -44,12 +44,12 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -
pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
context
.sql
.query_value(
.query_get_value::<String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
paramsx![namespace, foreign_id],
paramsv![namespace, foreign_id],
)
.await
.ok()
}
pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
@@ -65,7 +65,7 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
paramsx![namespace, token],
paramsv![namespace, token],
)
.await
.unwrap_or_default()