From 4737192853a1f0e4bb4517484f308bd9227596d7 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Thu, 22 Jan 2026 23:25:11 +0100 Subject: [PATCH 01/41] fix email as 2fa with auth requests (#6736) * fix email as 2fa with auth requests * increase expiry time of auth_requests to 15 minutes --- src/api/core/two_factor/email.rs | 45 +++++++++++++++++++++++++------- src/db/models/auth_request.rs | 4 ++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index b8724cf1..25218069 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -7,10 +7,10 @@ use crate::{ core::{log_user_event, two_factor::_generate_recover_code}, EmptyResult, JsonResult, PasswordOrOtpData, }, - auth::Headers, + auth::{ClientHeaders, Headers}, crypto, db::{ - models::{DeviceId, EventType, TwoFactor, TwoFactorType, User, UserId}, + models::{AuthRequest, AuthRequestId, DeviceId, EventType, TwoFactor, TwoFactorType, User, UserId}, DbConn, }, error::{Error, MapResult}, @@ -30,12 +30,14 @@ struct SendEmailLoginData { email: Option, #[serde(alias = "MasterPasswordHash")] master_password_hash: Option, + auth_request_id: Option, + auth_request_access_code: Option, } /// User is trying to login and wants to use email 2FA. /// Does not require Bearer token #[post("/two-factor/send-email-login", data = "")] // JsonResult -async fn send_email_login(data: Json, conn: DbConn) -> EmptyResult { +async fn send_email_login(data: Json, client_headers: ClientHeaders, conn: DbConn) -> EmptyResult { let data: SendEmailLoginData = data.into_inner(); if !CONFIG._enable_email_2fa() { @@ -47,18 +49,41 @@ async fn send_email_login(data: Json, conn: DbConn) -> Empty Some(email) if !email.is_empty() => Some(email), _ => None, }; - let user = if let Some(email) = email { - let Some(master_password_hash) = &data.master_password_hash else { - err!("No password hash has been submitted.") - }; + let master_password_hash = match &data.master_password_hash { + Some(password_hash) if !password_hash.is_empty() => Some(password_hash), + _ => None, + }; + let auth_request_id = match &data.auth_request_id { + Some(auth_request_id) if !auth_request_id.is_empty() => Some(auth_request_id), + _ => None, + }; + let user = if let Some(email) = email { let Some(user) = User::find_by_mail(email, &conn).await else { err!("Username or password is incorrect. Try again.") }; - // Check password - if !user.check_valid_password(master_password_hash) { - err!("Username or password is incorrect. Try again.") + if let Some(master_password_hash) = master_password_hash { + // Check password + if !user.check_valid_password(master_password_hash) { + err!("Username or password is incorrect. Try again.") + } + } else if let Some(auth_request_id) = auth_request_id { + let Some(auth_request) = AuthRequest::find_by_uuid(auth_request_id, &conn).await else { + err!("AuthRequest doesn't exist", "User not found") + }; + let Some(code) = &data.auth_request_access_code else { + err!("no auth request access code") + }; + + if auth_request.device_type != client_headers.device_type + || auth_request.request_ip != client_headers.ip.ip.to_string() + || !auth_request.check_access_code(code) + { + err!("AuthRequest doesn't exist", "Invalid device, IP or code") + } + } else { + err!("No password hash has been submitted.") } user diff --git a/src/db/models/auth_request.rs b/src/db/models/auth_request.rs index c2af8d74..93c6e445 100644 --- a/src/db/models/auth_request.rs +++ b/src/db/models/auth_request.rs @@ -177,7 +177,9 @@ impl AuthRequest { } pub async fn purge_expired_auth_requests(conn: &DbConn) { - let expiry_time = Utc::now().naive_utc() - chrono::TimeDelta::try_minutes(5).unwrap(); //after 5 minutes, clients reject the request + // delete auth requests older than 15 minutes which is functionally equivalent to upstream: + // https://github.com/bitwarden/server/blob/f8ee2270409f7a13125cd414c450740af605a175/src/Sql/dbo/Auth/Stored%20Procedures/AuthRequest_DeleteIfExpired.sql + let expiry_time = Utc::now().naive_utc() - chrono::TimeDelta::try_minutes(15).unwrap(); for auth_request in Self::find_created_before(&expiry_time, conn).await { auth_request.delete(conn).await.ok(); } From cc80f689ed626ac9cfbbaa810601ed02225a9398 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Thu, 22 Jan 2026 23:40:39 +0100 Subject: [PATCH 02/41] Update crates, web-vault, js, workflows (#6749) - Updated all crates - Updated web-vault to v2025.12.2 - Updated all JavaScript files - Updated all GitHub Action Workflows Also added the `concurrency` option to all workflows. Signed-off-by: BlackDex --- .github/workflows/build.yml | 9 +- .github/workflows/check-templates.yml | 8 + .github/workflows/hadolint.yml | 12 +- .github/workflows/release.yml | 12 +- .github/workflows/releasecache-cleanup.yml | 4 + .github/workflows/trivy.yml | 6 +- .github/workflows/typos.yml | 8 +- .github/workflows/zizmor.yml | 9 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 339 +- Cargo.toml | 18 +- docker/DockerSettings.yaml | 4 +- docker/Dockerfile.alpine | 12 +- docker/Dockerfile.debian | 12 +- macros/Cargo.toml | 4 +- src/api/web.rs | 4 +- src/static/scripts/datatables.css | 113 +- src/static/scripts/datatables.js | 82 +- src/static/scripts/jdenticon-3.3.0.js | 2896 ++-- ...ery-3.7.1.slim.js => jquery-4.0.0.slim.js} | 11101 +++++++--------- src/static/templates/admin/organizations.hbs | 2 +- src/static/templates/admin/users.hbs | 2 +- 22 files changed, 6485 insertions(+), 8174 deletions(-) rename src/static/scripts/{jquery-3.7.1.slim.js => jquery-4.0.0.slim.js} (64%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8901ea41..3e7818ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,10 @@ name: Build permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + on: push: paths: @@ -30,6 +34,10 @@ on: - "docker/DockerSettings.yaml" - "macros/**" +defaults: + run: + shell: bash + jobs: build: name: Build and Test ${{ matrix.channel }} @@ -63,7 +71,6 @@ jobs: # Determine rust-toolchain version - name: Init Variables id: toolchain - shell: bash env: CHANNEL: ${{ matrix.channel }} run: | diff --git a/.github/workflows/check-templates.yml b/.github/workflows/check-templates.yml index 2e02f574..a8415dde 100644 --- a/.github/workflows/check-templates.yml +++ b/.github/workflows/check-templates.yml @@ -1,8 +1,16 @@ name: Check templates permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + on: [ push, pull_request ] +defaults: + run: + shell: bash + jobs: docker-templates: name: Validate docker templates diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 8a6d1218..1e0fa48c 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -1,8 +1,15 @@ name: Hadolint +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true on: [ push, pull_request ] -permissions: {} +defaults: + run: + shell: bash jobs: hadolint: @@ -25,7 +32,6 @@ jobs: # Download hadolint - https://github.com/hadolint/hadolint/releases - name: Download hadolint - shell: bash run: | 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 @@ -41,13 +47,11 @@ jobs: # Test Dockerfiles with hadolint - name: Run hadolint - shell: bash run: hadolint docker/Dockerfile.{debian,alpine} # End Test Dockerfiles with hadolint # Test Dockerfiles with docker build checks - name: Run docker build check - shell: bash run: | echo "Checking docker/Dockerfile.debian" docker build --check . -f docker/Dockerfile.debian diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 378682d3..48cca0bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,12 @@ name: Release permissions: {} +concurrency: + # Apply concurrency control only on the upstream repo + group: ${{ github.repository == 'dani-garcia/vaultwarden' && format('{0}-{1}', github.workflow, github.ref) || github.run_id }} + # Don't cancel other runs when creating a tag + cancel-in-progress: ${{ github.ref_type == 'branch' }} + on: push: branches: @@ -10,12 +16,6 @@ on: # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet - '[1-2].[0-9]+.[0-9]+' -concurrency: - # Apply concurrency control only on the upstream repo - group: ${{ github.repository == 'dani-garcia/vaultwarden' && format('{0}-{1}', github.workflow, github.ref) || github.run_id }} - # Don't cancel other runs when creating a tag - cancel-in-progress: ${{ github.ref_type == 'branch' }} - defaults: run: shell: bash diff --git a/.github/workflows/releasecache-cleanup.yml b/.github/workflows/releasecache-cleanup.yml index 22d98fa2..66bdf228 100644 --- a/.github/workflows/releasecache-cleanup.yml +++ b/.github/workflows/releasecache-cleanup.yml @@ -1,6 +1,10 @@ name: Cleanup permissions: {} +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + on: workflow_dispatch: inputs: diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index bd1043a0..4aeb43b1 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,6 +1,10 @@ name: Trivy permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + on: push: branches: @@ -46,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index b3dae9b7..45a596ce 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -1,7 +1,11 @@ name: Code Spell Checking +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true on: [ push, pull_request ] -permissions: {} jobs: typos: @@ -19,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@1a319b54cc9e3b333fed6a5c88ba1a90324da514 # v1.40.1 + uses: crate-ci/typos@65120634e79d8374d1aa2f27e54baa0c364fff5a # v1.42.1 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 8ea25a4a..6083ef95 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -1,4 +1,9 @@ name: Security Analysis with zizmor +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true on: push: @@ -6,8 +11,6 @@ on: pull_request: branches: ["**"] -permissions: {} - jobs: zizmor: name: Run zizmor @@ -21,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0 + uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 448ccbeb..6da57526 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,6 @@ repos: - "cd docker && make" # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 1a319b54cc9e3b333fed6a5c88ba1a90324da514 # v1.40.1 + rev: 65120634e79d8374d1aa2f27e54baa0c364fff5a # v1.42.1 hooks: - id: typos diff --git a/Cargo.lock b/Cargo.lock index 07f5a49b..a4697493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,13 +161,12 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] @@ -403,9 +402,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.17" +version = "1.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81b5b2898f6798ad58f484856768bca817e3cd9de0974c24ae0f1113fe88f1b" +checksum = "959dab27ce613e6c9658eb3621064d0e2027e5f2acb65bc526a43577facea557" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -427,15 +426,16 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.91.0" +version = "1.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee6402a36f27b52fe67661c6732d684b2635152b676aa2babbfb5204f99115d" +checksum = "b7d63bd2bdeeb49aa3f9b00c15e18583503b778b2e792fc06284d54e7d5b6566" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -449,15 +449,16 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.93.0" +version = "1.94.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45a7f750bbd170ee3677671ad782d90b894548f4e4ae168302c57ec9de5cb3e" +checksum = "532d93574bf731f311bafb761366f9ece345a0416dbcc273d81d6d1a1205239b" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -471,15 +472,16 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.95.0" +version = "1.96.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55542378e419558e6b1f398ca70adb0b2088077e79ad9f14eb09441f2f7b2164" +checksum = "357e9a029c7524db6a0099cd77fbd5da165540339e7296cca603531bc783b56c" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -557,9 +559,9 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f616c3f2260612fe44cede278bafa18e73e6479c4e393e2c4518cf2a9a228a" +checksum = "ef1fcbefc7ece1d70dcce29e490f269695dfca2d2bacdeaf9e5c3f799e4e6a42" dependencies = [ "aws-smithy-runtime-api", ] @@ -576,9 +578,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.5" +version = "1.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a392db6c583ea4a912538afb86b7be7c5d8887d91604f50eb55c262ee1b4a5f5" +checksum = "bb5b6167fcdf47399024e81ac08e795180c576a20e4d4ce67949f9a88ae37dc1" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -599,9 +601,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.9.3" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0d43d899f9e508300e587bf582ba54c27a452dd0a9ea294690669138ae14a2" +checksum = "efce7aaaf59ad53c5412f14fc19b2d5c6ab2c3ec688d272fd31f76ec12f44fb0" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -616,9 +618,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.5" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905cb13a9895626d49cf2ced759b062d913834c7482c38e49557eac4e6193f01" +checksum = "65f172bcb02424eb94425db8aed1b6d583b5104d4d5ddddf22402c661a320048" dependencies = [ "base64-simd", "bytes", @@ -701,9 +703,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "base64urlsafedata" @@ -855,7 +857,7 @@ dependencies = [ "futures", "hashbrown 0.15.5", "once_cell", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "web-time", ] @@ -920,9 +922,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.51" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -944,9 +946,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -994,9 +996,9 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "brotli", "compression-core", @@ -1042,7 +1044,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] @@ -1333,9 +1335,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "data-url" @@ -1821,15 +1823,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -2011,9 +2013,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -2084,7 +2086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.12.1", + "indexmap 2.13.0", "lasso", "once_cell", "phf 0.11.3", @@ -2103,9 +2105,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -2113,7 +2115,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap 2.12.1", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -2133,9 +2135,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ "derive_builder", "log", @@ -2144,7 +2146,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", ] @@ -2222,7 +2224,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tokio", "tracing", @@ -2245,7 +2247,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -2418,7 +2420,7 @@ dependencies = [ "http 1.4.0", "hyper 1.8.1", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -2614,9 +2616,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -2702,9 +2704,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2717,9 +2719,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -2764,9 +2766,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2795,7 +2797,7 @@ checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e" dependencies = [ "base64 0.22.1", "ed25519-dalek", - "getrandom 0.2.16", + "getrandom 0.2.17", "hmac", "js-sys", "p256", @@ -2859,7 +2861,7 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-native-certs", "serde", "socket2 0.6.1", @@ -2871,9 +2873,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" @@ -2999,7 +3001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" dependencies = [ "serde", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -3283,7 +3285,7 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64 0.22.1", "chrono", - "getrandom 0.2.16", + "getrandom 0.2.17", "http 1.4.0", "rand 0.8.5", "reqwest", @@ -3335,7 +3337,7 @@ dependencies = [ "bytes", "crc32c", "futures", - "getrandom 0.2.16", + "getrandom 0.2.17", "http 1.4.0", "http-body 1.0.1", "jiff", @@ -3417,9 +3419,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" @@ -3604,9 +3606,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -3614,9 +3616,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -3624,9 +3626,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", @@ -3637,9 +3639,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -3853,9 +3855,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3963,9 +3965,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.36", "socket2 0.6.1", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3983,10 +3985,10 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4008,9 +4010,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -4056,7 +4058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4076,7 +4078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4085,14 +4087,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -4192,7 +4194,7 @@ dependencies = [ "base64 0.22.1", "chrono", "form_urlencoded", - "getrandom 0.2.16", + "getrandom 0.2.17", "hex", "hmac", "home", @@ -4243,7 +4245,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-native-certs", "rustls-pki-types", "serde", @@ -4289,7 +4291,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -4327,7 +4329,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.12.1", + "indexmap 2.13.0", "log", "memchr", "multer", @@ -4359,7 +4361,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.12.1", + "indexmap 2.13.0", "proc-macro2", "quote", "rocket_http", @@ -4379,7 +4381,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.32", - "indexmap 2.12.1", + "indexmap 2.13.0", "log", "memchr", "pear", @@ -4421,9 +4423,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -4440,6 +4442,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + [[package]] name = "rtoolbox" version = "0.0.3" @@ -4511,15 +4523,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -4530,7 +4542,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -4547,9 +4559,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -4567,9 +4579,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -4793,9 +4805,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -4864,7 +4876,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.1", + "indexmap 2.13.0", "schemars 0.9.0", "schemars 1.2.0", "serde_core", @@ -4966,7 +4978,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] @@ -5050,14 +5062,13 @@ dependencies = [ [[package]] name = "sqlite-wasm-rs" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e98301bf8b0540c7de45ecd760539b9c62f5772aed172f08efba597c11cd5d" +checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" dependencies = [ "cc", - "hashbrown 0.16.1", "js-sys", - "thiserror 2.0.17", + "rsqlite-vfs", "wasm-bindgen", ] @@ -5126,9 +5137,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -5218,11 +5229,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -5238,9 +5249,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -5267,9 +5278,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -5277,22 +5288,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -5334,9 +5345,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -5386,15 +5397,15 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.36", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -5415,9 +5426,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -5441,9 +5452,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "serde_core", "serde_spanned 1.0.4", @@ -5476,7 +5487,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5513,9 +5524,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -5687,9 +5698,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -5717,14 +5728,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -5892,18 +5904,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -5914,11 +5926,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -5927,9 +5940,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5937,9 +5950,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -5950,9 +5963,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -5972,9 +5985,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -6060,9 +6073,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -6461,9 +6474,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -6550,18 +6563,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -6630,9 +6643,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.2" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4a4e8e9dc5c62d159f04fcdbe07f4c3fb710415aab4754bf11505501e3251d" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index ea2d5ecb..9d54590e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,12 +79,12 @@ dashmap = "6.1.0" # Async futures futures = "0.3.31" -tokio = { version = "1.48.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } -tokio-util = { version = "0.7.17", features = ["compat"]} +tokio = { version = "1.49.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } +tokio-util = { version = "0.7.18", features = ["compat"]} # A generic serialization/deserialization framework serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.148" +serde_json = "1.0.149" # A safe, extensible ORM and Query builder # Currently pinned diesel to v2.3.3 as newer version break MySQL/MariaDB compatibility @@ -106,15 +106,15 @@ subtle = "2.6.1" uuid = { version = "1.19.0", features = ["v4"] } # Date and time libraries -chrono = { version = "0.4.42", features = ["clock", "serde"], default-features = false } +chrono = { version = "0.4.43", features = ["clock", "serde"], default-features = false } chrono-tz = "0.10.4" -time = "0.3.44" +time = "0.3.45" # Job scheduler job_scheduler_ng = "2.4.0" # Data encoding library Hex/Base32/Base64 -data-encoding = "2.9.0" +data-encoding = "2.10.0" # JWT library jsonwebtoken = { version = "10.2.0", features = ["use_pem", "rust_crypto"], default-features = false } @@ -133,7 +133,7 @@ webauthn-rs-proto = "0.5.4" webauthn-rs-core = "0.5.4" # Handling of URL's for WebAuthn and favicons -url = "2.5.7" +url = "2.5.8" # Email libraries lettre = { version = "0.11.19", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "hostname", "tracing", "tokio1-rustls", "ring", "rustls-native-certs"], default-features = false } @@ -141,7 +141,7 @@ percent-encoding = "2.3.2" # URL encoding library used for URL's in the emails email_address = "0.2.9" # HTML Template library -handlebars = { version = "6.3.2", features = ["dir_source"] } +handlebars = { version = "6.4.0", features = ["dir_source"] } # HTTP client (Used for favicons, version check, DUO and HIBP API) reqwest = { version = "0.12.28", features = ["rustls-tls", "rustls-tls-native-roots", "stream", "json", "deflate", "gzip", "brotli", "zstd", "socks", "cookies", "charset", "http2", "system-proxy"], default-features = false} @@ -200,7 +200,7 @@ opendal = { version = "0.55.0", features = ["services-fs"], default-features = f anyhow = { version = "1.0.100", optional = true } aws-config = { version = "1.8.12", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } aws-credential-types = { version = "1.2.11", optional = true } -aws-smithy-runtime-api = { version = "1.9.3", optional = true } +aws-smithy-runtime-api = { version = "1.10.0", optional = true } http = { version = "1.4.0", optional = true } reqsign = { version = "0.16.5", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index dd87a9e3..02cd166b 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,6 +1,6 @@ --- -vault_version: "v2025.12.1+build.3" -vault_image_digest: "sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42" +vault_version: "v2025.12.2" +vault_image_digest: "sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7" # Cross Compile Docker Helper Scripts v1.9.0 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 2a6cf9f2..95aae642 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2025.12.1_build.3 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.12.1_build.3 -# [docker.io/vaultwarden/web-vault@sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42] +# $ docker pull docker.io/vaultwarden/web-vault:v2025.12.2 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.12.2 +# [docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42 -# [docker.io/vaultwarden/web-vault:v2025.12.1_build.3] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 +# [docker.io/vaultwarden/web-vault:v2025.12.2] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 AS vault ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 03c0faba..113304b8 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2025.12.1_build.3 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.12.1_build.3 -# [docker.io/vaultwarden/web-vault@sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42] +# $ docker pull docker.io/vaultwarden/web-vault:v2025.12.2 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.12.2 +# [docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42 -# [docker.io/vaultwarden/web-vault:v2025.12.1_build.3] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 +# [docker.io/vaultwarden/web-vault:v2025.12.2] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:bf5aa55dc7bcb99f85d2a88ff44d32cdc832e934a0603fe28e5c3f92904bad42 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 AS vault ########################## Cross Compile Docker Helper Scripts ########################## ## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 9855c56e..933d46d3 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,8 +13,8 @@ path = "src/lib.rs" proc-macro = true [dependencies] -quote = "1.0.42" -syn = "2.0.111" +quote = "1.0.43" +syn = "2.0.114" [lints] workspace = true diff --git a/src/api/web.rs b/src/api/web.rs index 91191968..d1ca0db4 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -239,8 +239,8 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro "jdenticon-3.3.0.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon-3.3.0.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), - "jquery-3.7.1.slim.js" => { - Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.1.slim.js"))) + "jquery-4.0.0.slim.js" => { + Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-4.0.0.slim.js"))) } _ => err!(format!("Static file not found: {filename}")), } diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index af6a9b1e..fe087655 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,10 +4,10 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.5 + * https://datatables.net/download/#bs5/dt-2.3.6 * * Included libraries: - * DataTables 2.3.5 + * DataTables 2.3.6 */ :root { @@ -88,42 +88,42 @@ table.dataTable thead > tr > th:active, table.dataTable thead > tr > td:active { outline: none; } -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before { +table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before { position: absolute; display: block; bottom: 50%; content: "\25B2"; content: "\25B2"/""; } -table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { +table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { position: absolute; display: block; top: 50%; content: "\25BC"; content: "\25BC"/""; } -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { +table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order, +table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order, +table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order, +table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order { position: relative; width: 12px; - height: 24px; -} -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + height: 20px; +} +table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc .dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-desc .dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { left: 0; opacity: 0.125; line-height: 9px; @@ -140,15 +140,15 @@ table.dataTable thead > tr > td.dt-orderable-desc:hover { outline: 2px solid rgba(0, 0, 0, 0.05); outline-offset: -2px; } -table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { +table.dataTable thead > tr > th.dt-ordering-asc .dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc .dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc .dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc .dt-column-order:after { opacity: 0.6; } -table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) span.dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) span.dt-column-order:empty, -table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, -table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { +table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled .dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled .dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) .dt-column-order:empty, +table.dataTable thead > tr > td.sorting_desc_disabled .dt-column-order:after, +table.dataTable thead > tr > td.sorting_asc_disabled .dt-column-order:before { display: none; } table.dataTable thead > tr > th:active, @@ -169,24 +169,24 @@ table.dataTable tfoot > tr > td div.dt-column-footer { align-items: var(--dt-header-align-items); gap: 4px; } -table.dataTable thead > tr > th div.dt-column-header span.dt-column-title, -table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title, -table.dataTable thead > tr > td div.dt-column-header span.dt-column-title, -table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title, -table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title, -table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title, -table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title, -table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title { +table.dataTable thead > tr > th div.dt-column-header .dt-column-title, +table.dataTable thead > tr > th div.dt-column-footer .dt-column-title, +table.dataTable thead > tr > td div.dt-column-header .dt-column-title, +table.dataTable thead > tr > td div.dt-column-footer .dt-column-title, +table.dataTable tfoot > tr > th div.dt-column-header .dt-column-title, +table.dataTable tfoot > tr > th div.dt-column-footer .dt-column-title, +table.dataTable tfoot > tr > td div.dt-column-header .dt-column-title, +table.dataTable tfoot > tr > td div.dt-column-footer .dt-column-title { flex-grow: 1; } -table.dataTable thead > tr > th div.dt-column-header span.dt-column-title:empty, -table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title:empty, -table.dataTable thead > tr > td div.dt-column-header span.dt-column-title:empty, -table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title:empty, -table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title:empty, -table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title:empty, -table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title:empty, -table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title:empty { +table.dataTable thead > tr > th div.dt-column-header .dt-column-title:empty, +table.dataTable thead > tr > th div.dt-column-footer .dt-column-title:empty, +table.dataTable thead > tr > td div.dt-column-header .dt-column-title:empty, +table.dataTable thead > tr > td div.dt-column-footer .dt-column-title:empty, +table.dataTable tfoot > tr > th div.dt-column-header .dt-column-title:empty, +table.dataTable tfoot > tr > th div.dt-column-footer .dt-column-title:empty, +table.dataTable tfoot > tr > td div.dt-column-header .dt-column-title:empty, +table.dataTable tfoot > tr > td div.dt-column-footer .dt-column-title:empty { display: none; } @@ -588,16 +588,16 @@ table.dataTable.table-sm > thead > tr td.dt-ordering-asc, table.dataTable.table-sm > thead > tr td.dt-ordering-desc { padding-right: 0.25rem; } -table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order { +table.dataTable.table-sm > thead > tr th.dt-orderable-asc .dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc .dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc .dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc .dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-orderable-asc .dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-orderable-desc .dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-ordering-asc .dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-ordering-desc .dt-column-order { right: 0.25rem; } -table.dataTable.table-sm > thead > tr th.dt-type-date span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-type-numeric span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-type-date span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-type-numeric span.dt-column-order { +table.dataTable.table-sm > thead > tr th.dt-type-date .dt-column-order, table.dataTable.table-sm > thead > tr th.dt-type-numeric .dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-type-date .dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-type-numeric .dt-column-order { left: 0.25rem; } @@ -606,7 +606,8 @@ div.dt-scroll-head table.table-bordered { } div.table-responsive > div.dt-container > div.row { - margin: 0; + margin-left: 0; + margin-right: 0; } div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child { padding-left: 0; diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index 961af0b4..75f7965e 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,13 +4,13 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.5 + * https://datatables.net/download/#bs5/dt-2.3.6 * * Included libraries: - * DataTables 2.3.5 + * DataTables 2.3.6 */ -/*! DataTables 2.3.5 +/*! DataTables 2.3.6 * © SpryMedia Ltd - datatables.net/license */ @@ -186,7 +186,7 @@ "sDestroyWidth": $this[0].style.width, "sInstance": sId, "sTableId": sId, - colgroup: $('').prependTo(this), + colgroup: $(''), fastData: function (row, column, type) { return _fnGetCellData(oSettings, row, column, type); } @@ -259,6 +259,7 @@ "orderHandler", "titleRow", "typeDetect", + "columnTitleTag", [ "iCookieDuration", "iStateDuration" ], // backwards compat [ "oSearch", "oPreviousSearch" ], [ "aoSearchCols", "aoPreSearchCols" ], @@ -423,7 +424,7 @@ if ( oSettings.caption ) { if ( caption.length === 0 ) { - caption = $('').appendTo( $this ); + caption = $('').prependTo( $this ); } caption.html( oSettings.caption ); @@ -436,6 +437,14 @@ oSettings.captionNode = caption[0]; } + // Place the colgroup element in the correct location for the HTML structure + if (caption.length) { + oSettings.colgroup.insertAfter(caption); + } + else { + oSettings.colgroup.prependTo(oSettings.nTable); + } + if ( thead.length === 0 ) { thead = $('').appendTo($this); } @@ -451,7 +460,7 @@ if ( tfoot.length === 0 ) { // If we are a scrolling table, and no footer has been given, then we need to create // a tfoot element for the caption element to be appended to - tfoot = $('').appendTo($this); + tfoot = $('').insertAfter(thead); } oSettings.nTFoot = tfoot[0]; @@ -516,7 +525,7 @@ * * @type string */ - builder: "bs5/dt-2.3.5", + builder: "bs5/dt-2.3.6", /** * Buttons. For use with the Buttons extension for DataTables. This is @@ -1292,7 +1301,7 @@ }; // Replaceable function in api.util - var _stripHtml = function (input) { + var _stripHtml = function (input, replacement) { if (! input || typeof input !== 'string') { return input; } @@ -1304,7 +1313,7 @@ var previous; - input = input.replace(_re_html, ''); // Complete tags + input = input.replace(_re_html, replacement || ''); // Complete tags // Safety for incomplete script tag - use do / while to ensure that // we get all instances @@ -1769,7 +1778,7 @@ } }, - stripHtml: function (mixed) { + stripHtml: function (mixed, replacement) { var type = typeof mixed; if (type === 'function') { @@ -1777,7 +1786,7 @@ return; } else if (type === 'string') { - return _stripHtml(mixed); + return _stripHtml(mixed, replacement); } return mixed; }, @@ -3379,7 +3388,7 @@ colspan++; } - var titleSpan = $('span.dt-column-title', cell); + var titleSpan = $('.dt-column-title', cell); structure[row][column] = { cell: cell, @@ -4093,8 +4102,8 @@ } // Wrap the column title so we can write to it in future - if ( $('span.dt-column-title', cell).length === 0) { - $('') + if ( $('.dt-column-title', cell).length === 0) { + $(document.createElement(settings.columnTitleTag)) .addClass('dt-column-title') .append(cell.childNodes) .appendTo(cell); @@ -4105,9 +4114,9 @@ isHeader && jqCell.filter(':not([data-dt-order=disable])').length !== 0 && jqCell.parent(':not([data-dt-order=disable])').length !== 0 && - $('span.dt-column-order', cell).length === 0 + $('.dt-column-order', cell).length === 0 ) { - $('') + $(document.createElement(settings.columnTitleTag)) .addClass('dt-column-order') .appendTo(cell); } @@ -4116,7 +4125,7 @@ // layout for those elements var headerFooter = isHeader ? 'header' : 'footer'; - if ( $('span.dt-column-' + headerFooter, cell).length === 0) { + if ( $('div.dt-column-' + headerFooter, cell).length === 0) { $('
') .addClass('dt-column-' + headerFooter) .append(cell.childNodes) @@ -4273,6 +4282,10 @@ // Custom Ajax option to submit the parameters as a JSON string if (baseAjax.submitAs === 'json' && typeof data === 'object') { baseAjax.data = JSON.stringify(data); + + if (!baseAjax.contentType) { + baseAjax.contentType = 'application/json; charset=utf-8'; + } } if (typeof ajax === 'function') { @@ -5531,7 +5544,7 @@ var autoClass = _ext.type.className[column.sType]; var padding = column.sContentPadding || (scrollX ? '-' : ''); var text = longest + padding; - var insert = longest.indexOf('<') === -1 + var insert = longest.indexOf('<') === -1 && longest.indexOf('&') === -1 ? document.createTextNode(text) : text @@ -5719,15 +5732,20 @@ .replace(/id=".*?"/g, '') .replace(/name=".*?"/g, ''); - var s = _stripHtml(cellString) + // Don't want Javascript at all in these calculation cells. + cellString = cellString.replace(//gi, ' '); + + var noHtml = _stripHtml(cellString, ' ') .replace( / /g, ' ' ); + // The length is calculated on the text only, but we keep the HTML + // in the string so it can be used in the calculation table collection.push({ - str: s, - len: s.length + str: cellString, + len: noHtml.length }); - allStrings.push(s); + allStrings.push(noHtml); } // Order and then cut down to the size we need @@ -8782,7 +8800,7 @@ // Automatic - find the _last_ unique cell from the top that is not empty (last for // backwards compatibility) for (var i=0 ; i 6 ? h - 6 : h; + return decToHex(255 * ( + h < 1 ? m1 + (m2 - m1) * h : + h < 3 ? m2 : + h < 4 ? m1 + (m2 - m1) * (4 - h) : + m1)); +} + +/** + * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported. + * @returns {string} + */ +function parseColor(color) { + if (/^#[0-9a-f]{3,8}$/i.test(color)) { + var result; + var colorLength = color.length; + + if (colorLength < 6) { + var r = color[1], + g = color[2], + b = color[3], + a = color[4] || ""; + result = "#" + r + r + g + g + b + b + a + a; + } + if (colorLength == 7 || colorLength > 8) { + result = color; + } + + return result; + } +} + +/** + * Converts a hexadecimal color to a CSS3 compatible color. + * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA" + * @returns {string} + */ +function toCss3Color(hexColor) { + var a = parseHex(hexColor, 7, 2); + var result; + + if (isNaN(a)) { + result = hexColor; + } else { + var r = parseHex(hexColor, 1, 2), + g = parseHex(hexColor, 3, 2), + b = parseHex(hexColor, 5, 2); + result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")"; + } + + return result; +} + +/** + * Converts an HSL color to a hexadecimal RGB color. + * @param {number} hue Hue in range [0, 1] + * @param {number} saturation Saturation in range [0, 1] + * @param {number} lightness Lightness in range [0, 1] + * @returns {string} + */ +function hsl(hue, saturation, lightness) { + // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color + var result; + + if (saturation == 0) { + var partialHex = decToHex(lightness * 255); + result = partialHex + partialHex + partialHex; + } + else { + var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation, + m1 = lightness * 2 - m2; + result = + hueToRgb(m1, m2, hue * 6 + 2) + + hueToRgb(m1, m2, hue * 6) + + hueToRgb(m1, m2, hue * 6 - 2); + } + + return "#" + result; } -function decToHex(v) { - v |= 0; // Ensure integer value - return v < 0 ? "00" : - v < 16 ? "0" + v.toString(16) : - v < 256 ? v.toString(16) : - "ff"; -} - -function hueToRgb(m1, m2, h) { - h = h < 0 ? h + 6 : h > 6 ? h - 6 : h; - return decToHex(255 * ( - h < 1 ? m1 + (m2 - m1) * h : - h < 3 ? m2 : - h < 4 ? m1 + (m2 - m1) * (4 - h) : - m1)); -} - -/** - * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported. - * @returns {string} - */ -function parseColor(color) { - if (/^#[0-9a-f]{3,8}$/i.test(color)) { - var result; - var colorLength = color.length; - - if (colorLength < 6) { - var r = color[1], - g = color[2], - b = color[3], - a = color[4] || ""; - result = "#" + r + r + g + g + b + b + a + a; - } - if (colorLength == 7 || colorLength > 8) { - result = color; - } - - return result; - } -} - -/** - * Converts a hexadecimal color to a CSS3 compatible color. - * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA" - * @returns {string} - */ -function toCss3Color(hexColor) { - var a = parseHex(hexColor, 7, 2); - var result; - - if (isNaN(a)) { - result = hexColor; - } else { - var r = parseHex(hexColor, 1, 2), - g = parseHex(hexColor, 3, 2), - b = parseHex(hexColor, 5, 2); - result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")"; - } - - return result; -} - -/** - * Converts an HSL color to a hexadecimal RGB color. - * @param {number} hue Hue in range [0, 1] - * @param {number} saturation Saturation in range [0, 1] - * @param {number} lightness Lightness in range [0, 1] - * @returns {string} - */ -function hsl(hue, saturation, lightness) { - // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color - var result; - - if (saturation == 0) { - var partialHex = decToHex(lightness * 255); - result = partialHex + partialHex + partialHex; - } - else { - var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation, - m1 = lightness * 2 - m2; - result = - hueToRgb(m1, m2, hue * 6 + 2) + - hueToRgb(m1, m2, hue * 6) + - hueToRgb(m1, m2, hue * 6 - 2); - } - - return "#" + result; -} - -/** - * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues - * @param {number} hue Hue in range [0, 1] - * @param {number} saturation Saturation in range [0, 1] - * @param {number} lightness Lightness in range [0, 1] - * @returns {string} - */ -function correctedHsl(hue, saturation, lightness) { - // The corrector specifies the perceived middle lightness for each hue - var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ], - corrector = correctors[(hue * 6 + 0.5) | 0]; - - // Adjust the input lightness relative to the corrector - lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2; - - return hsl(hue, saturation, lightness); +/** + * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues + * @param {number} hue Hue in range [0, 1] + * @param {number} saturation Saturation in range [0, 1] + * @param {number} lightness Lightness in range [0, 1] + * @returns {string} + */ +function correctedHsl(hue, saturation, lightness) { + // The corrector specifies the perceived middle lightness for each hue + var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ], + corrector = correctors[(hue * 6 + 0.5) | 0]; + + // Adjust the input lightness relative to the corrector + lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2; + + return hsl(hue, saturation, lightness); } -/* global umdGlobal */ - -// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for -// backward compatibility. +/* global umdGlobal */ + +// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for +// backward compatibility. var GLOBAL = umdGlobal; -/** - * @typedef {Object} ParsedConfiguration - * @property {number} colorSaturation - * @property {number} grayscaleSaturation - * @property {string} backColor - * @property {number} iconPadding - * @property {function(number):number} hue - * @property {function(number):number} colorLightness - * @property {function(number):number} grayscaleLightness - */ - -var CONFIG_PROPERTIES = { - G/*GLOBAL*/: "jdenticon_config", - n/*MODULE*/: "config", -}; - -var rootConfigurationHolder = {}; - -/** - * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console - * when it is being used. - * @param {!Object} rootObject - */ -function defineConfigProperty(rootObject) { - rootConfigurationHolder = rootObject; -} - -/** - * Sets a new icon style configuration. The new configuration is not merged with the previous one. * - * @param {Object} newConfiguration - New configuration object. - */ -function configure(newConfiguration) { - if (arguments.length) { - rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration; - } - return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/]; -} - -/** - * Gets the normalized current Jdenticon color configuration. Missing fields have default values. - * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A - * local configuration overrides the global configuration in it entirety. This parameter can for backward - * compatibility also contain a padding value. A padding value only overrides the global padding, not the - * entire global configuration. - * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor - * explicitly to the API method. - * @returns {ParsedConfiguration} - */ -function getConfiguration(paddingOrLocalConfig, defaultPadding) { - var configObject = - typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig || - rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] || - GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] || - { }, - - lightnessConfig = configObject["lightness"] || { }, - - // In versions < 2.1.0 there was no grayscale saturation - - // saturation was the color saturation. - saturation = configObject["saturation"] || { }, - colorSaturation = "color" in saturation ? saturation["color"] : saturation, - grayscaleSaturation = saturation["grayscale"], - - backColor = configObject["backColor"], - padding = configObject["padding"]; - - /** - * Creates a lightness range. - */ - function lightness(configName, defaultRange) { - var range = lightnessConfig[configName]; - - // Check if the lightness range is an array-like object. This way we ensure the - // array contain two values at the same time. - if (!(range && range.length > 1)) { - range = defaultRange; - } - - /** - * Gets a lightness relative the specified value in the specified lightness range. - */ - return function (value) { - value = range[0] + value * (range[1] - range[0]); - return value < 0 ? 0 : value > 1 ? 1 : value; - }; - } - - /** - * Gets a hue allowed by the configured hue restriction, - * provided the originally computed hue. - */ - function hueFunction(originalHue) { - var hueConfig = configObject["hues"]; - var hue; - - // Check if 'hues' is an array-like object. This way we also ensure that - // the array is not empty, which would mean no hue restriction. - if (hueConfig && hueConfig.length > 0) { - // originalHue is in the range [0, 1] - // Multiply with 0.999 to change the range to [0, 1) and then truncate the index. - hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)]; - } - - return typeof hue == "number" ? - - // A hue was specified. We need to convert the hue from - // degrees on any turn - e.g. 746° is a perfectly valid hue - - // to turns in the range [0, 1). - ((((hue / 360) % 1) + 1) % 1) : - - // No hue configured => use original hue - originalHue; - } - - return { - X/*hue*/: hueFunction, - p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5, - H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0, - q/*colorLightness*/: lightness("color", [0.4, 0.8]), - I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]), - J/*backColor*/: parseColor(backColor), - Y/*iconPadding*/: - typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig : - typeof padding == "number" ? padding : - defaultPadding - } +/** + * @typedef {Object} ParsedConfiguration + * @property {number} colorSaturation + * @property {number} grayscaleSaturation + * @property {string} backColor + * @property {number} iconPadding + * @property {function(number):number} hue + * @property {function(number):number} colorLightness + * @property {function(number):number} grayscaleLightness + */ + +var CONFIG_PROPERTIES = { + G/*GLOBAL*/: "jdenticon_config", + n/*MODULE*/: "config", +}; + +var rootConfigurationHolder = {}; + +/** + * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console + * when it is being used. + * @param {!Object} rootObject + */ +function defineConfigProperty(rootObject) { + rootConfigurationHolder = rootObject; +} + +/** + * Sets a new icon style configuration. The new configuration is not merged with the previous one. * + * @param {Object} newConfiguration - New configuration object. + */ +function configure(newConfiguration) { + if (arguments.length) { + rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration; + } + return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/]; +} + +/** + * Gets the normalized current Jdenticon color configuration. Missing fields have default values. + * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A + * local configuration overrides the global configuration in it entirety. This parameter can for backward + * compatibility also contain a padding value. A padding value only overrides the global padding, not the + * entire global configuration. + * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor + * explicitly to the API method. + * @returns {ParsedConfiguration} + */ +function getConfiguration(paddingOrLocalConfig, defaultPadding) { + var configObject = + typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig || + rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] || + GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] || + { }, + + lightnessConfig = configObject["lightness"] || { }, + + // In versions < 2.1.0 there was no grayscale saturation - + // saturation was the color saturation. + saturation = configObject["saturation"] || { }, + colorSaturation = "color" in saturation ? saturation["color"] : saturation, + grayscaleSaturation = saturation["grayscale"], + + backColor = configObject["backColor"], + padding = configObject["padding"]; + + /** + * Creates a lightness range. + */ + function lightness(configName, defaultRange) { + var range = lightnessConfig[configName]; + + // Check if the lightness range is an array-like object. This way we ensure the + // array contain two values at the same time. + if (!(range && range.length > 1)) { + range = defaultRange; + } + + /** + * Gets a lightness relative the specified value in the specified lightness range. + */ + return function (value) { + value = range[0] + value * (range[1] - range[0]); + return value < 0 ? 0 : value > 1 ? 1 : value; + }; + } + + /** + * Gets a hue allowed by the configured hue restriction, + * provided the originally computed hue. + */ + function hueFunction(originalHue) { + var hueConfig = configObject["hues"]; + var hue; + + // Check if 'hues' is an array-like object. This way we also ensure that + // the array is not empty, which would mean no hue restriction. + if (hueConfig && hueConfig.length > 0) { + // originalHue is in the range [0, 1] + // Multiply with 0.999 to change the range to [0, 1) and then truncate the index. + hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)]; + } + + return typeof hue == "number" ? + + // A hue was specified. We need to convert the hue from + // degrees on any turn - e.g. 746° is a perfectly valid hue - + // to turns in the range [0, 1). + ((((hue / 360) % 1) + 1) % 1) : + + // No hue configured => use original hue + originalHue; + } + + return { + X/*hue*/: hueFunction, + p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5, + H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0, + q/*colorLightness*/: lightness("color", [0.4, 0.8]), + I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]), + J/*backColor*/: parseColor(backColor), + Y/*iconPadding*/: + typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig : + typeof padding == "number" ? padding : + defaultPadding + } +} + +var ICON_TYPE_SVG = 1; + +var ICON_TYPE_CANVAS = 2; + +var ATTRIBUTES = { + t/*HASH*/: "data-jdenticon-hash", + o/*VALUE*/: "data-jdenticon-value" +}; + +var IS_RENDERED_PROPERTY = "jdenticonRendered"; + +var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]"; + +var documentQuerySelectorAll = /** @type {!Function} */ ( + typeof document !== "undefined" && document.querySelectorAll.bind(document)); + +function getIdenticonType(el) { + if (el) { + var tagName = el["tagName"]; + + if (/^svg$/i.test(tagName)) { + return ICON_TYPE_SVG; + } + + if (/^canvas$/i.test(tagName) && "getContext" in el) { + return ICON_TYPE_CANVAS; + } + } } -var ICON_TYPE_SVG = 1; - -var ICON_TYPE_CANVAS = 2; - -var ATTRIBUTES = { - t/*HASH*/: "data-jdenticon-hash", - o/*VALUE*/: "data-jdenticon-value" -}; - -var IS_RENDERED_PROPERTY = "jdenticonRendered"; - -var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]"; - -var documentQuerySelectorAll = /** @type {!Function} */ ( - typeof document !== "undefined" && document.querySelectorAll.bind(document)); - -function getIdenticonType(el) { - if (el) { - var tagName = el["tagName"]; - - if (/^svg$/i.test(tagName)) { - return ICON_TYPE_SVG; - } - - if (/^canvas$/i.test(tagName) && "getContext" in el) { - return ICON_TYPE_CANVAS; - } - } -} - -function whenDocumentIsReady(/** @type {Function} */ callback) { - function loadedHandler() { - document.removeEventListener("DOMContentLoaded", loadedHandler); - window.removeEventListener("load", loadedHandler); - setTimeout(callback, 0); // Give scripts a chance to run - } - - if (typeof document !== "undefined" && - typeof window !== "undefined" && - typeof setTimeout !== "undefined" - ) { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", loadedHandler); - window.addEventListener("load", loadedHandler); - } else { - // Document already loaded. The load events above likely won't be raised - setTimeout(callback, 0); - } - } +function whenDocumentIsReady(/** @type {Function} */ callback) { + function loadedHandler() { + document.removeEventListener("DOMContentLoaded", loadedHandler); + window.removeEventListener("load", loadedHandler); + setTimeout(callback, 0); // Give scripts a chance to run + } + + if (typeof document !== "undefined" && + typeof window !== "undefined" && + typeof setTimeout !== "undefined" + ) { + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", loadedHandler); + window.addEventListener("load", loadedHandler); + } else { + // Document already loaded. The load events above likely won't be raised + setTimeout(callback, 0); + } + } } -function observer(updateCallback) { - if (typeof MutationObserver != "undefined") { - var mutationObserver = new MutationObserver(function onmutation(mutations) { - for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) { - var mutation = mutations[mutationIndex]; - var addedNodes = mutation.addedNodes; - - for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) { - var addedNode = addedNodes[addedNodeIndex]; - - // Skip other types of nodes than element nodes, since they might not support - // the querySelectorAll method => runtime error. - if (addedNode.nodeType == 1) { - if (getIdenticonType(addedNode)) { - updateCallback(addedNode); - } - else { - var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR); - for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) { - updateCallback(icons[iconIndex]); - } - } - } - } - - if (mutation.type == "attributes" && getIdenticonType(mutation.target)) { - updateCallback(mutation.target); - } - } - }); - - mutationObserver.observe(document.body, { - "childList": true, - "attributes": true, - "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"], - "subtree": true, - }); - } +function observer(updateCallback) { + if (typeof MutationObserver != "undefined") { + var mutationObserver = new MutationObserver(function onmutation(mutations) { + for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) { + var mutation = mutations[mutationIndex]; + var addedNodes = mutation.addedNodes; + + for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) { + var addedNode = addedNodes[addedNodeIndex]; + + // Skip other types of nodes than element nodes, since they might not support + // the querySelectorAll method => runtime error. + if (addedNode.nodeType == 1) { + if (getIdenticonType(addedNode)) { + updateCallback(addedNode); + } + else { + var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR); + for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) { + updateCallback(icons[iconIndex]); + } + } + } + } + + if (mutation.type == "attributes" && getIdenticonType(mutation.target)) { + updateCallback(mutation.target); + } + } + }); + + mutationObserver.observe(document.body, { + "childList": true, + "attributes": true, + "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"], + "subtree": true, + }); + } } -/** - * Represents a point. - */ -function Point(x, y) { - this.x = x; - this.y = y; +/** + * Represents a point. + */ +function Point(x, y) { + this.x = x; + this.y = y; } -/** - * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself, - * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly. - */ -function Transform(x, y, size, rotation) { - this.u/*_x*/ = x; - this.v/*_y*/ = y; - this.K/*_size*/ = size; - this.Z/*_rotation*/ = rotation; -} - -/** - * Transforms the specified point based on the translation and rotation specification for this Transform. - * @param {number} x x-coordinate - * @param {number} y y-coordinate - * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. - * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. - */ -Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) { - var right = this.u/*_x*/ + this.K/*_size*/, - bottom = this.v/*_y*/ + this.K/*_size*/, - rotation = this.Z/*_rotation*/; - return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) : - rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) : - rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) : - new Point(this.u/*_x*/ + x, this.v/*_y*/ + y); -}; - +/** + * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself, + * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly. + */ +function Transform(x, y, size, rotation) { + this.u/*_x*/ = x; + this.v/*_y*/ = y; + this.K/*_size*/ = size; + this.Z/*_rotation*/ = rotation; +} + +/** + * Transforms the specified point based on the translation and rotation specification for this Transform. + * @param {number} x x-coordinate + * @param {number} y y-coordinate + * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. + * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. + */ +Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) { + var right = this.u/*_x*/ + this.K/*_size*/, + bottom = this.v/*_y*/ + this.K/*_size*/, + rotation = this.Z/*_rotation*/; + return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) : + rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) : + rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) : + new Point(this.u/*_x*/ + x, this.v/*_y*/ + y); +}; + var NO_TRANSFORM = new Transform(0, 0, 0, 0); - - -/** - * Provides helper functions for rendering common basic shapes. - */ -function Graphics(renderer) { - /** - * @type {Renderer} - * @private - */ - this.M/*_renderer*/ = renderer; - - /** - * @type {Transform} - */ - this.A/*currentTransform*/ = NO_TRANSFORM; -} -var Graphics__prototype = Graphics.prototype; - -/** - * Adds a polygon to the underlying renderer. - * @param {Array} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ] - * @param {boolean=} invert Specifies if the polygon will be inverted. - */ + + +/** + * Provides helper functions for rendering common basic shapes. + */ +function Graphics(renderer) { + /** + * @type {Renderer} + * @private + */ + this.M/*_renderer*/ = renderer; + + /** + * @type {Transform} + */ + this.A/*currentTransform*/ = NO_TRANSFORM; +} +var Graphics__prototype = Graphics.prototype; + +/** + * Adds a polygon to the underlying renderer. + * @param {Array} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ] + * @param {boolean=} invert Specifies if the polygon will be inverted. + */ Graphics__prototype.g/*addPolygon*/ = function addPolygon (points, invert) { var this$1 = this; - - var di = invert ? -2 : 2, - transformedPoints = []; - - for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) { - transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1])); - } - - this.M/*_renderer*/.g/*addPolygon*/(transformedPoints); -}; - -/** - * Adds a polygon to the underlying renderer. - * Source: http://stackoverflow.com/a/2173084 - * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse. - * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse. - * @param {number} size The size of the ellipse. - * @param {boolean=} invert Specifies if the ellipse will be inverted. - */ -Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) { - var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size); - this.M/*_renderer*/.h/*addCircle*/(p, size, invert); -}; - -/** - * Adds a rectangle to the underlying renderer. - * @param {number} x The x-coordinate of the upper left corner of the rectangle. - * @param {number} y The y-coordinate of the upper left corner of the rectangle. - * @param {number} w The width of the rectangle. - * @param {number} h The height of the rectangle. - * @param {boolean=} invert Specifies if the rectangle will be inverted. - */ -Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) { - this.g/*addPolygon*/([ - x, y, - x + w, y, - x + w, y + h, - x, y + h - ], invert); -}; - -/** - * Adds a right triangle to the underlying renderer. - * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle. - * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle. - * @param {number} w The width of the triangle. - * @param {number} h The height of the triangle. - * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle. - * @param {boolean=} invert Specifies if the triangle will be inverted. - */ -Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) { - var points = [ - x + w, y, - x + w, y + h, - x, y + h, - x, y - ]; - points.splice(((r || 0) % 4) * 2, 2); - this.g/*addPolygon*/(points, invert); -}; - -/** - * Adds a rhombus to the underlying renderer. - * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus. - * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus. - * @param {number} w The width of the rhombus. - * @param {number} h The height of the rhombus. - * @param {boolean=} invert Specifies if the rhombus will be inverted. - */ -Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) { - this.g/*addPolygon*/([ - x + w / 2, y, - x + w, y + h / 2, - x + w / 2, y + h, - x, y + h / 2 - ], invert); + + var di = invert ? -2 : 2, + transformedPoints = []; + + for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) { + transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1])); + } + + this.M/*_renderer*/.g/*addPolygon*/(transformedPoints); +}; + +/** + * Adds a polygon to the underlying renderer. + * Source: http://stackoverflow.com/a/2173084 + * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse. + * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse. + * @param {number} size The size of the ellipse. + * @param {boolean=} invert Specifies if the ellipse will be inverted. + */ +Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) { + var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size); + this.M/*_renderer*/.h/*addCircle*/(p, size, invert); +}; + +/** + * Adds a rectangle to the underlying renderer. + * @param {number} x The x-coordinate of the upper left corner of the rectangle. + * @param {number} y The y-coordinate of the upper left corner of the rectangle. + * @param {number} w The width of the rectangle. + * @param {number} h The height of the rectangle. + * @param {boolean=} invert Specifies if the rectangle will be inverted. + */ +Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) { + this.g/*addPolygon*/([ + x, y, + x + w, y, + x + w, y + h, + x, y + h + ], invert); +}; + +/** + * Adds a right triangle to the underlying renderer. + * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle. + * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle. + * @param {number} w The width of the triangle. + * @param {number} h The height of the triangle. + * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle. + * @param {boolean=} invert Specifies if the triangle will be inverted. + */ +Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) { + var points = [ + x + w, y, + x + w, y + h, + x, y + h, + x, y + ]; + points.splice(((r || 0) % 4) * 2, 2); + this.g/*addPolygon*/(points, invert); }; -/** - * @param {number} index - * @param {Graphics} g - * @param {number} cell - * @param {number} positionIndex - */ -function centerShape(index, g, cell, positionIndex) { - index = index % 14; - - var k, m, w, h, inner, outer; - - !index ? ( - k = cell * 0.42, - g.g/*addPolygon*/([ - 0, 0, - cell, 0, - cell, cell - k * 2, - cell - k, cell, - 0, cell - ])) : - - index == 1 ? ( - w = 0 | (cell * 0.5), - h = 0 | (cell * 0.8), - - g.j/*addTriangle*/(cell - w, 0, w, h, 2)) : - - index == 2 ? ( - w = 0 | (cell / 3), - g.i/*addRectangle*/(w, w, cell - w, cell - w)) : - - index == 3 ? ( - inner = cell * 0.1, - // Use fixed outer border widths in small icons to ensure the border is drawn - outer = - cell < 6 ? 1 : - cell < 8 ? 2 : - (0 | (cell * 0.25)), - - inner = - inner > 1 ? (0 | inner) : // large icon => truncate decimals - inner > 0.5 ? 1 : // medium size icon => fixed width - inner, // small icon => anti-aliased border - - g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) : - - index == 4 ? ( - m = 0 | (cell * 0.15), - w = 0 | (cell * 0.5), - g.h/*addCircle*/(cell - w - m, cell - w - m, w)) : - - index == 5 ? ( - inner = cell * 0.1, - outer = inner * 4, - - // Align edge to nearest pixel in large icons - outer > 3 && (outer = 0 | outer), - - g.i/*addRectangle*/(0, 0, cell, cell), - g.g/*addPolygon*/([ - outer, outer, - cell - inner, outer, - outer + (cell - outer - inner) / 2, cell - inner - ], true)) : - - index == 6 ? - g.g/*addPolygon*/([ - 0, 0, - cell, 0, - cell, cell * 0.7, - cell * 0.4, cell * 0.4, - cell * 0.7, cell, - 0, cell - ]) : - - index == 7 ? - g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : - - index == 8 ? ( - g.i/*addRectangle*/(0, 0, cell, cell / 2), - g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2), - g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) : - - index == 9 ? ( - inner = cell * 0.14, - // Use fixed outer border widths in small icons to ensure the border is drawn - outer = - cell < 4 ? 1 : - cell < 6 ? 2 : - (0 | (cell * 0.35)), - - inner = - cell < 8 ? inner : // small icon => anti-aliased border - (0 | inner), // large icon => truncate decimals - - g.i/*addRectangle*/(0, 0, cell, cell), - g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) : - - index == 10 ? ( - inner = cell * 0.12, - outer = inner * 3, - - g.i/*addRectangle*/(0, 0, cell, cell), - g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) : - - index == 11 ? - g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : - - index == 12 ? ( - m = cell * 0.25, - g.i/*addRectangle*/(0, 0, cell, cell), - g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) : - - // 13 - ( - !positionIndex && ( - m = cell * 0.4, w = cell * 1.2, - g.h/*addCircle*/(m, m, w) - ) - ); -} - -/** - * @param {number} index - * @param {Graphics} g - * @param {number} cell - */ -function outerShape(index, g, cell) { - index = index % 4; - - var m; - - !index ? - g.j/*addTriangle*/(0, 0, cell, cell, 0) : - - index == 1 ? - g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) : - - index == 2 ? - g.N/*addRhombus*/(0, 0, cell, cell) : - - // 3 - ( - m = cell / 6, - g.h/*addCircle*/(m, m, cell - 2 * m) - ); +/** + * Adds a rhombus to the underlying renderer. + * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus. + * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus. + * @param {number} w The width of the rhombus. + * @param {number} h The height of the rhombus. + * @param {boolean=} invert Specifies if the rhombus will be inverted. + */ +Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) { + this.g/*addPolygon*/([ + x + w / 2, y, + x + w, y + h / 2, + x + w / 2, y + h, + x, y + h / 2 + ], invert); +}; + +/** + * @param {number} index + * @param {Graphics} g + * @param {number} cell + * @param {number} positionIndex + */ +function centerShape(index, g, cell, positionIndex) { + index = index % 14; + + var k, m, w, h, inner, outer; + + !index ? ( + k = cell * 0.42, + g.g/*addPolygon*/([ + 0, 0, + cell, 0, + cell, cell - k * 2, + cell - k, cell, + 0, cell + ])) : + + index == 1 ? ( + w = 0 | (cell * 0.5), + h = 0 | (cell * 0.8), + + g.j/*addTriangle*/(cell - w, 0, w, h, 2)) : + + index == 2 ? ( + w = 0 | (cell / 3), + g.i/*addRectangle*/(w, w, cell - w, cell - w)) : + + index == 3 ? ( + inner = cell * 0.1, + // Use fixed outer border widths in small icons to ensure the border is drawn + outer = + cell < 6 ? 1 : + cell < 8 ? 2 : + (0 | (cell * 0.25)), + + inner = + inner > 1 ? (0 | inner) : // large icon => truncate decimals + inner > 0.5 ? 1 : // medium size icon => fixed width + inner, // small icon => anti-aliased border + + g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) : + + index == 4 ? ( + m = 0 | (cell * 0.15), + w = 0 | (cell * 0.5), + g.h/*addCircle*/(cell - w - m, cell - w - m, w)) : + + index == 5 ? ( + inner = cell * 0.1, + outer = inner * 4, + + // Align edge to nearest pixel in large icons + outer > 3 && (outer = 0 | outer), + + g.i/*addRectangle*/(0, 0, cell, cell), + g.g/*addPolygon*/([ + outer, outer, + cell - inner, outer, + outer + (cell - outer - inner) / 2, cell - inner + ], true)) : + + index == 6 ? + g.g/*addPolygon*/([ + 0, 0, + cell, 0, + cell, cell * 0.7, + cell * 0.4, cell * 0.4, + cell * 0.7, cell, + 0, cell + ]) : + + index == 7 ? + g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : + + index == 8 ? ( + g.i/*addRectangle*/(0, 0, cell, cell / 2), + g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2), + g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) : + + index == 9 ? ( + inner = cell * 0.14, + // Use fixed outer border widths in small icons to ensure the border is drawn + outer = + cell < 4 ? 1 : + cell < 6 ? 2 : + (0 | (cell * 0.35)), + + inner = + cell < 8 ? inner : // small icon => anti-aliased border + (0 | inner), // large icon => truncate decimals + + g.i/*addRectangle*/(0, 0, cell, cell), + g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) : + + index == 10 ? ( + inner = cell * 0.12, + outer = inner * 3, + + g.i/*addRectangle*/(0, 0, cell, cell), + g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) : + + index == 11 ? + g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : + + index == 12 ? ( + m = cell * 0.25, + g.i/*addRectangle*/(0, 0, cell, cell), + g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) : + + // 13 + ( + !positionIndex && ( + m = cell * 0.4, w = cell * 1.2, + g.h/*addCircle*/(m, m, w) + ) + ); } -/** - * Gets a set of identicon color candidates for a specified hue and config. - * @param {number} hue - * @param {ParsedConfiguration} config - */ -function colorTheme(hue, config) { - hue = config.X/*hue*/(hue); - return [ - // Dark gray - correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)), - // Mid color - correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)), - // Light gray - correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)), - // Light color - correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)), - // Dark color - correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0)) - ]; +/** + * @param {number} index + * @param {Graphics} g + * @param {number} cell + */ +function outerShape(index, g, cell) { + index = index % 4; + + var m; + + !index ? + g.j/*addTriangle*/(0, 0, cell, cell, 0) : + + index == 1 ? + g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) : + + index == 2 ? + g.N/*addRhombus*/(0, 0, cell, cell) : + + // 3 + ( + m = cell / 6, + g.h/*addCircle*/(m, m, cell - 2 * m) + ); } -/** - * Draws an identicon to a specified renderer. - * @param {Renderer} renderer - * @param {string} hash - * @param {Object|number=} config - */ -function iconGenerator(renderer, hash, config) { - var parsedConfig = getConfiguration(config, 0.08); - - // Set background color - if (parsedConfig.J/*backColor*/) { - renderer.m/*setBackground*/(parsedConfig.J/*backColor*/); - } - - // Calculate padding and round to nearest integer - var size = renderer.k/*iconSize*/; - var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0; - size -= padding * 2; - - var graphics = new Graphics(renderer); - - // Calculate cell size and ensure it is an integer - var cell = 0 | (size / 4); - - // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon - var x = 0 | (padding + size / 2 - cell * 2); - var y = 0 | (padding + size / 2 - cell * 2); - - function renderShape(colorIndex, shapes, index, rotationIndex, positions) { - var shapeIndex = parseHex(hash, index, 1); - var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0; - - renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]); - - for (var i = 0; i < positions.length; i++) { - graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4); - shapes(shapeIndex, graphics, cell, i); - } - - renderer.P/*endShape*/(); - } - - // AVAILABLE COLORS - var hue = parseHex(hash, -7) / 0xfffffff, - - // Available colors for this icon - availableColors = colorTheme(hue, parsedConfig), - - // The index of the selected colors - selectedColorIndexes = []; - - var index; - - function isDuplicate(values) { - if (values.indexOf(index) >= 0) { - for (var i = 0; i < values.length; i++) { - if (selectedColorIndexes.indexOf(values[i]) >= 0) { - return true; - } - } - } - } - - for (var i = 0; i < 3; i++) { - index = parseHex(hash, 8 + i, 1) % availableColors.length; - if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo - isDuplicate([2, 3])) { // Disallow light gray and light color combo - index = 1; - } - selectedColorIndexes.push(index); - } - - // ACTUAL RENDERING - // Sides - renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]); - // Corners - renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]); - // Center - renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]); - - renderer.finish(); +/** + * Gets a set of identicon color candidates for a specified hue and config. + * @param {number} hue + * @param {ParsedConfiguration} config + */ +function colorTheme(hue, config) { + hue = config.X/*hue*/(hue); + return [ + // Dark gray + correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)), + // Mid color + correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)), + // Light gray + correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)), + // Light color + correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)), + // Dark color + correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0)) + ]; } -/** - * Computes a SHA1 hash for any value and returns it as a hexadecimal string. - * - * This function is optimized for minimal code size and rather short messages. - * - * @param {string} message - */ -function sha1(message) { - var HASH_SIZE_HALF_BYTES = 40; - var BLOCK_SIZE_WORDS = 16; - - // Variables - // `var` is used to be able to minimize the number of `var` keywords. - var i = 0, - f = 0, - - // Use `encodeURI` to UTF8 encode the message without any additional libraries - // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky - // since `unescape` is deprecated. - urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding - - // This can be changed to a preallocated Uint32Array array for greater performance and larger code size - data = [], - dataSize, - - hashBuffer = [], - - a = 0x67452301, - b = 0xefcdab89, - c = ~a, - d = ~b, - e = 0xc3d2e1f0, - hash = [a, b, c, d, e], - - blockStartIndex = 0, - hexHash = ""; - - /** - * Rotates the value a specified number of bits to the left. - * @param {number} value Value to rotate - * @param {number} shift Bit count to shift. - */ - function rotl(value, shift) { - return (value << shift) | (value >>> (32 - shift)); - } - - // Message data - for ( ; i < urlEncodedMessage.length; f++) { - data[f >> 2] = data[f >> 2] | - ( - ( - urlEncodedMessage[i] == "%" - // Percent encoded byte - ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16) - // Unencoded byte - : urlEncodedMessage.charCodeAt(i++) - ) - - // Read bytes in reverse order (big endian words) - << ((3 - (f & 3)) * 8) - ); - } - - // f is now the length of the utf8 encoded message - // 7 = 8 bytes (64 bit) for message size, -1 to round down - // >> 6 = integer division with block size - dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS; - - // Message size in bits. - // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least - // significant 32 bits are set. -8 is for the '1' bit padding byte. - data[dataSize - 1] = f * 8 - 8; - - // Compute hash - for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) { - for (i = 0; i < 80; i++) { - f = rotl(a, 5) + e + ( - // Ch - i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 : - - // Parity - i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 : - - // Maj - i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc : - - // Parity - (b ^ c ^ d) + 0xca62c1d6 - ) + ( - hashBuffer[i] = i < BLOCK_SIZE_WORDS - // Bitwise OR is used to coerse `undefined` to 0 - ? (data[blockStartIndex + i] | 0) - : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1) - ); - - e = d; - d = c; - c = rotl(b, 30); - b = a; - a = f; - } - - hash[0] = a = ((hash[0] + a) | 0); - hash[1] = b = ((hash[1] + b) | 0); - hash[2] = c = ((hash[2] + c) | 0); - hash[3] = d = ((hash[3] + d) | 0); - hash[4] = e = ((hash[4] + e) | 0); - } - - // Format hex hash - for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) { - hexHash += ( - ( - // Get word (2^3 half-bytes per word) - hash[i >> 3] >>> - - // Append half-bytes in reverse order - ((7 - (i & 7)) * 4) - ) - // Clamp to half-byte - & 0xf - ).toString(16); - } - - return hexHash; +/** + * Draws an identicon to a specified renderer. + * @param {Renderer} renderer + * @param {string} hash + * @param {Object|number=} config + */ +function iconGenerator(renderer, hash, config) { + var parsedConfig = getConfiguration(config, 0.08); + + // Set background color + if (parsedConfig.J/*backColor*/) { + renderer.m/*setBackground*/(parsedConfig.J/*backColor*/); + } + + // Calculate padding and round to nearest integer + var size = renderer.k/*iconSize*/; + var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0; + size -= padding * 2; + + var graphics = new Graphics(renderer); + + // Calculate cell size and ensure it is an integer + var cell = 0 | (size / 4); + + // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon + var x = 0 | (padding + size / 2 - cell * 2); + var y = 0 | (padding + size / 2 - cell * 2); + + function renderShape(colorIndex, shapes, index, rotationIndex, positions) { + var shapeIndex = parseHex(hash, index, 1); + var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0; + + renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]); + + for (var i = 0; i < positions.length; i++) { + graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4); + shapes(shapeIndex, graphics, cell, i); + } + + renderer.P/*endShape*/(); + } + + // AVAILABLE COLORS + var hue = parseHex(hash, -7) / 0xfffffff, + + // Available colors for this icon + availableColors = colorTheme(hue, parsedConfig), + + // The index of the selected colors + selectedColorIndexes = []; + + var index; + + function isDuplicate(values) { + if (values.indexOf(index) >= 0) { + for (var i = 0; i < values.length; i++) { + if (selectedColorIndexes.indexOf(values[i]) >= 0) { + return true; + } + } + } + } + + for (var i = 0; i < 3; i++) { + index = parseHex(hash, 8 + i, 1) % availableColors.length; + if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo + isDuplicate([2, 3])) { // Disallow light gray and light color combo + index = 1; + } + selectedColorIndexes.push(index); + } + + // ACTUAL RENDERING + // Sides + renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]); + // Corners + renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]); + // Center + renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]); + + renderer.finish(); +} + +/** + * Computes a SHA1 hash for any value and returns it as a hexadecimal string. + * + * This function is optimized for minimal code size and rather short messages. + * + * @param {string} message + */ +function sha1(message) { + var HASH_SIZE_HALF_BYTES = 40; + var BLOCK_SIZE_WORDS = 16; + + // Variables + // `var` is used to be able to minimize the number of `var` keywords. + var i = 0, + f = 0, + + // Use `encodeURI` to UTF8 encode the message without any additional libraries + // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky + // since `unescape` is deprecated. + urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding + + // This can be changed to a preallocated Uint32Array array for greater performance and larger code size + data = [], + dataSize, + + hashBuffer = [], + + a = 0x67452301, + b = 0xefcdab89, + c = ~a, + d = ~b, + e = 0xc3d2e1f0, + hash = [a, b, c, d, e], + + blockStartIndex = 0, + hexHash = ""; + + /** + * Rotates the value a specified number of bits to the left. + * @param {number} value Value to rotate + * @param {number} shift Bit count to shift. + */ + function rotl(value, shift) { + return (value << shift) | (value >>> (32 - shift)); + } + + // Message data + for ( ; i < urlEncodedMessage.length; f++) { + data[f >> 2] = data[f >> 2] | + ( + ( + urlEncodedMessage[i] == "%" + // Percent encoded byte + ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16) + // Unencoded byte + : urlEncodedMessage.charCodeAt(i++) + ) + + // Read bytes in reverse order (big endian words) + << ((3 - (f & 3)) * 8) + ); + } + + // f is now the length of the utf8 encoded message + // 7 = 8 bytes (64 bit) for message size, -1 to round down + // >> 6 = integer division with block size + dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS; + + // Message size in bits. + // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least + // significant 32 bits are set. -8 is for the '1' bit padding byte. + data[dataSize - 1] = f * 8 - 8; + + // Compute hash + for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) { + for (i = 0; i < 80; i++) { + f = rotl(a, 5) + e + ( + // Ch + i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 : + + // Parity + i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 : + + // Maj + i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc : + + // Parity + (b ^ c ^ d) + 0xca62c1d6 + ) + ( + hashBuffer[i] = i < BLOCK_SIZE_WORDS + // Bitwise OR is used to coerse `undefined` to 0 + ? (data[blockStartIndex + i] | 0) + : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1) + ); + + e = d; + d = c; + c = rotl(b, 30); + b = a; + a = f; + } + + hash[0] = a = ((hash[0] + a) | 0); + hash[1] = b = ((hash[1] + b) | 0); + hash[2] = c = ((hash[2] + c) | 0); + hash[3] = d = ((hash[3] + d) | 0); + hash[4] = e = ((hash[4] + e) | 0); + } + + // Format hex hash + for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) { + hexHash += ( + ( + // Get word (2^3 half-bytes per word) + hash[i >> 3] >>> + + // Append half-bytes in reverse order + ((7 - (i & 7)) * 4) + ) + // Clamp to half-byte + & 0xf + ).toString(16); + } + + return hexHash; +} + +/** + * Inputs a value that might be a valid hash string for Jdenticon and returns it + * if it is determined valid, otherwise a falsy value is returned. + */ +function isValidHash(hashCandidate) { + return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate; } -/** - * Inputs a value that might be a valid hash string for Jdenticon and returns it - * if it is determined valid, otherwise a falsy value is returned. - */ -function isValidHash(hashCandidate) { - return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate; -} - -/** - * Computes a hash for the specified value. Currently SHA1 is used. This function - * always returns a valid hash. - */ -function computeHash(value) { - return sha1(value == null ? "" : "" + value); +/** + * Computes a hash for the specified value. Currently SHA1 is used. This function + * always returns a valid hash. + */ +function computeHash(value) { + return sha1(value == null ? "" : "" + value); +} + + + +/** + * Renderer redirecting drawing commands to a canvas context. + * @implements {Renderer} + */ +function CanvasRenderer(ctx, iconSize) { + var canvas = ctx.canvas; + var width = canvas.width; + var height = canvas.height; + + ctx.save(); + + if (!iconSize) { + iconSize = Math.min(width, height); + + ctx.translate( + ((width - iconSize) / 2) | 0, + ((height - iconSize) / 2) | 0); + } + + /** + * @private + */ + this.l/*_ctx*/ = ctx; + this.k/*iconSize*/ = iconSize; + + ctx.clearRect(0, 0, iconSize, iconSize); } +var CanvasRenderer__prototype = CanvasRenderer.prototype; + +/** + * Fills the background with the specified color. + * @param {string} fillColor Fill color on the format #rrggbb[aa]. + */ +CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) { + var ctx = this.l/*_ctx*/; + var iconSize = this.k/*iconSize*/; + + ctx.fillStyle = toCss3Color(fillColor); + ctx.fillRect(0, 0, iconSize, iconSize); +}; + +/** + * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. + * @param {string} fillColor Fill color on format #rrggbb[aa]. + */ +CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) { + var ctx = this.l/*_ctx*/; + ctx.fillStyle = toCss3Color(fillColor); + ctx.beginPath(); +}; + +/** + * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas. + */ +CanvasRenderer__prototype.P/*endShape*/ = function endShape () { + this.l/*_ctx*/.fill(); +}; + +/** + * Adds a polygon to the rendering queue. + * @param points An array of Point objects. + */ +CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) { + var ctx = this.l/*_ctx*/; + ctx.moveTo(points[0].x, points[0].y); + for (var i = 1; i < points.length; i++) { + ctx.lineTo(points[i].x, points[i].y); + } + ctx.closePath(); +}; + +/** + * Adds a circle to the rendering queue. + * @param {Point} point The upper left corner of the circle bounding box. + * @param {number} diameter The diameter of the circle. + * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). + */ +CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { + var ctx = this.l/*_ctx*/, + radius = diameter / 2; + ctx.moveTo(point.x + radius, point.y + radius); + ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise); + ctx.closePath(); +}; - - -/** - * Renderer redirecting drawing commands to a canvas context. - * @implements {Renderer} - */ -function CanvasRenderer(ctx, iconSize) { - var canvas = ctx.canvas; - var width = canvas.width; - var height = canvas.height; - - ctx.save(); - - if (!iconSize) { - iconSize = Math.min(width, height); - - ctx.translate( - ((width - iconSize) / 2) | 0, - ((height - iconSize) / 2) | 0); - } - - /** - * @private - */ - this.l/*_ctx*/ = ctx; - this.k/*iconSize*/ = iconSize; - - ctx.clearRect(0, 0, iconSize, iconSize); -} -var CanvasRenderer__prototype = CanvasRenderer.prototype; - -/** - * Fills the background with the specified color. - * @param {string} fillColor Fill color on the format #rrggbb[aa]. - */ -CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) { - var ctx = this.l/*_ctx*/; - var iconSize = this.k/*iconSize*/; - - ctx.fillStyle = toCss3Color(fillColor); - ctx.fillRect(0, 0, iconSize, iconSize); -}; - -/** - * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. - * @param {string} fillColor Fill color on format #rrggbb[aa]. - */ -CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) { - var ctx = this.l/*_ctx*/; - ctx.fillStyle = toCss3Color(fillColor); - ctx.beginPath(); -}; - -/** - * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas. - */ -CanvasRenderer__prototype.P/*endShape*/ = function endShape () { - this.l/*_ctx*/.fill(); -}; - -/** - * Adds a polygon to the rendering queue. - * @param points An array of Point objects. - */ -CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) { - var ctx = this.l/*_ctx*/; - ctx.moveTo(points[0].x, points[0].y); - for (var i = 1; i < points.length; i++) { - ctx.lineTo(points[i].x, points[i].y); - } - ctx.closePath(); -}; - -/** - * Adds a circle to the rendering queue. - * @param {Point} point The upper left corner of the circle bounding box. - * @param {number} diameter The diameter of the circle. - * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). - */ -CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { - var ctx = this.l/*_ctx*/, - radius = diameter / 2; - ctx.moveTo(point.x + radius, point.y + radius); - ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise); - ctx.closePath(); -}; - -/** - * Called when the icon has been completely drawn. - */ -CanvasRenderer__prototype.finish = function finish () { - this.l/*_ctx*/.restore(); +/** + * Called when the icon has been completely drawn. + */ +CanvasRenderer__prototype.finish = function finish () { + this.l/*_ctx*/.restore(); }; -/** - * Draws an identicon to a context. - * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0). - * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. - * @param {number} size - Icon size in pixels. - * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any - * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be - * specified in place of a configuration object. - */ -function drawIcon(ctx, hashOrValue, size, config) { - if (!ctx) { - throw new Error("No canvas specified."); - } - - iconGenerator(new CanvasRenderer(ctx, size), - isValidHash(hashOrValue) || computeHash(hashOrValue), - config); - - var canvas = ctx.canvas; - if (canvas) { - canvas[IS_RENDERED_PROPERTY] = true; - } +/** + * Draws an identicon to a context. + * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0). + * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. + * @param {number} size - Icon size in pixels. + * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any + * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be + * specified in place of a configuration object. + */ +function drawIcon(ctx, hashOrValue, size, config) { + if (!ctx) { + throw new Error("No canvas specified."); + } + + iconGenerator(new CanvasRenderer(ctx, size), + isValidHash(hashOrValue) || computeHash(hashOrValue), + config); + + var canvas = ctx.canvas; + if (canvas) { + canvas[IS_RENDERED_PROPERTY] = true; + } +} + +/** + * Prepares a measure to be used as a measure in an SVG path, by + * rounding the measure to a single decimal. This reduces the file + * size of the generated SVG with more than 50% in some cases. + */ +function svgValue(value) { + return ((value * 10 + 0.5) | 0) / 10; +} + +/** + * Represents an SVG path element. + */ +function SvgPath() { + /** + * This property holds the data string (path.d) of the SVG path. + * @type {string} + */ + this.B/*dataString*/ = ""; } +var SvgPath__prototype = SvgPath.prototype; -/** - * Prepares a measure to be used as a measure in an SVG path, by - * rounding the measure to a single decimal. This reduces the file - * size of the generated SVG with more than 50% in some cases. - */ -function svgValue(value) { - return ((value * 10 + 0.5) | 0) / 10; -} - -/** - * Represents an SVG path element. - */ -function SvgPath() { - /** - * This property holds the data string (path.d) of the SVG path. - * @type {string} - */ - this.B/*dataString*/ = ""; -} -var SvgPath__prototype = SvgPath.prototype; - -/** - * Adds a polygon with the current fill color to the SVG path. - * @param points An array of Point objects. - */ -SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) { - var dataString = ""; - for (var i = 0; i < points.length; i++) { - dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y); - } - this.B/*dataString*/ += dataString + "Z"; -}; - -/** - * Adds a circle with the current fill color to the SVG path. - * @param {Point} point The upper left corner of the circle bounding box. - * @param {number} diameter The diameter of the circle. - * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). - */ -SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { - var sweepFlag = counterClockwise ? 0 : 1, - svgRadius = svgValue(diameter / 2), - svgDiameter = svgValue(diameter), - svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " "; - - this.B/*dataString*/ += - "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) + - svgArc + svgDiameter + ",0" + - svgArc + (-svgDiameter) + ",0"; +/** + * Adds a polygon with the current fill color to the SVG path. + * @param points An array of Point objects. + */ +SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) { + var dataString = ""; + for (var i = 0; i < points.length; i++) { + dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y); + } + this.B/*dataString*/ += dataString + "Z"; }; - - -/** - * Renderer producing SVG output. - * @implements {Renderer} - */ -function SvgRenderer(target) { - /** - * @type {SvgPath} - * @private - */ - this.C/*_path*/; - - /** - * @type {Object.} - * @private - */ - this.D/*_pathsByColor*/ = { }; - - /** - * @type {SvgElement|SvgWriter} - * @private - */ - this.R/*_target*/ = target; - - /** - * @type {number} - */ - this.k/*iconSize*/ = target.k/*iconSize*/; -} -var SvgRenderer__prototype = SvgRenderer.prototype; - -/** - * Fills the background with the specified color. - * @param {string} fillColor Fill color on the format #rrggbb[aa]. - */ -SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) { - var match = /^(#......)(..)?/.exec(fillColor), - opacity = match[2] ? parseHex(match[2], 0) / 255 : 1; - this.R/*_target*/.m/*setBackground*/(match[1], opacity); -}; - -/** - * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. - * @param {string} color Fill color on format #xxxxxx. - */ -SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) { - this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath()); -}; - -/** - * Marks the end of the currently drawn shape. - */ -SvgRenderer__prototype.P/*endShape*/ = function endShape () { }; - -/** - * Adds a polygon with the current fill color to the SVG. - * @param points An array of Point objects. - */ -SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) { - this.C/*_path*/.g/*addPolygon*/(points); -}; - -/** - * Adds a circle with the current fill color to the SVG. - * @param {Point} point The upper left corner of the circle bounding box. - * @param {number} diameter The diameter of the circle. - * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). - */ -SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { - this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise); -}; - -/** - * Called when the icon has been completely drawn. - */ +/** + * Adds a circle with the current fill color to the SVG path. + * @param {Point} point The upper left corner of the circle bounding box. + * @param {number} diameter The diameter of the circle. + * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). + */ +SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { + var sweepFlag = counterClockwise ? 0 : 1, + svgRadius = svgValue(diameter / 2), + svgDiameter = svgValue(diameter), + svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " "; + + this.B/*dataString*/ += + "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) + + svgArc + svgDiameter + ",0" + + svgArc + (-svgDiameter) + ",0"; +}; + + + +/** + * Renderer producing SVG output. + * @implements {Renderer} + */ +function SvgRenderer(target) { + /** + * @type {SvgPath} + * @private + */ + this.C/*_path*/; + + /** + * @type {Object.} + * @private + */ + this.D/*_pathsByColor*/ = { }; + + /** + * @type {SvgElement|SvgWriter} + * @private + */ + this.R/*_target*/ = target; + + /** + * @type {number} + */ + this.k/*iconSize*/ = target.k/*iconSize*/; +} +var SvgRenderer__prototype = SvgRenderer.prototype; + +/** + * Fills the background with the specified color. + * @param {string} fillColor Fill color on the format #rrggbb[aa]. + */ +SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) { + var match = /^(#......)(..)?/.exec(fillColor), + opacity = match[2] ? parseHex(match[2], 0) / 255 : 1; + this.R/*_target*/.m/*setBackground*/(match[1], opacity); +}; + +/** + * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. + * @param {string} color Fill color on format #xxxxxx. + */ +SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) { + this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath()); +}; + +/** + * Marks the end of the currently drawn shape. + */ +SvgRenderer__prototype.P/*endShape*/ = function endShape () { }; + +/** + * Adds a polygon with the current fill color to the SVG. + * @param points An array of Point objects. + */ +SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) { + this.C/*_path*/.g/*addPolygon*/(points); +}; + +/** + * Adds a circle with the current fill color to the SVG. + * @param {Point} point The upper left corner of the circle bounding box. + * @param {number} diameter The diameter of the circle. + * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). + */ +SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) { + this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise); +}; + +/** + * Called when the icon has been completely drawn. + */ SvgRenderer__prototype.finish = function finish () { var this$1 = this; - - var pathsByColor = this.D/*_pathsByColor*/; - for (var color in pathsByColor) { - // hasOwnProperty cannot be shadowed in pathsByColor - // eslint-disable-next-line no-prototype-builtins - if (pathsByColor.hasOwnProperty(color)) { - this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/); - } - } + + var pathsByColor = this.D/*_pathsByColor*/; + for (var color in pathsByColor) { + // hasOwnProperty cannot be shadowed in pathsByColor + // eslint-disable-next-line no-prototype-builtins + if (pathsByColor.hasOwnProperty(color)) { + this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/); + } + } +}; + +var SVG_CONSTANTS = { + T/*XMLNS*/: "http://www.w3.org/2000/svg", + U/*WIDTH*/: "width", + V/*HEIGHT*/: "height", +}; + +/** + * Renderer producing SVG output. + */ +function SvgWriter(iconSize) { + /** + * @type {number} + */ + this.k/*iconSize*/ = iconSize; + + /** + * @type {string} + * @private + */ + this.F/*_s*/ = + ''; +} +var SvgWriter__prototype = SvgWriter.prototype; + +/** + * Fills the background with the specified color. + * @param {string} fillColor Fill color on the format #rrggbb. + * @param {number} opacity Opacity in the range [0.0, 1.0]. + */ +SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) { + if (opacity) { + this.F/*_s*/ += ''; + } }; -var SVG_CONSTANTS = { - T/*XMLNS*/: "http://www.w3.org/2000/svg", - U/*WIDTH*/: "width", - V/*HEIGHT*/: "height", +/** + * Writes a path to the SVG string. + * @param {string} color Fill color on format #rrggbb. + * @param {string} dataString The SVG path data string. + */ +SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) { + this.F/*_s*/ += ''; }; -/** - * Renderer producing SVG output. - */ -function SvgWriter(iconSize) { - /** - * @type {number} - */ - this.k/*iconSize*/ = iconSize; - - /** - * @type {string} - * @private - */ - this.F/*_s*/ = - ''; -} -var SvgWriter__prototype = SvgWriter.prototype; - -/** - * Fills the background with the specified color. - * @param {string} fillColor Fill color on the format #rrggbb. - * @param {number} opacity Opacity in the range [0.0, 1.0]. - */ -SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) { - if (opacity) { - this.F/*_s*/ += ''; - } -}; - -/** - * Writes a path to the SVG string. - * @param {string} color Fill color on format #rrggbb. - * @param {string} dataString The SVG path data string. - */ -SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) { - this.F/*_s*/ += ''; -}; - -/** - * Gets the rendered image as an SVG string. - */ -SvgWriter__prototype.toString = function toString () { - return this.F/*_s*/ + ""; +/** + * Gets the rendered image as an SVG string. + */ +SvgWriter__prototype.toString = function toString () { + return this.F/*_s*/ + ""; }; -/** - * Draws an identicon as an SVG string. - * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. - * @param {number} size - Icon size in pixels. - * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any - * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be - * specified in place of a configuration object. - * @returns {string} SVG string - */ -function toSvg(hashOrValue, size, config) { - var writer = new SvgWriter(size); - iconGenerator(new SvgRenderer(writer), - isValidHash(hashOrValue) || computeHash(hashOrValue), - config); - return writer.toString(); +/** + * Draws an identicon as an SVG string. + * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. + * @param {number} size - Icon size in pixels. + * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any + * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be + * specified in place of a configuration object. + * @returns {string} SVG string + */ +function toSvg(hashOrValue, size, config) { + var writer = new SvgWriter(size); + iconGenerator(new SvgRenderer(writer), + isValidHash(hashOrValue) || computeHash(hashOrValue), + config); + return writer.toString(); } -/** - * Creates a new element and adds it to the specified parent. - * @param {Element} parentNode - * @param {string} name - * @param {...(string|number)} keyValuePairs - */ +/** + * Creates a new element and adds it to the specified parent. + * @param {Element} parentNode + * @param {string} name + * @param {...(string|number)} keyValuePairs + */ function SvgElement_append(parentNode, name) { var keyValuePairs = [], len = arguments.length - 2; while ( len-- > 0 ) keyValuePairs[ len ] = arguments[ len + 2 ]; - - var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name); - - for (var i = 0; i + 1 < keyValuePairs.length; i += 2) { - el.setAttribute( - /** @type {string} */(keyValuePairs[i]), - /** @type {string} */(keyValuePairs[i + 1]) - ); - } - - parentNode.appendChild(el); -} - - -/** - * Renderer producing SVG output. - */ -function SvgElement(element) { - // Don't use the clientWidth and clientHeight properties on SVG elements - // since Firefox won't serve a proper value of these properties on SVG - // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811) - // Instead use 100px as a hardcoded size (the svg viewBox will rescale - // the icon to the correct dimensions) - var iconSize = this.k/*iconSize*/ = Math.min( - (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100), - (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100) - ); - - /** - * @type {Element} - * @private - */ - this.W/*_el*/ = element; - - // Clear current SVG child elements - while (element.firstChild) { - element.removeChild(element.firstChild); - } - - // Set viewBox attribute to ensure the svg scales nicely. - element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize); - element.setAttribute("preserveAspectRatio", "xMidYMid meet"); -} -var SvgElement__prototype = SvgElement.prototype; - -/** - * Fills the background with the specified color. - * @param {string} fillColor Fill color on the format #rrggbb. - * @param {number} opacity Opacity in the range [0.0, 1.0]. - */ -SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) { - if (opacity) { - SvgElement_append(this.W/*_el*/, "rect", - SVG_CONSTANTS.U/*WIDTH*/, "100%", - SVG_CONSTANTS.V/*HEIGHT*/, "100%", - "fill", fillColor, - "opacity", opacity); - } -}; - -/** - * Appends a path to the SVG element. - * @param {string} color Fill color on format #xxxxxx. - * @param {string} dataString The SVG path data string. - */ -SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) { - SvgElement_append(this.W/*_el*/, "path", - "fill", color, - "d", dataString); + + var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name); + + for (var i = 0; i + 1 < keyValuePairs.length; i += 2) { + el.setAttribute( + /** @type {string} */(keyValuePairs[i]), + /** @type {string} */(keyValuePairs[i + 1]) + ); + } + + parentNode.appendChild(el); +} + + +/** + * Renderer producing SVG output. + */ +function SvgElement(element) { + // Don't use the clientWidth and clientHeight properties on SVG elements + // since Firefox won't serve a proper value of these properties on SVG + // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811) + // Instead use 100px as a hardcoded size (the svg viewBox will rescale + // the icon to the correct dimensions) + var iconSize = this.k/*iconSize*/ = Math.min( + (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100), + (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100) + ); + + /** + * @type {Element} + * @private + */ + this.W/*_el*/ = element; + + // Clear current SVG child elements + while (element.firstChild) { + element.removeChild(element.firstChild); + } + + // Set viewBox attribute to ensure the svg scales nicely. + element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize); + element.setAttribute("preserveAspectRatio", "xMidYMid meet"); +} +var SvgElement__prototype = SvgElement.prototype; + +/** + * Fills the background with the specified color. + * @param {string} fillColor Fill color on the format #rrggbb. + * @param {number} opacity Opacity in the range [0.0, 1.0]. + */ +SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) { + if (opacity) { + SvgElement_append(this.W/*_el*/, "rect", + SVG_CONSTANTS.U/*WIDTH*/, "100%", + SVG_CONSTANTS.V/*HEIGHT*/, "100%", + "fill", fillColor, + "opacity", opacity); + } }; -/** - * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute. - */ -function updateAll() { - if (documentQuerySelectorAll) { - update(ICON_SELECTOR); - } -} - -/** - * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already - * been rendered. - */ -function updateAllConditional() { - if (documentQuerySelectorAll) { - /** @type {NodeListOf} */ - var elements = documentQuerySelectorAll(ICON_SELECTOR); - - for (var i = 0; i < elements.length; i++) { - var el = elements[i]; - if (!el[IS_RENDERED_PROPERTY]) { - update(el); - } - } - } -} - -/** - * Updates the identicon in the specified `` or `` elements. - * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type - * `` or ``, or a CSS selector to such an element. - * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or - * `data-jdenticon-value` attribute will be evaluated. - * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any - * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be - * specified in place of a configuration object. - */ -function update(el, hashOrValue, config) { - renderDomElement(el, hashOrValue, config, function (el, iconType) { - if (iconType) { - return iconType == ICON_TYPE_SVG ? - new SvgRenderer(new SvgElement(el)) : - new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d")); - } - }); -} - -/** - * Updates the identicon in the specified canvas or svg elements. - * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type - * `` or ``, or a CSS selector to such an element. - * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or - * `data-jdenticon-value` attribute will be evaluated. - * @param {Object|number|undefined} config - * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer. - */ -function renderDomElement(el, hashOrValue, config, rendererFactory) { - if (typeof el === "string") { - if (documentQuerySelectorAll) { - var elements = documentQuerySelectorAll(el); - for (var i = 0; i < elements.length; i++) { - renderDomElement(elements[i], hashOrValue, config, rendererFactory); - } - } - return; - } - - // Hash selection. The result from getValidHash or computeHash is - // accepted as a valid hash. - var hash = - // 1. Explicit valid hash - isValidHash(hashOrValue) || - - // 2. Explicit value (`!= null` catches both null and undefined) - hashOrValue != null && computeHash(hashOrValue) || - - // 3. `data-jdenticon-hash` attribute - isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) || - - // 4. `data-jdenticon-value` attribute. - // We want to treat an empty attribute as an empty value. - // Some browsers return empty string even if the attribute - // is not specified, so use hasAttribute to determine if - // the attribute is specified. - el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/)); - - if (!hash) { - // No hash specified. Don't render an icon. - return; - } - - var renderer = rendererFactory(el, getIdenticonType(el)); - if (renderer) { - // Draw icon - iconGenerator(renderer, hash, config); - el[IS_RENDERED_PROPERTY] = true; - } +/** + * Appends a path to the SVG element. + * @param {string} color Fill color on format #xxxxxx. + * @param {string} dataString The SVG path data string. + */ +SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) { + SvgElement_append(this.W/*_el*/, "path", + "fill", color, + "d", dataString); +}; + +/** + * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute. + */ +function updateAll() { + if (documentQuerySelectorAll) { + update(ICON_SELECTOR); + } +} + +/** + * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already + * been rendered. + */ +function updateAllConditional() { + if (documentQuerySelectorAll) { + /** @type {NodeListOf} */ + var elements = documentQuerySelectorAll(ICON_SELECTOR); + + for (var i = 0; i < elements.length; i++) { + var el = elements[i]; + if (!el[IS_RENDERED_PROPERTY]) { + update(el); + } + } + } +} + +/** + * Updates the identicon in the specified `` or `` elements. + * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type + * `` or ``, or a CSS selector to such an element. + * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or + * `data-jdenticon-value` attribute will be evaluated. + * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any + * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be + * specified in place of a configuration object. + */ +function update(el, hashOrValue, config) { + renderDomElement(el, hashOrValue, config, function (el, iconType) { + if (iconType) { + return iconType == ICON_TYPE_SVG ? + new SvgRenderer(new SvgElement(el)) : + new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d")); + } + }); +} + +/** + * Updates the identicon in the specified canvas or svg elements. + * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type + * `` or ``, or a CSS selector to such an element. + * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or + * `data-jdenticon-value` attribute will be evaluated. + * @param {Object|number|undefined} config + * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer. + */ +function renderDomElement(el, hashOrValue, config, rendererFactory) { + if (typeof el === "string") { + if (documentQuerySelectorAll) { + var elements = documentQuerySelectorAll(el); + for (var i = 0; i < elements.length; i++) { + renderDomElement(elements[i], hashOrValue, config, rendererFactory); + } + } + return; + } + + // Hash selection. The result from getValidHash or computeHash is + // accepted as a valid hash. + var hash = + // 1. Explicit valid hash + isValidHash(hashOrValue) || + + // 2. Explicit value (`!= null` catches both null and undefined) + hashOrValue != null && computeHash(hashOrValue) || + + // 3. `data-jdenticon-hash` attribute + isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) || + + // 4. `data-jdenticon-value` attribute. + // We want to treat an empty attribute as an empty value. + // Some browsers return empty string even if the attribute + // is not specified, so use hasAttribute to determine if + // the attribute is specified. + el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/)); + + if (!hash) { + // No hash specified. Don't render an icon. + return; + } + + var renderer = rendererFactory(el, getIdenticonType(el)); + if (renderer) { + // Draw icon + iconGenerator(renderer, hash, config); + el[IS_RENDERED_PROPERTY] = true; + } } -/** - * Renders an identicon for all matching supported elements. - * - * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not - * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be - * evaluated. - * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global - * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be - * specified in place of a configuration object. - */ -function jdenticonJqueryPlugin(hashOrValue, config) { - this["each"](function (index, el) { - update(el, hashOrValue, config); - }); - return this; +/** + * Renders an identicon for all matching supported elements. + * + * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not + * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be + * evaluated. + * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global + * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be + * specified in place of a configuration object. + */ +function jdenticonJqueryPlugin(hashOrValue, config) { + this["each"](function (index, el) { + update(el, hashOrValue, config); + }); + return this; } -// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js - -var jdenticon = updateAll; - -defineConfigProperty(jdenticon); - -// Export public API -jdenticon["configure"] = configure; -jdenticon["drawIcon"] = drawIcon; -jdenticon["toSvg"] = toSvg; -jdenticon["update"] = update; -jdenticon["updateCanvas"] = update; -jdenticon["updateSvg"] = update; - -/** - * Specifies the version of the Jdenticon package in use. - * @type {string} - */ -jdenticon["version"] = "3.3.0"; - -/** - * Specifies which bundle of Jdenticon that is used. - * @type {string} - */ -jdenticon["bundle"] = "browser-umd"; - -// Basic jQuery plugin -var jQuery = GLOBAL["jQuery"]; -if (jQuery) { - jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin; -} - -/** - * This function is called once upon page load. - */ -function jdenticonStartup() { - var replaceMode = ( - jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] || - GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] || - { } - )["replaceMode"]; - - if (replaceMode != "never") { - updateAllConditional(); - - if (replaceMode == "observe") { - observer(update); - } - } -} - -// Schedule to render all identicons on the page once it has been loaded. -whenDocumentIsReady(jdenticonStartup); - +// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js + +var jdenticon = updateAll; + +defineConfigProperty(jdenticon); + +// Export public API +jdenticon["configure"] = configure; +jdenticon["drawIcon"] = drawIcon; +jdenticon["toSvg"] = toSvg; +jdenticon["update"] = update; +jdenticon["updateCanvas"] = update; +jdenticon["updateSvg"] = update; + +/** + * Specifies the version of the Jdenticon package in use. + * @type {string} + */ +jdenticon["version"] = "3.3.0"; + +/** + * Specifies which bundle of Jdenticon that is used. + * @type {string} + */ +jdenticon["bundle"] = "browser-umd"; + +// Basic jQuery plugin +var jQuery = GLOBAL["jQuery"]; +if (jQuery) { + jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin; +} + +/** + * This function is called once upon page load. + */ +function jdenticonStartup() { + var replaceMode = ( + jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] || + GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] || + { } + )["replaceMode"]; + + if (replaceMode != "never") { + updateAllConditional(); + + if (replaceMode == "observe") { + observer(update); + } + } +} + +// Schedule to render all identicons on the page once it has been loaded. +whenDocumentIsReady(jdenticonStartup); + return jdenticon; - + }); \ No newline at end of file diff --git a/src/static/scripts/jquery-3.7.1.slim.js b/src/static/scripts/jquery-4.0.0.slim.js similarity index 64% rename from src/static/scripts/jquery-3.7.1.slim.js rename to src/static/scripts/jquery-4.0.0.slim.js index f122b10d..a7bb40cf 100644 --- a/src/static/scripts/jquery-3.7.1.slim.js +++ b/src/static/scripts/jquery-4.0.0.slim.js @@ -1,12 +1,12 @@ /*! - * jQuery JavaScript Library v3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween + * jQuery JavaScript Library v4.0.0+slim * https://jquery.com/ * * Copyright OpenJS Foundation and other contributors * Released under the MIT license - * https://jquery.org/license + * https://jquery.com/license/ * - * Date: 2023-08-28T13:37Z + * Date: 2026-01-18T00:20Z */ ( function( global, factory ) { @@ -16,19 +16,7 @@ // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket trac-14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; + module.exports = factory( global, true ); } else { factory( global ); } @@ -36,29 +24,31 @@ // Pass this if window is not defined yet } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. "use strict"; +if ( !window.document ) { + throw new Error( "jQuery requires a window with a document" ); +} + var arr = []; var getProto = Object.getPrototypeOf; var slice = arr.slice; +// Support: IE 11+ +// IE doesn't have Array#flat; provide a fallback. var flat = arr.flat ? function( array ) { return arr.flat.call( array ); } : function( array ) { return arr.concat.apply( [], array ); }; - var push = arr.push; var indexOf = arr.indexOf; +// [[Class]] -> type pairs var class2type = {}; var toString = class2type.toString; @@ -69,85 +59,64 @@ var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); +// All support tests are defined in their respective modules. var support = {}; -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 - // Plus for old WebKit, typeof returns "function" for HTML collections - // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) - return typeof obj === "function" && typeof obj.nodeType !== "number" && - typeof obj.item !== "function"; - }; +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + return typeof obj === "object" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; +function isWindow( obj ) { + return obj != null && obj === obj.window; +} +function isArrayLike( obj ) { -var document = window.document; + var length = !!obj && obj.length, + type = toType( obj ); + if ( typeof obj === "function" || isWindow( obj ) ) { + return false; + } + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; +var document$1 = window.document; - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } +var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true +}; +function DOMEval( code, node, doc ) { + doc = doc || document$1; -function toType( obj ) { - if ( obj == null ) { - return obj + ""; + var i, + script = doc.createElement( "script" ); + + script.text = code; + for ( i in preservedScriptAttributes ) { + if ( node && node[ i ] ) { + script[ i ] = node[ i ]; + } } - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; + if ( doc.head.appendChild( script ).parentNode ) { + script.parentNode.removeChild( script ); + } } -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - -var version = "3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween", +var version = "4.0.0+slim", rhtmlSuffix = /HTML$/i, @@ -243,13 +212,7 @@ jQuery.fn = jQuery.prototype = { end: function() { return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice + } }; jQuery.extend = jQuery.fn.extend = function() { @@ -269,7 +232,7 @@ jQuery.extend = jQuery.fn.extend = function() { } // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { + if ( typeof target !== "object" && typeof target !== "function" ) { target = {}; } @@ -427,6 +390,7 @@ jQuery.extend( { return ret; }, + // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -458,8 +422,20 @@ jQuery.extend( { return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); }, - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit + // Note: an element does not contain itself + contains: function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + // Support: IE 9 - 11+ + // IE doesn't have `contains` on SVG. + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + }, + merge: function( first, second ) { var len = +second.length, j = 0, @@ -543,201 +519,128 @@ jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symb class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} - - function nodeName( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - } -var pop = arr.pop; +var pop = arr.pop; -var sort = arr.sort; +// https://www.w3.org/TR/css3-selectors/#whitespace +var whitespace = "[\\x20\\t\\r\\n\\f]"; +var isIE = document$1.documentMode; -var splice = arr.splice; +var rbuggyQSA = isIE && new RegExp( + // Support: IE 9 - 11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + ":enabled|:disabled|" + -var whitespace = "[\\x20\\t\\r\\n\\f]"; + // Support: IE 11+ + // IE 11 doesn't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" +); var rtrimCSS = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ); +// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram +var identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+"; +var rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + + whitespace + ")" + whitespace + "*" ); +var rdescend = new RegExp( whitespace + "|>" ); -// Note: an element does not contain itself -jQuery.contains = function( a, b ) { - var bup = b && b.parentNode; - - return a === bup || !!( bup && bup.nodeType === 1 && ( - - // Support: IE 9 - 11+ - // IE doesn't have `contains` on SVG. - a.contains ? - a.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); -}; +var rsibling = /[+~]/; +var documentElement$1 = document$1.documentElement; +// Support: IE 9 - 11+ +// IE requires a prefix. +var matches = documentElement$1.matches || documentElement$1.msMatchesSelector; +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; -// CSS string/identifier serialization -// https://drafts.csswg.org/cssom/#common-serializing-idioms -var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + function cache( key, value ) { -function fcssescape( ch, asCodePoint ) { - if ( asCodePoint ) { + // Use (key + " ") to avoid collision with native prototype properties + // (see https://github.com/jquery/sizzle/issues/157) + if ( keys.push( key + " " ) > jQuery.expr.cacheLength ) { - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; + // Only keep the most recent entries + delete cache[ keys.shift() ]; } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return ( cache[ key + " " ] = value ); } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; + return cache; } -jQuery.escapeSelector = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - - - - -var preferredDoc = document, - pushNative = push; - -( function() { - -var i, - Expr, - outermostContext, - sortInput, - hasDuplicate, - push = pushNative, - - // Local document vars - document, - documentElement, - documentIsHTML, - rbuggyQSA, - matches, - - // Instance-specific data - expando = jQuery.expando, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + - "loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + +/** + * Checks a node for validity as a jQuery selector context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + +// Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors +var attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]"; - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + - whitespace + "*" ), - rdescend = new RegExp( whitespace + "|>" ), +var pseudos = ":(" + identifier + ")(?:\\((" + - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - matchExpr = { - ID: new RegExp( "^#(" + identifier + ")" ), - CLASS: new RegExp( "^\\.(" + identifier + ")" ), - TAG: new RegExp( "^(" + identifier + "|[*])" ), - ATTR: new RegExp( "^" + attributes ), - PSEUDO: new RegExp( "^" + pseudos ), - CHILD: new RegExp( - "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - bool: new RegExp( "^(?:" + booleans + ")$", "i" ), + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // For use in libraries implementing .is() - // We use this for POS matching in `select` - needsContext: new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, + // 3. anything else (capture 2) + ".*" + + ")\\)|)"; - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, +var filterMatchExpr = { + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ) +}; - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, +var rpseudo = new RegExp( pseudos ); - rsibling = /[+~]/, +// CSS escapes +// https://www.w3.org/TR/CSS21/syndata.html#escaped-characters - // CSS escapes - // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\([^\\r\\n\\f])", "g" ), +var runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), funescape = function( escape, nonHex ) { var high = "0x" + escape.slice( 1 ) - 0x10000; @@ -754,738 +657,435 @@ var i, return high < 0 ? String.fromCharCode( high + 0x10000 ) : String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // Used for iframes; see `setDocument`. - // Support: IE 9 - 11+, Edge 12 - 18+ - // Removing the function wrapper causes a "Permission Denied" - // error in IE/Edge. - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && nodeName( elem, "fieldset" ); - }, - { dir: "parentNode", next: "legend" } - ); + }; -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } +function unescapeSelector( sel ) { + return sel.replace( runescape, funescape ); } -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android <=4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { - apply: function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - }, - call: function( target ) { - pushNative.apply( target, slice.call( arguments, 1 ) ); - } - }; +function selectorError( msg ) { + jQuery.error( "Syntax error, unrecognized expression: " + msg ); } -function find( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; +var rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ); - results = results || []; +var tokenCache = createCache(); - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; - return results; + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); } - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { + soFar = selector; + groups = []; + preFilters = jQuery.expr.preFilter; - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + while ( soFar ) { - // ID selector - if ( ( m = match[ 1 ] ) ) { + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } - // Support: IE 9 only - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - push.call( results, elem ); - return results; - } - } else { - return results; - } + matched = false; - // Element context - } else { + // Combinators + if ( ( match = rleadingCombinator.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, - // Support: IE 9 only - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - find.contains( context, elem ) && - elem.id === m ) { + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrimCSS, " " ) + } ); + soFar = soFar.slice( matched.length ); + } - push.call( results, elem ); - return results; - } - } + // Filters + for ( type in filterMatchExpr ) { + if ( ( match = jQuery.expr.match[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; + if ( !matched ) { + break; + } + } - // Class selector - } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + if ( parseOnly ) { + return soFar.length; + } - // Take advantage of querySelectorAll - if ( !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { + return soFar ? + selectorError( selector ) : - newSelector = selector; - newContext = context; + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { +var preFilter = { + ATTR: function( match ) { + match[ 1 ] = unescapeSelector( match[ 1 ] ); - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = unescapeSelector( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ); - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when - // strict-comparing two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( newContext != context || !support.scope ) { + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = jQuery.escapeSelector( nid ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } + return match.slice( 0, 4 ); + }, - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } + CHILD: function( match ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } + /* matches from filterMatchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + selectorError( match[ 0 ] ); } - } - } - // All others - return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); -} + // numeric x and y parameters for jQuery.expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; + // other types prohibit arguments + } else if ( match[ 3 ] ) { + selectorError( match[ 0 ] ); + } - function cache( key, value ) { + return match; + }, - // Use (key + " ") to avoid collision with native prototype properties - // (see https://github.com/jquery/sizzle/issues/157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { + PSEUDO: function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; - // Only keep the most recent entries - delete cache[ keys.shift() ]; + if ( filterMatchExpr.CHILD.test( match[ 0 ] ) ) { + return null; } - return ( cache[ key + " " ] = value ); - } - return cache; -} -/** - * Mark a function for special use by jQuery selector module - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - + unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); } - // release memory in IE - el = null; + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); } -} +}; -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - return nodeName( elem, "input" ) && elem.type === type; - }; +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; } -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && - elem.type === type; - }; -} +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +function access( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { + // Sets one value + } else if ( value !== undefined ) { + chainable = true; - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { + if ( typeof value !== "function" ) { + raw = true; + } - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { + if ( bulk ) { - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; - // Support: IE 6 - 11+ - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } - // Where there is no isDisabled, check manually - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); } + } + } - return elem.disabled === disabled; + if ( chainable ) { + return elems; + } - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } + // Gets + if ( bulk ) { + return fn.call( elems ); + } - // Remaining elements are neither :enabled nor :disabled - return false; - }; + return len ? fn( elems[ 0 ], key ) : emptyGet; } -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; +// Only count HTML whitespace +// Other whitespace should count in values +// https://infra.spec.whatwg.org/#ascii-whitespace +var rnothtmlwhite = /[^\x20\t\r\n\f]+/g; - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); } ); - } ); -} + } +} ); -/** - * Checks a node for validity as a jQuery selector context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [node] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -function setDocument( node ) { - var subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - documentElement = document.documentElement; - documentIsHTML = !jQuery.isXMLDoc( document ); - - // Support: iOS 7 only, IE 9 - 11+ - // Older browsers didn't support unprefixed `matches`. - matches = documentElement.matches || - documentElement.webkitMatchesSelector || - documentElement.msMatchesSelector; - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors - // (see trac-13936). - // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`, - // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well. - if ( documentElement.msMatchesSelector && - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 9 - 11+, Edge 12 - 18+ - subWindow.addEventListener( "unload", unloadHandler ); - } - - // Support: IE <10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - documentElement.appendChild( el ).id = jQuery.expando; - return !document.getElementsByName || - !document.getElementsByName( jQuery.expando ).length; - } ); - - // Support: IE 9 only - // Check to see if it's possible to do matchesSelector - // on a disconnected node. - support.disconnectedMatch = assert( function( el ) { - return matches.call( el, "*" ); - } ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // IE/Edge don't support the :scope pseudo-class. - support.scope = assert( function() { - return document.querySelectorAll( ":scope" ); - } ); +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; - // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only - // Make sure the `:has()` argument is parsed unforgivingly. - // We include `*` in the test to detect buggy implementations that are - // _selectively_ forgiving (specifically when the list includes at least - // one valid selector). - // Note that we treat complete lack of support for `:has()` as if it were - // spec-compliant support, which is fine because use of `:has()` in such - // environments will fail in the qSA path and fall back to jQuery traversal - // anyway. - support.cssHas = assert( function() { - try { - document.querySelector( ":has(*,:jqfake)" ); - return false; - } catch ( e ) { - return true; + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; } - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter.ID = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find.ID = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter.ID = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find.ID = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - return []; - } - }; - } - - // Tag - Expr.find.TAG = function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else { - return context.querySelectorAll( tag ); + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); } - }; - // Class - Expr.find.CLASS = function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ]; } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - rbuggyQSA = []; - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - documentElement.appendChild( el ).innerHTML = - "" + - ""; + if ( value !== undefined ) { + if ( value === null || - // Support: iOS <=7 - 8 only - // Boolean attributes and "value" are not treated correctly in some XML documents - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } + // For compat with previous handling of boolean attributes, + // remove when `false` passed. For ARIA attributes - + // many of which recognize a `"false"` value - continue to + // set the `"false"` value as jQuery <4 did. + ( value === false && name.toLowerCase().indexOf( "aria-" ) !== 0 ) ) { - // Support: iOS <=7 - 8 only - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } + jQuery.removeAttr( elem, name ); + return; + } - // Support: iOS 8 only - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } - // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ - // In some of the document kinds, these selectors wouldn't work natively. - // This is probably OK but for backwards compatibility we want to maintain - // handling them through jQuery traversal in jQuery 3.x. - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); + elem.setAttribute( name, value ); + return value; } - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE 9 - 11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ - // In some of the document kinds, these selectors wouldn't work natively. - // This is probably OK but for backwards compatibility we want to maintain - // handling them through jQuery traversal in jQuery 3.x. - documentElement.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; } - } ); - if ( !support.cssHas ) { + ret = elem.getAttribute( name ); - // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ - // Our regular `try-catch` mechanism fails to detect natively-unsupported - // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) - // in browsers that parse the `:has()` argument as a forgiving selector list. - // https://drafts.csswg.org/selectors/#relational now requires the argument - // to be parsed unforgivingly, but browsers have not yet fully adjusted. - rbuggyQSA.push( ":has" ); - } + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + attrHooks: {}, - /* Sorting - ---------------------------------------------------------------------- */ + removeAttr: function( elem, value ) { + var name, + i = 0, - // Document order sorting - sortOrder = function( a, b ) { + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } } + } +} ); - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; +// Support: IE <=11+ +// An input loses its value after becoming a radio +if ( isIE ) { + jQuery.attrHooks.type = { + set: function( elem, value ) { + if ( value === "radio" && nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } } + }; +} - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a === document || a.ownerDocument == preferredDoc && - find.contains( preferredDoc, a ) ) { - return -1; - } +// CSS string/identifier serialization +// https://drafts.csswg.org/cssom/#common-serializing-idioms +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b === document || b.ownerDocument == preferredDoc && - find.contains( preferredDoc, b ) ) { - return 1; - } +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { - // Maintain original order - return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; } - return compare & 4 ? -1 : 1; - }; + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } - return document; + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; } -find.matches = function( expr, elements ) { - return find( expr, null, null, elements ); +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); }; -find.matchesSelector = function( elem, expr ) { - setDocument( elem ); +var sort = arr.sort; - if ( documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { +var splice = arr.splice; - try { - var ret = matches.call( elem, expr ); +var hasDuplicate; - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || +// Document order sorting +function sortOrder( a, b ) { - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; } - return find( expr, document, null, [ elem ] ).length > 0; -}; - -find.contains = function( context, elem ) { + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // Calculate position if both inputs belong to the same document + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return jQuery.contains( context, elem ); -}; - + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : -find.attr = function( elem, name ) { + // Otherwise we know they are disconnected + 1; - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } + // Disconnected nodes + if ( compare & 1 ) { - var fn = Expr.attrHandle[ name.toLowerCase() ], + // Choose the first element that is related to the document + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document$1 || a.ownerDocument == document$1 && + jQuery.contains( document$1, a ) ) { + return -1; + } - // Don't get fooled by Object.prototype properties (see trac-13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document$1 || b.ownerDocument == document$1 && + jQuery.contains( document$1, b ) ) { + return 1; + } - if ( val !== undefined ) { - return val; + // Maintain original order + return 0; } - return elem.getAttribute( name ); -}; - -find.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; + return compare & 4 ? -1 : 1; +} /** * Document sorting and removing duplicates @@ -1497,13 +1097,8 @@ jQuery.uniqueSort = function( results ) { j = 0, i = 0; - // Unless we *know* we can detect duplicates, assume their presence - // - // Support: Android <=4.0+ - // Testing for detecting duplicates is unpredictable so instead assume we can't - // depend on duplicate detection in all browsers without a stable sort. - hasDuplicate = !support.sortStable; - sortInput = !support.sortStable && slice.call( results, 0 ); + hasDuplicate = false; + sort.call( results, sortOrder ); if ( hasDuplicate ) { @@ -1517,10 +1112,6 @@ jQuery.uniqueSort = function( results ) { } } - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - return results; }; @@ -1528,6394 +1119,5097 @@ jQuery.fn.uniqueSort = function() { return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); }; -Expr = jQuery.expr = { +var i, + outermostContext, - // Can be adjusted by the user - cacheLength: 50, + // Local document vars + document, + documentElement, + documentIsHTML, - createPseudo: markFunction, + // Instance-specific data + dirruns = 0, + done = 0, + classCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), - match: matchExpr, + // Regular expressions - attrHandle: {}, + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), - find: {}, + ridentifier = new RegExp( "^" + identifier + "$" ), - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, + matchExpr = jQuery.extend( { + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + needsContext: new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, filterMatchExpr ), - preFilter: { - ATTR: function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) - .replace( runescape, funescape ); + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr$1 = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+ + // Removing the function wrapper causes a "Permission Denied" + // error in IE. + unloadHandler = function() { + setDocument(); + }, - return match.slice( 0, 4 ); + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && nodeName( elem, "fieldset" ); }, + { dir: "parentNode", next: "legend" } + ); - CHILD: function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - find.error( match[ 0 ] ); - } +function find( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) - ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; - // other types prohibit arguments - } else if ( match[ 3 ] ) { - find.error( match[ 0 ] ); - } + results = results || []; - return match; - }, - - PSEUDO: function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr.CHILD.test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - TAG: function( nodeNameSelector ) { - var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return nodeName( elem, expectedNodeName ); - }; - }, - - CLASS: function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + ")" + className + - "(" + whitespace + "|$)" ) ) && - classCache( className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - ATTR: function( name, operator, check ) { - return function( elem ) { - var result = find.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - if ( operator === "=" ) { - return result === check; - } - if ( operator === "!=" ) { - return result !== check; - } - if ( operator === "^=" ) { - return check && result.indexOf( check ) === 0; - } - if ( operator === "*=" ) { - return check && result.indexOf( check ) > -1; - } - if ( operator === "$=" ) { - return check && result.slice( -check.length ) === check; - } - if ( operator === "~=" ) { - return ( " " + result.replace( rwhitespace, " " ) + " " ) - .indexOf( check ) > -1; - } - if ( operator === "|=" ) { - return result === check || result.slice( 0, check.length + 1 ) === check + "-"; - } - - return false; - }; - }, - - CHILD: function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - nodeName( node, name ) : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || ( parent[ expando ] = {} ); - cache = outerCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - cache = outerCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - nodeName( node, name ) : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - outerCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - PSEUDO: function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // https://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - find.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as jQuery does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - not: markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrimCSS, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element - // (see https://github.com/jquery/sizzle/issues/299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - has: markFunction( function( selector ) { - return function( elem ) { - return find( selector, elem ).length > 0; - }; - } ), - - contains: markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // https://www.w3.org/TR/selectors/#lang-pseudo - lang: markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - find.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - target: function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - root: function( elem ) { - return elem === documentElement; - }, - - focus: function( elem ) { - return elem === safeActiveElement() && - document.hasFocus() && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - enabled: createDisabledPseudo( false ), - disabled: createDisabledPseudo( true ), - - checked: function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - return ( nodeName( elem, "input" ) && !!elem.checked ) || - ( nodeName( elem, "option" ) && !!elem.selected ); - }, - - selected: function( elem ) { - - // Support: IE <=11+ - // Accessing the selectedIndex property - // forces the browser to treat the default option as - // selected when in an optgroup. - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - empty: function( elem ) { - - // https://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - parent: function( elem ) { - return !Expr.pseudos.empty( elem ); - }, - - // Element/input types - header: function( elem ) { - return rheader.test( elem.nodeName ); - }, - - input: function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - button: function( elem ) { - return nodeName( elem, "input" ) && elem.type === "button" || - nodeName( elem, "button" ); - }, - - text: function( elem ) { - var attr; - return nodeName( elem, "input" ) && elem.type === "text" && - - // Support: IE <10 only - // New HTML5 attribute values (e.g., "search") appear - // with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - first: createPositionalPseudo( function() { - return [ 0 ]; - } ), - - last: createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - even: createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - odd: createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - lt: createPositionalPseudo( function( matchIndexes, length, argument ) { - var i; - - if ( argument < 0 ) { - i = argument + length; - } else if ( argument > length ) { - i = length; - } else { - i = argument; - } - - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - gt: createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos.nth = Expr.pseudos.eq; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rleadingCombinator.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrimCSS, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - if ( parseOnly ) { - return soFar.length; - } - - return soFar ? - find.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - if ( skip && nodeName( elem, skip ) ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = outerCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - outerCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - find( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, matcherOut, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || - multipleContexts( selector || "*", - context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems; - - if ( matcher ) { - - // If we have a postFinder, or filtered seed, or non-seed postFilter - // or preexisting results, - matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results; - - // Find primary matches - matcher( matcherIn, matcherOut, context, xml ); - } else { - matcherOut = matcherIn; - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element - // (see https://github.com/jquery/sizzle/issues/299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrimCSS, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find.TAG( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: iOS <=7 - 9 only - // Tolerate NodeList properties (IE: "length"; Safari: ) matching - // elements by id. (see trac-14142) - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - push.call( results, elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - jQuery.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -function compile( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -} - -/** - * A low-level selection function that works with jQuery's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with jQuery selector compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -function select( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find.ID( - token.matches[ 0 ].replace( runescape, funescape ), - context - ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && - testContext( context.parentNode ) || context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -} - -// One-time assignments - -// Support: Android <=4.0 - 4.1+ -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Initialize against the default document -setDocument(); - -// Support: Android <=4.0 - 4.1+ -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -jQuery.find = find; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.unique = jQuery.uniqueSort; - -// These have always been private, but they used to be documented as part of -// Sizzle so let's maintain them for now for backwards compatibility purposes. -find.compile = compile; -find.select = select; -find.setDocument = setDocument; -find.tokenize = tokenize; - -find.escape = jQuery.escapeSelector; -find.getText = jQuery.text; -find.isXML = jQuery.isXMLDoc; -find.selectors = jQuery.expr; -find.support = jQuery.support; -find.uniqueSort = jQuery.uniqueSort; - - /* eslint-enable */ - -} )(); - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (trac-9521) - // Strict HTML recognition (trac-11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to jQuery#find - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); + return results; } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - returned = handler.apply( that, args ); + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } + if ( documentIsHTML ) { - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr$1.exec( selector ) ) ) { - // Handle all other returned values - } else { + // ID selector + if ( ( m = match[ 1 ] ) ) { - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + push.call( results, elem ); + } + return results; - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.error ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { + // Element context + } else { + if ( newContext && ( elem = newContext.getElementById( m ) ) && + jQuery.contains( context, elem ) ) { - // Call an optional hook to record the error, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getErrorHook ) { - process.error = jQuery.Deferred.getErrorHook(); - - // The deprecated alias of the above. While the name suggests - // returning the stack, not an error instance, jQuery just passes - // it directly to `console.warn` so both will work; an instance - // just better cooperates with source maps. - } else if ( jQuery.Deferred.getStackHook ) { - process.error = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; + push.call( results, elem ); + return results; + } } - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; + // Class selector + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; } - }, - deferred = {}; + } - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; + // Take advantage of querySelectorAll + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; + newSelector = selector; + newContext = context; - // Handle state - if ( stateString ) { - list.add( - function() { + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && + testContext( context.parentNode ) || + context; - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, + // Outside of IE, if we're not changing the context we can + // use :scope instead of an ID. + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( newContext != context || isIE ) { - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = jQuery.escapeSelector( nid ); + } else { + context.setAttribute( "id", ( nid = jQuery.expando ) ); + } + } - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === jQuery.expando ) { + context.removeAttribute( "id" ); + } + } } + } + } - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); + // All others + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); +} - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; +/** + * Mark a function for special use by jQuery selector module + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ jQuery.expando ] = true; + return fn; +} - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + return nodeName( elem, "input" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; + }; +} - // Make the deferred a promise - promise.promise( deferred ); +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { - // All done! - return deferred; - }, + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { - // Deferred helper - when: function( singleValue ) { - var + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { - // count of uncompleted subordinates - remaining = arguments.length, + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } - // count of unprocessed arguments - i = remaining, + // Support: IE 6 - 11+ + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), + // Where there is no isDisabled, check manually + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } - // the primary Deferred - primary = jQuery.Deferred(), + return elem.disabled === disabled; - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - primary.resolveWith( resolveContexts, resolveValues ); - } - }; - }; + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, - !remaining ); + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( primary.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; - return primary.then(); + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } } - } + } ); + } ); +} - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); - } +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [node] An element or document object to use to set the document + */ +function setDocument( node ) { + var subWindow, + doc = node ? node.ownerDocument || node : document$1; - return primary.promise(); + // Return early if doc is invalid or already selected + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 ) { + return; } -} ); + // Update global variables + document = doc; + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error -// captured before the async barrier to get the original error cause -// which may otherwise be hidden. -jQuery.Deferred.exceptionHook = function( error, asyncError ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, - error.stack, asyncError ); + // Support: IE 9 - 11+ + // Accessing iframe documents after unload throws "permission denied" errors (see trac-13936) + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( isIE && document$1 != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + subWindow.addEventListener( "unload", unloadHandler ); } +} + +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); }; +find.matchesSelector = function( elem, expr ) { + setDocument( elem ); + if ( documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + try { + return matches.call( elem, expr ); + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); + return find( expr, document, null, [ elem ] ).length > 0; }; +jQuery.expr = { + // Can be adjusted by the user + cacheLength: 50, + createPseudo: markFunction, -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { + match: matchExpr, - readyList - .then( fn ) + find: { + ID: function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }, - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); + TAG: function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); - return this; -}; + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }, -jQuery.extend( { + CLASS: function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + } + }, - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, - // A counter to track how many items to wait for before - // the ready event fires. See trac-6781 - readyWait: 1, + preFilter: preFilter, - // Handle when the DOM is ready - ready: function( wait ) { + filter: { + ID: function( id ) { + var attrId = unescapeSelector( id ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }, - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } + TAG: function( nodeNameSelector ) { + var expectedNodeName = unescapeSelector( nodeNameSelector ).toLowerCase(); + return nodeNameSelector === "*" ? - // Remember that the DOM is ready - jQuery.isReady = true; + function() { + return true; + } : - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } + function( elem ) { + return nodeName( elem, expectedNodeName ); + }; + }, - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); + CLASS: function( className ) { + var pattern = classCache[ className + " " ]; -jQuery.ready.then = readyList.then; + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} + ATTR: function( name, operator, check ) { + return function( elem ) { + var result = jQuery.attr( elem, name ); -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); + result += ""; -} else { + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); + return false; + }; + }, - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} + CHILD: function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + return first === 1 && last === 0 ? + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + function( elem, _context, xml ) { + var cache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; + if ( parent ) { - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) { - // Sets one value - } else if ( value !== undefined ) { - chainable = true; + return false; + } + } - if ( !isFunction( value ) ) { - raw = true; - } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } - if ( bulk ) { + start = [ forward ? parent.firstChild : parent.lastChild ]; - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } + // Seek `elem` from a previously-cached index + outerCache = parent[ jQuery.expando ] || + ( parent[ jQuery.expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } + while ( ( node = ++nodeIndex && node && node[ dir ] || - if ( chainable ) { - return elems; - } + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { - // Gets - if ( bulk ) { - return fn.call( elems ); - } + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; + } else { + // Use previously-cached element index if available + if ( useCache ) { + outerCache = elem[ jQuery.expando ] || + ( elem[ jQuery.expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (trac-9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { + if ( ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) && + ++diff ) { - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ jQuery.expando ] || + ( node[ jQuery.expando ] = {} ); + outerCache[ type ] = [ dirruns, diff ]; + } + if ( node === elem ) { + break; + } + } + } + } + } + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + PSEUDO: function( pseudo, argument ) { -function Data() { - this.expando = jQuery.expando + Data.uid++; -} + // pseudo-class names are case-insensitive + // https://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var fn = jQuery.expr.pseudos[ pseudo ] || + jQuery.expr.setFilters[ pseudo.toLowerCase() ] || + selectorError( "unsupported pseudo: " + pseudo ); -Data.uid = 1; + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as jQuery does + if ( fn[ jQuery.expando ] ) { + return fn( argument ); + } -Data.prototype = { + return fn; + } + }, - cache: function( owner ) { + pseudos: { - // Check if the owner object already has a cache - var value = owner[ this.expando ]; + // Potentially complex pseudos + not: markFunction( function( selector ) { - // If not, create one - if ( !value ) { - value = {}; + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrimCSS, "$1" ) ); - // We can accept data for non-element nodes in modern browsers, - // but we should not, see trac-8335. - // Always return an empty object. - if ( acceptData( owner ) ) { + return matcher[ jQuery.expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } + // Don't keep the element + // (see https://github.com/jquery/sizzle/issues/299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); + has: markFunction( function( selector ) { + return function( elem ) { + return find( selector, elem ).length > 0; + }; + } ), - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; + contains: markFunction( function( text ) { + text = unescapeSelector( text ); + return function( elem ) { + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; + }; + } ), - // Handle: [ owner, { properties } ] args - } else { + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // https://www.w3.org/TR/selectors/#lang-pseudo + lang: markFunction( function( lang ) { - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + selectorError( "unsupported lang: " + lang ); } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { + lang = unescapeSelector( lang ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), - return this.get( owner, key ); - } + // Miscellaneous + target: function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); + root: function( elem ) { + return elem === documentElement; + }, - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; + focus: function( elem ) { + return elem === document.activeElement && + document.hasFocus() && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, - if ( cache === undefined ) { - return; - } + // Boolean properties + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), - if ( key !== undefined ) { + checked: function( elem ) { - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { + // In CSS3, :checked should return both checked and selected elements + // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); + }, - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); + selected: function( elem ) { - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); + // Support: IE <=11+ + // Accessing the selectedIndex property + // forces the browser to treat the default option as + // selected when in an optgroup. + if ( isIE && elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; } - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } + return elem.selected === true; + }, - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + // Contents + empty: function( elem ) { - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; + // https://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); + return true; + }, + parent: function( elem ) { + return !jQuery.expr.pseudos.empty( elem ); + }, + // Element/input types + header: function( elem ) { + return rheader.test( elem.nodeName ); + }, -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + input: function( elem ) { + return rinputs.test( elem.nodeName ); + }, -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); + }, -function getData( data ) { - if ( data === "true" ) { - return true; - } + text: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "text"; + }, - if ( data === "false" ) { - return false; - } + // Position-in-collection + first: createPositionalPseudo( function() { + return [ 0 ]; + } ), - if ( data === "null" ) { - return null; - } + last: createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } + even: createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), - return data; -} + odd: createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), -function dataAttr( elem, key, data ) { - var name; + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) } - return data; +}; + +jQuery.expr.pseudos.nth = jQuery.expr.pseudos.eq; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + jQuery.expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + jQuery.expr.pseudos[ i ] = createButtonPseudo( i ); } -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = jQuery.expr.pseudos; +jQuery.expr.setFilters = new setFilters(); - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, + return combinator.first ? - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ jQuery.expando ] || ( elem[ jQuery.expando ] = {} ); - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); + if ( skip && nodeName( elem, skip ) ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = outerCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { - // Support: IE 11 only - // The attrs elements can be null (trac-14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); + // Reuse newcache so results back-propagate to previous elements + outerCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; } } } - dataPriv.set( elem, "hasDataAttrs", true ); } } + return false; + }; +} - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} - return access( this, function( value ) { - var data; +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + find( selector, contexts[ i ], results ); + } + return results; +} - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); } + } + } + } - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } + return newUnmatched; +} - // We tried really hard, but the data doesn't exist. - return; - } +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ jQuery.expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ jQuery.expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, matcherOut, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems; - // Set the data... - this.each( function() { + if ( matcher ) { - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, + // If we have a postFinder, or filtered seed, or non-seed postFilter + // or preexisting results, + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); + // ...intermediate processing is necessary + [] : + // ...otherwise use results directly + results; -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; + // Find primary matches + matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; + } - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } - return queue || []; } - }, - dequeue: function( elem, type ) { - type = type || "fx"; + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } - if ( fn ) { + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); + seed[ temp ] = !( results[ temp ] = elem ); + } + } } - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); + } ); +} -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = jQuery.expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || jQuery.expr.relative[ " " ], + i = leadingRelative ? 1 : 0, - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); + // Avoid hanging onto element + // (see https://github.com/jquery/sizzle/issues/299) + checkContext = null; + return ret; + } ]; - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); + for ( ; i < len; i++ ) { + if ( ( matcher = jQuery.expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = jQuery.expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, + // Return special upon seeing a positional matcher + if ( matcher[ jQuery.expando ] ) { - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( jQuery.expr.relative[ tokens[ j ].type ] ) { + break; + } } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrimCSS, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); } + matchers.push( matcher ); } - resolve(); - return defer.promise( obj ); } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && jQuery.expr.find.TAG( "*", outermost ), -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ); -var documentElement = document.documentElement; + if ( outermost ) { + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + // Add elements passing elementMatchers directly to results + for ( ; ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { + // Support: IE 11+ + // IE sometimes throws a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + push.call( results, elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; + // Track unmatched elements for set filters + if ( bySet ) { - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } - jQuery.css( elem, "display" ) === "none"; - }; + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + if ( seed ) { -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + jQuery.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; + return unmatched; + }; - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; +function compile( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; - while ( maxIterations-- ) { + if ( !cached ) { - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ jQuery.expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); } - initialInUnit = initialInUnit / scale; - } - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); + // Cache the compiled function + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - // Make sure we update the tween properties later on - valueParts = valueParts || []; + // Save selector and tokenization + cached.selector = selector; } + return cached; +} - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; +/** + * A low-level selection function that works with jQuery's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with jQuery selector compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} + results = results || []; + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { -var defaultDisplayMap = {}; + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && + jQuery.expr.relative[ tokens[ 1 ].type ] ) { -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; + context = ( jQuery.expr.find.ID( + unescapeSelector( token.matches[ 0 ] ), + context + ) || [] )[ 0 ]; + if ( !context ) { + return results; - if ( display ) { - return display; - } + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); + selector = selector.slice( tokens.shift().value.length ); + } - temp.parentNode.removeChild( temp ); + // Fetch a seed set for right-to-left matching + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; - if ( display === "none" ) { - display = "block"; + // Abort if we hit a combinator + if ( jQuery.expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = jQuery.expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + unescapeSelector( token.matches[ 0 ] ), + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } } - defaultDisplayMap[ nodeName ] = display; - return display; + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; } -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; +// Initialize against the default document +setDocument(); - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } +jQuery.find = find; - display = elem.style.display; - if ( show ) { +// These have always been private, but they used to be documented as part of +// Sizzle so let's maintain them for now for backwards compatibility purposes. +find.compile = compile; +find.select = select; +find.setDocument = setDocument; +find.tokenize = tokenize; - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; +function dir( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; } + matched.push( elem ); } } + return matched; +} - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; +function siblings( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); } } - return elements; + return matched; } -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); +var rneedsContext = jQuery.expr.match.needsContext; -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); +// rsingleTag matches a string consisting of a single HTML element with no attributes +// and captures the element's name +var rsingleTag = /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i; -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); +function isObviousHtml( input ) { + return input[ 0 ] === "<" && + input[ input.length - 1 ] === ">" && + input.length >= 3; +} +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( typeof qualifier === "function" ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (trac-11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (trac-14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} - div.appendChild( input ); +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + if ( not ) { + expr = ":not(" + expr + ")"; + } - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } - // Support: IE <=9 only - // IE <=9 replaces ", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], + ret = this.pushStack( [] ); - _default: [ 0, "", "" ] -}; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); +// Initialize a jQuery object -function getAll( context, tag ) { +// A central reference to the root jQuery(document) +var rootjQuery, - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) - var ret; + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (trac-9521) + // Strict HTML recognition (trac-11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); + init = jQuery.fn.init = function( selector, context ) { + var match, elem; - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } - } else { - ret = []; - } + // HANDLE: $(DOMElement) + if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } + // HANDLE: $(function) + // Shortcut for document ready + } else if ( typeof selector === "function" ) { + return rootjQuery.ready !== undefined ? + rootjQuery.ready( selector ) : - return ret; -} + // Execute immediately if ready is not present + selector( jQuery ); + } else { -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; + // Handle obvious HTML strings + match = selector + ""; + if ( isObviousHtml( match ) ) { - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} + // Assume that strings that start and end with <> are HTML and skip + // the regex check. This also handles browser-supported HTML wrappers + // like TrustedHTML. + match = [ null, selector, null ]; + // Handle HTML strings or selectors + } else if ( typeof selector === "string" ) { + match = rquickExpr.exec( selector ); + } else { + return jQuery.makeArray( selector, this ); + } -var rhtml = /<|&#?\w+;/; + // Match html or make sure no context is specified for #id + // Note: match[1] may be a string or a TrustedHTML wrapper + if ( match && ( match[ 1 ] || !context ) ) { -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; - for ( ; i < l; i++ ) { - elem = elems[ i ]; + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document$1, + true + ) ); - if ( elem || elem === 0 ) { + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { - // Add nodes directly - if ( toType( elem ) === "object" ) { + // Properties of context are called as methods if possible + if ( typeof this[ match ] === "function" ) { + this[ match ]( context[ match ] ); - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); + return this; - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + // HANDLE: $(#id) + } else { + elem = document$1.getElementById( match[ 2 ] ); - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + if ( elem ) { - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; } - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; + // HANDLE: $(expr) & $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); - // Ensure the created nodes are orphaned (trac-12392) - tmp.textContent = ""; + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); } } - } - // Remove wrapper from fragment - fragment.textContent = ""; + }; - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } +// Initialize central reference +rootjQuery = jQuery( document$1 ); - attached = isAttached( elem ); +var rparentsprev = /^(?:parents|prev(?:Until|All))/, - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; } } - } - } - - return fragment; -} - - -var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} + } ); + }, -function returnFalse() { - return false; -} + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); -function on( elem, types, selector, data, fn, one ) { - var origFn, type; + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - // Types can be a map of types/handlers - if ( typeof types === "object" ) { + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { + // Don't pass non-elements to jQuery#find + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); + matched.push( cur ); + break; + } + } + } } - return elem; - } - if ( data == null && fn == null ) { + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { + // Determine the position of an element within the set + index: function( elem ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { + // Locate the position of the desired element + return indexOf.call( this, - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); - global: {}, +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} - add: function( elem, types, handler, data, selector ) { +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; + return elem.contentDocument; } - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; + // Support: IE 9 - 11+ + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; } - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; + if ( name.slice( -5 ) !== "Until" ) { + selector = until; } - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } + if ( this.length > 1 ) { - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); } + } - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; + return this.pushStack( matched ); + }; +} ); - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; +// Matches dashed string for camelizing +var rdashAlpha = /-([a-z])/g; - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); +// Convert dashed to camelCase +function camelCase( string ) { + return string.replace( rdashAlpha, fcamelCase ); +} - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; +/** + * Determines whether an object can have data + */ +function acceptData( owner ) { - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +} - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = Object.create( null ); + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see trac-8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); } } + } - if ( special.add ) { - special.add.call( elem, handleObj ); + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } + // Handle: [ owner, { properties } ] args + } else { - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } } - + return value; }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { - if ( !elemData || !( events = elemData.events ) ) { - return; + return this.get( owner, key ); } - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + if ( cache === undefined ) { + return; + } - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; + if ( key !== undefined ) { - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } - jQuery.removeEvent( elem, type, elemData.handle ); - } + i = key.length; - delete events[ type ]; + while ( i-- ) { + delete cache[ key[ i ] ]; } } - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45+ + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } } }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), +var dataPriv = new Data(); - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; +var dataUser = new Data(); - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; - event.delegateTarget = this; +function getData( data ) { + if ( data === "true" ) { + return true; + } - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } + if ( data === "false" ) { + return false; + } - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + if ( data === "null" ) { + return null; + } - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { + return data; +} - event.handleObj = handleObj; - event.data = handleObj.data; +function dataAttr( elem, key, data ) { + var name; - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; } + } + return data; +} - return event.result; +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); }, - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, - // Find delegate handlers - if ( delegateCount && + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); - for ( ; cur !== this; cur = cur.parentNode || this ) { +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; - // Don't check non-elements (trac-13208) - // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); - // Don't conflict with Object.prototype properties (trac-13203) - sel = handleObj.selector + " "; + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); + // Support: IE 11+ + // The attrs elements can be null (trac-14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } } } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } + dataPriv.set( elem, "hasDataAttrs", true ); } } - } - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + return data; } - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; + return access( this, function( value ) { + var data; - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", true ); + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; } - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; + // We tried really hard, but the data doesn't exist. + return; + } - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { + // Set the data... + this.each( function() { - leverageNative( el, "click" ); - } + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, - // Return non-false to allow normal event-path propagation - return true; - }, + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; - beforeunload: { - postDispatch: function( event ) { +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); } -}; +} ); -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, isSetup ) { +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; - // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add - if ( !isSetup ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; } - return; - } - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var result, - saved = dataPriv.get( this, type ); + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - if ( ( event.isTrigger & 1 ) && this[ type ] ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } - // Interrupt processing of the outer synthetic .trigger()ed event - if ( !saved ) { + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); + return ( elem[ name ] = value ); + } - // Trigger the native event and capture its result - this[ type ](); - result = dataPriv.get( this, type ); - dataPriv.set( this, type, false ); + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } - if ( saved !== result ) { + return elem[ name ]; + }, - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); + propHooks: { + tabIndex: { + get: function( elem ) { - return result; - } + // Support: IE <=9 - 11+ + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // Use proper attribute retrieval (trac-12072) + var tabindex = elem.getAttribute( "tabindex" ); - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering - // the native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); + if ( tabindex ) { + return parseInt( tabindex, 10 ); } - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved ) { + if ( + rfocusable.test( elem.nodeName ) || - // ...and capture the result - dataPriv.set( this, type, jQuery.event.trigger( - saved[ 0 ], - saved.slice( 1 ), - this - ) ); + // href-less anchor's `tabIndex` property value is `0` and + // the `tabindex` attribute value: `null`. We want `-1`. + rclickable.test( elem.nodeName ) && elem.href + ) { + return 0; + } - // Abort handling of the native event by all jQuery handlers while allowing - // native handlers on the same element to run. On target, this is achieved - // by stopping immediate propagation just on the jQuery event. However, - // the native event is re-wrapped by a jQuery one on each level of the - // propagation so the only way to stop it for jQuery is to stop it for - // everyone via native `stopPropagation()`. This is not a problem for - // focus/blur which don't bubble, but it does also stop click on checkboxes - // and radios. We accept this limitation. - event.stopPropagation(); - event.isImmediatePropagationStopped = returnTrue; + return -1; } } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { + }, - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); + propFix: { + "for": "htmlFor", + "class": "className" } -}; +} ); -jQuery.Event = function( src, props ) { +// Support: IE <=11+ +// Accessing the selectedIndex property forces the browser to respect +// setting selected on the option. The getter ensures a default option +// is selected when in an optgroup. ESLint rule "no-unused-expressions" +// is disabled for this code since it considers such accessions noop. +if ( isIE ) { + jQuery.propHooks.selected = { + get: function( elem ) { - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + // eslint-disable-next-line no-unused-expressions + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && + var parent = elem.parentNode; + if ( parent ) { + // eslint-disable-next-line no-unused-expressions + parent.selectedIndex; - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; + if ( parent.parentNode ) { + // eslint-disable-next-line no-unused-expressions + parent.parentNode.selectedIndex; + } + } + } + }; +} - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (trac-504, trac-13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; +// Strip and collapse whitespace according to HTML spec +// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace +function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); +} - // Event type - } else { - this.type = src; - } +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); +jQuery.fn.extend( { + addClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; - // Mark it as fixed - this[ jQuery.expando ] = true; -}; + if ( typeof value === "function" ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, + classNames = classesToArray( value ); - preventDefault: function() { - var e = this.originalEvent; + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - this.isDefaultPrevented = returnTrue; + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; + } + } - if ( e && !this.isSimulated ) { - e.preventDefault(); + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); } + + return this; }, - stopPropagation: function() { - var e = this.originalEvent; - this.isPropagationStopped = returnTrue; + removeClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; - if ( e && !this.isSimulated ) { - e.stopPropagation(); + if ( typeof value === "function" ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); + if ( !arguments.length ) { + return this.attr( "class", "" ); } - this.stopPropagation(); - } -}; + classNames = classesToArray( value ); -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - which: true -}, jQuery.event.addProp ); + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + + // This expression is here for better compressibility (see addClass) + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + // Remove *all* instances + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + return this; + }, - function focusMappedHandler( nativeEvent ) { - if ( document.documentMode ) { + toggleClass: function( value, stateVal ) { + var classNames, className, i, self; - // Support: IE 11+ - // Attach a single focusin/focusout handler on the document while someone wants - // focus/blur. This is because the former are synchronous in IE while the latter - // are async. In other browsers, all those handlers are invoked synchronously. + if ( typeof value === "function" ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + if ( typeof stateVal === "boolean" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } - // `handle` from private data would already wrap the event, but we need - // to change the `type` here. - var handle = dataPriv.get( this, "handle" ), - event = jQuery.event.fix( nativeEvent ); - event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; - event.isSimulated = true; + classNames = classesToArray( value ); - // First, handle focusin/focusout - handle( nativeEvent ); + if ( classNames.length ) { + return this.each( function() { - // ...then, handle focus/blur - // - // focus/blur don't bubble while focusin/focusout do; simulate the former by only - // invoking the handler at the lower level. - if ( event.target === event.currentTarget ) { + // Toggle individual class names + self = jQuery( this ); - // The setup part calls `leverageNative`, which, in turn, calls - // `jQuery.event.add`, so event handle will already have been set - // by this point. - handle( event ); - } - } else { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; - // For non-IE browsers, attach a single capturing handler on the document - // while someone wants focusin/focusout. - jQuery.event.simulate( delegateType, nativeEvent.target, - jQuery.event.fix( nativeEvent ) ); + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + } ); } - } - jQuery.event.special[ type ] = { + return this; + }, - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } - var attaches; + return false; + } +} ); - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, true ); +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; - if ( document.documentMode ) { + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - // Support: IE 9 - 11+ - // We use the same native handler for focusin & focus (and focusout & blur) - // so we need to coordinate setup & teardown parts between those events. - // Use `delegateType` as the key as `type` is already used by `leverageNative`. - attaches = dataPriv.get( this, delegateType ); - if ( !attaches ) { - this.addEventListener( delegateType, focusMappedHandler ); + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; } - dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); - } else { - // Return false to allow normal processing in the caller - return false; + ret = elem.value; + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; } - }, - trigger: function() { - // Force setup before trigger - leverageNative( this, type ); + return; + } - // Return non-false to allow normal event-path propagation - return true; - }, + valueIsFunction = typeof value === "function"; - teardown: function() { - var attaches; + return this.each( function( i ) { + var val; - if ( document.documentMode ) { - attaches = dataPriv.get( this, delegateType ) - 1; - if ( !attaches ) { - this.removeEventListener( delegateType, focusMappedHandler ); - dataPriv.remove( this, delegateType ); - } else { - dataPriv.set( this, delegateType, attaches ); - } + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); } else { + val = value; + } - // Return false to indicate standard teardown should be applied - return false; + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); } - }, - // Suppress native focus or blur if we're currently inside - // a leveraged native-event stack - _default: function( event ) { - return dataPriv.get( event.target, type ); - }, + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - delegateType: delegateType - }; + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); - // Support: Firefox <=44 - // Firefox doesn't have focus(in | out) events - // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 - // - // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 - // focus(in | out) events fire after focus & blur events, - // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order - // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 - // - // Support: IE 9 - 11+ - // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch, - // attach a single handler for both events in IE. - jQuery.event.special[ delegateType ] = { - setup: function() { +jQuery.extend( { + valHooks: { + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - dataHolder = document.documentMode ? this : doc, - attaches = dataPriv.get( dataHolder, delegateType ); + if ( index < 0 ) { + i = max; - // Support: IE 9 - 11+ - // We use the same native handler for focusin & focus (and focusout & blur) - // so we need to coordinate setup & teardown parts between those events. - // Use `delegateType` as the key as `type` is already used by `leverageNative`. - if ( !attaches ) { - if ( document.documentMode ) { - this.addEventListener( delegateType, focusMappedHandler ); } else { - doc.addEventListener( type, focusMappedHandler, true ); + i = one ? index : 0; } - } - dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - dataHolder = document.documentMode ? this : doc, - attaches = dataPriv.get( dataHolder, delegateType ) - 1; - if ( !attaches ) { - if ( document.documentMode ) { - this.removeEventListener( delegateType, focusMappedHandler ); - } else { - doc.removeEventListener( type, focusMappedHandler, true ); + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + if ( option.selected && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } } - dataPriv.remove( dataHolder, delegateType ); - } else { - dataPriv.set( dataHolder, delegateType, attaches ); - } - } - }; -} ); -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, + return values; + }, - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; + while ( i-- ) { + option = options[ i ]; + + if ( ( option.selected = + jQuery.inArray( jQuery( option ).val(), values ) > -1 + ) ) { + optionSet = true; + } + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; } - return ret; } - }; + } } ); -jQuery.fn.extend( { +if ( isIE ) { + jQuery.valHooks.option = { + get: function( elem ) { - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { + var val = elem.getAttribute( "value" ); + return val != null ? + val : - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; + // Support: IE <=10 - 11+ + // option.text throws exceptions (trac-14686, trac-14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); } - if ( typeof types === "object" ) { + }; +} - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); } - return this; } - if ( selector === false || typeof selector === "function" ) { + }; +} ); - // ( types [, fn] ) - fn = selector; +var rcheckableType = /^(?:checkbox|radio)$/i; + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; selector = undefined; } - if ( fn === false ) { - fn = returnFalse; + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); + return elem; } -} ); + if ( data == null && fn == null ) { -var + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } - - return elem; + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); } -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { - return elem; -} + add: function( elem, types, handler, data, selector ) { -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); - if ( dest.nodeType !== 1 ) { - return; - } + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } - if ( events ) { - dataPriv.remove( dest, "handle events" ); + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement$1, selector ); + } - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; } - } - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { - dataUser.set( dest, udataCur ); - } -} + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; -function domManip( collection, args, callback, ignored ) { + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; - // Flatten any nested arrays - args = flat( args ); + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } } - domManip( self, args, callback, ignored ); - } ); - } - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; + if ( special.add ) { + special.add.call( elem, handleObj ); - if ( fragment.childNodes.length === 1 ) { - fragment = first; + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } } - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; + }, - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (trac-8070). - for ( ; i < l; i++ ) { - node = fragment; + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); } } - - callback.call( collection[ i ], node, i ); } - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - // Re-enable scripts - jQuery.map( scripts, restoreScript ); + jQuery.removeEvent( elem, type, elemData.handle ); + } - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { + delete events[ type ]; + } + } - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { + dispatch: function( nativeEvent ) { - // Unwrap a CDATA section containing script contents. This shouldn't be - // needed as in XML documents they're already not visible when - // inspecting element contents and in HTML documents they have no - // meaning but we're preserving that logic for backwards compatibility. - // This will be removed completely in 4.0. See gh-4904. - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), - return collection; -} + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; } - } - return elem; -} + event.delegateTarget = this; -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; - // We eschew jQuery#find here for performance reasons: - // https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); + event.handleObj = handleObj; + event.data = handleObj.data; - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } } - } else { - cloneCopyEvent( elem, clone ); } } - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); } - // Return the cloned set - return clone; + return event.result; }, - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); + // Find delegate handlers + if ( delegateCount && - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } + // Support: Firefox <=42 - 66+ + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11+ + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { + for ( ; cur !== this; cur = cur.parentNode || this ) { - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; + // Don't check non-elements (trac-13208) + // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (trac-13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } } } } - } -} ); -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } - remove: function( selector ) { - return remove( this, selector ); + return handlerQueue; }, - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: typeof hook === "function" ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); } - } ); - }, null, value, arguments.length ); - }, + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); } } ); }, - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); }, - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, + special: jQuery.extend( Object.create( null ), { + load: { - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { - empty: function() { - var elem, - i = 0; + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { - // Remove any remaining nodes - elem.textContent = ""; + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", true ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + if ( event.result !== undefined ) { + + // Setting `event.originalEvent.returnValue` in modern + // browsers does the same as just calling `preventDefault()`, + // the browsers ignore the value anyway. + // Incidentally, IE 11 is the only browser from our supported + // ones which respects the value returned from a `beforeunload` + // handler attached by `addEventListener`; other browsers do + // so only for inline handlers, so not setting the value + // directly shouldn't reduce any functionality. + event.preventDefault(); + } } } + } ) +}; - return this; - }, +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, isSetup ) { - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add + if ( !isSetup ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var result, + saved = dataPriv.get( this, type ); - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; + // This controller function is invoked under multiple circumstances, + // differentiated by the stored value in `saved`: + // 1. For an outer synthetic `.trigger()`ed event (detected by + // `event.isTrigger & 1` and non-array `saved`), it records arguments + // as an array and fires an [inner] native event to prompt state + // changes that should be observed by registered listeners (such as + // checkbox toggling and focus updating), then clears the stored value. + // 2. For an [inner] native event (detected by `saved` being + // an array), it triggers an inner synthetic event, records the + // result, and preempts propagation to further jQuery listeners. + // 3. For an inner synthetic event (detected by `event.isTrigger & 1` and + // array `saved`), it prevents double-propagation of surrogate events + // but otherwise allows everything to proceed (particularly including + // further listeners). + // Possible `saved` data shapes: `[...], `{ value }`, `false`. + if ( ( event.isTrigger & 1 ) && this[ type ] ) { - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } + // Interrupt processing of the outer synthetic .trigger()ed event + if ( !saved.length ) { - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), + // so this array will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); - value = jQuery.htmlPrefilter( value ); + // Trigger the native event and capture its result + this[ type ](); + result = dataPriv.get( this, type ); + dataPriv.set( this, type, false ); - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; + if ( saved !== result ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is + // blurred by clicking outside of it, it invokes the handler + // synchronously. If that handler calls `.remove()` on + // the element, the data is cleared, leaving `result` + // undefined. We need to guard against this. + return result && result.value; } - elem = 0; + // If this is an inner synthetic event for an event with a bubbling + // surrogate (focus or blur), assume that the surrogate already + // propagated from triggering the native event and prevent that + // from happening again here. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } + // If this is a native event triggered above, everything is now in order. + // Fire an inner synthetic event with the original arguments. + } else if ( saved.length ) { - if ( elem ) { - this.empty().append( value ); + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + saved[ 0 ], + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event by all jQuery handlers while allowing + // native handlers on the same element to run. On target, this is achieved + // by stopping immediate propagation just on the jQuery event. However, + // the native event is re-wrapped by a jQuery one on each level of the + // propagation so the only way to stop it for jQuery is to stop it for + // everyone via native `stopPropagation()`. This is not a problem for + // focus/blur which don't bubble, but it does also stop click on checkboxes + // and radios. We accept this limitation. + event.stopPropagation(); + event.isImmediatePropagationStopped = returnTrue; } - }, null, value, arguments.length ); - }, + } + } ); +} - replaceWith: function() { - var ignored = []; +jQuery.removeEvent = function( elem, type, handle ) { - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } +jQuery.Event = function( src, props ) { - // Force callback invocation - }, ignored ); + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); } -} ); -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented ? + returnTrue : + returnFalse; - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } + // Create target properties + this.target = src.target; + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } -var rcustomProp = /^--/; + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, -var getStyles = function( elem ) { + preventDefault: function() { + var e = this.originalEvent; - // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; + this.isDefaultPrevented = returnTrue; - if ( !view || !view.opener ) { - view = window; + if ( e && !this.isSimulated ) { + e.preventDefault(); } + }, + stopPropagation: function() { + var e = this.originalEvent; - return view.getComputedStyle( elem ); - }; + this.isPropagationStopped = returnTrue; -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } + this.isImmediatePropagationStopped = returnTrue; - ret = callback.call( elem ); + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; + this.stopPropagation(); } - - return ret; }; +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + // Support: IE 11+ + // Attach a single focusin/focusout handler on the document while someone wants focus/blur. + // This is because the former are synchronous in IE while the latter are async. In other + // browsers, all those handlers are invoked synchronously. + function focusMappedHandler( nativeEvent ) { -( function() { + // `eventHandle` would already wrap the event, but we need to change the `type` here. + var event = jQuery.event.fix( nativeEvent ); + event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; + event.isSimulated = true; - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { + // focus/blur don't bubble while focusin/focusout do; simulate the former by only + // invoking the handler at the lower level. + if ( event.target === event.currentTarget ) { - // This is a singleton, we need to execute it only once - if ( !div ) { - return; + // The setup part calls `leverageNative`, which, in turn, calls + // `jQuery.event.add`, so event handle will already have been set + // by this point. + dataPriv.get( this, "handle" )( event ); } + } + + jQuery.event.special[ type ] = { - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, true ); - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + if ( isIE ) { + this.addEventListener( delegateType, focusMappedHandler ); + } else { - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + // Return false to allow normal processing in the caller + return false; + } + }, + trigger: function() { - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + // Force setup before trigger + leverageNative( this, type ); - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + // Return non-false to allow normal event-path propagation + return true; + }, - documentElement.removeChild( container ); + teardown: function() { + if ( isIE ) { + this.removeEventListener( delegateType, focusMappedHandler ); + } else { - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } + // Return false to indicate standard teardown should be applied + return false; + } + }, - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } + // Suppress native focus or blur if we're currently inside + // a leveraged native-event stack + _default: function( event ) { + return dataPriv.get( event.target, type ); + }, - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); + delegateType: delegateType + }; +} ); - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (trac-8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - // - // Support: Firefox 70+ - // Only Firefox includes border widths - // in computed dimensions. (gh-4529) - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; - tr.style.cssText = "box-sizing:content-box;border:1px solid"; - - // Support: Chrome 86+ - // Height set through cssText does not get applied. - // Computed height then comes back as 0. - tr.style.height = "1px"; - trChild.style.height = "9px"; - - // Support: Android 8 Chrome 86+ - // In our bodyBackground.html iframe, - // display for all div elements is set to "inline", - // which causes a problem only in Android 8 Chrome 86. - // Ensuring the div is `display: block` - // gets around this issue. - trChild.style.display = "block"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + - parseInt( trStyle.borderTopWidth, 10 ) + - parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; } - } ); -} )(); + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - isCustomProp = rcustomProp.test( name ), + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; - computed = computed || getStyles( elem ); +jQuery.extend( jQuery.event, { - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, trac-12537) - // .css('--customProperty) (gh-3144) - if ( computed ) { + trigger: function( event, data, elem, onlyHandlers ) { - // Support: IE <=9 - 11+ - // IE only supports `"float"` in `getPropertyValue`; in computed styles - // it's only available as `"cssFloat"`. We no longer modify properties - // sent to `.css()` apart from camelCasing, so we need to check both. - // Normally, this would create difference in behavior: if - // `getPropertyValue` returns an empty string, the value returned - // by `.css()` would be `undefined`. This is usually the case for - // disconnected elements. However, in IE even disconnected elements - // with no styles return `"none"` for `getPropertyValue( "float" )` - ret = computed.getPropertyValue( name ) || computed[ name ]; + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document$1 ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - if ( isCustomProp && ret ) { + cur = lastElement = tmp = elem = elem || document$1; - // Support: Firefox 105+, Chrome <=105+ - // Spec requires trimming whitespace for custom properties (gh-4926). - // Firefox only trims leading whitespace. Chrome just collapses - // both leading & trailing whitespace to a single space. - // - // Fall back to `undefined` if empty string returned. - // This collapses a missing definition with property defined - // and set to an empty string but there's no standard API - // allowing us to differentiate them without a performance penalty - // and returning `undefined` aligns with older jQuery. - // - // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED - // as whitespace while CSS does not, but this is not a problem - // because CSS preprocessing replaces them with U+000A LINE FEED - // (which *is* CSS whitespace) - // https://www.w3.org/TR/css-syntax-3/#input-preprocessing - ret = ret.replace( rtrimCSS, "$1" ) || undefined; + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; } - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; } - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; } - } - return ret !== undefined ? + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (trac-9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } -function addGetHookIf( conditionFn, hookFn ) { + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document$1 ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); } - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } } - }; -} - + event.type = type; -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (trac-6170) + if ( ontype && typeof elem[ type ] === "function" && !isWindow( elem ) ) { - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + if ( tmp ) { + elem[ ontype ] = null; + } - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } -var + elem[ type ](); - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } -function setPositiveNumber( _elem, value, subtract ) { + jQuery.event.triggered = undefined; - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} + return event.result; + }, -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0, - marginDelta = 0; + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; + jQuery.event.trigger( e, null, elem ); } - for ( ; i < 4; i += 2 ) { +} ); - // Both box models exclude margin - // Count margin delta separately to only add it after scroll gutter adjustment. - // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). - if ( box === "margin" ) { - marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } +jQuery.fn.extend( { - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); +var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }, + composed = { composed: true }; + +// Support: IE 9 - 11+ +// Check attachment across shadow DOM boundaries when possible (gh-3504). +// Provide a fallback for browsers without Shadow DOM v1 support. +if ( !documentElement$1.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }; +} - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); +// rtagName captures the name from the first start tag in a string of HTML +// https://html.spec.whatwg.org/multipage/syntax.html#tag-open-state +// https://html.spec.whatwg.org/multipage/syntax.html#tag-name-state +var rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i; - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } +var wrapMap = { - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { + // Table parts need to be wrapped with `` or they're + // stripped to their contents when put in a div. + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do, so we cannot shorten + // this by omitting or other required elements. + thead: [ "table" ], + col: [ "colgroup", "table" ], + tr: [ "tbody", "table" ], + td: [ "tr", "tbody", "table" ] +}; - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } +function getAll( context, tag ) { - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { + // Support: IE <=9 - 11+ + // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) + var ret; - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 + if ( typeof context.getElementsByTagName !== "undefined" ) { - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } + // Use slice to snapshot the live collection from gEBTN + ret = arr.slice.call( context.getElementsByTagName( tag || "*" ) ); - return delta + marginDelta; -} + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); -function getWidthOrHeight( elem, dimension, extra ) { + } else { + ret = []; + } - // Start with computed style - var styles = getStyles( elem ), + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, + return ret; +} - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); +var rscriptType = /^$|^module$|\/(?:java|ecma)script/i; - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); } +} +var rhtml = /<|&#?\w+;/; - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + for ( ; i < l; i++ ) { + elem = elems[ i ]; - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || + if ( elem || elem === 0 ) { - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + // Add nodes directly + if ( toType( elem ) === "object" && ( elem.nodeType || isArrayLike( elem ) ) ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - // Make sure the element is visible & connected - elem.getClientRects().length ) { + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || arr; - // Normalize "" and auto - val = parseFloat( val ) || 0; + // Create wrappers & descend into them. + j = wrap.length; + while ( --j > -1 ) { + tmp = tmp.appendChild( context.createElement( wrap[ j ] ) ); + } - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, + tmp.innerHTML = jQuery.htmlPrefilter( elem ); - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} + jQuery.merge( nodes, tmp.childNodes ); -jQuery.extend( { + // Remember the top-level container + tmp = fragment.firstChild; - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } + // Ensure the created nodes are orphaned (trac-12392) + tmp.textContent = ""; } } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - animationIterationCount: true, - aspectRatio: true, - borderImageSlice: true, - columnCount: true, - flexGrow: true, - flexShrink: true, - fontWeight: true, - gridArea: true, - gridColumn: true, - gridColumnEnd: true, - gridColumnStart: true, - gridRow: true, - gridRowEnd: true, - gridRowStart: true, - lineHeight: true, - opacity: true, - order: true, - orphans: true, - scale: true, - widows: true, - zIndex: true, - zoom: true, - - // SVG-related - fillOpacity: true, - floodOpacity: true, - stopOpacity: true, - strokeMiterlimit: true, - strokeOpacity: true - }, + } - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, + // Remove wrapper from fragment + fragment.textContent = ""; - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; } - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; + attached = isAttached( elem ); - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); } - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; + return fragment; +} - // Convert "+=" or "-=" to relative numbers (trac-7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } - // Fixes bug trac-9237 - type = "number"; - } + return elem; +} - // Make sure that null and NaN values aren't set (trac-7116) - if ( value == null || value !== value ) { - return; - } +function domManip( collection, args, callback, ignored ) { - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } + // Flatten any nested arrays + args = flat( args ); - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = typeof value === "function"; - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + if ( valueIsFunction ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + args[ 0 ] = value.call( this, index, self.html() ); + domManip( self, args, callback, ignored ); + } ); + } - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; - } else { + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; - return ret; - } + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (trac-8070). + for ( ; i < l; i++ ) { + node = fragment; - // Otherwise just get the value from the style object - return style[ name ]; - } - }, + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } + callback.call( collection[ i ], node, i ); + } - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } + // Re-enable scripts + jQuery.map( scripts, restoreScript ); - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.get( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce, + crossOrigin: node.crossOrigin + }, doc ); + } + } else { + DOMEval( node.textContent, node, doc ); + } + } + } + } } - - return val; } -} ); -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { + return collection; +} + +var - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, + // Support: IE <=10 - 11+ + // In IE using regex groups here causes severe slowdowns. + rnoInnerhtml = / 1 ); - } -} ); + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + // Fix IE cloning issues + if ( isIE && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { -// Based off of the plugin by Clint Helfers, with permission. -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; + // We eschew jQuery#find here for performance reasons: + // https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; + for ( i = 0, l = srcElements.length; i < l; i++ ) { + // Support: IE <=11+ + // IE fails to set the defaultValue to the correct value when + // cloning textareas. + if ( nodeName( destElements[ i ], "textarea" ) ) { + destElements[ i ].defaultValue = srcElements[ i ].defaultValue; + } + } + } -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); - input.type = "checkbox"; + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; + // Return the cloned set + return clone; + }, - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); -var boolHook, - attrHandle = jQuery.expr.attrHandle; + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } } } ); -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } + remove: function( selector ) { + return remove( this, selector ); + }, - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); } + } ); + }, - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); } + } ); + }, - elem.setAttribute( name, value + "" ); - return value; - } + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } + empty: function() { + var elem, + i = 0; - ret = jQuery.find.attr( elem, name ); + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } + // Remove any remaining nodes + elem.textContent = ""; } } + + return this; }, - removeAttr: function( elem, value ) { - var name, - i = 0, + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; + value = jQuery.htmlPrefilter( value ); - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; - if ( !isXML ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); + elem = 0; + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + replaceWith: function() { + var ignored = []; -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); + // Force callback invocation + }, ignored ); } } ); -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + push.apply( ret, elems ); } - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + return this.pushStack( ret ); + }; +} ); - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; + if ( this[ 0 ] ) { + if ( typeof html === "function" ) { + html = html.call( this[ 0 ] ); } - return ( elem[ name ] = value ); - } + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); } - return elem[ name ]; + return this; }, - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // Use proper attribute retrieval (trac-12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); + wrapInner: function( html ) { + if ( typeof html === "function" ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } + if ( contents.length ) { + contents.wrapAll( html ); - return -1; + } else { + self.append( html ); } - } + } ); }, - propFix: { - "for": "htmlFor", - "class": "className" + wrap: function( html ) { + var htmlIsFunction = typeof html === "function"; + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; } } ); -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { +var pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source; - /* eslint no-unused-expressions: "off" */ +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - /* eslint no-unused-expressions: "off" */ +var rcustomProp = /^--/; - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; +var ralphaStart = /^[a-z]/, + + // The regex visualized: + // + // /----------\ + // | | /-------\ + // | / Top \ | | | + // /--- Border ---+-| Right |-+---+- Width -+---\ + // | | Bottom | | + // | \ Left / | + // | | + // | /----------\ | + // | /-------------\ | | |- END + // | | | | / Top \ | | + // | | / Margin \ | | | Right | | | + // |---------+-| |-+---+-| Bottom |-+----| + // | \ Padding / \ Left / | + // BEGIN -| | + // | /---------\ | + // | | | | + // | | / Min \ | / Width \ | + // \--------------+-| |-+---| |---/ + // \ Max / \ Height / + rautoPx = /^(?:Border(?:Top|Right|Bottom|Left)?(?:Width|)|(?:Margin|Padding)?(?:Top|Right|Bottom|Left)?|(?:Min|Max)?(?:Width|Height))$/; + +function isAutoPx( prop ) { + + // The first test is used to ensure that: + // 1. The prop starts with a lowercase letter (as we uppercase it for the second regex). + // 2. The prop is not empty. + return ralphaStart.test( prop ) && + rautoPx.test( prop[ 0 ].toUpperCase() + prop.slice( 1 ) ); } -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/; + +// Convert dashed to camelCase, handle vendor prefixes. +// Used by the css & effects modules. +// Support: IE <=9 - 11+ +// Microsoft forgot to hump their vendor prefix (trac-9572) +function cssCamelCase( string ) { + return camelCase( string.replace( rmsPrefix, "ms-" ) ); +} + +function getStyles( elem ) { + + // Support: IE <=11+ (trac-14150) + // In IE popup's `window` is the opener window which makes `window.getComputedStyle( elem )` + // break. Using `elem.ownerDocument.defaultView` avoids the issue. + var view = elem.ownerDocument.defaultView; + + // `document.implementation.createHTMLDocument( "" )` has a `null` `defaultView` + // property; check `defaultView` truthiness to fallback to window in such a case. + if ( !view ) { + view = window; + } + + return view.getComputedStyle( elem ); +} +// A method for quickly swapping in/out CSS properties to get correct calculations. +function swap( elem, options, callback ) { + var ret, name, + old = {}; + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + ret = callback.call( elem ); - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; } - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; + return ret; } -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} +function curCSS( elem, name, computed ) { + var ret, + isCustomProp = rcustomProp.test( name ); -jQuery.fn.extend( { - addClass: function( value ) { - var classNames, cur, curValue, className, i, finalValue; + computed = computed || getStyles( elem ); - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } + // getPropertyValue is needed for `.css('--customProperty')` (gh-3144) + if ( computed ) { - classNames = classesToArray( value ); + // A fallback to direct property access is needed as `computed`, being + // the output of `getComputedStyle`, contains camelCased keys and + // `getPropertyValue` requires kebab-case ones. + // + // Support: IE <=9 - 11+ + // IE only supports `"float"` in `getPropertyValue`; in computed styles + // it's only available as `"cssFloat"`. We no longer modify properties + // sent to `.css()` apart from camelCasing, so we need to check both. + // Normally, this would create difference in behavior: if + // `getPropertyValue` returns an empty string, the value returned + // by `.css()` would be `undefined`. This is usually the case for + // disconnected elements. However, in IE even disconnected elements + // with no styles return `"none"` for `getPropertyValue( "float" )` + ret = computed.getPropertyValue( name ) || computed[ name ]; - if ( classNames.length ) { - return this.each( function() { - curValue = getClass( this ); - cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + if ( isCustomProp && ret ) { - if ( cur ) { - for ( i = 0; i < classNames.length; i++ ) { - className = classNames[ i ]; - if ( cur.indexOf( " " + className + " " ) < 0 ) { - cur += className + " "; - } - } + // Support: Firefox 105 - 135+ + // Spec requires trimming whitespace for custom properties (gh-4926). + // Firefox only trims leading whitespace. + // + // Fall back to `undefined` if empty string returned. + // This collapses a missing definition with property defined + // and set to an empty string but there's no standard API + // allowing us to differentiate them without a performance penalty + // and returning `undefined` aligns with older jQuery. + // + // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED + // as whitespace while CSS does not, but this is not a problem + // because CSS preprocessing replaces them with U+000A LINE FEED + // (which *is* CSS whitespace) + // https://www.w3.org/TR/css-syntax-3/#input-preprocessing + ret = ret.replace( rtrimCSS, "$1" ) || undefined; + } - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - this.setAttribute( "class", finalValue ); - } - } - } ); + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); } + } - return this; - }, + return ret !== undefined ? - removeClass: function( value ) { - var classNames, cur, curValue, className, i, finalValue; + // Support: IE <=9 - 11+ + // IE returns zIndex value as an integer. + ret + "" : + ret; +} - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( isAutoPx( prop ) ? "px" : "" ), - if ( !arguments.length ) { - return this.attr( "class", "" ); - } + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( !isAutoPx( prop ) || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); - classNames = classesToArray( value ); + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - if ( classNames.length ) { - return this.each( function() { - curValue = getClass( this ); + // Support: Firefox <=54 - 66+ + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; - // This expression is here for better compressibility (see addClass) - cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; - if ( cur ) { - for ( i = 0; i < classNames.length; i++ ) { - className = classNames[ i ]; + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; - // Remove *all* instances - while ( cur.indexOf( " " + className + " " ) > -1 ) { - cur = cur.replace( " " + className + " ", " " ); - } - } + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - this.setAttribute( "class", finalValue ); - } - } - } ); } - return this; - }, + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); - toggleClass: function( value, stateVal ) { - var classNames, className, i, self, - type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + } + return adjusted; +} - classNames = classesToArray( value ); +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document$1.createElement( "div" ).style; - return this.each( function() { - if ( isValidValue ) { +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { - // Toggle individual class names - self = jQuery( this ); + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; - for ( i = 0; i < classNames.length; i++ ) { - className = classNames[ i ]; + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } +// Return a potentially-mapped vendor prefixed property +function finalPropName( name ) { + if ( name in emptyStyle ) { + return name; + } + return vendorPropName( name ) || name; +} - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { +var reliableTrDimensionsVal, reliableColDimensionsVal, + table = document$1.createElement( "table" ); - // Store className if set - dataPriv.set( this, "__className__", className ); - } +// Executing table tests requires only one layout, so they're executed +// at the same time to save the second computation. +function computeTableStyleTests() { + if ( - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, + // This is a singleton, we need to execute it only once + !table || - hasClass: function( selector ) { - var className, elem, - i = 0; + // Finish early in limited (non-browser) environments + !table.style + ) { + return; + } - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } + var trStyle, + col = document$1.createElement( "col" ), + tr = document$1.createElement( "tr" ), + td = document$1.createElement( "td" ); - return false; + table.style.cssText = "position:absolute;left:-11111px;" + + "border-collapse:separate;border-spacing:0"; + tr.style.cssText = "box-sizing:content-box;border:1px solid;height:1px"; + td.style.cssText = "height:9px;width:9px;padding:0"; + + col.span = 2; + + documentElement$1 + .appendChild( table ) + .appendChild( col ) + .parentNode + .appendChild( tr ) + .appendChild( td ) + .parentNode + .appendChild( td.cloneNode( true ) ); + + // Don't run until window is visible + if ( table.offsetWidth === 0 ) { + documentElement$1.removeChild( table ); + return; } -} ); + trStyle = window.getComputedStyle( tr ); + + // Support: Firefox 135+ + // Firefox always reports computed width as if `span` was 1. + // Support: Safari 18.3+ + // In Safari, computed width for columns is always 0. + // In both these browsers, using `offsetWidth` solves the issue. + // Support: IE 11+ + // In IE, `` computed width is `"auto"` unless `width` is set + // explicitly via CSS so measurements there remain incorrect. Because of + // the lack of a proper workaround, we accept this limitation, treating + // IE as passing the test. + reliableColDimensionsVal = isIE || Math.round( parseFloat( + window.getComputedStyle( col ).width ) + ) === 18; + + // Support: IE 10 - 11+ + // IE misreports `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Support: Firefox 70 - 135+ + // Only Firefox includes border widths + // in computed dimensions for table rows. (gh-4529) + reliableTrDimensionsVal = Math.round( parseFloat( trStyle.height ) + + parseFloat( trStyle.borderTopWidth ) + + parseFloat( trStyle.borderBottomWidth ) ) === tr.offsetHeight; + + documentElement$1.removeChild( table ); + + // Nullify the table so it wouldn't be stored in the memory; + // it will also be a sign that checks were already performed. + table = null; +} +jQuery.extend( support, { + reliableTrDimensions: function() { + computeTableStyleTests(); + return reliableTrDimensionsVal; + }, + reliableColDimensions: function() { + computeTableStyleTests(); + return reliableColDimensionsVal; + } +} ); -var rreturn = /\r/g; +var cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; +function setPositiveNumber( _elem, value, subtract ) { - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} - ret = elem.value; +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0, + marginDelta = 0; - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } + for ( ; i < 4; i += 2 ) { - return; + // Both box models exclude margin + // Count margin delta separately to only add it after scroll gutter adjustment. + // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). + if ( box === "margin" ) { + marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } - valueIsFunction = isFunction( value ); + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { - return this.each( function( i ) { - var val; + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - if ( this.nodeType !== 1 ) { - return; - } + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); + // But still keep track of it otherwise } else { - val = value; + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } - } ); + } } -} ); -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 - // Support: IE <=10 - 11 only - // option.text throws exceptions (trac-14686, trac-14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } - if ( index < 0 ) { - i = max; + return delta + marginDelta; +} - } else { - i = one ? index : 0; - } +function getWidthOrHeight( elem, dimension, extra ) { - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; + // Start with computed style + var styles = getStyles( elem ), - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (trac-2551) - if ( ( option.selected || i === index ) && + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = isIE || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - // Get the specific value for the option - value = jQuery( option ).val(); + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } - // We don't need an array for one selects - if ( one ) { - return value; - } - // Multi-Selects return an array - values.push( value ); - } - } + if ( + ( - return values; - }, + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; + // Support: IE 9 - 11+ + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + ( isIE && isBorderBox ) || - while ( i-- ) { - option = options[ i ]; + ( !support.reliableColDimensions() && nodeName( elem, "col" ) ) || - /* eslint-disable no-cond-assign */ + ( !support.reliableTrDimensions() && nodeName( elem, "tr" ) ) + ) && - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } + // Make sure the element is visible & connected + elem.getClientRects().length ) { - /* eslint-enable no-cond-assign */ - } + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; } } -} ); -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} +jQuery.extend( { + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: {}, + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { -// Return jQuery for attributes-only inclusion + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + // Make sure that we're working with the right name + var ret, type, hooks, + origName = cssCamelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml, parserErrorElem; - if ( !data || typeof data !== "string" ) { - return null; - } + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) {} + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; - if ( !xml || parserErrorElem ) { - jQuery.error( "Invalid XML: " + ( - parserErrorElem ? - jQuery.map( parserErrorElem.childNodes, function( el ) { - return el.textContent; - } ).join( "\n" ) : - data - ) ); - } - return xml; -}; + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + // Convert "+=" or "-=" to relative numbers (trac-7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; + // Fixes bug trac-9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (trac-7116) + if ( value == null || value !== value ) { + return; + } + + // If the value is a number, add `px` for certain CSS properties + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( isAutoPx( origName ) ? "px" : "" ); + } -jQuery.extend( jQuery.event, { + // Support: IE <=9 - 11+ + // background-* props of a cloned element affect the source element (trac-8908) + if ( isIE && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } - trigger: function( event, data, elem, onlyHandlers ) { + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } - cur = lastElement = tmp = elem = elem || document; + } else { - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; } + }, - if ( type.indexOf( "." ) > -1 ) { + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = cssCamelCase( name ), + isCustomProp = rcustomProp.test( name ); - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); } - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; } - // Determine event propagation path in advance, per W3C events spec (trac-9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + return val; + } +} ); - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Elements with `display: none` can have dimension info if + // we invisibly show them. + return jQuery.css( elem, "display" ) === "none" ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); } + }, - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + isBorderBox = extra && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); } + + return setPositiveNumber( elem, value, subtract ); } + }; +} ); - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; } - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); } + + return map; } - } - event.type = type; - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (trac-6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { +// isHiddenWithinTree reports if an element has a non-"none" display style (inline and/or +// through the CSS cascade), which is useful in deciding whether or not to make it visible. +// It differs from the :hidden selector (jQuery.expr.pseudos.hidden) in two important ways: +// * A hidden ancestor does not force an element to be classified as hidden. +// * Being disconnected from the document does not force an element to be classified as hidden. +// These differences improve the behavior of .toggle() et al. when applied to elements that are +// detached or contained within hidden ancestors (gh-2404, gh-2863). +function isHiddenWithinTree( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + jQuery.css( elem, "display" ) === "none"; +} - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; +var defaultDisplayMap = {}; - if ( tmp ) { - elem[ ontype ] = null; - } +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; + if ( display ) { + return display; + } - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); - elem[ type ](); + temp.parentNode.removeChild( temp ); - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; - jQuery.event.triggered = undefined; + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } - if ( tmp ) { - elem[ ontype ] = tmp; - } + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; } } - } - - return event.result; - }, + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); } - ); + } + } - jQuery.event.trigger( e, null, elem ); + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } } -} ); + return elements; +} jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } - trigger: function( type, data ) { return this.each( function() { - jQuery.event.trigger( type, data, this ); + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } } } ); - var rbracket = /\[\]$/, rCRLF = /\r?\n/g, @@ -7968,7 +6262,7 @@ jQuery.param = function( a, traditional ) { add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? + var value = typeof valueOrFunction === "function" ? valueOrFunction() : valueOrFunction; @@ -8036,102 +6330,38 @@ jQuery.fn.extend( { } } ); - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; } -} ); + // Support: IE 9 - 11+ + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; }; - - - -// Support: Safari 8 only -// In Safari 8 documents created via document.implementation.createHTMLDocument -// collapse sibling forms: the second one becomes a child of the first one. -// Because of that, this security measure has to be disabled in Safari 8. -// https://bugs.webkit.org/show_bug.cgi?id=137337 -support.createHTMLDocument = ( function() { - var body = document.implementation.createHTMLDocument( "" ).body; - body.innerHTML = ""; - return body.childNodes.length === 2; -} )(); - - -// Argument "data" should be string of html +// Argument "data" should be string of html or a TrustedHTML wrapper of obvious HTML // context (optional): If specified, the fragment will be created in this context, // defaults to document // keepScripts (optional): If true, will include scripts passed in the html string jQuery.parseHTML = function( data, context, keepScripts ) { - if ( typeof data !== "string" ) { + if ( typeof data !== "string" && !isObviousHtml( data + "" ) ) { return []; } if ( typeof context === "boolean" ) { @@ -8139,24 +6369,14 @@ jQuery.parseHTML = function( data, context, keepScripts ) { context = false; } - var base, parsed, scripts; + var parsed, scripts; if ( !context ) { // Stop scripts or inline event handlers from being executed immediately - // by using document.implementation - if ( support.createHTMLDocument ) { - context = document.implementation.createHTMLDocument( "" ); - - // Set the base href for the created document - // so any parsed elements with URLs - // are based on the document's URL (gh-2965) - base = context.createElement( "base" ); - base.href = document.location.href; - context.head.appendChild( base ); - } else { - context = document; - } + // by using DOMParser + context = ( new window.DOMParser() ) + .parseFromString( "", "text/html" ); } parsed = rsingleTag.exec( data ); @@ -8176,7 +6396,6 @@ jQuery.parseHTML = function( data, context, keepScripts ) { return jQuery.merge( [], parsed.childNodes ); }; - jQuery.offset = { setOffset: function( elem, options, i ) { var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, @@ -8207,7 +6426,7 @@ jQuery.offset = { curLeft = parseFloat( curCSSLeft ) || 0; } - if ( isFunction( options ) ) { + if ( typeof options === "function" ) { // Use jQuery.extend here to allow modification of coordinates argument (gh-1848) options = options.call( elem, i, jQuery.extend( {}, curOffset ) ); @@ -8251,7 +6470,7 @@ jQuery.fn.extend( { } // Return zeros for disconnected and hidden (display: none) elements (gh-2310) - // Support: IE <=11 only + // Support: IE <=11+ // Running getBoundingClientRect on a // disconnected node in IE throws an error if ( !elem.getClientRects().length ) { @@ -8292,12 +6511,13 @@ jQuery.fn.extend( { doc = elem.ownerDocument; offsetParent = elem.offsetParent || doc.documentElement; while ( offsetParent && - ( offsetParent === doc.body || offsetParent === doc.documentElement ) && + offsetParent !== doc.documentElement && jQuery.css( offsetParent, "position" ) === "static" ) { - offsetParent = offsetParent.parentNode; + offsetParent = offsetParent.offsetParent || doc.documentElement; } - if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) { + if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 && + jQuery.css( offsetParent, "position" ) !== "static" ) { // Incorporate borders into its offset, since they are outside its content origin parentOffset = jQuery( offsetParent ).offset(); @@ -8331,7 +6551,7 @@ jQuery.fn.extend( { offsetParent = offsetParent.offsetParent; } - return offsetParent || documentElement; + return offsetParent || documentElement$1; } ); } } ); @@ -8368,28 +6588,6 @@ jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( }; } ); -// Support: Safari <=7 - 9.1, Chrome <=37 - 49 -// Add the top/left cssHooks using jQuery.fn.position -// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 -// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 -// getComputedStyle returns percent when specified for top/left/bottom/right; -// rather than make the css module depend on the offset module, just check for it here -jQuery.each( [ "top", "left" ], function( _i, prop ) { - jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, - function( elem, computed ) { - if ( computed ) { - computed = curCSS( elem, prop ); - - // If curCSS returns percentage, fallback to offset - return rnumnonpx.test( computed ) ? - jQuery( elem ).position()[ prop ] + "px" : - computed; - } - } - ); -} ); - - // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { jQuery.each( { @@ -8439,7 +6637,6 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { } ); } ); - jQuery.fn.extend( { bind: function( types, data, fn ) { @@ -8482,15 +6679,6 @@ jQuery.each( } ); - - - -// Support: Android <=4.0 only -// Make sure we trim BOM and NBSP -// Require that the "whitespace run" starts from a non-whitespace -// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position. -var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g; - // Bind a function to a context, optionally partially applying any // arguments. // jQuery.proxy is deprecated to promote standards (specifically Function#bind) @@ -8506,7 +6694,7 @@ jQuery.proxy = function( fn, context ) { // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. - if ( !isFunction( fn ) ) { + if ( typeof fn !== "function" ) { return undefined; } @@ -8529,37 +6717,8 @@ jQuery.holdReady = function( hold ) { jQuery.ready( true ); } }; -jQuery.isArray = Array.isArray; -jQuery.parseJSON = JSON.parse; -jQuery.nodeName = nodeName; -jQuery.isFunction = isFunction; -jQuery.isWindow = isWindow; -jQuery.camelCase = camelCase; -jQuery.type = toType; - -jQuery.now = Date.now; - -jQuery.isNumeric = function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); -}; - -jQuery.trim = function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "$1" ); -}; - +jQuery.expr[ ":" ] = jQuery.expr.filters = jQuery.expr.pseudos; // Register as a named AMD module, since jQuery can be concatenated with other // files that may use define, but not via a proper concatenation script that @@ -8580,9 +6739,6 @@ if ( typeof define === "function" && define.amd ) { } ); } - - - var // Map over jQuery in case of overwrite @@ -8604,14 +6760,97 @@ jQuery.noConflict = function( deep ) { }; // Expose jQuery and $ identifiers, even in AMD -// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557) +// (trac-7102#comment:10, gh-557) // and CommonJS for browser emulators (trac-13566) if ( typeof noGlobal === "undefined" ) { window.jQuery = window.$ = jQuery; } +var readyCallbacks = [], + whenReady = function( fn ) { + readyCallbacks.push( fn ); + }, + executeReady = function( fn ) { + + // Prevent errors from freezing future callback execution (gh-1823) + // Not backwards-compatible as this does not execute sync + window.setTimeout( function() { + fn.call( document$1, jQuery ); + } ); + }; + +jQuery.fn.ready = function( fn ) { + whenReady( fn ); + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See trac-6781 + readyWait: 1, + + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + whenReady = function( fn ) { + readyCallbacks.push( fn ); + + while ( readyCallbacks.length ) { + fn = readyCallbacks.shift(); + if ( typeof fn === "function" ) { + executeReady( fn ); + } + } + }; + + whenReady(); + } +} ); + +// Make jQuery.ready Promise consumable (gh-1778) +jQuery.ready.then = jQuery.fn.ready; + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document$1.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +if ( document$1.readyState !== "loading" ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + // Use the handy event callback + document$1.addEventListener( "DOMContentLoaded", completed ); + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} return jQuery; + } ); diff --git a/src/static/templates/admin/organizations.hbs b/src/static/templates/admin/organizations.hbs index 130bb14b..14f06e34 100644 --- a/src/static/templates/admin/organizations.hbs +++ b/src/static/templates/admin/organizations.hbs @@ -59,7 +59,7 @@ - + diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs index 52458012..73f0cbc8 100644 --- a/src/static/templates/admin/users.hbs +++ b/src/static/templates/admin/users.hbs @@ -153,7 +153,7 @@ - + From 7f65a254b3a7439fc4dd882706727f5b7e505cee Mon Sep 17 00:00:00 2001 From: "Helmut K. C. Tessarek" Date: Sun, 1 Feb 2026 16:35:03 -0500 Subject: [PATCH 03/41] refactor: improve tooltips in diagnostics page (#6765) The term "seems to" is used too loosely in many of the tooltips, but in these 2 instances it is wrong wording. An update is either available or not. If there is no update, one could argue that "seems to" is valid, since the Internet could be down to check for a new version. But in this situation the update is availble. It is impossible that an update seems to be available. --- src/static/templates/admin/diagnostics.hbs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs index f8edabb2..77f2c95b 100644 --- a/src/static/templates/admin/diagnostics.hbs +++ b/src/static/templates/admin/diagnostics.hbs @@ -8,7 +8,7 @@
Server Installed Ok - Update + Update Branched
@@ -23,8 +23,8 @@ {{#if page_data.web_vault_enabled}}
Web Installed Ok - Update - Pre-Release + Update + Pre-Release
{{page_data.active_web_release}} From 347279a12c2ad3b99faf01e71a8dc20dbf521ad7 Mon Sep 17 00:00:00 2001 From: Timshel Date: Mon, 2 Feb 2026 05:35:22 +0800 Subject: [PATCH 04/41] Empty AccountKeys when no private key (#6761) Co-authored-by: Timshel --- src/api/identity.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 9eaa6b36..f5f2afd6 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -482,14 +482,18 @@ async fn authenticated_response( Value::Null }; - let account_keys = json!({ - "publicKeyEncryptionKeyPair": { - "wrappedPrivateKey": user.private_key, - "publicKey": user.public_key, - "Object": "publicKeyEncryptionKeyPair" - }, - "Object": "privateKeys" - }); + let account_keys = if user.private_key.is_some() { + json!({ + "publicKeyEncryptionKeyPair": { + "wrappedPrivateKey": user.private_key, + "publicKey": user.public_key, + "Object": "publicKeyEncryptionKeyPair" + }, + "Object": "privateKeys" + }) + } else { + Value::Null + }; let mut result = json!({ "access_token": auth_tokens.access_token(), From feecfb20daeee7c61af84a904fb1bf40a8fa056f Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:35:55 +0100 Subject: [PATCH 05/41] fix error message for purging auth requests (#6776) --- src/api/core/accounts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index f5c32acb..0e01c1c4 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1704,6 +1704,6 @@ pub async fn purge_auth_requests(pool: DbPool) { if let Ok(conn) = pool.get().await { AuthRequest::purge_expired_auth_requests(&conn).await; } else { - error!("Failed to get DB connection while purging trashed ciphers") + error!("Failed to get DB connection while purging auth requests") } } From d09c45bb63f734e0b3da32a5cbef09e91c27e8c0 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sun, 8 Feb 2026 19:24:20 +0100 Subject: [PATCH 06/41] Misc updates, crates, rust, js, gha, vault (#6799) --- .github/workflows/release.yml | 20 +-- .github/workflows/trivy.yml | 2 +- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 275 +++++++++++++++--------------- Cargo.toml | 20 +-- docker/DockerSettings.yaml | 6 +- docker/Dockerfile.alpine | 20 +-- docker/Dockerfile.debian | 14 +- macros/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- src/static/scripts/datatables.css | 4 +- src/static/scripts/datatables.js | 16 +- 14 files changed, 197 insertions(+), 190 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48cca0bb..fc117e30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -117,7 +117,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -133,7 +133,7 @@ jobs: # Login to Quay.io - name: Login to Quay.io - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -233,7 +233,7 @@ jobs: # Upload artifacts to Github Actions and Attest the binaries - name: Attest binaries - uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 + uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }} @@ -265,7 +265,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -280,7 +280,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -296,7 +296,7 @@ jobs: # Login to Quay.io - name: Login to Quay.io - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -358,7 +358,7 @@ jobs: # Attest container images - name: Attest - docker.io - ${{ matrix.base_image }} if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 + uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: subject-name: ${{ vars.DOCKERHUB_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -366,7 +366,7 @@ jobs: - name: Attest - ghcr.io - ${{ matrix.base_image }} if: ${{ env.HAVE_GHCR_LOGIN == 'true' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 + uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: subject-name: ${{ vars.GHCR_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -374,7 +374,7 @@ jobs: - name: Attest - quay.io - ${{ matrix.base_image }} if: ${{ env.HAVE_QUAY_LOGIN == 'true' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 + uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: subject-name: ${{ vars.QUAY_REPO }} subject-digest: ${{ env.DIGEST_SHA }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 4aeb43b1..5ef08998 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 + uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 45a596ce..99e2eacf 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@65120634e79d8374d1aa2f27e54baa0c364fff5a # v1.42.1 + uses: crate-ci/typos@9066e9940a8a05b98fb4733c62a726f83c9e57f8 # v1.43.3 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 6083ef95..4051a8b2 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1 + uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6da57526..771eb042 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,6 @@ repos: - "cd docker && make" # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 65120634e79d8374d1aa2f27e54baa0c364fff5a # v1.42.1 + rev: 9066e9940a8a05b98fb4733c62a726f83c9e57f8 # v1.43.3 hooks: - id: typos diff --git a/Cargo.lock b/Cargo.lock index a4697493..d9bf20c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,15 +72,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "ar_archive_writer" -version = "0.2.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" dependencies = [ "object", ] @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" dependencies = [ "compression-codecs", "compression-core", @@ -360,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.12" +version = "1.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5" +checksum = "c456581cb3c77fafcc8c67204a70680d40b61112d6da78c77bd31d945b65f1b5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.18" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959dab27ce613e6c9658eb3621064d0e2027e5f2acb65bc526a43577facea557" +checksum = "c635c2dc792cb4a11ce1a4f392a925340d1bdf499289b5ec1ec6810954eb43f5" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -416,8 +416,8 @@ dependencies = [ "aws-types", "bytes", "fastrand", - "http 0.2.12", - "http-body 0.4.6", + "http 1.4.0", + "http-body 1.0.1", "percent-encoding", "pin-project-lite", "tracing", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.92.0" +version = "1.93.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d63bd2bdeeb49aa3f9b00c15e18583503b778b2e792fc06284d54e7d5b6566" +checksum = "9dcb38bb33fc0a11f1ffc3e3e85669e0a11a37690b86f77e75306d8f369146a0" dependencies = [ "aws-credential-types", "aws-runtime", @@ -443,15 +443,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.94.0" +version = "1.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532d93574bf731f311bafb761366f9ece345a0416dbcc273d81d6d1a1205239b" +checksum = "2ada8ffbea7bd1be1f53df1dadb0f8fdb04badb13185b3321b929d1ee3caad09" dependencies = [ "aws-credential-types", "aws-runtime", @@ -466,15 +467,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.96.0" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357e9a029c7524db6a0099cd77fbd5da165540339e7296cca603531bc783b56c" +checksum = "e6443ccadc777095d5ed13e21f5c364878c9f5bad4e35187a6cdbd863b0afcad" dependencies = [ "aws-credential-types", "aws-runtime", @@ -490,15 +492,16 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.3.7" +version = "1.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c" +checksum = "efa49f3c607b92daae0c078d48a4571f599f966dce3caee5f1ea55c4d9073f99" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -518,9 +521,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.7" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" +checksum = "52eec3db979d18cb807fc1070961cc51d87d069abe9ab57917769687368a8c6c" dependencies = [ "futures-util", "pin-project-lite", @@ -529,9 +532,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.6" +version = "0.63.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" +checksum = "630e67f2a31094ffa51b210ae030855cb8f3b7ee1329bdd8d085aaf61e8b97fc" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -539,9 +542,9 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 0.2.12", "http 1.4.0", - "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", "percent-encoding", "pin-project-lite", "pin-utils", @@ -550,27 +553,27 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.61.9" +version = "0.62.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" +checksum = "3cb96aa208d62ee94104645f7b2ecaf77bf27edf161590b6224bfbac2832f979" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1fcbefc7ece1d70dcce29e490f269695dfca2d2bacdeaf9e5c3f799e4e6a42" +checksum = "c0a46543fbc94621080b3cf553eb4cbbdc41dd9780a30c4756400f0139440a1d" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.9" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" +checksum = "0cebbddb6f3a5bd81553643e9c7daf3cc3dc5b0b5f398ac668630e8a84e6fff0" dependencies = [ "aws-smithy-types", "urlencoding", @@ -578,9 +581,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.8" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb5b6167fcdf47399024e81ac08e795180c576a20e4d4ce67949f9a88ae37dc1" +checksum = "f3df87c14f0127a0d77eb261c3bc45d5b4833e2a1f63583ebfb728e4852134ee" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -593,6 +596,7 @@ dependencies = [ "http 1.4.0", "http-body 0.4.6", "http-body 1.0.1", + "http-body-util", "pin-project-lite", "pin-utils", "tokio", @@ -601,9 +605,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efce7aaaf59ad53c5412f14fc19b2d5c6ab2c3ec688d272fd31f76ec12f44fb0" +checksum = "49952c52f7eebb72ce2a754d3866cc0f87b97d2a46146b79f80f3a93fb2b3716" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -618,9 +622,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.6" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f172bcb02424eb94425db8aed1b6d583b5104d4d5ddddf22402c661a320048" +checksum = "3b3a26048eeab0ddeba4b4f9d51654c79af8c3b32357dc5f336cee85ab331c33" dependencies = [ "base64-simd", "bytes", @@ -818,9 +822,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -830,9 +834,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bytes-utils" @@ -922,9 +926,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.53" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -1469,9 +1473,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.5" +version = "2.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e130c806dccc85428c564f2dc5a96e05b6615a27c9a28776bd7761a9af4bb552" +checksum = "d9b6c2fc184a6fb6ebcf5f9a5e3bbfa84d8fd268cdfcce4ed508979a6259494d" dependencies = [ "bigdecimal", "bitflags", @@ -1506,9 +1510,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c30b2969f923fa1f73744b92bb7df60b858df8832742d9a3aceb79236c0be1d2" +checksum = "47618bf0fac06bb670c036e48404c26a865e6a71af4114dfd97dfe89936e404e" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -1823,15 +1827,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2447,14 +2451,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -2463,7 +2466,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -2473,9 +2476,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2704,9 +2707,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2719,9 +2722,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", @@ -2791,9 +2794,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.2.0" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "base64 0.22.1", "ed25519-dalek", @@ -2864,7 +2867,7 @@ dependencies = [ "rustls 0.23.36", "rustls-native-certs", "serde", - "socket2 0.6.1", + "socket2 0.6.2", "tokio", "tokio-rustls 0.26.4", "tracing", @@ -2879,9 +2882,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" @@ -2990,9 +2993,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "migrations_internals" @@ -3074,9 +3077,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.12" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -3110,9 +3113,9 @@ dependencies = [ [[package]] name = "mysqlclient-sys" -version = "0.4.7" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86a34a2bdec189f1060343ba712983e14cad7e87515cfd9ac4653e207535b6b1" +checksum = "92ed7312f0cfc4032aea6f8ea2abb4d288e4413e33bf0c80ad30eef8aa8fb9d8" dependencies = [ "pkg-config", "semver", @@ -3198,9 +3201,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" @@ -3299,9 +3302,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -3425,9 +3428,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.5.5+3.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] @@ -3606,9 +3609,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -3616,9 +3619,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -3626,9 +3629,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", @@ -3639,9 +3642,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2", @@ -3796,15 +3799,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -3883,9 +3886,9 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "psm" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" dependencies = [ "ar_archive_writer", "cc", @@ -3966,7 +3969,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.36", - "socket2 0.6.1", + "socket2 0.6.2", "thiserror 2.0.18", "tokio", "tracing", @@ -4003,16 +4006,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -4139,9 +4142,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -4151,9 +4154,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -4162,15 +4165,15 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reopen" @@ -4650,9 +4653,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -4878,7 +4881,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.2.0", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -4984,9 +4987,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "skeptic" @@ -5005,9 +5008,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -5027,9 +5030,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -5089,9 +5092,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" dependencies = [ "cc", "cfg-if", @@ -5180,9 +5183,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", "core-foundation 0.9.4", @@ -5278,9 +5281,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -5295,15 +5298,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -5355,7 +5358,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -5759,9 +5762,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -5815,7 +5818,7 @@ dependencies = [ "html5gum", "http 1.4.0", "job_scheduler_ng", - "jsonwebtoken 10.2.0", + "jsonwebtoken 10.3.0", "lettre", "libsqlite3-sys", "log", @@ -6073,9 +6076,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -6563,18 +6566,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -6643,9 +6646,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 9d54590e..88d8b3c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2021" -rust-version = "1.90.0" +rust-version = "1.91.0" license = "AGPL-3.0-only" repository = "https://github.com/dani-garcia/vaultwarden" publish = false @@ -88,7 +88,7 @@ serde_json = "1.0.149" # A safe, extensible ORM and Query builder # Currently pinned diesel to v2.3.3 as newer version break MySQL/MariaDB compatibility -diesel = { version = "2.3.5", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.6", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.1" derive_more = { version = "2.1.1", features = ["from", "into", "as_ref", "deref", "display"] } @@ -103,12 +103,12 @@ ring = "0.17.14" subtle = "2.6.1" # UUID generation -uuid = { version = "1.19.0", features = ["v4"] } +uuid = { version = "1.20.0", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.43", features = ["clock", "serde"], default-features = false } chrono-tz = "0.10.4" -time = "0.3.45" +time = "0.3.47" # Job scheduler job_scheduler_ng = "2.4.0" @@ -117,7 +117,7 @@ job_scheduler_ng = "2.4.0" data-encoding = "2.10.0" # JWT library -jsonwebtoken = { version = "10.2.0", features = ["use_pem", "rust_crypto"], default-features = false } +jsonwebtoken = { version = "10.3.0", features = ["use_pem", "rust_crypto"], default-features = false } # TOTP library totp-lite = "2.0.1" @@ -149,9 +149,9 @@ hickory-resolver = "0.25.2" # Favicon extraction libraries html5gum = "0.8.3" -regex = { version = "1.12.2", features = ["std", "perf", "unicode-perl"], default-features = false } +regex = { version = "1.12.3", features = ["std", "perf", "unicode-perl"], default-features = false } data-url = "0.3.2" -bytes = "1.11.0" +bytes = "1.11.1" svg-hush = "0.9.5" # Cache function results (Used for version check and favicon fetching) @@ -197,10 +197,10 @@ grass_compiler = { version = "0.13.4", default-features = false } opendal = { version = "0.55.0", features = ["services-fs"], default-features = false } # For retrieving AWS credentials, including temporary SSO credentials -anyhow = { version = "1.0.100", optional = true } -aws-config = { version = "1.8.12", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } +anyhow = { version = "1.0.101", optional = true } +aws-config = { version = "1.8.13", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } aws-credential-types = { version = "1.2.11", optional = true } -aws-smithy-runtime-api = { version = "1.10.0", optional = true } +aws-smithy-runtime-api = { version = "1.11.3", optional = true } http = { version = "1.4.0", optional = true } reqsign = { version = "0.16.5", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 02cd166b..a0f3c80a 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,11 +1,11 @@ --- -vault_version: "v2025.12.2" -vault_image_digest: "sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7" +vault_version: "v2026.1.0" +vault_image_digest: "sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced" # Cross Compile Docker Helper Scripts v1.9.0 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.92.0 # Rust version to be used +rust_version: 1.93.0 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.23" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 95aae642..9886b6c1 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,23 +19,23 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2025.12.2 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.12.2 -# [docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.0 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.0 +# [docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 -# [docker.io/vaultwarden/web-vault:v2025.12.2] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced +# [docker.io/vaultwarden/web-vault:v2026.1.0] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced AS vault ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.92.0 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.92.0 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.92.0 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.92.0 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.93.0 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.93.0 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.93.0 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.93.0 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 113304b8..4ca1b009 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2025.12.2 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.12.2 -# [docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.0 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.0 +# [docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 -# [docker.io/vaultwarden/web-vault:v2025.12.2] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced +# [docker.io/vaultwarden/web-vault:v2026.1.0] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3c9aec4924c4f529af5e48888cfddd559e553ee62ce3e81372b50103bbaf20f7 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced AS vault ########################## Cross Compile Docker Helper Scripts ########################## ## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.92.0-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.93.0-slim-trixie AS build COPY --from=xx / / ARG TARGETARCH ARG TARGETVARIANT diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 933d46d3..0a560a74 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,7 +13,7 @@ path = "src/lib.rs" proc-macro = true [dependencies] -quote = "1.0.43" +quote = "1.0.44" syn = "2.0.114" [lints] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 568d0faa..57c529a1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.92.0" +channel = "1.93.0" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index fe087655..d91ea601 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,10 +4,10 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.6 + * https://datatables.net/download/#bs5/dt-2.3.7 * * Included libraries: - * DataTables 2.3.6 + * DataTables 2.3.7 */ :root { diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index 75f7965e..9c7fa042 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,13 +4,13 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.6 + * https://datatables.net/download/#bs5/dt-2.3.7 * * Included libraries: - * DataTables 2.3.6 + * DataTables 2.3.7 */ -/*! DataTables 2.3.6 +/*! DataTables 2.3.7 * © SpryMedia Ltd - datatables.net/license */ @@ -460,7 +460,7 @@ if ( tfoot.length === 0 ) { // If we are a scrolling table, and no footer has been given, then we need to create // a tfoot element for the caption element to be appended to - tfoot = $('
').insertAfter(thead); + tfoot = $('').appendTo($this); } oSettings.nTFoot = tfoot[0]; @@ -525,7 +525,7 @@ * * @type string */ - builder: "bs5/dt-2.3.6", + builder: "bs5/dt-2.3.7", /** * Buttons. For use with the Buttons extension for DataTables. This is @@ -8896,6 +8896,10 @@ return null; } + if (col.responsiveVisible === false) { + return null; + } + // Selector if (match[1]) { return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null; @@ -10300,7 +10304,7 @@ * @type string * @default Version number */ - DataTable.version = "2.3.6"; + DataTable.version = "2.3.7"; /** * Private data store, containing all of the settings objects that are From 3cd2d4afe70046aaef97d8f9d71d58304bc091bc Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Tue, 10 Feb 2026 20:24:35 +0100 Subject: [PATCH 07/41] Update crates and web-vault (#6810) Signed-off-by: BlackDex --- Cargo.lock | 182 ++++++++++++++++++++++++++++++++++--- docker/DockerSettings.yaml | 4 +- docker/Dockerfile.alpine | 12 +-- docker/Dockerfile.debian | 12 +-- 4 files changed, 185 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9bf20c6..fb952dd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2042,6 +2042,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "glob" version = "0.3.3" @@ -2579,6 +2592,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2842,6 +2861,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lettre" version = "0.11.19" @@ -2876,9 +2901,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libm" @@ -3847,6 +3872,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -4599,9 +4634,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "salsa20" @@ -5210,12 +5245,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys 0.61.2", @@ -5707,9 +5742,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-segmentation" @@ -5914,6 +5949,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -5973,6 +6017,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -5986,6 +6052,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -6480,6 +6558,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -6646,9 +6806,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" [[package]] name = "zstd" diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index a0f3c80a..5380b3df 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,6 +1,6 @@ --- -vault_version: "v2026.1.0" -vault_image_digest: "sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced" +vault_version: "v2026.1.1" +vault_image_digest: "sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7" # Cross Compile Docker Helper Scripts v1.9.0 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 9886b6c1..f006f5b4 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.0 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.0 -# [docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.1 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.1 +# [docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced -# [docker.io/vaultwarden/web-vault:v2026.1.0] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 +# [docker.io/vaultwarden/web-vault:v2026.1.1] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 AS vault ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 4ca1b009..449bbcfd 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.0 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.0 -# [docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.1 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.1 +# [docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced -# [docker.io/vaultwarden/web-vault:v2026.1.0] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 +# [docker.io/vaultwarden/web-vault:v2026.1.1] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:3537918638c8bd4f62f3f7c626a8959e4e48d35e3e0d3a567f98569d8a97eced AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 AS vault ########################## Cross Compile Docker Helper Scripts ########################## ## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts From 36f0620fd19af0816a7ee2ed882d368b1b298ddc Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Tue, 10 Feb 2026 20:34:30 +0100 Subject: [PATCH 08/41] Fix org-details issue (#6811) Fix an issue where it was possible for users who were not eligible to access all org ciphers to be able to download and extract the encrypted contents. Only Managers with full access and Admins and Owners should be able to access this endpoint. This change will block and prevent access for other users. Signed-off-by: BlackDex --- src/api/core/organizations.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 356d7786..f173f90f 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -929,11 +929,15 @@ struct OrgIdData { } #[get("/ciphers/organization-details?")] -async fn get_org_details(data: OrgIdData, headers: OrgMemberHeaders, conn: DbConn) -> JsonResult { +async fn get_org_details(data: OrgIdData, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult { if data.organization_id != headers.membership.org_uuid { err_code!("Resource not found.", "Organization id's do not match", rocket::http::Status::NotFound.code); } + if !headers.membership.has_full_access() { + err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + } + Ok(Json(json!({ "data": _get_org_details(&data.organization_id, &headers.host, &headers.user.uuid, &conn).await?, "object": "list", From 1583fe4af3b3ce98e221172d649567630b899a36 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 18 Feb 2026 00:17:20 +0100 Subject: [PATCH 09/41] Update Rust and Crates and GHA (#6843) - Update Rust to v1.93.1 - Updated all the crates Adjust changes needed for the newer `rand` crate - Updated GitHub Actions Signed-off-by: BlackDex --- .github/workflows/build.yml | 4 +- .github/workflows/trivy.yml | 4 +- .github/workflows/typos.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 310 ++++++++++++++++-------------------- Cargo.toml | 18 +-- docker/DockerSettings.yaml | 2 +- docker/Dockerfile.alpine | 8 +- docker/Dockerfile.debian | 2 +- rust-toolchain.toml | 2 +- src/api/core/accounts.rs | 7 +- src/api/identity.rs | 11 +- src/crypto.rs | 4 +- 13 files changed, 173 insertions(+), 203 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3e7818ec..2e3468f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,7 +87,7 @@ jobs: # Only install the clippy and rustfmt components on the default rust-toolchain - name: "Install rust-toolchain version" - uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master @ Dec 16, 2025, 6:11 PM GMT+1 + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master @ Feb 13, 2026, 3:46 AM GMT+1 if: ${{ matrix.channel == 'rust-toolchain' }} with: toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" @@ -97,7 +97,7 @@ jobs: # Install the any other channel to be used for which we do not execute clippy and rustfmt - name: "Install MSRV version" - uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master @ Dec 16, 2025, 6:11 PM GMT+1 + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master @ Feb 13, 2026, 3:46 AM GMT+1 if: ${{ matrix.channel != 'rust-toolchain' }} with: toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 5ef08998..13cd2e24 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -38,7 +38,7 @@ jobs: persist-credentials: false - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0 env: TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2 TRIVY_JAVA_DB_REPOSITORY: docker.io/aquasec/trivy-java-db:1,public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1 @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2 + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 99e2eacf..5726a6fc 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@9066e9940a8a05b98fb4733c62a726f83c9e57f8 # v1.43.3 + uses: crate-ci/typos@57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1.43.5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 771eb042..54f09b8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,6 @@ repos: - "cd docker && make" # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 9066e9940a8a05b98fb4733c62a726f83c9e57f8 # v1.43.3 + rev: 57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1.43.5 hooks: - id: typos diff --git a/Cargo.lock b/Cargo.lock index fb952dd1..da9e0d79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -93,7 +93,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -360,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.13" +version = "1.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c456581cb3c77fafcc8c67204a70680d40b61112d6da78c77bd31d945b65f1b5" +checksum = "8a8fc176d53d6fe85017f230405e3255cedb4a02221cb55ed6d76dccbbb099b2" dependencies = [ "aws-credential-types", "aws-runtime", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.11" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +checksum = "6d203b0bf2626dcba8665f5cd0871d7c2c0930223d6b6be9097592fea21242d0" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c635c2dc792cb4a11ce1a4f392a925340d1bdf499289b5ec1ec6810954eb43f5" +checksum = "ede2ddc593e6c8acc6ce3358c28d6677a6dc49b65ba4b37a2befe14a11297e75" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -415,6 +415,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", + "bytes-utils", "fastrand", "http 1.4.0", "http-body 1.0.1", @@ -426,9 +427,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.93.0" +version = "1.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcb38bb33fc0a11f1ffc3e3e85669e0a11a37690b86f77e75306d8f369146a0" +checksum = "00c5ff27c6ba2cbd95e6e26e2e736676fdf6bcf96495b187733f521cfe4ce448" dependencies = [ "aws-credential-types", "aws-runtime", @@ -450,9 +451,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.95.0" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ada8ffbea7bd1be1f53df1dadb0f8fdb04badb13185b3321b929d1ee3caad09" +checksum = "4d186f1e5a3694a188e5a0640b3115ccc6e084d104e16fd6ba968dca072ffef8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -474,9 +475,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.97.0" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6443ccadc777095d5ed13e21f5c364878c9f5bad4e35187a6cdbd863b0afcad" +checksum = "9acba7c62f3d4e2408fa998a3a8caacd8b9a5b5549cf36e2372fbdae329d5449" dependencies = [ "aws-credential-types", "aws-runtime", @@ -499,9 +500,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.8" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa49f3c607b92daae0c078d48a4571f599f966dce3caee5f1ea55c4d9073f99" +checksum = "37411f8e0f4bea0c3ca0958ce7f18f6439db24d555dbd809787262cd00926aa9" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -521,9 +522,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.11" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52eec3db979d18cb807fc1070961cc51d87d069abe9ab57917769687368a8c6c" +checksum = "5cc50d0f63e714784b84223abd7abbc8577de8c35d699e0edd19f0a88a08ae13" dependencies = [ "futures-util", "pin-project-lite", @@ -532,9 +533,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.63.3" +version = "0.63.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630e67f2a31094ffa51b210ae030855cb8f3b7ee1329bdd8d085aaf61e8b97fc" +checksum = "d619373d490ad70966994801bc126846afaa0d1ee920697a031f0cf63f2568e7" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -553,27 +554,27 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.3" +version = "0.62.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb96aa208d62ee94104645f7b2ecaf77bf27edf161590b6224bfbac2832f979" +checksum = "27b3a779093e18cad88bbae08dc4261e1d95018c4c5b9356a52bcae7c0b6e9bb" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a46543fbc94621080b3cf553eb4cbbdc41dd9780a30c4756400f0139440a1d" +checksum = "4d3f39d5bb871aaf461d59144557f16d5927a5248a983a40654d9cf3b9ba183b" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.13" +version = "0.60.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cebbddb6f3a5bd81553643e9c7daf3cc3dc5b0b5f398ac668630e8a84e6fff0" +checksum = "05f76a580e3d8f8961e5d48763214025a2af65c2fa4cd1fb7f270a0e107a71b0" dependencies = [ "aws-smithy-types", "urlencoding", @@ -581,9 +582,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.10.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3df87c14f0127a0d77eb261c3bc45d5b4833e2a1f63583ebfb728e4852134ee" +checksum = "22ccf7f6eba8b2dcf8ce9b74806c6c185659c311665c4bf8d6e71ebd454db6bf" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -605,9 +606,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.11.3" +version = "1.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49952c52f7eebb72ce2a754d3866cc0f87b97d2a46146b79f80f3a93fb2b3716" +checksum = "b4af6e5def28be846479bbeac55aa4603d6f7986fc5da4601ba324dd5d377516" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -622,9 +623,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.4.3" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3a26048eeab0ddeba4b4f9d51654c79af8c3b32357dc5f336cee85ab331c33" +checksum = "8ca2734c16913a45343b37313605d84e7d8b34a4611598ce1d25b35860a2bed3" dependencies = [ "base64-simd", "bytes", @@ -645,18 +646,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.13" +version = "0.60.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.11" +version = "1.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +checksum = "0470cc047657c6e286346bdf10a8719d26efd6a91626992e0e64481e44323e96" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -743,9 +744,9 @@ checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake2" @@ -926,9 +927,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -948,6 +949,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.43" @@ -1075,9 +1087,9 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" dependencies = [ "cookie", "document-features", @@ -1126,6 +1138,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32c" version = "0.6.8" @@ -1220,7 +1241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1376,9 +1397,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", "serde_core", @@ -1885,9 +1906,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1900,9 +1921,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1910,15 +1931,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1927,9 +1948,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1946,9 +1967,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1957,15 +1978,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -1975,9 +1996,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1987,7 +2008,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2051,6 +2071,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -2446,22 +2467,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.8.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.20" @@ -2726,9 +2731,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2741,9 +2746,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" dependencies = [ "proc-macro2", "quote", @@ -2901,9 +2906,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libm" @@ -3029,7 +3034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" dependencies = [ "serde", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -3147,23 +3152,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "7.1.3" @@ -3439,12 +3427,6 @@ dependencies = [ "syn", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -4099,6 +4081,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -4137,6 +4130,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -4274,12 +4273,10 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -4291,7 +4288,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls 0.26.4", "tokio-util", "tower", @@ -4580,10 +4576,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -4747,22 +4743,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -4773,9 +4756,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -4942,7 +4925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -4953,7 +4936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5010,9 +4993,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", @@ -5161,23 +5144,22 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svg-hush" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d647e9386e34dd750ba80bdb7dae2a2c50b78338515ffeb9fa7bdd3ef803bf2" +checksum = "929223e80cdcec0482207576ea09692dd71b2b559057fc172e292ecec9a97559" dependencies = [ "base64 0.22.1", "data-url", - "once_cell", "quick-error", "url", - "xml-rs", + "xml", ] [[package]] name = "syn" -version = "2.0.114" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -5409,16 +5391,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -5490,9 +5462,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "serde_core", "serde_spanned 1.0.4", @@ -5535,9 +5507,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow 0.7.14", ] @@ -5742,9 +5714,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -5797,11 +5769,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -5868,7 +5840,7 @@ dependencies = [ "pastey 0.2.1", "percent-encoding", "pico-args", - "rand 0.9.2", + "rand 0.10.0", "regex", "reqsign", "reqwest", @@ -6665,10 +6637,10 @@ dependencies = [ ] [[package]] -name = "xml-rs" -version = "0.8.28" +name = "xml" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" [[package]] name = "xmlparser" @@ -6806,9 +6778,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 88d8b3c4..fd910852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ rmpv = "1.3.1" # MessagePack library dashmap = "6.1.0" # Async futures -futures = "0.3.31" +futures = "0.3.32" tokio = { version = "1.49.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } tokio-util = { version = "0.7.18", features = ["compat"]} @@ -98,12 +98,12 @@ diesel-derive-newtype = "2.1.2" libsqlite3-sys = { version = "0.35.0", features = ["bundled"], optional = true } # Crypto-related libraries -rand = "0.9.2" +rand = "0.10.0" ring = "0.17.14" subtle = "2.6.1" # UUID generation -uuid = { version = "1.20.0", features = ["v4"] } +uuid = { version = "1.21.0", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.43", features = ["clock", "serde"], default-features = false } @@ -152,14 +152,14 @@ html5gum = "0.8.3" regex = { version = "1.12.3", features = ["std", "perf", "unicode-perl"], default-features = false } data-url = "0.3.2" bytes = "1.11.1" -svg-hush = "0.9.5" +svg-hush = "0.9.6" # Cache function results (Used for version check and favicon fetching) cached = { version = "0.56.0", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction cookie = "0.18.1" -cookie_store = "0.22.0" +cookie_store = "0.22.1" # Used by U2F, JWT and PostgreSQL openssl = "0.10.75" @@ -172,7 +172,7 @@ pastey = "0.2.1" governor = "0.10.4" # OIDC for SSO -openidconnect = { version = "4.0.1", features = ["reqwest", "native-tls"] } +openidconnect = { version = "4.0.1", features = ["reqwest", "rustls-tls"] } mini-moka = "0.10.3" # Check client versions for specific features. @@ -198,9 +198,9 @@ opendal = { version = "0.55.0", features = ["services-fs"], default-features = f # For retrieving AWS credentials, including temporary SSO credentials anyhow = { version = "1.0.101", optional = true } -aws-config = { version = "1.8.13", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } -aws-credential-types = { version = "1.2.11", optional = true } -aws-smithy-runtime-api = { version = "1.11.3", optional = true } +aws-config = { version = "1.8.14", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } +aws-credential-types = { version = "1.2.13", optional = true } +aws-smithy-runtime-api = { version = "1.11.5", optional = true } http = { version = "1.4.0", optional = true } reqsign = { version = "0.16.5", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 5380b3df..7b4a9af7 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,7 +5,7 @@ vault_image_digest: "sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114e # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.93.0 # Rust version to be used +rust_version: 1.93.1 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.23" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index f006f5b4..b3df2f4e 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37 ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.93.0 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.93.0 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.93.0 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.93.0 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.93.1 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.93.1 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.93.1 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.93.1 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 449bbcfd..8796dedb 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.93.0-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.93.1-slim-trixie AS build COPY --from=xx / / ARG TARGETARCH ARG TARGETVARIANT diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 57c529a1..585747e0 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.93.0" +channel = "1.93.1" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 0e01c1c4..51ebbf03 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1199,10 +1199,9 @@ async fn password_hint(data: Json, conn: DbConn) -> EmptyResul // There is still a timing side channel here in that the code // paths that send mail take noticeably longer than ones that // don't. Add a randomized sleep to mitigate this somewhat. - use rand::{rngs::SmallRng, Rng, SeedableRng}; - let mut rng = SmallRng::from_os_rng(); - let delta: i32 = 100; - let sleep_ms = (1_000 + rng.random_range(-delta..=delta)) as u64; + use rand::{rngs::SmallRng, RngExt}; + let mut rng: SmallRng = rand::make_rng(); + let sleep_ms = rng.random_range(900..=1100) as u64; tokio::time::sleep(tokio::time::Duration::from_millis(sleep_ms)).await; Ok(()) } else { diff --git a/src/api/identity.rs b/src/api/identity.rs index f5f2afd6..0ac0a730 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -975,12 +975,11 @@ async fn register_verification_email( let user = User::find_by_mail(&data.email, &conn).await; if user.filter(|u| u.private_key.is_some()).is_some() { // There is still a timing side channel here in that the code - // paths that send mail take noticeably longer than ones that - // don't. Add a randomized sleep to mitigate this somewhat. - use rand::{rngs::SmallRng, Rng, SeedableRng}; - let mut rng = SmallRng::from_os_rng(); - let delta: i32 = 100; - let sleep_ms = (1_000 + rng.random_range(-delta..=delta)) as u64; + // paths that send mail take noticeably longer than ones that don't. + // Add a randomized sleep to mitigate this somewhat. + use rand::{rngs::SmallRng, RngExt}; + let mut rng: SmallRng = rand::make_rng(); + let sleep_ms = rng.random_range(900..=1100) as u64; tokio::time::sleep(tokio::time::Duration::from_millis(sleep_ms)).await; } else { mail::send_register_verify_email(&data.email, &token).await?; diff --git a/src/crypto.rs b/src/crypto.rs index e2add1c6..1930f380 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -55,13 +55,13 @@ pub fn encode_random_bytes(e: &Encoding) -> String { /// Generates a random string over a specified alphabet. pub fn get_random_string(alphabet: &[u8], num_chars: usize) -> String { // Ref: https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html - use rand::Rng; + use rand::RngExt; let mut rng = rand::rng(); (0..num_chars) .map(|_| { let i = rng.random_range(0..alphabet.len()); - alphabet[i] as char + char::from(alphabet[i]) }) .collect() } From da2af3d3629abec3bdfd91008f0f8805cb1d20f6 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:27:40 +0100 Subject: [PATCH 10/41] hide remember 2fa token (#6852) --- src/api/web.rs | 1 + src/static/templates/scss/vaultwarden.scss.hbs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/api/web.rs b/src/api/web.rs index d1ca0db4..0ae9c7db 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -60,6 +60,7 @@ fn vaultwarden_css() -> Cached> { "mail_2fa_enabled": CONFIG._enable_email_2fa(), "mail_enabled": CONFIG.mail_enabled(), "sends_allowed": CONFIG.sends_allowed(), + "remember_2fa_disabled": CONFIG.disable_2fa_remember(), "password_hints_allowed": CONFIG.password_hints_allowed(), "signup_disabled": CONFIG.is_signup_disabled(), "sso_enabled": CONFIG.sso_enabled(), diff --git a/src/static/templates/scss/vaultwarden.scss.hbs b/src/static/templates/scss/vaultwarden.scss.hbs index 1859c1ea..230ac2e7 100644 --- a/src/static/templates/scss/vaultwarden.scss.hbs +++ b/src/static/templates/scss/vaultwarden.scss.hbs @@ -158,6 +158,13 @@ app-root a[routerlink="/signup"] { {{/if}} {{/if}} +{{#if remember_2fa_disabled}} +/* Hide checkbox to remember 2FA token for 30 days */ +app-two-factor-auth > form > bit-form-control { + @extend %vw-hide; +} +{{/if}} + {{#unless mail_2fa_enabled}} /* Hide `Email` 2FA if mail is not enabled */ .providers-2fa-1 { From 74819b95bd09610b2dd4af633ceb1f9c72b353f4 Mon Sep 17 00:00:00 2001 From: proofofcopilot Date: Mon, 23 Feb 2026 20:28:12 +0100 Subject: [PATCH 11/41] fix(send_invite): add orgSsoIdentifier if sso_only is enabled (#6824) --- src/mail.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mail.rs b/src/mail.rs index 270a839e..cdbd269a 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -302,10 +302,10 @@ pub async fn send_invite( .append_pair("organizationUserId", &member_id) .append_pair("token", &invite_token); - if CONFIG.sso_enabled() { - query_params.append_pair("orgUserHasExistingUser", "false"); + if CONFIG.sso_enabled() && CONFIG.sso_only() { query_params.append_pair("orgSsoIdentifier", &org_id); - } else if user.private_key.is_some() { + } + if user.private_key.is_some() { query_params.append_pair("orgUserHasExistingUser", "true"); } } From c555f7d1980c8ab3ac8d64b0297e47c621590368 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 23 Feb 2026 21:52:44 +0100 Subject: [PATCH 12/41] Misc organization fixes (#6867) --- src/api/core/accounts.rs | 6 - src/api/core/ciphers.rs | 6 +- src/api/core/organizations.rs | 427 +++---------------------------- src/api/core/two_factor/email.rs | 3 + src/api/core/two_factor/mod.rs | 51 +--- src/auth.rs | 6 +- src/db/models/collection.rs | 11 +- 7 files changed, 51 insertions(+), 459 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 51ebbf03..0ce1f684 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -33,7 +33,6 @@ use rocket::{ pub fn routes() -> Vec { routes![ - register, profile, put_profile, post_profile, @@ -168,11 +167,6 @@ async fn is_email_2fa_required(member_id: Option, conn: &DbConn) - false } -#[post("/accounts/register", data = "")] -async fn register(data: Json, conn: DbConn) -> JsonResult { - _register(data, false, conn).await -} - pub async fn _register(data: Json, email_verification: bool, conn: DbConn) -> JsonResult { let mut data: RegisterData = data.into_inner(); let email = data.email.to_lowercase(); diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index d5f244f4..f7bf5cd3 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -715,9 +715,13 @@ async fn put_cipher_partial( let data: PartialCipherData = data.into_inner(); let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &conn).await else { - err!("Cipher doesn't exist") + err!("Cipher does not exist") }; + if !cipher.is_accessible_to_user(&headers.user.uuid, &conn).await { + err!("Cipher does not exist", "Cipher is not accessible for the current user") + } + if let Some(ref folder_id) = data.folder_id { if Folder::find_by_uuid_and_user(folder_id, &headers.user.uuid, &conn).await.is_none() { err!("Invalid folder", "Folder does not exist or belongs to another user"); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index f173f90f..4a5066ab 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -36,12 +36,9 @@ pub fn routes() -> Vec { get_org_collections_details, get_org_collection_detail, get_collection_users, - put_collection_users, put_organization, post_organization, post_organization_collections, - delete_organization_collection_member, - post_organization_collection_delete_member, post_bulk_access_collections, post_organization_collection_update, put_organization_collection_update, @@ -64,28 +61,20 @@ pub fn routes() -> Vec { put_member, delete_member, bulk_delete_member, - post_delete_member, post_org_import, list_policies, list_policies_token, get_master_password_policy, get_policy, put_policy, - get_organization_tax, + put_policy_vnext, get_plans, - get_plans_all, - get_plans_tax_rates, - import, post_org_keys, get_organization_keys, get_organization_public_key, bulk_public_keys, - deactivate_member, - bulk_deactivate_members, revoke_member, bulk_revoke_members, - activate_member, - bulk_activate_members, restore_member, bulk_restore_members, get_groups, @@ -100,10 +89,6 @@ pub fn routes() -> Vec { bulk_delete_groups, get_group_members, put_group_members, - get_user_groups, - post_user_groups, - put_user_groups, - delete_group_member, post_delete_group_member, put_reset_password_enrollment, get_reset_password_details, @@ -380,6 +365,11 @@ async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoos if org_id != headers.membership.org_uuid { err!("Organization not found", "Organization id's do not match"); } + + if !headers.membership.has_full_access() { + err_code!("Resource not found.", "User does not have full access", rocket::http::Status::NotFound.code); + } + Ok(Json(json!({ "data": _get_org_collections(&org_id, &conn).await, "object": "list", @@ -392,7 +382,6 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea if org_id != headers.membership.org_uuid { err!("Organization not found", "Organization id's do not match"); } - let mut data = Vec::new(); let Some(member) = Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await else { err!("User is not part of organization") @@ -424,6 +413,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea }) .collect(); + let mut data = Vec::new(); for col in Collection::find_by_organization(&org_id, &conn).await { // check whether the current user has access to the given collection let assigned = has_full_access_to_org @@ -566,6 +556,10 @@ async fn post_bulk_access_collections( err!("Collection not found") }; + if !collection.is_manageable_by_user(&headers.membership.user_uuid, &conn).await { + err!("Collection not found", "The current user isn't a manager for this collection") + } + // update collection modification date collection.save(&conn).await?; @@ -682,43 +676,6 @@ async fn post_organization_collection_update( Ok(Json(collection.to_json_details(&headers.user.uuid, None, &conn).await)) } -#[delete("/organizations//collections//user/")] -async fn delete_organization_collection_member( - org_id: OrganizationId, - col_id: CollectionId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, -) -> EmptyResult { - if org_id != headers.org_id { - err!("Organization not found", "Organization id's do not match"); - } - let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &conn).await else { - err!("Collection not found", "Collection does not exist or does not belong to this organization") - }; - - match Membership::find_by_uuid_and_org(&member_id, &org_id, &conn).await { - None => err!("User not found in organization"), - Some(member) => { - match CollectionUser::find_by_collection_and_user(&collection.uuid, &member.user_uuid, &conn).await { - None => err!("User not assigned to collection"), - Some(col_user) => col_user.delete(&conn).await, - } - } - } -} - -#[post("/organizations//collections//delete-user/")] -async fn post_organization_collection_delete_member( - org_id: OrganizationId, - col_id: CollectionId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, -) -> EmptyResult { - delete_organization_collection_member(org_id, col_id, member_id, headers, conn).await -} - async fn _delete_organization_collection( org_id: &OrganizationId, col_id: &CollectionId, @@ -887,41 +844,6 @@ async fn get_collection_users( Ok(Json(json!(member_list))) } -#[put("/organizations//collections//users", data = "")] -async fn put_collection_users( - org_id: OrganizationId, - col_id: CollectionId, - data: Json>, - headers: ManagerHeaders, - conn: DbConn, -) -> EmptyResult { - if org_id != headers.org_id { - err!("Organization not found", "Organization id's do not match"); - } - // Get org and collection, check that collection is from org - if Collection::find_by_uuid_and_org(&col_id, &org_id, &conn).await.is_none() { - err!("Collection not found in Organization") - } - - // Delete all the user-collections - CollectionUser::delete_all_by_collection(&col_id, &conn).await?; - - // And then add all the received ones (except if the user has access_all) - for d in data.iter() { - let Some(user) = Membership::find_by_uuid_and_org(&d.id, &org_id, &conn).await else { - err!("User is not part of organization") - }; - - if user.access_all { - continue; - } - - CollectionUser::save(&user.user_uuid, &col_id, d.read_only, d.hide_passwords, d.manage, &conn).await?; - } - - Ok(()) -} - #[derive(FromForm)] struct OrgIdData { #[field(name = "organizationId")] @@ -1719,17 +1641,6 @@ async fn delete_member( _delete_member(&org_id, &member_id, &headers, &conn, &nt).await } -#[post("/organizations//users//delete")] -async fn post_delete_member( - org_id: OrganizationId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, - nt: Notify<'_>, -) -> EmptyResult { - _delete_member(&org_id, &member_id, &headers, &conn, &nt).await -} - async fn _delete_member( org_id: &OrganizationId, member_id: &MembershipId, @@ -2182,14 +2093,26 @@ async fn put_policy( Ok(Json(policy.to_json())) } -#[allow(unused_variables)] -#[get("/organizations//tax")] -fn get_organization_tax(org_id: OrganizationId, _headers: Headers) -> Json { - // Prevent a 404 error, which also causes Javascript errors. - // Upstream sends "Only allowed when not self hosted." As an error message. - // If we do the same it will also output this to the log, which is overkill. - // An empty list/data also works fine. - Json(_empty_data_json()) +#[derive(Deserialize)] +struct PolicyDataVnext { + policy: PolicyData, + // Ignore metadata for now as we do not yet support this + // "metadata": { + // "defaultUserCollectionName": "2.xx|xx==|xx=" + // } +} + +#[put("/organizations//policies//vnext", data = "")] +async fn put_policy_vnext( + org_id: OrganizationId, + pol_type: i32, + data: Json, + headers: AdminHeaders, + conn: DbConn, +) -> JsonResult { + let data: PolicyDataVnext = data.into_inner(); + let policy: PolicyData = data.policy; + put_policy(org_id, pol_type, Json(policy), headers, conn).await } #[get("/plans")] @@ -2220,17 +2143,6 @@ fn get_plans() -> Json { })) } -#[get("/plans/all")] -fn get_plans_all() -> Json { - get_plans() -} - -#[get("/plans/sales-tax-rates")] -fn get_plans_tax_rates(_headers: Headers) -> Json { - // Prevent a 404 error, which also causes Javascript errors. - Json(_empty_data_json()) -} - #[get("/organizations/<_org_id>/billing/metadata")] fn get_billing_metadata(_org_id: OrganizationId, _headers: Headers) -> Json { // Prevent a 404 error, which also causes Javascript errors. @@ -2255,174 +2167,12 @@ fn _empty_data_json() -> Value { }) } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct OrgImportGroupData { - #[allow(dead_code)] - name: String, // "GroupName" - #[allow(dead_code)] - external_id: String, // "cn=GroupName,ou=Groups,dc=example,dc=com" - #[allow(dead_code)] - users: Vec, // ["uid=user,ou=People,dc=example,dc=com"] -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct OrgImportUserData { - email: String, // "user@maildomain.net" - #[allow(dead_code)] - external_id: String, // "uid=user,ou=People,dc=example,dc=com" - deleted: bool, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct OrgImportData { - #[allow(dead_code)] - groups: Vec, - overwrite_existing: bool, - users: Vec, -} - -/// This function seems to be deprecated -/// It is only used with older directory connectors -/// TODO: Cleanup Tech debt -#[post("/organizations//import", data = "")] -async fn import(org_id: OrganizationId, data: Json, headers: Headers, conn: DbConn) -> EmptyResult { - let data = data.into_inner(); - - // TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way - // to differentiate between auto-imported users and manually added ones. - // This means that this endpoint can end up removing users that were added manually by an admin, - // as opposed to upstream which only removes auto-imported users. - - // User needs to be admin or owner to use the Directory Connector - match Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await { - Some(member) if member.atype >= MembershipType::Admin => { /* Okay, nothing to do */ } - Some(_) => err!("User has insufficient permissions to use Directory Connector"), - None => err!("User not part of organization"), - }; - - for user_data in &data.users { - if user_data.deleted { - // If user is marked for deletion and it exists, delete it - if let Some(member) = Membership::find_by_email_and_org(&user_data.email, &org_id, &conn).await { - log_event( - EventType::OrganizationUserRemoved as i32, - &member.uuid, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; - - member.delete(&conn).await?; - } - - // If user is not part of the organization, but it exists - } else if Membership::find_by_email_and_org(&user_data.email, &org_id, &conn).await.is_none() { - if let Some(user) = User::find_by_mail(&user_data.email, &conn).await { - let member_status = if CONFIG.mail_enabled() { - MembershipStatus::Invited as i32 - } else { - MembershipStatus::Accepted as i32 // Automatically mark user as accepted if no email invites - }; - - let mut new_member = - Membership::new(user.uuid.clone(), org_id.clone(), Some(headers.user.email.clone())); - new_member.access_all = false; - new_member.atype = MembershipType::User as i32; - new_member.status = member_status; - - if CONFIG.mail_enabled() { - let org_name = match Organization::find_by_uuid(&org_id, &conn).await { - Some(org) => org.name, - None => err!("Error looking up organization"), - }; - - mail::send_invite( - &user, - org_id.clone(), - new_member.uuid.clone(), - &org_name, - Some(headers.user.email.clone()), - ) - .await?; - } - - // Save the member after sending an email - // If sending fails the member will not be saved to the database, and will not result in the admin needing to reinvite the users manually - new_member.save(&conn).await?; - - log_event( - EventType::OrganizationUserInvited as i32, - &new_member.uuid, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; - } - } - } - - // If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true) - if data.overwrite_existing { - for member in Membership::find_by_org_and_type(&org_id, MembershipType::User, &conn).await { - if let Some(user_email) = User::find_by_uuid(&member.user_uuid, &conn).await.map(|u| u.email) { - if !data.users.iter().any(|u| u.email == user_email) { - log_event( - EventType::OrganizationUserRemoved as i32, - &member.uuid, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; - - member.delete(&conn).await?; - } - } - } - } - - Ok(()) -} - -// Pre web-vault v2022.9.x endpoint -#[put("/organizations//users//deactivate")] -async fn deactivate_member( - org_id: OrganizationId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, -) -> EmptyResult { - _revoke_member(&org_id, &member_id, &headers, &conn).await -} - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct BulkRevokeMembershipIds { ids: Option>, } -// Pre web-vault v2022.9.x endpoint -#[put("/organizations//users/deactivate", data = "")] -async fn bulk_deactivate_members( - org_id: OrganizationId, - data: Json, - headers: AdminHeaders, - conn: DbConn, -) -> JsonResult { - bulk_revoke_members(org_id, data, headers, conn).await -} - #[put("/organizations//users//revoke")] async fn revoke_member( org_id: OrganizationId, @@ -2516,28 +2266,6 @@ async fn _revoke_member( Ok(()) } -// Pre web-vault v2022.9.x endpoint -#[put("/organizations//users//activate")] -async fn activate_member( - org_id: OrganizationId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, -) -> EmptyResult { - _restore_member(&org_id, &member_id, &headers, &conn).await -} - -// Pre web-vault v2022.9.x endpoint -#[put("/organizations//users/activate", data = "")] -async fn bulk_activate_members( - org_id: OrganizationId, - data: Json, - headers: AdminHeaders, - conn: DbConn, -) -> JsonResult { - bulk_restore_members(org_id, data, headers, conn).await -} - #[put("/organizations//users//restore")] async fn restore_member( org_id: OrganizationId, @@ -3006,88 +2734,6 @@ async fn put_group_members( Ok(()) } -#[get("/organizations//users//groups")] -async fn get_user_groups( - org_id: OrganizationId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, -) -> JsonResult { - if org_id != headers.org_id { - err!("Organization not found", "Organization id's do not match"); - } - if !CONFIG.org_groups_enabled() { - err!("Group support is disabled"); - } - - if Membership::find_by_uuid_and_org(&member_id, &org_id, &conn).await.is_none() { - err!("User could not be found!") - }; - - let user_groups: Vec = - GroupUser::find_by_member(&member_id, &conn).await.iter().map(|entry| entry.groups_uuid.clone()).collect(); - - Ok(Json(json!(user_groups))) -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct OrganizationUserUpdateGroupsRequest { - group_ids: Vec, -} - -#[post("/organizations//users//groups", data = "")] -async fn post_user_groups( - org_id: OrganizationId, - member_id: MembershipId, - data: Json, - headers: AdminHeaders, - conn: DbConn, -) -> EmptyResult { - put_user_groups(org_id, member_id, data, headers, conn).await -} - -#[put("/organizations//users//groups", data = "")] -async fn put_user_groups( - org_id: OrganizationId, - member_id: MembershipId, - data: Json, - headers: AdminHeaders, - conn: DbConn, -) -> EmptyResult { - if org_id != headers.org_id { - err!("Organization not found", "Organization id's do not match"); - } - if !CONFIG.org_groups_enabled() { - err!("Group support is disabled"); - } - - if Membership::find_by_uuid_and_org(&member_id, &org_id, &conn).await.is_none() { - err!("User could not be found or does not belong to the organization."); - } - - GroupUser::delete_all_by_member(&member_id, &conn).await?; - - let assigned_group_ids = data.into_inner(); - for assigned_group_id in assigned_group_ids.group_ids { - let mut group_user = GroupUser::new(assigned_group_id.clone(), member_id.clone()); - group_user.save(&conn).await?; - } - - log_event( - EventType::OrganizationUserUpdatedGroups as i32, - &member_id, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; - - Ok(()) -} - #[post("/organizations//groups//delete-user/")] async fn post_delete_group_member( org_id: OrganizationId, @@ -3095,17 +2741,6 @@ async fn post_delete_group_member( member_id: MembershipId, headers: AdminHeaders, conn: DbConn, -) -> EmptyResult { - delete_group_member(org_id, group_id, member_id, headers, conn).await -} - -#[delete("/organizations//groups//users/")] -async fn delete_group_member( - org_id: OrganizationId, - group_id: GroupId, - member_id: MembershipId, - headers: AdminHeaders, - conn: DbConn, ) -> EmptyResult { if org_id != headers.org_id { err!("Organization not found", "Organization id's do not match"); diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index 25218069..e7d1aed2 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -44,6 +44,9 @@ async fn send_email_login(data: Json, client_headers: Client err!("Email 2FA is disabled") } + // Ratelimit the login + crate::ratelimit::check_limit_login(&client_headers.ip.ip)?; + // Get the user let email = match &data.email { Some(email) if !email.is_empty() => Some(email), diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index dfaae77a..34fbfaa9 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -9,7 +9,7 @@ use crate::{ core::{log_event, log_user_event}, EmptyResult, JsonResult, PasswordOrOtpData, }, - auth::{ClientHeaders, Headers}, + auth::Headers, crypto, db::{ models::{ @@ -35,7 +35,6 @@ pub fn routes() -> Vec { let mut routes = routes![ get_twofactor, get_recover, - recover, disable_twofactor, disable_twofactor_put, get_device_verification_settings, @@ -76,54 +75,6 @@ async fn get_recover(data: Json, headers: Headers, conn: DbCo }))) } -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct RecoverTwoFactor { - master_password_hash: String, - email: String, - recovery_code: String, -} - -#[post("/two-factor/recover", data = "")] -async fn recover(data: Json, client_headers: ClientHeaders, conn: DbConn) -> JsonResult { - let data: RecoverTwoFactor = data.into_inner(); - - use crate::db::models::User; - - // Get the user - let Some(mut user) = User::find_by_mail(&data.email, &conn).await else { - err!("Username or password is incorrect. Try again.") - }; - - // Check password - if !user.check_valid_password(&data.master_password_hash) { - err!("Username or password is incorrect. Try again.") - } - - // Check if recovery code is correct - if !user.check_valid_recovery_code(&data.recovery_code) { - err!("Recovery code is incorrect. Try again.") - } - - // Remove all twofactors from the user - TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; - enforce_2fa_policy(&user, &user.uuid, client_headers.device_type, &client_headers.ip.ip, &conn).await?; - - log_user_event( - EventType::UserRecovered2fa as i32, - &user.uuid, - client_headers.device_type, - &client_headers.ip.ip, - &conn, - ) - .await; - - // Remove the recovery code, not needed without twofactors - user.totp_recover = None; - user.save(&conn).await?; - Ok(Json(Value::Object(serde_json::Map::new()))) -} - async fn _generate_recover_code(user: &mut User, conn: &DbConn) { if user.totp_recover.is_none() { let totp_recover = crypto::encode_random_bytes::<20>(&BASE32); diff --git a/src/auth.rs b/src/auth.rs index ab41898f..b71a5bd9 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -826,7 +826,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders { _ => err_handler!("Error getting DB"), }; - if !Collection::can_access_collection(&headers.membership, &col_id, &conn).await { + if !Collection::is_coll_manageable_by_user(&col_id, &headers.membership.user_uuid, &conn).await { err_handler!("The current user isn't a manager for this collection") } } @@ -908,8 +908,8 @@ impl ManagerHeaders { if uuid::Uuid::parse_str(col_id.as_ref()).is_err() { err!("Collection Id is malformed!"); } - if !Collection::can_access_collection(&h.membership, col_id, conn).await { - err!("You don't have access to all collections!"); + if !Collection::is_coll_manageable_by_user(col_id, &h.membership.user_uuid, conn).await { + err!("Collection not found", "The current user isn't a manager for this collection") } } diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 52ded966..3e6ccf21 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -513,7 +513,8 @@ impl Collection { }} } - pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool { + pub async fn is_coll_manageable_by_user(uuid: &CollectionId, user_uuid: &UserId, conn: &DbConn) -> bool { + let uuid = uuid.to_string(); let user_uuid = user_uuid.to_string(); db_run! { conn: { collections::table @@ -538,9 +539,9 @@ impl Collection { collections_groups::collections_uuid.eq(collections::uuid) ) )) - .filter(collections::uuid.eq(&self.uuid)) + .filter(collections::uuid.eq(&uuid)) .filter( - users_collections::collection_uuid.eq(&self.uuid).and(users_collections::manage.eq(true)).or(// Directly accessed collection + users_collections::collection_uuid.eq(&uuid).and(users_collections::manage.eq(true)).or(// Directly accessed collection users_organizations::access_all.eq(true).or( // access_all in Organization users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner )).or( @@ -558,6 +559,10 @@ impl Collection { .unwrap_or(0) != 0 }} } + + pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool { + Self::is_coll_manageable_by_user(&self.uuid, user_uuid, conn).await + } } /// Database methods From ba5519167634ebe1e1f0fc10d610d10d1f405101 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Wed, 4 Mar 2026 06:58:39 +0100 Subject: [PATCH 13/41] apply policies only to confirmed members (#6892) --- src/db/models/org_policy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 0607f146..96811a2b 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -269,7 +269,7 @@ impl OrgPolicy { continue; } - if let Some(user) = Membership::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await { + if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await { if user.atype < MembershipType::Admin { return true; } From 937857a0bc518710f1ff33e27e0c420d5e030da8 Mon Sep 17 00:00:00 2001 From: Ken Watanabe <51844896+dorakemon@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:50:21 +0900 Subject: [PATCH 14/41] Merge commit from fork * Fix WebAuthn backup flag update before signature verification * fix format rust file * Add test for migration update * Remove webauthn test --- src/api/core/two_factor/webauthn.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index b10a5ded..6ae12752 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/src/api/core/two_factor/webauthn.rs @@ -438,7 +438,7 @@ pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &Db // We need to check for and update the backup_eligible flag when needed. // Vaultwarden did not have knowledge of this flag prior to migrating to webauthn-rs v0.5.x // Because of this we check the flag at runtime and update the registrations and state when needed - check_and_update_backup_eligible(user_id, &rsp, &mut registrations, &mut state, conn).await?; + let backup_flags_updated = check_and_update_backup_eligible(&rsp, &mut registrations, &mut state)?; let authentication_result = WEBAUTHN.finish_passkey_authentication(&rsp, &state)?; @@ -446,7 +446,8 @@ pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &Db if ct_eq(reg.credential.cred_id(), authentication_result.cred_id()) { // If the cred id matches and the credential is updated, Some(true) is returned // In those cases, update the record, else leave it alone - if reg.credential.update_credential(&authentication_result) == Some(true) { + let credential_updated = reg.credential.update_credential(&authentication_result) == Some(true); + if credential_updated || backup_flags_updated { TwoFactor::new(user_id.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) .save(conn) .await?; @@ -463,13 +464,11 @@ pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &Db ) } -async fn check_and_update_backup_eligible( - user_id: &UserId, +fn check_and_update_backup_eligible( rsp: &PublicKeyCredential, registrations: &mut Vec, state: &mut PasskeyAuthentication, - conn: &DbConn, -) -> EmptyResult { +) -> Result { // The feature flags from the response // For details see: https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data const FLAG_BACKUP_ELIGIBLE: u8 = 0b0000_1000; @@ -486,16 +485,7 @@ async fn check_and_update_backup_eligible( let rsp_id = rsp.raw_id.as_slice(); for reg in &mut *registrations { if ct_eq(reg.credential.cred_id().as_slice(), rsp_id) { - // Try to update the key, and if needed also update the database, before the actual state check is done if reg.set_backup_eligible(backup_eligible, backup_state) { - TwoFactor::new( - user_id.clone(), - TwoFactorType::Webauthn, - serde_json::to_string(®istrations)?, - ) - .save(conn) - .await?; - // We also need to adjust the current state which holds the challenge used to start the authentication verification // Because Vaultwarden supports multiple keys, we need to loop through the deserialized state and check which key to update let mut raw_state = serde_json::to_value(&state)?; @@ -517,11 +507,12 @@ async fn check_and_update_backup_eligible( } *state = serde_json::from_value(raw_state)?; + return Ok(true); } break; } } } } - Ok(()) + Ok(false) } From e60105411b1c31a2ba4a975fec671b153eaf6bdb Mon Sep 17 00:00:00 2001 From: DerPlayer2001 <84600865+DerPlayer2001@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:57:10 +0100 Subject: [PATCH 15/41] Feat(config): add feature flag for Safari account switching (#6891) This enables the use of the Feature from this PR https://github.com/bitwarden/clients/pull/18339 --- .env.template | 1 + src/config.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 67f531fc..01b2a76e 100644 --- a/.env.template +++ b/.env.template @@ -372,6 +372,7 @@ ## Note that clients cache the /api/config endpoint for about 1 hour and it could take some time before they are enabled or disabled! ## ## The following flags are available: +## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Needs Safari >=2026.2.0) ## - "inline-menu-positioning-improvements": Enable the use of inline menu password generator and identity suggestions in the browser extension. ## - "inline-menu-totp": Enable the use of inline menu TOTP codes in the browser extension. ## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.0) diff --git a/src/config.rs b/src/config.rs index 4fb103fa..fdf72608 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1027,12 +1027,14 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { } // Server (v2025.6.2): https://github.com/bitwarden/server/blob/d094be3267f2030bd0dc62106bc6871cf82682f5/src/Core/Constants.cs#L103 - // Client (web-v2025.6.1): https://github.com/bitwarden/clients/blob/747c2fd6a1c348a57a76e4a7de8128466ffd3c01/libs/common/src/enums/feature-flag.enum.ts#L12 + // Client (web-v2026.2.0): https://github.com/bitwarden/clients/blob/a2fefe804d8c9b4a56c42f9904512c5c5821e2f6/libs/common/src/enums/feature-flag.enum.ts#L12 // Android (v2025.6.0): https://github.com/bitwarden/android/blob/b5b022caaad33390c31b3021b2c1205925b0e1a2/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L22 // iOS (v2025.6.0): https://github.com/bitwarden/ios/blob/ff06d9c6cc8da89f78f37f376495800201d7261a/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7 // // NOTE: Move deprecated flags to the utils::parse_experimental_client_feature_flags() DEPRECATED_FLAGS const! const KNOWN_FLAGS: &[&str] = &[ + // Auth Team + "pm-5594-safari-account-switching", // Autofill Team "inline-menu-positioning-improvements", "inline-menu-totp", From 747286dccdf94f762c3b4c93a82da935563418a7 Mon Sep 17 00:00:00 2001 From: Chris Kruger Date: Tue, 10 Mar 2026 01:04:17 +0800 Subject: [PATCH 16/41] fix: add ForcePasswordReset to api key login (#6904) --- src/api/identity.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/identity.rs b/src/api/identity.rs index 0ac0a730..bf093536 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -647,6 +647,7 @@ async fn _user_api_key_login( "KdfMemory": user.client_kdf_memory, "KdfParallelism": user.client_kdf_parallelism, "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing + "ForcePasswordReset": false, "scope": AuthMethod::UserApiKey.scope(), "UserDecryptionOptions": { "HasMasterPassword": has_master_password, From df25d316d68a90ecf42c31f044d09ca23a393282 Mon Sep 17 00:00:00 2001 From: pasarenicu <140453985+pasarenicu@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:06:41 +0200 Subject: [PATCH 17/41] Add Webauthn related origins flag to known flags. (#6900) support pm-30529-webauthn-related-origins flag --- src/config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config.rs b/src/config.rs index fdf72608..c12ded48 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1048,6 +1048,8 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { "anon-addy-self-host-alias", "simple-login-self-host-alias", "mutual-tls", + // Webauthn Related Origins + "pm-30529-webauthn-related-origins", ]; let configured_flags = parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags); let invalid_flags: Vec<_> = configured_flags.keys().filter(|flag| !KNOWN_FLAGS.contains(&flag.as_str())).collect(); From ecdb18fcde34743833fad40a2b8510b4e25a8cd1 Mon Sep 17 00:00:00 2001 From: Timshel Date: Mon, 9 Mar 2026 17:10:06 +0000 Subject: [PATCH 18/41] Add 30s cache to SSO exchange_refresh_token (#6866) Co-authored-by: Timshel --- Cargo.lock | 125 +++------------------------------------------- Cargo.toml | 2 +- src/sso_client.rs | 48 +++++++++++------- 3 files changed, 37 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da9e0d79..e9605aa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -815,12 +815,6 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" - [[package]] name = "bytemuck" version = "1.25.0" @@ -885,37 +879,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", -] - [[package]] name = "cbc" version = "0.1.2" @@ -1331,19 +1294,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dashmap" version = "6.1.0" @@ -1762,15 +1712,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -2101,7 +2042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8" dependencies = [ "cfg-if", - "dashmap 6.1.0", + "dashmap", "futures-sink", "futures-timer", "futures-util", @@ -3063,21 +3004,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mini-moka" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" -dependencies = [ - "crossbeam-channel", - "crossbeam-utils", - "dashmap 5.5.3", - "skeptic", - "smallvec", - "tagptr", - "triomphe", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3111,10 +3037,13 @@ version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" dependencies = [ + "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", + "event-listener 5.4.1", + "futures-util", "parking_lot", "portable-atomic", "smallvec", @@ -3921,17 +3850,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "pulldown-cmark" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - [[package]] name = "quanta" version = "0.12.6" @@ -4769,10 +4687,6 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "serde" @@ -5009,21 +4923,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" -[[package]] -name = "skeptic" -version = "0.13.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" -dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", -] - [[package]] name = "slab" version = "0.4.12" @@ -5644,12 +5543,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "triomphe" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" - [[package]] name = "try-lock" version = "0.2.5" @@ -5706,12 +5599,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -5807,7 +5694,7 @@ dependencies = [ "chrono-tz", "cookie", "cookie_store", - "dashmap 6.1.0", + "dashmap", "data-encoding", "data-url", "derive_more", @@ -5831,7 +5718,7 @@ dependencies = [ "log", "macros", "mimalloc", - "mini-moka", + "moka", "num-derive", "num-traits", "opendal", diff --git a/Cargo.toml b/Cargo.toml index fd910852..e50e006a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ governor = "0.10.4" # OIDC for SSO openidconnect = { version = "4.0.1", features = ["reqwest", "rustls-tls"] } -mini-moka = "0.10.3" +moka = { version = "0.12.13", features = ["future"] } # Check client versions for specific features. semver = "1.0.27" diff --git a/src/sso_client.rs b/src/sso_client.rs index 0d73d906..6204ab48 100644 --- a/src/sso_client.rs +++ b/src/sso_client.rs @@ -1,6 +1,5 @@ use std::{borrow::Cow, sync::LazyLock, time::Duration}; -use mini_moka::sync::Cache; use openidconnect::{core::*, reqwest, *}; use regex::Regex; use url::Url; @@ -13,9 +12,14 @@ use crate::{ }; static CLIENT_CACHE_KEY: LazyLock = LazyLock::new(|| "sso-client".to_string()); -static CLIENT_CACHE: LazyLock> = LazyLock::new(|| { - Cache::builder().max_capacity(1).time_to_live(Duration::from_secs(CONFIG.sso_client_cache_expiration())).build() +static CLIENT_CACHE: LazyLock> = LazyLock::new(|| { + moka::sync::Cache::builder() + .max_capacity(1) + .time_to_live(Duration::from_secs(CONFIG.sso_client_cache_expiration())) + .build() }); +static REFRESH_CACHE: LazyLock>> = + LazyLock::new(|| moka::future::Cache::builder().max_capacity(1000).time_to_live(Duration::from_secs(30)).build()); /// OpenID Connect Core client. pub type CustomClient = openidconnect::Client< @@ -38,6 +42,8 @@ pub type CustomClient = openidconnect::Client< EndpointSet, >; +pub type RefreshTokenResponse = (Option, String, Option); + #[derive(Clone)] pub struct Client { pub http_client: reqwest::Client, @@ -231,23 +237,29 @@ impl Client { verifier } - pub async fn exchange_refresh_token( - refresh_token: String, - ) -> ApiResult<(Option, String, Option)> { + pub async fn exchange_refresh_token(refresh_token: String) -> ApiResult { + let client = Client::cached().await?; + + REFRESH_CACHE + .get_with(refresh_token.clone(), async move { client._exchange_refresh_token(refresh_token).await }) + .await + .map_err(Into::into) + } + + async fn _exchange_refresh_token(&self, refresh_token: String) -> Result { let rt = RefreshToken::new(refresh_token); - let client = Client::cached().await?; - let token_response = - match client.core_client.exchange_refresh_token(&rt).request_async(&client.http_client).await { - Err(err) => err!(format!("Request to exchange_refresh_token endpoint failed: {:?}", err)), - Ok(token_response) => token_response, - }; - - Ok(( - token_response.refresh_token().map(|token| token.secret().clone()), - token_response.access_token().secret().clone(), - token_response.expires_in(), - )) + match self.core_client.exchange_refresh_token(&rt).request_async(&self.http_client).await { + Err(err) => { + error!("Request to exchange_refresh_token endpoint failed: {err}"); + Err(format!("Request to exchange_refresh_token endpoint failed: {err}")) + } + Ok(token_response) => Ok(( + token_response.refresh_token().map(|token| token.secret().clone()), + token_response.access_token().secret().clone(), + token_response.expires_in(), + )), + } } } From c6e99489847c5edd31bf53e1ed2f974497e88a60 Mon Sep 17 00:00:00 2001 From: phoeagon Date: Mon, 9 Mar 2026 10:21:23 -0700 Subject: [PATCH 19/41] Add cxp-import-mobile and cxp-export-mobile: feature flags on mobile (#6853) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel García --- .env.template | 2 ++ src/config.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.env.template b/.env.template index 01b2a76e..c5563a1d 100644 --- a/.env.template +++ b/.env.template @@ -382,6 +382,8 @@ ## - "anon-addy-self-host-alias": Enable configuring self-hosted Anon Addy alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0) ## - "simple-login-self-host-alias": Enable configuring self-hosted Simple Login alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0) ## - "mutual-tls": Enable the use of mutual TLS on Android (Client >= 2025.2.0) +## - "cxp-import-mobile": Enable the import via CXP on iOS (Clients >=2025.9.2) +## - "cxp-export-mobile": Enable the export via CXP on iOS (Clients >=2025.9.2) # EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials ## Require new device emails. When a user logs in an email is required to be sent. diff --git a/src/config.rs b/src/config.rs index c12ded48..0221fd9a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1048,6 +1048,8 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { "anon-addy-self-host-alias", "simple-login-self-host-alias", "mutual-tls", + "cxp-import-mobile", + "cxp-export-mobile", // Webauthn Related Origins "pm-30529-webauthn-related-origins", ]; From 2b16a05e54363ea231333e3bb55f2a481afd1acb Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 9 Mar 2026 18:38:22 +0100 Subject: [PATCH 20/41] Misc updates and fixes (#6910) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix collection details response Signed-off-by: BlackDex * Misc updates and fixes - Some clippy fixes - Crate updates - Updated Rust to v1.94.0 - Updated all GitHub Actions - Updated web-vault v2026.2.0 Signed-off-by: BlackDex * Remove commented out code --------- Signed-off-by: BlackDex Co-authored-by: Daniel García --- .github/workflows/hadolint.yml | 2 +- .github/workflows/release.yml | 32 ++-- .github/workflows/trivy.yml | 4 +- .github/workflows/typos.yml | 2 +- .pre-commit-config.yaml | 2 +- Cargo.lock | 276 +++++++++++++++++---------------- Cargo.toml | 18 +-- docker/DockerSettings.yaml | 6 +- docker/Dockerfile.alpine | 20 +-- docker/Dockerfile.debian | 14 +- macros/Cargo.toml | 4 +- rust-toolchain.toml | 2 +- src/api/core/organizations.rs | 7 +- src/api/icons.rs | 10 +- 14 files changed, 204 insertions(+), 195 deletions(-) diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 1e0fa48c..29fce9b4 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -20,7 +20,7 @@ jobs: steps: # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc117e30..8898e055 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,13 +54,13 @@ jobs: steps: - name: Initialize QEMU binfmt support - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 with: platforms: "arm64,arm" # Start Docker Buildx - name: Setup Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 # https://github.com/moby/buildkit/issues/3969 # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills with: @@ -102,7 +102,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -117,7 +117,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -133,7 +133,7 @@ jobs: # Login to Quay.io - name: Login to Quay.io - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -181,7 +181,7 @@ jobs: - name: Bake ${{ matrix.base_image }} containers id: bake_vw - uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0 + uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7.0.0 env: BASE_TAGS: "${{ steps.determine-version.outputs.BASE_TAGS }}" SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}" @@ -218,7 +218,7 @@ jobs: touch "${RUNNER_TEMP}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: digests-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }} path: ${{ runner.temp }}/digests/* @@ -233,12 +233,12 @@ jobs: # Upload artifacts to Github Actions and Attest the binaries - name: Attest binaries - uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }} - name: Upload binaries as artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }} path: vaultwarden-${{ env.NORMALIZED_ARCH }} @@ -257,7 +257,7 @@ jobs: steps: - name: Download digests - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: path: ${{ runner.temp }}/digests pattern: digests-*-${{ matrix.base_image }} @@ -265,7 +265,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -280,7 +280,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -296,7 +296,7 @@ jobs: # Login to Quay.io - name: Login to Quay.io - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} @@ -358,7 +358,7 @@ jobs: # Attest container images - name: Attest - docker.io - ${{ matrix.base_image }} if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ vars.DOCKERHUB_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -366,7 +366,7 @@ jobs: - name: Attest - ghcr.io - ${{ matrix.base_image }} if: ${{ env.HAVE_GHCR_LOGIN == 'true' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ vars.GHCR_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -374,7 +374,7 @@ jobs: - name: Attest - quay.io - ${{ matrix.base_image }} if: ${{ env.HAVE_QUAY_LOGIN == 'true' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ vars.QUAY_REPO }} subject-digest: ${{ env.DIGEST_SHA }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 13cd2e24..5fc16e71 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -38,7 +38,7 @@ jobs: persist-credentials: false - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0 + uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # 0.34.2 env: TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2 TRIVY_JAVA_DB_REPOSITORY: docker.io/aquasec/trivy-java-db:1,public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1 @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 5726a6fc..fc61e266 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -23,4 +23,4 @@ jobs: # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too - name: Spell Check Repo - uses: crate-ci/typos@57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1.43.5 + uses: crate-ci/typos@631208b7aac2daa8b707f55e7331f9112b0e062d # v1.44.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54f09b8b..3a151637 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,6 @@ repos: - "cd docker && make" # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too - repo: https://github.com/crate-ci/typos - rev: 57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1.43.5 + rev: 631208b7aac2daa8b707f55e7331f9112b0e062d # v1.44.0 hooks: - id: typos diff --git a/Cargo.lock b/Cargo.lock index e9605aa6..8a694069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ar_archive_writer" @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", @@ -360,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.14" +version = "1.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8fc176d53d6fe85017f230405e3255cedb4a02221cb55ed6d76dccbbb099b2" +checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc" dependencies = [ "aws-credential-types", "aws-runtime", @@ -380,7 +380,7 @@ dependencies = [ "fastrand", "hex", "http 1.4.0", - "ring", + "sha1", "time", "tokio", "tracing", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.13" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d203b0bf2626dcba8665f5cd0871d7c2c0930223d6b6be9097592fea21242d0" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede2ddc593e6c8acc6ce3358c28d6677a6dc49b65ba4b37a2befe14a11297e75" +checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.95.0" +version = "1.96.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c5ff27c6ba2cbd95e6e26e2e736676fdf6bcf96495b187733f521cfe4ce448" +checksum = "f64a6eded248c6b453966e915d32aeddb48ea63ad17932682774eb026fbef5b1" dependencies = [ "aws-credential-types", "aws-runtime", @@ -451,9 +451,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.97.0" +version = "1.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d186f1e5a3694a188e5a0640b3115ccc6e084d104e16fd6ba968dca072ffef8" +checksum = "db96d720d3c622fcbe08bae1c4b04a72ce6257d8b0584cb5418da00ae20a344f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.99.0" +version = "1.100.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acba7c62f3d4e2408fa998a3a8caacd8b9a5b5549cf36e2372fbdae329d5449" +checksum = "fafbdda43b93f57f699c5dfe8328db590b967b8a820a13ccdd6687355dfcc7ca" dependencies = [ "aws-credential-types", "aws-runtime", @@ -500,9 +500,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37411f8e0f4bea0c3ca0958ce7f18f6439db24d555dbd809787262cd00926aa9" +checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -522,9 +522,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.13" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc50d0f63e714784b84223abd7abbc8577de8c35d699e0edd19f0a88a08ae13" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" dependencies = [ "futures-util", "pin-project-lite", @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.63.5" +version = "0.63.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d619373d490ad70966994801bc126846afaa0d1ee920697a031f0cf63f2568e7" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -554,27 +554,27 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.4" +version = "0.62.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b3a779093e18cad88bbae08dc4261e1d95018c4c5b9356a52bcae7c0b6e9bb" +checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3f39d5bb871aaf461d59144557f16d5927a5248a983a40654d9cf3b9ba183b" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.14" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f76a580e3d8f8961e5d48763214025a2af65c2fa4cd1fb7f270a0e107a71b0" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" dependencies = [ "aws-smithy-types", "urlencoding", @@ -582,9 +582,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ccf7f6eba8b2dcf8ce9b74806c6c185659c311665c4bf8d6e71ebd454db6bf" +checksum = "028999056d2d2fd58a697232f9eec4a643cf73a71cf327690a7edad1d2af2110" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.11.5" +version = "1.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4af6e5def28be846479bbeac55aa4603d6f7986fc5da4601ba324dd5d377516" +checksum = "876ab3c9c29791ba4ba02b780a3049e21ec63dabda09268b175272c3733a79e6" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca2734c16913a45343b37313605d84e7d8b34a4611598ce1d25b35860a2bed3" +checksum = "d2b1117b3b2bbe166d11199b540ceed0d0f7676e36e7b962b5a437a9971eac75" dependencies = [ "base64-simd", "bytes", @@ -646,18 +646,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.14" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.13" +version = "1.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0470cc047657c6e286346bdf10a8719d26efd6a91626992e0e64481e44323e96" +checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -811,9 +811,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" @@ -925,9 +925,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -975,9 +975,9 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" [[package]] name = "compression-codecs" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "brotli", "compression-core", @@ -1347,9 +1347,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -1998,20 +1998,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.0", "wasip2", "wasip3", @@ -2399,7 +2399,7 @@ dependencies = [ "http 1.4.0", "hyper 1.8.1", "hyper-util", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -2425,7 +2425,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -2624,9 +2624,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" @@ -2672,9 +2672,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] name = "jiff" -version = "0.2.20" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2687,9 +2687,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.20" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -2698,9 +2698,9 @@ dependencies = [ [[package]] name = "jiff-tzdb" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] name = "jiff-tzdb-platform" @@ -2734,9 +2734,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -2835,10 +2835,10 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "serde", - "socket2 0.6.2", + "socket2 0.6.3", "tokio", "tokio-rustls 0.26.4", "tracing", @@ -2880,9 +2880,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -3033,9 +3033,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.13" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" dependencies = [ "async-lock", "crossbeam-channel", @@ -3654,9 +3654,9 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -3666,9 +3666,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", @@ -3903,8 +3903,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.36", - "socket2 0.6.2", + "rustls 0.23.37", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -3923,7 +3923,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -3941,16 +3941,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3967,6 +3967,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "r2d2" version = "0.8.10" @@ -4006,7 +4012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ "chacha20", - "getrandom 0.4.1", + "getrandom 0.4.2", "rand_core 0.10.0", ] @@ -4123,9 +4129,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reopen" @@ -4198,7 +4204,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "serde", @@ -4450,9 +4456,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -4475,9 +4481,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -4661,9 +4667,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -4674,9 +4680,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -4803,9 +4809,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "base64 0.22.1", "chrono", @@ -4822,9 +4828,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -4947,12 +4953,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5056,9 +5062,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -5126,12 +5132,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -5264,9 +5270,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -5274,16 +5280,16 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -5306,7 +5312,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.36", + "rustls 0.23.37", "tokio", ] @@ -5369,7 +5375,7 @@ dependencies = [ "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -5401,7 +5407,7 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -5410,7 +5416,7 @@ version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -5656,11 +5662,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -5819,9 +5825,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -5832,9 +5838,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -5846,9 +5852,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5856,9 +5862,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -5869,9 +5875,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -5925,9 +5931,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -6022,9 +6028,9 @@ dependencies = [ [[package]] name = "which" -version = "8.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +checksum = "3a824aeba0fbb27264f815ada4cff43d65b1741b7a4ed7629ff9089148c4a4e0" dependencies = [ "env_home", "rustix", @@ -6389,9 +6395,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -6585,18 +6591,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e50e006a..39de03e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2021" -rust-version = "1.91.0" +rust-version = "1.92.0" license = "AGPL-3.0-only" repository = "https://github.com/dani-garcia/vaultwarden" publish = false @@ -79,7 +79,7 @@ dashmap = "6.1.0" # Async futures futures = "0.3.32" -tokio = { version = "1.49.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } +tokio = { version = "1.50.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } tokio-util = { version = "0.7.18", features = ["compat"]} # A generic serialization/deserialization framework @@ -103,10 +103,10 @@ ring = "0.17.14" subtle = "2.6.1" # UUID generation -uuid = { version = "1.21.0", features = ["v4"] } +uuid = { version = "1.22.0", features = ["v4"] } # Date and time libraries -chrono = { version = "0.4.43", features = ["clock", "serde"], default-features = false } +chrono = { version = "0.4.44", features = ["clock", "serde"], default-features = false } chrono-tz = "0.10.4" time = "0.3.47" @@ -182,7 +182,7 @@ semver = "1.0.27" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.48", features = ["secure"], default-features = false, optional = true } -which = "8.0.0" +which = "8.0.1" # Argon2 library with support for the PHC format argon2 = "0.5.3" @@ -197,10 +197,10 @@ grass_compiler = { version = "0.13.4", default-features = false } opendal = { version = "0.55.0", features = ["services-fs"], default-features = false } # For retrieving AWS credentials, including temporary SSO credentials -anyhow = { version = "1.0.101", optional = true } -aws-config = { version = "1.8.14", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } -aws-credential-types = { version = "1.2.13", optional = true } -aws-smithy-runtime-api = { version = "1.11.5", optional = true } +anyhow = { version = "1.0.102", optional = true } +aws-config = { version = "1.8.15", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true } +aws-credential-types = { version = "1.2.14", optional = true } +aws-smithy-runtime-api = { version = "1.11.6", optional = true } http = { version = "1.4.0", optional = true } reqsign = { version = "0.16.5", optional = true } diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 7b4a9af7..610f6b4a 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -1,11 +1,11 @@ --- -vault_version: "v2026.1.1" -vault_image_digest: "sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7" +vault_version: "v2026.2.0" +vault_image_digest: "sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447" # Cross Compile Docker Helper Scripts v1.9.0 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.93.1 # Rust version to be used +rust_version: 1.94.0 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.23" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index b3df2f4e..44242e08 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -19,23 +19,23 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.1 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.1 -# [docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.2.0 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.2.0 +# [docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 -# [docker.io/vaultwarden/web-vault:v2026.1.1] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 +# [docker.io/vaultwarden/web-vault:v2026.2.0] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 AS vault ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.93.1 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.93.1 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.93.1 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.93.1 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.94.0 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.94.0 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.94.0 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.94.0 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 8796dedb..a60c485d 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -19,15 +19,15 @@ # - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # click the tag name to view the digest of the image it currently points to. # - From the command line: -# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.1 -# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.1 -# [docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7] +# $ docker pull docker.io/vaultwarden/web-vault:v2026.2.0 +# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.2.0 +# [docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447] # # - Conversely, to get the tag name from the digest: -# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 -# [docker.io/vaultwarden/web-vault:v2026.1.1] +# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 +# [docker.io/vaultwarden/web-vault:v2026.2.0] # -FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 AS vault +FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 AS vault ########################## Cross Compile Docker Helper Scripts ########################## ## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.93.1-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.94.0-slim-trixie AS build COPY --from=xx / / ARG TARGETARCH ARG TARGETVARIANT diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 0a560a74..eb3bd670 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,8 +13,8 @@ path = "src/lib.rs" proc-macro = true [dependencies] -quote = "1.0.44" -syn = "2.0.114" +quote = "1.0.45" +syn = "2.0.117" [lints] workspace = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 585747e0..0fc3f36d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.93.1" +channel = "1.94.0" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 4a5066ab..0213a006 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -395,7 +395,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea Membership::find_confirmed_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect(); // check if current user has full access to the organization (either directly or via any group) - let has_full_access_to_org = member.access_all + let has_full_access_to_org = member.has_full_access() || (CONFIG.org_groups_enabled() && GroupUser::has_full_access_by_member(&org_id, &member.uuid, &conn).await); // Get all admins, owners and managers who can manage/access all @@ -421,6 +421,11 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea || (CONFIG.org_groups_enabled() && GroupUser::has_access_to_collection_by_member(&col.uuid, &member.uuid, &conn).await); + // If the user is a manager, and is not assigned to this collection, skip this and continue with the next collection + if !assigned { + continue; + } + // get the users assigned directly to the given collection let mut users: Vec = col_users .iter() diff --git a/src/api/icons.rs b/src/api/icons.rs index 35a1de30..da83d0c4 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -513,13 +513,11 @@ fn parse_sizes(sizes: &str) -> (u16, u16) { if !sizes.is_empty() { match ICON_SIZE_REGEX.captures(sizes.trim()) { - None => {} - Some(dimensions) => { - if dimensions.len() >= 3 { - width = dimensions[1].parse::().unwrap_or_default(); - height = dimensions[2].parse::().unwrap_or_default(); - } + Some(dimensions) if dimensions.len() >= 3 => { + width = dimensions[1].parse::().unwrap_or_default(); + height = dimensions[2].parse::().unwrap_or_default(); } + _ => {} } } From 1a1d7f578a6a9cad24af667ad0eb6380d04affab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 9 Mar 2026 19:14:28 +0100 Subject: [PATCH 21/41] Support new desktop origin on CORS (#6920) --- src/util.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util.rs b/src/util.rs index aa4e7914..6da1c3df 100644 --- a/src/util.rs +++ b/src/util.rs @@ -153,9 +153,11 @@ impl Cors { fn get_allowed_origin(headers: &HeaderMap<'_>) -> Option { let origin = Cors::get_header(headers, "Origin"); let safari_extension_origin = "file://"; + let desktop_custom_file_origin = "bw-desktop-file://bundle"; if origin == CONFIG.domain_origin() || origin == safari_extension_origin + || origin == desktop_custom_file_origin || (CONFIG.sso_enabled() && origin == CONFIG.sso_authority()) { Some(origin) From 065c1f2cd515dfc16251ce993768d174b309275f Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 9 Mar 2026 20:35:14 +0200 Subject: [PATCH 22/41] Fix `checkout` action version (#6921) - wasn't getting picked up when updating action due to being formatted as `#v6.0.0` instead of `# v6.0.0` --- .github/workflows/build.yml | 2 +- .github/workflows/check-templates.yml | 2 +- .github/workflows/hadolint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/trivy.yml | 2 +- .github/workflows/typos.yml | 2 +- .github/workflows/zizmor.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e3468f4..8d03ee00 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: # Checkout the repo - name: "Checkout" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/check-templates.yml b/.github/workflows/check-templates.yml index a8415dde..57b53bf4 100644 --- a/.github/workflows/check-templates.yml +++ b/.github/workflows/check-templates.yml @@ -20,7 +20,7 @@ jobs: steps: # Checkout the repo - name: "Checkout" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # End Checkout the repo diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 29fce9b4..2b476904 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -40,7 +40,7 @@ jobs: # End Download hadolint # Checkout the repo - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # End Checkout the repo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8898e055..c3b0b9a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: # Checkout the repo - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # We need fetch-depth of 0 so we also get all the tag metadata with: persist-credentials: false diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 5fc16e71..1f36ab01 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index fc61e266..d7b645e0 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -16,7 +16,7 @@ jobs: steps: # Checkout the repo - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # End Checkout the repo diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 4051a8b2..22f3e7e9 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -19,7 +19,7 @@ jobs: security-events: write # To write the security report steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false From 9c7df6412c088832e1aeb673000716f5801dc8ed Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 9 Mar 2026 21:13:27 +0100 Subject: [PATCH 23/41] Fix apikey login (#6922) The API Key login needs some extra JSON return key's, same as password login. Fixes #6912 Signed-off-by: BlackDex --- src/api/identity.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/api/identity.rs b/src/api/identity.rs index bf093536..f3fd3d1a 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -633,6 +633,19 @@ async fn _user_api_key_login( Value::Null }; + let account_keys = if user.private_key.is_some() { + json!({ + "publicKeyEncryptionKeyPair": { + "wrappedPrivateKey": user.private_key, + "publicKey": user.public_key, + "Object": "publicKeyEncryptionKeyPair" + }, + "Object": "privateKeys" + }) + } else { + Value::Null + }; + // Note: No refresh_token is returned. The CLI just repeats the // client_credentials login flow when the existing token expires. let result = json!({ @@ -649,6 +662,7 @@ async fn _user_api_key_login( "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing "ForcePasswordReset": false, "scope": AuthMethod::UserApiKey.scope(), + "AccountKeys": account_keys, "UserDecryptionOptions": { "HasMasterPassword": has_master_password, "MasterPasswordUnlock": master_password_unlock, From 2b3736802d5ce1de3ba73b7ae82b9ecdb8cbd1be Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Tue, 17 Mar 2026 17:01:32 +0100 Subject: [PATCH 24/41] Fix email header base64 padding (#6961) Newer versions of the Bitwarden client use Base64 with padding. Since this is not a streaming string, but a defined length, we can just strip the `=` chars. Fixes #6960 Signed-off-by: BlackDex --- src/api/core/accounts.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 0ce1f684..d91eb4cd 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -1328,6 +1328,11 @@ impl<'r> FromRequest<'r> for KnownDevice { async fn from_request(req: &'r Request<'_>) -> Outcome { let email = if let Some(email_b64) = req.headers().get_one("X-Request-Email") { + // Bitwarden seems to send padded Base64 strings since 2026.2.1 + // Since these values are not streamed and Headers are always split by newlines + // we can safely ignore padding here and remove any '=' appended. + let email_b64 = email_b64.trim_end_matches('='); + let Ok(email_bytes) = data_encoding::BASE64URL_NOPAD.decode(email_b64.as_bytes()) else { return Outcome::Error((Status::BadRequest, "X-Request-Email value failed to decode as base64url")); }; From 650defac75571b9ec74e38d92bed0c49f64e3ba2 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 23 Mar 2026 21:21:21 +0100 Subject: [PATCH 25/41] Update Feature Flags (#6981) * Update Feature Flags Added new feature flags which could be supported without issues. Removed all deprecated feature flags and only match supported flags. Do not error on invalid flags during load, but do on config save via admin interface. During load it will print a `WARNING`, this is to prevent breaking setups when flags are removed, but are still configured. There are no feature flags anymore currently needed to be set by default, so those are removed now. Signed-off-by: BlackDex * Adjust code a bit and add Diagnostics check Signed-off-by: BlackDex * Update .env template Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- .env.template | 29 ++++---- src/api/admin.rs | 10 ++- src/api/core/mod.rs | 31 ++++----- src/config.rs | 80 ++++++++++++---------- src/static/scripts/admin_diagnostics.js | 7 ++ src/static/templates/admin/diagnostics.hbs | 8 +++ src/util.rs | 34 +++++---- 7 files changed, 122 insertions(+), 77 deletions(-) diff --git a/.env.template b/.env.template index c5563a1d..03990820 100644 --- a/.env.template +++ b/.env.template @@ -372,19 +372,22 @@ ## Note that clients cache the /api/config endpoint for about 1 hour and it could take some time before they are enabled or disabled! ## ## The following flags are available: -## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Needs Safari >=2026.2.0) -## - "inline-menu-positioning-improvements": Enable the use of inline menu password generator and identity suggestions in the browser extension. -## - "inline-menu-totp": Enable the use of inline menu TOTP codes in the browser extension. -## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.0) -## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Needs clients >=2024.12.0) -## - "pm-25373-windows-biometrics-v2": Enable the new implementation of biometrics on Windows. (Needs desktop >= 2025.11.0) -## - "export-attachments": Enable support for exporting attachments (Clients >=2025.4.0) -## - "anon-addy-self-host-alias": Enable configuring self-hosted Anon Addy alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0) -## - "simple-login-self-host-alias": Enable configuring self-hosted Simple Login alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0) -## - "mutual-tls": Enable the use of mutual TLS on Android (Client >= 2025.2.0) -## - "cxp-import-mobile": Enable the import via CXP on iOS (Clients >=2025.9.2) -## - "cxp-export-mobile": Enable the export via CXP on iOS (Clients >=2025.9.2) -# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials +## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Safari >= 2026.2.0) +## - "ssh-agent": Enable SSH agent support on Desktop. (Desktop >= 2024.12.0) +## - "ssh-agent-v2": Enable newer SSH agent support. (Desktop >= 2026.2.1) +## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Clients >= 2024.12.0) +## - "pm-25373-windows-biometrics-v2": Enable the new implementation of biometrics on Windows. (Desktop >= 2025.11.0) +## - "anon-addy-self-host-alias": Enable configuring self-hosted Anon Addy alias generator. (Android >= 2025.3.0, iOS >= 2025.4.0) +## - "simple-login-self-host-alias": Enable configuring self-hosted Simple Login alias generator. (Android >= 2025.3.0, iOS >= 2025.4.0) +## - "mutual-tls": Enable the use of mutual TLS on Android (Clients >= 2025.2.0) +## - "cxp-import-mobile": Enable the import via CXP on iOS (Clients >= 2025.9.2) +## - "cxp-export-mobile": Enable the export via CXP on iOS (Clients >= 2025.9.2) +## - "pm-30529-webauthn-related-origins": +## - "desktop-ui-migration-milestone-1": Special feature flag for desktop UI (Desktop >= 2026.2.0) +## - "desktop-ui-migration-milestone-2": Special feature flag for desktop UI (Desktop >= 2026.2.0) +## - "desktop-ui-migration-milestone-3": Special feature flag for desktop UI (Desktop >= 2026.2.0) +## - "desktop-ui-migration-milestone-4": Special feature flag for desktop UI (Desktop >= 2026.2.0) +# EXPERIMENTAL_CLIENT_FEATURE_FLAGS= ## Require new device emails. When a user logs in an email is required to be sent. ## If sending the email fails the login attempt will fail!! diff --git a/src/api/admin.rs b/src/api/admin.rs index badfaa3a..475797a7 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -32,7 +32,7 @@ use crate::{ mail, util::{ container_base_image, format_naive_datetime_local, get_active_web_release, get_display_size, - is_running_in_container, NumberOrString, + is_running_in_container, parse_experimental_client_feature_flags, FeatureFlagFilter, NumberOrString, }, CONFIG, VERSION, }; @@ -734,6 +734,13 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A let ip_header_name = &ip_header.0.unwrap_or_default(); + let invalid_feature_flags: Vec = parse_experimental_client_feature_flags( + &CONFIG.experimental_client_feature_flags(), + FeatureFlagFilter::InvalidOnly, + ) + .into_keys() + .collect(); + let diagnostics_json = json!({ "dns_resolved": dns_resolved, "current_release": VERSION, @@ -756,6 +763,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A "db_version": get_sql_server_version(&conn).await, "admin_url": format!("{}/diagnostics", admin_url()), "overrides": &CONFIG.get_overrides().join(", "), + "invalid_feature_flags": invalid_feature_flags, "host_arch": env::consts::ARCH, "host_os": env::consts::OS, "tz_env": env::var("TZ").unwrap_or_default(), diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index dc7f4628..038b9a6d 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -59,7 +59,8 @@ use crate::{ error::Error, http_client::make_http_request, mail, - util::parse_experimental_client_feature_flags, + util::{parse_experimental_client_feature_flags, FeatureFlagFilter}, + CONFIG, }; #[derive(Debug, Serialize, Deserialize)] @@ -136,7 +137,7 @@ async fn put_eq_domains(data: Json, headers: Headers, conn: DbC #[get("/hibp/breach?")] async fn hibp_breach(username: &str, _headers: Headers) -> JsonResult { let username: String = url::form_urlencoded::byte_serialize(username.as_bytes()).collect(); - if let Some(api_key) = crate::CONFIG.hibp_api_key() { + if let Some(api_key) = CONFIG.hibp_api_key() { let url = format!( "https://haveibeenpwned.com/api/v3/breachedaccount/{username}?truncateResponse=false&includeUnverified=false" ); @@ -197,19 +198,17 @@ fn get_api_webauthn(_headers: Headers) -> Json { #[get("/config")] fn config() -> Json { - let domain = crate::CONFIG.domain(); + let domain = CONFIG.domain(); // Official available feature flags can be found here: - // Server (v2025.6.2): https://github.com/bitwarden/server/blob/d094be3267f2030bd0dc62106bc6871cf82682f5/src/Core/Constants.cs#L103 - // Client (web-v2025.6.1): https://github.com/bitwarden/clients/blob/747c2fd6a1c348a57a76e4a7de8128466ffd3c01/libs/common/src/enums/feature-flag.enum.ts#L12 - // Android (v2025.6.0): https://github.com/bitwarden/android/blob/b5b022caaad33390c31b3021b2c1205925b0e1a2/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L22 - // iOS (v2025.6.0): https://github.com/bitwarden/ios/blob/ff06d9c6cc8da89f78f37f376495800201d7261a/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7 - let mut feature_states = - parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags()); - feature_states.insert("duo-redirect".to_string(), true); - feature_states.insert("email-verification".to_string(), true); - feature_states.insert("unauth-ui-refresh".to_string(), true); - feature_states.insert("enable-pm-flight-recorder".to_string(), true); - feature_states.insert("mobile-error-reporting".to_string(), true); + // Server (v2026.2.1): https://github.com/bitwarden/server/blob/0e42725d0837bd1c0dabd864ff621a579959744b/src/Core/Constants.cs#L135 + // Client (v2026.2.1): https://github.com/bitwarden/clients/blob/f96380c3138291a028bdd2c7a5fee540d5c98ba5/libs/common/src/enums/feature-flag.enum.ts#L12 + // Android (v2026.2.1): https://github.com/bitwarden/android/blob/6902c19c0093fa476bbf74ccaa70c9f14afbb82f/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt#L31 + // iOS (v2026.2.1): https://github.com/bitwarden/ios/blob/cdd9ba1770ca2ffc098d02d12cc3208e3a830454/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7 + let feature_states = parse_experimental_client_feature_flags( + &CONFIG.experimental_client_feature_flags(), + FeatureFlagFilter::ValidOnly, + ); + // Add default feature_states here if needed, currently no features are needed by default. Json(json!({ // Note: The clients use this version to handle backwards compatibility concerns @@ -225,7 +224,7 @@ fn config() -> Json { "url": "https://github.com/dani-garcia/vaultwarden" }, "settings": { - "disableUserRegistration": crate::CONFIG.is_signup_disabled() + "disableUserRegistration": CONFIG.is_signup_disabled() }, "environment": { "vault": domain, @@ -278,7 +277,7 @@ async fn accept_org_invite( member.save(conn).await?; - if crate::CONFIG.mail_enabled() { + if CONFIG.mail_enabled() { let org = match Organization::find_by_uuid(&member.org_uuid, conn).await { Some(org) => org, None => err!("Organization not found."), diff --git a/src/config.rs b/src/config.rs index 0221fd9a..6ff09467 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,7 +14,10 @@ use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; use crate::{ error::Error, - util::{get_active_web_release, get_env, get_env_bool, is_valid_email, parse_experimental_client_feature_flags}, + util::{ + get_active_web_release, get_env, get_env_bool, is_valid_email, parse_experimental_client_feature_flags, + FeatureFlagFilter, + }, }; static CONFIG_FILE: LazyLock = LazyLock::new(|| { @@ -920,7 +923,7 @@ make_config! { }, } -fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { +fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { // Validate connection URL is valid and DB feature is enabled #[cfg(sqlite)] { @@ -1026,39 +1029,17 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { } } - // Server (v2025.6.2): https://github.com/bitwarden/server/blob/d094be3267f2030bd0dc62106bc6871cf82682f5/src/Core/Constants.cs#L103 - // Client (web-v2026.2.0): https://github.com/bitwarden/clients/blob/a2fefe804d8c9b4a56c42f9904512c5c5821e2f6/libs/common/src/enums/feature-flag.enum.ts#L12 - // Android (v2025.6.0): https://github.com/bitwarden/android/blob/b5b022caaad33390c31b3021b2c1205925b0e1a2/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L22 - // iOS (v2025.6.0): https://github.com/bitwarden/ios/blob/ff06d9c6cc8da89f78f37f376495800201d7261a/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7 - // - // NOTE: Move deprecated flags to the utils::parse_experimental_client_feature_flags() DEPRECATED_FLAGS const! - const KNOWN_FLAGS: &[&str] = &[ - // Auth Team - "pm-5594-safari-account-switching", - // Autofill Team - "inline-menu-positioning-improvements", - "inline-menu-totp", - "ssh-agent", - // Key Management Team - "ssh-key-vault-item", - "pm-25373-windows-biometrics-v2", - // Tools - "export-attachments", - // Mobile Team - "anon-addy-self-host-alias", - "simple-login-self-host-alias", - "mutual-tls", - "cxp-import-mobile", - "cxp-export-mobile", - // Webauthn Related Origins - "pm-30529-webauthn-related-origins", - ]; - let configured_flags = parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags); - let invalid_flags: Vec<_> = configured_flags.keys().filter(|flag| !KNOWN_FLAGS.contains(&flag.as_str())).collect(); + let invalid_flags = + parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags, FeatureFlagFilter::InvalidOnly); if !invalid_flags.is_empty() { - err!(format!("Unrecognized experimental client feature flags: {invalid_flags:?}.\n\n\ + let feature_flags_error = format!("Unrecognized experimental client feature flags: {:?}.\n\ Please ensure all feature flags are spelled correctly and that they are supported in this version.\n\ - Supported flags: {KNOWN_FLAGS:?}")); + Supported flags: {:?}\n", invalid_flags, SUPPORTED_FEATURE_FLAGS); + if on_update { + err!(feature_flags_error); + } else { + println!("[WARNING] {feature_flags_error}"); + } } const MAX_FILESIZE_KB: i64 = i64::MAX >> 10; @@ -1477,6 +1458,35 @@ pub enum PathType { RsaKey, } +// Official available feature flags can be found here: +// Server (v2026.2.1): https://github.com/bitwarden/server/blob/0e42725d0837bd1c0dabd864ff621a579959744b/src/Core/Constants.cs#L135 +// Client (v2026.2.1): https://github.com/bitwarden/clients/blob/f96380c3138291a028bdd2c7a5fee540d5c98ba5/libs/common/src/enums/feature-flag.enum.ts#L12 +// Android (v2026.2.1): https://github.com/bitwarden/android/blob/6902c19c0093fa476bbf74ccaa70c9f14afbb82f/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt#L31 +// iOS (v2026.2.1): https://github.com/bitwarden/ios/blob/cdd9ba1770ca2ffc098d02d12cc3208e3a830454/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7 +pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[ + // Architecture + "desktop-ui-migration-milestone-1", + "desktop-ui-migration-milestone-2", + "desktop-ui-migration-milestone-3", + "desktop-ui-migration-milestone-4", + // Auth Team + "pm-5594-safari-account-switching", + // Autofill Team + "ssh-agent", + "ssh-agent-v2", + // Key Management Team + "ssh-key-vault-item", + "pm-25373-windows-biometrics-v2", + // Mobile Team + "anon-addy-self-host-alias", + "simple-login-self-host-alias", + "mutual-tls", + "cxp-import-mobile", + "cxp-export-mobile", + // Platform Team + "pm-30529-webauthn-related-origins", +]; + impl Config { pub async fn load() -> Result { // Loading from env and file @@ -1490,7 +1500,7 @@ impl Config { // Fill any missing with defaults let config = builder.build(); if !SKIP_CONFIG_VALIDATION.load(Ordering::Relaxed) { - validate_config(&config)?; + validate_config(&config, false)?; } Ok(Config { @@ -1526,7 +1536,7 @@ impl Config { let env = &self.inner.read().unwrap()._env; env.merge(&builder, false, &mut overrides).build() }; - validate_config(&config)?; + validate_config(&config, true)?; // Save both the user and the combined config { diff --git a/src/static/scripts/admin_diagnostics.js b/src/static/scripts/admin_diagnostics.js index 5594b439..2cff4410 100644 --- a/src/static/scripts/admin_diagnostics.js +++ b/src/static/scripts/admin_diagnostics.js @@ -109,6 +109,9 @@ async function generateSupportString(event, dj) { supportString += "* Websocket Check: disabled\n"; } supportString += `* HTTP Response Checks: ${httpResponseCheck}\n`; + if (dj.invalid_feature_flags != "") { + supportString += `* Invalid feature flags: true\n`; + } const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, { "headers": { "Accept": "application/json" } @@ -128,6 +131,10 @@ async function generateSupportString(event, dj) { supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`; } + if (dj.invalid_feature_flags != "") { + supportString += `\n**Invalid feature flags:** ${dj.invalid_feature_flags}\n`; + } + // Add http response check messages if they exists if (httpResponseCheck === false) { supportString += "\n**Failed HTTP Checks:**\n"; diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs index 77f2c95b..d27bce4c 100644 --- a/src/static/templates/admin/diagnostics.hbs +++ b/src/static/templates/admin/diagnostics.hbs @@ -194,6 +194,14 @@
+ {{#if page_data.invalid_feature_flags}} +
Invalid Feature Flags + Warning +
+
+ Flags: {{page_data.invalid_feature_flags}} +
+ {{/if}} diff --git a/src/util.rs b/src/util.rs index 6da1c3df..d336689d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -16,7 +16,10 @@ use tokio::{ time::{sleep, Duration}, }; -use crate::{config::PathType, CONFIG}; +use crate::{ + config::{PathType, SUPPORTED_FEATURE_FLAGS}, + CONFIG, +}; pub struct AppHeaders(); @@ -765,21 +768,28 @@ pub fn convert_json_key_lcase_first(src_json: Value) -> Value { } } +pub enum FeatureFlagFilter { + #[allow(dead_code)] + Unfiltered, + ValidOnly, + InvalidOnly, +} + /// Parses the experimental client feature flags string into a HashMap. -pub fn parse_experimental_client_feature_flags(experimental_client_feature_flags: &str) -> HashMap { - // These flags could still be configured, but are deprecated and not used anymore - // To prevent old installations from starting filter these out and not error out - const DEPRECATED_FLAGS: &[&str] = - &["autofill-overlay", "autofill-v2", "browser-fileless-import", "extension-refresh", "fido2-vault-credentials"]; +pub fn parse_experimental_client_feature_flags( + experimental_client_feature_flags: &str, + filter_mode: FeatureFlagFilter, +) -> HashMap { experimental_client_feature_flags .split(',') - .filter_map(|f| { - let flag = f.trim(); - if !flag.is_empty() && !DEPRECATED_FLAGS.contains(&flag) { - return Some((flag.to_owned(), true)); - } - None + .map(str::trim) + .filter(|flag| !flag.is_empty()) + .filter(|flag| match filter_mode { + FeatureFlagFilter::Unfiltered => true, + FeatureFlagFilter::ValidOnly => SUPPORTED_FEATURE_FLAGS.contains(flag), + FeatureFlagFilter::InvalidOnly => !SUPPORTED_FEATURE_FLAGS.contains(flag), }) + .map(|flag| (flag.to_owned(), true)) .collect() } From 711bb53d3d0e5d1aa63bbd1b8ba23ae654d9896f Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 23 Mar 2026 21:26:11 +0100 Subject: [PATCH 26/41] Update crates and GHA (#6980) Updated all crates which are possible. Updated all GitHub Actions to their latest version. There was a supply-chain attack on the trivy action to which we were not exposed since we were using pinned sha hashes. The latest version v0.35.0 is not vulnerable and that version will be used with this commit. Also removed `dtolnay/rust-toolchain` as suggested by zizmor and adjusted the way to install the correct toolchain. Since this GitHub Action did not used any version tagging, it was also cumbersome to update. Signed-off-by: BlackDex --- .github/workflows/build.yml | 35 +++---- .github/workflows/release.yml | 2 +- .github/workflows/trivy.yml | 4 +- .github/workflows/zizmor.yml | 2 +- Cargo.lock | 187 +++++++++++++++++++--------------- Cargo.toml | 12 +-- src/api/admin.rs | 1 - 7 files changed, 129 insertions(+), 114 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d03ee00..6269e595 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,32 +85,23 @@ jobs: # End Determine rust-toolchain version - # Only install the clippy and rustfmt components on the default rust-toolchain - - name: "Install rust-toolchain version" - uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master @ Feb 13, 2026, 3:46 AM GMT+1 - if: ${{ matrix.channel == 'rust-toolchain' }} - with: - toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" - components: clippy, rustfmt - # End Uses the rust-toolchain file to determine version - - - # Install the any other channel to be used for which we do not execute clippy and rustfmt - - name: "Install MSRV version" - uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master @ Feb 13, 2026, 3:46 AM GMT+1 - if: ${{ matrix.channel != 'rust-toolchain' }} - with: - toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" - # End Install the MSRV channel to be used - - # Set the current matrix toolchain version as default - - name: "Set toolchain ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} as default" + - name: "Install toolchain ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} as default" env: + CHANNEL: ${{ matrix.channel }} RUST_TOOLCHAIN: ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} run: | # Remove the rust-toolchain.toml rm rust-toolchain.toml - # Set the default + + # Install the correct toolchain version + rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal --no-self-update + + # If this matrix is the `rust-toolchain` flow, also install rustfmt and clippy + if [[ "${CHANNEL}" == 'rust-toolchain' ]]; then + rustup component add --toolchain "${RUST_TOOLCHAIN}" rustfmt clippy + fi + + # Set as the default toolchain rustup default "${RUST_TOOLCHAIN}" # Show environment @@ -122,7 +113,7 @@ jobs: # Enable Rust Caching - name: Rust Caching - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: # Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes. # Like changing the build host from Ubuntu 20.04 to 22.04 for example. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3b0b9a7..f2080cb2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -257,7 +257,7 @@ jobs: steps: - name: Download digests - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: ${{ runner.temp }}/digests pattern: digests-*-${{ matrix.base_image }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 1f36ab01..94ff6f63 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -38,7 +38,7 @@ jobs: persist-credentials: false - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # 0.34.2 + uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0 env: TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2 TRIVY_JAVA_DB_REPOSITORY: docker.io/aquasec/trivy-java-db:1,public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1 @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 22f3e7e9..4bd40db3 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 + uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 with: # intentionally not scanning the entire repository, # since it contains integration tests. diff --git a/Cargo.lock b/Cargo.lock index 8a694069..b16d3fdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.96.0" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64a6eded248c6b453966e915d32aeddb48ea63ad17932682774eb026fbef5b1" +checksum = "9aadc669e184501caaa6beafb28c6267fc1baef0810fb58f9b205485ca3f2567" dependencies = [ "aws-credential-types", "aws-runtime", @@ -451,9 +451,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.98.0" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db96d720d3c622fcbe08bae1c4b04a72ce6257d8b0584cb5418da00ae20a344f" +checksum = "1342a7db8f358d3de0aed2007a0b54e875458e39848d54cc1d46700b2bfcb0a8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.100.0" +version = "1.101.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafbdda43b93f57f699c5dfe8328db590b967b8a820a13ccdd6687355dfcc7ca" +checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a" dependencies = [ "aws-credential-types", "aws-runtime", @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b1117b3b2bbe166d11199b540ceed0d0f7676e36e7b962b5a437a9971eac75" +checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" dependencies = [ "base64-simd", "bytes", @@ -845,17 +845,18 @@ dependencies = [ [[package]] name = "cached" -version = "0.56.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801927ee168e17809ab8901d9f01f700cd7d8d6a6527997fee44e4b0327a253c" +checksum = "53b6f5d101f0f6322c8646a45b7c581a673e476329040d97565815c2461dd0c4" dependencies = [ "ahash", "async-trait", "cached_proc_macro", "cached_proc_macro_types", "futures", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "once_cell", + "parking_lot", "thiserror 2.0.18", "tokio", "web-time", @@ -863,9 +864,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201" +checksum = "8ebcf9c75f17a17d55d11afc98e46167d4790a263f428891b8705ab2f793eca3" dependencies = [ "darling 0.20.11", "proc-macro2", @@ -890,9 +891,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -1244,6 +1245,16 @@ dependencies = [ "darling_macro 0.21.3", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + [[package]] name = "darling_core" version = "0.20.11" @@ -1272,6 +1283,19 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_macro" version = "0.20.11" @@ -1294,6 +1318,17 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -1444,9 +1479,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b6c2fc184a6fb6ebcf5f9a5e3bbfa84d8fd268cdfcce4ed508979a6259494d" +checksum = "f4ae09a41a4b89f94ec1e053623da8340d996bc32c6517d325a9daad9b239358" dependencies = [ "bigdecimal", "bitflags", @@ -1690,12 +1725,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "equivalent" version = "1.0.2" @@ -2151,8 +2180,6 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash 0.1.5", ] @@ -2630,9 +2657,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" dependencies = [ "memchr", "serde", @@ -2660,9 +2687,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jetscii" @@ -2847,9 +2874,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -2869,9 +2896,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" dependencies = [ "cc", "pkg-config", @@ -3033,9 +3060,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "async-lock", "crossbeam-channel", @@ -3262,9 +3289,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -3332,9 +3359,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ "bitflags", "cfg-if", @@ -3373,9 +3400,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -3741,9 +3768,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -3913,9 +3940,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "getrandom 0.3.4", @@ -3957,9 +3984,9 @@ dependencies = [ [[package]] name = "quoted_printable" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" [[package]] name = "r-efi" @@ -4489,7 +4516,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.9", + "rustls-webpki 0.103.10", "subtle", "zeroize", ] @@ -4537,9 +4564,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", @@ -4578,9 +4605,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -4809,9 +4836,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", @@ -4828,11 +4855,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn", @@ -5132,9 +5159,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -5255,9 +5282,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -5412,11 +5439,11 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow 0.7.15", + "winnow 1.0.0", ] [[package]] @@ -5533,9 +5560,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -6028,13 +6055,11 @@ dependencies = [ [[package]] name = "which" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a824aeba0fbb27264f815ada4cff43d65b1741b7a4ed7629ff9089148c4a4e0" +checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" dependencies = [ - "env_home", - "rustix", - "winsafe", + "libc", ] [[package]] @@ -6402,6 +6427,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" + [[package]] name = "winreg" version = "0.50.0" @@ -6412,12 +6443,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -6591,18 +6616,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 39de03e9..48d977ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,14 +88,14 @@ serde_json = "1.0.149" # A safe, extensible ORM and Query builder # Currently pinned diesel to v2.3.3 as newer version break MySQL/MariaDB compatibility -diesel = { version = "2.3.6", features = ["chrono", "r2d2", "numeric"] } +diesel = { version = "2.3.7", features = ["chrono", "r2d2", "numeric"] } diesel_migrations = "2.3.1" derive_more = { version = "2.1.1", features = ["from", "into", "as_ref", "deref", "display"] } diesel-derive-newtype = "2.1.2" # Bundled/Static SQLite -libsqlite3-sys = { version = "0.35.0", features = ["bundled"], optional = true } +libsqlite3-sys = { version = "0.36.0", features = ["bundled"], optional = true } # Crypto-related libraries rand = "0.10.0" @@ -155,14 +155,14 @@ bytes = "1.11.1" svg-hush = "0.9.6" # Cache function results (Used for version check and favicon fetching) -cached = { version = "0.56.0", features = ["async"] } +cached = { version = "0.59.0", features = ["async"] } # Used for custom short lived cookie jar during favicon extraction cookie = "0.18.1" cookie_store = "0.22.1" # Used by U2F, JWT and PostgreSQL -openssl = "0.10.75" +openssl = "0.10.76" # CLI argument parsing pico-args = "0.5.0" @@ -173,7 +173,7 @@ governor = "0.10.4" # OIDC for SSO openidconnect = { version = "4.0.1", features = ["reqwest", "rustls-tls"] } -moka = { version = "0.12.13", features = ["future"] } +moka = { version = "0.12.15", features = ["future"] } # Check client versions for specific features. semver = "1.0.27" @@ -182,7 +182,7 @@ semver = "1.0.27" # Mainly used for the musl builds, since the default musl malloc is very slow mimalloc = { version = "0.1.48", features = ["secure"], default-features = false, optional = true } -which = "8.0.1" +which = "8.0.2" # Argon2 library with support for the PHC format argon2 = "0.5.3" diff --git a/src/api/admin.rs b/src/api/admin.rs index 475797a7..6cedc040 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -637,7 +637,6 @@ use cached::proc_macro::cached; /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already /// It will cache this function for 600 seconds (10 minutes) which should prevent the exhaustion of the rate limit /// Any cache will be lost if Vaultwarden is restarted -use std::time::Duration; // Needed for cached #[cached(time = 600, sync_writes = "default")] async fn get_release_info(has_http_access: bool) -> (String, String, String) { // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. From c0a78dd55a4b37a78e4fd34d943920f4d4b76569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 23 Mar 2026 22:25:03 +0100 Subject: [PATCH 27/41] Use protected CI environment (#7004) --- .github/workflows/release.yml | 59 ++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2080cb2..8a3a7937 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,23 +20,25 @@ defaults: run: shell: bash -env: - # The *_REPO variables need to be configured as repository variables - # Append `/settings/variables/actions` to your repo url - # DOCKERHUB_REPO needs to be 'index.docker.io//' - # Check for Docker hub credentials in secrets - HAVE_DOCKERHUB_LOGIN: ${{ vars.DOCKERHUB_REPO != '' && secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }} - # GHCR_REPO needs to be 'ghcr.io//' - # Check for Github credentials in secrets - HAVE_GHCR_LOGIN: ${{ vars.GHCR_REPO != '' && github.repository_owner != '' && secrets.GITHUB_TOKEN != '' }} - # QUAY_REPO needs to be 'quay.io//' - # Check for Quay.io credentials in secrets - HAVE_QUAY_LOGIN: ${{ vars.QUAY_REPO != '' && secrets.QUAY_USERNAME != '' && secrets.QUAY_TOKEN != '' }} +# A "release" environment must be created in the repository settings +# (Settings > Environments > New environment) with the following +# variables and secrets configured as needed. +# +# Variables (only set the ones for registries you want to push to): +# DOCKERHUB_REPO: 'index.docker.io//' +# QUAY_REPO: 'quay.io//' +# GHCR_REPO: 'ghcr.io//' +# +# Secrets (only required when the corresponding *_REPO variable is set): +# DOCKERHUB_REPO => DOCKERHUB_USERNAME, DOCKERHUB_TOKEN +# QUAY_REPO => QUAY_USERNAME, QUAY_TOKEN +# GITHUB_TOKEN is provided automatically jobs: docker-build: name: Build Vaultwarden containers if: ${{ github.repository == 'dani-garcia/vaultwarden' }} + environment: release permissions: packages: write # Needed to upload packages and artifacts contents: read @@ -106,10 +108,10 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }} + if: ${{ vars.DOCKERHUB_REPO != '' }} - name: Add registry for DockerHub - if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }} + if: ${{ vars.DOCKERHUB_REPO != '' }} env: DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }} run: | @@ -122,10 +124,10 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - if: ${{ env.HAVE_GHCR_LOGIN == 'true' }} + if: ${{ vars.GHCR_REPO != '' }} - name: Add registry for ghcr.io - if: ${{ env.HAVE_GHCR_LOGIN == 'true' }} + if: ${{ vars.GHCR_REPO != '' }} env: GHCR_REPO: ${{ vars.GHCR_REPO }} run: | @@ -138,10 +140,10 @@ jobs: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_TOKEN }} - if: ${{ env.HAVE_QUAY_LOGIN == 'true' }} + if: ${{ vars.QUAY_REPO != '' }} - name: Add registry for Quay.io - if: ${{ env.HAVE_QUAY_LOGIN == 'true' }} + if: ${{ vars.QUAY_REPO != '' }} env: QUAY_REPO: ${{ vars.QUAY_REPO }} run: | @@ -155,7 +157,7 @@ jobs: run: | # # Check if there is a GitHub Container Registry Login and use it for caching - if [[ -n "${HAVE_GHCR_LOGIN}" ]]; then + if [[ -n "${GHCR_REPO}" ]]; then echo "BAKE_CACHE_FROM=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}-${NORMALIZED_ARCH}" | tee -a "${GITHUB_ENV}" echo "BAKE_CACHE_TO=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}-${NORMALIZED_ARCH},compression=zstd,mode=max" | tee -a "${GITHUB_ENV}" else @@ -247,6 +249,7 @@ jobs: name: Merge manifests runs-on: ubuntu-latest needs: docker-build + environment: release permissions: packages: write # Needed to upload packages and artifacts attestations: write # Needed to generate an artifact attestation for a build @@ -269,10 +272,10 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }} + if: ${{ vars.DOCKERHUB_REPO != '' }} - name: Add registry for DockerHub - if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }} + if: ${{ vars.DOCKERHUB_REPO != '' }} env: DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }} run: | @@ -285,10 +288,10 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - if: ${{ env.HAVE_GHCR_LOGIN == 'true' }} + if: ${{ vars.GHCR_REPO != '' }} - name: Add registry for ghcr.io - if: ${{ env.HAVE_GHCR_LOGIN == 'true' }} + if: ${{ vars.GHCR_REPO != '' }} env: GHCR_REPO: ${{ vars.GHCR_REPO }} run: | @@ -301,10 +304,10 @@ jobs: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_TOKEN }} - if: ${{ env.HAVE_QUAY_LOGIN == 'true' }} + if: ${{ vars.QUAY_REPO != '' }} - name: Add registry for Quay.io - if: ${{ env.HAVE_QUAY_LOGIN == 'true' }} + if: ${{ vars.QUAY_REPO != '' }} env: QUAY_REPO: ${{ vars.QUAY_REPO }} run: | @@ -357,7 +360,7 @@ jobs: # Attest container images - name: Attest - docker.io - ${{ matrix.base_image }} - if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && env.DIGEST_SHA != ''}} + if: ${{ vars.DOCKERHUB_REPO != '' && env.DIGEST_SHA != ''}} uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ vars.DOCKERHUB_REPO }} @@ -365,7 +368,7 @@ jobs: push-to-registry: true - name: Attest - ghcr.io - ${{ matrix.base_image }} - if: ${{ env.HAVE_GHCR_LOGIN == 'true' && env.DIGEST_SHA != ''}} + if: ${{ vars.GHCR_REPO != '' && env.DIGEST_SHA != ''}} uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ vars.GHCR_REPO }} @@ -373,7 +376,7 @@ jobs: push-to-registry: true - name: Attest - quay.io - ${{ matrix.base_image }} - if: ${{ env.HAVE_QUAY_LOGIN == 'true' && env.DIGEST_SHA != ''}} + if: ${{ vars.QUAY_REPO != '' && env.DIGEST_SHA != ''}} uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ vars.QUAY_REPO }} From 235cf88231edff38a0afac9ce75835a8592f101a Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Mon, 23 Mar 2026 23:12:07 +0100 Subject: [PATCH 28/41] Fix 2FA Remember to actually be 30 days (#6929) Currently we always regenerate the 2FA Remember token, and always send that back to the client. This is not the correct way, and in turn causes the remember token to never expire. While this might be convenient, it is not really safe. This commit changes the 2FA Remember Tokens from random string to a JWT. This JWT has a lifetime of 30 days and is validated per device & user combination. This does mean that once this commit is merged, and users are using this version, all their remember tokens will be invalidated. From my point of view this isn't a bad thing, since those tokens should have expired already. Only users who recently checked the remember checkbox within 30 days have to login again, but that is a minor inconvenience I think. Signed-off-by: BlackDex --- src/api/identity.rs | 21 +++++++++++++++------ src/auth.rs | 30 ++++++++++++++++++++++++++++++ src/db/models/device.rs | 11 +++++++---- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index f3fd3d1a..fcd8c388 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -757,7 +757,6 @@ async fn twofactor_auth( use crate::crypto::ct_eq; let selected_data = _selected_data(selected_twofactor); - let mut remember = data.two_factor_remember.unwrap_or(0); match TwoFactorType::from_i32(selected_id) { Some(TwoFactorType::Authenticator) => { @@ -789,13 +788,23 @@ async fn twofactor_auth( } Some(TwoFactorType::Remember) => { match device.twofactor_remember { - Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => { - remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time - } + // When a 2FA Remember token is used, check and validate this JWT token, if it is valid, just continue + // If it is invalid we need to trigger the 2FA Login prompt + Some(ref token) + if !CONFIG.disable_2fa_remember() + && (ct_eq(token, twofactor_code) + && auth::decode_2fa_remember(twofactor_code) + .is_ok_and(|t| t.sub == device.uuid && t.user_uuid == user.uuid)) => {} _ => { + // Always delete the current twofactor remember token here if it exists + if device.twofactor_remember.is_some() { + device.delete_twofactor_remember(); + // We need to save here, since we send a err_json!() which prevents saving `device` at a later stage + device.save(true, conn).await?; + } err_json!( _json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?, - "2FA Remember token not provided" + "2FA Remember token not provided or expired" ) } } @@ -826,10 +835,10 @@ async fn twofactor_auth( TwoFactorIncomplete::mark_complete(&user.uuid, &device.uuid, conn).await?; + let remember = data.two_factor_remember.unwrap_or(0); let two_factor = if !CONFIG.disable_2fa_remember() && remember == 1 { Some(device.refresh_twofactor_remember()) } else { - device.delete_twofactor_remember(); None }; Ok(two_factor) diff --git a/src/auth.rs b/src/auth.rs index b71a5bd9..99741277 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -46,6 +46,7 @@ static JWT_FILE_DOWNLOAD_ISSUER: LazyLock = LazyLock::new(|| format!("{}|file_download", CONFIG.domain_origin())); static JWT_REGISTER_VERIFY_ISSUER: LazyLock = LazyLock::new(|| format!("{}|register_verify", CONFIG.domain_origin())); +static JWT_2FA_REMEMBER_ISSUER: LazyLock = LazyLock::new(|| format!("{}|2faremember", CONFIG.domain_origin())); static PRIVATE_RSA_KEY: OnceLock = OnceLock::new(); static PUBLIC_RSA_KEY: OnceLock = OnceLock::new(); @@ -160,6 +161,10 @@ pub fn decode_register_verify(token: &str) -> Result Result { + decode_jwt(token, JWT_2FA_REMEMBER_ISSUER.to_string()) +} + #[derive(Debug, Serialize, Deserialize)] pub struct LoginJwtClaims { // Not before @@ -440,6 +445,31 @@ pub fn generate_register_verify_claims(email: String, name: Option, veri } } +#[derive(Serialize, Deserialize)] +pub struct TwoFactorRememberClaims { + // Not before + pub nbf: i64, + // Expiration time + pub exp: i64, + // Issuer + pub iss: String, + // Subject + pub sub: DeviceId, + // UserId + pub user_uuid: UserId, +} + +pub fn generate_2fa_remember_claims(device_uuid: DeviceId, user_uuid: UserId) -> TwoFactorRememberClaims { + let time_now = Utc::now(); + TwoFactorRememberClaims { + nbf: time_now.timestamp(), + exp: (time_now + TimeDelta::try_days(30).unwrap()).timestamp(), + iss: JWT_2FA_REMEMBER_ISSUER.to_string(), + sub: device_uuid, + user_uuid, + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct BasicJwtClaims { // Not before diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 4e3d0197..d3c32213 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -1,6 +1,6 @@ use chrono::{NaiveDateTime, Utc}; -use data_encoding::{BASE64, BASE64URL}; +use data_encoding::BASE64URL; use derive_more::{Display, From}; use serde_json::Value; @@ -67,10 +67,13 @@ impl Device { } pub fn refresh_twofactor_remember(&mut self) -> String { - let twofactor_remember = crypto::encode_random_bytes::<180>(&BASE64); - self.twofactor_remember = Some(twofactor_remember.clone()); + use crate::auth::{encode_jwt, generate_2fa_remember_claims}; - twofactor_remember + let two_factor_remember_claim = generate_2fa_remember_claims(self.uuid.clone(), self.user_uuid.clone()); + let two_factor_remember_string = encode_jwt(&two_factor_remember_claim); + self.twofactor_remember = Some(two_factor_remember_string.clone()); + + two_factor_remember_string } pub fn delete_twofactor_remember(&mut self) { From dde63e209e00e4e1a02d8e43175976386a31c53b Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sun, 29 Mar 2026 22:21:39 +0200 Subject: [PATCH 29/41] Misc Updates (#7027) - Update Rust to v1.94.1 - Updated all crates - Update GHA - Update global domains and ensure a newline is always present Signed-off-by: BlackDex --- .github/workflows/trivy.yml | 2 +- Cargo.lock | 166 ++++++++++----------------------- Cargo.toml | 4 +- docker/DockerSettings.yaml | 2 +- docker/Dockerfile.alpine | 8 +- docker/Dockerfile.debian | 2 +- rust-toolchain.toml | 2 +- src/static/global_domains.json | 13 ++- tools/global_domains.py | 1 + 9 files changed, 69 insertions(+), 131 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 94ff6f63..c9e02cf9 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -50,6 +50,6 @@ jobs: severity: CRITICAL,HIGH - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: sarif_file: 'trivy-results.sarif' diff --git a/Cargo.lock b/Cargo.lock index b16d3fdb..290627c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,15 +76,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "ar_archive_writer" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" -dependencies = [ - "object", -] - [[package]] name = "argon2" version = "0.5.3" @@ -891,9 +882,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -948,16 +939,6 @@ dependencies = [ "phf 0.12.1", ] -[[package]] -name = "chumsky" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" -dependencies = [ - "hashbrown 0.14.5", - "stacker", -] - [[package]] name = "cipher" version = "0.4.4" @@ -2639,14 +2620,15 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2 0.6.3", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", ] [[package]] @@ -2761,10 +2743,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2842,14 +2826,13 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lettre" -version = "0.11.19" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" +checksum = "471816f3e24b85e820dee02cde962379ea1a669e5242f19c61bcbcffedf4c4fb" dependencies = [ "async-std", "async-trait", "base64 0.22.1", - "chumsky", "email-encoding", "email_address", "fastrand", @@ -3049,9 +3032,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -3170,9 +3153,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" @@ -3269,15 +3252,6 @@ dependencies = [ "url", ] -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "oid-registry" version = "0.7.1" @@ -3857,16 +3831,6 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" -[[package]] -name = "psm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" -dependencies = [ - "ar_archive_writer", - "cc", -] - [[package]] name = "publicsuffix" version = "2.3.0" @@ -4459,9 +4423,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -4815,9 +4779,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -4934,9 +4898,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simple_asn1" @@ -5040,19 +5004,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "stacker" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "state" version = "0.6.0" @@ -5399,7 +5350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "serde_core", - "serde_spanned 1.0.4", + "serde_spanned 1.1.0", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow 0.7.15", @@ -5439,9 +5390,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow 1.0.0", ] @@ -5640,9 +5591,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -5689,9 +5640,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5852,9 +5803,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" dependencies = [ "cfg-if", "once_cell", @@ -5865,23 +5816,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "2d1faf851e778dfa54db7cd438b70758eba9755cb47403f3496edd7c8fc212f0" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5889,9 +5836,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" dependencies = [ "bumpalo", "proc-macro2", @@ -5902,9 +5849,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" dependencies = [ "unicode-ident", ] @@ -5958,9 +5905,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" dependencies = [ "js-sys", "wasm-bindgen", @@ -6178,15 +6125,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -6433,16 +6371,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -6616,18 +6544,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 48d977ab..60286287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ ring = "0.17.14" subtle = "2.6.1" # UUID generation -uuid = { version = "1.22.0", features = ["v4"] } +uuid = { version = "1.23.0", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.44", features = ["clock", "serde"], default-features = false } @@ -136,7 +136,7 @@ webauthn-rs-core = "0.5.4" url = "2.5.8" # Email libraries -lettre = { version = "0.11.19", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "hostname", "tracing", "tokio1-rustls", "ring", "rustls-native-certs"], default-features = false } +lettre = { version = "0.11.20", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "hostname", "tracing", "tokio1-rustls", "ring", "rustls-native-certs"], default-features = false } percent-encoding = "2.3.2" # URL encoding library used for URL's in the emails email_address = "0.2.9" diff --git a/docker/DockerSettings.yaml b/docker/DockerSettings.yaml index 610f6b4a..c679b0da 100644 --- a/docker/DockerSettings.yaml +++ b/docker/DockerSettings.yaml @@ -5,7 +5,7 @@ vault_image_digest: "sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812 # We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts # https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707" -rust_version: 1.94.0 # Rust version to be used +rust_version: 1.94.1 # Rust version to be used debian_version: trixie # Debian release name to be used alpine_version: "3.23" # Alpine version to be used # For which platforms/architectures will we try to build images diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 44242e08..ddcc9efe 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,10 +32,10 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dc ########################## ALPINE BUILD IMAGES ########################## ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64 ## And for Alpine we define all build images here, they will only be loaded when actually used -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.94.0 AS build_amd64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.94.0 AS build_arm64 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.94.0 AS build_armv7 -FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.94.0 AS build_armv6 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.94.1 AS build_amd64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.94.1 AS build_arm64 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.94.1 AS build_armv7 +FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.94.1 AS build_armv6 ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index a60c485d..18dd3d6c 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f ########################## BUILD IMAGE ########################## # hadolint ignore=DL3006 -FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.94.0-slim-trixie AS build +FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.94.1-slim-trixie AS build COPY --from=xx / / ARG TARGETARCH ARG TARGETVARIANT diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0fc3f36d..151be09f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.94.0" +channel = "1.94.1" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/src/static/global_domains.json b/src/static/global_domains.json index e3f08813..3b13a3e9 100644 --- a/src/static/global_domains.json +++ b/src/static/global_domains.json @@ -111,7 +111,8 @@ "microsoftstore.com", "xbox.com", "azure.com", - "windowsazure.com" + "windowsazure.com", + "cloud.microsoft" ], "excluded": false }, @@ -971,5 +972,13 @@ "pinterest.se" ], "excluded": false + }, + { + "type": 91, + "domains": [ + "twitter.com", + "x.com" + ], + "excluded": false } -] \ No newline at end of file +] diff --git a/tools/global_domains.py b/tools/global_domains.py index 66edca31..78a31701 100755 --- a/tools/global_domains.py +++ b/tools/global_domains.py @@ -79,3 +79,4 @@ for name, domain_list in domain_lists.items(): # Write out the global domains JSON file. with open(file=OUTPUT_FILE, mode='w', encoding='utf-8') as f: json.dump(global_domains, f, indent=2) + f.write("\n") From 3a1378f469c66dc4cd92a32add3c47594c40604f Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 29 Mar 2026 23:22:27 +0300 Subject: [PATCH 30/41] Switch to `attest` action (#7017) From the `attest-build-provenance` changelog: > As of version 4, actions/attest-build-provenance is simply a wrapper on top of actions/attest. > Existing applications may continue to use the attest-build-provenance action, but new implementations should use actions/attest instead. Please see the actions/attest repository for usage information. --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8a3a7937..35e995c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -235,7 +235,7 @@ jobs: # Upload artifacts to Github Actions and Attest the binaries - name: Attest binaries - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }} @@ -361,7 +361,7 @@ jobs: # Attest container images - name: Attest - docker.io - ${{ matrix.base_image }} if: ${{ vars.DOCKERHUB_REPO != '' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ${{ vars.DOCKERHUB_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -369,7 +369,7 @@ jobs: - name: Attest - ghcr.io - ${{ matrix.base_image }} if: ${{ vars.GHCR_REPO != '' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ${{ vars.GHCR_REPO }} subject-digest: ${{ env.DIGEST_SHA }} @@ -377,7 +377,7 @@ jobs: - name: Attest - quay.io - ${{ matrix.base_image }} if: ${{ vars.QUAY_REPO != '' && env.DIGEST_SHA != ''}} - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ${{ vars.QUAY_REPO }} subject-digest: ${{ env.DIGEST_SHA }} From f62a7a66c8768204e82e0efa8b04108f3a544ce9 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sun, 29 Mar 2026 22:43:36 +0200 Subject: [PATCH 31/41] Rotate refresh-tokens on sstamp reset (#7031) When a security-stamp gets reset/rotated we should also rotate all device refresh-tokens to invalidate them. Else clients are still able to use old refresh tokens. Signed-off-by: BlackDex --- src/api/admin.rs | 4 ++-- src/api/core/accounts.rs | 24 ++++++++++++++++-------- src/api/core/emergency_access.rs | 2 +- src/api/core/organizations.rs | 3 ++- src/db/models/device.rs | 18 +++++++++++++++++- src/db/models/user.rs | 12 ++++++++---- 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 6cedc040..a4f53e0e 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -472,7 +472,7 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti } Device::delete_all_by_user(&user.uuid, &conn).await?; - user.reset_security_stamp(); + user.reset_security_stamp(&conn).await?; user.save(&conn).await } @@ -481,7 +481,7 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult { let mut user = get_user_or_404(&user_id, &conn).await?; Device::delete_all_by_user(&user.uuid, &conn).await?; - user.reset_security_stamp(); + user.reset_security_stamp(&conn).await?; user.enabled = false; let save_result = user.save(&conn).await; diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index d91eb4cd..e0869c63 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -296,7 +296,7 @@ pub async fn _register(data: Json, email_verification: bool, conn: set_kdf_data(&mut user, &data.kdf)?; - user.set_password(&data.master_password_hash, Some(data.key), true, None); + user.set_password(&data.master_password_hash, Some(data.key), true, None, &conn).await?; user.password_hint = password_hint; // Add extra fields if present @@ -364,7 +364,9 @@ async fn post_set_password(data: Json, headers: Headers, conn: Some(data.key), false, Some(vec![String::from("revision_date")]), // We need to allow revision-date to use the old security_timestamp - ); + &conn, + ) + .await?; user.password_hint = password_hint; if let Some(keys) = data.keys { @@ -532,7 +534,9 @@ async fn post_password(data: Json, headers: Headers, conn: DbCon String::from("get_public_keys"), String::from("get_api_webauthn"), ]), - ); + &conn, + ) + .await?; let save_result = user.save(&conn).await; @@ -633,7 +637,9 @@ async fn post_kdf(data: Json, headers: Headers, conn: DbConn, nt: Some(data.unlock_data.master_key_wrapped_user_key), true, None, - ); + &conn, + ) + .await?; let save_result = user.save(&conn).await; nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await; @@ -900,7 +906,9 @@ async fn post_rotatekey(data: Json, headers: Headers, conn: DbConn, nt: Some(data.account_unlock_data.master_password_unlock_data.master_key_encrypted_user_key), true, None, - ); + &conn, + ) + .await?; let save_result = user.save(&conn).await; @@ -920,7 +928,7 @@ async fn post_sstamp(data: Json, headers: Headers, conn: DbCo data.validate(&user, true, &conn).await?; Device::delete_all_by_user(&user.uuid, &conn).await?; - user.reset_security_stamp(); + user.reset_security_stamp(&conn).await?; let save_result = user.save(&conn).await; nt.send_logout(&user, None, &conn).await; @@ -1042,7 +1050,7 @@ async fn post_email(data: Json, headers: Headers, conn: DbConn, user.email_new = None; user.email_new_token = None; - user.set_password(&data.new_master_password_hash, Some(data.key), true, None); + user.set_password(&data.new_master_password_hash, Some(data.key), true, None, &conn).await?; let save_result = user.save(&conn).await; @@ -1254,7 +1262,7 @@ struct SecretVerificationRequest { pub async fn kdf_upgrade(user: &mut User, pwd_hash: &str, conn: &DbConn) -> ApiResult<()> { if user.password_iterations < CONFIG.password_iterations() { user.password_iterations = CONFIG.password_iterations(); - user.set_password(pwd_hash, None, false, None); + user.set_password(pwd_hash, None, false, None, conn).await?; if let Err(e) = user.save(conn).await { error!("Error updating user: {e:#?}"); diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs index 1897f995..29a15c8d 100644 --- a/src/api/core/emergency_access.rs +++ b/src/api/core/emergency_access.rs @@ -653,7 +653,7 @@ async fn password_emergency_access( }; // change grantor_user password - grantor_user.set_password(new_master_password_hash, Some(data.key), true, None); + grantor_user.set_password(new_master_password_hash, Some(data.key), true, None, &conn).await?; grantor_user.save(&conn).await?; // Disable TwoFactor providers since they will otherwise block logins diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 0213a006..0beb7036 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -2858,7 +2858,8 @@ async fn put_reset_password( let reset_request = data.into_inner(); let mut user = user; - user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None); + user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None, &conn) + .await?; user.save(&conn).await?; nt.send_logout(&user, None, &conn).await; diff --git a/src/db/models/device.rs b/src/db/models/device.rs index d3c32213..1026574c 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -49,11 +49,16 @@ impl Device { push_uuid: Some(PushId(get_uuid())), push_token: None, - refresh_token: crypto::encode_random_bytes::<64>(&BASE64URL), + refresh_token: Device::generate_refresh_token(), twofactor_remember: None, } } + #[inline(always)] + pub fn generate_refresh_token() -> String { + crypto::encode_random_bytes::<64>(&BASE64URL) + } + pub fn to_json(&self) -> Value { json!({ "id": self.uuid, @@ -260,6 +265,17 @@ impl Device { .unwrap_or(0) != 0 }} } + + pub async fn rotate_refresh_tokens_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult { + // Generate a new token per device. + // We cannot do a single UPDATE with one value because each device needs a unique token. + let devices = Self::find_by_user(user_uuid, conn).await; + for mut device in devices { + device.refresh_token = Device::generate_refresh_token(); + device.save(false, conn).await?; + } + Ok(()) + } } #[derive(Display)] diff --git a/src/db/models/user.rs b/src/db/models/user.rs index e88c7296..ebc72101 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -185,13 +185,14 @@ impl User { /// These routes are able to use the previous stamp id for the next 2 minutes. /// After these 2 minutes this stamp will expire. /// - pub fn set_password( + pub async fn set_password( &mut self, password: &str, new_key: Option, reset_security_stamp: bool, allow_next_route: Option>, - ) { + conn: &DbConn, + ) -> EmptyResult { self.password_hash = crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations as u32); if let Some(route) = allow_next_route { @@ -203,12 +204,15 @@ impl User { } if reset_security_stamp { - self.reset_security_stamp() + self.reset_security_stamp(conn).await?; } + Ok(()) } - pub fn reset_security_stamp(&mut self) { + pub async fn reset_security_stamp(&mut self, conn: &DbConn) -> EmptyResult { self.security_stamp = get_uuid(); + Device::rotate_refresh_tokens_by_user(&self.uuid, conn).await?; + Ok(()) } /// Set the stamp_exception to only allow a subsequent request matching a specific route using the current security-stamp. From 787822854cb73aeb8375a4e69ea188aa5f02db02 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sun, 29 Mar 2026 23:15:48 +0200 Subject: [PATCH 32/41] Misc org fixes (#7032) * Split vault org/personal purge endpoints Signed-off-by: BlackDex * Adjust several other call-sites Signed-off-by: BlackDex * Several other misc fixes Signed-off-by: BlackDex * Add some more validation for groups, collections and memberships Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- src/api/core/accounts.rs | 16 ++- src/api/core/ciphers.rs | 112 +++++++++++--------- src/api/core/events.rs | 2 +- src/api/core/organizations.rs | 186 +++++++++++++++++++++++----------- src/api/core/public.rs | 2 +- src/auth.rs | 51 +++++++--- src/db/models/cipher.rs | 51 ++++++---- src/db/models/collection.rs | 22 ++-- src/db/models/group.rs | 82 +++++++++++---- src/db/models/org_policy.rs | 2 +- src/db/models/organization.rs | 4 +- src/db/models/two_factor.rs | 1 - 12 files changed, 340 insertions(+), 191 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index e0869c63..ba36fc9b 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -106,7 +106,6 @@ pub struct RegisterData { name: Option, - #[allow(dead_code)] organization_user_id: Option, // Used only from the register/finish endpoint @@ -376,14 +375,12 @@ async fn post_set_password(data: Json, headers: Headers, conn: if let Some(identifier) = data.org_identifier { if identifier != crate::sso::FAKE_IDENTIFIER && identifier != crate::api::admin::FAKE_ADMIN_UUID { - let org = match Organization::find_by_uuid(&identifier.into(), &conn).await { - None => err!("Failed to retrieve the associated organization"), - Some(org) => org, + let Some(org) = Organization::find_by_uuid(&identifier.into(), &conn).await else { + err!("Failed to retrieve the associated organization") }; - let membership = match Membership::find_by_user_and_org(&user.uuid, &org.uuid, &conn).await { - None => err!("Failed to retrieve the invitation"), - Some(org) => org, + let Some(membership) = Membership::find_by_user_and_org(&user.uuid, &org.uuid, &conn).await else { + err!("Failed to retrieve the invitation") }; accept_org_invite(&user, membership, None, &conn).await?; @@ -583,7 +580,6 @@ fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult { Ok(()) } -#[allow(dead_code)] #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct AuthenticationData { @@ -592,7 +588,6 @@ struct AuthenticationData { master_password_authentication_hash: String, } -#[allow(dead_code)] #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct UnlockData { @@ -601,11 +596,12 @@ struct UnlockData { master_key_wrapped_user_key: String, } -#[allow(dead_code)] #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ChangeKdfData { + #[allow(dead_code)] new_master_password_hash: String, + #[allow(dead_code)] key: String, authentication_data: AuthenticationData, unlock_data: UnlockData, diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index f7bf5cd3..8a8d9838 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -14,7 +14,7 @@ use crate::auth::ClientVersion; use crate::util::{save_temp_file, NumberOrString}; use crate::{ api::{self, core::log_event, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType}, - auth::Headers, + auth::{Headers, OrgIdGuard, OwnerHeaders}, config::PathType, crypto, db::{ @@ -86,7 +86,8 @@ pub fn routes() -> Vec { restore_cipher_put_admin, restore_cipher_selected, restore_cipher_selected_admin, - delete_all, + purge_org_vault, + purge_personal_vault, move_cipher_selected, move_cipher_selected_put, put_collections2_update, @@ -425,7 +426,7 @@ pub async fn update_cipher_from_data( let transfer_cipher = cipher.organization_uuid.is_none() && data.organization_id.is_some(); if let Some(org_id) = data.organization_id { - match Membership::find_by_user_and_org(&headers.user.uuid, &org_id, conn).await { + match Membership::find_confirmed_by_user_and_org(&headers.user.uuid, &org_id, conn).await { None => err!("You don't have permission to add item to organization"), Some(member) => { if shared_to_collections.is_some() @@ -1642,67 +1643,75 @@ struct OrganizationIdData { org_id: OrganizationId, } +// Use the OrgIdGuard here, to ensure there an organization id present. +// If there is no organization id present, it should be forwarded to purge_personal_vault. +// This guard needs to be the first argument, else OwnerHeaders will be triggered which will logout the user. #[post("/ciphers/purge?", data = "")] -async fn delete_all( - organization: Option, +async fn purge_org_vault( + _org_id_guard: OrgIdGuard, + organization: OrganizationIdData, data: Json, - headers: Headers, + headers: OwnerHeaders, conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { + if organization.org_id != headers.org_id { + err!("Organization not found", "Organization id's do not match"); + } + let data: PasswordOrOtpData = data.into_inner(); - let mut user = headers.user; + let user = headers.user; data.validate(&user, true, &conn).await?; - match organization { - Some(org_data) => { - // Organization ID in query params, purging organization vault - match Membership::find_by_user_and_org(&user.uuid, &org_data.org_id, &conn).await { - None => err!("You don't have permission to purge the organization vault"), - Some(member) => { - if member.atype == MembershipType::Owner { - Cipher::delete_all_by_organization(&org_data.org_id, &conn).await?; - nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &conn).await; - - log_event( - EventType::OrganizationPurgedVault as i32, - &org_data.org_id, - &org_data.org_id, - &user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; - - Ok(()) - } else { - err!("You don't have permission to purge the organization vault"); - } - } - } - } - None => { - // No organization ID in query params, purging user vault - // Delete ciphers and their attachments - for cipher in Cipher::find_owned_by_user(&user.uuid, &conn).await { - cipher.delete(&conn).await?; - } - - // Delete folders - for f in Folder::find_by_user(&user.uuid, &conn).await { - f.delete(&conn).await?; - } - - user.update_revision(&conn).await?; + match Membership::find_confirmed_by_user_and_org(&user.uuid, &organization.org_id, &conn).await { + Some(member) if member.atype == MembershipType::Owner => { + Cipher::delete_all_by_organization(&organization.org_id, &conn).await?; nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &conn).await; + log_event( + EventType::OrganizationPurgedVault as i32, + &organization.org_id, + &organization.org_id, + &user.uuid, + headers.device.atype, + &headers.ip.ip, + &conn, + ) + .await; + Ok(()) } + _ => err!("You don't have permission to purge the organization vault"), } } +#[post("/ciphers/purge", data = "")] +async fn purge_personal_vault( + data: Json, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + let data: PasswordOrOtpData = data.into_inner(); + let mut user = headers.user; + + data.validate(&user, true, &conn).await?; + + for cipher in Cipher::find_owned_by_user(&user.uuid, &conn).await { + cipher.delete(&conn).await?; + } + + for f in Folder::find_by_user(&user.uuid, &conn).await { + f.delete(&conn).await?; + } + + user.update_revision(&conn).await?; + nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &conn).await; + + Ok(()) +} + #[derive(PartialEq)] pub enum CipherDeleteOptions { SoftSingle, @@ -1980,8 +1989,11 @@ impl CipherSyncData { } // Generate a HashMap with the Organization UUID as key and the Membership record - let members: HashMap = - Membership::find_by_user(user_id, conn).await.into_iter().map(|m| (m.org_uuid.clone(), m)).collect(); + let members: HashMap = Membership::find_confirmed_by_user(user_id, conn) + .await + .into_iter() + .map(|m| (m.org_uuid.clone(), m)) + .collect(); // Generate a HashMap with the User_Collections UUID as key and the CollectionUser record let user_collections: HashMap = CollectionUser::find_by_user(user_id, conn) diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 2f33a407..d1612255 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -240,7 +240,7 @@ async fn _log_user_event( ip: &IpAddr, conn: &DbConn, ) { - let memberships = Membership::find_by_user(user_id, conn).await; + let memberships = Membership::find_confirmed_by_user(user_id, conn).await; let mut events: Vec = Vec::with_capacity(memberships.len() + 1); // We need an event per org and one without an org // Upstream saves the event also without any org_id. diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 0beb7036..36e3e4a0 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -131,6 +131,24 @@ struct FullCollectionData { external_id: Option, } +impl FullCollectionData { + pub async fn validate(&self, org_id: &OrganizationId, conn: &DbConn) -> EmptyResult { + let org_groups = Group::find_by_organization(org_id, conn).await; + let org_group_ids: HashSet<&GroupId> = org_groups.iter().map(|c| &c.uuid).collect(); + if let Some(e) = self.groups.iter().find(|g| !org_group_ids.contains(&g.id)) { + err!("Invalid group", format!("Group {} does not belong to organization {}!", e.id, org_id)) + } + + let org_memberships = Membership::find_by_org(org_id, conn).await; + let org_membership_ids: HashSet<&MembershipId> = org_memberships.iter().map(|m| &m.uuid).collect(); + if let Some(e) = self.users.iter().find(|m| !org_membership_ids.contains(&m.id)) { + err!("Invalid member", format!("Member {} does not belong to organization {}!", e.id, org_id)) + } + + Ok(()) + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CollectionGroupData { @@ -233,30 +251,30 @@ async fn post_delete_organization( } #[post("/organizations//leave")] -async fn leave_organization(org_id: OrganizationId, headers: Headers, conn: DbConn) -> EmptyResult { - match Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await { - None => err!("User not part of organization"), - Some(member) => { - if member.atype == MembershipType::Owner - && Membership::count_confirmed_by_org_and_type(&org_id, MembershipType::Owner, &conn).await <= 1 - { - err!("The last owner can't leave") - } - - log_event( - EventType::OrganizationUserLeft as i32, - &member.uuid, - &org_id, - &headers.user.uuid, - headers.device.atype, - &headers.ip.ip, - &conn, - ) - .await; +async fn leave_organization(org_id: OrganizationId, headers: OrgMemberHeaders, conn: DbConn) -> EmptyResult { + if headers.membership.status != MembershipStatus::Confirmed as i32 { + err!("You need to be a Member of the Organization to call this endpoint") + } + let membership = headers.membership; - member.delete(&conn).await - } + if membership.atype == MembershipType::Owner + && Membership::count_confirmed_by_org_and_type(&org_id, MembershipType::Owner, &conn).await <= 1 + { + err!("The last owner can't leave") } + + log_event( + EventType::OrganizationUserLeft as i32, + &membership.uuid, + &org_id, + &headers.user.uuid, + headers.device.atype, + &headers.ip.ip, + &conn, + ) + .await; + + membership.delete(&conn).await } #[get("/organizations/")] @@ -480,12 +498,9 @@ async fn post_organization_collections( err!("Organization not found", "Organization id's do not match"); } let data: FullCollectionData = data.into_inner(); + data.validate(&org_id, &conn).await?; - let Some(org) = Organization::find_by_uuid(&org_id, &conn).await else { - err!("Can't find organization details") - }; - - let collection = Collection::new(org.uuid, data.name, data.external_id); + let collection = Collection::new(org_id.clone(), data.name, data.external_id); collection.save(&conn).await?; log_event( @@ -501,7 +516,7 @@ async fn post_organization_collections( for group in data.groups { CollectionGroup::new(collection.uuid.clone(), group.id, group.read_only, group.hide_passwords, group.manage) - .save(&conn) + .save(&org_id, &conn) .await?; } @@ -579,10 +594,10 @@ async fn post_bulk_access_collections( ) .await; - CollectionGroup::delete_all_by_collection(&col_id, &conn).await?; + CollectionGroup::delete_all_by_collection(&col_id, &org_id, &conn).await?; for group in &data.groups { CollectionGroup::new(col_id.clone(), group.id.clone(), group.read_only, group.hide_passwords, group.manage) - .save(&conn) + .save(&org_id, &conn) .await?; } @@ -627,6 +642,7 @@ async fn post_organization_collection_update( err!("Organization not found", "Organization id's do not match"); } let data: FullCollectionData = data.into_inner(); + data.validate(&org_id, &conn).await?; if Organization::find_by_uuid(&org_id, &conn).await.is_none() { err!("Can't find organization details") @@ -655,11 +671,11 @@ async fn post_organization_collection_update( ) .await; - CollectionGroup::delete_all_by_collection(&col_id, &conn).await?; + CollectionGroup::delete_all_by_collection(&col_id, &org_id, &conn).await?; for group in data.groups { CollectionGroup::new(col_id.clone(), group.id, group.read_only, group.hide_passwords, group.manage) - .save(&conn) + .save(&org_id, &conn) .await?; } @@ -1003,6 +1019,24 @@ struct InviteData { permissions: HashMap, } +impl InviteData { + async fn validate(&self, org_id: &OrganizationId, conn: &DbConn) -> EmptyResult { + let org_collections = Collection::find_by_organization(org_id, conn).await; + let org_collection_ids: HashSet<&CollectionId> = org_collections.iter().map(|c| &c.uuid).collect(); + if let Some(e) = self.collections.iter().flatten().find(|c| !org_collection_ids.contains(&c.id)) { + err!("Invalid collection", format!("Collection {} does not belong to organization {}!", e.id, org_id)) + } + + let org_groups = Group::find_by_organization(org_id, conn).await; + let org_group_ids: HashSet<&GroupId> = org_groups.iter().map(|c| &c.uuid).collect(); + if let Some(e) = self.groups.iter().find(|g| !org_group_ids.contains(g)) { + err!("Invalid group", format!("Group {} does not belong to organization {}!", e, org_id)) + } + + Ok(()) + } +} + #[post("/organizations//users/invite", data = "")] async fn send_invite( org_id: OrganizationId, @@ -1014,6 +1048,7 @@ async fn send_invite( err!("Organization not found", "Organization id's do not match"); } let data: InviteData = data.into_inner(); + data.validate(&org_id, &conn).await?; // HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission // The from_str() will convert the custom role type into a manager role type @@ -1273,20 +1308,20 @@ async fn accept_invite( // skip invitation logic when we were invited via the /admin panel if **member_id != FAKE_ADMIN_UUID { - let Some(mut member) = Membership::find_by_uuid_and_org(member_id, &claims.org_id, &conn).await else { + let Some(mut membership) = Membership::find_by_uuid_and_org(member_id, &claims.org_id, &conn).await else { err!("Error accepting the invitation") }; - let reset_password_key = match OrgPolicy::org_is_reset_password_auto_enroll(&member.org_uuid, &conn).await { + let reset_password_key = match OrgPolicy::org_is_reset_password_auto_enroll(&membership.org_uuid, &conn).await { true if data.reset_password_key.is_none() => err!("Reset password key is required, but not provided."), true => data.reset_password_key, false => None, }; // In case the user was invited before the mail was saved in db. - member.invited_by_email = member.invited_by_email.or(claims.invited_by_email); + membership.invited_by_email = membership.invited_by_email.or(claims.invited_by_email); - accept_org_invite(&headers.user, member, reset_password_key, &conn).await?; + accept_org_invite(&headers.user, membership, reset_password_key, &conn).await?; } else if CONFIG.mail_enabled() { // User was invited from /admin, so they are automatically confirmed let org_name = CONFIG.invitation_org_name(); @@ -1520,9 +1555,8 @@ async fn edit_member( && data.permissions.get("deleteAnyCollection") == Some(&json!(true)) && data.permissions.get("createNewCollections") == Some(&json!(true))); - let mut member_to_edit = match Membership::find_by_uuid_and_org(&member_id, &org_id, &conn).await { - Some(member) => member, - None => err!("The specified user isn't member of the organization"), + let Some(mut member_to_edit) = Membership::find_by_uuid_and_org(&member_id, &org_id, &conn).await else { + err!("The specified user isn't member of the organization") }; if new_type != member_to_edit.atype @@ -1839,7 +1873,6 @@ async fn post_org_import( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] -#[allow(dead_code)] struct BulkCollectionsData { organization_id: OrganizationId, cipher_ids: Vec, @@ -1853,6 +1886,10 @@ struct BulkCollectionsData { async fn post_bulk_collections(data: Json, headers: Headers, conn: DbConn) -> EmptyResult { let data: BulkCollectionsData = data.into_inner(); + if Membership::find_confirmed_by_user_and_org(&headers.user.uuid, &data.organization_id, &conn).await.is_none() { + err!("You need to be a Member of the Organization to call this endpoint") + } + // Get all the collection available to the user in one query // Also filter based upon the provided collections let user_collections: HashMap = @@ -1941,7 +1978,7 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, conn: DbConn) // Called during the SSO enrollment. // Return the org policy if it exists, otherwise use the default one. #[get("/organizations//policies/master-password", rank = 1)] -async fn get_master_password_policy(org_id: OrganizationId, _headers: Headers, conn: DbConn) -> JsonResult { +async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberHeaders, conn: DbConn) -> JsonResult { let policy = OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::MasterPassword, &conn).await.unwrap_or_else(|| { let (enabled, data) = match CONFIG.sso_master_password_policy_value() { @@ -2149,13 +2186,13 @@ fn get_plans() -> Json { } #[get("/organizations/<_org_id>/billing/metadata")] -fn get_billing_metadata(_org_id: OrganizationId, _headers: Headers) -> Json { +fn get_billing_metadata(_org_id: OrganizationId, _headers: OrgMemberHeaders) -> Json { // Prevent a 404 error, which also causes Javascript errors. Json(_empty_data_json()) } #[get("/organizations/<_org_id>/billing/vnext/warnings")] -fn get_billing_warnings(_org_id: OrganizationId, _headers: Headers) -> Json { +fn get_billing_warnings(_org_id: OrganizationId, _headers: OrgMemberHeaders) -> Json { Json(json!({ "freeTrial":null, "inactiveSubscription":null, @@ -2427,6 +2464,23 @@ impl GroupRequest { group } + + /// Validate if all the collections and members belong to the provided organization + pub async fn validate(&self, org_id: &OrganizationId, conn: &DbConn) -> EmptyResult { + let org_collections = Collection::find_by_organization(org_id, conn).await; + let org_collection_ids: HashSet<&CollectionId> = org_collections.iter().map(|c| &c.uuid).collect(); + if let Some(e) = self.collections.iter().find(|c| !org_collection_ids.contains(&c.id)) { + err!("Invalid collection", format!("Collection {} does not belong to organization {}!", e.id, org_id)) + } + + let org_memberships = Membership::find_by_org(org_id, conn).await; + let org_membership_ids: HashSet<&MembershipId> = org_memberships.iter().map(|m| &m.uuid).collect(); + if let Some(e) = self.users.iter().find(|m| !org_membership_ids.contains(m)) { + err!("Invalid member", format!("Member {} does not belong to organization {}!", e, org_id)) + } + + Ok(()) + } } #[derive(Deserialize, Serialize)] @@ -2470,6 +2524,8 @@ async fn post_groups( } let group_request = data.into_inner(); + group_request.validate(&org_id, &conn).await?; + let group = group_request.to_group(&org_id); log_event( @@ -2506,10 +2562,12 @@ async fn put_group( }; let group_request = data.into_inner(); + group_request.validate(&org_id, &conn).await?; + let updated_group = group_request.update_group(group); - CollectionGroup::delete_all_by_group(&group_id, &conn).await?; - GroupUser::delete_all_by_group(&group_id, &conn).await?; + CollectionGroup::delete_all_by_group(&group_id, &org_id, &conn).await?; + GroupUser::delete_all_by_group(&group_id, &org_id, &conn).await?; log_event( EventType::GroupUpdated as i32, @@ -2537,7 +2595,7 @@ async fn add_update_group( for col_selection in collections { let mut collection_group = col_selection.to_collection_group(group.uuid.clone()); - collection_group.save(conn).await?; + collection_group.save(&org_id, conn).await?; } for assigned_member in members { @@ -2630,7 +2688,7 @@ async fn _delete_group( ) .await; - group.delete(conn).await + group.delete(org_id, conn).await } #[delete("/organizations//groups", data = "")] @@ -2689,7 +2747,7 @@ async fn get_group_members( err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization") }; - let group_members: Vec = GroupUser::find_by_group(&group_id, &conn) + let group_members: Vec = GroupUser::find_by_group(&group_id, &org_id, &conn) .await .iter() .map(|entry| entry.users_organizations_uuid.clone()) @@ -2717,9 +2775,15 @@ async fn put_group_members( err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization") }; - GroupUser::delete_all_by_group(&group_id, &conn).await?; - let assigned_members = data.into_inner(); + + let org_memberships = Membership::find_by_org(&org_id, &conn).await; + let org_membership_ids: HashSet<&MembershipId> = org_memberships.iter().map(|m| &m.uuid).collect(); + if let Some(e) = assigned_members.iter().find(|m| !org_membership_ids.contains(m)) { + err!("Invalid member", format!("Member {} does not belong to organization {}!", e, org_id)) + } + + GroupUser::delete_all_by_group(&group_id, &org_id, &conn).await?; for assigned_member in assigned_members { let mut user_entry = GroupUser::new(group_id.clone(), assigned_member.clone()); user_entry.save(&conn).await?; @@ -2951,15 +3015,20 @@ async fn check_reset_password_applicable(org_id: &OrganizationId, conn: &DbConn) Ok(()) } -#[put("/organizations//users//reset-password-enrollment", data = "")] +#[put("/organizations//users//reset-password-enrollment", data = "")] async fn put_reset_password_enrollment( org_id: OrganizationId, - member_id: MembershipId, - headers: Headers, + user_id: UserId, + headers: OrgMemberHeaders, data: Json, conn: DbConn, ) -> EmptyResult { - let Some(mut member) = Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await else { + if user_id != headers.user.uuid { + err!("User to enroll isn't member of required organization", "The user_id and acting user do not match"); + } + + let Some(mut membership) = Membership::find_confirmed_by_user_and_org(&headers.user.uuid, &org_id, &conn).await + else { err!("User to enroll isn't member of required organization") }; @@ -2986,16 +3055,17 @@ async fn put_reset_password_enrollment( .await?; } - member.reset_password_key = reset_password_key; - member.save(&conn).await?; + membership.reset_password_key = reset_password_key; + membership.save(&conn).await?; - let log_id = if member.reset_password_key.is_some() { + let event_type = if membership.reset_password_key.is_some() { EventType::OrganizationUserResetPasswordEnroll as i32 } else { EventType::OrganizationUserResetPasswordWithdraw as i32 }; - log_event(log_id, &member_id, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn).await; + log_event(event_type, &membership.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn) + .await; Ok(()) } diff --git a/src/api/core/public.rs b/src/api/core/public.rs index 6a317b96..d757d953 100644 --- a/src/api/core/public.rs +++ b/src/api/core/public.rs @@ -156,7 +156,7 @@ async fn ldap_import(data: Json, token: PublicToken, conn: DbConn } }; - GroupUser::delete_all_by_group(&group_uuid, &conn).await?; + GroupUser::delete_all_by_group(&group_uuid, &org_id, &conn).await?; for ext_id in &group_data.member_external_ids { if let Some(member) = Membership::find_by_external_id_and_org(ext_id, &org_id, &conn).await { diff --git a/src/auth.rs b/src/auth.rs index 99741277..43184369 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -704,10 +704,9 @@ pub struct OrgHeaders { impl OrgHeaders { fn is_member(&self) -> bool { - // NOTE: we don't care about MembershipStatus at the moment because this is only used - // where an invited, accepted or confirmed user is expected if this ever changes or - // if from_i32 is changed to return Some(Revoked) this check needs to be changed accordingly - self.membership_type >= MembershipType::User + // Only allow not revoked members, we can not use the Confirmed status here + // as some endpoints can be triggered by invited users during joining + self.membership_status != MembershipStatus::Revoked && self.membership_type >= MembershipType::User } fn is_confirmed_and_admin(&self) -> bool { self.membership_status == MembershipStatus::Confirmed && self.membership_type >= MembershipType::Admin @@ -720,6 +719,36 @@ impl OrgHeaders { } } +// org_id is usually the second path param ("/organizations/"), +// but there are cases where it is a query value. +// First check the path, if this is not a valid uuid, try the query values. +fn get_org_id(request: &Request<'_>) -> Option { + if let Some(Ok(org_id)) = request.param::(1) { + Some(org_id) + } else if let Some(Ok(org_id)) = request.query_value::("organizationId") { + Some(org_id) + } else { + None + } +} + +// Special Guard to ensure that there is an organization id present +// If there is no org id trigger the Outcome::Forward. +// This is useful for endpoints which work for both organization and personal vaults, like purge. +pub struct OrgIdGuard; + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for OrgIdGuard { + type Error = &'static str; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + match get_org_id(request) { + Some(_) => Outcome::Success(OrgIdGuard), + None => Outcome::Forward(rocket::http::Status::NotFound), + } + } +} + #[rocket::async_trait] impl<'r> FromRequest<'r> for OrgHeaders { type Error = &'static str; @@ -727,18 +756,8 @@ impl<'r> FromRequest<'r> for OrgHeaders { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(Headers::from_request(request).await); - // org_id is usually the second path param ("/organizations/"), - // but there are cases where it is a query value. - // First check the path, if this is not a valid uuid, try the query values. - let url_org_id: Option = { - if let Some(Ok(org_id)) = request.param::(1) { - Some(org_id) - } else if let Some(Ok(org_id)) = request.query_value::("organizationId") { - Some(org_id) - } else { - None - } - }; + // Extract the org_id from the request + let url_org_id = get_org_id(request); match url_org_id { Some(org_id) if uuid::Uuid::parse_str(&org_id).is_ok() => { diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index b28a25cd..edc5f8c9 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -559,7 +559,7 @@ impl Cipher { if let Some(cached_member) = cipher_sync_data.members.get(org_uuid) { return cached_member.has_full_access(); } - } else if let Some(member) = Membership::find_by_user_and_org(user_uuid, org_uuid, conn).await { + } else if let Some(member) = Membership::find_confirmed_by_user_and_org(user_uuid, org_uuid, conn).await { return member.has_full_access(); } } @@ -668,10 +668,12 @@ impl Cipher { ciphers::table .filter(ciphers::uuid.eq(&self.uuid)) .inner_join(ciphers_collections::table.on( - ciphers::uuid.eq(ciphers_collections::cipher_uuid))) + ciphers::uuid.eq(ciphers_collections::cipher_uuid) + )) .inner_join(users_collections::table.on( ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) - .and(users_collections::user_uuid.eq(user_uuid)))) + .and(users_collections::user_uuid.eq(user_uuid)) + )) .select((users_collections::read_only, users_collections::hide_passwords, users_collections::manage)) .load::<(bool, bool, bool)>(conn) .expect("Error getting user access restrictions") @@ -697,6 +699,9 @@ impl Cipher { .inner_join(users_organizations::table.on( users_organizations::uuid.eq(groups_users::users_organizations_uuid) )) + .inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) + )) .filter(users_organizations::user_uuid.eq(user_uuid)) .select((collections_groups::read_only, collections_groups::hide_passwords, collections_groups::manage)) .load::<(bool, bool, bool)>(conn) @@ -795,28 +800,28 @@ impl Cipher { let mut query = ciphers::table .left_join(ciphers_collections::table.on( ciphers::uuid.eq(ciphers_collections::cipher_uuid) - )) + )) .left_join(users_organizations::table.on( - ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()) + ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()) .and(users_organizations::user_uuid.eq(user_uuid)) .and(users_organizations::status.eq(MembershipStatus::Confirmed as i32)) - )) + )) .left_join(users_collections::table.on( - ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) + ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) // Ensure that users_collections::user_uuid is NULL for unconfirmed users. .and(users_organizations::user_uuid.eq(users_collections::user_uuid)) - )) + )) .left_join(groups_users::table.on( - groups_users::users_organizations_uuid.eq(users_organizations::uuid) - )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) - )) + groups_users::users_organizations_uuid.eq(users_organizations::uuid) + )) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + // Ensure that group and membership belong to the same org + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) + )) .left_join(collections_groups::table.on( - collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and( - collections_groups::groups_uuid.eq(groups::uuid) - ) - )) + collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid) + .and(collections_groups::groups_uuid.eq(groups::uuid)) + )) .filter(ciphers::user_uuid.eq(user_uuid)) // Cipher owner .or_filter(users_organizations::access_all.eq(true)) // access_all in org .or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection @@ -986,7 +991,9 @@ impl Cipher { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid))) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) + )) .left_join(collections_groups::table.on( collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid) .and(collections_groups::groups_uuid.eq(groups::uuid)) @@ -1047,7 +1054,9 @@ impl Cipher { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid))) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) + )) .left_join(collections_groups::table.on( collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid) .and(collections_groups::groups_uuid.eq(groups::uuid)) @@ -1115,8 +1124,8 @@ impl Cipher { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .left_join(collections_groups::table.on( collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and( diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 3e6ccf21..b1f82335 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -191,7 +191,7 @@ impl Collection { self.update_users_revision(conn).await; CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?; CollectionUser::delete_all_by_collection(&self.uuid, conn).await?; - CollectionGroup::delete_all_by_collection(&self.uuid, conn).await?; + CollectionGroup::delete_all_by_collection(&self.uuid, &self.org_uuid, conn).await?; db_run! { conn: { diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid))) @@ -239,8 +239,8 @@ impl Collection { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .left_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid).and( @@ -355,8 +355,8 @@ impl Collection { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .left_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid).and( @@ -422,8 +422,8 @@ impl Collection { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .left_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid) @@ -484,8 +484,8 @@ impl Collection { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .left_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid).and( @@ -531,8 +531,8 @@ impl Collection { .left_join(groups_users::table.on( groups_users::users_organizations_uuid.eq(users_organizations::uuid) )) - .left_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .left_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid).and( diff --git a/src/db/models/group.rs b/src/db/models/group.rs index a24b5325..f41ad9ca 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -1,6 +1,6 @@ use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId}; use crate::api::EmptyResult; -use crate::db::schema::{collections_groups, groups, groups_users, users_organizations}; +use crate::db::schema::{collections, collections_groups, groups, groups_users, users_organizations}; use crate::db::DbConn; use crate::error::MapResult; use chrono::{NaiveDateTime, Utc}; @@ -81,7 +81,7 @@ impl Group { // If both read_only and hide_passwords are false, then manage should be true // You can't have an entry with read_only and manage, or hide_passwords and manage // Or an entry with everything to false - let collections_groups: Vec = CollectionGroup::find_by_group(&self.uuid, conn) + let collections_groups: Vec = CollectionGroup::find_by_group(&self.uuid, &self.organizations_uuid, conn) .await .iter() .map(|entry| { @@ -191,7 +191,7 @@ impl Group { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult { for group in Self::find_by_organization(org_uuid, conn).await { - group.delete(conn).await?; + group.delete(org_uuid, conn).await?; } Ok(()) } @@ -246,8 +246,8 @@ impl Group { .inner_join(users_organizations::table.on( users_organizations::uuid.eq(groups_users::users_organizations_uuid) )) - .inner_join(groups::table.on( - groups::uuid.eq(groups_users::groups_uuid) + .inner_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) )) .filter(users_organizations::user_uuid.eq(user_uuid)) .filter(groups::access_all.eq(true)) @@ -276,9 +276,9 @@ impl Group { }} } - pub async fn delete(&self, conn: &DbConn) -> EmptyResult { - CollectionGroup::delete_all_by_group(&self.uuid, conn).await?; - GroupUser::delete_all_by_group(&self.uuid, conn).await?; + pub async fn delete(&self, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult { + CollectionGroup::delete_all_by_group(&self.uuid, org_uuid, conn).await?; + GroupUser::delete_all_by_group(&self.uuid, org_uuid, conn).await?; db_run! { conn: { diesel::delete(groups::table.filter(groups::uuid.eq(&self.uuid))) @@ -306,8 +306,8 @@ impl Group { } impl CollectionGroup { - pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { - let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await; + pub async fn save(&mut self, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult { + let group_users = GroupUser::find_by_group(&self.groups_uuid, org_uuid, conn).await; for group_user in group_users { group_user.update_user_revision(conn).await; } @@ -365,10 +365,19 @@ impl CollectionGroup { } } - pub async fn find_by_group(group_uuid: &GroupId, conn: &DbConn) -> Vec { + pub async fn find_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> Vec { db_run! { conn: { collections_groups::table + .inner_join(groups::table.on( + groups::uuid.eq(collections_groups::groups_uuid) + )) + .inner_join(collections::table.on( + collections::uuid.eq(collections_groups::collections_uuid) + .and(collections::org_uuid.eq(groups::organizations_uuid)) + )) .filter(collections_groups::groups_uuid.eq(group_uuid)) + .filter(collections::org_uuid.eq(org_uuid)) + .select(collections_groups::all_columns) .load::(conn) .expect("Error loading collection groups") }} @@ -383,6 +392,13 @@ impl CollectionGroup { .inner_join(users_organizations::table.on( users_organizations::uuid.eq(groups_users::users_organizations_uuid) )) + .inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) + )) + .inner_join(collections::table.on( + collections::uuid.eq(collections_groups::collections_uuid) + .and(collections::org_uuid.eq(groups::organizations_uuid)) + )) .filter(users_organizations::user_uuid.eq(user_uuid)) .select(collections_groups::all_columns) .load::(conn) @@ -394,14 +410,20 @@ impl CollectionGroup { db_run! { conn: { collections_groups::table .filter(collections_groups::collections_uuid.eq(collection_uuid)) + .inner_join(collections::table.on( + collections::uuid.eq(collections_groups::collections_uuid) + )) + .inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid) + .and(groups::organizations_uuid.eq(collections::org_uuid)) + )) .select(collections_groups::all_columns) .load::(conn) .expect("Error loading collection groups") }} } - pub async fn delete(&self, conn: &DbConn) -> EmptyResult { - let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await; + pub async fn delete(&self, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult { + let group_users = GroupUser::find_by_group(&self.groups_uuid, org_uuid, conn).await; for group_user in group_users { group_user.update_user_revision(conn).await; } @@ -415,8 +437,8 @@ impl CollectionGroup { }} } - pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &DbConn) -> EmptyResult { - let group_users = GroupUser::find_by_group(group_uuid, conn).await; + pub async fn delete_all_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult { + let group_users = GroupUser::find_by_group(group_uuid, org_uuid, conn).await; for group_user in group_users { group_user.update_user_revision(conn).await; } @@ -429,10 +451,14 @@ impl CollectionGroup { }} } - pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult { + pub async fn delete_all_by_collection( + collection_uuid: &CollectionId, + org_uuid: &OrganizationId, + conn: &DbConn, + ) -> EmptyResult { let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await; for collection_assigned_to_group in collection_assigned_to_groups { - let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await; + let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, org_uuid, conn).await; for group_user in group_users { group_user.update_user_revision(conn).await; } @@ -494,10 +520,19 @@ impl GroupUser { } } - pub async fn find_by_group(group_uuid: &GroupId, conn: &DbConn) -> Vec { + pub async fn find_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> Vec { db_run! { conn: { groups_users::table + .inner_join(groups::table.on( + groups::uuid.eq(groups_users::groups_uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::uuid.eq(groups_users::users_organizations_uuid) + .and(users_organizations::org_uuid.eq(groups::organizations_uuid)) + )) .filter(groups_users::groups_uuid.eq(group_uuid)) + .filter(groups::organizations_uuid.eq(org_uuid)) + .select(groups_users::all_columns) .load::(conn) .expect("Error loading group users") }} @@ -522,6 +557,13 @@ impl GroupUser { .inner_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid) )) + .inner_join(groups::table.on( + groups::uuid.eq(groups_users::groups_uuid) + )) + .inner_join(collections::table.on( + collections::uuid.eq(collections_groups::collections_uuid) + .and(collections::org_uuid.eq(groups::organizations_uuid)) + )) .filter(collections_groups::collections_uuid.eq(collection_uuid)) .filter(groups_users::users_organizations_uuid.eq(member_uuid)) .count() @@ -575,8 +617,8 @@ impl GroupUser { }} } - pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &DbConn) -> EmptyResult { - let group_users = GroupUser::find_by_group(group_uuid, conn).await; + pub async fn delete_all_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult { + let group_users = GroupUser::find_by_group(group_uuid, org_uuid, conn).await; for group_user in group_users { group_user.update_user_revision(conn).await; } diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 96811a2b..7e922f35 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -332,7 +332,7 @@ impl OrgPolicy { for policy in OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await { - if let Some(user) = Membership::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await { + if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await { if user.atype < MembershipType::Admin { match serde_json::from_str::(&policy.data) { Ok(opts) => { diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 0b722ef6..9021c739 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -1073,7 +1073,9 @@ impl Membership { .left_join(collections_groups::table.on( collections_groups::groups_uuid.eq(groups_users::groups_uuid) )) - .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid))) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid) + .and(groups::organizations_uuid.eq(users_organizations::org_uuid)) + )) .left_join(ciphers_collections::table.on( ciphers_collections::collection_uuid.eq(collections_groups::collections_uuid).and(ciphers_collections::cipher_uuid.eq(&cipher_uuid)) diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index f0a1e663..0dc08e3e 100644 --- a/src/db/models/two_factor.rs +++ b/src/db/models/two_factor.rs @@ -20,7 +20,6 @@ pub struct TwoFactor { pub last_used: i64, } -#[allow(dead_code)] #[derive(num_derive::FromPrimitive)] pub enum TwoFactorType { Authenticator = 0, From f07a91141a8b8f62fd1c5ec3240d51c6ec72af73 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 1 Apr 2026 23:04:10 +0200 Subject: [PATCH 33/41] Fix empty string FolderId (#7048) In newer versions of Bitwarden Clients instead of using `null` the folder_id will be an empty string. This commit adds a special deserialize_with function to keep the same way of working code-wise. Fixes #6962 Signed-off-by: BlackDex --- src/api/core/accounts.rs | 3 ++- src/api/core/ciphers.rs | 5 ++++- src/api/core/folders.rs | 2 ++ src/util.rs | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index ba36fc9b..4855e8c2 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -22,7 +22,7 @@ use crate::{ DbConn, }, mail, - util::{format_date, NumberOrString}, + util::{deser_opt_nonempty_str, format_date, NumberOrString}, CONFIG, }; @@ -649,6 +649,7 @@ struct UpdateFolderData { // There is a bug in 2024.3.x which adds a `null` item. // To bypass this we allow a Option here, but skip it during the updates // See: https://github.com/bitwarden/clients/issues/8453 + #[serde(default, deserialize_with = "deser_opt_nonempty_str")] id: Option, name: String, } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 8a8d9838..6d4e1f41 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -11,7 +11,7 @@ use rocket::{ use serde_json::Value; use crate::auth::ClientVersion; -use crate::util::{save_temp_file, NumberOrString}; +use crate::util::{deser_opt_nonempty_str, save_temp_file, NumberOrString}; use crate::{ api::{self, core::log_event, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType}, auth::{Headers, OrgIdGuard, OwnerHeaders}, @@ -248,6 +248,7 @@ pub struct CipherData { // Id is optional as it is included only in bulk share pub id: Option, // Folder id is not included in import + #[serde(default, deserialize_with = "deser_opt_nonempty_str")] pub folder_id: Option, // TODO: Some of these might appear all the time, no need for Option #[serde(alias = "organizationID")] @@ -297,6 +298,7 @@ pub struct CipherData { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PartialCipherData { + #[serde(default, deserialize_with = "deser_opt_nonempty_str")] folder_id: Option, favorite: bool, } @@ -1569,6 +1571,7 @@ async fn restore_cipher_selected( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct MoveCipherData { + #[serde(default, deserialize_with = "deser_opt_nonempty_str")] folder_id: Option, ids: Vec, } diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs index dc971a13..1b3fd714 100644 --- a/src/api/core/folders.rs +++ b/src/api/core/folders.rs @@ -8,6 +8,7 @@ use crate::{ models::{Folder, FolderId}, DbConn, }, + util::deser_opt_nonempty_str, }; pub fn routes() -> Vec { @@ -38,6 +39,7 @@ async fn get_folder(folder_id: FolderId, headers: Headers, conn: DbConn) -> Json #[serde(rename_all = "camelCase")] pub struct FolderData { pub name: String, + #[serde(default, deserialize_with = "deser_opt_nonempty_str")] pub id: Option, } diff --git a/src/util.rs b/src/util.rs index d336689d..182b7b3b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -634,6 +634,21 @@ fn _process_key(key: &str) -> String { } } +pub fn deser_opt_nonempty_str<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: From, +{ + use serde::Deserialize; + Ok(Option::::deserialize(deserializer)?.and_then(|s| { + if s.is_empty() { + None + } else { + Some(T::from(s)) + } + })) +} + #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] pub enum NumberOrString { From 8f0e99b875bfde3bdf0f3eba8aff806da85d25e5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 2 Apr 2026 00:04:34 +0300 Subject: [PATCH 34/41] Disable deployments for release env (#7033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As according to the docs: https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/control-deployments#using-environments-without-deployments This is useful when you want to use environments for: Organizing secrets—group related secrets under an environment name without creating deployment records. Access control—restrict which branches can use certain secrets via environment branch policies, without deployment tracking. CI and testing jobs—reference an environment for its configuration without adding noise to the deployment history. --- .github/workflows/release.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35e995c5..5b72da3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,9 @@ jobs: docker-build: name: Build Vaultwarden containers if: ${{ github.repository == 'dani-garcia/vaultwarden' }} - environment: release + environment: + name: release + deployment: false permissions: packages: write # Needed to upload packages and artifacts contents: read @@ -249,7 +251,9 @@ jobs: name: Merge manifests runs-on: ubuntu-latest needs: docker-build - environment: release + environment: + name: release + deployment: false permissions: packages: write # Needed to upload packages and artifacts attestations: write # Needed to generate an artifact attestation for a build From 2811df29539d686400e83c1af0296aa5fc6e44d9 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sun, 5 Apr 2026 22:35:21 +0200 Subject: [PATCH 35/41] Fix Send icons (#7051) Send uses icons to display if it is protected by password or not. Bitwarden has added a feature to use email with an OTP for newer versions. Vaultwarden does not yet support this, but this commit adds an Enum with all 3 the options. The email option currently needs a feature-flag and newer web-vault/clients. For now, this will at least fix the display of icons. Fixes #6976 Signed-off-by: BlackDex --- src/db/models/send.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/db/models/send.rs b/src/db/models/send.rs index 8180f843..84802c54 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -46,6 +46,16 @@ pub enum SendType { File = 1, } +enum SendAuthType { + #[allow(dead_code)] + // Send requires email OTP verification + Email = 0, // Not yet supported by Vaultwarden + // Send requires a password + Password = 1, + // Send requires no auth + None = 2, +} + impl Send { pub fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self { let now = Utc::now().naive_utc(); @@ -145,6 +155,7 @@ impl Send { "maxAccessCount": self.max_access_count, "accessCount": self.access_count, "password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)), + "authType": if self.password_hash.is_some() { SendAuthType::Password as i32 } else { SendAuthType::None as i32 }, "disabled": self.disabled, "hideEmail": self.hide_email, From d29cd29f55c4fddf71aac842d337c0eec58c140c Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:39:33 +0200 Subject: [PATCH 36/41] prevent managers from creating collections (#6890) managers without the access_all flag should not be able to create collections. the manage all collections permission actually consists of three separate custom permissions that have not been implemented yet for more fine-grain access control. --- src/api/core/organizations.rs | 8 ++++---- src/db/models/organization.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 36e3e4a0..9a5079cb 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -500,6 +500,10 @@ async fn post_organization_collections( let data: FullCollectionData = data.into_inner(); data.validate(&org_id, &conn).await?; + if headers.membership.atype == MembershipType::Manager && !headers.membership.access_all { + err!("You don't have permission to create collections") + } + let collection = Collection::new(org_id.clone(), data.name, data.external_id); collection.save(&conn).await?; @@ -540,10 +544,6 @@ async fn post_organization_collections( .await?; } - if headers.membership.atype == MembershipType::Manager && !headers.membership.access_all { - CollectionUser::save(&headers.membership.user_uuid, &collection.uuid, false, false, false, &conn).await?; - } - Ok(Json(collection.to_json_details(&headers.membership.user_uuid, None, &conn).await)) } diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 9021c739..ae19b30c 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -514,7 +514,8 @@ impl Membership { "familySponsorshipValidUntil": null, "familySponsorshipToDelete": null, "accessSecretsManager": false, - "limitCollectionCreation": self.atype < MembershipType::Manager, // If less then a manager return true, to limit collection creations + // limit collection creation to managers with access_all permission to prevent issues + "limitCollectionCreation": self.atype < MembershipType::Manager || !self.access_all, "limitCollectionDeletion": true, "limitItemDeletion": false, "allowAdminAccessToAllCollectionItems": true, From 43df0fb7f4bf18c253b2e114e3b6408d34909c2a Mon Sep 17 00:00:00 2001 From: Aaron Brager <789577+getaaron@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:40:00 -0500 Subject: [PATCH 37/41] Change SQLite backup to use VACUUM INTO query (#6989) * Refactor SQLite backup to use VACUUM INTO query Replaced manual file creation for SQLite backup with a VACUUM INTO query. * Fix VACUUM INTO query error handling --- src/db/mod.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/db/mod.rs b/src/db/mod.rs index ae2b1221..d2ed9479 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -387,7 +387,6 @@ pub mod models; #[cfg(sqlite)] pub fn backup_sqlite() -> Result { use diesel::Connection; - use std::{fs::File, io::Write}; let db_url = CONFIG.database_url(); if DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::Sqlite).unwrap_or(false) { @@ -401,16 +400,13 @@ pub fn backup_sqlite() -> Result { .to_string_lossy() .into_owned(); - match File::create(backup_file.clone()) { - Ok(mut f) => { - let serialized_db = conn.serialize_database_to_buffer(); - f.write_all(serialized_db.as_slice()).expect("Error writing SQLite backup"); - Ok(backup_file) - } - Err(e) => { - err_silent!(format!("Unable to save SQLite backup: {e:?}")) - } - } + diesel::sql_query("VACUUM INTO ?") + .bind::(&backup_file) + .execute(&mut conn) + .map(|_| ()) + .map_res("VACUUM INTO failed")?; + + Ok(backup_file) } else { err_silent!("The database type is not SQLite. Backups only works for SQLite databases") } From fc43737868463daf81f44c9330b5bdd06dbe6cf4 Mon Sep 17 00:00:00 2001 From: Hex <0x484558@pm.me> Date: Sun, 5 Apr 2026 22:41:14 +0200 Subject: [PATCH 38/41] Handle `SIGTERM` and `SIGQUIT` shutdown signals. (#7008) * handle more shutdown signals * disable Rocket's built-in signal handlers --- src/main.rs | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8eef2e8c..b4885831 100644 --- a/src/main.rs +++ b/src/main.rs @@ -558,6 +558,11 @@ 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()); + + // We install our own signal handlers below; disable Rocket's built-in handlers + config.shutdown.ctrlc = false; + config.shutdown.signals.clear(); + config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into(); config.cli_colors = false; // Make sure Rocket does not color any values for logging. config.limits = Limits::new() @@ -589,11 +594,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> CONFIG.set_rocket_shutdown_handle(instance.shutdown()); - tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Error setting Ctrl-C handler"); - info!("Exiting Vaultwarden!"); - CONFIG.shutdown(); - }); + spawn_shutdown_signal_handler(); #[cfg(all(unix, sqlite))] { @@ -621,6 +622,35 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> Ok(()) } +#[cfg(unix)] +fn spawn_shutdown_signal_handler() { + tokio::spawn(async move { + use tokio::signal::unix::signal; + + let mut sigint = signal(SignalKind::interrupt()).expect("Error setting SIGINT handler"); + let mut sigterm = signal(SignalKind::terminate()).expect("Error setting SIGTERM handler"); + let mut sigquit = signal(SignalKind::quit()).expect("Error setting SIGQUIT handler"); + + let signal_name = tokio::select! { + _ = sigint.recv() => "SIGINT", + _ = sigterm.recv() => "SIGTERM", + _ = sigquit.recv() => "SIGQUIT", + }; + + info!("Received {signal_name}, initiating graceful shutdown"); + CONFIG.shutdown(); + }); +} + +#[cfg(not(unix))] +fn spawn_shutdown_signal_handler() { + tokio::spawn(async move { + tokio::signal::ctrl_c().await.expect("Error setting Ctrl-C handler"); + info!("Received Ctrl-C, initiating graceful shutdown"); + CONFIG.shutdown(); + }); +} + fn schedule_jobs(pool: db::DbPool) { if CONFIG.job_poll_interval_ms() == 0 { info!("Job scheduler disabled."); From d4f67429d6e6d4b83d49491ac32b8abf3ce43bd9 Mon Sep 17 00:00:00 2001 From: Hex <0x484558@pm.me> Date: Sun, 5 Apr 2026 22:43:06 +0200 Subject: [PATCH 39/41] Do not display unavailable 2FA options (#7013) * do not display unavailable 2FA options * use existing function to check webauthn support * clarity in 2fa skip code --- src/api/core/two_factor/mod.rs | 49 +++++++++++++++++++++++++++-- src/api/core/two_factor/webauthn.rs | 4 +-- src/api/identity.rs | 25 +++++++++++++-- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index 34fbfaa9..3a503a23 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -1,7 +1,9 @@ use chrono::{TimeDelta, Utc}; use data_encoding::BASE32; +use num_traits::FromPrimitive; use rocket::serde::json::Json; use rocket::Route; +use serde::Deserialize; use serde_json::Value; use crate::{ @@ -14,7 +16,7 @@ use crate::{ db::{ models::{ DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor, - TwoFactorIncomplete, User, UserId, + TwoFactorIncomplete, TwoFactorType, User, UserId, }, DbConn, DbPool, }, @@ -31,6 +33,43 @@ pub mod protected_actions; pub mod webauthn; pub mod yubikey; +fn has_global_duo_credentials() -> bool { + CONFIG._enable_duo() && CONFIG.duo_host().is_some() && CONFIG.duo_ikey().is_some() && CONFIG.duo_skey().is_some() +} + +pub fn is_twofactor_provider_usable(provider_type: TwoFactorType, provider_data: Option<&str>) -> bool { + #[derive(Deserialize)] + struct DuoProviderData { + host: String, + ik: String, + sk: String, + } + + match provider_type { + TwoFactorType::Authenticator => true, + TwoFactorType::Email => CONFIG._enable_email_2fa(), + TwoFactorType::Duo | TwoFactorType::OrganizationDuo => { + provider_data + .and_then(|raw| serde_json::from_str::(raw).ok()) + .is_some_and(|duo| !duo.host.is_empty() && !duo.ik.is_empty() && !duo.sk.is_empty()) + || has_global_duo_credentials() + } + TwoFactorType::YubiKey => { + CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some() + } + TwoFactorType::Webauthn => CONFIG.is_webauthn_2fa_supported(), + TwoFactorType::Remember => !CONFIG.disable_2fa_remember(), + TwoFactorType::RecoveryCode => true, + TwoFactorType::U2f + | TwoFactorType::U2fRegisterChallenge + | TwoFactorType::U2fLoginChallenge + | TwoFactorType::EmailVerificationChallenge + | TwoFactorType::WebauthnRegisterChallenge + | TwoFactorType::WebauthnLoginChallenge + | TwoFactorType::ProtectedActions => false, + } +} + pub fn routes() -> Vec { let mut routes = routes![ get_twofactor, @@ -53,7 +92,13 @@ pub fn routes() -> Vec { #[get("/two-factor")] async fn get_twofactor(headers: Headers, conn: DbConn) -> Json { let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await; - let twofactors_json: Vec = twofactors.iter().map(TwoFactor::to_json_provider).collect(); + let twofactors_json: Vec = twofactors + .iter() + .filter_map(|tf| { + let provider_type = TwoFactorType::from_i32(tf.atype)?; + is_twofactor_provider_usable(provider_type, Some(&tf.data)).then(|| TwoFactor::to_json_provider(tf)) + }) + .collect(); Json(json!({ "data": twofactors_json, diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index 6ae12752..0ec0e30e 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/src/api/core/two_factor/webauthn.rs @@ -108,8 +108,8 @@ impl WebauthnRegistration { #[post("/two-factor/get-webauthn", data = "")] async fn get_webauthn(data: Json, headers: Headers, conn: DbConn) -> JsonResult { - if !CONFIG.domain_set() { - err!("`DOMAIN` environment variable is not set. Webauthn disabled") + if !CONFIG.is_webauthn_2fa_supported() { + err!("Configured `DOMAIN` is not compatible with Webauthn") } let data: PasswordOrOtpData = data.into_inner(); diff --git a/src/api/identity.rs b/src/api/identity.rs index fcd8c388..b9a753b9 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -14,7 +14,10 @@ use crate::{ core::{ accounts::{PreloginData, RegisterData, _prelogin, _register, kdf_upgrade}, log_user_event, - two_factor::{authenticator, duo, duo_oidc, email, enforce_2fa_policy, webauthn, yubikey}, + two_factor::{ + authenticator, duo, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn, + yubikey, + }, }, master_password_policy, push::register_push_device, @@ -739,8 +742,24 @@ async fn twofactor_auth( TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, device.atype, ip, conn).await?; - let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect(); + let twofactor_ids: Vec<_> = twofactors + .iter() + .filter_map(|tf| { + let provider_type = TwoFactorType::from_i32(tf.atype)?; + (tf.enabled && is_twofactor_provider_usable(provider_type, Some(&tf.data))).then_some(tf.atype) + }) + .collect(); + if twofactor_ids.is_empty() { + err!("No enabled and usable two factor providers are available for this account") + } + let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one + if !twofactor_ids.contains(&selected_id) { + err_json!( + _json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?, + "Invalid two factor provider" + ) + } let twofactor_code = match data.two_factor_token { Some(ref code) => code, @@ -871,7 +890,7 @@ async fn _json_err_twofactor( match TwoFactorType::from_i32(*provider) { Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } - Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => { + Some(TwoFactorType::Webauthn) if CONFIG.is_webauthn_2fa_supported() => { let request = webauthn::generate_webauthn_login(user_id, conn).await?; result["TwoFactorProviders2"][provider.to_string()] = request.0; } From 3f28b583dbad90ba5851760c1722044ac4b66a3c Mon Sep 17 00:00:00 2001 From: qaz741wsd856 Date: Mon, 6 Apr 2026 04:43:58 +0800 Subject: [PATCH 40/41] Fix logout push identifiers and send logout before clearing devices (#7047) * Fix logout push identifiers and send logout before clearing devices * Refactor logout function parameters * Fix parameters in logout notification functions --- src/api/admin.rs | 3 ++- src/api/core/accounts.rs | 9 +++++---- src/api/notifications.rs | 7 ++++--- src/api/push.rs | 10 ++++------ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index a4f53e0e..1546676f 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -480,7 +480,6 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti #[post("/users//disable", format = "application/json")] async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult { let mut user = get_user_or_404(&user_id, &conn).await?; - Device::delete_all_by_user(&user.uuid, &conn).await?; user.reset_security_stamp(&conn).await?; user.enabled = false; @@ -488,6 +487,8 @@ async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Not nt.send_logout(&user, None, &conn).await; + Device::delete_all_by_user(&user.uuid, &conn).await?; + save_result } diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 4855e8c2..8841c184 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -540,7 +540,7 @@ async fn post_password(data: Json, headers: Headers, conn: DbCon // Prevent logging out the client where the user requested this endpoint from. // If you do logout the user it will causes issues at the client side. // Adding the device uuid will prevent this. - nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await; + nt.send_logout(&user, Some(&headers.device), &conn).await; save_result } @@ -638,7 +638,7 @@ async fn post_kdf(data: Json, headers: Headers, conn: DbConn, nt: .await?; let save_result = user.save(&conn).await; - nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await; + nt.send_logout(&user, Some(&headers.device), &conn).await; save_result } @@ -912,7 +912,7 @@ async fn post_rotatekey(data: Json, headers: Headers, conn: DbConn, nt: // Prevent logging out the client where the user requested this endpoint from. // If you do logout the user it will causes issues at the client side. // Adding the device uuid will prevent this. - nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await; + nt.send_logout(&user, Some(&headers.device), &conn).await; save_result } @@ -924,12 +924,13 @@ async fn post_sstamp(data: Json, headers: Headers, conn: DbCo data.validate(&user, true, &conn).await?; - Device::delete_all_by_user(&user.uuid, &conn).await?; user.reset_security_stamp(&conn).await?; let save_result = user.save(&conn).await; nt.send_logout(&user, None, &conn).await; + Device::delete_all_by_user(&user.uuid, &conn).await?; + save_result } diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 42157ac3..492fdb19 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -358,15 +358,16 @@ impl WebSocketUsers { } } - pub async fn send_logout(&self, user: &User, acting_device_id: Option, conn: &DbConn) { + pub async fn send_logout(&self, user: &User, acting_device: Option<&Device>, conn: &DbConn) { // Skip any processing if both WebSockets and Push are not active if *NOTIFICATIONS_DISABLED { return; } + let acting_device_id = acting_device.map(|d| d.uuid.clone()); let data = create_update( vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))], UpdateType::LogOut, - acting_device_id.clone(), + acting_device_id, ); if CONFIG.enable_websocket() { @@ -374,7 +375,7 @@ impl WebSocketUsers { } if CONFIG.push_enabled() { - push_logout(user, acting_device_id.clone(), conn).await; + push_logout(user, acting_device, conn).await; } } diff --git a/src/api/push.rs b/src/api/push.rs index a7e88455..5000869d 100644 --- a/src/api/push.rs +++ b/src/api/push.rs @@ -13,7 +13,7 @@ use tokio::sync::RwLock; use crate::{ api::{ApiResult, EmptyResult, UpdateType}, db::{ - models::{AuthRequestId, Cipher, Device, DeviceId, Folder, PushId, Send, User, UserId}, + models::{AuthRequestId, Cipher, Device, Folder, PushId, Send, User, UserId}, DbConn, }, http_client::make_http_request, @@ -188,15 +188,13 @@ pub async fn push_cipher_update(ut: UpdateType, cipher: &Cipher, device: &Device } } -pub async fn push_logout(user: &User, acting_device_id: Option, conn: &DbConn) { - let acting_device_id: Value = acting_device_id.map(|v| v.to_string().into()).unwrap_or_else(|| Value::Null); - +pub async fn push_logout(user: &User, acting_device: Option<&Device>, conn: &DbConn) { if Device::check_user_has_push_device(&user.uuid, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": user.uuid, "organizationId": (), - "deviceId": acting_device_id, - "identifier": acting_device_id, + "deviceId": acting_device.and_then(|d| d.push_uuid.as_ref()), + "identifier": acting_device.map(|d| &d.uuid), "type": UpdateType::LogOut as i32, "payload": { "userId": user.uuid, From a6b43651ca2896fad9ecf8583f0a20d8101f2443 Mon Sep 17 00:00:00 2001 From: idontneedonetho Date: Wed, 8 Apr 2026 09:35:18 -0400 Subject: [PATCH 41/41] Fix windows build issues (#7065) Need to set signals to UNIX only so we can build on windows. --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index b4885831..60c5a593 100644 --- a/src/main.rs +++ b/src/main.rs @@ -561,6 +561,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> // We install our own signal handlers below; disable Rocket's built-in handlers config.shutdown.ctrlc = false; + #[cfg(unix)] config.shutdown.signals.clear(); config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into();