mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-11-25 05:10:20 +00:00
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:
parent
5f01db69ff
commit
42136a7097
16 changed files with 4390 additions and 2180 deletions
|
@ -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
|
||||||
|
|
|
@ -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
356
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>()
|
||||||
|
|
478
src/api/icons.rs
478
src/api/icons.rs
|
@ -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) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
5369
src/static/scripts/bootstrap-native.js
vendored
5369
src/static/scripts/bootstrap-native.js
vendored
File diff suppressed because it is too large
Load diff
38
src/static/scripts/datatables.css
vendored
38
src/static/scripts/datatables.css
vendored
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
69
src/static/scripts/datatables.js
vendored
69
src/static/scripts/datatables.js
vendored
|
@ -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",
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue