Favicon, SMTP and misc updates

Favicon:
- Replaced HTML tokenizer, much faster now.
- Caching the domain blacklist function.
- Almost all functions are async now.
- Fixed bug on minimizing data to parse
- Changed maximum icon download size to 5MB to match Bitwarden
- Added `apple-touch-icon.png` as a second fallback besides `favicon.ico`

SMTP:
- Deprecated SMTP_SSL and SMTP_EXPLICIT_TLS, replaced with SMTP_SECURITY

Misc:
- Fixed issue when `resolv.conf` contains errors and trust-dns panics (Fixes #2283)
- Updated Javscript and CSS files for admin interface
- Fixed an issue with the /admin interface which did not cleared the login cookie correctly
- Prevent websocket notifications during org import, this caused a lot of traffic, and slowed down the import.
  This is also the same as Bitwarden which does not trigger this refresh via websockets.

Rust:
- Updated to use v1.59
- Use the new `strip` option and enabled to strip `debuginfo`
- Enabled `lto` with `thin`
- Removed the strip RUN from the alpine armv7, this is now done automatically
This commit is contained in:
BlackDex 2022-02-22 20:48:00 +01:00
parent 5f01db69ff
commit 42136a7097
No known key found for this signature in database
GPG key ID: 58C80A2AA6C765E1
16 changed files with 4390 additions and 2180 deletions

View file

@ -331,9 +331,8 @@
# SMTP_HOST=smtp.domain.tld # SMTP_HOST=smtp.domain.tld
# SMTP_FROM=vaultwarden@domain.tld # SMTP_FROM=vaultwarden@domain.tld
# SMTP_FROM_NAME=Vaultwarden # SMTP_FROM_NAME=Vaultwarden
# SMTP_SECURITY=starttls # ("starttls", "force_tls", "off") Enable a secure connection. Default is "starttls" (Explicit - ports 587 or 25), "force_tls" (Implicit - port 465) or "off", no encryption (port 25)
# SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 is outdated and used with Implicit TLS. # SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 is outdated and used with Implicit TLS.
# SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true. Either port 587 or 25 are default.
# SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work. Usually port 465 is used here.
# SMTP_USERNAME=username # SMTP_USERNAME=username
# SMTP_PASSWORD=password # SMTP_PASSWORD=password
# SMTP_TIMEOUT=15 # SMTP_TIMEOUT=15

View file

@ -1,7 +1,7 @@
--- ---
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1 rev: v4.1.0
hooks: hooks:
- id: check-yaml - id: check-yaml
- id: check-json - id: check-json

356
Cargo.lock generated
View file

@ -99,6 +99,25 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "async-mutex"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
dependencies = [
"event-listener",
]
[[package]]
name = "async-rwlock"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c"
dependencies = [
"async-mutex",
"event-listener",
]
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.2" version = "0.3.2"
@ -302,6 +321,40 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cached"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4dfac631a8e77b2f327f7852bb6172771f5279c4512efe79fad6067b37be3d"
dependencies = [
"async-mutex",
"async-rwlock",
"async-trait",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
"hashbrown",
"once_cell",
]
[[package]]
name = "cached_proc_macro"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "725f434d6da2814b989bd905c62ca28a9383feff7440210dc279665fbbbc9511"
dependencies = [
"cached_proc_macro_types",
"darling",
"quote",
"syn",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.73" version = "1.0.73"
@ -352,7 +405,7 @@ checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz-build", "chrono-tz-build",
"phf 0.10.1", "phf",
] ]
[[package]] [[package]]
@ -362,8 +415,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069"
dependencies = [ dependencies = [
"parse-zoneinfo", "parse-zoneinfo",
"phf 0.10.1", "phf",
"phf_codegen 0.10.0", "phf_codegen",
] ]
[[package]] [[package]]
@ -530,6 +583,41 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "darling"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "5.1.0" version = "5.1.0"
@ -685,9 +773,9 @@ dependencies = [
[[package]] [[package]]
name = "enum-as-inner" name = "enum-as-inner"
version = "0.3.3" version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -704,6 +792,12 @@ dependencies = [
"backtrace", "backtrace",
] ]
[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -808,16 +902,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
dependencies = [
"mac",
"new_debug_unreachable",
]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.21"
@ -958,9 +1042,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.4" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
@ -1054,12 +1138,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.3" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -1120,17 +1201,12 @@ dependencies = [
] ]
[[package]] [[package]]
name = "html5ever" name = "html5gum"
version = "0.25.1" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" checksum = "2dad48b66db55322add2819ae1d7bda0c32f3415269a08330679dbc8b0afeb30"
dependencies = [ dependencies = [
"log", "jetscii",
"mac",
"markup5ever",
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@ -1204,6 +1280,12 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.1.5" version = "0.1.5"
@ -1285,6 +1367,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "jetscii"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9447923c57a8a2d5c1b0875cdf96a6324275df728b498f2ede0e5cbde088a15"
[[package]] [[package]]
name = "job_scheduler" name = "job_scheduler"
version = "1.2.1" version = "1.2.1"
@ -1363,9 +1451,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.118" version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
@ -1426,12 +1514,6 @@ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]] [[package]]
name = "mach" name = "mach"
version = "0.3.2" version = "0.3.2"
@ -1447,32 +1529,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "markup5ever"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
"log",
"phf 0.8.0",
"phf_codegen 0.8.0",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever_rcdom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b"
dependencies = [
"html5ever",
"markup5ever",
"tendril",
"xml5ever",
]
[[package]] [[package]]
name = "match_cfg" name = "match_cfg"
version = "0.1.0" version = "0.1.0"
@ -1682,12 +1738,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.23.1" version = "0.23.1"
@ -2073,32 +2123,13 @@ dependencies = [
"sha-1 0.8.2", "sha-1 0.8.2",
] ]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared 0.8.0",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.10.1" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [ dependencies = [
"phf_shared 0.10.0", "phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
dependencies = [
"phf_generator 0.8.0",
"phf_shared 0.8.0",
] ]
[[package]] [[package]]
@ -2107,18 +2138,8 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [ dependencies = [
"phf_generator 0.10.0", "phf_generator",
"phf_shared 0.10.0", "phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared 0.8.0",
"rand 0.7.3",
] ]
[[package]] [[package]]
@ -2127,19 +2148,10 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [ dependencies = [
"phf_shared 0.10.0", "phf_shared",
"rand 0.8.5", "rand 0.8.5",
] ]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.10.0" version = "0.10.0"
@ -2201,12 +2213,6 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]] [[package]]
name = "proc-macro-hack" name = "proc-macro-hack"
version = "0.5.19" version = "0.5.19"
@ -2331,7 +2337,6 @@ dependencies = [
"rand_chacha 0.2.2", "rand_chacha 0.2.2",
"rand_core 0.5.1", "rand_core 0.5.1",
"rand_hc", "rand_hc",
"rand_pcg",
] ]
[[package]] [[package]]
@ -2395,7 +2400,7 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [ dependencies = [
"getrandom 0.2.4", "getrandom 0.2.5",
] ]
[[package]] [[package]]
@ -2407,15 +2412,6 @@ dependencies = [
"rand_core 0.5.1", "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]] [[package]]
name = "raw-cpuid" name = "raw-cpuid"
version = "10.2.0" version = "10.2.0"
@ -2589,7 +2585,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket" name = "rocket"
version = "0.5.0-rc.1" version = "0.5.0-rc.1"
source = "git+https://github.com/SergioBenitez/Rocket?rev=66d18bf66517e2765494d082629e9b9748ff8ad6#66d18bf66517e2765494d082629e9b9748ff8ad6" source = "git+https://github.com/SergioBenitez/Rocket?rev=91e3b4397a1637d0f55f23db712cf7bda0c7f891#91e3b4397a1637d0f55f23db712cf7bda0c7f891"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
@ -2627,7 +2623,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket_codegen" name = "rocket_codegen"
version = "0.5.0-rc.1" version = "0.5.0-rc.1"
source = "git+https://github.com/SergioBenitez/Rocket?rev=66d18bf66517e2765494d082629e9b9748ff8ad6#66d18bf66517e2765494d082629e9b9748ff8ad6" source = "git+https://github.com/SergioBenitez/Rocket?rev=91e3b4397a1637d0f55f23db712cf7bda0c7f891#91e3b4397a1637d0f55f23db712cf7bda0c7f891"
dependencies = [ dependencies = [
"devise", "devise",
"glob", "glob",
@ -2642,7 +2638,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket_http" name = "rocket_http"
version = "0.5.0-rc.1" version = "0.5.0-rc.1"
source = "git+https://github.com/SergioBenitez/Rocket?rev=66d18bf66517e2765494d082629e9b9748ff8ad6#66d18bf66517e2765494d082629e9b9748ff8ad6" source = "git+https://github.com/SergioBenitez/Rocket?rev=91e3b4397a1637d0f55f23db712cf7bda0c7f891#91e3b4397a1637d0f55f23db712cf7bda0c7f891"
dependencies = [ dependencies = [
"cookie 0.16.0", "cookie 0.16.0",
"either", "either",
@ -2656,6 +2652,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"ref-cast", "ref-cast",
"rustls", "rustls",
"rustls-pemfile",
"serde", "serde",
"smallvec 1.8.0", "smallvec 1.8.0",
"stable-pattern", "stable-pattern",
@ -2683,17 +2680,25 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.19.1" version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921"
dependencies = [ dependencies = [
"base64 0.13.0",
"log", "log",
"ring", "ring",
"sct", "sct",
"webpki", "webpki",
] ]
[[package]]
name = "rustls-pemfile"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
dependencies = [
"base64 0.13.0",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.6" version = "1.0.6"
@ -2748,9 +2753,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "sct" name = "sct"
version = "0.6.1" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -3083,30 +3088,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]] [[package]]
name = "string_cache" name = "strsim"
version = "0.8.3" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"parking_lot 0.11.2",
"phf_shared 0.10.0",
"precomputed-hash",
"serde",
]
[[package]]
name = "string_cache_codegen"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [
"phf_generator 0.8.0",
"phf_shared 0.8.0",
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
@ -3151,17 +3136,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "tendril"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33"
dependencies = [
"futf",
"mac",
"utf-8",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.30" version = "1.0.30"
@ -3324,9 +3298,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.22.0" version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b"
dependencies = [ dependencies = [
"rustls", "rustls",
"tokio", "tokio",
@ -3588,12 +3562,6 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -3640,19 +3608,13 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "0.8.2" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [ dependencies = [
"getrandom 0.2.4", "getrandom 0.2.5",
] ]
[[package]] [[package]]
@ -3667,6 +3629,7 @@ version = "1.0.0"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes 1.1.0", "bytes 1.1.0",
"cached",
"chashmap", "chashmap",
"chrono", "chrono",
"chrono-tz", "chrono-tz",
@ -3682,14 +3645,13 @@ dependencies = [
"futures", "futures",
"governor", "governor",
"handlebars", "handlebars",
"html5ever", "html5gum",
"idna 0.2.3", "idna 0.2.3",
"job_scheduler", "job_scheduler",
"jsonwebtoken", "jsonwebtoken",
"lettre", "lettre",
"libsqlite3-sys", "libsqlite3-sys",
"log", "log",
"markup5ever_rcdom",
"num-derive", "num-derive",
"num-traits", "num-traits",
"once_cell", "once_cell",
@ -3860,9 +3822,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki" name = "webpki"
version = "0.21.4" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -3988,18 +3950,6 @@ dependencies = [
"winapi-build", "winapi-build",
] ]
[[package]]
name = "xml5ever"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9234163818fd8e2418fcde330655e757900d4236acd8cc70fef345ef91f6d865"
dependencies = [
"log",
"mac",
"markup5ever",
"time 0.1.43",
]
[[package]] [[package]]
name = "yansi" name = "yansi"
version = "0.5.0" version = "0.5.0"

View file

@ -3,7 +3,7 @@ name = "vaultwarden"
version = "1.0.0" version = "1.0.0"
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"] authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
edition = "2021" edition = "2021"
rust-version = "1.58.1" rust-version = "1.59"
resolver = "2" resolver = "2"
repository = "https://github.com/dani-garcia/vaultwarden" repository = "https://github.com/dani-garcia/vaultwarden"
@ -116,11 +116,11 @@ handlebars = { version = "4.2.1", features = ["dir_source"] }
reqwest = { version = "0.11.9", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } reqwest = { version = "0.11.9", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
# For favicon extraction from main website # For favicon extraction from main website
html5ever = "0.25.1" html5gum = "0.4.0"
markup5ever_rcdom = "0.1.0"
regex = { version = "1.5.4", features = ["std", "perf", "unicode-perl"], default-features = false } regex = { version = "1.5.4", features = ["std", "perf", "unicode-perl"], default-features = false }
data-url = "0.1.1" data-url = "0.1.1"
bytes = "1.1.0" bytes = "1.1.0"
cached = "0.30.0"
# Used for custom short lived cookie jar during favicon extraction # Used for custom short lived cookie jar during favicon extraction
cookie = "0.15.1" cookie = "0.15.1"
@ -140,7 +140,7 @@ governor = "0.4.2"
ctrlc = { version = "3.2.1", features = ["termination"] } ctrlc = { version = "3.2.1", features = ["termination"] }
[patch.crates-io] [patch.crates-io]
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '66d18bf66517e2765494d082629e9b9748ff8ad6' } rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '91e3b4397a1637d0f55f23db712cf7bda0c7f891' }
# The maintainer of the `job_scheduler` crate doesn't seem to have responded # The maintainer of the `job_scheduler` crate doesn't seem to have responded
# to any issues or PRs for almost a year (as of April 2021). This hopefully # to any issues or PRs for almost a year (as of April 2021). This hopefully
@ -148,3 +148,9 @@ rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '66d18bf66517e
# In particular, `cron` has since implemented parsing of some common syntax # In particular, `cron` has since implemented parsing of some common syntax
# that wasn't previously supported (https://github.com/zslayton/cron/pull/64). # that wasn't previously supported (https://github.com/zslayton/cron/pull/64).
job_scheduler = { git = 'https://github.com/jjlin/job_scheduler', rev = 'ee023418dbba2bfe1e30a5fd7d937f9e33739806' } job_scheduler = { git = 'https://github.com/jjlin/job_scheduler', rev = 'ee023418dbba2bfe1e30a5fd7d937f9e33739806' }
# Strip debuginfo from the release builds
# Also enable thin LTO for some optimizations
[profile.release]
strip = "debuginfo"
lto = "thin"

View file

@ -182,12 +182,6 @@ RUN touch src/main.rs
# your actual source files being built # your actual source files being built
# hadolint ignore=DL3059 # hadolint ignore=DL3059
RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }} RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }}
{% if "alpine" in target_file %}
{% if "armv7" in target_file %}
# hadolint ignore=DL3059
RUN musl-strip target/{{ package_arch_target }}/release/vaultwarden
{% endif %}
{% endif %}
######################## RUNTIME IMAGE ######################## ######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image # Create a new stage with a minimal image

View file

@ -78,8 +78,6 @@ RUN touch src/main.rs
# your actual source files being built # your actual source files being built
# hadolint ignore=DL3059 # hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
# hadolint ignore=DL3059
RUN musl-strip target/armv7-unknown-linux-musleabihf/release/vaultwarden
######################## RUNTIME IMAGE ######################## ######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image # Create a new stage with a minimal image

View file

@ -78,8 +78,6 @@ RUN touch src/main.rs
# your actual source files being built # your actual source files being built
# hadolint ignore=DL3059 # hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
# hadolint ignore=DL3059
RUN musl-strip target/armv7-unknown-linux-musleabihf/release/vaultwarden
######################## RUNTIME IMAGE ######################## ######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image # Create a new stage with a minimal image

View file

@ -301,7 +301,7 @@ fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
#[get("/logout")] #[get("/logout")]
fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect { fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect {
cookies.remove(Cookie::named(COOKIE_NAME)); cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
Redirect::to(admin_url(referer)) Redirect::to(admin_url(referer))
} }
@ -638,7 +638,7 @@ impl<'r> FromRequest<'r> for AdminToken {
if decode_admin(access_token).is_err() { if decode_admin(access_token).is_err() {
// Remove admin cookie // Remove admin cookie
cookies.remove(Cookie::named(COOKIE_NAME)); cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
error!("Invalid or expired admin JWT. IP: {}.", ip); error!("Invalid or expired admin JWT. IP: {}.", ip);
return Outcome::Forward(()); return Outcome::Forward(());
} }

View file

@ -1182,9 +1182,7 @@ async fn post_org_import(
let ciphers = stream::iter(data.Ciphers) let ciphers = stream::iter(data.Ciphers)
.then(|cipher_data| async { .then(|cipher_data| async {
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone()); let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::CipherCreate) update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await.ok();
.await
.ok();
cipher cipher
}) })
.collect::<Vec<Cipher>>() .collect::<Vec<Cipher>>()

View file

@ -1,21 +1,28 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
net::{IpAddr, ToSocketAddrs}, net::IpAddr,
sync::{Arc, RwLock}, sync::Arc,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use bytes::{Buf, Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{stream::StreamExt, TryFutureExt}; use futures::{stream::StreamExt, TryFutureExt};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use reqwest::{header, Client, Response}; use reqwest::{
header::{self, HeaderMap, HeaderValue},
Client, Response,
};
use rocket::{http::ContentType, response::Redirect, Route}; use rocket::{http::ContentType, response::Redirect, Route};
use tokio::{ use tokio::{
fs::{create_dir_all, remove_file, symlink_metadata, File}, fs::{create_dir_all, remove_file, symlink_metadata, File},
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},
net::lookup_host,
sync::RwLock,
}; };
use html5gum::{Emitter, EndTag, InfallibleTokenizer, Readable, StartTag, StringReader, Tokenizer};
use crate::{ use crate::{
error::Error, error::Error,
util::{get_reqwest_client_builder, Cached}, util::{get_reqwest_client_builder, Cached},
@ -34,39 +41,50 @@ pub fn routes() -> Vec<Route> {
static CLIENT: Lazy<Client> = Lazy::new(|| { static CLIENT: Lazy<Client> = Lazy::new(|| {
// Generate the default headers // Generate the default headers
let mut default_headers = header::HeaderMap::new(); let mut default_headers = HeaderMap::new();
default_headers default_headers.insert(header::USER_AGENT, HeaderValue::from_static("Links (2.22; Linux X86_64; GNU C; text)"));
.insert(header::USER_AGENT, header::HeaderValue::from_static("Links (2.22; Linux X86_64; GNU C; text)")); default_headers.insert(header::ACCEPT, HeaderValue::from_static("text/html, text/*;q=0.5, image/*, */*;q=0.1"));
default_headers default_headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en,*;q=0.1"));
.insert(header::ACCEPT, header::HeaderValue::from_static("text/html, text/*;q=0.5, image/*, */*;q=0.1")); default_headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
default_headers.insert(header::ACCEPT_LANGUAGE, header::HeaderValue::from_static("en,*;q=0.1")); default_headers.insert(header::PRAGMA, HeaderValue::from_static("no-cache"));
default_headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache"));
default_headers.insert(header::PRAGMA, header::HeaderValue::from_static("no-cache")); // Generate the cookie store
let cookie_store = Arc::new(Jar::default());
// Reuse the client between requests // Reuse the client between requests
let client = get_reqwest_client_builder()
.cookie_provider(cookie_store.clone())
.timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
.default_headers(default_headers.clone());
match client.build() {
Ok(client) => client,
Err(e) => {
error!("Possible trust-dns error, trying with trust-dns disabled: '{e}'");
get_reqwest_client_builder() get_reqwest_client_builder()
.cookie_provider(Arc::new(Jar::default())) .cookie_provider(cookie_store)
.timeout(Duration::from_secs(CONFIG.icon_download_timeout())) .timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
.default_headers(default_headers) .default_headers(default_headers)
.trust_dns(false)
.build() .build()
.expect("Failed to build icon client") .expect("Failed to build client")
}
}
}); });
// Build Regex only once since this takes a lot of time. // Build Regex only once since this takes a lot of time.
static ICON_REL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)icon$|apple.*icon").unwrap());
static ICON_REL_BLACKLIST: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)mask-icon").unwrap());
static ICON_SIZE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap()); static ICON_SIZE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap());
// Special HashMap which holds the user defined Regex to speedup matching the regex. // Special HashMap which holds the user defined Regex to speedup matching the regex.
static ICON_BLACKLIST_REGEX: Lazy<RwLock<HashMap<String, Regex>>> = Lazy::new(|| RwLock::new(HashMap::new())); static ICON_BLACKLIST_REGEX: Lazy<RwLock<HashMap<String, Regex>>> = Lazy::new(|| RwLock::new(HashMap::new()));
fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> { async fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> {
if !is_valid_domain(domain) { if !is_valid_domain(domain).await {
warn!("Invalid domain: {}", domain); warn!("Invalid domain: {}", domain);
return None; return None;
} }
if is_domain_blacklisted(domain) { if is_domain_blacklisted(domain).await {
return None; return None;
} }
@ -84,30 +102,30 @@ fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> {
} }
#[get("/<domain>/icon.png")] #[get("/<domain>/icon.png")]
fn icon_custom(domain: String) -> Option<Redirect> { async fn icon_custom(domain: String) -> Option<Redirect> {
icon_redirect(&domain, &CONFIG.icon_service()) icon_redirect(&domain, &CONFIG.icon_service()).await
} }
#[get("/<domain>/icon.png")] #[get("/<domain>/icon.png")]
fn icon_bitwarden(domain: String) -> Option<Redirect> { async fn icon_bitwarden(domain: String) -> Option<Redirect> {
icon_redirect(&domain, "https://icons.bitwarden.net/{}/icon.png") icon_redirect(&domain, "https://icons.bitwarden.net/{}/icon.png").await
} }
#[get("/<domain>/icon.png")] #[get("/<domain>/icon.png")]
fn icon_duckduckgo(domain: String) -> Option<Redirect> { async fn icon_duckduckgo(domain: String) -> Option<Redirect> {
icon_redirect(&domain, "https://icons.duckduckgo.com/ip3/{}.ico") icon_redirect(&domain, "https://icons.duckduckgo.com/ip3/{}.ico").await
} }
#[get("/<domain>/icon.png")] #[get("/<domain>/icon.png")]
fn icon_google(domain: String) -> Option<Redirect> { async fn icon_google(domain: String) -> Option<Redirect> {
icon_redirect(&domain, "https://www.google.com/s2/favicons?domain={}&sz=32") icon_redirect(&domain, "https://www.google.com/s2/favicons?domain={}&sz=32").await
} }
#[get("/<domain>/icon.png")] #[get("/<domain>/icon.png")]
async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> { async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png"); const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png");
if !is_valid_domain(&domain) { if !is_valid_domain(&domain).await {
warn!("Invalid domain: {}", domain); warn!("Invalid domain: {}", domain);
return Cached::ttl( return Cached::ttl(
(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()), (ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
@ -128,7 +146,7 @@ async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
/// ///
/// This does some manual checks and makes use of Url to do some basic checking. /// This does some manual checks and makes use of Url to do some basic checking.
/// domains can't be larger then 63 characters (not counting multiple subdomains) according to the RFC's, but we limit the total size to 255. /// domains can't be larger then 63 characters (not counting multiple subdomains) according to the RFC's, but we limit the total size to 255.
fn is_valid_domain(domain: &str) -> bool { async fn is_valid_domain(domain: &str) -> bool {
const ALLOWED_CHARS: &str = "_-."; const ALLOWED_CHARS: &str = "_-.";
// If parsing the domain fails using Url, it will not work with reqwest. // If parsing the domain fails using Url, it will not work with reqwest.
@ -260,25 +278,22 @@ mod tests {
} }
} }
fn is_domain_blacklisted(domain: &str) -> bool { use cached::proc_macro::cached;
let mut is_blacklisted = CONFIG.icon_blacklist_non_global_ips() #[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)]
&& (domain, 0) async fn is_domain_blacklisted(domain: &str) -> bool {
.to_socket_addrs() if CONFIG.icon_blacklist_non_global_ips() {
.map(|x| { if let Ok(s) = lookup_host((domain, 0)).await {
for ip_port in x { for addr in s {
if !is_global(ip_port.ip()) { if !is_global(addr.ip()) {
warn!("IP {} for domain '{}' is not a global IP!", ip_port.ip(), domain); debug!("IP {} for domain '{}' is not a global IP!", addr.ip(), domain);
return true; return true;
} }
} }
false }
}) }
.unwrap_or(false);
// Skip the regex check if the previous one is true already
if !is_blacklisted {
if let Some(blacklist) = CONFIG.icon_blacklist_regex() { if let Some(blacklist) = CONFIG.icon_blacklist_regex() {
let mut regex_hashmap = ICON_BLACKLIST_REGEX.read().unwrap(); let mut regex_hashmap = ICON_BLACKLIST_REGEX.read().await;
// Use the pre-generate Regex stored in a Lazy HashMap if there's one, else generate it. // Use the pre-generate Regex stored in a Lazy HashMap if there's one, else generate it.
let regex = if let Some(regex) = regex_hashmap.get(&blacklist) { let regex = if let Some(regex) = regex_hashmap.get(&blacklist) {
@ -286,7 +301,7 @@ fn is_domain_blacklisted(domain: &str) -> bool {
} else { } else {
drop(regex_hashmap); drop(regex_hashmap);
let mut regex_hashmap_write = ICON_BLACKLIST_REGEX.write().unwrap(); let mut regex_hashmap_write = ICON_BLACKLIST_REGEX.write().await;
// Clear the current list if the previous key doesn't exists. // Clear the current list if the previous key doesn't exists.
// To prevent growing of the HashMap after someone has changed it via the admin interface. // To prevent growing of the HashMap after someone has changed it via the admin interface.
if regex_hashmap_write.len() >= 1 { if regex_hashmap_write.len() >= 1 {
@ -294,23 +309,21 @@ fn is_domain_blacklisted(domain: &str) -> bool {
} }
// Generate the regex to store in too the Lazy Static HashMap. // Generate the regex to store in too the Lazy Static HashMap.
let blacklist_regex = Regex::new(&blacklist).unwrap(); let blacklist_regex = Regex::new(&blacklist);
regex_hashmap_write.insert(blacklist.to_string(), blacklist_regex); regex_hashmap_write.insert(blacklist.to_string(), blacklist_regex.unwrap());
drop(regex_hashmap_write); drop(regex_hashmap_write);
regex_hashmap = ICON_BLACKLIST_REGEX.read().unwrap(); regex_hashmap = ICON_BLACKLIST_REGEX.read().await;
regex_hashmap.get(&blacklist).unwrap() regex_hashmap.get(&blacklist).unwrap()
}; };
// Use the pre-generate Regex stored in a Lazy HashMap. // Use the pre-generate Regex stored in a Lazy HashMap.
if regex.is_match(domain) { if regex.is_match(domain) {
debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain); debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain);
is_blacklisted = true; return true;
} }
} }
} false
is_blacklisted
} }
async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> { async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
@ -322,7 +335,7 @@ async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
} }
if let Some(icon) = get_cached_icon(&path).await { if let Some(icon) = get_cached_icon(&path).await {
let icon_type = match get_icon_type(&icon) { let icon_type = match get_icon_type(&icon).await {
Some(x) => x, Some(x) => x,
_ => "x-icon", _ => "x-icon",
}; };
@ -412,91 +425,62 @@ impl Icon {
} }
} }
/// Iterates over the HTML document to find <base href="http://domain.tld"> async fn get_favicons_node(
/// When found it will stop the iteration and the found base href will be shared deref via `base_href`. dom: InfallibleTokenizer<StringReader<'_>, FaviconEmitter>,
/// icons: &mut Vec<Icon>,
/// # Arguments url: &url::Url,
/// * `node` - A Parsed HTML document via html5ever::parse_document() ) {
/// * `base_href` - a mutable url::Url which will be overwritten when a base href tag has been found. const TAG_LINK: &[u8] = b"link";
/// const TAG_BASE: &[u8] = b"base";
fn get_base_href(node: &std::rc::Rc<markup5ever_rcdom::Node>, base_href: &mut url::Url) -> bool { const TAG_HEAD: &[u8] = b"head";
if let markup5ever_rcdom::NodeData::Element { const ATTR_REL: &[u8] = b"rel";
name, const ATTR_HREF: &[u8] = b"href";
attrs, const ATTR_SIZES: &[u8] = b"sizes";
..
} = &node.data
{
if name.local.as_ref() == "base" {
let attrs = attrs.borrow();
for attr in attrs.iter() {
let attr_name = attr.name.local.as_ref();
let attr_value = attr.value.as_ref();
if attr_name == "href" { let mut base_url = url.clone();
debug!("Found base href: {}", attr_value); let mut icon_tags: Vec<StartTag> = Vec::new();
*base_href = match base_href.join(attr_value) { for token in dom {
Ok(href) => href, match token {
_ => base_href.clone(), FaviconToken::StartTag(tag) => {
if tag.name == TAG_LINK
&& tag.attributes.contains_key(ATTR_REL)
&& tag.attributes.contains_key(ATTR_HREF)
{
let rel_value = std::str::from_utf8(tag.attributes.get(ATTR_REL).unwrap())
.unwrap_or_default()
.to_ascii_lowercase();
if rel_value.contains("icon") && !rel_value.contains("mask-icon") {
icon_tags.push(tag);
}
} else if tag.name == TAG_BASE && tag.attributes.contains_key(ATTR_HREF) {
let href = std::str::from_utf8(tag.attributes.get(ATTR_HREF).unwrap()).unwrap_or_default();
debug!("Found base href: {href}");
base_url = match base_url.join(href) {
Ok(inner_url) => inner_url,
_ => url.clone(),
}; };
return true;
} }
} }
return true; FaviconToken::EndTag(tag) => {
} if tag.name == TAG_HEAD {
} break;
// TODO: Might want to limit the recursion depth?
for child in node.children.borrow().iter() {
// Check if we got a true back and stop the iter.
// This means we found a <base> tag and can stop processing the html.
if get_base_href(child, base_href) {
return true;
}
}
false
}
fn get_favicons_node(node: &std::rc::Rc<markup5ever_rcdom::Node>, icons: &mut Vec<Icon>, url: &url::Url) {
if let markup5ever_rcdom::NodeData::Element {
name,
attrs,
..
} = &node.data
{
if name.local.as_ref() == "link" {
let mut has_rel = false;
let mut href = None;
let mut sizes = None;
let attrs = attrs.borrow();
for attr in attrs.iter() {
let attr_name = attr.name.local.as_ref();
let attr_value = attr.value.as_ref();
if attr_name == "rel" && ICON_REL_REGEX.is_match(attr_value) && !ICON_REL_BLACKLIST.is_match(attr_value)
{
has_rel = true;
} else if attr_name == "href" {
href = Some(attr_value);
} else if attr_name == "sizes" {
sizes = Some(attr_value);
}
}
if has_rel {
if let Some(inner_href) = href {
if let Ok(full_href) = url.join(inner_href).map(String::from) {
let priority = get_icon_priority(&full_href, sizes);
icons.push(Icon::new(priority, full_href));
}
} }
} }
} }
} }
// TODO: Might want to limit the recursion depth? for icon_tag in icon_tags {
for child in node.children.borrow().iter() { if let Some(icon_href) = icon_tag.attributes.get(ATTR_HREF) {
get_favicons_node(child, icons, url); if let Ok(full_href) = base_url.join(std::str::from_utf8(icon_href).unwrap_or_default()) {
let sizes = if let Some(v) = icon_tag.attributes.get(ATTR_SIZES) {
std::str::from_utf8(v).unwrap_or_default()
} else {
""
};
let priority = get_icon_priority(full_href.as_str(), sizes).await;
icons.push(Icon::new(priority, full_href.to_string()));
}
};
} }
} }
@ -514,13 +498,13 @@ struct IconUrlResult {
/// ///
/// # Example /// # Example
/// ``` /// ```
/// let icon_result = get_icon_url("github.com")?; /// let icon_result = get_icon_url("github.com").await?;
/// let icon_result = get_icon_url("vaultwarden.discourse.group")?; /// let icon_result = get_icon_url("vaultwarden.discourse.group").await?;
/// ``` /// ```
async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> { async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
// Default URL with secure and insecure schemes // Default URL with secure and insecure schemes
let ssldomain = format!("https://{}", domain); let ssldomain = format!("https://{domain}");
let httpdomain = format!("http://{}", domain); let httpdomain = format!("http://{domain}");
// First check the domain as given during the request for both HTTPS and HTTP. // First check the domain as given during the request for both HTTPS and HTTP.
let resp = match get_page(&ssldomain).or_else(|_| get_page(&httpdomain)).await { let resp = match get_page(&ssldomain).or_else(|_| get_page(&httpdomain)).await {
@ -537,26 +521,25 @@ async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
tld = domain_parts.next_back().unwrap(), tld = domain_parts.next_back().unwrap(),
base = domain_parts.next_back().unwrap() base = domain_parts.next_back().unwrap()
); );
if is_valid_domain(&base_domain) { if is_valid_domain(&base_domain).await {
let sslbase = format!("https://{}", base_domain); let sslbase = format!("https://{base_domain}");
let httpbase = format!("http://{}", base_domain); let httpbase = format!("http://{base_domain}");
debug!("[get_icon_url]: Trying without subdomains '{}'", base_domain); debug!("[get_icon_url]: Trying without subdomains '{base_domain}'");
sub_resp = get_page(&sslbase).or_else(|_| get_page(&httpbase)).await; sub_resp = get_page(&sslbase).or_else(|_| get_page(&httpbase)).await;
} }
// When the domain is not an IP, and has less then 2 dots, try to add www. infront of it. // When the domain is not an IP, and has less then 2 dots, try to add www. infront of it.
} else if is_ip.is_err() && domain.matches('.').count() < 2 { } else if is_ip.is_err() && domain.matches('.').count() < 2 {
let www_domain = format!("www.{}", domain); let www_domain = format!("www.{domain}");
if is_valid_domain(&www_domain) { if is_valid_domain(&www_domain).await {
let sslwww = format!("https://{}", www_domain); let sslwww = format!("https://{www_domain}");
let httpwww = format!("http://{}", www_domain); let httpwww = format!("http://{www_domain}");
debug!("[get_icon_url]: Trying with www. prefix '{}'", www_domain); debug!("[get_icon_url]: Trying with www. prefix '{www_domain}'");
sub_resp = get_page(&sslwww).or_else(|_| get_page(&httpwww)).await; sub_resp = get_page(&sslwww).or_else(|_| get_page(&httpwww)).await;
} }
} }
sub_resp sub_resp
} }
}; };
@ -571,26 +554,23 @@ async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
// Set the referer to be used on the final request, some sites check this. // Set the referer to be used on the final request, some sites check this.
// Mostly used to prevent direct linking and other security resons. // Mostly used to prevent direct linking and other security resons.
referer = url.as_str().to_string(); referer = url.to_string();
// Add the default favicon.ico to the list with the domain the content responded from. // Add the fallback favicon.ico and apple-touch-icon.png to the list with the domain the content responded from.
iconlist.push(Icon::new(35, String::from(url.join("/favicon.ico").unwrap()))); iconlist.push(Icon::new(35, String::from(url.join("/favicon.ico").unwrap())));
iconlist.push(Icon::new(40, String::from(url.join("/apple-touch-icon.png").unwrap())));
// 384KB should be more than enough for the HTML, though as we only really need the HTML header. // 384KB should be more than enough for the HTML, though as we only really need the HTML header.
let mut limited_reader = stream_to_bytes_limit(content, 384 * 1024).await?.reader(); let limited_reader = stream_to_bytes_limit(content, 384 * 1024).await?.to_vec();
use html5ever::tendril::TendrilSink; let dom = Tokenizer::new_with_emitter(limited_reader.to_reader(), FaviconEmitter::default()).infallible();
let dom = html5ever::parse_document(markup5ever_rcdom::RcDom::default(), Default::default()) get_favicons_node(dom, &mut iconlist, &url).await;
.from_utf8()
.read_from(&mut limited_reader)?;
let mut base_url: url::Url = url;
get_base_href(&dom.document, &mut base_url);
get_favicons_node(&dom.document, &mut iconlist, &base_url);
} else { } else {
// Add the default favicon.ico to the list with just the given domain // Add the default favicon.ico to the list with just the given domain
iconlist.push(Icon::new(35, format!("{}/favicon.ico", ssldomain))); iconlist.push(Icon::new(35, format!("{ssldomain}/favicon.ico")));
iconlist.push(Icon::new(35, format!("{}/favicon.ico", httpdomain))); iconlist.push(Icon::new(40, format!("{ssldomain}/apple-touch-icon.png")));
iconlist.push(Icon::new(35, format!("{httpdomain}/favicon.ico")));
iconlist.push(Icon::new(40, format!("{httpdomain}/apple-touch-icon.png")));
} }
// Sort the iconlist by priority // Sort the iconlist by priority
@ -608,7 +588,7 @@ async fn get_page(url: &str) -> Result<Response, Error> {
} }
async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> { async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> {
if is_domain_blacklisted(url::Url::parse(url).unwrap().host_str().unwrap_or_default()) { if is_domain_blacklisted(url::Url::parse(url).unwrap().host_str().unwrap_or_default()).await {
warn!("Favicon '{}' resolves to a blacklisted domain or IP!", url); warn!("Favicon '{}' resolves to a blacklisted domain or IP!", url);
} }
@ -632,12 +612,12 @@ async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Err
/// ///
/// # Example /// # Example
/// ``` /// ```
/// priority1 = get_icon_priority("http://example.com/path/to/a/favicon.png", "32x32"); /// priority1 = get_icon_priority("http://example.com/path/to/a/favicon.png", "32x32").await;
/// priority2 = get_icon_priority("https://example.com/path/to/a/favicon.ico", ""); /// priority2 = get_icon_priority("https://example.com/path/to/a/favicon.ico", "").await;
/// ``` /// ```
fn get_icon_priority(href: &str, sizes: Option<&str>) -> u8 { async fn get_icon_priority(href: &str, sizes: &str) -> u8 {
// Check if there is a dimension set // Check if there is a dimension set
let (width, height) = parse_sizes(sizes); let (width, height) = parse_sizes(sizes).await;
// Check if there is a size given // Check if there is a size given
if width != 0 && height != 0 { if width != 0 && height != 0 {
@ -679,15 +659,15 @@ fn get_icon_priority(href: &str, sizes: Option<&str>) -> u8 {
/// ///
/// # Example /// # Example
/// ``` /// ```
/// let (width, height) = parse_sizes("64x64"); // (64, 64) /// let (width, height) = parse_sizes("64x64").await; // (64, 64)
/// let (width, height) = parse_sizes("x128x128"); // (128, 128) /// let (width, height) = parse_sizes("x128x128").await; // (128, 128)
/// let (width, height) = parse_sizes("32"); // (0, 0) /// let (width, height) = parse_sizes("32").await; // (0, 0)
/// ``` /// ```
fn parse_sizes(sizes: Option<&str>) -> (u16, u16) { async fn parse_sizes(sizes: &str) -> (u16, u16) {
let mut width: u16 = 0; let mut width: u16 = 0;
let mut height: u16 = 0; let mut height: u16 = 0;
if let Some(sizes) = sizes { if !sizes.is_empty() {
match ICON_SIZE_REGEX.captures(sizes.trim()) { match ICON_SIZE_REGEX.captures(sizes.trim()) {
None => {} None => {}
Some(dimensions) => { Some(dimensions) => {
@ -703,7 +683,7 @@ fn parse_sizes(sizes: Option<&str>) -> (u16, u16) {
} }
async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> { async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
if is_domain_blacklisted(domain) { if is_domain_blacklisted(domain).await {
err_silent!("Domain is blacklisted", domain) err_silent!("Domain is blacklisted", domain)
} }
@ -727,7 +707,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
// Also check if the size is atleast 67 bytes, which seems to be the smallest png i could create // Also check if the size is atleast 67 bytes, which seems to be the smallest png i could create
if body.len() >= 67 { if body.len() >= 67 {
// Check if the icon type is allowed, else try an icon from the list. // Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&body); icon_type = get_icon_type(&body).await;
if icon_type.is_none() { if icon_type.is_none() {
debug!("Icon from {} data:image uri, is not a valid image type", domain); debug!("Icon from {} data:image uri, is not a valid image type", domain);
continue; continue;
@ -742,10 +722,10 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
} else { } else {
match get_page_with_referer(&icon.href, &icon_result.referer).await { match get_page_with_referer(&icon.href, &icon_result.referer).await {
Ok(res) => { Ok(res) => {
buffer = stream_to_bytes_limit(res, 512 * 1024).await?; // 512 KB for each icon max buffer = stream_to_bytes_limit(res, 5120 * 1024).await?; // 5120KB/5MB for each icon max (Same as icons.bitwarden.net)
// Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&buffer);
// Check if the icon type is allowed, else try an icon from the list. // Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&buffer).await;
if icon_type.is_none() { if icon_type.is_none() {
buffer.clear(); buffer.clear();
debug!("Icon from {}, is not a valid image type", icon.href); debug!("Icon from {}, is not a valid image type", icon.href);
@ -780,7 +760,7 @@ async fn save_icon(path: &str, icon: &[u8]) {
} }
} }
fn get_icon_type(bytes: &[u8]) -> Option<&'static str> { async fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
match bytes { match bytes {
[137, 80, 78, 71, ..] => Some("png"), [137, 80, 78, 71, ..] => Some("png"),
[0, 0, 1, 0, ..] => Some("x-icon"), [0, 0, 1, 0, ..] => Some("x-icon"),
@ -792,13 +772,30 @@ fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
} }
} }
/// Minimize the amount of bytes to be parsed from a reqwest result.
/// This prevents very long parsing and memory usage.
async fn stream_to_bytes_limit(res: Response, max_size: usize) -> Result<Bytes, reqwest::Error> {
let mut stream = res.bytes_stream().take(max_size);
let mut buf = BytesMut::new();
let mut size = 0;
while let Some(chunk) = stream.next().await {
let chunk = &chunk?;
size += chunk.len();
buf.extend(chunk);
if size >= max_size {
break;
}
}
Ok(buf.freeze())
}
/// This is an implementation of the default Cookie Jar from Reqwest and reqwest_cookie_store build by pfernie. /// This is an implementation of the default Cookie Jar from Reqwest and reqwest_cookie_store build by pfernie.
/// The default cookie jar used by Reqwest keeps all the cookies based upon the Max-Age or Expires which could be a long time. /// The default cookie jar used by Reqwest keeps all the cookies based upon the Max-Age or Expires which could be a long time.
/// That could be used for tracking, to prevent this we force the lifespan of the cookies to always be max two minutes. /// That could be used for tracking, to prevent this we force the lifespan of the cookies to always be max two minutes.
/// A Cookie Jar is needed because some sites force a redirect with cookies to verify if a request uses cookies or not. /// A Cookie Jar is needed because some sites force a redirect with cookies to verify if a request uses cookies or not.
use cookie_store::CookieStore; use cookie_store::CookieStore;
#[derive(Default)] #[derive(Default)]
pub struct Jar(RwLock<CookieStore>); pub struct Jar(std::sync::RwLock<CookieStore>);
impl reqwest::cookie::CookieStore for Jar { impl reqwest::cookie::CookieStore for Jar {
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &header::HeaderValue>, url: &url::Url) { fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &header::HeaderValue>, url: &url::Url) {
@ -836,11 +833,136 @@ impl reqwest::cookie::CookieStore for Jar {
} }
} }
async fn stream_to_bytes_limit(res: Response, max_size: usize) -> Result<Bytes, reqwest::Error> { /// Custom FaviconEmitter for the html5gum parser.
let mut stream = res.bytes_stream().take(max_size); /// The FaviconEmitter is using an almost 1:1 copy of the DefaultEmitter with some small changes.
let mut buf = BytesMut::new(); /// This prevents emitting tags like comments, doctype and also strings between the tags.
while let Some(chunk) = stream.next().await { /// Therefor parsing the HTML content is faster.
buf.extend(chunk?); use std::collections::{BTreeSet, VecDeque};
enum FaviconToken {
StartTag(StartTag),
EndTag(EndTag),
} }
Ok(buf.freeze())
#[derive(Default)]
struct FaviconEmitter {
current_token: Option<FaviconToken>,
last_start_tag: Vec<u8>,
current_attribute: Option<(Vec<u8>, Vec<u8>)>,
seen_attributes: BTreeSet<Vec<u8>>,
emitted_tokens: VecDeque<FaviconToken>,
}
impl FaviconEmitter {
fn emit_token(&mut self, token: FaviconToken) {
self.emitted_tokens.push_front(token);
}
fn flush_current_attribute(&mut self) {
if let Some((k, v)) = self.current_attribute.take() {
match self.current_token {
Some(FaviconToken::StartTag(ref mut tag)) => {
tag.attributes.entry(k).and_modify(|_| {}).or_insert(v);
}
Some(FaviconToken::EndTag(_)) => {
self.seen_attributes.insert(k);
}
_ => {
debug_assert!(false);
}
}
}
}
}
impl Emitter for FaviconEmitter {
type Token = FaviconToken;
fn set_last_start_tag(&mut self, last_start_tag: Option<&[u8]>) {
self.last_start_tag.clear();
self.last_start_tag.extend(last_start_tag.unwrap_or_default());
}
fn pop_token(&mut self) -> Option<Self::Token> {
self.emitted_tokens.pop_back()
}
fn init_start_tag(&mut self) {
self.current_token = Some(FaviconToken::StartTag(StartTag::default()));
}
fn init_end_tag(&mut self) {
self.current_token = Some(FaviconToken::EndTag(EndTag::default()));
self.seen_attributes.clear();
}
fn emit_current_tag(&mut self) {
self.flush_current_attribute();
let mut token = self.current_token.take().unwrap();
match token {
FaviconToken::EndTag(_) => {
self.seen_attributes.clear();
}
FaviconToken::StartTag(ref mut tag) => {
self.set_last_start_tag(Some(&tag.name));
}
}
self.emit_token(token);
}
fn push_tag_name(&mut self, s: &[u8]) {
match self.current_token {
Some(
FaviconToken::StartTag(StartTag {
ref mut name,
..
})
| FaviconToken::EndTag(EndTag {
ref mut name,
..
}),
) => {
name.extend(s);
}
_ => debug_assert!(false),
}
}
fn init_attribute(&mut self) {
self.flush_current_attribute();
self.current_attribute = Some((Vec::new(), Vec::new()));
}
fn push_attribute_name(&mut self, s: &[u8]) {
self.current_attribute.as_mut().unwrap().0.extend(s);
}
fn push_attribute_value(&mut self, s: &[u8]) {
self.current_attribute.as_mut().unwrap().1.extend(s);
}
fn current_is_appropriate_end_tag_token(&mut self) -> bool {
match self.current_token {
Some(FaviconToken::EndTag(ref tag)) => !self.last_start_tag.is_empty() && self.last_start_tag == tag.name,
_ => false,
}
}
// We do not want and need these parts of the HTML document
// These will be skipped and ignored during the tokenization and iteration.
fn emit_current_comment(&mut self) {}
fn emit_current_doctype(&mut self) {}
fn emit_eof(&mut self) {}
fn emit_error(&mut self, _: html5gum::Error) {}
fn emit_string(&mut self, _: &[u8]) {}
fn init_comment(&mut self) {}
fn init_doctype(&mut self) {}
fn push_comment(&mut self, _: &[u8]) {}
fn push_doctype_name(&mut self, _: &[u8]) {}
fn push_doctype_public_identifier(&mut self, _: &[u8]) {}
fn push_doctype_system_identifier(&mut self, _: &[u8]) {}
fn set_doctype_public_identifier(&mut self, _: &[u8]) {}
fn set_doctype_system_identifier(&mut self, _: &[u8]) {}
fn set_force_quirks(&mut self) {}
fn set_self_closing(&mut self) {}
} }

View file

@ -569,12 +569,14 @@ make_config! {
_enable_smtp: bool, true, def, true; _enable_smtp: bool, true, def, true;
/// Host /// Host
smtp_host: String, true, option; smtp_host: String, true, option;
/// Enable Secure SMTP |> (Explicit) - Enabling this by default would use STARTTLS (Standard ports 587 or 25) /// DEPRECATED smtp_ssl |> DEPRECATED - Please use SMTP_SECURITY
smtp_ssl: bool, true, def, true; smtp_ssl: bool, false, option;
/// Force TLS |> (Implicit) - Enabling this would force the use of an SSL/TLS connection, instead of upgrading an insecure one with STARTTLS (Standard port 465) /// DEPRECATED smtp_explicit_tls |> DEPRECATED - Please use SMTP_SECURITY
smtp_explicit_tls: bool, true, def, false; smtp_explicit_tls: bool, false, option;
/// Secure SMTP |> ("starttls", "force_tls", "off") Enable a secure connection. Default is "starttls" (Explicit - ports 587 or 25), "force_tls" (Implicit - port 465) or "off", no encryption
smtp_security: String, true, auto, |c| smtp_convert_deprecated_ssl_options(c.smtp_ssl, c.smtp_explicit_tls); // TODO: After deprecation make it `def, "starttls".to_string()`
/// Port /// Port
smtp_port: u16, true, auto, |c| if c.smtp_explicit_tls {465} else if c.smtp_ssl {587} else {25}; smtp_port: u16, true, auto, |c| if c.smtp_security == *"force_tls" {465} else if c.smtp_security == *"starttls" {587} else {25};
/// From Address /// From Address
smtp_from: String, true, def, String::new(); smtp_from: String, true, def, String::new();
/// From Name /// From Name
@ -657,6 +659,13 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
} }
if cfg._enable_smtp { if cfg._enable_smtp {
match cfg.smtp_security.as_str() {
"off" | "starttls" | "force_tls" => (),
_ => err!(
"`SMTP_SECURITY` is invalid. It needs to be one of the following options: starttls, force_tls or off"
),
}
if cfg.smtp_host.is_some() == cfg.smtp_from.is_empty() { if cfg.smtp_host.is_some() == cfg.smtp_from.is_empty() {
err!("Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support") err!("Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support")
} }
@ -735,6 +744,20 @@ fn extract_url_path(url: &str) -> String {
} }
} }
/// Convert the old SMTP_SSL and SMTP_EXPLICIT_TLS options
fn smtp_convert_deprecated_ssl_options(smtp_ssl: Option<bool>, smtp_explicit_tls: Option<bool>) -> String {
if smtp_explicit_tls.is_some() || smtp_ssl.is_some() {
println!("[DEPRECATED]: `SMTP_SSL` or `SMTP_EXPLICIT_TLS` is set. Please use `SMTP_SECURITY` instead.");
}
if smtp_explicit_tls.is_some() && smtp_explicit_tls.unwrap() {
return "force_tls".to_string();
} else if smtp_ssl.is_some() && !smtp_ssl.unwrap() {
return "off".to_string();
}
// Return the default `starttls` in all other cases
"starttls".to_string()
}
impl Config { impl Config {
pub fn load() -> Result<Self, Error> { pub fn load() -> Result<Self, Error> {
// Loading from env and file // Loading from env and file

View file

@ -30,7 +30,7 @@ fn mailer() -> SmtpTransport {
.timeout(Some(Duration::from_secs(CONFIG.smtp_timeout()))); .timeout(Some(Duration::from_secs(CONFIG.smtp_timeout())));
// Determine security // Determine security
let smtp_client = if CONFIG.smtp_ssl() || CONFIG.smtp_explicit_tls() { let smtp_client = if CONFIG.smtp_security() != *"off" {
let mut tls_parameters = TlsParameters::builder(host); let mut tls_parameters = TlsParameters::builder(host);
if CONFIG.smtp_accept_invalid_hostnames() { if CONFIG.smtp_accept_invalid_hostnames() {
tls_parameters = tls_parameters.dangerous_accept_invalid_hostnames(true); tls_parameters = tls_parameters.dangerous_accept_invalid_hostnames(true);
@ -40,7 +40,7 @@ fn mailer() -> SmtpTransport {
} }
let tls_parameters = tls_parameters.build().unwrap(); let tls_parameters = tls_parameters.build().unwrap();
if CONFIG.smtp_explicit_tls() { if CONFIG.smtp_security() == *"force_tls" {
smtp_client.tls(Tls::Wrapper(tls_parameters)) smtp_client.tls(Tls::Wrapper(tls_parameters))
} else { } else {
smtp_client.tls(Tls::Required(tls_parameters)) smtp_client.tls(Tls::Required(tls_parameters))

File diff suppressed because it is too large Load diff

View file

@ -4,22 +4,13 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.11.3 * https://datatables.net/download/#bs5/dt-1.11.4
* *
* Included libraries: * Included libraries:
* DataTables 1.11.3 * DataTables 1.11.4
*/ */
@charset "UTF-8"; @charset "UTF-8";
td.dt-control {
background: url("https://www.datatables.net/examples/resources/details_open.png") no-repeat center center;
cursor: pointer;
}
tr.dt-hasChild td.dt-control {
background: url("https://www.datatables.net/examples/resources/details_close.png") no-repeat center center;
}
table.dataTable th.dt-left, table.dataTable th.dt-left,
table.dataTable td.dt-left { table.dataTable td.dt-left {
text-align: left; text-align: left;
@ -91,6 +82,31 @@ table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap { table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap; white-space: nowrap;
} }
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #31b131;
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-";
background-color: #d33333;
}
/*! Bootstrap 5 integration for DataTables /*! Bootstrap 5 integration for DataTables
* *

View file

@ -4,20 +4,20 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.11.3 * https://datatables.net/download/#bs5/dt-1.11.4
* *
* Included libraries: * Included libraries:
* DataTables 1.11.3 * DataTables 1.11.4
*/ */
/*! DataTables 1.11.3 /*! DataTables 1.11.4
* ©2008-2021 SpryMedia Ltd - datatables.net/license * ©2008-2021 SpryMedia Ltd - datatables.net/license
*/ */
/** /**
* @summary DataTables * @summary DataTables
* @description Paginate, search and order HTML tables * @description Paginate, search and order HTML tables
* @version 1.11.3 * @version 1.11.4
* @file jquery.dataTables.js * @file jquery.dataTables.js
* @author SpryMedia Ltd * @author SpryMedia Ltd
* @contact www.datatables.net * @contact www.datatables.net
@ -3462,6 +3462,9 @@
*/ */
function _fnDraw( oSettings, ajaxComplete ) function _fnDraw( oSettings, ajaxComplete )
{ {
// Allow for state saving and a custom start position
_fnStart( oSettings );
/* Provide a pre-callback function which can be used to cancel the draw is false is returned */ /* Provide a pre-callback function which can be used to cancel the draw is false is returned */
var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
if ( $.inArray( false, aPreDraw ) !== -1 ) if ( $.inArray( false, aPreDraw ) !== -1 )
@ -3470,34 +3473,18 @@
return; return;
} }
var i, iLen, n;
var anRows = []; var anRows = [];
var iRowCount = 0; var iRowCount = 0;
var asStripeClasses = oSettings.asStripeClasses; var asStripeClasses = oSettings.asStripeClasses;
var iStripes = asStripeClasses.length; var iStripes = asStripeClasses.length;
var iOpenRows = oSettings.aoOpenRows.length;
var oLang = oSettings.oLanguage; var oLang = oSettings.oLanguage;
var iInitDisplayStart = oSettings.iInitDisplayStart;
var bServerSide = _fnDataSource( oSettings ) == 'ssp'; var bServerSide = _fnDataSource( oSettings ) == 'ssp';
var aiDisplay = oSettings.aiDisplay; var aiDisplay = oSettings.aiDisplay;
oSettings.bDrawing = true;
/* Check and see if we have an initial draw position from state saving */
if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
{
oSettings._iDisplayStart = bServerSide ?
iInitDisplayStart :
iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
0 :
iInitDisplayStart;
oSettings.iInitDisplayStart = -1;
}
var iDisplayStart = oSettings._iDisplayStart; var iDisplayStart = oSettings._iDisplayStart;
var iDisplayEnd = oSettings.fnDisplayEnd(); var iDisplayEnd = oSettings.fnDisplayEnd();
oSettings.bDrawing = true;
/* Server-side processing draw intercept */ /* Server-side processing draw intercept */
if ( oSettings.bDeferLoading ) if ( oSettings.bDeferLoading )
{ {
@ -3899,6 +3886,28 @@
return aReturn; return aReturn;
} }
/**
* Set the start position for draw
* @param {object} oSettings dataTables settings object
*/
function _fnStart( oSettings )
{
var bServerSide = _fnDataSource( oSettings ) == 'ssp';
var iInitDisplayStart = oSettings.iInitDisplayStart;
// Check and see if we have an initial draw position from state saving
if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
{
oSettings._iDisplayStart = bServerSide ?
iInitDisplayStart :
iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
0 :
iInitDisplayStart;
oSettings.iInitDisplayStart = -1;
}
}
/** /**
* Create an Ajax call based on the table's settings, taking into account that * Create an Ajax call based on the table's settings, taking into account that
* parameters can have multiple forms, and backwards compatibility. * parameters can have multiple forms, and backwards compatibility.
@ -3942,8 +3951,8 @@
var ajax = oSettings.ajax; var ajax = oSettings.ajax;
var instance = oSettings.oInstance; var instance = oSettings.oInstance;
var callback = function ( json ) { var callback = function ( json ) {
var status = oSettings.jqXhr var status = oSettings.jqXHR
? oSettings.jqXhr.status ? oSettings.jqXHR.status
: null; : null;
if ( json === null || (typeof status === 'number' && status == 204 ) ) { if ( json === null || (typeof status === 'number' && status == 204 ) ) {
@ -5487,7 +5496,7 @@
// Sanity check that the table is of a sensible width. If not then we are going to get // Sanity check that the table is of a sensible width. If not then we are going to get
// misalignment - try to prevent this by not allowing the table to shrink below its min width // misalignment - try to prevent this by not allowing the table to shrink below its min width
if ( table.outerWidth() < sanityWidth ) if ( Math.round(table.outerWidth()) < Math.round(sanityWidth) )
{ {
// The min width depends upon if we have a vertical scrollbar visible or not */ // The min width depends upon if we have a vertical scrollbar visible or not */
correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight || correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
@ -6496,10 +6505,14 @@
// Restore key features - todo - for 1.11 this needs to be done by // Restore key features - todo - for 1.11 this needs to be done by
// subscribed events // subscribed events
if ( s.start !== undefined ) { if ( s.start !== undefined ) {
settings._iDisplayStart = s.start;
if(api === null) { if(api === null) {
settings._iDisplayStart = s.start;
settings.iInitDisplayStart = s.start; settings.iInitDisplayStart = s.start;
} }
else {
_fnPageChange(settings, s.start/s.length);
}
} }
if ( s.length !== undefined ) { if ( s.length !== undefined ) {
settings._iDisplayLength = s.length; settings._iDisplayLength = s.length;
@ -9644,7 +9657,7 @@
* @type string * @type string
* @default Version number * @default Version number
*/ */
DataTable.version = "1.11.3"; DataTable.version = "1.11.4";
/** /**
* Private data store, containing all of the settings objects that are * Private data store, containing all of the settings objects that are
@ -14069,7 +14082,7 @@
* *
* @type string * @type string
*/ */
build:"bs5/dt-1.11.3", build:"bs5/dt-1.11.4",
/** /**

View file

@ -616,7 +616,13 @@ where
use reqwest::{header, Client, ClientBuilder}; use reqwest::{header, Client, ClientBuilder};
pub fn get_reqwest_client() -> Client { pub fn get_reqwest_client() -> Client {
get_reqwest_client_builder().build().expect("Failed to build client") match get_reqwest_client_builder().build() {
Ok(client) => client,
Err(e) => {
error!("Possible trust-dns error, trying with trust-dns disabled: '{e}'");
get_reqwest_client_builder().trust_dns(false).build().expect("Failed to build client")
}
}
} }
pub fn get_reqwest_client_builder() -> ClientBuilder { pub fn get_reqwest_client_builder() -> ClientBuilder {