diff --git a/.dockerignore b/.dockerignore index 69f51d2a..c7ffe132 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,13 +3,18 @@ target # Data folder data + +# Misc .env .env.template .gitattributes +.gitignore +rustfmt.toml # IDE files .vscode .idea +.editorconfig *.iml # Documentation @@ -19,9 +24,17 @@ data *.yml *.yaml -# Docker folders +# Docker hooks tools +Dockerfile +.dockerignore +docker/** +!docker/healthcheck.sh +!docker/start.sh # Web vault -web-vault \ No newline at end of file +web-vault + +# Vaultwarden Resources +resources diff --git a/.env.template b/.env.template index 5bc3d047..3c8a5ebb 100644 --- a/.env.template +++ b/.env.template @@ -336,9 +336,8 @@ # SMTP_HOST=smtp.domain.tld # SMTP_FROM=vaultwarden@domain.tld # 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_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_PASSWORD=password # SMTP_TIMEOUT=15 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f92e6e54..465cf2a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,14 +30,14 @@ jobs: fail-fast: false matrix: channel: - - nightly + - stable target-triple: - x86_64-unknown-linux-gnu include: - target-triple: x86_64-unknown-linux-gnu host-triple: x86_64-unknown-linux-gnu features: [sqlite,mysql,postgresql] # Remember to update the `cargo test` to match the amount of features - channel: nightly + channel: stable os: ubuntu-20.04 ext: "" @@ -46,7 +46,7 @@ jobs: steps: # Checkout the repo - name: Checkout - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 # End Checkout the repo @@ -82,28 +82,28 @@ jobs: # Run cargo tests (In release mode to speed up future builds) # First test all features together, afterwards test them separately. - name: "`cargo test --release --features ${{ join(matrix.features, ',') }} --target ${{ matrix.target-triple }}`" - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: test args: --release --features ${{ join(matrix.features, ',') }} --target ${{ matrix.target-triple }} # Test single features # 0: sqlite - name: "`cargo test --release --features ${{ matrix.features[0] }} --target ${{ matrix.target-triple }}`" - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: test args: --release --features ${{ matrix.features[0] }} --target ${{ matrix.target-triple }} if: ${{ matrix.features[0] != '' }} # 1: mysql - name: "`cargo test --release --features ${{ matrix.features[1] }} --target ${{ matrix.target-triple }}`" - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: test args: --release --features ${{ matrix.features[1] }} --target ${{ matrix.target-triple }} if: ${{ matrix.features[1] != '' }} # 2: postgresql - name: "`cargo test --release --features ${{ matrix.features[2] }} --target ${{ matrix.target-triple }}`" - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: test args: --release --features ${{ matrix.features[2] }} --target ${{ matrix.target-triple }} @@ -113,7 +113,7 @@ jobs: # Run cargo clippy, and fail on warnings (In release mode to speed up future builds) - name: "`cargo clippy --release --features ${{ join(matrix.features, ',') }} --target ${{ matrix.target-triple }}`" - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: clippy args: --release --features ${{ join(matrix.features, ',') }} --target ${{ matrix.target-triple }} -- -D warnings @@ -122,7 +122,7 @@ jobs: # Run cargo fmt - name: '`cargo fmt`' - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: fmt args: --all -- --check @@ -131,7 +131,7 @@ jobs: # Build the binary - name: "`cargo build --release --features ${{ join(matrix.features, ',') }} --target ${{ matrix.target-triple }}`" - uses: actions-rs/cargo@ae10961054e4aa8b4aa7dffede299aaf087aa33b # v1.0.1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: command: build args: --release --features ${{ join(matrix.features, ',') }} --target ${{ matrix.target-triple }} @@ -140,7 +140,7 @@ jobs: # Upload artifact to Github Actions - name: Upload artifact - uses: actions/upload-artifact@27121b0bdffd731efa15d66772be8dc71245d074 # v2.2.4 + uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1 with: name: vaultwarden-${{ matrix.target-triple }}${{ matrix.ext }} path: target/${{ matrix.target-triple }}/release/vaultwarden${{ matrix.ext }} diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 375e437a..4b95d653 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -16,7 +16,7 @@ jobs: steps: # Checkout the repo - name: Checkout - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 # End Checkout the repo @@ -27,7 +27,7 @@ jobs: sudo curl -L https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint && \ sudo chmod +x /usr/local/bin/hadolint env: - HADOLINT_VERSION: 2.7.0 + HADOLINT_VERSION: 2.8.0 # End Download hadolint # Test Dockerfiles diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b425c01..00711726 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,13 +60,13 @@ jobs: steps: # Checkout the repo - name: Checkout - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 with: fetch-depth: 0 # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 # v1.10.0 + uses: docker/login-action@42d299face0c5c43a0487c477f595ac9cf22f1a7 # v1.12.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b26d8445..f18ddbf1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-yaml - id: check-json diff --git a/Cargo.lock b/Cargo.lock index 63504f0a..9de3455d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array 0.14.5", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -64,6 +99,25 @@ dependencies = [ "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]] name = "async-stream" version = "0.3.2" @@ -118,9 +172,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" @@ -194,6 +248,15 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.5", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -259,10 +322,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] -name = "cc" -version = "1.0.72" +name = "cached" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +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]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -296,7 +393,7 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time 0.1.44", + "time 0.1.43", "winapi 0.3.9", ] @@ -308,7 +405,7 @@ checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" dependencies = [ "chrono", "chrono-tz-build", - "phf 0.10.1", + "phf", ] [[package]] @@ -318,8 +415,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" dependencies = [ "parse-zoneinfo", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf", + "phf_codegen", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.5", ] [[package]] @@ -345,7 +451,14 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" dependencies = [ + "aes-gcm", + "base64 0.13.0", + "hkdf", + "hmac 0.12.1", "percent-encoding 2.1.0", + "rand 0.8.5", + "sha2 0.10.2", + "subtle", "time 0.3.7", "version_check", ] @@ -368,9 +481,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -393,9 +506,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] @@ -413,14 +526,24 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if 1.0.0", "lazy_static", ] +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.5", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.10.1" @@ -441,6 +564,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "ctrlc" version = "3.2.1" @@ -452,13 +584,49 @@ dependencies = [ ] [[package]] -name = "dashmap" -version = "4.0.2" +name = "darling" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +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]] +name = "dashmap" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0834a35a3fce649144119e18da2a4d8ed12ef3862f47183fd46f625d072d96c" dependencies = [ "cfg-if 1.0.0", "num_cpus", + "parking_lot 0.12.0", ] [[package]] @@ -565,6 +733,17 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + [[package]] name = "discard" version = "1.0.4" @@ -594,9 +773,9 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" dependencies = [ "heck", "proc-macro2", @@ -613,6 +792,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + [[package]] name = "fake-simd" version = "0.1.2" @@ -717,21 +902,11 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -[[package]] -name = "futf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -744,9 +919,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -754,15 +929,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -771,15 +946,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -788,15 +963,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-timer" @@ -806,9 +981,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", @@ -867,13 +1042,23 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug 0.3.0", + "polyval", ] [[package]] @@ -890,18 +1075,18 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "governor" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df0ee4b237afb71e99f7e2fbd840ffec2d6c4bb569f69b2af18aa1f63077d38" +checksum = "19775995ee20209163239355bc3ad2f33f83da35d9ef72dea26e5af753552c87" dependencies = [ "dashmap", "futures", "futures-timer", "no-std-compat", "nonzero_ext", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "quanta", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.8.0", ] @@ -920,7 +1105,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.6.9", "tracing", ] @@ -953,12 +1138,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -969,6 +1151,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.10.1" @@ -989,6 +1180,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "hostname" version = "0.3.1" @@ -1001,17 +1201,12 @@ dependencies = [ ] [[package]] -name = "html5ever" -version = "0.25.1" +name = "html5gum" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +checksum = "2dad48b66db55322add2819ae1d7bda0c32f3415269a08330679dbc8b0afeb30" dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn", + "jetscii", ] [[package]] @@ -1022,7 +1217,7 @@ checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes 1.1.0", "fnv", - "itoa 1.0.1", + "itoa", ] [[package]] @@ -1038,9 +1233,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" [[package]] name = "httpdate" @@ -1050,9 +1245,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" dependencies = [ "bytes 1.1.0", "futures-channel", @@ -1063,7 +1258,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", + "itoa", "pin-project-lite", "socket2 0.4.4", "tokio", @@ -1085,6 +1280,12 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.1.5" @@ -1160,18 +1361,18 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "jetscii" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9447923c57a8a2d5c1b0875cdf96a6324275df728b498f2ede0e5cbde088a15" + [[package]] name = "job_scheduler" version = "1.2.1" @@ -1250,9 +1451,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.116" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libsqlite3-sys" @@ -1313,12 +1514,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "mach" version = "0.3.2" @@ -1334,32 +1529,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "match_cfg" version = "0.1.0" @@ -1466,9 +1635,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log", @@ -1526,7 +1695,7 @@ dependencies = [ "mime", "spin 0.9.2", "tokio", - "tokio-util", + "tokio-util 0.6.9", "version_check", ] @@ -1569,12 +1738,6 @@ dependencies = [ "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]] name = "nix" version = "0.23.1" @@ -1622,9 +1785,9 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi 0.3.9", ] @@ -1807,6 +1970,16 @@ dependencies = [ "parking_lot_core 0.8.5", ] +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.1", +] + [[package]] name = "parking_lot_core" version = "0.2.14" @@ -1833,6 +2006,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec 1.8.0", + "windows-sys", +] + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -1937,32 +2123,13 @@ dependencies = [ "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]] name = "phf" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "phf_shared 0.10.0", -] - -[[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", + "phf_shared", ] [[package]] @@ -1971,18 +2138,8 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[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", + "phf_generator", + "phf_shared", ] [[package]] @@ -1991,17 +2148,8 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.4", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", + "phf_shared", + "rand 0.8.5", ] [[package]] @@ -2038,6 +2186,18 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -2053,12 +2213,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2116,7 +2270,7 @@ dependencies = [ "mach", "once_cell", "raw-cpuid", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.10.2+wasi-snapshot-preview1", "web-sys", "winapi 0.3.9", ] @@ -2182,20 +2336,18 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", - "rand_pcg", + "rand_hc", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", - "rand_hc 0.3.1", ] [[package]] @@ -2248,7 +2400,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", ] [[package]] @@ -2260,24 +2412,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "raw-cpuid" version = "10.2.0" @@ -2394,7 +2528,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-socks", - "tokio-util", + "tokio-util 0.6.9", "trust-dns-resolver", "url 2.2.2", "wasm-bindgen", @@ -2451,7 +2585,7 @@ dependencies = [ [[package]] name = "rocket" version = "0.5.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?rev=8cae077ba1d54b92cdef3e171a730b819d5eeb8e#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?rev=91e3b4397a1637d0f55f23db712cf7bda0c7f891#91e3b4397a1637d0f55f23db712cf7bda0c7f891" dependencies = [ "async-stream", "async-trait", @@ -2467,9 +2601,9 @@ dependencies = [ "memchr", "multer", "num_cpus", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "pin-project-lite", - "rand 0.8.4", + "rand 0.8.5", "ref-cast", "rocket_codegen", "rocket_http", @@ -2480,7 +2614,7 @@ dependencies = [ "time 0.3.7", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.7.0", "ubyte", "version_check", "yansi", @@ -2489,7 +2623,7 @@ dependencies = [ [[package]] name = "rocket_codegen" version = "0.5.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?rev=8cae077ba1d54b92cdef3e171a730b819d5eeb8e#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?rev=91e3b4397a1637d0f55f23db712cf7bda0c7f891#91e3b4397a1637d0f55f23db712cf7bda0c7f891" dependencies = [ "devise", "glob", @@ -2504,7 +2638,7 @@ dependencies = [ [[package]] name = "rocket_http" version = "0.5.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?rev=8cae077ba1d54b92cdef3e171a730b819d5eeb8e#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?rev=91e3b4397a1637d0f55f23db712cf7bda0c7f891#91e3b4397a1637d0f55f23db712cf7bda0c7f891" dependencies = [ "cookie 0.16.0", "either", @@ -2518,6 +2652,7 @@ dependencies = [ "pin-project-lite", "ref-cast", "rustls", + "rustls-pemfile", "serde", "smallvec 1.8.0", "stable-pattern", @@ -2545,17 +2680,25 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ - "base64 0.13.0", "log", "ring", "sct", "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]] name = "rustversion" version = "1.0.6" @@ -2610,9 +2753,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -2620,9 +2763,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -2633,9 +2776,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -2688,11 +2831,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] @@ -2704,7 +2847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa", "ryu", "serde", ] @@ -2762,6 +2905,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2934,30 +3088,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] -name = "string_cache" -version = "0.8.2" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "parking_lot 0.11.2", - "phf_shared 0.8.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", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" @@ -2985,7 +3119,7 @@ dependencies = [ "error-chain", "libc", "log", - "time 0.1.44", + "time 0.1.43", ] [[package]] @@ -3002,17 +3136,6 @@ dependencies = [ "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]] name = "thiserror" version = "1.0.30" @@ -3053,12 +3176,11 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] @@ -3083,7 +3205,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ - "itoa 1.0.1", + "itoa", "libc", "num_threads", "time-macros 0.2.3", @@ -3135,19 +3257,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.16.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.7.14", + "mio 0.8.0", "num_cpus", "once_cell", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "pin-project-lite", "signal-hook-registry", + "socket2 0.4.4", "tokio-macros", "winapi 0.3.9", ] @@ -3175,9 +3298,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ "rustls", "tokio", @@ -3221,6 +3344,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.8" @@ -3239,7 +3376,7 @@ dependencies = [ "digest 0.9.0", "hmac 0.11.0", "sha-1 0.9.8", - "sha2", + "sha2 0.9.9", ] [[package]] @@ -3250,9 +3387,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if 1.0.0", "log", @@ -3263,9 +3400,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" dependencies = [ "proc-macro2", "quote", @@ -3274,11 +3411,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -3294,9 +3432,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" dependencies = [ "ansi_term", "lazy_static", @@ -3312,9 +3450,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4" +checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -3327,7 +3465,7 @@ dependencies = [ "ipnet", "lazy_static", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.8.0", "thiserror", "tinyvec", @@ -3337,9 +3475,9 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770" +checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" dependencies = [ "cfg-if 1.0.0", "futures-util", @@ -3381,7 +3519,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.1.44", + "time 0.1.43", ] [[package]] @@ -3424,18 +3562,22 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array 0.14.5", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -3466,27 +3608,28 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vaultwarden" version = "1.0.0" dependencies = [ "backtrace", "bytes 1.1.0", + "cached", "chashmap", "chrono", "chrono-tz", @@ -3502,14 +3645,13 @@ dependencies = [ "futures", "governor", "handlebars", - "html5ever", + "html5gum", "idna 0.2.3", "job_scheduler", "jsonwebtoken", "lettre", "libsqlite3-sys", "log", - "markup5ever_rcdom", "num-derive", "num-traits", "once_cell", @@ -3518,7 +3660,7 @@ dependencies = [ "paste", "percent-encoding 2.1.0", "pico-args", - "rand 0.8.4", + "rand 0.8.5", "regex", "reqwest", "ring", @@ -3579,9 +3721,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" @@ -3668,7 +3810,7 @@ dependencies = [ "base64 0.13.0", "nom 7.1.0", "openssl", - "rand 0.8.4", + "rand 0.8.5", "serde", "serde_cbor", "serde_derive", @@ -3680,9 +3822,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -3737,6 +3879,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "winreg" version = "0.6.2" @@ -3765,18 +3950,6 @@ dependencies = [ "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.44", -] - [[package]] name = "yansi" version = "0.5.0" @@ -3793,7 +3966,7 @@ dependencies = [ "crypto-mac 0.10.1", "futures", "hmac 0.10.1", - "rand 0.8.4", + "rand 0.8.5", "reqwest", "sha-1 0.9.8", "threadpool", diff --git a/Cargo.toml b/Cargo.toml index 3cdd3d2b..1f76c0ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "vaultwarden" version = "1.0.0" authors = ["Daniel García "] edition = "2021" -rust-version = "1.56" +rust-version = "1.59" resolver = "2" repository = "https://github.com/dani-garcia/vaultwarden" @@ -27,79 +27,15 @@ vendored_openssl = ["openssl/vendored"] unstable = [] [target."cfg(not(windows))".dependencies] -syslog = "4.0.1" +# Logging +syslog = "4.0.1" # Needs to be v4 until fern is updated [dependencies] -# Web framework -rocket = { version = "0.5.0-rc.1", features = ["tls", "json"], default-features = false } - -# Async futures -futures = "0.3.19" -tokio = { version = "1.16.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot"] } - - # HTTP client -reqwest = { version = "0.11.9", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } -bytes = "1.1.0" - -# Used for custom short lived cookie jar -cookie = "0.15.1" -cookie_store = "0.15.1" -url = "2.2.2" - -# WebSockets library -ws = { version = "0.11.1", package = "parity-ws" } - -# MessagePack library -rmpv = "1.0.0" - -# Concurrent hashmap implementation -chashmap = "2.2.2" - -# A generic serialization/deserialization framework -serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.78" - # Logging log = "0.4.14" fern = { version = "0.6.0", features = ["syslog-4"] } - -# A safe, extensible ORM and Query builder -diesel = { version = "1.4.8", features = [ "chrono", "r2d2"] } -diesel_migrations = "1.4.0" - -# Bundled SQLite -libsqlite3-sys = { version = "0.22.2", features = ["bundled"], optional = true } - -# Crypto-related libraries -rand = "0.8.4" -ring = "0.16.20" - -# UUID generation -uuid = { version = "0.8.2", features = ["v4"] } - -# Date and time libraries -chrono = { version = "0.4.19", features = ["serde"] } -chrono-tz = "0.6.1" -time = "0.2.27" - -# Job scheduler -job_scheduler = "1.2.1" - -# TOTP library -totp-lite = "1.0.3" - -# Data encoding library -data-encoding = "2.3.2" - -# JWT library -jsonwebtoken = "7.2.0" - -# U2F library -u2f = "0.2.0" -webauthn-rs = "0.3.2" - -# Yubico Library -yubico = { version = "0.10.0", features = ["online-tokio"], default-features = false } +tracing = { version = "0.1.31", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work +backtrace = "0.3.64" # Logging panics to logfile instead stderr only # A `dotenv` implementation for Rust dotenv = { version = "0.15.0", default-features = false } @@ -111,41 +47,100 @@ once_cell = "1.9.0" num-traits = "0.2.14" num-derive = "0.3.3" +# Web framework +rocket = { version = "0.5.0-rc.1", features = ["tls", "json"], default-features = false } + +# WebSockets libraries +ws = { version = "0.11.1", package = "parity-ws" } +rmpv = "1.0.0" # MessagePack library +chashmap = "2.2.2" # Concurrent hashmap implementation + +# Async futures +futures = "0.3.21" +tokio = { version = "1.17.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot"] } + +# A generic serialization/deserialization framework +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.79" + +# A safe, extensible ORM and Query builder +diesel = { version = "1.4.8", features = [ "chrono", "r2d2"] } +diesel_migrations = "1.4.0" + +# Bundled SQLite +libsqlite3-sys = { version = "0.22.2", features = ["bundled"], optional = true } + +# Crypto-related libraries +rand = "0.8.5" +ring = "0.16.20" + +# UUID generation +uuid = { version = "0.8.2", features = ["v4"] } + +# Date and time libraries +chrono = { version = "0.4.19", features = ["clock", "serde"], default-features = false } +chrono-tz = "0.6.1" +time = "0.2.27" + +# Job scheduler +job_scheduler = "1.2.1" + +# Data encoding library Hex/Base32/Base64 +data-encoding = "2.3.2" + +# JWT library +jsonwebtoken = "7.2.0" + +# TOTP library +totp-lite = "1.0.3" + +# Yubico Library +yubico = { version = "0.10.0", features = ["online-tokio"], default-features = false } + +# U2F libraries +u2f = "0.2.0" +webauthn-rs = "0.3.2" + +# Handling of URL's for WebAuthn +url = "2.2.2" + # Email libraries -tracing = { version = "0.1.29", features = ["log"] } # Needed to have lettre trace logging used when SMTP_DEBUG is enabled. lettre = { version = "0.10.0-rc.4", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false } +idna = "0.2.3" # Punycode conversion +percent-encoding = "2.1.0" # URL encoding library used for URL's in the emails # Template library handlebars = { version = "4.2.1", features = ["dir_source"] } +# HTTP client +reqwest = { version = "0.11.9", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } + # For favicon extraction from main website -html5ever = "0.25.1" -markup5ever_rcdom = "0.1.0" +html5gum = "0.4.0" regex = { version = "1.5.4", features = ["std", "perf", "unicode-perl"], default-features = false } data-url = "0.1.1" +bytes = "1.1.0" +cached = "0.30.0" + +# Used for custom short lived cookie jar during favicon extraction +cookie = "0.15.1" +cookie_store = "0.15.1" # Used by U2F, JWT and Postgres openssl = "0.10.38" -# URL encoding library -percent-encoding = "2.1.0" -# Punycode conversion -idna = "0.2.3" - # CLI argument parsing pico-args = "0.4.2" -# Logging panics to logfile instead stderr only -backtrace = "0.3.64" - # Macro ident concatenation paste = "1.0.6" -governor = "0.4.1" +governor = "0.4.2" +# Capture CTRL+C ctrlc = { version = "3.2.1", features = ["termination"] } [patch.crates-io] -rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '8cae077ba1d54b92cdef3e171a730b819d5eeb8e' } +rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '91e3b4397a1637d0f55f23db712cf7bda0c7f891' } # 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 @@ -153,3 +148,9 @@ rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '8cae077ba1d54 # In particular, `cron` has since implemented parsing of some common syntax # that wasn't previously supported (https://github.com/zslayton/cron/pull/64). 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" diff --git a/docker/Dockerfile.buildx b/docker/Dockerfile.buildx index ed0d23b3..c250312c 100644 --- a/docker/Dockerfile.buildx +++ b/docker/Dockerfile.buildx @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 # The cross-built images have the build arch (`amd64`) embedded in the image # manifest, rather than the target arch. For example: # diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2 index 2cffc647..a5194254 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -6,19 +6,19 @@ {% set build_stage_base_image = "rust:1.58-buster" %} {% if "alpine" in target_file %} {% if "amd64" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-nightly-2022-01-23" %} +{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-stable" %} {% set runtime_stage_base_image = "alpine:3.15" %} {% set package_arch_target = "x86_64-unknown-linux-musl" %} {% elif "armv7" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-nightly-2022-01-23" %} +{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-stable" %} {% set runtime_stage_base_image = "balenalib/armv7hf-alpine:3.15" %} {% set package_arch_target = "armv7-unknown-linux-musleabihf" %} {% elif "armv6" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-nightly-2022-01-23" %} +{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-stable" %} {% set runtime_stage_base_image = "balenalib/rpi-alpine:3.15" %} {% set package_arch_target = "arm-unknown-linux-musleabi" %} {% elif "arm64" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-nightly-2022-01-23" %} +{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-stable" %} {% set runtime_stage_base_image = "balenalib/aarch64-alpine:3.15" %} {% set package_arch_target = "aarch64-unknown-linux-musl" %} {% endif %} @@ -182,21 +182,15 @@ RUN touch src/main.rs # your actual source files being built # hadolint ignore=DL3059 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 ######################## # Create a new stage with a minimal image # because we already have a binary built FROM {{ runtime_stage_base_image }} -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 {%- if "alpine" in runtime_stage_base_image %} \ SSL_CERT_DIR=/etc/ssl/certs {% endif %} diff --git a/docker/amd64/Dockerfile b/docker/amd64/Dockerfile index 3af0f411..c588c8e9 100644 --- a/docker/amd64/Dockerfile +++ b/docker/amd64/Dockerfile @@ -89,9 +89,9 @@ RUN cargo build --features ${DB} --release # because we already have a binary built FROM debian:buster-slim -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # Create data folder and Install needed libraries diff --git a/docker/amd64/Dockerfile.alpine b/docker/amd64/Dockerfile.alpine index 189f50e6..9266da29 100644 --- a/docker/amd64/Dockerfile.alpine +++ b/docker/amd64/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:x86_64-musl-nightly-2022-01-23 as build +FROM blackdex/rust-musl:x86_64-musl-stable as build @@ -83,9 +83,9 @@ RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl # because we already have a binary built FROM alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/amd64/Dockerfile.buildx b/docker/amd64/Dockerfile.buildx index 05b6b71d..aa61d037 100644 --- a/docker/amd64/Dockerfile.buildx +++ b/docker/amd64/Dockerfile.buildx @@ -89,9 +89,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM debian:buster-slim -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # Create data folder and Install needed libraries diff --git a/docker/amd64/Dockerfile.buildx.alpine b/docker/amd64/Dockerfile.buildx.alpine index 066b8fe1..e0afd4fd 100644 --- a/docker/amd64/Dockerfile.buildx.alpine +++ b/docker/amd64/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:x86_64-musl-nightly-2022-01-23 as build +FROM blackdex/rust-musl:x86_64-musl-stable as build @@ -83,9 +83,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/arm64/Dockerfile b/docker/arm64/Dockerfile index d3a32dc4..40bfccc5 100644 --- a/docker/arm64/Dockerfile +++ b/docker/arm64/Dockerfile @@ -109,9 +109,9 @@ RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu # because we already have a binary built FROM balenalib/aarch64-debian:buster -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # hadolint ignore=DL3059 RUN [ "cross-build-start" ] diff --git a/docker/arm64/Dockerfile.alpine b/docker/arm64/Dockerfile.alpine index 6890d7bf..b233ac1b 100644 --- a/docker/arm64/Dockerfile.alpine +++ b/docker/arm64/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:aarch64-musl-nightly-2022-01-23 as build +FROM blackdex/rust-musl:aarch64-musl-stable as build @@ -83,9 +83,9 @@ RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl # because we already have a binary built FROM balenalib/aarch64-alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/arm64/Dockerfile.buildx b/docker/arm64/Dockerfile.buildx index b93cd90e..27b97333 100644 --- a/docker/arm64/Dockerfile.buildx +++ b/docker/arm64/Dockerfile.buildx @@ -109,9 +109,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM balenalib/aarch64-debian:buster -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # hadolint ignore=DL3059 RUN [ "cross-build-start" ] diff --git a/docker/arm64/Dockerfile.buildx.alpine b/docker/arm64/Dockerfile.buildx.alpine index dd4107c6..521fbd8f 100644 --- a/docker/arm64/Dockerfile.buildx.alpine +++ b/docker/arm64/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:aarch64-musl-nightly-2022-01-23 as build +FROM blackdex/rust-musl:aarch64-musl-stable as build @@ -83,9 +83,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM balenalib/aarch64-alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/armv6/Dockerfile b/docker/armv6/Dockerfile index e9e6d4bb..8cf59c4e 100644 --- a/docker/armv6/Dockerfile +++ b/docker/armv6/Dockerfile @@ -109,9 +109,9 @@ RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi # because we already have a binary built FROM balenalib/rpi-debian:buster -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # hadolint ignore=DL3059 RUN [ "cross-build-start" ] diff --git a/docker/armv6/Dockerfile.alpine b/docker/armv6/Dockerfile.alpine index 19f7f936..bdfdc612 100644 --- a/docker/armv6/Dockerfile.alpine +++ b/docker/armv6/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:arm-musleabi-nightly-2022-01-23 as build +FROM blackdex/rust-musl:arm-musleabi-stable as build @@ -83,9 +83,9 @@ RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi # because we already have a binary built FROM balenalib/rpi-alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/armv6/Dockerfile.buildx b/docker/armv6/Dockerfile.buildx index 7d6131bf..6c6eb562 100644 --- a/docker/armv6/Dockerfile.buildx +++ b/docker/armv6/Dockerfile.buildx @@ -109,9 +109,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM balenalib/rpi-debian:buster -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # hadolint ignore=DL3059 RUN [ "cross-build-start" ] diff --git a/docker/armv6/Dockerfile.buildx.alpine b/docker/armv6/Dockerfile.buildx.alpine index 5e9d68f9..369dfb4b 100644 --- a/docker/armv6/Dockerfile.buildx.alpine +++ b/docker/armv6/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:arm-musleabi-nightly-2022-01-23 as build +FROM blackdex/rust-musl:arm-musleabi-stable as build @@ -83,9 +83,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM balenalib/rpi-alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/armv7/Dockerfile b/docker/armv7/Dockerfile index 3ac3f106..5b26b5e1 100644 --- a/docker/armv7/Dockerfile +++ b/docker/armv7/Dockerfile @@ -109,9 +109,9 @@ RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabih # because we already have a binary built FROM balenalib/armv7hf-debian:buster -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # hadolint ignore=DL3059 RUN [ "cross-build-start" ] diff --git a/docker/armv7/Dockerfile.alpine b/docker/armv7/Dockerfile.alpine index 1ed36519..e05965bc 100644 --- a/docker/armv7/Dockerfile.alpine +++ b/docker/armv7/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:armv7-musleabihf-nightly-2022-01-23 as build +FROM blackdex/rust-musl:armv7-musleabihf-stable as build @@ -78,17 +78,15 @@ RUN touch src/main.rs # your actual source files being built # hadolint ignore=DL3059 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 ######################## # Create a new stage with a minimal image # because we already have a binary built FROM balenalib/armv7hf-alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/docker/armv7/Dockerfile.buildx b/docker/armv7/Dockerfile.buildx index 8df0f309..8c36f605 100644 --- a/docker/armv7/Dockerfile.buildx +++ b/docker/armv7/Dockerfile.buildx @@ -109,9 +109,9 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. # because we already have a binary built FROM balenalib/armv7hf-debian:buster -ENV ROCKET_ENV="staging" \ - ROCKET_PORT=80 \ - ROCKET_WORKERS=10 +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ + ROCKET_PORT=80 # hadolint ignore=DL3059 RUN [ "cross-build-start" ] diff --git a/docker/armv7/Dockerfile.buildx.alpine b/docker/armv7/Dockerfile.buildx.alpine index 56d8e7ff..431e0ff9 100644 --- a/docker/armv7/Dockerfile.buildx.alpine +++ b/docker/armv7/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:9b82318d553d72f091e8755f5aff80eed495f90bbe5b0703522953480f5c2fba as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:armv7-musleabihf-nightly-2022-01-23 as build +FROM blackdex/rust-musl:armv7-musleabihf-stable as build @@ -78,17 +78,15 @@ RUN touch src/main.rs # your actual source files being built # 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 -# hadolint ignore=DL3059 -RUN musl-strip target/armv7-unknown-linux-musleabihf/release/vaultwarden ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built FROM balenalib/armv7hf-alpine:3.15 -ENV ROCKET_ENV="staging" \ +ENV ROCKET_PROFILE="release" \ + ROCKET_ADDRESS=0.0.0.0 \ ROCKET_PORT=80 \ - ROCKET_WORKERS=10 \ SSL_CERT_DIR=/etc/ssl/certs diff --git a/src/api/admin.rs b/src/api/admin.rs index 015ec7c7..6fbf30e9 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -301,7 +301,7 @@ fn test_smtp(data: Json, _token: AdminToken) -> EmptyResult { #[get("/logout")] 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)) } @@ -638,7 +638,7 @@ impl<'r> FromRequest<'r> for AdminToken { if decode_admin(access_token).is_err() { // 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); return Outcome::Forward(()); } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index bb6c6634..13012e96 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1182,9 +1182,7 @@ async fn post_org_import( let ciphers = stream::iter(data.Ciphers) .then(|cipher_data| async { 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) - .await - .ok(); + update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await.ok(); cipher }) .collect::>() diff --git a/src/api/icons.rs b/src/api/icons.rs index 6af10a35..71c4899d 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -1,21 +1,28 @@ use std::{ collections::HashMap, - net::{IpAddr, ToSocketAddrs}, - sync::{Arc, RwLock}, + net::IpAddr, + sync::Arc, time::{Duration, SystemTime}, }; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use futures::{stream::StreamExt, TryFutureExt}; use once_cell::sync::Lazy; use regex::Regex; -use reqwest::{header, Client, Response}; +use reqwest::{ + header::{self, HeaderMap, HeaderValue}, + Client, Response, +}; use rocket::{http::ContentType, response::Redirect, Route}; use tokio::{ fs::{create_dir_all, remove_file, symlink_metadata, File}, io::{AsyncReadExt, AsyncWriteExt}, + net::lookup_host, + sync::RwLock, }; +use html5gum::{Emitter, EndTag, InfallibleTokenizer, Readable, StartTag, StringReader, Tokenizer}; + use crate::{ error::Error, util::{get_reqwest_client_builder, Cached}, @@ -34,39 +41,50 @@ pub fn routes() -> Vec { static CLIENT: Lazy = Lazy::new(|| { // Generate the default headers - let mut default_headers = header::HeaderMap::new(); - default_headers - .insert(header::USER_AGENT, header::HeaderValue::from_static("Links (2.22; Linux X86_64; GNU C; text)")); - default_headers - .insert(header::ACCEPT, header::HeaderValue::from_static("text/html, text/*;q=0.5, image/*, */*;q=0.1")); - default_headers.insert(header::ACCEPT_LANGUAGE, header::HeaderValue::from_static("en,*;q=0.1")); - default_headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache")); - default_headers.insert(header::PRAGMA, header::HeaderValue::from_static("no-cache")); + let mut default_headers = HeaderMap::new(); + default_headers.insert(header::USER_AGENT, 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.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en,*;q=0.1")); + default_headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache")); + default_headers.insert(header::PRAGMA, HeaderValue::from_static("no-cache")); + + // Generate the cookie store + let cookie_store = Arc::new(Jar::default()); // Reuse the client between requests - get_reqwest_client_builder() - .cookie_provider(Arc::new(Jar::default())) + let client = get_reqwest_client_builder() + .cookie_provider(cookie_store.clone()) .timeout(Duration::from_secs(CONFIG.icon_download_timeout())) - .default_headers(default_headers) - .build() - .expect("Failed to build icon client") + .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() + .cookie_provider(cookie_store) + .timeout(Duration::from_secs(CONFIG.icon_download_timeout())) + .default_headers(default_headers) + .trust_dns(false) + .build() + .expect("Failed to build client") + } + } }); // Build Regex only once since this takes a lot of time. -static ICON_REL_REGEX: Lazy = Lazy::new(|| Regex::new(r"(?i)icon$|apple.*icon").unwrap()); -static ICON_REL_BLACKLIST: Lazy = Lazy::new(|| Regex::new(r"(?i)mask-icon").unwrap()); static ICON_SIZE_REGEX: Lazy = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap()); // Special HashMap which holds the user defined Regex to speedup matching the regex. static ICON_BLACKLIST_REGEX: Lazy>> = Lazy::new(|| RwLock::new(HashMap::new())); -fn icon_redirect(domain: &str, template: &str) -> Option { - if !is_valid_domain(domain) { +async fn icon_redirect(domain: &str, template: &str) -> Option { + if !is_valid_domain(domain).await { warn!("Invalid domain: {}", domain); return None; } - if is_domain_blacklisted(domain) { + if is_domain_blacklisted(domain).await { return None; } @@ -84,30 +102,30 @@ fn icon_redirect(domain: &str, template: &str) -> Option { } #[get("//icon.png")] -fn icon_custom(domain: String) -> Option { - icon_redirect(&domain, &CONFIG.icon_service()) +async fn icon_custom(domain: String) -> Option { + icon_redirect(&domain, &CONFIG.icon_service()).await } #[get("//icon.png")] -fn icon_bitwarden(domain: String) -> Option { - icon_redirect(&domain, "https://icons.bitwarden.net/{}/icon.png") +async fn icon_bitwarden(domain: String) -> Option { + icon_redirect(&domain, "https://icons.bitwarden.net/{}/icon.png").await } #[get("//icon.png")] -fn icon_duckduckgo(domain: String) -> Option { - icon_redirect(&domain, "https://icons.duckduckgo.com/ip3/{}.ico") +async fn icon_duckduckgo(domain: String) -> Option { + icon_redirect(&domain, "https://icons.duckduckgo.com/ip3/{}.ico").await } #[get("//icon.png")] -fn icon_google(domain: String) -> Option { - icon_redirect(&domain, "https://www.google.com/s2/favicons?domain={}&sz=32") +async fn icon_google(domain: String) -> Option { + icon_redirect(&domain, "https://www.google.com/s2/favicons?domain={}&sz=32").await } #[get("//icon.png")] async fn icon_internal(domain: String) -> Cached<(ContentType, Vec)> { 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); return Cached::ttl( (ContentType::new("image", "png"), FALLBACK_ICON.to_vec()), @@ -128,7 +146,7 @@ async fn icon_internal(domain: String) -> Cached<(ContentType, Vec)> { /// /// 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. -fn is_valid_domain(domain: &str) -> bool { +async fn is_valid_domain(domain: &str) -> bool { const ALLOWED_CHARS: &str = "_-."; // If parsing the domain fails using Url, it will not work with reqwest. @@ -260,57 +278,52 @@ mod tests { } } -fn is_domain_blacklisted(domain: &str) -> bool { - let mut is_blacklisted = CONFIG.icon_blacklist_non_global_ips() - && (domain, 0) - .to_socket_addrs() - .map(|x| { - for ip_port in x { - if !is_global(ip_port.ip()) { - warn!("IP {} for domain '{}' is not a global IP!", ip_port.ip(), domain); - return true; - } +use cached::proc_macro::cached; +#[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)] +async fn is_domain_blacklisted(domain: &str) -> bool { + if CONFIG.icon_blacklist_non_global_ips() { + if let Ok(s) = lookup_host((domain, 0)).await { + for addr in s { + if !is_global(addr.ip()) { + debug!("IP {} for domain '{}' is not a global IP!", addr.ip(), domain); + 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() { - let mut regex_hashmap = ICON_BLACKLIST_REGEX.read().unwrap(); - - // 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) { - regex - } else { - drop(regex_hashmap); - - let mut regex_hashmap_write = ICON_BLACKLIST_REGEX.write().unwrap(); - // 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. - if regex_hashmap_write.len() >= 1 { - regex_hashmap_write.clear(); - } - - // Generate the regex to store in too the Lazy Static HashMap. - let blacklist_regex = Regex::new(&blacklist).unwrap(); - regex_hashmap_write.insert(blacklist.to_string(), blacklist_regex); - drop(regex_hashmap_write); - - regex_hashmap = ICON_BLACKLIST_REGEX.read().unwrap(); - regex_hashmap.get(&blacklist).unwrap() - }; - - // Use the pre-generate Regex stored in a Lazy HashMap. - if regex.is_match(domain) { - debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain); - is_blacklisted = true; } } } - is_blacklisted + if let Some(blacklist) = CONFIG.icon_blacklist_regex() { + 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. + let regex = if let Some(regex) = regex_hashmap.get(&blacklist) { + regex + } else { + drop(regex_hashmap); + + let mut regex_hashmap_write = ICON_BLACKLIST_REGEX.write().await; + // 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. + if regex_hashmap_write.len() >= 1 { + regex_hashmap_write.clear(); + } + + // Generate the regex to store in too the Lazy Static HashMap. + let blacklist_regex = Regex::new(&blacklist); + regex_hashmap_write.insert(blacklist.to_string(), blacklist_regex.unwrap()); + drop(regex_hashmap_write); + + regex_hashmap = ICON_BLACKLIST_REGEX.read().await; + regex_hashmap.get(&blacklist).unwrap() + }; + + // Use the pre-generate Regex stored in a Lazy HashMap. + if regex.is_match(domain) { + debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain); + return true; + } + } + false } async fn get_icon(domain: &str) -> Option<(Vec, String)> { @@ -322,7 +335,7 @@ async fn get_icon(domain: &str) -> Option<(Vec, String)> { } 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, _ => "x-icon", }; @@ -412,91 +425,62 @@ impl Icon { } } -/// Iterates over the HTML document to find -/// When found it will stop the iteration and the found base href will be shared deref via `base_href`. -/// -/// # Arguments -/// * `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. -/// -fn get_base_href(node: &std::rc::Rc, base_href: &mut url::Url) -> bool { - if let markup5ever_rcdom::NodeData::Element { - name, - attrs, - .. - } = &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(); +async fn get_favicons_node( + dom: InfallibleTokenizer, FaviconEmitter>, + icons: &mut Vec, + url: &url::Url, +) { + const TAG_LINK: &[u8] = b"link"; + const TAG_BASE: &[u8] = b"base"; + const TAG_HEAD: &[u8] = b"head"; + const ATTR_REL: &[u8] = b"rel"; + const ATTR_HREF: &[u8] = b"href"; + const ATTR_SIZES: &[u8] = b"sizes"; - if attr_name == "href" { - debug!("Found base href: {}", attr_value); - *base_href = match base_href.join(attr_value) { - Ok(href) => href, - _ => base_href.clone(), - }; - return true; - } - } - return true; - } - } - - // 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 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, icons: &mut Vec, 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) + let mut base_url = url.clone(); + let mut icon_tags: Vec = Vec::new(); + for token in dom { + match token { + FaviconToken::StartTag(tag) => { + if tag.name == TAG_LINK + && tag.attributes.contains_key(ATTR_REL) + && tag.attributes.contains_key(ATTR_HREF) { - has_rel = true; - } else if attr_name == "href" { - href = Some(attr_value); - } else if attr_name == "sizes" { - sizes = Some(attr_value); + 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(), + }; } } - - 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)); - } + FaviconToken::EndTag(tag) => { + if tag.name == TAG_HEAD { + break; } } } } - // TODO: Might want to limit the recursion depth? - for child in node.children.borrow().iter() { - get_favicons_node(child, icons, url); + for icon_tag in icon_tags { + if let Some(icon_href) = icon_tag.attributes.get(ATTR_HREF) { + 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 /// ``` -/// let icon_result = get_icon_url("github.com")?; -/// let icon_result = get_icon_url("vaultwarden.discourse.group")?; +/// let icon_result = get_icon_url("github.com").await?; +/// let icon_result = get_icon_url("vaultwarden.discourse.group").await?; /// ``` async fn get_icon_url(domain: &str) -> Result { // Default URL with secure and insecure schemes - let ssldomain = format!("https://{}", domain); - let httpdomain = format!("http://{}", domain); + let ssldomain = format!("https://{domain}"); + let httpdomain = format!("http://{domain}"); // 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 { @@ -537,26 +521,25 @@ async fn get_icon_url(domain: &str) -> Result { tld = domain_parts.next_back().unwrap(), base = domain_parts.next_back().unwrap() ); - if is_valid_domain(&base_domain) { - let sslbase = format!("https://{}", base_domain); - let httpbase = format!("http://{}", base_domain); - debug!("[get_icon_url]: Trying without subdomains '{}'", base_domain); + if is_valid_domain(&base_domain).await { + let sslbase = format!("https://{base_domain}"); + let httpbase = format!("http://{base_domain}"); + debug!("[get_icon_url]: Trying without subdomains '{base_domain}'"); 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. } else if is_ip.is_err() && domain.matches('.').count() < 2 { - let www_domain = format!("www.{}", domain); - if is_valid_domain(&www_domain) { - let sslwww = format!("https://{}", www_domain); - let httpwww = format!("http://{}", www_domain); - debug!("[get_icon_url]: Trying with www. prefix '{}'", www_domain); + let www_domain = format!("www.{domain}"); + if is_valid_domain(&www_domain).await { + let sslwww = format!("https://{www_domain}"); + let httpwww = format!("http://{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 } }; @@ -571,26 +554,23 @@ async fn get_icon_url(domain: &str) -> Result { // Set the referer to be used on the final request, some sites check this. // 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(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. - 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 = html5ever::parse_document(markup5ever_rcdom::RcDom::default(), Default::default()) - .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); + let dom = Tokenizer::new_with_emitter(limited_reader.to_reader(), FaviconEmitter::default()).infallible(); + get_favicons_node(dom, &mut iconlist, &url).await; } else { // 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!("{}/favicon.ico", httpdomain))); + iconlist.push(Icon::new(35, format!("{ssldomain}/favicon.ico"))); + 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 @@ -608,7 +588,7 @@ async fn get_page(url: &str) -> Result { } async fn get_page_with_referer(url: &str, referer: &str) -> Result { - 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); } @@ -632,12 +612,12 @@ async fn get_page_with_referer(url: &str, referer: &str) -> Result) -> u8 { +async fn get_icon_priority(href: &str, sizes: &str) -> u8 { // 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 if width != 0 && height != 0 { @@ -679,15 +659,15 @@ fn get_icon_priority(href: &str, sizes: Option<&str>) -> u8 { /// /// # Example /// ``` -/// let (width, height) = parse_sizes("64x64"); // (64, 64) -/// let (width, height) = parse_sizes("x128x128"); // (128, 128) -/// let (width, height) = parse_sizes("32"); // (0, 0) +/// let (width, height) = parse_sizes("64x64").await; // (64, 64) +/// let (width, height) = parse_sizes("x128x128").await; // (128, 128) +/// 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 height: u16 = 0; - if let Some(sizes) = sizes { + if !sizes.is_empty() { match ICON_SIZE_REGEX.captures(sizes.trim()) { None => {} 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> { - if is_domain_blacklisted(domain) { + if is_domain_blacklisted(domain).await { 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 if body.len() >= 67 { // 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() { debug!("Icon from {} data:image uri, is not a valid image type", domain); continue; @@ -742,10 +722,10 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> { } else { match get_page_with_referer(&icon.href, &icon_result.referer).await { Ok(res) => { - buffer = stream_to_bytes_limit(res, 512 * 1024).await?; // 512 KB for each icon max - // Check if the icon type is allowed, else try an icon from the list. - icon_type = get_icon_type(&buffer); + 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).await; if icon_type.is_none() { buffer.clear(); 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 { [137, 80, 78, 71, ..] => Some("png"), [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 { + 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. /// 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. /// 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; #[derive(Default)] -pub struct Jar(RwLock); +pub struct Jar(std::sync::RwLock); impl reqwest::cookie::CookieStore for Jar { fn set_cookies(&self, cookie_headers: &mut dyn Iterator, 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 { - let mut stream = res.bytes_stream().take(max_size); - let mut buf = BytesMut::new(); - while let Some(chunk) = stream.next().await { - buf.extend(chunk?); - } - Ok(buf.freeze()) +/// Custom FaviconEmitter for the html5gum parser. +/// The FaviconEmitter is using an almost 1:1 copy of the DefaultEmitter with some small changes. +/// This prevents emitting tags like comments, doctype and also strings between the tags. +/// Therefor parsing the HTML content is faster. +use std::collections::{BTreeSet, VecDeque}; + +enum FaviconToken { + StartTag(StartTag), + EndTag(EndTag), +} + +#[derive(Default)] +struct FaviconEmitter { + current_token: Option, + last_start_tag: Vec, + current_attribute: Option<(Vec, Vec)>, + seen_attributes: BTreeSet>, + emitted_tokens: VecDeque, +} + +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.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) {} } diff --git a/src/config.rs b/src/config.rs index 936353ce..59f02b74 100644 --- a/src/config.rs +++ b/src/config.rs @@ -569,12 +569,14 @@ make_config! { _enable_smtp: bool, true, def, true; /// Host smtp_host: String, true, option; - /// Enable Secure SMTP |> (Explicit) - Enabling this by default would use STARTTLS (Standard ports 587 or 25) - smtp_ssl: bool, true, def, true; - /// 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) - smtp_explicit_tls: bool, true, def, false; + /// DEPRECATED smtp_ssl |> DEPRECATED - Please use SMTP_SECURITY + smtp_ssl: bool, false, option; + /// DEPRECATED smtp_explicit_tls |> DEPRECATED - Please use SMTP_SECURITY + 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 - 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 smtp_from: String, true, def, String::new(); /// From Name @@ -657,6 +659,13 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { } 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() { 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, smtp_explicit_tls: Option) -> 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 { pub fn load() -> Result { // Loading from env and file diff --git a/src/mail.rs b/src/mail.rs index df9919d2..362d4aa3 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -30,7 +30,7 @@ fn mailer() -> SmtpTransport { .timeout(Some(Duration::from_secs(CONFIG.smtp_timeout()))); // 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); if CONFIG.smtp_accept_invalid_hostnames() { tls_parameters = tls_parameters.dangerous_accept_invalid_hostnames(true); @@ -40,7 +40,7 @@ fn mailer() -> SmtpTransport { } 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)) } else { smtp_client.tls(Tls::Required(tls_parameters)) diff --git a/src/main.rs b/src/main.rs index cb382723..08ac9d7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -329,7 +329,6 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> let basepath = &CONFIG.domain_path(); let mut config = rocket::Config::from(rocket::Config::figment()); - config.address = std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED); // TODO: Allow this to be changed, keep ROCKET_ADDRESS for compat config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into(); config.limits = Limits::new() // .limit("json", 10.megabytes()) diff --git a/src/static/scripts/bootstrap-native.js b/src/static/scripts/bootstrap-native.js index 3827dfa6..c00b4e87 100644 --- a/src/static/scripts/bootstrap-native.js +++ b/src/static/scripts/bootstrap-native.js @@ -1,6 +1,6 @@ /*! - * Native JavaScript for Bootstrap v4.0.8 (https://thednp.github.io/bootstrap.native/) - * Copyright 2015-2021 © dnp_theme + * Native JavaScript for Bootstrap v4.1.0 (https://thednp.github.io/bootstrap.native/) + * Copyright 2015-2022 © dnp_theme * Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE) */ (function (global, factory) { @@ -9,157 +9,599 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BSN = factory()); })(this, (function () { 'use strict'; - const transitionEndEvent = 'webkitTransition' in document.head.style ? 'webkitTransitionEnd' : 'transitionend'; + /** @type {Record} */ + const EventRegistry = {}; - const supportTransition = 'webkitTransition' in document.head.style || 'transition' in document.head.style; + /** + * The global event listener. + * + * @this {Element | HTMLElement | Window | Document} + * @param {Event} e + * @returns {void} + */ + function globalListener(e) { + const that = this; + const { type } = e; + const oneEvMap = EventRegistry[type] ? [...EventRegistry[type]] : []; - const transitionDuration = 'webkitTransition' in document.head.style ? 'webkitTransitionDuration' : 'transitionDuration'; + oneEvMap.forEach((elementsMap) => { + const [element, listenersMap] = elementsMap; + [...listenersMap].forEach((listenerMap) => { + if (element === that) { + const [listener, options] = listenerMap; + listener.apply(element, [e]); - const transitionProperty = 'webkitTransition' in document.head.style ? 'webkitTransitionProperty' : 'transitionProperty'; + if (options && options.once) { + removeListener(element, type, listener, options); + } + } + }); + }); + } - function getElementTransitionDuration(element) { + /** + * Register a new listener with its options and attach the `globalListener` + * to the target if this is the first listener. + * + * @param {Element | HTMLElement | Window | Document} element + * @param {string} eventType + * @param {EventListenerObject['handleEvent']} listener + * @param {AddEventListenerOptions=} options + */ + const addListener = (element, eventType, listener, options) => { + // get element listeners first + if (!EventRegistry[eventType]) { + EventRegistry[eventType] = new Map(); + } + const oneEventMap = EventRegistry[eventType]; + + if (!oneEventMap.has(element)) { + oneEventMap.set(element, new Map()); + } + const oneElementMap = oneEventMap.get(element); + + // get listeners size + const { size } = oneElementMap; + + // register listener with its options + if (oneElementMap) { + oneElementMap.set(listener, options); + } + + // add listener last + if (!size) { + element.addEventListener(eventType, globalListener, options); + } + }; + + /** + * Remove a listener from registry and detach the `globalListener` + * if no listeners are found in the registry. + * + * @param {Element | HTMLElement | Window | Document} element + * @param {string} eventType + * @param {EventListenerObject['handleEvent']} listener + * @param {AddEventListenerOptions=} options + */ + const removeListener = (element, eventType, listener, options) => { + // get listener first + const oneEventMap = EventRegistry[eventType]; + const oneElementMap = oneEventMap && oneEventMap.get(element); + const savedOptions = oneElementMap && oneElementMap.get(listener); + + // also recover initial options + const { options: eventOptions } = savedOptions !== undefined + ? savedOptions + : { options }; + + // unsubscribe second, remove from registry + if (oneElementMap && oneElementMap.has(listener)) oneElementMap.delete(listener); + if (oneEventMap && (!oneElementMap || !oneElementMap.size)) oneEventMap.delete(element); + if (!oneEventMap || !oneEventMap.size) delete EventRegistry[eventType]; + + // remove listener last + if (!oneElementMap || !oneElementMap.size) { + element.removeEventListener(eventType, globalListener, eventOptions); + } + }; + + /** + * Advanced event listener based on subscribe / publish pattern. + * @see https://www.patterns.dev/posts/classic-design-patterns/#observerpatternjavascript + * @see https://gist.github.com/shystruk/d16c0ee7ac7d194da9644e5d740c8338#file-subpub-js + * @see https://hackernoon.com/do-you-still-register-window-event-listeners-in-each-component-react-in-example-31a4b1f6f1c8 + */ + const EventListener = { + on: addListener, + off: removeListener, + globalListener, + registry: EventRegistry, + }; + + /** + * A global namespace for `click` event. + * @type {string} + */ + const mouseclickEvent = 'click'; + + /** + * A global namespace for 'transitionend' string. + * @type {string} + */ + const transitionEndEvent = 'transitionend'; + + /** + * A global namespace for 'transitionDelay' string. + * @type {string} + */ + const transitionDelay = 'transitionDelay'; + + /** + * A global namespace for `transitionProperty` string for modern browsers. + * + * @type {string} + */ + const transitionProperty = 'transitionProperty'; + + /** + * Shortcut for `window.getComputedStyle(element).propertyName` + * static method. + * + * * If `element` parameter is not an `HTMLElement`, `getComputedStyle` + * throws a `ReferenceError`. + * + * @param {HTMLElement | Element} element target + * @param {string} property the css property + * @return {string} the css property value + */ + function getElementStyle(element, property) { const computedStyle = getComputedStyle(element); - const propertyValue = computedStyle[transitionProperty]; - const durationValue = computedStyle[transitionDuration]; + + // @ts-ignore -- must use camelcase strings, + // or non-camelcase strings with `getPropertyValue` + return property in computedStyle ? computedStyle[property] : ''; + } + + /** + * Utility to get the computed `transitionDelay` + * from Element in miliseconds. + * + * @param {HTMLElement | Element} element target + * @return {number} the value in miliseconds + */ + function getElementTransitionDelay(element) { + const propertyValue = getElementStyle(element, transitionProperty); + const delayValue = getElementStyle(element, transitionDelay); + + const delayScale = delayValue.includes('ms') ? 1 : 1000; + const duration = propertyValue && propertyValue !== 'none' + ? parseFloat(delayValue) * delayScale : 0; + + return !Number.isNaN(duration) ? duration : 0; + } + + /** + * A global namespace for 'transitionDuration' string. + * @type {string} + */ + const transitionDuration = 'transitionDuration'; + + /** + * Utility to get the computed `transitionDuration` + * from Element in miliseconds. + * + * @param {HTMLElement | Element} element target + * @return {number} the value in miliseconds + */ + function getElementTransitionDuration(element) { + const propertyValue = getElementStyle(element, transitionProperty); + const durationValue = getElementStyle(element, transitionDuration); const durationScale = durationValue.includes('ms') ? 1 : 1000; - const duration = supportTransition && propertyValue && propertyValue !== 'none' + const duration = propertyValue && propertyValue !== 'none' ? parseFloat(durationValue) * durationScale : 0; return !Number.isNaN(duration) ? duration : 0; } + /** + * Utility to make sure callbacks are consistently + * called when transition ends. + * + * @param {HTMLElement | Element} element target + * @param {EventListener} handler `transitionend` callback + */ function emulateTransitionEnd(element, handler) { let called = 0; const endEvent = new Event(transitionEndEvent); const duration = getElementTransitionDuration(element); + const delay = getElementTransitionDelay(element); if (duration) { - element.addEventListener(transitionEndEvent, function transitionEndWrapper(e) { + /** + * Wrap the handler in on -> off callback + * @type {EventListener} e Event object + */ + const transitionEndWrapper = (e) => { if (e.target === element) { handler.apply(element, [e]); element.removeEventListener(transitionEndEvent, transitionEndWrapper); called = 1; } - }); + }; + element.addEventListener(transitionEndEvent, transitionEndWrapper); setTimeout(() => { if (!called) element.dispatchEvent(endEvent); - }, duration + 17); + }, duration + delay + 17); } else { handler.apply(element, [endEvent]); } } - function queryElement(selector, parent) { - const lookUp = parent && parent instanceof Element ? parent : document; - return selector instanceof Element ? selector : lookUp.querySelector(selector); + /** + * Returns the `document` or the `#document` element. + * @see https://github.com/floating-ui/floating-ui + * @param {(Node | HTMLElement | Element | globalThis)=} node + * @returns {Document} + */ + function getDocument(node) { + if (node instanceof HTMLElement) return node.ownerDocument; + if (node instanceof Window) return node.document; + return window.document; } + /** + * A global array of possible `ParentNode`. + */ + const parentNodes = [Document, Element, HTMLElement]; + + /** + * A global array with `Element` | `HTMLElement`. + */ + const elementNodes = [Element, HTMLElement]; + + /** + * Utility to check if target is typeof `HTMLElement`, `Element`, `Node` + * or find one that matches a selector. + * + * @param {HTMLElement | Element | string} selector the input selector or target element + * @param {(HTMLElement | Element | Document)=} parent optional node to look into + * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result + */ + function querySelector(selector, parent) { + const lookUp = parentNodes.some((x) => parent instanceof x) + ? parent : getDocument(); + + // @ts-ignore + return elementNodes.some((x) => selector instanceof x) + // @ts-ignore + ? selector : lookUp.querySelector(selector); + } + + /** + * Shortcut for `HTMLElement.closest` method which also works + * with children of `ShadowRoot`. The order of the parameters + * is intentional since they're both required. + * + * @see https://stackoverflow.com/q/54520554/803358 + * + * @param {HTMLElement | Element} element Element to look into + * @param {string} selector the selector name + * @return {(HTMLElement | Element)?} the query result + */ + function closest(element, selector) { + return element ? (element.closest(selector) + // @ts-ignore -- break out of `ShadowRoot` + || closest(element.getRootNode().host, selector)) : null; + } + + /** + * Shortcut for `Object.assign()` static method. + * @param {Record} obj a target object + * @param {Record} source a source object + */ + const ObjectAssign = (obj, source) => Object.assign(obj, source); + + /** + * Check class in `HTMLElement.classList`. + * + * @param {HTMLElement | Element} element target + * @param {string} classNAME to check + * @returns {boolean} + */ function hasClass(element, classNAME) { return element.classList.contains(classNAME); } + /** + * Remove class from `HTMLElement.classList`. + * + * @param {HTMLElement | Element} element target + * @param {string} classNAME to remove + * @returns {void} + */ function removeClass(element, classNAME) { element.classList.remove(classNAME); } - const addEventListener = 'addEventListener'; + /** + * Shortcut for the `Element.dispatchEvent(Event)` method. + * + * @param {HTMLElement | Element} element is the target + * @param {Event} event is the `Event` object + */ + const dispatchEvent = (element, event) => element.dispatchEvent(event); - const removeEventListener = 'removeEventListener'; + /** @type {Map>>} */ + const componentData = new Map(); + /** + * An interface for web components background data. + * @see https://github.com/thednp/bootstrap.native/blob/master/src/components/base-component.js + */ + const Data = { + /** + * Sets web components data. + * @param {HTMLElement | Element | string} target target element + * @param {string} component the component's name or a unique key + * @param {Record} instance the component instance + */ + set: (target, component, instance) => { + const element = querySelector(target); + if (!element) return; - const fadeClass = 'fade'; + if (!componentData.has(component)) { + componentData.set(component, new Map()); + } - const showClass = 'show'; + const instanceMap = componentData.get(component); + // @ts-ignore - not undefined, but defined right above + instanceMap.set(element, instance); + }, - const dataBsDismiss = 'data-bs-dismiss'; + /** + * Returns all instances for specified component. + * @param {string} component the component's name or a unique key + * @returns {Map>?} all the component instances + */ + getAllFor: (component) => { + const instanceMap = componentData.get(component); - function bootstrapCustomEvent(namespacedEventType, eventProperties) { - const OriginalCustomEvent = new CustomEvent(namespacedEventType, { cancelable: true }); + return instanceMap || null; + }, - if (eventProperties instanceof Object) { - Object.keys(eventProperties).forEach((key) => { - Object.defineProperty(OriginalCustomEvent, key, { - value: eventProperties[key], - }); - }); + /** + * Returns the instance associated with the target. + * @param {HTMLElement | Element | string} target target element + * @param {string} component the component's name or a unique key + * @returns {Record?} the instance + */ + get: (target, component) => { + const element = querySelector(target); + const allForC = Data.getAllFor(component); + const instance = element && allForC && allForC.get(element); + + return instance || null; + }, + + /** + * Removes web components data. + * @param {HTMLElement | Element | string} target target element + * @param {string} component the component's name or a unique key + */ + remove: (target, component) => { + const element = querySelector(target); + const instanceMap = componentData.get(component); + if (!instanceMap || !element) return; + + instanceMap.delete(element); + + if (instanceMap.size === 0) { + componentData.delete(component); + } + }, + }; + + /** + * An alias for `Data.get()`. + * @type {SHORTER.getInstance} + */ + const getInstance = (target, component) => Data.get(target, component); + + /** + * Returns a namespaced `CustomEvent` specific to each component. + * @param {string} EventType Event.type + * @param {Record=} config Event.options | Event.properties + * @returns {SHORTER.OriginalEvent} a new namespaced event + */ + function OriginalEvent(EventType, config) { + const OriginalCustomEvent = new CustomEvent(EventType, { + cancelable: true, bubbles: true, + }); + + if (config instanceof Object) { + ObjectAssign(OriginalCustomEvent, config); } return OriginalCustomEvent; } + /** + * Global namespace for most components `fade` class. + */ + const fadeClass = 'fade'; + + /** + * Global namespace for most components `show` class. + */ + const showClass = 'show'; + + /** + * Global namespace for most components `dismiss` option. + */ + const dataBsDismiss = 'data-bs-dismiss'; + + /** @type {string} */ + const alertString = 'alert'; + + /** @type {string} */ + const alertComponent = 'Alert'; + + /** + * Shortcut for `HTMLElement.getAttribute()` method. + * @param {HTMLElement | Element} element target element + * @param {string} attribute attribute name + * @returns {string?} attribute value + */ + const getAttribute = (element, attribute) => element.getAttribute(attribute); + + /** + * The raw value or a given component option. + * + * @typedef {string | HTMLElement | Function | number | boolean | null} niceValue + */ + + /** + * Utility to normalize component options + * + * @param {any} value the input value + * @return {niceValue} the normalized value + */ function normalizeValue(value) { - if (value === 'true') { + if (value === 'true') { // boolean return true; } - if (value === 'false') { + if (value === 'false') { // boolean return false; } - if (!Number.isNaN(+value)) { + if (!Number.isNaN(+value)) { // number return +value; } - if (value === '' || value === 'null') { + if (value === '' || value === 'null') { // null return null; } - // string / function / Element / Object + // string / function / HTMLElement / object return value; } + /** + * Shortcut for `Object.keys()` static method. + * @param {Record} obj a target object + * @returns {string[]} + */ + const ObjectKeys = (obj) => Object.keys(obj); + + /** + * Shortcut for `String.toLowerCase()`. + * + * @param {string} source input string + * @returns {string} lowercase output string + */ + const toLowerCase = (source) => source.toLowerCase(); + + /** + * Utility to normalize component options. + * + * @param {HTMLElement | Element} element target + * @param {Record} defaultOps component default options + * @param {Record} inputOps component instance options + * @param {string=} ns component namespace + * @return {Record} normalized component options object + */ function normalizeOptions(element, defaultOps, inputOps, ns) { - const normalOps = {}; - const dataOps = {}; + // @ts-ignore -- our targets are always `HTMLElement` const data = { ...element.dataset }; + /** @type {Record} */ + const normalOps = {}; + /** @type {Record} */ + const dataOps = {}; + const title = 'title'; - Object.keys(data) - .forEach((k) => { - const key = k.includes(ns) - ? k.replace(ns, '').replace(/[A-Z]/, (match) => match.toLowerCase()) - : k; + ObjectKeys(data).forEach((k) => { + const key = ns && k.includes(ns) + ? k.replace(ns, '').replace(/[A-Z]/, (match) => toLowerCase(match)) + : k; - dataOps[key] = normalizeValue(data[k]); - }); + dataOps[key] = normalizeValue(data[k]); + }); - Object.keys(inputOps) - .forEach((k) => { - inputOps[k] = normalizeValue(inputOps[k]); - }); + ObjectKeys(inputOps).forEach((k) => { + inputOps[k] = normalizeValue(inputOps[k]); + }); - Object.keys(defaultOps) - .forEach((k) => { - if (k in inputOps) { - normalOps[k] = inputOps[k]; - } else if (k in dataOps) { - normalOps[k] = dataOps[k]; - } else { - normalOps[k] = defaultOps[k]; - } - }); + ObjectKeys(defaultOps).forEach((k) => { + if (k in inputOps) { + normalOps[k] = inputOps[k]; + } else if (k in dataOps) { + normalOps[k] = dataOps[k]; + } else { + normalOps[k] = k === title + ? getAttribute(element, title) + : defaultOps[k]; + } + }); return normalOps; } + var version = "4.1.0"; + + const Version = version; + /* Native JavaScript for Bootstrap 5 | Base Component ----------------------------------------------------- */ + /** Returns a new `BaseComponent` instance. */ class BaseComponent { - constructor(name, target, defaults, config) { + /** + * @param {HTMLElement | Element | string} target `Element` or selector string + * @param {BSN.ComponentOptions=} config component instance options + */ + constructor(target, config) { const self = this; - const element = queryElement(target); + const element = querySelector(target); - if (element[name]) element[name].dispose(); + if (!element) { + throw Error(`${self.name} Error: "${target}" is not a valid selector.`); + } + + /** @static @type {BSN.ComponentOptions} */ + self.options = {}; + + const prevInstance = Data.get(element, self.name); + if (prevInstance) prevInstance.dispose(); + + /** @type {HTMLElement | Element} */ self.element = element; - if (defaults && Object.keys(defaults).length) { - self.options = normalizeOptions(element, defaults, (config || {}), 'bs'); + if (self.defaults && Object.keys(self.defaults).length) { + self.options = normalizeOptions(element, self.defaults, (config || {}), 'bs'); } - element[name] = self; + + Data.set(element, self.name, self); } - dispose(name) { + /* eslint-disable */ + /** @static */ + get version() { return Version; } + /* eslint-enable */ + + /** @static */ + get name() { return this.constructor.name; } + + /** @static */ + // @ts-ignore + get defaults() { return this.constructor.defaults; } + + /** + * Removes component from target element; + */ + dispose() { const self = this; - self.element[name] = null; - Object.keys(self).forEach((prop) => { self[prop] = null; }); + Data.remove(self.element, self.name); + // @ts-ignore + ObjectKeys(self).forEach((prop) => { self[prop] = null; }); } } @@ -168,24 +610,39 @@ // ALERT PRIVATE GC // ================ - const alertString = 'alert'; - const alertComponent = 'Alert'; const alertSelector = `.${alertString}`; const alertDismissSelector = `[${dataBsDismiss}="${alertString}"]`; + /** + * Static method which returns an existing `Alert` instance associated + * to a target `Element`. + * + * @type {BSN.GetInstance} + */ + const getAlertInstance = (element) => getInstance(element, alertComponent); + + /** + * An `Alert` initialization callback. + * @type {BSN.InitCallback} + */ + const alertInitCallback = (element) => new Alert(element); + // ALERT CUSTOM EVENTS // =================== - const closeAlertEvent = bootstrapCustomEvent(`close.bs.${alertString}`); - const closedAlertEvent = bootstrapCustomEvent(`closed.bs.${alertString}`); + const closeAlertEvent = OriginalEvent(`close.bs.${alertString}`); + const closedAlertEvent = OriginalEvent(`closed.bs.${alertString}`); - // ALERT EVENT HANDLERS - // ==================== + // ALERT EVENT HANDLER + // =================== + /** + * Alert `transitionend` callback. + * @param {Alert} self target Alert instance + */ function alertTransitionEnd(self) { - const { element, relatedTarget } = self; + const { element } = self; toggleAlertHandler(self); - if (relatedTarget) closedAlertEvent.relatedTarget = relatedTarget; - element.dispatchEvent(closedAlertEvent); + dispatchEvent(element, closedAlertEvent); self.dispose(); element.remove(); @@ -193,16 +650,24 @@ // ALERT PRIVATE METHOD // ==================== + /** + * Toggle on / off the `click` event listener. + * @param {Alert} self the target alert instance + * @param {boolean=} add when `true`, event listener is added + */ function toggleAlertHandler(self, add) { - const action = add ? addEventListener : removeEventListener; - if (self.dismiss) self.dismiss[action]('click', self.close); + const action = add ? addListener : removeListener; + const { dismiss } = self; + if (dismiss) action(dismiss, mouseclickEvent, self.close); } // ALERT DEFINITION // ================ + /** Creates a new Alert instance. */ class Alert extends BaseComponent { + /** @param {HTMLElement | Element | string} target element or selector */ constructor(target) { - super(alertComponent, target); + super(target); // bind const self = this; @@ -210,28 +675,39 @@ const { element } = self; // the dismiss button - self.dismiss = queryElement(alertDismissSelector, element); - self.relatedTarget = null; + /** @static @type {(HTMLElement | Element)?} */ + self.dismiss = querySelector(alertDismissSelector, element); // add event listener - toggleAlertHandler(self, 1); + toggleAlertHandler(self, true); } + /* eslint-disable */ + /** + * Returns component name string. + * @readonly @static + */ + get name() { return alertComponent; } + /* eslint-enable */ + // ALERT PUBLIC METHODS // ==================== + /** + * Public method that hides the `.alert` element from the user, + * disposes the instance once animation is complete, then + * removes the element from the DOM. + * + * @param {Event=} e most likely the `click` event + * @this {Alert} the `Alert` instance or `EventTarget` + */ close(e) { - const target = e ? e.target : null; - const self = e - ? e.target.closest(alertSelector)[alertComponent] - : this; + // @ts-ignore + const self = e ? getAlertInstance(closest(this, alertSelector)) : this; + if (!self) return; const { element } = self; - if (self && element && hasClass(element, showClass)) { - if (target) { - closeAlertEvent.relatedTarget = target; - self.relatedTarget = target; - } - element.dispatchEvent(closeAlertEvent); + if (hasClass(element, showClass)) { + dispatchEvent(element, closeAlertEvent); if (closeAlertEvent.defaultPrevented) return; removeClass(element, showClass); @@ -242,123 +718,452 @@ } } + /** Remove the component from target element. */ dispose() { toggleAlertHandler(this); - super.dispose(alertComponent); + super.dispose(); } } - Alert.init = { - component: alertComponent, + ObjectAssign(Alert, { selector: alertSelector, - constructor: Alert, - }; + init: alertInitCallback, + getInstance: getAlertInstance, + }); + /** + * A global namespace for aria-pressed. + * @type {string} + */ + const ariaPressed = 'aria-pressed'; + + /** + * Shortcut for `HTMLElement.setAttribute()` method. + * @param {HTMLElement | Element} element target element + * @param {string} attribute attribute name + * @param {string} value attribute value + * @returns {void} + */ + const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value); + + /** + * Add class to `HTMLElement.classList`. + * + * @param {HTMLElement | Element} element target + * @param {string} classNAME to add + * @returns {void} + */ function addClass(element, classNAME) { element.classList.add(classNAME); } + /** + * Global namespace for most components active class. + */ const activeClass = 'active'; + /** + * Global namespace for most components `toggle` option. + */ const dataBsToggle = 'data-bs-toggle'; + /** @type {string} */ + const buttonString = 'button'; + + /** @type {string} */ + const buttonComponent = 'Button'; + /* Native JavaScript for Bootstrap 5 | Button ---------------------------------------------*/ // BUTTON PRIVATE GC // ================= - const buttonString = 'button'; - const buttonComponent = 'Button'; const buttonSelector = `[${dataBsToggle}="${buttonString}"]`; - const ariaPressed = 'aria-pressed'; + + /** + * Static method which returns an existing `Button` instance associated + * to a target `Element`. + * + * @type {BSN.GetInstance