diff --git a/.github/update-ada.sh b/.github/update-ada.sh deleted file mode 100755 index abe7b24..0000000 --- a/.github/update-ada.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -set -e - -BASE_DIR=$(pwd) -DEPENDENCIES_DIR="$BASE_DIR/deps" - -WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp') - -cleanup () { - EXIT_CODE=$? - [ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE" - exit $EXIT_CODE -} - -trap cleanup INT TERM EXIT - -cd "$WORKSPACE" -curl -sL -o "ada" "https://github.com/ada-url/ada/releases/latest/download/singleheader.zip" -unzip ada -rm ada -cp * "$DEPENDENCIES_DIR" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 099f647..81e0e62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,9 @@ on: paths-ignore: - '**/*.md' +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref_name != 'main' }} @@ -42,7 +45,7 @@ jobs: sudo dpkg --install wasi-sdk-33.0-x86_64-linux.deb curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/v46.0.1/wasmtime-v46.0.1-x86_64-linux.tar.xz tar xvf wasmtime-v46.0.1-x86_64-linux.tar.xz - echo `pwd`/wasmtime-v46.0.1-x86_64-linux >> $GITHUB_PATH + echo "$PWD/wasmtime-v46.0.1-x86_64-linux" >> "$GITHUB_PATH" - uses: Swatinem/rust-cache@v2 with: @@ -55,56 +58,33 @@ jobs: uses: taiki-e/install-action@cargo-hack - name: Clippy - run: cargo hack clippy --feature-powerset -- -D warnings + run: cargo hack clippy --feature-powerset --locked -- -D warnings - name: Test - # `bundled` must stay enabled for every combination: it is what compiles - # and statically links the C++ ada sources. Without it (and with no - # external ada provided via ADA_LIB_DIR/ADA_LIB_NAME) the test binaries - # fail to link with `undefined symbol: ada_*`. `--features bundled` forces - # it into each powerset entry while still exercising the other features. - run: cargo hack test --feature-powerset --features bundled + run: cargo hack test --feature-powerset --locked - name: Check Documentation env: RUSTDOCFLAGS: '-D warnings' - run: cargo hack doc --feature-powerset + run: cargo hack doc --feature-powerset --locked - external-ada: - name: External ada (non-bundled) + feature-modes: + name: no_std and optional features runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: Swatinem/rust-cache@v2 with: - shared-key: external-ada + shared-key: feature-modes save-if: ${{ github.ref_name == 'main' }} - run: rustup show - # Exercise the non-bundled linking path: instead of compiling the bundled - # C++ sources, build ada once as a standalone shared library and link the - # crate against it via ADA_LIB_DIR / ADA_LIB_NAME. This mirrors how a host - # build system (Bazel, CMake, a distro package) would provide ada. The - # shared library records its own libstdc++ dependency (DT_NEEDED), so the - # final Rust binary needs no extra C++ runtime link flags. - - name: Build standalone libada.so - run: | - c++ -std=c++20 -O2 -DADA_INCLUDE_URL_PATTERN=0 -Ideps -fPIC -shared \ - deps/ada.cpp -o "$RUNNER_TEMP/libada.so" - - # `bundled` is intentionally disabled (default-features off). `std` is - # re-enabled because dropping default features also drops it. The serde - # combination is run separately to cover the extra feature on this path. - - name: Test against external ada - env: - ADA_LIB_DIR: ${{ runner.temp }} - ADA_LIB_NAME: ada - LD_LIBRARY_PATH: ${{ runner.temp }} + - name: Test dependency-free core and Serde integration run: | - cargo test --no-default-features --features std - cargo test --no-default-features --features serde + cargo test --no-default-features --locked + cargo test --no-default-features --features serde --locked sanitizers: name: AddressSanitizer + LeakSanitizer @@ -112,11 +92,6 @@ jobs: steps: - uses: actions/checkout@v7 - # AddressSanitizer (which bundles LeakSanitizer) requires nightly. We use - # clang for the bundled C++ so it shares the LLVM sanitizer runtime that - # rustc links into the test binaries. Note: `cargo +nightly` is used - # explicitly because rust-toolchain.toml pins a stable channel that would - # otherwise override the default toolchain in this directory. - name: Install nightly toolchain run: rustup toolchain install nightly --profile minimal @@ -127,15 +102,8 @@ jobs: - run: rustup +nightly show - # `--target` is required so build scripts / proc-macros (host) are not - # instrumented. LeakSanitizer is enabled by default under ASan on Linux; - # this catches regressions like https://github.com/ada-url/rust/issues/101 - # where the failed-parse path leaked the allocation from `ada_parse`. - name: Test under AddressSanitizer + LeakSanitizer env: - CC: clang - CXX: clang++ - ADA_SANITIZE: address RUSTFLAGS: '-Zsanitizer=address' ASAN_OPTIONS: 'detect_leaks=1' run: cargo +nightly test --tests --target x86_64-unknown-linux-gnu @@ -163,4 +131,22 @@ jobs: - run: rustup show - - run: cargo clippy -- -D warnings + - run: cargo clippy --all-targets --all-features --locked -- -D warnings + + miri: + name: Miri + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: dtolnay/rust-toolchain@nightly + with: + components: miri + + - uses: Swatinem/rust-cache@v2 + + # Miri interprets Rust MIR to catch undefined behavior that normal tests + # cannot reliably observe. rust-toolchain.toml pins stable, so select + # nightly explicitly. + - run: cargo +nightly miri setup + - run: cargo +nightly miri test --lib --no-default-features diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 046d895..817567d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: sudo dpkg --install wasi-sdk-33.0-x86_64-linux.deb curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/v46.0.1/wasmtime-v46.0.1-x86_64-linux.tar.xz tar xvf wasmtime-v46.0.1-x86_64-linux.tar.xz - echo `pwd`/wasmtime-v46.0.1-x86_64-linux >> $GITHUB_PATH + echo "$PWD/wasmtime-v46.0.1-x86_64-linux" >> "$GITHUB_PATH" - uses: Swatinem/rust-cache@v2 with: @@ -57,15 +57,15 @@ jobs: uses: taiki-e/install-action@cargo-hack - name: Clippy - run: cargo hack clippy --feature-powerset -- -D warnings + run: cargo hack clippy --feature-powerset --locked -- -D warnings - name: Test - run: cargo hack test --feature-powerset + run: cargo hack test --feature-powerset --locked - name: Check Documentation env: RUSTDOCFLAGS: '-D warnings' - run: cargo hack doc --feature-powerset + run: cargo hack doc --feature-powerset --locked - name: Check semver if: matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/update-ada.yml b/.github/workflows/update-ada.yml deleted file mode 100644 index 68bc753..0000000 --- a/.github/workflows/update-ada.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Update Ada - -on: - workflow_dispatch: - schedule: - # Check for new Ada releases weekly on Mondays at 09:00 UTC - - cron: '0 9 * * 1' - -jobs: - update: - name: Update Ada single-header - runs-on: ubuntu-latest - outputs: - changed: ${{ steps.diff.outputs.changed }} - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: true - - - name: Download latest Ada single-header release - run: .github/update-ada.sh - - - name: Check for changes - id: diff - run: | - if git diff --quiet deps/; then - echo "changed=false" >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Get new Ada version - if: steps.diff.outputs.changed == 'true' - id: version - run: | - VERSION=$(grep -oP '(?<=ADA_VERSION ")[^"]+' deps/ada.h | head -1) - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - - name: Commit updated deps - if: steps.diff.outputs.changed == 'true' - env: - ADA_VERSION: ${{ steps.version.outputs.version }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout -b chore/update-ada-${ADA_VERSION} - git add deps/ - git commit -m "chore: update ada to ${ADA_VERSION}" - git push -u origin chore/update-ada-${ADA_VERSION} - - test: - name: Build & Test (${{ matrix.os }}) - needs: update - if: needs.update.outputs.changed == 'true' - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v7 - with: - ref: chore/update-ada-${{ needs.update.outputs.version }} - - - uses: Swatinem/rust-cache@v2 - with: - shared-key: update-ada - - - run: rustup show - - - name: Run tests - run: cargo test - - open-pr: - name: Open pull request - needs: [update, test] - if: needs.update.outputs.changed == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: chore/update-ada-${{ needs.update.outputs.version }} - - - name: Open pull request - uses: peter-evans/create-pull-request@v8 - with: - title: "chore: update Ada to ${{ needs.update.outputs.version }}" - body: "Automated update of the Ada single-header files to version ${{ needs.update.outputs.version }}." - branch: chore/update-ada-${{ needs.update.outputs.version }} - labels: dependencies diff --git a/.gitignore b/.gitignore index ea8c4bf..6af5945 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ -/target +/target/ +/fuzz/target/ +/fuzz/corpus/ +/fuzz/artifacts/ +/.idea/ +/.vscode/ +*.profraw +*.profdata +perf.data* diff --git a/Cargo.lock b/Cargo.lock index cdf77f5..69a7809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,10 +6,7 @@ version = 4 name = "ada-url" version = "4.0.0" dependencies = [ - "cc", "codspeed-criterion-compat", - "link_args", - "regex", "serde", "serde_json", "url", @@ -38,9 +35,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "approx" @@ -53,15 +50,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "cast" @@ -71,13 +68,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.65" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -89,9 +84,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "ciborium" @@ -122,18 +117,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstyle", "clap_lex", @@ -154,7 +149,7 @@ dependencies = [ "anyhow", "cc", "colored", - "getrandom 0.2.17", + "getrandom", "glob", "libc", "nix", @@ -229,20 +224,20 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "find-msvc-tools" @@ -270,23 +265,11 @@ dependencies = [ "wasi", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "half" @@ -307,12 +290,13 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -320,9 +304,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -333,9 +317,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -347,15 +331,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -367,15 +351,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -399,9 +383,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -433,16 +417,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -451,33 +425,27 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" - -[[package]] -name = "link_args" -version = "0.6.0" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7721e472624c9aaad27a5eb6b7c9c6045c7a396f2efb6dabaec1b640d5e89b" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags", "cfg-if", @@ -514,42 +482,36 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -559,9 +521,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -570,9 +532,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "same-file" @@ -585,9 +547,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -595,29 +557,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -634,9 +596,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "stable_deref_trait" @@ -656,9 +618,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -673,14 +646,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -736,15 +709,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "winapi-util" version = "0.1.11" @@ -842,23 +806,17 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" - [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -867,62 +825,62 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -931,9 +889,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -942,17 +900,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 2132a02..ca2639d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ authors = [ "Yagiz Nizipli ", "Daniel Lemire ", "LongYinan ", - "Boshen " + "Boshen ", + "Diaconu Radu-Mihai <52667211+countradooku@users.noreply.github.com>" ] version = "4.0.0" edition = "2024" @@ -16,7 +17,6 @@ categories = ["parser-implementations", "web-programming", "encoding", "parsing" repository = "https://github.com/ada-url/rust" homepage = "https://ada-url.com" license = "MIT OR Apache-2.0" -links = "ada-url" [[bench]] name = "parse" @@ -49,12 +49,10 @@ path = "bench/wpt.rs" harness = false [features] -default = ["bundled", "std"] -# compile and statically link the bundled C++ `ada` sources. Enabled by -# default; disable (`default-features = false`) to link an externally provided -# ada instead (see `ADA_LIB_DIR` / `ADA_LIB_NAME` in build.rs). +default = ["std"] +# Kept as no-op compatibility features for downstream manifests which selected +# the former C++ implementation's build modes. bundled = [] -# pass `cpp_set_stdlib("c++")` to `cc` libcpp = [] # enables serde serialization/deserialization support serde = ["dep:serde", "std"] @@ -70,11 +68,6 @@ url = "2" # Used by benchmarks serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -[build-dependencies] -cc = { version = "1.2", features = ["parallel"] } -link_args = "0.6" -regex = { version = "1.11", features = [] } - [package.metadata.docs.rs] features = ["serde"] diff --git a/README.md b/README.md index 9f90bea..8e52524 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # WHATWG URL parser for Rust -Fast [WHATWG URL Specification](https://url.spec.whatwg.org) compliant URL parser for Rust. -Well-tested and widely used by Node.js since [Node 18](https://nodejs.org/en/blog/release/v18.17.0). +Fast, memory-safe [WHATWG URL Specification](https://url.spec.whatwg.org) +compliant URL parser for Rust. The parser is implemented in Rust and keeps +unsafe code confined to audited, bounds-checked SIMD byte-search kernels. -The Ada library passes the full range of tests from the specification, across a wide range of platforms (e.g., Windows, Linux, macOS). -It fully supports the relevant [Unicode Technical Standard](https://www.unicode.org/reports/tr46/#ToUnicode). +The crate runs Ada's current URL, setter, IDNA, percent-encoding, and DNS-length +conformance fixtures. It supports the relevant +[Unicode Technical Standard](https://www.unicode.org/reports/tr46/#ToUnicode) +through UTS #46 processing. ## Usage @@ -14,67 +17,28 @@ Feel free to adjust it for exploring this crate further. ### Features -**std:** Functionalities that require `std`. -This feature is enabled by default, set `no-default-features` to `true` if you want `no-std`. +**std:** Enables standard-library integrations. This feature is enabled by +default; set `default-features = false` for `no_std` plus `alloc`. -**serde:** Allow `Url` to work with `serde`. This feature is disabled by default. Enabling this feature without `std` would provide you only `Serialize`. -Enabling this feature and `std` would provide you both `Serialize` and `Deserialize`. +**serde:** Implements `Serialize` and `Deserialize` for `Url` and +`UrlSearchParams`. This feature is disabled by default and enables `std`. -**libcpp:** Build `ada-url` with `libc++`. This feature is disabled by default. -Enabling this feature without `libc++` installed would cause compile error. +The former `bundled` and `libcpp` feature names remain as no-ops so existing +downstream manifests continue to resolve after the move away from the C++ build. -**bundled:** Compile and statically link the bundled C++ `ada` sources. This -feature is **enabled by default**. See "Linking against an external ada" below -for how to disable it. - -### Linking against an external ada - -By default (the `bundled` feature) the build script compiles the bundled C++ -`ada` sources and links them statically. If ada is already provided by your -host build system (e.g. Bazel, CMake, or a distribution package), compiling the -bundled copy would produce a second, duplicate copy of ada in the final binary. - -Disable the `bundled` feature to skip the bundled build and link against an -external ada instead: - -```toml -# Cargo.toml — re-enable `std` since disabling default features also drops it -ada-url = { version = "4", default-features = false, features = ["std"] } -``` - -When `bundled` is disabled, these optional environment variables configure how -the external ada is linked: - -| Variable | Description | -| ------------- | -------------------------------------------------------------------------------------------------- | -| `ADA_LIB_DIR` | Optional directory added to the linker search path (`cargo:rustc-link-search=native`). | -| `ADA_LIB_NAME`| Optional library to link, passed verbatim to `cargo:rustc-link-lib` (e.g. `ada` or `static=ada`). | +### Performance -If neither is provided, no link directive is emitted, leaving it to the host -build system to provide the ada symbols at final link time. The external ada -must expose the same C ABI (`ada_*`) and be ABI-compatible with this crate's -version. +The parser uses a single normalized buffer with compact component offsets, +plus conservative fast paths for common normalized URLs. Run the included +Criterion comparisons against the `url` crate with: ```sh -# Link against a system-installed libada in /usr/local/lib -ADA_LIB_DIR=/usr/local/lib ADA_LIB_NAME=ada \ - cargo build --no-default-features --features std +cargo bench --bench parse +cargo bench --bench wpt ``` -### Performance - -Ada is fast. The benchmark below shows **3.49 times** faster URL parsing compared to `url` - -```text -can_parse/ada_url time: [1.2109 µs 1.2121 µs 1.2133 µs] - thrpt: [635.09 MiB/s 635.75 MiB/s 636.38 MiB/s] - -parse/ada_url time: [2.0124 µs 2.0157 µs 2.0190 µs] - thrpt: [381.67 MiB/s 382.28 MiB/s 382.91 MiB/s] - -parse/url time: [7.0530 µs 7.0597 µs 7.0666 µs] - thrpt: [109.04 MiB/s 109.15 MiB/s 109.25 MiB/s] -``` +See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for storage invariants, +fallback boundaries, and the performance policy. ### Implemented traits @@ -110,7 +74,7 @@ just all **Skipping features:** ```sh -just all --skip=libcpp,serde +just all --skip=serde ``` ## License diff --git a/bench/scheme.rs b/bench/scheme.rs index 2f8b833..ee34be4 100644 --- a/bench/scheme.rs +++ b/bench/scheme.rs @@ -128,7 +128,7 @@ pub fn bench_scheme_naive(c: &mut Criterion) { b.iter(|| { let mut count = 0usize; for &s in &strings { - if hash_map.get(black_box(s)).is_some() { + if hash_map.contains_key(black_box(s)) { count += 1; } } @@ -152,7 +152,7 @@ pub fn bench_scheme_naive(c: &mut Criterion) { b.iter(|| { let mut count = 0usize; for &s in &strings { - if btree_map.get(black_box(s)).is_some() { + if btree_map.contains_key(black_box(s)) { count += 1; } } diff --git a/bench/search_params.rs b/bench/search_params.rs index e96c98f..190f3a7 100644 --- a/bench/search_params.rs +++ b/bench/search_params.rs @@ -27,11 +27,7 @@ pub fn url_search_params_bench(c: &mut Criterion) { if let Ok(parsed) = Url::parse(black_box(url), None) { let search = parsed.search(); // Strip leading '?' if present, matching C++ url->get_search() behavior - let search_str = if search.starts_with('?') { - &search[1..] - } else { - search - }; + let search_str = search.strip_prefix('?').unwrap_or(search); if let Ok(params) = UrlSearchParams::parse(search_str) { param_count += params.len(); } diff --git a/bench/wpt.rs b/bench/wpt.rs index 6b02b66..ccc2523 100644 --- a/bench/wpt.rs +++ b/bench/wpt.rs @@ -88,15 +88,12 @@ pub fn wpt_bench_ada_url(c: &mut Criterion) { let parsed = if !base.is_empty() { match Url::parse(black_box(base.as_str()), None) { Ok(base_url) => { - // Pass the base URL's href string as the base for input parsing. - // This is equivalent to using a pre-parsed base pointer in C++. - let base_href = base_url.href().to_owned(); - Url::parse(black_box(input.as_str()), Some(&base_href)) + Url::parse_with_url_base(black_box(input.as_str()), Some(&base_url)) } Err(_) => continue, } } else { - Url::parse(black_box(input.as_str()), None) + Url::parse_with_url_base(black_box(input.as_str()), None) }; if let Ok(url) = parsed { href_size += url.href().len(); diff --git a/build.rs b/build.rs deleted file mode 100644 index 88309ac..0000000 --- a/build.rs +++ /dev/null @@ -1,256 +0,0 @@ -use regex::Regex; -use std::fmt::{Display, Formatter}; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::{env, fmt}; - -#[derive(Clone, Debug)] -pub struct Target { - pub architecture: String, - pub vendor: String, - pub system: Option, - pub abi: Option, -} - -impl Target { - pub fn as_strs(&self) -> (&str, &str, Option<&str>, Option<&str>) { - ( - self.architecture.as_str(), - self.vendor.as_str(), - self.system.as_deref(), - self.abi.as_deref(), - ) - } -} - -impl Display for Target { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}-{}", &self.architecture, &self.vendor)?; - - if let Some(ref system) = self.system { - write!(f, "-{}", system) - } else { - Ok(()) - }?; - - if let Some(ref abi) = self.abi { - write!(f, "-{}", abi) - } else { - Ok(()) - } - } -} - -pub fn ndk() -> String { - env::var("ANDROID_NDK").expect("ANDROID_NDK variable not set") -} - -pub fn target_arch(arch: &str) -> &str { - match arch { - "armv7" => "arm", - "aarch64" => "arm64", - "i686" => "x86", - arch => arch, - } -} - -fn host_tag() -> &'static str { - // Because this is part of build.rs, the target_os is actually the host system - if cfg!(target_os = "windows") { - "windows-x86_64" - } else if cfg!(target_os = "linux") { - "linux-x86_64" - } else if cfg!(target_os = "macos") { - "darwin-x86_64" - } else { - panic!("host os is not supported") - } -} - -/// Get NDK major version from source.properties -fn ndk_major_version(ndk_dir: &Path) -> u32 { - // Capture version from the line with Pkg.Revision - let re = Regex::new(r"Pkg.Revision = (\d+)\.(\d+)\.(\d+)").unwrap(); - // There's a source.properties file in the ndk directory, which contains - let mut source_properties = - File::open(ndk_dir.join("source.properties")).expect("Couldn't open source.properties"); - let mut buf = String::new(); - source_properties - .read_to_string(&mut buf) - .expect("Could not read source.properties"); - // Capture version info - let captures = re - .captures(&buf) - .expect("source.properties did not match the regex"); - // Capture 0 is the whole line of text - captures[1].parse().expect("could not parse major version") -} - -fn main() { - // By default the bundled C++ `ada` sources are compiled and linked - // statically (the `bundled` feature, enabled by default). Disabling the - // `bundled` feature (e.g. `default-features = false`) skips that compile and - // instead links against an externally provided ada library. This is useful - // when ada is already provided by the host build system (e.g. Bazel, CMake, - // or a distro package), where compiling the bundled copy would otherwise - // produce a second, duplicate copy of ada in the final binary. - // - // When the `bundled` feature is disabled, these optional environment - // variables configure how the external ada is linked: - // - // ADA_LIB_DIR directory added to the link search path. - // ADA_LIB_NAME library to link, passed verbatim to `cargo:rustc-link-lib` - // (e.g. `ada` or `static=ada`). - // - // If neither is provided, no link directive is emitted, leaving it to the - // host build system to provide the ada symbols at final link time. The - // external ada must expose the same C ABI (`ada_*`) and be ABI-compatible - // with this crate's version. - println!("cargo:rerun-if-env-changed=ADA_LIB_DIR"); - println!("cargo:rerun-if-env-changed=ADA_LIB_NAME"); - if !cfg!(feature = "bundled") { - if let Some(dir) = env::var_os("ADA_LIB_DIR") { - println!( - "cargo:rustc-link-search=native={}", - Path::new(&dir).display() - ); - } - if let Ok(name) = env::var("ADA_LIB_NAME") - && !name.is_empty() - { - println!("cargo:rustc-link-lib={name}"); - } - return; - } - - let target_str = env::var("TARGET").unwrap(); - let target: Vec = target_str.split('-').map(|s| s.into()).collect(); - assert!(target.len() >= 2, "Failed to parse TARGET {}", target_str); - - let abi = if target.len() > 3 { - Some(target[3].clone()) - } else { - None - }; - - let system = if target.len() > 2 { - Some(target[2].clone()) - } else { - None - }; - - let target = Target { - architecture: target[0].clone(), - vendor: target[1].clone(), - system, - abi, - }; - - let mut build = cc::Build::new(); - build - .file("./deps/ada.cpp") - .include("./deps") - .cpp(true) - .std("c++20") - .define("ADA_INCLUDE_URL_PATTERN", "0"); - - let compile_target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH"); - let compile_target_os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS"); - let compile_target_feature = env::var("CARGO_CFG_TARGET_FEATURE"); - // Except for Emscripten target (which emulates POSIX environment), compile to Wasm via WASI SDK - // which is currently the only standalone provider of stdlib for compilation of C/C++ libraries. - - match target.system.as_deref() { - Some("android" | "androideabi") => { - let ndk = ndk(); - let major = ndk_major_version(Path::new(&ndk)); - if major < 22 { - build - .flag(format!("--sysroot={}/sysroot", ndk)) - .flag(format!( - "-isystem{}/sources/cxx-stl/llvm-libc++/include", - ndk - )); - } else { - // NDK versions >= 22 have the sysroot in the llvm prebuilt by - let host_toolchain = format!("{}/toolchains/llvm/prebuilt/{}", ndk, host_tag()); - // sysroot is stored in the prebuilt llvm, under the host - build.flag(format!("--sysroot={}/sysroot", host_toolchain)); - } - } - _ => { - if compile_target_arch.starts_with("wasm") && compile_target_os != "emscripten" { - let wasi_sdk = env::var("WASI_SDK").unwrap_or_else(|_| "/opt/wasi-sdk".to_owned()); - assert!( - Path::new(&wasi_sdk).exists(), - "WASI SDK not found at {wasi_sdk}" - ); - build.compiler(format!("{wasi_sdk}/bin/clang++")); - let wasi_sysroot_lib = match compile_target_feature { - Ok(compile_target_feature) if compile_target_feature.contains("atomics") => { - "wasm32-wasip1-threads" - } - _ => "wasm32-wasip1", - }; - let sysroot_lib = format!("{wasi_sdk}/share/wasi-sysroot/lib/{wasi_sysroot_lib}"); - // libc and its startup objects (crt1) live directly in this directory. - println!("cargo:rustc-link-search={sysroot_lib}"); - // wasi-sdk 27+ split libc++/libc++abi into `eh/` (exceptions) and - // `noeh/` (no exceptions) subdirectories. We build with - // `-fno-exceptions`, so add the `noeh` variant to the search path - // when it exists; older wasi-sdk keeps these libs flat in `sysroot_lib`. - let noeh_lib = format!("{sysroot_lib}/noeh"); - if Path::new(&noeh_lib).exists() { - println!("cargo:rustc-link-search={noeh_lib}"); - } - // Wasm exceptions are new and not yet supported by WASI SDK. - build.flag("-fno-exceptions"); - // WASI SDK only has libc++ available. - build.cpp_set_stdlib("c++"); - // Explicitly link C++ ABI to avoid linking errors (it takes care of C++ -> C "lowering"). - println!("cargo:rustc-link-lib=c++abi"); - // Because Ada is a pure parsing library that doesn't need any OS syscalls, - // it's also possible to compile it to wasm32-unknown-unknown. - // This still requires WASI SDK for libc & libc++, but then we need a few hacks / overrides to get a pure Wasm w/o imports instead. - if compile_target_os == "unknown" { - build.target("wasm32-wasip1"); - println!("cargo:rustc-link-lib=c"); - build.file("./deps/wasi_to_unknown.cpp"); - } - } - - let compiler = build.get_compiler(); - // Note: it's possible to use Clang++ explicitly on Windows as well, so this check - // should be specifically for "is target compiler MSVC" and not "is target OS Windows". - if compiler.is_like_msvc() { - build.static_crt(true); - link_args::windows! { - unsafe { - no_default_lib( - "libcmt.lib", - ); - } - } - } else if compiler.is_like_clang() && cfg!(feature = "libcpp") { - build.cpp_set_stdlib("c++"); - } - } - } - - // Optionally compile the bundled C++ (ada) with a sanitizer. This is used - // by the AddressSanitizer CI job so that FFI allocations (e.g. the result - // `ada_parse` always heap-allocates) are instrumented and leaks/errors - // inside ada itself are caught. Enable with `ADA_SANITIZE=address` and a - // matching `RUSTFLAGS=-Zsanitizer=address`; use a clang toolchain (CC/CXX) - // so the C++ shares the LLVM sanitizer runtime that rustc links in. - println!("cargo:rerun-if-env-changed=ADA_SANITIZE"); - if let Ok(sanitizer) = env::var("ADA_SANITIZE") - && !sanitizer.is_empty() - { - build.flag(format!("-fsanitize={sanitizer}")); - build.flag("-fno-omit-frame-pointer"); - } - - build.compile("ada"); -} diff --git a/deps/ada.cpp b/deps/ada.cpp deleted file mode 100644 index fd55888..0000000 --- a/deps/ada.cpp +++ /dev/null @@ -1,15486 +0,0 @@ -/* auto-generated on 2026-07-27 16:10:09 -0400. Do not edit! */ -/* begin file src/ada.cpp */ -#include "ada.h" -/* begin file src/checkers.cpp */ - -#include -#include -#include - -namespace ada::checkers { - -ada_really_inline constexpr bool is_ipv4(std::string_view view) noexcept { - // The string is not empty and does not contain upper case ASCII characters. - // - // Optimization. To be considered as a possible ipv4, the string must end - // with 'x' or a lowercase hex character. - // Most of the time, this will be false so this simple check will save a lot - // of effort. - // If the address ends with a dot, we need to prune it (special case). - if (view.ends_with('.')) { - view.remove_suffix(1); - if (view.empty()) { - return false; - } - } - char last_char = view.back(); - bool possible_ipv4 = (last_char >= '0' && last_char <= '9') || - (last_char >= 'a' && last_char <= 'f') || - last_char == 'x'; - if (!possible_ipv4) { - return false; - } - // From the last character, find the last dot. - size_t last_dot = view.rfind('.'); - if (last_dot != std::string_view::npos) { - // We have at least one dot. - view.remove_prefix(last_dot + 1); - } - /** Optimization opportunity: we have basically identified the last number of - the ipv4 if we return true here. We might as well parse it and have at - least one number parsed when we get to parse_ipv4. */ - if (std::ranges::all_of(view, ada::checkers::is_digit)) { - return true; - } - // It could be hex (0x), but not if there is a single character. - if (view.size() == 1) { - return false; - } - // It must start with 0x. - if (!view.starts_with("0x")) { - return false; - } - // We must allow "0x". - if (view.size() == 2) { - return true; - } - // We have 0x followed by some characters, we need to check that they are - // hexadecimals. - view.remove_prefix(2); - return std::ranges::all_of(view, ada::unicode::is_lowercase_hex); -} - -// for use with path_signature, we include all characters that need percent -// encoding. -static constexpr std::array path_signature_table = - []() consteval { - std::array result{}; - for (size_t i = 0; i < 256; i++) { - if (i <= 0x20 || i == 0x22 || i == 0x23 || i == 0x3c || i == 0x3e || - i == 0x3f || i == 0x5e || i == 0x60 || i == 0x7b || i == 0x7d || - i > 0x7e) { - result[i] = 1; - } else if (i == 0x25) { - result[i] = 8; - } else if (i == 0x2e) { - result[i] = 4; - } else if (i == 0x5c) { - result[i] = 2; - } else { - result[i] = 0; - } - } - return result; - }(); - -ada_really_inline constexpr uint8_t path_signature( - std::string_view input) noexcept { - // The path percent-encode set is the query percent-encode set and U+003F (?), - // U+0060 (`), U+007B ({), and U+007D (}). The query percent-encode set is the - // C0 control percent-encode set and U+0020 SPACE, U+0022 ("), U+0023 (#), - // U+003C (<), and U+003E (>). The C0 control percent-encode set are the C0 - // controls and all code points greater than U+007E (~). - size_t i = 0; - uint8_t accumulator{}; - for (; i + 7 < input.size(); i += 8) { - accumulator |= uint8_t(path_signature_table[uint8_t(input[i])] | - path_signature_table[uint8_t(input[i + 1])] | - path_signature_table[uint8_t(input[i + 2])] | - path_signature_table[uint8_t(input[i + 3])] | - path_signature_table[uint8_t(input[i + 4])] | - path_signature_table[uint8_t(input[i + 5])] | - path_signature_table[uint8_t(input[i + 6])] | - path_signature_table[uint8_t(input[i + 7])]); - } - for (; i < input.size(); i++) { - accumulator |= uint8_t(path_signature_table[uint8_t(input[i])]); - } - return accumulator; -} - -ada_really_inline constexpr bool verify_dns_length( - std::string_view input) noexcept { - if (input.empty()) return false; - if (input.back() == '.') { - if (input.size() > 254) return false; - } else if (input.size() > 253) - return false; - - size_t start = 0; - while (start < input.size()) { - auto dot_location = input.find('.', start); - // If not found, it's likely the end of the domain - if (dot_location == std::string_view::npos) dot_location = input.size(); - - auto label_size = dot_location - start; - if (label_size > 63 || label_size == 0) return false; - - start = dot_location + 1; - } - - return true; -} - -} // namespace ada::checkers -/* end file src/checkers.cpp */ -/* begin file src/unicode.cpp */ - - -ADA_PUSH_DISABLE_ALL_WARNINGS -/* begin file src/ada_idna.cpp */ -/* auto-generated on 2026-07-12 20:34:08 -0400. Do not edit! */ -/* begin file src/idna.cpp */ -/* begin file src/unicode_transcoding.cpp */ -/* begin file include/ada/idna/unicode_transcoding.h */ -#ifndef ADA_IDNA_UNICODE_TRANSCODING_H -#define ADA_IDNA_UNICODE_TRANSCODING_H - -#include -#include - -namespace ada::idna { - -size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output); - -size_t utf8_length_from_utf32(const char32_t* buf, size_t len); - -size_t utf32_length_from_utf8(const char* buf, size_t len); - -size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output); - -} // namespace ada::idna - -#endif // ADA_IDNA_UNICODE_TRANSCODING_H -/* end file include/ada/idna/unicode_transcoding.h */ - -#include -#include -#include - -namespace ada::idna { - -size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output) { - const uint8_t* data = reinterpret_cast(buf); - size_t pos = 0; - const char32_t* start{utf32_output}; - while (pos < len) { - // try to convert the next block of 16 ASCII bytes - if (pos + 16 <= len) { // if it is safe to read 16 more - // bytes, check that they are ascii - uint64_t v1; - std::memcpy(&v1, data + pos, sizeof(uint64_t)); - uint64_t v2; - std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t)); - uint64_t v{v1 | v2}; - if ((v & 0x8080808080808080) == 0) { - size_t final_pos = pos + 16; - while (pos < final_pos) { - *utf32_output++ = char32_t(buf[pos]); - pos++; - } - continue; - } - } - uint8_t leading_byte = data[pos]; // leading byte - if (leading_byte < 0b10000000) { - // converting one ASCII byte !!! - *utf32_output++ = char32_t(leading_byte); - pos++; - } else if ((leading_byte & 0b11100000) == 0b11000000) { - // We have a two-byte UTF-8 - if (pos + 1 >= len) { - return 0; - } // minimal bound checking - if ((data[pos + 1] & 0b11000000) != 0b10000000) { - return 0; - } - // range check - uint32_t code_point = - (leading_byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111); - if (code_point < 0x80 || 0x7ff < code_point) { - return 0; - } - *utf32_output++ = char32_t(code_point); - pos += 2; - } else if ((leading_byte & 0b11110000) == 0b11100000) { - // We have a three-byte UTF-8 - if (pos + 2 >= len) { - return 0; - } // minimal bound checking - - if ((data[pos + 1] & 0b11000000) != 0b10000000) { - return 0; - } - if ((data[pos + 2] & 0b11000000) != 0b10000000) { - return 0; - } - // range check - uint32_t code_point = (leading_byte & 0b00001111) << 12 | - (data[pos + 1] & 0b00111111) << 6 | - (data[pos + 2] & 0b00111111); - if (code_point < 0x800 || 0xffff < code_point || - (0xd7ff < code_point && code_point < 0xe000)) { - return 0; - } - *utf32_output++ = char32_t(code_point); - pos += 3; - } else if ((leading_byte & 0b11111000) == 0b11110000) { // 0b11110000 - // we have a 4-byte UTF-8 word. - if (pos + 3 >= len) { - return 0; - } // minimal bound checking - if ((data[pos + 1] & 0b11000000) != 0b10000000) { - return 0; - } - if ((data[pos + 2] & 0b11000000) != 0b10000000) { - return 0; - } - if ((data[pos + 3] & 0b11000000) != 0b10000000) { - return 0; - } - - // range check - uint32_t code_point = (leading_byte & 0b00000111) << 18 | - (data[pos + 1] & 0b00111111) << 12 | - (data[pos + 2] & 0b00111111) << 6 | - (data[pos + 3] & 0b00111111); - if (code_point <= 0xffff || 0x10ffff < code_point) { - return 0; - } - *utf32_output++ = char32_t(code_point); - pos += 4; - } else { - return 0; - } - } - return utf32_output - start; -} - -size_t utf8_length_from_utf32(const char32_t* buf, size_t len) { - // We are not BOM aware. - const uint32_t* p = reinterpret_cast(buf); - size_t counter{0}; - for (size_t i = 0; i != len; ++i) { - ++counter; // ASCII - counter += static_cast(p[i] > 0x7F); // two-byte - counter += static_cast(p[i] > 0x7FF); // three-byte - counter += static_cast(p[i] > 0xFFFF); // four-bytes - } - return counter; -} - -size_t utf32_length_from_utf8(const char* buf, size_t len) { - const int8_t* p = reinterpret_cast(buf); - return std::count_if(p, std::next(p, len), [](int8_t c) { - // -65 is 0b10111111, anything larger in two-complement's - // should start a new code point. - return c > -65; - }); -} - -size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output) { - const uint32_t* data = reinterpret_cast(buf); - size_t pos = 0; - const char* start{utf8_output}; - while (pos < len) { - // try to convert the next block of 2 ASCII characters - if (pos + 2 <= len) { // if it is safe to read 8 more - // bytes, check that they are ascii - uint64_t v; - std::memcpy(&v, data + pos, sizeof(uint64_t)); - if ((v & 0xFFFFFF80FFFFFF80) == 0) { - *utf8_output++ = char(buf[pos]); - *utf8_output++ = char(buf[pos + 1]); - pos += 2; - continue; - } - } - uint32_t word = data[pos]; - if ((word & 0xFFFFFF80) == 0) { - // will generate one UTF-8 bytes - *utf8_output++ = char(word); - pos++; - } else if ((word & 0xFFFFF800) == 0) { - // will generate two UTF-8 bytes - // we have 0b110XXXXX 0b10XXXXXX - *utf8_output++ = char((word >> 6) | 0b11000000); - *utf8_output++ = char((word & 0b111111) | 0b10000000); - pos++; - } else if ((word & 0xFFFF0000) == 0) { - // will generate three UTF-8 bytes - // we have 0b1110XXXX 0b10XXXXXX 0b10XXXXXX - if (word >= 0xD800 && word <= 0xDFFF) { - return 0; - } - *utf8_output++ = char((word >> 12) | 0b11100000); - *utf8_output++ = char(((word >> 6) & 0b111111) | 0b10000000); - *utf8_output++ = char((word & 0b111111) | 0b10000000); - pos++; - } else { - // will generate four UTF-8 bytes - // we have 0b11110XXX 0b10XXXXXX 0b10XXXXXX - // 0b10XXXXXX - if (word > 0x10FFFF) { - return 0; - } - *utf8_output++ = char((word >> 18) | 0b11110000); - *utf8_output++ = char(((word >> 12) & 0b111111) | 0b10000000); - *utf8_output++ = char(((word >> 6) & 0b111111) | 0b10000000); - *utf8_output++ = char((word & 0b111111) | 0b10000000); - pos++; - } - } - return utf8_output - start; -} -} // namespace ada::idna -/* end file src/unicode_transcoding.cpp */ -/* begin file src/mapping.cpp */ -/* begin file include/ada/idna/mapping.h */ -#ifndef ADA_IDNA_MAPPING_H -#define ADA_IDNA_MAPPING_H - -#include -#include - -namespace ada::idna { - -// If the input is ascii, then the mapping is just -> lower case. -void ascii_map(char* input, size_t length); -// Map the characters according to IDNA, returning the empty string on error. -std::u32string map(std::u32string_view input); -// Map into an existing buffer (cleared on entry). Returns false if any code -// point is disallowed. Reusing the buffer avoids repeated heap allocations -// when called in a loop over multiple labels. -bool map(std::u32string_view input, std::u32string& out); - -} // namespace ada::idna - -#endif -/* end file include/ada/idna/mapping.h */ - -#include -#include -#include -#include - -/* begin file src/table_store.hpp */ -// Runtime store for compressed Unicode/IDNA tables. -// Large tables are DEFLATE-compressed (read-only) and expanded once on first -// use into a heap buffer so the working set does not bloat the on-disk binary. -// Hot-path lookups use the same O(1) multi-stage layout as the pre-compression -// code; compression only affects on-disk size and one-time init. -// -// Thread-safe without mutex: atomic CAS + spin-wait. ensure_tables() returns -// false if initialization fails (OOM, corrupt blob); callers must treat that -// as a hard error and not touch table pointers. - -/* begin file src/raw_inflate.hpp */ -// Minimal raw DEFLATE inflater (RFC 1951), enough for zlib raw streams. -// Returns number of output bytes written, or 0 on failure. -#include -#include - -namespace ada::idna::deflate { - -struct BitReader { - const uint8_t* p; - const uint8_t* end; - uint32_t acc = 0; - int bits = 0; - - explicit BitReader(const uint8_t* data, size_t len) - : p(data), end(data + len) {} - - bool ensure(int n) { - while (bits < n) { - if (p >= end) return false; - acc |= uint32_t(*p++) << bits; - bits += 8; - } - return true; - } - uint32_t get(int n) { - if (!ensure(n)) return UINT32_MAX; - uint32_t v = acc & ((1u << n) - 1); - acc >>= n; - bits -= n; - return v; - } - void align_byte() { - acc >>= (bits & 7); - bits -= (bits & 7); - } -}; - -struct Huff { - // canonical codes: for each length, list of symbols - // Fast path: table for codes up to max_bits - static constexpr int MAXBITS = 15; - uint8_t lengths[288]{}; - int counts[MAXBITS + 1]{}; - int first_code[MAXBITS + 1]{}; - int16_t symbols[288]{}; - int nsymbols = 0; - int max_bits = 0; - - // Base index into symbols[] for each code length (used by decode). - int base_idx[MAXBITS + 1]{}; - - bool build(const uint8_t* lens, int n) { - std::memset(counts, 0, sizeof(counts)); - std::memset(first_code, 0, sizeof(first_code)); - std::memset(base_idx, 0, sizeof(base_idx)); - nsymbols = n; - max_bits = 0; - for (int i = 0; i < n; i++) { - lengths[i] = lens[i]; - if (lens[i]) { - counts[lens[i]]++; - if (lens[i] > max_bits) max_bits = lens[i]; - } - } - // Canonical first code for each bit length (RFC 1951). - int code = 0; - for (int bits = 1; bits <= MAXBITS; bits++) { - code = (code + counts[bits - 1]) << 1; - first_code[bits] = code; - } - int idx = 0; - for (int bits = 1; bits <= max_bits; bits++) { - base_idx[bits] = idx; - // assign symbols in symbol order for codes of this length - for (int sym = 0; sym < n; sym++) { - if (lengths[sym] == bits) { - symbols[idx++] = static_cast(sym); - } - } - } - return true; - } - - int decode(BitReader& br) const { - if (max_bits == 0) return -1; - int code = 0; - for (int len = 1; len <= max_bits; len++) { - uint32_t b = br.get(1); - if (b == UINT32_MAX) return -1; - code = (code << 1) | int(b); - int first = first_code[len]; - int cnt = counts[len]; - if (cnt && code - first < cnt) { - return symbols[base_idx[len] + (code - first)]; - } - } - return -1; - } -}; - -// Fixed Huffman tables are built once at dynamic initialization (before main / -// single-threaded), so concurrent first-use of inflate cannot race. -inline Huff make_fixed_litlen_huff() { - Huff h; - uint8_t lens[288]; - // Fixed Huffman: 0-143:8, 144-255:9, 256-279:7, 280-287:8 - for (int i = 0; i <= 143; i++) lens[i] = 8; - for (int i = 144; i <= 255; i++) lens[i] = 9; - for (int i = 256; i <= 279; i++) lens[i] = 7; - for (int i = 280; i <= 287; i++) lens[i] = 8; - h.build(lens, 288); - return h; -} - -inline Huff make_fixed_dist_huff() { - Huff h; - uint8_t lens[32]; - for (int i = 0; i < 32; i++) lens[i] = 5; - h.build(lens, 32); - return h; -} - -// NOLINTNEXTLINE(cert-err58-cpp) -- intentional process-lifetime tables -inline const Huff kFixedLitLenHuff = make_fixed_litlen_huff(); -// NOLINTNEXTLINE(cert-err58-cpp) -inline const Huff kFixedDistHuff = make_fixed_dist_huff(); - -inline int fixed_litlen(BitReader& br) { return kFixedLitLenHuff.decode(br); } - -inline int fixed_dist(BitReader& br) { return kFixedDistHuff.decode(br); } - -// length and distance base tables -static const uint16_t len_base[29] = { - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, - 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; -static const uint8_t len_extra[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, - 4, 4, 4, 4, 5, 5, 5, 5, 0}; -static const uint16_t dist_base[30] = { - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, - 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, - 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; -static const uint8_t dist_extra[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, - 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, - 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; - -inline size_t inflate_raw(const uint8_t* src, size_t src_len, uint8_t* dst, - size_t dst_cap) { - BitReader br(src, src_len); - size_t out = 0; - for (;;) { - uint32_t bfinal = br.get(1); - uint32_t btype = br.get(2); - if (bfinal == UINT32_MAX || btype == UINT32_MAX) return 0; - if (btype == 0) { - // stored - br.align_byte(); - if (!br.ensure(16)) return 0; - // read 16+16 from bit buffer awkwardly - use byte path - // Align: bits is multiple of 8 - // Get len from remaining bits then bytes - uint32_t len = br.get(16); - uint32_t nlen = br.get(16); - if (len == UINT32_MAX || nlen == UINT32_MAX) return 0; - if ((len ^ 0xFFFF) != nlen) return 0; - // After get, may have bits in acc from previous - for stored, should be - // byte aligned Copy len bytes from p - if (br.bits != 0) { - // leftover bits should be 0 if aligned - } - if (size_t(br.end - br.p) < len) return 0; - if (out + len > dst_cap) return 0; - std::memcpy(dst + out, br.p, len); - br.p += len; - out += len; - } else if (btype == 1 || btype == 2) { - Huff lit, dist; - if (btype == 1) { - // use fixed via functions - } else { - // dynamic - uint32_t hlit = br.get(5); - if (hlit == UINT32_MAX) return 0; - hlit += 257; - uint32_t hdist = br.get(5); - if (hdist == UINT32_MAX) return 0; - hdist += 1; - uint32_t hclen = br.get(4); - if (hclen == UINT32_MAX) return 0; - hclen += 4; - static const int order[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, - 11, 4, 12, 3, 13, 2, 14, 1, 15}; - uint8_t clen[19]{}; - for (uint32_t i = 0; i < hclen; i++) { - uint32_t v = br.get(3); - if (v == UINT32_MAX) return 0; - clen[order[i]] = uint8_t(v); - } - Huff cl; - if (!cl.build(clen, 19)) return 0; - uint8_t lens[288 + 32]{}; - uint32_t n = hlit + hdist; - for (uint32_t i = 0; i < n;) { - int sym = cl.decode(br); - if (sym < 0) return 0; - if (sym < 16) { - lens[i++] = uint8_t(sym); - } else if (sym == 16) { - uint32_t rep = br.get(2); - if (rep == UINT32_MAX) return 0; - rep += 3; - if (i == 0) return 0; - uint8_t v = lens[i - 1]; - while (rep--) lens[i++] = v; - } else if (sym == 17) { - uint32_t rep = br.get(3); - if (rep == UINT32_MAX) return 0; - rep += 3; - while (rep--) lens[i++] = 0; - } else if (sym == 18) { - uint32_t rep = br.get(7); - if (rep == UINT32_MAX) return 0; - rep += 11; - while (rep--) lens[i++] = 0; - } else - return 0; - } - if (!lit.build(lens, int(hlit))) return 0; - if (!dist.build(lens + hlit, int(hdist))) return 0; - } - for (;;) { - int sym; - if (btype == 1) - sym = fixed_litlen(br); - else - sym = lit.decode(br); - if (sym < 0) return 0; - if (sym < 256) { - if (out >= dst_cap) return 0; - dst[out++] = uint8_t(sym); - } else if (sym == 256) { - break; - } else { - int len_code = sym - 257; - if (len_code < 0 || len_code > 28) return 0; - uint32_t extra = br.get(len_extra[len_code]); - if (len_extra[len_code] && extra == UINT32_MAX) return 0; - uint32_t length = - len_base[len_code] + (len_extra[len_code] ? extra : 0); - int dsym; - if (btype == 1) - dsym = fixed_dist(br); - else - dsym = dist.decode(br); - if (dsym < 0 || dsym > 29) return 0; - uint32_t dextra = br.get(dist_extra[dsym]); - if (dist_extra[dsym] && dextra == UINT32_MAX) return 0; - uint32_t distance = dist_base[dsym] + (dist_extra[dsym] ? dextra : 0); - if (distance > out || out + length > dst_cap) return 0; - for (uint32_t k = 0; k < length; k++) { - dst[out] = dst[out - distance]; - out++; - } - } - } - } else { - return 0; // reserved - } - if (bfinal) break; - } - return out; -} - -} // namespace ada::idna::deflate -/* end file src/raw_inflate.hpp */ -/* begin file src/table_blob.inc */ -// Auto-generated by scripts/pack_tables.py - do not edit. -// Compressed Unicode/IDNA tables (raw DEFLATE). -// clang-format off -#ifndef ADA_IDNA_TABLE_BLOB_H -#define ADA_IDNA_TABLE_BLOB_H -#include -#include -namespace ada::idna::table_blob { -constexpr size_t uncompressed_size = 224608; -constexpr size_t compressed_size = 62377; -constexpr uint32_t uncompressed_crc32 = 0x6F4A6B2Eu; -constexpr size_t decomposition_block_rows = 67; -constexpr size_t decomposition_block_cols = 257; -constexpr size_t ccc_block_rows = 67; -constexpr size_t ccc_block_cols = 256; -constexpr size_t composition_block_rows = 67; -constexpr size_t composition_block_cols = 257; -constexpr size_t id_continue_count = 1418; -constexpr size_t id_start_count = 776; -constexpr size_t dir_table_count = 1449; -constexpr size_t combining_range_count = 290; -// Offsets into the decompressed buffer: -constexpr size_t off_idna_stage1 = 0; -constexpr size_t count_idna_stage1 = 3282; -constexpr size_t off_idna_stage2 = 6564; -constexpr size_t count_idna_stage2 = 11456; -constexpr size_t off_idna_bool_blocks = 29480; -constexpr size_t count_idna_bool_blocks = 225; -constexpr size_t off_idna_utf8_mappings = 31280; -constexpr size_t count_idna_utf8_mappings = 17383; -constexpr size_t off_decomposition_index = 48663; -constexpr size_t count_decomposition_index = 4352; -constexpr size_t off_decomposition_block = 53016; -constexpr size_t count_decomposition_block = 17219; -constexpr size_t off_decomposition_data = 87456; -constexpr size_t count_decomposition_data = 9102; -constexpr size_t off_ccc_index = 123864; -constexpr size_t count_ccc_index = 4352; -constexpr size_t off_ccc_block = 128216; -constexpr size_t count_ccc_block = 17152; -constexpr size_t off_composition_index = 145368; -constexpr size_t count_composition_index = 4352; -constexpr size_t off_composition_block = 149720; -constexpr size_t count_composition_block = 17219; -constexpr size_t off_composition_data = 184160; -constexpr size_t count_composition_data = 1883; -constexpr size_t off_id_continue_flat = 191692; -constexpr size_t count_id_continue_flat = 2836; -constexpr size_t off_id_start_flat = 203036; -constexpr size_t count_id_start_flat = 1552; -constexpr size_t off_dir_start = 209244; -constexpr size_t count_dir_start = 1449; -constexpr size_t off_dir_final = 215040; -constexpr size_t count_dir_final = 1449; -constexpr size_t off_dir_value = 220836; -constexpr size_t count_dir_value = 1449; -constexpr size_t off_combining_flat = 222288; -constexpr size_t count_combining_flat = 580; -alignas(8) inline constexpr uint8_t compressed[] = { -0xec,0x9c,0x09,0x40,0x53,0xc7,0xda,0xfe,0x5f,0x08,0x98,0x28,0x4b,0x4c,0xc2,0x12, -0x14,0x09,0x12,0x42,0x12,0x76,0x12,0x20,0xec,0x09,0xd0,0xdd,0x56,0xdb,0xda,0xdd, -0x5b,0x5b,0x97,0x6a,0xdd,0xad,0xb6,0xda,0xc5,0x96,0x23,0xbb,0xa2,0xe0,0xbe,0xb0, -0x8a,0xa8,0xa8,0x88,0x08,0xa8,0xec,0xcb,0x2d,0x2a,0x62,0xad,0x88,0xa8,0xdd,0xeb, -0x56,0x36,0x6d,0x6b,0x5b,0xdb,0xda,0xd6,0x96,0xfc,0x9f,0x73,0x12,0xd4,0xb6,0xf6, -0xb6,0xb7,0xcb,0xfd,0xbe,0xfb,0xfd,0x9b,0xe1,0x37,0x33,0xe7,0x9c,0x99,0x79,0xde, -0x79,0xcf,0x9c,0x73,0x66,0x4e,0x00,0x62,0x88,0x0c,0xc4,0x50,0x33,0x91,0x85,0xc1, -0x82,0xb1,0x68,0xb6,0x20,0x4b,0x83,0x25,0x63,0x49,0x4c,0xb3,0x25,0xf1,0x0c,0x3c, -0x86,0xd7,0xcc,0x23,0x2b,0x83,0x15,0x63,0xd5,0x6c,0x45,0xd6,0x16,0x8c,0x25,0x63, -0xb0,0x26,0x86,0xc7,0x58,0x31,0xd6,0xcc,0x20,0x86,0xcf,0x08,0x98,0xc1,0xcc,0x10, -0xc6,0x86,0x21,0x86,0xb1,0xb6,0x65,0x9a,0xad,0x69,0x90,0x61,0x90,0x1d,0x63,0xcf, -0x08,0x19,0x66,0xd0,0x50,0x46,0xc4,0x88,0x19,0x09,0xe3,0xc0,0x38,0x32,0x4e,0x8c, -0x33,0x23,0x65,0x5c,0x98,0xe6,0x41,0xc3,0x18,0xe2,0x1b,0xf8,0x0c,0xbf,0x99,0x4f, -0x82,0xe1,0xa8,0x47,0x8c,0x41,0xc0,0x08,0xa0,0x27,0x20,0x6e,0xcb,0x95,0x19,0xc1, -0xb8,0x31,0x32,0xc6,0x9d,0x19,0xc9,0xd0,0x60,0xd3,0xbe,0x1f,0x07,0x0f,0x46,0xce, -0x78,0x32,0x0a,0xc6,0x30,0xd8,0x8b,0x61,0x06,0xcb,0x19,0x25,0xa3,0x62,0xd4,0x8c, -0x37,0xe3,0xc3,0xf8,0x32,0x7e,0x8c,0x3f,0x13,0xc0,0x04,0xa2,0x54,0x10,0xd0,0x30, -0x5a,0x26,0x98,0x69,0x1e,0x1c,0xc2,0xd0,0x10,0xc3,0x10,0x66,0x08,0x54,0x86,0x90, -0x8d,0xc1,0x86,0xb1,0x69,0xb6,0x21,0x5b,0x83,0x2d,0x63,0xdb,0x6c,0x4b,0x76,0x06, -0xbb,0x50,0x86,0xb1,0x6b,0xb6,0x23,0x7b,0xd8,0x62,0x6f,0xd2,0x60,0xcc,0xa9,0x8e, -0x69,0xb6,0x27,0xa1,0x41,0x78,0x2b,0x3b,0x6e,0x15,0x18,0x61,0x33,0x5b,0x76,0x28, -0x31,0x61,0xa6,0xbe,0x0d,0x65,0x86,0x36,0x0f,0x25,0x51,0x38,0x63,0x10,0x45,0x30, -0x91,0xd8,0x13,0xc5,0x30,0xa2,0x66,0x11,0x89,0x0d,0x62,0x46,0xdc,0x2c,0x26,0x49, -0x34,0x63,0x90,0x30,0x92,0x66,0x09,0x39,0x18,0x1c,0x62,0x18,0xc6,0xa1,0xd9,0x81, -0x1c,0x0d,0x8e,0x8c,0x63,0xb3,0x23,0x39,0x19,0x9c,0x7e,0xab,0xee,0xdf,0xe1,0xef, -0xf0,0x77,0xf8,0x6b,0x82,0x9e,0x31,0x5c,0xcf,0xc7,0x32,0x8c,0x53,0xb3,0x53,0x1c, -0x43,0xce,0x06,0x67,0xc6,0xb9,0xd9,0x39,0x9e,0x41,0x9e,0xb9,0x0d,0xdc,0x0e,0xee, -0x60,0xee,0x64,0xee,0x42,0x7a,0x37,0x73,0x0f,0x43,0x52,0x83,0x74,0xd4,0xdf,0xde, -0xfb,0x0b,0xc3,0xbd,0xcc,0x7d,0xcc,0xe8,0xbf,0x03,0x17,0x18,0x69,0xb3,0x94,0x5c, -0x0c,0x2e,0x8c,0x4b,0xb3,0x0b,0x0d,0x33,0x0c,0x63,0x86,0x35,0x0f,0xa3,0xe1,0x86, -0xe1,0xcc,0xf0,0xe6,0xe1,0xe4,0x6a,0x70,0x65,0x5c,0x9b,0x5d,0x69,0x84,0x61,0x04, -0x33,0xa2,0x79,0x04,0xb9,0x19,0xdc,0x18,0xb7,0x66,0x37,0x92,0x19,0x64,0x63,0x98, -0xfb,0xe1,0xc9,0x07,0x98,0x07,0x11,0x8f,0x65,0x1e,0xe2,0xda,0x7a,0x98,0x79,0x84, -0x79,0x94,0x79,0x8c,0x79,0x9c,0x79,0x82,0x61,0x64,0x78,0x66,0xca,0xc8,0x7d,0x1c, -0x63,0x70,0x67,0xdc,0xff,0x71,0xdd,0xf7,0x77,0x32,0x4f,0x32,0xcd,0xee,0xa3,0x99, -0xf1,0xcc,0x53,0xcc,0xd3,0xcc,0x04,0x66,0x22,0x33,0x89,0x99,0xcc,0x3c,0xc3,0x4c, -0x61,0xa6,0x32,0xcf,0x32,0xd3,0x98,0xe9,0xcc,0x0c,0x66,0x26,0xda,0x22,0x66,0x16, -0x43,0x23,0x67,0x33,0x73,0x18,0xc3,0xc8,0xb9,0x5c,0xeb,0xcf,0x31,0xf3,0x98,0xf9, -0xcc,0x38,0xe6,0x79,0xe6,0x05,0x66,0x01,0x8e,0x2f,0x64,0x5e,0x64,0x5e,0x62,0x5e, -0x66,0x5e,0x41,0x7e,0x11,0xf3,0x2a,0xf3,0x1a,0x93,0xc0,0x30,0xcc,0x62,0x26,0x91, -0x49,0x62,0x92,0xb1,0x2f,0x05,0xa4,0x72,0x35,0xd3,0xf0,0xec,0x24,0x26,0x9d,0x59, -0xc2,0x2c,0x65,0x32,0x98,0x65,0xdc,0xbe,0xe5,0x6c,0xdf,0x47,0x66,0x32,0x59,0xcc, -0x0a,0x66,0x25,0xb3,0x0a,0xc7,0x57,0x33,0x6b,0x30,0x3f,0x58,0xcb,0xac,0xc3,0x91, -0xf5,0xcc,0x06,0x66,0x23,0x93,0x8d,0x5c,0x0e,0x93,0xcb,0xe4,0x31,0xf9,0x66,0x6f, -0x15,0x30,0x9b,0x98,0x42,0x66,0x33,0x53,0xf4,0x2f,0xc7,0xd7,0x24,0xce,0xfe,0x2d, -0xe6,0xad,0xad,0x7f,0xfa,0x39,0xdb,0xc6,0x14,0xff,0xea,0x18,0xdf,0xce,0xec,0xf8, -0x1f,0xbc,0xc2,0x1e,0xb8,0xc5,0xbe,0x65,0xff,0x27,0xaf,0x9f,0x25,0xbf,0xb3,0xde, -0x4f,0xbd,0x23,0x67,0x76,0x32,0x25,0xcc,0x2e,0xe4,0x4a,0x99,0xdd,0xb7,0x54,0x68, -0x1e,0x49,0x1e,0x65,0x38,0xbe,0x87,0x29,0x67,0x2a,0xfe,0x3f,0xb9,0x53,0x57,0x32, -0x7b,0x39,0x5f,0x15,0xff,0x7d,0xa7,0xfe,0x51,0xd8,0x77,0xdd,0x43,0xfb,0x99,0xaa, -0x9b,0xfc,0xb5,0xfc,0x4f,0x53,0x20,0xa6,0x9a,0x31,0x78,0xfc,0xcf,0xf5,0xd0,0xbc, -0x2a,0xf2,0xb8,0x79,0x34,0xd4,0x30,0xb5,0x4c,0x1d,0x37,0xa7,0xda,0x6a,0xde,0xa3, -0x62,0xea,0x99,0x66,0x0f,0x92,0x1b,0xe4,0xc4,0x34,0xa0,0x56,0x23,0xf6,0x35,0x99, -0x9e,0x6b,0xf2,0x66,0x39,0x79,0x1a,0x3c,0x19,0xcf,0x66,0x4f,0x52,0x18,0x14,0x8c, -0xa2,0x59,0x41,0x5e,0x06,0x2f,0xc6,0xab,0xd9,0x8b,0x94,0x06,0xe5,0xad,0x46,0x5b, -0xf3,0x6f,0xb0,0xeb,0x9f,0x03,0xcf,0x4d,0x65,0xb3,0xf2,0x75,0xa4,0x2d,0xcc,0x81, -0xeb,0xc7,0x0e,0x32,0x87,0x7e,0x52,0xba,0xd5,0x9c,0x1e,0x36,0xa7,0x6d,0xe6,0xf4, -0x88,0x59,0xf1,0x0d,0x86,0x54,0x47,0x7f,0x41,0xe9,0x4d,0xe6,0x18,0xe2,0x76,0xf3, -0x96,0x41,0xc5,0xa8,0x9a,0x55,0xc7,0xaf,0x1f,0xf5,0x41,0xed,0x0e,0xe6,0x04,0x43, -0x6a,0x83,0x9a,0x51,0x77,0x32,0xcd,0x6a,0xf2,0xbe,0xd5,0x7d,0xe5,0xc7,0xe1,0xa4, -0x39,0x3d,0xc5,0x9c,0x66,0xde,0x62,0xde,0x66,0xde,0xb9,0xe9,0xd8,0xbb,0xcc,0x7b, -0xcc,0xfb,0x5c,0xee,0x03,0xc6,0xe0,0xfd,0xef,0xde,0xbf,0xfe,0x0e,0x7f,0x87,0xbf, -0xc3,0xdf,0xe1,0xef,0xf0,0xaf,0xc2,0xb3,0xff,0x6b,0x2c,0xf9,0xf0,0xbf,0xd0,0x7b, -0x75,0xff,0xdf,0x8c,0x93,0x33,0xb7,0xd8,0x17,0xf5,0xe7,0xae,0xfb,0xbd,0x9b,0xbd, -0xc9,0xc7,0xe0,0xc3,0xf8,0x34,0xfb,0x90,0xaf,0xc1,0x97,0xf1,0xfd,0x63,0xeb,0x97, -0xff,0x3d,0xe1,0xec,0xdf,0xf7,0x19,0x2e,0x2c,0x61,0x8c,0x46,0x0b,0xe2,0x91,0x35, -0xf1,0x69,0x30,0xd9,0x90,0x1d,0x09,0x49,0x44,0x12,0x72,0x24,0x67,0x72,0xa1,0xe1, -0x34,0x82,0x64,0x34,0x92,0xe4,0xa4,0x20,0x25,0xa9,0xc9,0x87,0xfc,0x28,0x80,0x82, -0x48,0x4b,0xc6,0x3f,0xed,0xd3,0xff,0x07,0x43,0xc8,0x4f,0x6c,0xd1,0x11,0xdb,0x23, -0x36,0x47,0x88,0x23,0xb9,0x9c,0x9e,0x62,0x29,0x9e,0xee,0xe4,0xf2,0xf7,0xd0,0x68, -0xf4,0xca,0x68,0xbc,0x9f,0x1e,0xa1,0x7f,0x20,0x9d,0x48,0xcf,0xd0,0xb3,0x34,0x83, -0x66,0xd3,0x73,0xf4,0x3c,0x2d,0xa4,0x97,0xe9,0x55,0x38,0x26,0x89,0x52,0x69,0x09, -0x2d,0xa3,0x2c,0x5a,0x45,0x6b,0x69,0x03,0xe5,0x50,0x3e,0x15,0xd2,0x16,0x94,0x2f, -0xa6,0x9d,0x54,0x4a,0x7b,0xa8,0x92,0xf6,0x53,0xcd,0x9f,0xe0,0x87,0x7a,0xb4,0xd1, -0x04,0x5e,0x07,0x07,0xc1,0x61,0xf0,0x06,0x38,0x06,0x3a,0xc0,0x49,0xf0,0x16,0x78, -0x17,0x7c,0x00,0xce,0x82,0x0b,0xa0,0x1b,0xf4,0x81,0x8f,0xc1,0x65,0xf0,0x05,0xf8, -0x0a,0x7c,0x03,0xae,0x81,0x7e,0xd6,0x0f,0x16,0x46,0xa3,0x15,0x10,0x58,0x08,0x2c, -0x6c,0x90,0xda,0x5b,0xb0,0x8a,0x22,0xc4,0x0e,0xc0,0x19,0x0c,0xb3,0x18,0x66,0xe1, -0x86,0x74,0x24,0xf0,0x04,0x4a,0xe0,0x6d,0xe1,0x8f,0x38,0x08,0x04,0x03,0x1d,0x88, -0x00,0xd1,0xc0,0x00,0xe2,0xc1,0x1d,0xe0,0x6e,0x70,0x2f,0x18,0x03,0x1e,0x04,0x0f, -0x83,0xc7,0xc0,0x38,0x30,0x1e,0x4c,0x00,0x93,0xc1,0x54,0x30,0x1d,0xcc,0x02,0x73, -0xc1,0x7c,0x8b,0x05,0x88,0x5f,0x02,0x8b,0x80,0x02,0x56,0x26,0x58,0x24,0x22,0x97, -0x02,0xd2,0x2d,0x32,0x10,0x67,0x5a,0xac,0xb4,0x58,0xc3,0x59,0xba,0xde,0x22,0xdb, -0x22,0xcf,0x62,0x13,0xf2,0x45,0x16,0xdb,0x10,0xef,0xb0,0xd8,0x65,0x51,0x66,0x61, -0xf2,0x5b,0x85,0xc5,0x3e,0xe4,0xaa,0x2d,0xea,0x10,0x37,0x82,0x7f,0x82,0x03,0x16, -0xad,0x88,0x8f,0x70,0x25,0xde,0x44,0x7c,0xdc,0xa2,0x13,0xf1,0x69,0x8b,0x77,0x2c, -0xde,0x47,0x7a,0x06,0x9c,0xb7,0xe8,0x32,0xd7,0xef,0xb5,0xf8,0xf1,0x79,0xb8,0x64, -0xc1,0x86,0xcb,0x5c,0xf8,0x82,0x0b,0x5f,0xa1,0xc4,0x37,0xe0,0x1a,0xe8,0x07,0x16, -0x96,0xf0,0x26,0xe0,0x83,0x21,0x96,0x6c,0x1d,0x3b,0xc4,0x43,0x81,0x04,0x38,0x01, -0x17,0xe0,0x0a,0x64,0xc0,0x03,0x28,0xb8,0x52,0x2a,0x4b,0x36,0xf8,0x20,0xef,0x6f, -0x19,0x64,0x19,0x8c,0x54,0x07,0x22,0x40,0x34,0x30,0x80,0x78,0x70,0x07,0xb8,0x1b, -0xdc,0x0b,0xc6,0x80,0x07,0xc1,0xc3,0xe0,0x31,0x30,0x0e,0x8c,0x07,0x13,0xc0,0x64, -0x30,0x15,0x4c,0x07,0xb3,0xc0,0x5c,0x30,0x1f,0x2c,0x00,0x2f,0x81,0x45,0x20,0x01, -0x24,0x82,0x14,0x90,0x0e,0x32,0x2c,0x7f,0xdc,0xd7,0x4c,0xcb,0x55,0xd8,0xb3,0xd6, -0x72,0x03,0xb7,0x3f,0x17,0x71,0x81,0xe5,0x66,0xcb,0xad,0x96,0xdb,0x91,0x2b,0x01, -0xbb,0x41,0x39,0xd8,0x6b,0x69,0xfc,0xaf,0xff,0x08,0xa9,0xca,0x52,0x82,0x3b,0x58, -0xad,0x65,0x83,0x65,0xb3,0xa5,0x1f,0xee,0x5e,0x7f,0xac,0xbd,0x16,0xcb,0x56,0xcb, -0x37,0x2c,0xdb,0x2d,0x3b,0x2d,0xdf,0xe2,0xbc,0xb3,0xcd,0xc2,0x19,0x77,0xc7,0x00, -0x7a,0xef,0x77,0xfa,0xea,0x43,0xcb,0x73,0xa8,0xf9,0x91,0x65,0x8f,0xe5,0xc7,0xb7, -0x68,0x81,0xfe,0xf0,0x1d,0xe6,0x32,0x5a,0xfd,0x02,0x7c,0x05,0xbe,0xb1,0x34,0xdd, -0x6f,0xaf,0x0d,0x28,0x59,0x5a,0xf0,0x06,0xee,0xa3,0xf1,0x64,0xc5,0x1b,0xc2,0xb3, -0xe3,0x0d,0xe5,0x49,0x78,0x4e,0xd8,0xeb,0x02,0x5c,0x79,0x32,0x1e,0x46,0x32,0x4f, -0xc1,0x53,0xf1,0x7c,0x78,0xfe,0xbc,0x20,0x5e,0x30,0x4f,0xc7,0xfb,0xd8,0x32,0x82, -0x17,0xcd,0xbb,0x93,0x0c,0xbc,0x78,0xde,0x1d,0xbc,0xbb,0x79,0xf7,0xa2,0xe4,0x18, -0xde,0x83,0xbc,0x87,0x79,0x8f,0xf1,0xc6,0xf1,0xc6,0xf3,0x26,0xf0,0x26,0xf3,0xa6, -0xf2,0xfe,0x9c,0xb3,0x37,0x1d,0xda,0x3a,0xb4,0xec,0x8a,0x16,0x1f,0x83,0x16,0xc6, -0x3c,0x98,0x0b,0xe6,0x83,0x05,0xe0,0x25,0xb0,0x08,0x24,0x80,0x44,0x90,0x02,0xd2, -0x41,0x06,0xc8,0x04,0x11,0xb0,0x6f,0x0c,0x52,0x1d,0xec,0x37,0x1a,0x57,0x82,0x31, -0xbc,0x35,0x9c,0x75,0xeb,0x79,0xd9,0xbc,0x3c,0xde,0x26,0x5e,0x11,0x6f,0x1b,0x6f, -0x07,0x6f,0x17,0xaf,0x8c,0x57,0xc1,0xdb,0xc7,0xab,0xe6,0xd5,0xf1,0x1a,0x79,0xff, -0xe4,0x1d,0xe0,0xb5,0xf2,0x8e,0xf0,0xde,0xe4,0x1d,0xe7,0x75,0xf2,0x4e,0xf3,0xde, -0xe1,0xbd,0xcf,0x3b,0xc3,0x3b,0xcf,0xeb,0xe2,0xf5,0xf2,0x2e,0xf1,0x3e,0xe5,0x7d, -0xce,0xfb,0x92,0x77,0x95,0xf7,0x1d,0xef,0x07,0x1e,0xdc,0x66,0x35,0xc8,0x6a,0xb0, -0x95,0xad,0x95,0xd0,0x4a,0x6c,0xe5,0x68,0x25,0xb5,0x1a,0x6e,0xe5,0x66,0x35,0xd2, -0xca,0xd3,0x4a,0x69,0xe5,0x6d,0xe5,0x67,0xf5,0x57,0x8f,0xef,0x40,0x28,0x68,0x41, -0x28,0x08,0x07,0x51,0x40,0x0f,0xe2,0xc0,0xed,0xe0,0x2e,0x30,0x0a,0x8c,0x06,0x0f, -0x80,0x87,0xc0,0xa3,0xe0,0x09,0xf0,0x24,0x78,0xfa,0x16,0x16,0x4e,0xc2,0xbe,0x29, -0x60,0x1a,0x98,0x09,0xe6,0x80,0x79,0xe0,0x05,0xf0,0x22,0x78,0x05,0xbc,0x06,0x16, -0x83,0x64,0x90,0x06,0x96,0x82,0xe5,0x60,0x05,0x58,0x0d,0xd6,0x81,0x8d,0x20,0x17, -0x14,0x80,0xcd,0x60,0x2b,0xd8,0x0e,0x4a,0xc0,0x6e,0x50,0x0e,0xf6,0x5a,0x55,0x21, -0xae,0x05,0x0d,0xa0,0x19,0xb4,0x80,0x43,0xa0,0x8d,0xb3,0xec,0x28,0xe2,0x76,0x70, -0x02,0x9c,0x02,0x6f,0x83,0xf7,0xc0,0x87,0xe0,0x1c,0xf8,0x08,0xf4,0x80,0x8b,0xe0, -0x13,0xf0,0x19,0xb8,0x02,0xbe,0x06,0xdf,0x82,0xef,0xd9,0x56,0x80,0xa5,0xb5,0xd1, -0x68,0x0d,0x04,0xc0,0x06,0xd8,0x03,0x11,0x70,0x00,0xce,0x60,0x18,0x18,0x01,0xdc, -0x81,0x1c,0x78,0x01,0x35,0xf0,0x05,0x01,0x40,0x03,0x42,0x40,0x18,0x88,0x04,0x31, -0x20,0x16,0xdc,0x06,0xee,0x04,0xf7,0x80,0xfb,0xc0,0xfd,0x60,0x2c,0x78,0x04,0x3c, -0x6e,0xcd,0x5e,0x6f,0xff,0xb0,0x7e,0xca,0x7a,0xa2,0xf5,0x33,0xd6,0xcf,0x5a,0xcf, -0xb0,0x9e,0x6d,0xfd,0x9c,0xf5,0xf3,0xd6,0x0b,0xad,0x5f,0xb6,0x7e,0xd5,0x9a,0xb1, -0x4e,0xb2,0x4e,0xb5,0x5e,0x62,0xbd,0xcc,0x3a,0xcb,0x7a,0x95,0xf5,0x5a,0xeb,0x0d, -0xd6,0x39,0xd6,0xf9,0xd6,0x85,0xd6,0x5b,0xac,0x8b,0xad,0x77,0x5a,0x97,0x5a,0xef, -0xb1,0xae,0xb4,0xde,0x6f,0x5d,0x63,0x5d,0x6f,0xdd,0x64,0xfd,0xba,0xf5,0x41,0xeb, -0xc3,0xd6,0x6f,0x58,0xb3,0x57,0xea,0x5f,0xf9,0x39,0x66,0x7d,0x63,0x56,0x36,0x90, -0xfb,0xbf,0xf2,0xe9,0xb4,0x7e,0xdb,0xfa,0x03,0xeb,0xf3,0xd6,0x7f,0xac,0x95,0x1e, -0xeb,0x4f,0xad,0xbf,0xb2,0xfe,0xde,0x9a,0x37,0x68,0xc8,0x20,0xd1,0x20,0xe9,0xa0, -0x3f,0x77,0x26,0xfc,0x63,0xdf,0xff,0x7c,0x76,0x7c,0x63,0x8f,0x6c,0x90,0xd7,0xa0, -0x7e,0xa3,0xdf,0xa0,0x9f,0xd6,0xfe,0x7d,0x9a,0x3f,0x3d,0xcf,0x37,0xf4,0xfe,0xfd, -0x96,0xfb,0x6f,0xb1,0x1d,0x3c,0x88,0x8d,0x23,0x07,0xdd,0xdc,0x62,0xff,0xaf,0xea, -0xfd,0xb4,0xcf,0xa6,0x10,0x37,0xe8,0xee,0x41,0xf7,0x0f,0x62,0xf7,0x3e,0x3a,0xe8, -0xa7,0xde,0xf9,0xf7,0x57,0x1b,0xff,0xca,0xff,0xb7,0x2a,0x39,0xb0,0x35,0x7e,0xd0, -0x33,0x83,0xfa,0x6f,0xe9,0xc9,0xdf,0x6f,0xc1,0x5f,0xf7,0x99,0x39,0xc8,0xf8,0x0b, -0xe7,0xb7,0xff,0xfa,0x39,0xea,0xff,0x17,0xe7,0xf0,0xb7,0x9d,0xf5,0x5f,0xae,0x35, -0x7f,0xd0,0xbf,0x1e,0x7d,0xbf,0xd4,0x42,0xff,0x2f,0xfa,0xf0,0xe5,0x41,0x8b,0x07, -0xfd,0x19,0xeb,0xc7,0x5f,0xfa,0xa4,0x0f,0xfa,0x4f,0xde,0x9b,0x96,0x0d,0xfa,0x79, -0x8f,0x57,0x5f,0xdf,0x97,0x7d,0x3d,0x57,0x78,0x3d,0xb7,0xfd,0x17,0xed,0x2b,0x1b, -0xf4,0xf3,0x6b,0x6a,0x3f,0xf6,0x35,0x0c,0x3a,0x30,0xe8,0x8d,0x41,0xa7,0x06,0xbd, -0xff,0x93,0x9a,0xdd,0xbf,0xa1,0xa7,0x9f,0xdc,0xc2,0xbe,0x2f,0xaf,0xef,0xbb,0x76, -0x3d,0x67,0xc9,0x1f,0xc8,0x0d,0xe6,0xff,0x52,0x5b,0x43,0xf9,0x7f,0xe6,0x93,0xc5, -0x99,0x3f,0x9c,0x2f,0xe3,0xcb,0xf9,0x4a,0xbe,0x0f,0x3f,0x80,0xaf,0xe5,0xeb,0xf8, -0x91,0x7c,0x3d,0x3f,0x9e,0x7f,0x27,0x7f,0x14,0x7f,0x0c,0x7f,0x2c,0xff,0x51,0xfe, -0x38,0xfe,0x53,0xfc,0x49,0xfc,0xa9,0xfc,0x19,0xfc,0x39,0xfc,0xf9,0xfc,0x85,0xfc, -0x57,0xf8,0x09,0xfc,0x24,0x7e,0x1a,0x3f,0x83,0x9f,0xc5,0x5f,0xcd,0x5f,0xcf,0xcf, -0xe1,0x17,0xf0,0x8b,0xf8,0xc5,0xfc,0x12,0x7e,0xbf,0xb1,0x8c,0x7f,0x63,0x64,0x54, -0xf2,0xff,0xca,0xab,0xb3,0x8a,0xff,0x67,0xb4,0x42,0x44,0x64,0xfc,0xaf,0xf9,0xb0, -0xfe,0xac,0xe3,0x37,0xf1,0x5b,0xf8,0xad,0xfc,0x37,0xf8,0xed,0xfc,0xfe,0xff,0xe0, -0xec,0xe2,0xdf,0xf3,0x14,0x5d,0xff,0xfc,0xda,0x3d,0xfc,0x8f,0x7e,0x4e,0x63,0x65, -0xf1,0x1d,0x8f,0x5d,0x43,0x0c,0xc2,0xba,0x41,0x6b,0xd5,0xc9,0x7f,0x8b,0x7f,0xb3, -0xc2,0x7b,0xfc,0x33,0xfc,0x0b,0xfc,0x1e,0xfe,0x25,0xfe,0x65,0xfe,0x15,0xfe,0x55, -0xfe,0x35,0xbe,0x91,0xcf,0x13,0xf0,0x05,0x55,0x7c,0x1b,0x81,0x50,0x20,0x11,0x38, -0x0b,0x86,0x0b,0x64,0x02,0xb9,0x40,0x29,0xf0,0x11,0x04,0x08,0xb4,0x02,0x9d,0x20, -0x52,0xa0,0x17,0xc4,0x0b,0xee,0x14,0x8c,0x12,0x8c,0x11,0x8c,0x15,0x3c,0x2a,0x18, -0x27,0x78,0x4a,0x30,0x49,0x30,0x55,0x30,0x43,0x30,0x47,0x30,0x5f,0xb0,0x50,0xf0, -0x8a,0x20,0x41,0xc0,0xb6,0x9c,0x24,0x48,0x13,0x64,0x08,0xfe,0x2a,0x7f,0x5b,0xd0, -0xf3,0xc4,0x83,0xf7,0xd8,0xf7,0x8a,0xeb,0x2d,0x7e,0xfa,0x5e,0x11,0x73,0x6d,0x9a, -0x6f,0x29,0xc3,0xba,0x9c,0x7d,0xaf,0xe8,0x47,0x16,0x94,0x25,0x58,0x25,0x58,0x2b, -0xe0,0x71,0xef,0x21,0xd9,0xb7,0x3e,0x1b,0x05,0x76,0x28,0xe5,0x88,0xd2,0xfe,0x16, -0x23,0x28,0xdd,0x22,0x57,0xb0,0x49,0x20,0xe3,0x4a,0x6f,0x11,0x54,0x58,0xf8,0xd0, -0x76,0x81,0x69,0x55,0xca,0xae,0x39,0x45,0x68,0x87,0x7d,0x3b,0xc9,0xee,0xb9,0x97, -0xdb,0xf3,0xcb,0x76,0x5d,0xfd,0x53,0x56,0xa6,0xbb,0x04,0xd6,0x54,0x26,0x58,0x45, -0x1b,0x05,0x36,0x54,0x21,0xd8,0x27,0xa8,0x16,0xec,0xb2,0xd8,0x61,0x51,0x27,0x68, -0x14,0xbc,0x2e,0x38,0x28,0x38,0x2c,0x38,0x2a,0x68,0x17,0x9c,0x10,0xec,0xb3,0x38, -0x25,0x78,0x5b,0x50,0x6d,0xf1,0x9e,0xe0,0x43,0xc1,0x11,0x8b,0x73,0x82,0xcd,0x96, -0xa7,0x2d,0x3e,0x12,0xbc,0x63,0xb1,0xd5,0x52,0x4b,0xbd,0x82,0x4b,0x82,0xf3,0x16, -0x3a,0xde,0xa7,0x38,0x03,0x5f,0x80,0xaf,0xc1,0x77,0xa0,0x1f,0x58,0x0e,0x36,0x1a, -0x07,0x81,0x21,0xc0,0x1e,0x88,0x81,0x13,0x18,0x06,0xdc,0x80,0x07,0xf0,0x02,0xde, -0xc0,0x1f,0x68,0x40,0x28,0x88,0x00,0x31,0x20,0x0e,0xdc,0x01,0xee,0x01,0xa3,0xc1, -0x83,0xe0,0x11,0xf0,0x04,0x18,0x0f,0x26,0x82,0x29,0x60,0x3a,0x98,0x0d,0xe6,0x81, -0x05,0xe0,0x65,0xf0,0x1a,0x48,0x04,0xa9,0x60,0x29,0xc8,0x04,0xab,0xc0,0x3a,0x90, -0x0d,0xf2,0xc1,0x66,0xb0,0x0d,0xec,0x04,0xbb,0x41,0x05,0xd8,0x0f,0x6a,0x41,0x23, -0x78,0x1d,0x1c,0x02,0x47,0xc0,0x31,0x70,0x02,0x9c,0x06,0xef,0x82,0x0f,0xc1,0x79, -0xd0,0x0d,0x2e,0x82,0x4f,0xc1,0x17,0xe0,0x6b,0xf0,0x1d,0xe8,0x07,0x96,0x43,0xd0, -0x7f,0x30,0x04,0xd8,0x03,0x31,0x70,0x1a,0x72,0xc3,0xfb,0xc3,0x86,0xb0,0xaa,0xf0, -0x05,0xf6,0x8d,0x04,0x0a,0xa0,0x06,0x7e,0x20,0x08,0x84,0x80,0x70,0x10,0x0d,0x62, -0xc1,0xed,0xe0,0x6e,0x70,0x1f,0x78,0x00,0x3c,0x0c,0x1e,0x07,0x4f,0x82,0x09,0xe0, -0x19,0x30,0x0d,0xcc,0x02,0xcf,0x81,0x17,0xc0,0x4b,0xe0,0x55,0xb0,0x18,0xa4,0x80, -0x25,0x60,0x39,0x58,0x09,0xd6,0x82,0x8d,0x20,0x0f,0x14,0x82,0xad,0x60,0x07,0x28, -0x05,0xe5,0x60,0x1f,0xa8,0x01,0x0d,0xe0,0x9f,0xe0,0x20,0x68,0x03,0x6f,0x82,0x0e, -0x70,0x0a,0xbc,0x03,0x3e,0x18,0xf2,0xf3,0x91,0x75,0x6e,0x48,0xd7,0x90,0xbe,0x21, -0x9f,0x0c,0xf9,0x7c,0xc8,0x57,0x43,0xbe,0x1d,0xf2,0xc3,0x90,0x9f,0xde,0x77,0x2c, -0x6c,0xac,0x6d,0x06,0xdb,0xd8,0xd9,0x88,0x6c,0x1c,0x6d,0x6e,0x75,0xff,0x71,0xb1, -0x19,0x61,0x33,0xd2,0x46,0x61,0xa3,0xb6,0xf1,0xb3,0x09,0xb2,0x09,0xb1,0xf9,0xe9, -0xf1,0x70,0x9b,0x68,0x9b,0x58,0x9b,0xdb,0x6d,0xee,0xb6,0xb9,0xcf,0xe6,0x01,0x9b, -0x87,0x6d,0x7e,0xda,0xfe,0xe3,0x36,0x4f,0xda,0x4c,0xb0,0x79,0xc6,0x66,0x9a,0xcd, -0xac,0x5b,0xb6,0xdf,0x6f,0x7c,0x0e,0xfb,0x5f,0x00,0x2f,0x81,0x57,0x7f,0xd6,0xfe, -0x62,0x9b,0x14,0x9b,0x25,0x36,0xcb,0x6d,0x56,0xda,0xac,0xb5,0xd9,0x68,0x93,0x87, -0xe3,0x43,0x70,0xc5,0x0d,0x05,0x12,0xe0,0x04,0x5c,0x80,0x2b,0x90,0x71,0xef,0x9e, -0x0a,0x6d,0x8a,0x6d,0x4a,0x6d,0x2a,0x6d,0x6a,0x6c,0x9a,0x6c,0x0e,0xda,0xbc,0x61, -0xf3,0xd3,0xed,0x0e,0x9b,0xb7,0x6c,0x3e,0xb0,0xb9,0x60,0xd3,0x67,0x73,0xd9,0xe6, -0x2b,0x9b,0x6b,0x3f,0xdb,0xb6,0xb0,0xe5,0xdb,0xda,0xd9,0x4a,0x6c,0x5d,0x6c,0x65, -0xb6,0x0a,0x5b,0x1f,0xdb,0x9f,0x6e,0xb3,0x36,0x05,0xd9,0xea,0x6c,0xa3,0x6c,0xd9, -0xbe,0xc4,0xda,0xde,0x69,0x3b,0xca,0x76,0x8c,0xed,0x10,0x9e,0xce,0x76,0xac,0xed, -0xc7,0x96,0x63,0x6d,0x1f,0xb5,0x1d,0x67,0x3b,0xc1,0x76,0xaa,0xed,0x4c,0xee,0xf8, -0x73,0xb6,0x0b,0x6d,0x87,0xf2,0x5e,0xb1,0x95,0xf0,0xa6,0xda,0x26,0xd8,0xa6,0xd8, -0x66,0xd8,0x9a,0x7a,0xb5,0xd2,0x76,0xc0,0x17,0x6b,0x6c,0x37,0xd8,0xe6,0xda,0xb2, -0x6f,0xc8,0x36,0xd9,0x6e,0xb3,0xdd,0x65,0x3e,0x5e,0x61,0x3b,0xd0,0xff,0x7d,0xb6, -0x35,0xb6,0x0d,0xb6,0xae,0xbc,0x7f,0xda,0x1e,0xb4,0xb5,0xe2,0xbd,0xc1,0xd5,0x7b, -0xd3,0xb6,0xd3,0xf6,0x6d,0xae,0xfd,0x0f,0x6c,0x2f,0xd8,0xba,0xf0,0x7a,0x6c,0x65, -0xbc,0x4e,0xdb,0x78,0xba,0x64,0xcb,0x7e,0x7f,0x71,0xab,0x60,0x7a,0xee,0x98,0x34, -0x2f,0xdb,0xde,0xf0,0xee,0x15,0xdb,0x7f,0xbd,0xae,0x30,0xfe,0xc2,0xb7,0x23,0x57, -0x6d,0xfb,0x51,0x53,0x60,0x27,0xb4,0xbb,0x3e,0x4e,0x90,0x73,0xb5,0xfb,0x69,0x2b, -0xee,0x76,0x72,0x3b,0x2f,0xbb,0x5f,0xbe,0xef,0xa9,0x7f,0x72,0x2c,0xe4,0xc6,0x83, -0x92,0x6e,0xa8,0xde,0xb4,0x93,0x42,0xed,0x44,0xdc,0x91,0x30,0xbb,0x08,0xbb,0x28, -0xbb,0x18,0x3b,0x83,0x5d,0x9c,0xdd,0x6d,0x76,0x77,0xd8,0xdd,0x63,0x77,0xaf,0xdd, -0x68,0xbb,0xe1,0x38,0x3e,0x9a,0xd8,0xef,0x67,0x6e,0x7d,0x1c,0xe3,0x1d,0xcf,0x83, -0x11,0x14,0x80,0x67,0x82,0xf0,0xfa,0xd3,0x43,0xc6,0x7d,0x1f,0x75,0xeb,0xe7,0xef, -0xfd,0x76,0xff,0xde,0x9d,0x7c,0xac,0xdd,0xa3,0x76,0xd6,0x34,0x0e,0xb5,0x9e,0xb2, -0x9b,0x64,0x97,0xc7,0x7e,0x63,0x61,0xc7,0x3e,0xad,0x4c,0xe1,0x2b,0x3c,0xb5,0x44, -0x50,0x75,0x86,0x1f,0x87,0xd3,0x0c,0xae,0x6d,0xd3,0x37,0x62,0x6c,0x60,0xb7,0x66, -0xdb,0x3d,0x67,0xf7,0x02,0xf6,0xb3,0xdf,0x89,0x4d,0xe0,0x99,0x52,0x47,0x7a,0x8e, -0xfb,0x4e,0x0d,0xb3,0x60,0xee,0x5b,0xb5,0x17,0xed,0x5c,0xd0,0x87,0x45,0x76,0x09, -0x76,0x89,0x76,0x29,0xf0,0x07,0x56,0x17,0x76,0x77,0xe3,0x59,0xa5,0xe2,0xdd,0xcd, -0x5b,0x76,0xdd,0x5e,0x3e,0xf7,0xec,0x63,0x9f,0x92,0x37,0xdb,0xb7,0xc2,0x6e,0x9d, -0x5d,0xae,0x5d,0x91,0xdd,0x0e,0xbb,0x32,0xbb,0x7d,0x76,0x75,0x76,0xff,0xb4,0x6b, -0xb5,0x7b,0xd3,0xae,0xd3,0xee,0x1d,0xbb,0x33,0x76,0x5d,0x76,0x97,0xd0,0xda,0x67, -0x76,0x57,0xec,0xae,0xda,0xf9,0xd0,0x77,0x76,0x3f,0xd8,0x59,0xd8,0x0f,0xb2,0x0f, -0xa0,0xc1,0xf6,0xb6,0xf6,0xce,0xdc,0x77,0x7a,0x2e,0xf4,0x6b,0xc7,0xcd,0x73,0x72, -0xfb,0x1b,0x8a,0x0e,0xf6,0x3f,0x5d,0x3d,0xfc,0x37,0x7e,0x86,0xd9,0x8f,0x44,0x3f, -0x7c,0xec,0x35,0xf6,0xff,0x69,0xe5,0x28,0x7b,0xc3,0x1f,0xd6,0xfc,0xed,0xeb,0xd7, -0x5b,0x5c,0x3d,0xf6,0x77,0xda,0xdf,0x63,0x7f,0x9f,0xfd,0xfd,0xf6,0x63,0xed,0x1f, -0xb1,0x7f,0xdc,0xfe,0x1f,0xf6,0x4f,0xd9,0x4f,0xb4,0x7f,0xc6,0x7e,0x9a,0xfd,0x2c, -0xfb,0xe7,0xec,0x5f,0xb0,0x7f,0xc9,0xfe,0x55,0xfb,0xc5,0xf6,0x29,0xf6,0x4b,0xec, -0x33,0xed,0x57,0xdb,0x6f,0xb0,0xcf,0xb3,0xdf,0x6c,0x5f,0x6c,0xbf,0xcb,0xbe,0xdc, -0x7e,0xbf,0x7d,0x9d,0xfd,0x6f,0xd5,0x6d,0xb6,0x3f,0x60,0x7f,0xd8,0xfe,0xa8,0xfd, -0x71,0xfb,0x93,0xf6,0x6f,0xdb,0xbf,0x6f,0x7f,0xd6,0xfe,0x23,0xfb,0x5e,0xfb,0x8f, -0xed,0x3f,0xb3,0xff,0xd2,0xfe,0x1b,0xfb,0xef,0xed,0x49,0x68,0x25,0x14,0x08,0x6d, -0x85,0x43,0x85,0x0e,0x42,0xa9,0xd0,0x55,0xe8,0x2e,0xf4,0x14,0xfe,0x9e,0x6f,0x9a, -0x7f,0x4f,0x9d,0x50,0xbb,0x3f,0x7e,0x16,0x55,0x42,0xe3,0xff,0xa9,0x4f,0x88,0x30, -0x5c,0x18,0xf5,0x27,0xf6,0xc9,0xf0,0x27,0xb5,0x75,0x87,0xf0,0x1e,0xe1,0x68,0xe1, -0x83,0xc2,0x47,0x84,0x4f,0x08,0xc7,0x0b,0x27,0x0a,0xa7,0x08,0xa7,0x0b,0x67,0x0b, -0xe7,0x09,0x17,0x08,0x5f,0x16,0xbe,0x26,0x4c,0x14,0xa6,0x0a,0x97,0x0a,0x33,0x85, -0xab,0x84,0xeb,0x84,0xd9,0xc2,0x7c,0xe1,0x66,0xe1,0x36,0xe1,0x4e,0xe1,0x6e,0x61, -0x85,0x70,0xbf,0xb0,0x56,0xd8,0x28,0x7c,0x5d,0x78,0x48,0x78,0x44,0x78,0x4c,0x78, -0x42,0x78,0x5a,0xf8,0xae,0xf0,0x43,0xe1,0x79,0x61,0xb7,0xf0,0xa2,0xf0,0x53,0xe1, -0x17,0xc2,0xaf,0x85,0xdf,0x09,0xfb,0x85,0x96,0x43,0xff,0x6a,0xbf,0x0e,0x82,0xc2, -0x90,0xa1,0x76,0x43,0x45,0x9c,0x92,0x03,0x62,0x29,0x70,0x05,0xab,0xb0,0x06,0xc8, -0x12,0xec,0xc2,0x7c,0xde,0x9d,0x3b,0xe6,0xf9,0x23,0x5b,0x24,0x18,0xaf,0xaa,0xa1, -0x3e,0x43,0xfd,0xb1,0x57,0x03,0x42,0x41,0x04,0x88,0x01,0x71,0xe0,0x0e,0x70,0x0f, -0x18,0x0d,0x1e,0x04,0x8f,0x80,0x27,0xc0,0x78,0x30,0x11,0x4c,0x01,0xd3,0xc1,0x6c, -0x30,0x0f,0x2c,0x00,0x2f,0x83,0xd7,0x40,0x22,0x48,0x05,0x4b,0x41,0x26,0x6b,0x09, -0x58,0x07,0xb2,0x41,0x3e,0xd8,0x0c,0xb6,0x81,0x9d,0x60,0x37,0xa8,0x00,0xfb,0x41, -0x2d,0x68,0x04,0xaf,0x83,0x43,0xe0,0x08,0x38,0x06,0x4e,0x80,0xd3,0xe0,0x5d,0xf0, -0x21,0x38,0x0f,0xba,0xc1,0x45,0xf0,0x29,0xf8,0xe2,0x67,0x5e,0xfe,0x1a,0x7b,0xbe, -0xbb,0xbe,0xb7,0x7f,0xa8,0xf1,0x3f,0xf6,0xbe,0xf3,0xc7,0x77,0x29,0x4b,0x91,0xf1, -0x77,0xbd,0xfd,0xfb,0xad,0x6f,0xb8,0x07,0x89,0xfe,0x3b,0xae,0xfe,0x21,0xa2,0x5f, -0xf2,0x80,0xbd,0x48,0x2c,0x72,0x12,0x0d,0x13,0xb9,0x89,0x3c,0x44,0x5e,0x22,0x6f, -0x91,0xbf,0x48,0x23,0x0a,0x15,0x45,0x88,0x62,0x44,0x71,0xa2,0x3b,0x44,0xf7,0x88, -0x46,0x8b,0x1e,0x14,0x3d,0x22,0x7a,0x42,0x34,0x5e,0x34,0x51,0x34,0x45,0x34,0x5d, -0x34,0x5b,0x34,0x4f,0xb4,0x40,0xf4,0xb2,0xe8,0x35,0x51,0xa2,0x28,0x55,0xb4,0x54, -0x94,0x29,0x5a,0x25,0x5a,0x27,0xca,0x16,0xe5,0x8b,0x36,0x8b,0xb6,0x89,0x76,0x8a, -0x76,0x8b,0x2a,0x44,0xfb,0x45,0xb5,0xa2,0x46,0xd1,0xeb,0xa2,0x43,0xa2,0x23,0xa2, -0x63,0xa2,0x13,0xa2,0xd3,0xa2,0x77,0x45,0x1f,0x8a,0xce,0x8b,0xba,0x45,0x17,0x45, -0x9f,0x8a,0xbe,0x10,0x7d,0x2d,0xfa,0x4e,0xd4,0x2f,0xb2,0x14,0x0f,0x12,0x0f,0x11, -0xdb,0x8b,0xc5,0x62,0x27,0xf1,0x30,0xb1,0x9b,0xd8,0x43,0xec,0x25,0xf6,0x16,0xfb, -0x8b,0x35,0xe2,0x50,0x71,0x84,0x38,0x46,0x1c,0x27,0xbe,0x43,0x7c,0x8f,0x78,0xb4, -0xf8,0x41,0xf1,0x23,0xe2,0x27,0xc4,0xe3,0xc5,0x13,0xc5,0x53,0xc4,0xd3,0xc5,0xb3, -0xc5,0xf3,0xc4,0x0b,0xc4,0x2f,0x8b,0x5f,0x13,0x27,0x8a,0x53,0xc5,0x4b,0xc5,0x99, -0xe2,0x55,0xe2,0x75,0xe2,0x6c,0x71,0xbe,0x78,0xb3,0x78,0x9b,0x78,0xa7,0x78,0xb7, -0xb8,0x42,0xbc,0x5f,0x5c,0x2b,0x6e,0x14,0xbf,0x2e,0x3e,0x24,0x3e,0x22,0x3e,0x26, -0x3e,0x21,0x3e,0x2d,0x7e,0x57,0xfc,0xa1,0xf8,0xbc,0xb8,0x5b,0x7c,0x51,0xfc,0xa9, -0xf8,0x0b,0xf1,0xd7,0xe2,0xef,0xc4,0xfd,0x62,0x4b,0xc9,0x20,0xc9,0x10,0x89,0xbd, -0x44,0x2c,0x71,0x92,0x0c,0x93,0xb8,0x49,0x3c,0x24,0x5e,0x12,0x6f,0x89,0xbf,0x44, -0x23,0x09,0x95,0x44,0x48,0x62,0x24,0x71,0x92,0x3b,0x24,0xf7,0x48,0x46,0x4b,0x1e, -0x94,0x3c,0x22,0x79,0x42,0x32,0x5e,0x32,0x51,0x32,0x45,0x32,0x5d,0x32,0x5b,0x32, -0x4f,0xb2,0x40,0xf2,0xb2,0xe4,0x35,0x49,0xa2,0x24,0x55,0xb2,0x54,0x92,0x29,0x59, -0x25,0x59,0x27,0xc9,0x96,0xe4,0x4b,0x36,0x4b,0xb6,0x49,0x76,0x4a,0x76,0x4b,0x2a, -0x24,0xfb,0x25,0xb5,0x92,0x46,0xc9,0xeb,0x92,0x43,0x92,0x23,0x92,0x63,0x92,0x13, -0x92,0xd3,0x92,0x77,0x25,0x1f,0x4a,0xce,0x4b,0xba,0x25,0x17,0x25,0x9f,0x4a,0xbe, -0x90,0x7c,0x2d,0xf9,0x4e,0xd2,0x2f,0xb1,0x74,0x18,0xe4,0x30,0xc4,0xc1,0xde,0x41, -0xec,0xe0,0xe4,0x30,0xcc,0xc1,0xcd,0xc1,0xc3,0xc1,0xcb,0xc1,0xdb,0xc1,0xdf,0x41, -0xe3,0x10,0xea,0x10,0xe1,0x10,0xe3,0x10,0xe7,0x70,0x87,0xc3,0x3d,0x0e,0xa3,0x1d, -0x1e,0x74,0x78,0xc4,0xe1,0x09,0x87,0xf1,0x0e,0x13,0x1d,0xfa,0x8d,0x7f,0x7e,0x60, -0x57,0x08,0x53,0x1c,0xfe,0x33,0xa3,0xf0,0x59,0xe8,0x4c,0x17,0xcd,0x74,0x98,0xeb, -0xf0,0x47,0xae,0x6e,0xf6,0xf3,0xbc,0xc3,0x4b,0x5c,0x1b,0xaf,0x39,0xfc,0x77,0x3f, -0x95,0x53,0x1c,0xfe,0xea,0xfb,0x62,0xbf,0x71,0x99,0xc3,0x0a,0x87,0x35,0x0e,0x1b, -0x1c,0x72,0x1d,0x36,0x39,0x6c,0x71,0xd8,0xee,0xb0,0xcb,0x61,0x8f,0xc3,0x5e,0x87, -0x6a,0x87,0x7a,0x87,0x66,0x87,0x03,0x0e,0x87,0x1d,0x8e,0x3a,0x1c,0x77,0x38,0xe9, -0xf0,0xb6,0xc3,0xfb,0x0e,0x67,0x1d,0x3e,0x72,0xe8,0x75,0xf8,0xd8,0xe1,0x33,0x87, -0x2f,0x1d,0xbe,0x71,0xf8,0xde,0x81,0x1c,0xad,0x1c,0x05,0x8e,0xb6,0x8e,0x43,0x1d, -0x1d,0x1c,0xa5,0x8e,0xae,0x8e,0xee,0x8e,0x9e,0x8e,0x2a,0x47,0x5f,0xc7,0x40,0xc7, -0x60,0xc7,0x30,0xc7,0x28,0x47,0x83,0xe3,0x6d,0x8e,0x77,0x39,0xde,0xeb,0x78,0xbf, -0xe3,0x43,0x8e,0x44,0x8f,0x39,0xfe,0xc3,0xf1,0x69,0xc7,0xc9,0x8e,0xcf,0x3a,0xce, -0x74,0x9c,0xeb,0xf8,0xbc,0xe3,0x8b,0x8e,0x8b,0x1c,0x19,0xc7,0x64,0xc7,0x74,0xc7, -0x65,0x8e,0x2b,0x1c,0xd7,0x38,0x6e,0x70,0xcc,0x75,0xdc,0xe4,0xb8,0xc5,0x71,0xbb, -0xe3,0x2e,0xc7,0x3d,0x8e,0x7b,0x1d,0xab,0x1d,0xeb,0x1d,0x9b,0x1d,0x0f,0x38,0x1e, -0x76,0x3c,0xea,0x78,0xdc,0xf1,0xa4,0xe3,0xdb,0x8e,0xef,0x3b,0x9e,0x75,0xfc,0xc8, -0xb1,0xd7,0xf1,0x63,0xc7,0xcf,0x1c,0xbf,0x74,0xfc,0xc6,0xf1,0x7b,0x47,0x93,0x57, -0xec,0x71,0x67,0x22,0x27,0x2b,0x27,0x81,0x93,0xad,0xd3,0x50,0x27,0x07,0x27,0x37, -0x91,0xd4,0xc9,0xd5,0xc9,0xdd,0xc9,0xd3,0xc9,0xff,0x0f,0xdf,0x7f,0x55,0x4e,0xfe, -0x4e,0xc1,0x4e,0x11,0x4e,0x06,0xa7,0x3b,0x9c,0xee,0x75,0x7a,0xd0,0xe9,0x31,0xa7, -0xf1,0x4e,0x93,0x9d,0xa6,0x3b,0xcd,0x75,0x5a,0xe0,0xb4,0xc8,0x29,0xd1,0x29,0xdd, -0x29,0xd3,0x69,0x8d,0x53,0xb6,0xd3,0x26,0xa7,0x6d,0x4e,0xbb,0x9c,0x2a,0x9c,0xaa, -0x9d,0x1a,0x9d,0x0e,0x38,0x1d,0x71,0x3a,0xee,0x74,0xda,0xe9,0x8c,0x53,0xbf,0xb1, -0xd7,0xe9,0x53,0xa7,0x2f,0x9d,0xbe,0x73,0x22,0xe7,0x41,0xce,0xb6,0xce,0x62,0x67, -0xa9,0xb3,0x9b,0xb3,0xa7,0xb3,0xb7,0x73,0xa0,0x73,0xa8,0x73,0x94,0x73,0x9c,0xf3, -0x5d,0xce,0xa3,0x9d,0x1f,0x72,0x7e,0xc2,0xf9,0x69,0xe7,0x29,0xce,0x33,0x9d,0xe7, -0x39,0xbf,0xe8,0xfc,0x9a,0x73,0xb2,0xf3,0x52,0xe7,0x15,0xce,0xeb,0x9c,0x73,0x9d, -0x37,0x3b,0x6f,0x77,0xde,0xed,0xbc,0xd7,0xb9,0xd6,0xb9,0xd9,0xf9,0x80,0xb3,0x93, -0xf8,0xb0,0xf3,0x4f,0xed,0x3b,0xea,0x7c,0xdc,0xb9,0xd3,0xf9,0xb4,0xf3,0x3b,0xce, -0xef,0x3b,0x9f,0x71,0x3e,0xef,0xdc,0xe5,0xdc,0xeb,0x7c,0xc9,0xf9,0x53,0xe7,0xcf, -0x9d,0xbf,0x74,0xbe,0xea,0xfc,0x9d,0xf3,0x32,0x9c,0xd9,0x2d,0x38,0xa7,0xec,0x59, -0x7c,0x9f,0x3b,0x7f,0x37,0xce,0xde,0x0f,0xce,0x16,0x52,0x6b,0xe9,0x60,0xa9,0x9d, -0x54,0x24,0x75,0x94,0xba,0x48,0x47,0x48,0x47,0x4a,0x15,0x52,0xb5,0xd4,0x4f,0x1a, -0x24,0x0d,0x91,0x46,0x49,0xe3,0xa5,0x37,0x7c,0x7b,0xa7,0x74,0x94,0x74,0x8c,0x34, -0x42,0x34,0x56,0x3a,0x5d,0xa4,0xc1,0xbd,0x76,0x3c,0xee,0xc2,0xdb,0x24,0x4b,0x45, -0xde,0xe2,0x47,0xa5,0xe3,0xa4,0x4f,0x49,0x27,0x49,0xa7,0x4a,0x67,0x48,0xe7,0x48, -0xe7,0x4b,0x17,0x4a,0x5f,0x91,0x6e,0x16,0x25,0x48,0x93,0xa4,0x69,0xd2,0x0c,0x69, -0x96,0x74,0xb5,0x74,0xbd,0x34,0x47,0x6a,0x3a,0x37,0x05,0xd2,0x22,0x69,0xb1,0xb4, -0x44,0x5a,0x26,0xad,0x94,0x56,0x49,0xeb,0xa4,0x4d,0xd2,0x16,0x69,0xab,0xf4,0x88, -0xf4,0x4d,0xe9,0x71,0x69,0xa7,0xf4,0xb4,0xf4,0x1d,0xe9,0xfb,0xd2,0x33,0xd2,0xf3, -0xd2,0x2e,0x69,0xaf,0xf4,0x92,0xf4,0x53,0xe9,0xe7,0xd2,0x2f,0xa5,0xdf,0x4a,0xfb, -0xa5,0x3c,0x17,0x81,0x8b,0x9d,0x8b,0xd8,0xc5,0xd9,0xc5,0xd5,0x65,0xa4,0x8b,0xd2, -0xc5,0xcf,0x45,0xeb,0x12,0xea,0x12,0xe1,0x12,0xed,0x12,0xeb,0x72,0xbb,0xcb,0xdd, -0x2e,0xf7,0xb9,0x3c,0xe0,0xf2,0xb0,0xcb,0xe3,0x2e,0x4f,0xba,0x4c,0x70,0x79,0xc6, -0x65,0x9a,0xcb,0x2c,0x97,0xe7,0x5c,0x5e,0x70,0x79,0xc9,0xe5,0x55,0x97,0xc5,0x2e, -0x29,0x2e,0x4b,0x5c,0x96,0xbb,0xac,0x74,0x59,0xeb,0xb2,0xd1,0x25,0xcf,0xa5,0xd0, -0x65,0xab,0xcb,0x0e,0x97,0x52,0x97,0x72,0x97,0x7d,0x2e,0x35,0x2e,0x0d,0x2e,0xff, -0x74,0x39,0xe8,0xd2,0xe6,0xf2,0xa6,0x4b,0x87,0xcb,0x29,0x97,0x77,0x5c,0x3e,0x70, -0x39,0xe7,0xd2,0xe5,0xd2,0xe7,0xf2,0x89,0xcb,0xe7,0x2e,0x5f,0xb9,0x7c,0xeb,0xf2, -0x83,0x8b,0xd5,0x30,0xd1,0x30,0xb7,0x61,0x3e,0xc3,0x42,0x86,0xc5,0x0d,0xbb,0x77, -0xd8,0x23,0xc3,0xa6,0x0c,0x9b,0x3f,0xec,0xd5,0x61,0x69,0xc3,0x56,0x0e,0xcb,0x1f, -0x56,0x32,0x6c,0xdf,0xb0,0xa6,0x61,0x87,0x86,0x1d,0x1f,0xf6,0xc1,0xb0,0x8b,0xc3, -0x3e,0x1f,0x66,0x31,0xdc,0x61,0xb8,0xe7,0x70,0xff,0xe1,0x31,0xc3,0x47,0x0f,0x1f, -0x37,0xfc,0x99,0xe1,0x73,0x86,0xbf,0x34,0x3c,0x75,0xf8,0xba,0xe1,0x45,0xc3,0x4b, -0x87,0x57,0x0d,0xff,0xe7,0xf0,0xc3,0xc3,0xdb,0x87,0x9f,0x1e,0xfe,0xc1,0xf0,0x9e, -0xe1,0x9f,0x0f,0xb7,0x70,0xb5,0x71,0x95,0xba,0xaa,0x5c,0x35,0xae,0xe1,0xae,0x06, -0xd7,0xfb,0x5d,0x9f,0x74,0x9d,0xe5,0xba,0xd0,0x35,0xcd,0x35,0xd3,0x75,0x83,0xeb, -0x66,0xd7,0x5d,0xae,0xfb,0x5d,0x9b,0x5d,0xdf,0x74,0x7d,0xcb,0xf5,0x43,0xd7,0x5e, -0xd7,0x2f,0x5c,0xbf,0x77,0x1d,0x3c,0x42,0x32,0x62,0xc4,0x08,0xe5,0x08,0xdd,0x88, -0xdb,0x46,0x8c,0x1a,0x31,0x6e,0xc4,0xc4,0x11,0x73,0x46,0x2c,0x1a,0x91,0x3a,0x62, -0xc5,0x88,0xec,0x11,0xc5,0x23,0x76,0x8f,0xa8,0x1e,0x71,0x70,0xc4,0xd1,0x11,0xef, -0x8f,0xe8,0x1e,0x71,0x69,0xc4,0x67,0x23,0xbe,0x1a,0xf1,0xdd,0x08,0xe3,0x08,0x2b, -0xb7,0xc1,0x6e,0xf6,0x6e,0x12,0x37,0xa9,0x9b,0x9b,0x9b,0xa7,0x9b,0xb7,0x5b,0xa0, -0x5b,0xa8,0x5b,0x94,0x5b,0x9c,0xdb,0x5d,0x6e,0xa3,0xdd,0x1e,0x72,0x7b,0xc2,0xed, -0x69,0xb7,0x29,0x6e,0x33,0xdd,0xe6,0xb9,0x2d,0x70,0x7b,0xc9,0x6d,0x91,0x1b,0xe3, -0x96,0xe4,0x96,0xea,0xb6,0xc4,0x6d,0xb9,0xdb,0x4a,0xb7,0x35,0x6e,0x39,0x6e,0x9b, -0xdd,0x76,0xb8,0xed,0x71,0x6b,0x70,0x6b,0x76,0x6b,0x71,0x6b,0x75,0x3b,0xe2,0xf6, -0xa6,0xdb,0x71,0xb7,0x4e,0xb7,0xd3,0x6e,0xef,0xba,0x9d,0x71,0x3b,0xef,0xd6,0xe5, -0xd6,0xe7,0xf6,0x89,0xdb,0x67,0x6e,0x57,0xdc,0xbe,0x76,0xfb,0xce,0xad,0xdf,0xcd, -0x52,0x36,0x48,0x36,0x44,0x66,0x27,0x1b,0x2a,0x93,0xc8,0x9c,0x64,0x2e,0xb2,0x11, -0x32,0x77,0x99,0x5c,0xe6,0x25,0xf3,0x96,0xf9,0xcb,0x82,0x64,0x21,0xb2,0x70,0x59, -0xb4,0xcc,0x20,0xbb,0x4d,0x76,0x8f,0xac,0xc1,0xed,0x01,0xd9,0xc3,0xb2,0xc7,0x65, -0x4f,0xca,0x26,0xc8,0xa6,0xc9,0xe6,0xc9,0x5e,0x90,0xbd,0x28,0x5b,0x24,0x4b,0x90, -0x25,0xca,0x52,0x64,0x4b,0x64,0xcb,0x40,0x96,0x6c,0x95,0x6c,0xad,0x6c,0xa3,0x2c, -0x17,0x14,0xc8,0x8a,0x64,0xfd,0xc6,0x62,0xd9,0x4e,0x59,0xa9,0x6c,0x0f,0x72,0x55, -0xb2,0x5a,0x59,0x83,0xac,0x59,0xd6,0x22,0x3b,0x84,0xf6,0xdb,0x64,0x47,0x65,0xed, -0xb2,0x13,0xb2,0xd3,0xb2,0xe3,0x6e,0xef,0xc8,0x3e,0x90,0x9d,0x43,0x89,0x8f,0x64, -0xbd,0xb2,0x4b,0xb2,0x4f,0x65,0x9f,0xcb,0xbe,0x94,0x7d,0x27,0x23,0x77,0x6b,0xf7, -0x21,0xee,0x42,0x77,0x07,0x77,0x17,0x77,0x37,0x77,0xb9,0xbb,0xca,0xdd,0xcf,0x5d, -0xeb,0x1e,0xee,0xae,0x77,0xbf,0xdd,0x7d,0x94,0xfb,0x03,0xee,0x8f,0xba,0x3f,0xe9, -0x3e,0xc9,0x7d,0x9a,0xfb,0x1c,0xf7,0x17,0xdc,0x5f,0x71,0x5f,0xec,0x9e,0xe6,0xbe, -0xdc,0x7d,0xb5,0xfb,0x46,0xf7,0x02,0xf7,0xad,0xee,0x25,0xee,0xe5,0xee,0xfb,0xdc, -0x8d,0xc6,0x1a,0xd0,0x00,0xfe,0x09,0x0e,0x82,0x4e,0xbe,0xd1,0xd8,0x86,0xf4,0x4d, -0xd0,0x01,0x4e,0x81,0x77,0xc0,0x07,0xe0,0x1c,0xe8,0x02,0x7d,0xe0,0x13,0xf0,0x39, -0xf8,0x0a,0x7c,0x0b,0x7e,0x00,0x16,0x23,0x8d,0x46,0x6b,0x30,0x78,0xe4,0x6f,0xbb, -0xff,0xd8,0xa1,0x9c,0x08,0x38,0x02,0x17,0x30,0x02,0x8c,0x04,0x0a,0xa0,0x06,0x7e, -0x20,0x08,0x84,0x80,0x70,0x10,0x0d,0x62,0x81,0x9b,0x95,0xa7,0xd5,0xff,0x96,0xa7, -0xd8,0xed,0xb0,0xe7,0x6e,0x70,0x1f,0x78,0x00,0x3c,0x0c,0x1e,0x07,0x4f,0x9a,0x7d, -0x30,0x01,0xe9,0x33,0x60,0x1a,0x98,0x05,0x9e,0x03,0x2f,0x80,0x97,0xc0,0xab,0x60, -0x31,0x48,0x01,0x4b,0xc0,0x72,0xb0,0x12,0xac,0x05,0x1b,0x41,0x1e,0x28,0x04,0x5b, -0xc1,0x0e,0x50,0x0a,0xca,0xc1,0x3e,0x50,0x03,0x1a,0xc0,0x3f,0xc1,0x41,0xd0,0x06, -0xde,0x04,0x1d,0xe0,0x14,0x78,0xc7,0xcc,0x8f,0x3f,0x1f,0x60,0xcf,0x39,0xd0,0x35, -0xb2,0x0f,0xf1,0x27,0xe0,0x73,0xf0,0x15,0xf8,0xf6,0x7a,0xd9,0x1f,0x90,0xab,0xe6, -0xbe,0x1b,0xb3,0xf0,0xc0,0xf9,0xf4,0x30,0x7f,0xa7,0x8d,0xd4,0x0e,0x88,0x80,0x23, -0x70,0x01,0x23,0xc0,0x48,0xa0,0x00,0x6a,0xe0,0x07,0xaa,0x2c,0x37,0x0a,0xf6,0x09, -0x82,0x3c,0xea,0xd0,0x42,0xb0,0x87,0xce,0xe3,0x75,0x41,0x84,0x47,0x0c,0xf6,0xc7, -0x81,0x3b,0xc0,0x3d,0x60,0x34,0x78,0x10,0x3c,0x02,0x9e,0x00,0xe3,0x3d,0x3e,0x14, -0x4c,0xf4,0x98,0x82,0xdc,0x74,0x30,0xdb,0xe3,0x39,0xc4,0x2f,0x80,0x97,0xc0,0xab, -0x60,0x31,0x48,0x01,0x4b,0xc0,0x72,0xb0,0xd2,0xe3,0xb7,0xce,0xf0,0x14,0x64,0x4d, -0x36,0x34,0x92,0xd6,0x70,0xbd,0xf8,0x8a,0x22,0x2c,0xfe,0x8c,0x73,0x7e,0xdf,0xc8, -0x0d,0x1e,0x43,0x86,0xe6,0x7a,0xfc,0xfc,0xc8,0x26,0x8f,0x9b,0xd7,0x61,0x45,0x1e, -0xc5,0x1e,0x25,0x1e,0x65,0x1e,0x95,0x1e,0x55,0x1e,0x75,0x1e,0x4d,0x1e,0x2d,0x1e, -0xad,0x1e,0x6f,0x78,0xb4,0x7b,0x74,0x7a,0xbc,0xe5,0xf1,0x9e,0xc7,0x19,0x8f,0x0b, -0x1e,0x3d,0x1e,0x97,0x3c,0x2e,0x7b,0x5c,0xf1,0xb8,0xea,0x71,0xcd,0xc3,0xe8,0xc1, -0x93,0xf3,0xe5,0x36,0x72,0xa1,0x5c,0x22,0x77,0x96,0x0f,0x97,0xcb,0xe4,0x72,0xb9, -0x52,0xee,0x23,0x0f,0x90,0x6b,0xe5,0x3a,0x79,0xa4,0x5c,0x2f,0x8f,0x97,0xdf,0x29, -0x1f,0x25,0x1f,0x23,0x1f,0x2b,0x7f,0x54,0x3e,0x4e,0xfe,0x94,0x7c,0x92,0x7c,0xaa, -0x7c,0x86,0x7c,0x8e,0x7c,0xbe,0x7c,0xa1,0xfc,0x15,0x79,0x82,0x3c,0x49,0x9e,0x26, -0xcf,0x90,0x67,0xc9,0x57,0xcb,0xd7,0xcb,0x73,0xe4,0x05,0xf2,0x22,0x79,0xb1,0xbc, -0x44,0x5e,0x26,0xaf,0x94,0x57,0xc9,0xeb,0xe4,0x4d,0xf2,0x16,0x79,0xab,0xfc,0x0d, -0x79,0xbb,0xbc,0x53,0xfe,0x96,0xfc,0x3d,0xf9,0x19,0xf9,0x05,0x79,0x8f,0x3c,0x55, -0x72,0x49,0x7e,0x59,0x7e,0x45,0x7e,0x55,0x3e,0x1e,0xf3,0xec,0x6b,0xf2,0x6d,0x12, -0xa3,0x9c,0xe7,0xc9,0xf7,0xb4,0xf1,0x14,0x7a,0x4a,0x3c,0x9d,0x3d,0x87,0x7b,0xca, -0x3c,0xe5,0x9e,0x4a,0x4f,0x1f,0xcf,0x00,0x4f,0xad,0xa7,0xce,0x33,0xd2,0x53,0xef, -0x19,0xef,0x79,0xa7,0xe7,0x28,0xcf,0x31,0x9e,0x63,0x3d,0x1f,0xf5,0x1c,0xe7,0xf9, -0x94,0xe7,0x24,0xcf,0xa9,0x9e,0x33,0x3c,0xe7,0x78,0xce,0xf7,0x5c,0xe8,0xf9,0x8a, -0x67,0x82,0x67,0x92,0x67,0x9a,0x67,0x86,0x67,0x96,0xe7,0x6a,0xcf,0xf5,0x9e,0x39, -0x9e,0x05,0x9e,0xfd,0xe2,0x22,0xcf,0x62,0xcf,0x12,0xcf,0x32,0xcf,0x4a,0xcf,0x2a, -0xcf,0x3a,0xcf,0x26,0xcf,0x16,0xcf,0x56,0xcf,0x37,0x3c,0x3d,0x1c,0xda,0x3d,0x3b, -0x3d,0xdf,0xf2,0x7c,0xcf,0xf3,0x8c,0xe7,0x05,0xcf,0x1e,0xcf,0x4b,0x9e,0x97,0x3d, -0xaf,0x78,0x5e,0xf5,0xbc,0xe6,0x69,0xf4,0xe4,0x29,0xf8,0x0a,0x1b,0x85,0x50,0x21, -0x51,0x38,0x2b,0x86,0x2b,0x64,0x0a,0xb9,0x42,0xa9,0xf0,0x51,0x04,0x28,0xb4,0x0a, -0x9d,0x42,0xee,0x19,0xa9,0xd0,0x2b,0xe2,0x15,0x77,0x2a,0x46,0x29,0xc6,0x28,0xc6, -0x2a,0x1e,0x55,0x8c,0x53,0x3c,0xa5,0x98,0xa4,0x98,0xaa,0x98,0xa1,0x98,0xa3,0x98, -0xaf,0x58,0xa8,0x78,0x45,0x91,0xa0,0x48,0x52,0xa4,0x29,0x32,0x25,0x19,0x8a,0x2c, -0xc5,0x6a,0xc5,0x7a,0x45,0x8e,0xa2,0x40,0x51,0xa4,0x28,0x56,0x94,0x28,0xca,0x14, -0x95,0x8a,0x2a,0x45,0x9d,0xa2,0x49,0xd1,0xa2,0xd8,0x2c,0x6a,0x55,0xbc,0xa1,0x68, -0x57,0x74,0x2a,0xde,0x52,0xbc,0xa7,0x38,0xa3,0xb8,0xa0,0x78,0x44,0xd4,0xa3,0xb8, -0xa4,0xb8,0xac,0xb8,0xa2,0xb8,0xaa,0xb8,0xa6,0x30,0x2a,0x78,0x5e,0x7c,0x2f,0x1b, -0x2f,0xa1,0x97,0xc4,0xcb,0xd9,0x6b,0xb8,0x97,0xcc,0x4b,0xee,0xa5,0xf4,0xf2,0xf1, -0x0a,0xf0,0xd2,0x7a,0xe9,0xbc,0x22,0xbd,0x92,0x14,0x7a,0xaf,0x78,0xaf,0x3b,0xbd, -0x46,0x79,0x8d,0xf1,0x1a,0xeb,0xf5,0xa8,0xd7,0x38,0xaf,0x78,0xc5,0x53,0x5e,0x93, -0xbc,0xa6,0x7a,0xcd,0xf0,0x9a,0xe3,0x35,0xdf,0x6b,0xa1,0xd7,0x2b,0x5e,0x09,0x5e, -0x49,0x5e,0x69,0x5e,0x19,0x5e,0x59,0x5e,0xab,0xbd,0xd6,0x7b,0xe5,0x78,0x15,0x78, -0x15,0x79,0x15,0x7b,0x95,0x78,0xc9,0x3d,0xcb,0xbc,0x2a,0xbd,0xaa,0xbc,0xea,0xbc, -0x9e,0x70,0x68,0xf2,0x6a,0xf1,0x6a,0xf5,0x7a,0xc3,0xab,0xdd,0xab,0xd3,0xeb,0x2d, -0xaf,0xf7,0xbc,0xce,0x78,0x5d,0xf0,0xea,0xf1,0xba,0xe4,0x35,0x4a,0x7a,0xd9,0xeb, -0x8a,0xd7,0x55,0xaf,0x6b,0x5e,0x46,0x2f,0x9e,0x92,0xaf,0xb4,0x51,0x0a,0x95,0xa3, -0x14,0x12,0xa5,0xb3,0x72,0xb8,0x52,0xa6,0x94,0x2b,0x95,0x4a,0x1f,0x65,0x80,0x52, -0xab,0xd4,0x29,0x23,0x95,0x7a,0x65,0xbc,0x72,0xb3,0xe4,0x4e,0xe5,0x28,0xe5,0x18, -0xe5,0x58,0xe5,0xa3,0xca,0x71,0xca,0xa7,0x94,0x93,0x94,0x53,0x95,0x33,0x94,0x73, -0x94,0xf3,0x95,0x0b,0x95,0x1f,0x8a,0x5f,0x51,0x26,0x28,0x93,0x94,0x69,0xca,0x0c, -0x65,0x96,0x72,0xb5,0x72,0xbd,0x32,0x47,0x59,0xa0,0x2c,0x52,0x16,0x2b,0x4b,0x94, -0x65,0xca,0x4a,0x65,0x95,0xf2,0x1e,0x49,0x9d,0xf2,0x11,0x49,0x93,0xb2,0x45,0xd9, -0xaa,0x64,0xc7,0xf1,0x1b,0x88,0xdb,0xb9,0x5c,0xa7,0xf2,0x2d,0xe5,0x7b,0xca,0x33, -0xca,0x0b,0xca,0x1e,0xe5,0x25,0xe5,0x65,0xe5,0x15,0xe5,0x77,0x62,0xa3,0xf1,0x2a, -0x8e,0x5d,0xe3,0x8e,0x1b,0x95,0x3c,0x95,0xf9,0x4d,0xbe,0xca,0x46,0x25,0x54,0x49, -0x54,0xce,0xaa,0xe1,0x2a,0x99,0x4a,0xae,0x52,0xaa,0x7c,0x54,0x01,0x2a,0xad,0x4a, -0xa7,0x8a,0x54,0xe9,0x55,0xf1,0xaa,0x3b,0x55,0xa3,0x54,0x8d,0xa2,0x31,0xaa,0xb1, -0xaa,0x47,0x55,0xe3,0x54,0x4f,0xa9,0x26,0xa9,0xa6,0xaa,0x66,0xa8,0xe6,0xa8,0xe6, -0xab,0x16,0xaa,0x5e,0x51,0x25,0xa8,0x92,0x54,0x69,0xaa,0xa7,0xa4,0x19,0xaa,0x2c, -0xd5,0x6a,0xd5,0x7a,0xd5,0x1c,0x69,0x8e,0xaa,0x40,0x55,0xa4,0x2a,0x56,0x95,0xa8, -0x24,0x5e,0x65,0xaa,0x4a,0x55,0x95,0xaa,0x4e,0xd5,0xa4,0x6a,0x41,0x68,0x55,0xbd, -0xa1,0x6a,0x57,0x75,0xaa,0xde,0x52,0xbd,0xa7,0x3a,0xa3,0xba,0xa0,0x32,0x2a,0x7b, -0x54,0x97,0x54,0x97,0x55,0x57,0x54,0x57,0x55,0xdf,0xab,0xb8,0xef,0x62,0xd4,0x56, -0x6a,0x81,0xda,0x56,0x3d,0x54,0xed,0xa0,0x96,0xaa,0x5d,0xd5,0x3a,0x95,0xbb,0xda, -0x53,0xad,0x52,0xbf,0xa1,0xf4,0x55,0x07,0xaa,0x83,0xd5,0x61,0xea,0x28,0xb5,0x41, -0x7d,0x9b,0xfa,0x2e,0xf5,0xbd,0xea,0xfb,0xd5,0x0f,0xa9,0x1f,0x53,0x3f,0xaa,0xfa, -0x87,0x7a,0x9c,0xea,0x69,0xf5,0x64,0xf5,0xb3,0xea,0x99,0xea,0xb9,0xea,0x76,0xe5, -0x42,0xcf,0xe7,0xd5,0x2f,0xaa,0xe3,0xc4,0x69,0x8a,0x4e,0xaf,0x45,0x6a,0x46,0xbd, -0x50,0x95,0xac,0x7e,0x45,0x95,0xae,0x5e,0xa6,0x5e,0xa1,0x7e,0x4b,0xb9,0x46,0xbd, -0x41,0x9d,0xab,0xde,0xa4,0xde,0xa2,0x7e,0x4f,0xb9,0x5d,0xbd,0x4b,0xbd,0x47,0xbd, -0x57,0x5d,0xad,0xae,0x57,0x97,0xa8,0x9a,0xd5,0x07,0xd4,0x12,0xaf,0xc3,0xea,0x3a, -0xd5,0x51,0xf5,0x71,0xf5,0x49,0xf5,0xdb,0xea,0xf7,0xd5,0xed,0xaa,0xb3,0xea,0x6b, -0xca,0x8f,0xd4,0x9d,0xaa,0x48,0x45,0xaf,0xfa,0x2d,0xd5,0xc7,0xea,0x33,0xaa,0xcf, -0xd4,0x5f,0xaa,0xbf,0x51,0x7f,0xaf,0x26,0xef,0x1e,0xd5,0x65,0xa5,0x95,0xf7,0x25, -0x95,0xc0,0xfb,0xb2,0xca,0xd6,0x7b,0xbc,0xc3,0x50,0x6f,0x47,0xef,0x61,0xde,0x32, -0x6f,0xb9,0xb7,0xd2,0xdb,0xc7,0x3b,0xd0,0x3b,0xc4,0x3b,0xc2,0x3b,0xc6,0xfb,0xcf, -0x5c,0xff,0xc6,0x79,0xdf,0xee,0x7d,0x97,0xf7,0x28,0xef,0x31,0xde,0x63,0x11,0x7e, -0xa9,0xd4,0x23,0xde,0xe3,0xbc,0x9f,0xf6,0x7e,0xc6,0x7b,0xfa,0x4d,0x25,0xe6,0x78, -0x63,0x1d,0xea,0xfd,0x92,0xf7,0x22,0xbb,0x14,0xbb,0x45,0xde,0x09,0xde,0x89,0xde, -0x29,0xde,0xe9,0xde,0x19,0xde,0xb7,0xd9,0x65,0x7a,0xaf,0xf6,0xde,0xe0,0x5d,0xe0, -0xbd,0xcd,0xbb,0xc4,0x7b,0x8f,0xf7,0x3e,0xef,0x5a,0xef,0x26,0xef,0x03,0xde,0x6d, -0xde,0xc7,0x50,0xbb,0xd3,0xfb,0x6d,0xef,0x0f,0xbc,0xcf,0x7b,0xf7,0x20,0xff,0x31, -0xf8,0xdc,0xfb,0x6b,0xc4,0xd7,0xbc,0xc9,0xa7,0xdf,0x68,0xed,0x33,0xc4,0x47,0xe8, -0xe3,0xe0,0xe3,0xe2,0xe3,0xe6,0x23,0xf7,0x51,0xf9,0xf8,0xf9,0x68,0x7c,0x74,0x08, -0x11,0xe6,0x10,0x6d,0x0e,0x06,0x73,0x88,0x37,0x87,0x3b,0xcc,0xe1,0x6e,0x73,0xb8, -0xd7,0x1c,0xc6,0x98,0xc3,0x83,0xe6,0xf0,0xb0,0x39,0x3c,0x66,0x0e,0xe3,0xcc,0x61, -0x3c,0xc2,0x04,0x84,0xc9,0x08,0x53,0x11,0xa6,0x23,0xcc,0x42,0x98,0x6b,0x0e,0xf3, -0xcd,0x61,0x81,0x39,0xbc,0x64,0x0e,0x8b,0x10,0x12,0xcc,0x21,0x11,0x21,0xc5,0x1c, -0xd2,0xcd,0x21,0x03,0x21,0x13,0xe1,0x8f,0x3f,0x6d,0x56,0xfa,0x98,0xc2,0x1a,0x84, -0xf5,0x08,0xd9,0x08,0x1f,0x58,0xe7,0xf9,0xe4,0xf9,0x6c,0x42,0x28,0x42,0xd8,0x66, -0x0e,0x3b,0x10,0x76,0x21,0x94,0x23,0xec,0x47,0xa8,0x43,0x68,0x46,0x38,0x88,0x70, -0x84,0x0b,0xed,0x5c,0x38,0x69,0x0e,0x6f,0xf9,0xbc,0xef,0x73,0x0e,0xdb,0xdd,0x3e, -0x97,0x7c,0x3e,0xf3,0xf9,0xca,0xe7,0x3b,0x1f,0xa3,0x8f,0x95,0xef,0x60,0x5f,0x7b, -0x5f,0x89,0xaf,0xd4,0x77,0x84,0xaf,0x87,0xaf,0xd2,0xd7,0xd7,0x37,0xc8,0x37,0xd4, -0x37,0xd2,0xd7,0xe0,0x7b,0xbb,0xef,0x3d,0xbe,0x63,0x7c,0x1f,0xf2,0x7d,0xdc,0x77, -0xbc,0xef,0x24,0xdf,0x67,0x7d,0x67,0xf9,0xce,0xf3,0x5d,0xe8,0xbb,0xc8,0x77,0xb1, -0x6f,0xaa,0x6f,0x86,0xef,0x0a,0xdf,0xb5,0xbe,0xd9,0xbe,0x05,0xbe,0x5b,0x7c,0x77, -0xf8,0xee,0xf6,0xad,0xf4,0xad,0xf6,0x6d,0xf0,0x7d,0xdd,0xb7,0xd5,0xf7,0xa8,0x6f, -0x87,0xef,0x69,0xdf,0xf7,0x7c,0xcf,0xfa,0x76,0xf9,0x5e,0xf4,0xbd,0xec,0xfb,0xa5, -0xef,0xb7,0xbe,0xfd,0xbe,0x3c,0x3f,0x81,0x9f,0x9d,0x9f,0xd8,0xcf,0xd9,0xcf,0xd5, -0x6f,0xa4,0x9f,0x97,0x9f,0x8f,0x5f,0xa0,0x5f,0x88,0x5f,0x84,0x9f,0xde,0xef,0x36, -0xbf,0xbb,0xfd,0x46,0xfb,0x8d,0xf5,0x7b,0xcc,0xef,0x49,0xbf,0x89,0x7e,0x53,0xfd, -0x66,0xfa,0x3d,0xe7,0xb7,0xc0,0xef,0x15,0x3f,0xc6,0x2f,0xc5,0x6f,0xa9,0x5f,0x96, -0xdf,0x1a,0xbf,0x8d,0x7e,0xf9,0x7e,0x45,0x7e,0xdb,0xfd,0x76,0xfb,0xed,0xf5,0xab, -0xf5,0x6b,0xf6,0x3b,0xe4,0x77,0xd4,0xaf,0xc3,0xef,0x9c,0xcf,0x69,0x3f,0xb6,0x27, -0xef,0xf9,0x9d,0xf5,0xfb,0xce,0xa7,0xcb,0x8f,0xed,0xc9,0x45,0xbf,0xcb,0x7e,0x52, -0xdf,0x2f,0xfd,0xd8,0x9e,0x7c,0xeb,0xd7,0xef,0xe7,0xeb,0xcb,0xf3,0x67,0x7b,0xc3, -0x5a,0x65,0xb2,0x68,0xc0,0x16,0x93,0x15,0x02,0xff,0x08,0x3f,0x3b,0x7f,0xb1,0xff, -0x63,0x7e,0xce,0xfe,0xac,0x7a,0x91,0x9f,0xab,0xff,0x48,0xff,0xa5,0x7e,0x5e,0xfe, -0xac,0x2e,0xeb,0x2d,0x1f,0xff,0x73,0x3e,0x81,0xfe,0x03,0xde,0x0a,0xf1,0x1f,0xf0, -0x55,0x84,0xbf,0xef,0x4d,0x3e,0xba,0xd9,0x43,0x7a,0xff,0x9b,0x7d,0xf4,0x73,0x0f, -0xb1,0xde,0xb9,0xe1,0x97,0x01,0x9f,0xdc,0xe6,0x3f,0xe0,0x91,0x01,0x6f,0xdc,0xed, -0xcf,0xfa,0x62,0xb4,0xff,0x80,0x27,0xc6,0x72,0x96,0xb0,0x36,0x98,0xd4,0x1f,0xf3, -0x9f,0xe5,0xfb,0xa4,0xff,0x44,0xff,0xa9,0xfe,0x6c,0x3b,0xae,0x5c,0x0d,0xb6,0xd4, -0x4c,0xff,0xf9,0xfe,0x2f,0xfb,0x2f,0xf6,0x4f,0xf5,0xcf,0xf0,0x5f,0xe1,0xbf,0xd6, -0x3f,0xdb,0xbf,0xc0,0x7f,0x8b,0xff,0x0e,0xff,0xdd,0xfe,0x95,0xfe,0xd5,0xfe,0x0d, -0xfe,0xaf,0xfb,0xb7,0xfa,0x1f,0xf5,0xef,0xf0,0x3f,0xed,0xff,0x9e,0xff,0x59,0xff, -0x2e,0xff,0x8b,0xfe,0x97,0xd1,0xca,0x97,0xfe,0xdf,0xfa,0xf7,0xfb,0xf3,0x02,0x7e, -0x6f,0xbd,0x81,0xed,0x27,0x61,0xcf,0x5a,0xb3,0x37,0x4c,0xfb,0xd8,0x51,0x22,0x08, -0x10,0x04,0xfc,0xa6,0xf5,0x41,0x80,0x03,0xc2,0xf0,0x00,0x8f,0x00,0x75,0x40,0x60, -0x80,0x2e,0x20,0x06,0xe1,0xf6,0x80,0x7b,0x03,0xc6,0x06,0x3c,0x11,0x30,0x21,0xe0, -0x59,0x84,0x39,0x01,0x0b,0x10,0x5e,0x45,0x48,0x0e,0xc8,0x40,0x58,0x15,0xb0,0x11, -0x61,0x13,0x42,0x71,0xc0,0x6e,0x84,0x7d,0x08,0xf5,0x01,0x2d,0x01,0x47,0x02,0x3a, -0x10,0xde,0x0e,0x38,0x13,0xd0,0x1d,0xf0,0x49,0xc0,0x97,0x08,0xd7,0x02,0x2c,0x03, -0x07,0x07,0x0e,0x0d,0x74,0x0e,0xc4,0xd2,0x31,0x50,0x81,0xe0,0x8b,0xa0,0x0d,0x8c, -0x08,0x8c,0x0d,0xbc,0x2b,0x70,0x4c,0xe0,0x23,0x81,0x4f,0x06,0xb2,0x36,0x4c,0x0e, -0x9c,0x11,0x38,0x2f,0xf0,0xa5,0x40,0x26,0x30,0x0d,0x21,0x33,0x70,0x6d,0x60,0x6e, -0x60,0x11,0xc2,0xce,0xc0,0xf2,0xc0,0xea,0xc0,0xa6,0xc0,0x43,0x81,0x6f,0x06,0x9e, -0x0c,0x7c,0x2f,0xf0,0x7c,0x60,0x5f,0xe0,0x67,0x81,0x57,0x03,0xfb,0x03,0xad,0x83, -0x6c,0x83,0x24,0x41,0xc3,0x82,0x46,0x06,0xa9,0x82,0x02,0x82,0x42,0x83,0xa2,0x83, -0xae,0x05,0x0c,0x0e,0xbc,0x2d,0x68,0x54,0xd0,0x83,0x41,0x8f,0x07,0x3d,0x1d,0x34, -0x15,0x3c,0x18,0x34,0x3b,0xe8,0x85,0xa0,0x45,0x41,0x49,0x41,0x4b,0xb1,0x7d,0x04, -0xd6,0xaf,0x0c,0xda,0x10,0xf4,0xfb,0xbf,0x09,0xbd,0x75,0x28,0x08,0xda,0x16,0x54, -0x1a,0xb4,0x3f,0xa8,0x29,0xe8,0x70,0x50,0x47,0xd0,0xbb,0x41,0x17,0x82,0x3e,0x0e, -0xba,0x12,0xe4,0xa8,0x51,0x68,0x7e,0xfa,0x9b,0x7e,0xb7,0xfe,0xf8,0x6b,0x02,0x35, -0xfd,0xc6,0x60,0x8d,0xd1,0x32,0x54,0x13,0xa6,0x89,0xd0,0xc4,0x68,0xfe,0xbd,0x6f, -0x10,0xf0,0x84,0xd1,0xdc,0xa1,0xb9,0x07,0x81,0xfd,0x7d,0x86,0x7b,0x35,0xa3,0x35, -0xf7,0x6b,0x1e,0xd2,0x3c,0xa6,0xf9,0x87,0xe6,0x69,0xcd,0x64,0x0d,0xfb,0x5d,0xf1, -0xb3,0x9a,0x99,0x9a,0xb9,0x9a,0xe7,0x39,0x7b,0x5e,0xd4,0xbc,0xac,0x71,0xb5,0x33, -0x85,0x7b,0xb8,0x5a,0x26,0x7d,0xe8,0x58,0x06,0x43,0x3f,0x54,0x13,0xf7,0xa3,0x76, -0x16,0x69,0x5e,0xd3,0x30,0x9a,0xdb,0xec,0x12,0x35,0xc9,0x9a,0x54,0xcd,0x3d,0x76, -0xfd,0xc6,0x74,0xcd,0x52,0xcd,0x32,0x4d,0xe6,0x75,0x2b,0x57,0x68,0xd6,0x68,0x36, -0xa2,0xed,0x3c,0xec,0x29,0xd4,0x6c,0xd5,0xec,0xd4,0xec,0xd6,0x54,0x6a,0xaa,0x34, -0xf5,0x9a,0x66,0xcd,0x41,0x4d,0x9b,0xe6,0x98,0xa6,0x03,0xe1,0x24,0xc2,0x5b,0x08, -0xef,0x22,0x7c,0x60,0x0e,0x67,0x11,0x2e,0x98,0x43,0x37,0x42,0x9f,0x39,0x7c,0x6c, -0x0e,0x97,0xcd,0xe1,0x0b,0x73,0xf8,0xca,0x1c,0xbe,0x41,0xb8,0x86,0xd0,0x8f,0x60, -0xa1,0xb5,0xd0,0x5a,0x99,0x03,0xdf,0x1c,0x86,0x98,0x83,0x9d,0x39,0x0c,0x35,0x07, -0x89,0x39,0x38,0x99,0x83,0x8b,0x39,0xb8,0x9a,0x83,0xcc,0x1c,0x3c,0xcc,0x41,0x61, -0x0e,0x2a,0x73,0xf0,0x31,0x07,0x7f,0x73,0x08,0x42,0x60,0x9f,0x06,0xc1,0x5a,0x53, -0xd0,0x21,0x44,0x21,0xc4,0x22,0xdc,0x81,0x60,0xfa,0x4d,0x95,0x7e,0x63,0xa8,0x66, -0x94,0x76,0x11,0xe7,0xb5,0xd7,0x34,0xf7,0x69,0x59,0xef,0xb2,0x1e,0xf5,0xd7,0x24, -0x6a,0xa6,0x38,0x8c,0xd1,0xde,0xea,0xf7,0x53,0xd8,0xd1,0x90,0x0c,0x6f,0xa7,0xe2, -0x8c,0x64,0x6a,0x7e,0xcf,0x37,0xd4,0x2f,0x6a,0xd2,0x71,0xa6,0x1f,0xd0,0xde,0xa3, -0x79,0xc3,0xf6,0xf7,0xd4,0xbf,0x57,0x33,0x56,0x3b,0x5a,0xf3,0xb0,0xf6,0x51,0xed, -0x38,0xed,0x14,0x07,0x76,0x04,0x05,0x6a,0x9e,0xd2,0x7e,0xeb,0x32,0x49,0x3b,0x55, -0x3b,0x43,0x3b,0x47,0x3b,0x5f,0xbb,0x50,0xfb,0x8a,0x36,0x41,0x9b,0xa4,0x4d,0xd3, -0xfe,0x55,0x6f,0xd0,0x32,0xb4,0x59,0xda,0xd5,0x5a,0xa2,0x3f,0xf2,0x7e,0xd9,0x34, -0x46,0x6f,0x7e,0xcb,0xcc,0x6e,0xdf,0xfc,0xa6,0x99,0xdd,0xbe,0xf9,0x6d,0x33,0xbb, -0x6d,0x7a,0xe3,0x6c,0xaa,0xbb,0x5e,0x9b,0xad,0xcd,0xd3,0x46,0xd2,0x26,0x6d,0x91, -0x76,0x1b,0xce,0xe9,0x4e,0xed,0x6e,0x6d,0x85,0x76,0xbf,0xb6,0x56,0xdb,0xa8,0x7d, -0x5d,0xfb,0xeb,0xf7,0x87,0x43,0xda,0x37,0xb4,0xc7,0xb5,0xa7,0xb4,0xef,0x6a,0xcf, -0x68,0x3f,0xd2,0xf6,0x69,0x3f,0xd5,0x5e,0xd1,0x7e,0xa3,0xfd,0x41,0x6b,0x19,0xcc, -0x0f,0xb6,0x0d,0x16,0x05,0x3b,0x05,0x0f,0x0f,0x76,0x0f,0x56,0x04,0x7b,0x07,0x07, -0x04,0x07,0x07,0x87,0x07,0xc7,0x04,0xc7,0x07,0xdf,0x15,0x7c,0x5f,0xf0,0x83,0xc1, -0x8f,0x06,0xff,0x23,0x78,0x42,0xf0,0x94,0xe0,0x19,0xc1,0x73,0x83,0x5f,0x08,0x7e, -0x39,0x38,0x21,0x38,0x39,0x78,0x49,0xf0,0x7f,0xfa,0xf7,0xa9,0x7f,0xed,0x3b,0xd0, -0xcc,0xe0,0xd5,0xc1,0x1b,0x82,0xf3,0x82,0x37,0x07,0x17,0x07,0xef,0x0a,0x2e,0x0f, -0xde,0x1f,0x5c,0x17,0xdc,0x1c,0x7c,0x30,0xf8,0x48,0x70,0x7b,0xf0,0xc9,0xe0,0x77, -0x82,0x3f,0x0c,0xbe,0x10,0xdc,0x1b,0xfc,0x49,0xf0,0x17,0xc1,0x57,0x83,0xbf,0x0f, -0xb6,0x08,0x19,0x14,0x62,0x13,0x32,0x34,0xc4,0x31,0x64,0x58,0x88,0x2c,0xc4,0x33, -0x44,0x1d,0xe2,0x1f,0xa2,0x0d,0x09,0x0b,0x89,0x0e,0xf9,0xb3,0xbf,0xa5,0xf8,0xab, -0xda,0xfb,0xe9,0xdd,0x39,0x2e,0xe4,0xce,0x90,0x7b,0x43,0x1e,0x08,0x79,0x24,0x64, -0x5c,0xc8,0xd3,0x21,0xcf,0x84,0x4c,0x0f,0x99,0x13,0xf2,0x3c,0x7a,0xf3,0x52,0xc8, -0x6b,0x21,0x49,0x21,0xe9,0x21,0xcb,0x43,0x56,0x85,0xac,0x0f,0xc9,0x0d,0x29,0x0c, -0xd9,0x16,0x52,0x12,0xb2,0x27,0x64,0x5f,0x48,0x6d,0x48,0x13,0x8e,0x1f,0x08,0x69, -0x0b,0x39,0x16,0xd2,0x19,0xf2,0x76,0xc8,0x07,0x21,0xe7,0xb1,0xdd,0x13,0xf2,0x71, -0x48,0xff,0xaf,0x7e,0x1f,0xfc,0xdb,0x8e,0xf7,0xff,0xe8,0x37,0xf6,0x3e,0x0f,0xf9, -0x32,0xe4,0x79,0xba,0x1a,0x92,0x60,0xd1,0x6f,0xfc,0x2e,0xe4,0x87,0x10,0x8b,0x50, -0xab,0x50,0xf6,0x6f,0xe0,0xf9,0xa1,0x36,0xa1,0xf6,0xa1,0xa2,0xd0,0xd9,0x1e,0x0e, -0xa1,0x45,0x16,0xce,0xa1,0x5f,0xd1,0xb0,0xd0,0x11,0xa1,0xee,0xa1,0xf2,0x50,0xaf, -0xd0,0x20,0x0f,0x75,0xa8,0x7f,0xa8,0x26,0x34,0x24,0x34,0x22,0x34,0x3a,0xb4,0x98, -0xe2,0x42,0x6f,0x0f,0x1d,0x49,0x77,0x85,0x8e,0x0a,0x15,0x0d,0xbd,0x3f,0xf4,0x80, -0xc5,0xd8,0xd0,0x47,0x42,0x1f,0x0f,0x7d,0x32,0xf4,0xb8,0xc5,0xd3,0xa1,0xfd,0xc6, -0xc9,0xa1,0x53,0x43,0xa7,0x87,0xce,0x0a,0x9d,0x1b,0x3a,0x3f,0x74,0x41,0xe8,0x4b, -0xa1,0xaf,0x85,0xde,0xf0,0x50,0x52,0x68,0x7a,0xe8,0xf2,0xd0,0x55,0xa1,0xeb,0x43, -0x73,0x43,0x0b,0x43,0xb7,0x85,0x96,0x84,0xee,0x09,0xdd,0x17,0x5a,0x1b,0xda,0x14, -0x7a,0x20,0xb4,0x2d,0xf4,0x58,0x68,0x67,0xe8,0xdb,0xa1,0x1f,0x84,0x9e,0x0f,0xed, -0x09,0xfd,0x38,0xf4,0xf3,0xd0,0xaf,0x43,0xaf,0x85,0x92,0xce,0x5a,0x37,0x44,0x27, -0xd4,0x39,0xe8,0x5c,0x74,0x6e,0x3a,0xb9,0x4e,0xa5,0xf3,0xd3,0x69,0x74,0x3a,0x5d, -0x94,0x2e,0x56,0x77,0x87,0x6e,0x94,0xee,0x7e,0xdd,0xc3,0xba,0x27,0x74,0x4f,0xe9, -0x26,0xeb,0xa6,0xe9,0x66,0xeb,0xe6,0xeb,0x5e,0xd4,0xbd,0xaa,0xfb,0xfd,0xdf,0xc0, -0x27,0xea,0xd2,0x74,0xcb,0x74,0x2b,0x75,0xeb,0x74,0x39,0xba,0x4d,0xba,0xad,0xba, -0x9d,0xba,0x32,0xdd,0x5e,0x5d,0x8d,0xae,0x51,0xd7,0xa2,0x3b,0xac,0x7b,0x53,0x77, -0x42,0xf7,0x96,0xee,0x7d,0xdd,0x39,0x5d,0xb7,0xee,0x92,0xee,0xf7,0x8c,0xa8,0x3f, -0x3a,0xbf,0xf8,0x4c,0xf7,0x95,0xee,0x3b,0x9d,0x51,0x67,0x15,0x36,0x38,0xcc,0x3e, -0x4c,0x12,0x26,0x0d,0x1b,0x11,0xe6,0x11,0xa6,0x0c,0xf3,0x0d,0x0b,0x0a,0x0b,0x0d, -0x8b,0x0c,0x33,0x84,0xdd,0x1e,0x76,0x4f,0xd8,0x98,0xb0,0x87,0xc2,0x1e,0x0f,0x1b, -0x1f,0x36,0x29,0xec,0xd9,0xb0,0x59,0x61,0xf3,0xc2,0x16,0x86,0x2d,0x0a,0x5b,0x1c, -0x96,0x1a,0x96,0x11,0xb6,0x22,0x6c,0x6d,0x58,0x76,0x58,0x41,0xd8,0x96,0xb0,0x1d, -0x61,0xbb,0xc3,0x2a,0xc3,0xaa,0xc3,0x1a,0xc2,0x5e,0x0f,0x6b,0x0d,0x3b,0x1a,0xd6, -0x11,0x76,0x3a,0xec,0xbd,0xb0,0xb3,0x61,0x5d,0x61,0x17,0xc3,0x2e,0x87,0x7d,0x19, -0xf6,0x6d,0x58,0x7f,0x18,0x2f,0x5c,0x10,0x6e,0x17,0x2e,0x0e,0x77,0x0e,0x77,0x0d, -0x1f,0x19,0xee,0x15,0xee,0x13,0xfe,0x3f,0xf7,0x97,0x1c,0xa6,0x10,0x18,0x1e,0x12, -0x1e,0x11,0xae,0x0f,0xbf,0x2d,0xfc,0xee,0xf0,0xd1,0xe1,0x63,0xc3,0x1f,0x0b,0x7f, -0x32,0x7c,0x62,0xf8,0xd4,0xf0,0x99,0xe1,0xcf,0x85,0x2f,0x08,0x7f,0x25,0x9c,0x09, -0x4f,0x09,0x5f,0x1a,0x9e,0x15,0xbe,0x26,0x7c,0x63,0x78,0x7e,0x78,0x51,0xf8,0xf6, -0xf0,0x5f,0x3b,0x47,0xbf,0x3e,0x3e,0x6e,0xde,0xff,0xf3,0xdf,0x3e,0xfd,0x73,0xfe, -0xfe,0xeb,0xd6,0x7f,0xd3,0xf0,0x7b,0x7e,0x17,0xed,0xe7,0xf3,0x87,0x1f,0xff,0xc6, -0xee,0xef,0xff,0x94,0x86,0xef,0x0f,0x6f,0x0a,0x3f,0x1a,0xfe,0x4e,0x78,0x77,0xf8, -0x95,0xf0,0xdf,0xf6,0xf7,0x2c,0xbf,0xfc,0x57,0x2d,0xff,0xc9,0x0f,0x45,0x0c,0x8e, -0x10,0x47,0xc8,0x22,0x7c,0x23,0xc2,0x23,0xfe,0xfa,0xdf,0x02,0xfa,0x57,0xe1,0x3f, -0xf5,0x3b,0x89,0x7f,0xbc,0x4e,0xbf,0xf1,0x7f,0x97,0x6d,0xfd,0x46,0xb6,0x0e,0xeb, -0x43,0x3b,0x2e,0x66,0xeb,0xb0,0xe9,0x40,0x1d,0xf6,0x9b,0x87,0x5b,0xeb,0xf4,0x1b, -0x6d,0xc0,0x8f,0x75,0xfa,0x8d,0xff,0xf3,0xbe,0xee,0x37,0xde,0xf0,0xb5,0xa9,0x3f, -0x37,0xd7,0xf9,0x69,0x7f,0x7e,0xff,0xd8,0xb9,0x59,0xe7,0x46,0x9d,0x7e,0xe3,0x08, -0x1a,0xf8,0xc6,0xe6,0xcf,0xd1,0xf9,0xbb,0xce,0xdf,0x75,0xfe,0x53,0x75,0xee,0x88, -0xb8,0x3b,0x82,0x1d,0xbb,0xbf,0xfe,0xdf,0x62,0x74,0xbc,0x1f,0xff,0xb7,0x98,0x7b, -0x23,0x7e,0xbd,0xce,0x98,0x9f,0xd4,0xb9,0x3f,0xc2,0x1f,0xe5,0x22,0xb0,0x7d,0x2f, -0x8e,0xff,0xad,0xf9,0x7f,0x4b,0x73,0x3e,0x02,0x3b,0x96,0x6e,0x35,0x77,0xfa,0xb3, -0xf7,0xfd,0x7b,0x2b,0xb9,0x5b,0xaf,0xba,0xfb,0x7f,0x56,0xf7,0xc6,0xfc,0xe2,0xe7, -0xff,0x3f,0xc8,0xf4,0xdf,0x83,0x6e,0xfd,0xbf,0x83,0x46,0x72,0xff,0x35,0x68,0xc4, -0xc8,0xb7,0xad,0x2a,0x78,0xd5,0xbc,0xcf,0xac,0x36,0x5a,0xed,0xb5,0xba,0x75,0x0b, -0x6c,0x7d,0xde,0x8f,0xea,0xb2,0xff,0x75,0x68,0x26,0xea,0x95,0xf1,0xde,0xe4,0xad, -0xb6,0xea,0x70,0xcf,0xb5,0xfa,0x2d,0xf3,0x9f,0x87,0x22,0x1e,0x8f,0x18,0x1f,0x31, -0x29,0xe2,0xd9,0x88,0x59,0x11,0xf3,0x22,0x16,0x46,0x2c,0x8a,0x58,0x1c,0x91,0x1a, -0x91,0x11,0xb1,0x22,0x62,0x6d,0x44,0x76,0x44,0x41,0xc4,0x96,0x88,0x1d,0x11,0xbb, -0x23,0x2a,0x23,0xaa,0x23,0x1a,0x22,0x5e,0x8f,0x68,0x8d,0x38,0x1a,0xd1,0x11,0x71, -0x3a,0xe2,0xbd,0x88,0xb3,0x11,0x5d,0x11,0x17,0x23,0x2e,0x47,0x7c,0x19,0xf1,0xed, -0x1f,0x9a,0xc1,0xb1,0xef,0x18,0x2f,0x6b,0xbe,0xd1,0xf4,0x1b,0x83,0xb4,0x16,0xda, -0x2f,0x34,0x43,0xb5,0xc1,0xdc,0xbb,0x3d,0xf6,0x7d,0x9e,0x95,0xd6,0x49,0xeb,0xaa, -0x1d,0xa2,0x95,0x69,0xfb,0x35,0x7c,0x2d,0xfb,0xe6,0xf1,0x2b,0xcd,0x35,0x8d,0x9d, -0x56,0xa2,0x75,0xd1,0xf6,0x47,0x2c,0xf2,0xb1,0x88,0xb4,0x8a,0xec,0x37,0xb2,0xf5, -0xfb,0x8d,0xfe,0xdc,0xbb,0x9e,0x2f,0xd8,0x77,0xb5,0xb7,0xaa,0x6f,0x1c,0xa8,0x8f, -0xd9,0x0a,0xb6,0x5c,0x7e,0xf4,0x66,0xe8,0xf2,0xf5,0xb7,0xa5,0xa6,0xfa,0x78,0x02, -0x03,0x53,0xfd,0x7e,0xe3,0x40,0x7d,0xf6,0xf8,0xcd,0xf5,0x17,0xf9,0xf4,0x1b,0x7f, -0xae,0x6f,0xb2,0xbf,0xdf,0xf8,0xaf,0xf4,0x4d,0xf6,0xe3,0xb9,0x8e,0xda,0x03,0xfd, -0x67,0xdf,0x57,0x0e,0xf4,0x9f,0xd5,0xff,0xd5,0xfe,0x5f,0xb7,0xfe,0xe7,0xfe,0xfb, -0xf7,0xea,0xf3,0x23,0x87,0x44,0xda,0x45,0x0e,0x8d,0x94,0x44,0x3a,0x45,0xba,0x44, -0xba,0x46,0xca,0x22,0x3d,0x22,0x6f,0x9c,0x9f,0xdf,0xf3,0x77,0x3e,0x8a,0x48,0x6b, -0x3c,0x21,0x4a,0x65,0x7e,0x91,0xbf,0x6f,0xcd,0x14,0x18,0xb9,0x44,0xa6,0x8d,0x0c, -0x8d,0x0c,0x8f,0x8c,0x8e,0xfc,0x5d,0x7f,0x9b,0x12,0x19,0x1f,0x79,0x47,0xe4,0x9f, -0xb3,0x92,0xb8,0x3b,0xf2,0xaf,0x5d,0x85,0xdc,0x1b,0x39,0x36,0x72,0x9a,0xcb,0x2f, -0x1f,0x1f,0x22,0x7e,0x22,0x72,0x7c,0xe4,0xc4,0x48,0x2f,0xd1,0x94,0xc8,0xe9,0x91, -0xee,0x4e,0xb3,0x23,0xe7,0x45,0x2e,0x88,0x2c,0xf1,0x7a,0x39,0xf2,0xb5,0xc8,0xc4, -0xc8,0xd4,0xc8,0xa5,0x91,0x99,0x91,0xd9,0xe2,0x55,0x91,0xeb,0x22,0xb3,0x23,0xf3, -0x23,0x37,0x47,0x6e,0x8b,0xb4,0x17,0x91,0xd3,0xce,0xc8,0x02,0xa9,0xad,0x53,0x91, -0x74,0x77,0xe4,0xcb,0x92,0x8a,0xc8,0xfd,0x91,0xb5,0x91,0x8d,0x91,0xaf,0x47,0x8e, -0x93,0x6a,0xc4,0x87,0x22,0x8f,0x44,0x1e,0x8b,0x3c,0x11,0x39,0xd0,0xfe,0xe9,0xc8, -0xb3,0x91,0x17,0x23,0xbf,0x8c,0xec,0x8f,0x14,0x44,0x89,0xa3,0x5c,0xa3,0xbc,0xa2, -0x7e,0xac,0x1f,0x18,0x15,0x1c,0xf5,0x5b,0xd7,0xcf,0x7f,0xf5,0xfa,0xfb,0xf7,0x7c, -0x6e,0x7d,0xcf,0xbf,0xa1,0x1f,0x16,0x15,0x15,0x65,0x88,0xba,0x2d,0xea,0xee,0x28, -0x99,0xea,0xbe,0xa8,0x07,0xa2,0x1e,0x8e,0x7a,0x3c,0x4a,0xae,0x7a,0x32,0x6a,0x42, -0xd4,0x33,0x51,0x4a,0xd5,0xf4,0xa8,0xd9,0x51,0xf3,0xa2,0x16,0x44,0xbd,0x12,0x95, -0x10,0x95,0x18,0x99,0x14,0x95,0x1e,0xb5,0x2c,0x6a,0x45,0xd4,0x9a,0x28,0x2b,0xf5, -0x86,0xa8,0xd1,0xa2,0xbc,0xa8,0xc2,0xa8,0xad,0x51,0x3b,0xa2,0x8e,0x44,0x96,0x46, -0x95,0x47,0x49,0xd5,0xec,0x6f,0x79,0xb8,0xaa,0xf7,0x45,0xd5,0x44,0x8d,0x55,0x34, -0x44,0x69,0x55,0xff,0x8c,0x3a,0x18,0xd5,0x16,0xf5,0x26,0x17,0x3a,0xa2,0x4e,0x47, -0xbd,0x1b,0xf5,0x61,0xd4,0xf9,0xa8,0x9e,0xa8,0x4b,0x51,0x97,0xa3,0xae,0x44,0x5d, -0x8d,0xba,0x16,0x65,0x8c,0xe2,0x45,0xf3,0xa3,0x6d,0xa2,0x85,0xd1,0x92,0x68,0x67, -0x04,0x4f,0xf5,0xf0,0x68,0x59,0xb4,0x3c,0x5a,0x19,0x1d,0xa9,0xf2,0x89,0x0e,0x88, -0xd6,0x46,0xe7,0x28,0x75,0xd1,0x91,0xd1,0xfa,0xe8,0xf8,0xe8,0x3b,0xa3,0x47,0x45, -0x8f,0x89,0x1e,0x1b,0xfd,0x68,0xf4,0x3f,0xa2,0x9f,0x8e,0x9e,0x1c,0x3d,0x25,0xf2, -0xd9,0xe8,0x99,0xd1,0x73,0xa3,0x5f,0x88,0x7e,0x39,0xfa,0xb5,0xe8,0xc4,0xe8,0xd4, -0xe8,0xa5,0xd1,0x99,0xd1,0xab,0xa2,0xd7,0x45,0x67,0x23,0xe4,0x47,0x17,0x45,0x17, -0x47,0xc7,0x2b,0x4a,0xa2,0xcb,0xa2,0xf7,0x46,0x57,0x47,0xd7,0x47,0xef,0x17,0x35, -0x47,0x1f,0x88,0x6e,0x14,0x1d,0x8e,0x3e,0x1a,0x7d,0x3c,0xfa,0x54,0xf4,0x3b,0xd1, -0x1f,0x46,0x9f,0x8f,0xee,0x8e,0xbe,0x18,0xfd,0x69,0xf4,0x17,0xd1,0x5f,0x47,0x7f, -0x17,0xdd,0x1f,0x6d,0x19,0x33,0x28,0x66,0x48,0x8c,0x30,0x46,0x12,0xe3,0x1c,0x33, -0x3c,0x66,0xbe,0xa7,0x2c,0xa6,0x5b,0xe4,0x19,0xe3,0x19,0xa3,0x8e,0xf1,0x43,0x08, -0x8a,0x09,0x89,0x89,0x88,0xd1,0xc7,0xc4,0xc7,0xdc,0x19,0x33,0x2a,0x66,0x4c,0xcc, -0xd8,0x98,0x47,0x63,0xc6,0xc5,0x3c,0x15,0x33,0x29,0x66,0x8c,0x6a,0x6a,0xcc,0xcc, -0x98,0xb9,0x31,0xcf,0xc7,0x3c,0xa4,0x7e,0x3e,0xe6,0xc5,0x98,0x47,0x55,0x8b,0x62, -0x98,0x98,0xe4,0x98,0xf4,0x98,0x71,0x2a,0xbe,0xe7,0xb2,0x98,0x15,0x31,0x6b,0x62, -0x36,0xc4,0xe4,0xc6,0x6c,0x8a,0xd9,0x12,0xb3,0x23,0xa6,0x34,0xa6,0x3c,0x66,0x5f, -0x4c,0x4d,0x4c,0x43,0xcc,0xeb,0x31,0x87,0x62,0x8e,0xc4,0x1c,0x8b,0x39,0x11,0x73, -0x3a,0xe6,0xdd,0x98,0x0f,0x63,0xce,0xc7,0x3c,0xa5,0xea,0x8e,0xb9,0x18,0x73,0x39, -0xe6,0x4a,0xcc,0xd5,0x98,0x6b,0x31,0x53,0x55,0xc6,0x18,0x9e,0x9e,0xaf,0xb7,0xd1, -0x0b,0xf5,0x12,0xbd,0xb3,0x7e,0xb8,0x9e,0xfd,0x5d,0x14,0x99,0x5e,0xae,0x57,0xea, -0x7d,0xf4,0x81,0xfa,0x60,0x7d,0x98,0x3e,0x4a,0x3f,0x43,0x65,0xd0,0xdf,0xae,0xbf, -0x5b,0x7f,0x9f,0x5e,0xe6,0xfd,0x80,0xfe,0x61,0xfd,0xe3,0xfa,0x27,0xf5,0x13,0xf4, -0x53,0xf4,0xd3,0xf5,0xb3,0xf5,0xf3,0xf4,0x0b,0xf5,0xaf,0xe8,0x13,0xf4,0x49,0xfa, -0x34,0x45,0x9a,0x3e,0x43,0xbf,0x42,0xbf,0x56,0x9f,0xad,0xcf,0xd7,0x17,0xe9,0x8b, -0xf5,0x25,0xfa,0x32,0x7d,0xa5,0x7e,0x8e,0xaa,0xd3,0xab,0x4a,0x5f,0xa7,0x6f,0xd2, -0xb7,0xe8,0x0f,0xeb,0x8f,0xea,0x8f,0xeb,0x4f,0xea,0x19,0xf5,0xdb,0xfa,0xf7,0xf5, -0xe7,0xf4,0x5d,0xfa,0x3e,0xfd,0xa7,0xfa,0x2b,0xfa,0xab,0xfa,0x64,0xf5,0x35,0xbd, -0x51,0xcf,0x33,0xf0,0x0d,0x36,0x06,0xa1,0x41,0x62,0x90,0x1a,0x5c,0x0d,0x23,0x0d, -0x0a,0x83,0xb7,0x61,0x99,0xda,0xdf,0xa0,0x31,0xe8,0x0c,0x91,0x06,0xbd,0xe1,0x36, -0xc3,0xdd,0x86,0xfb,0x0c,0x0f,0x18,0x1e,0x36,0x3c,0x8e,0xf0,0xa4,0x61,0x82,0x61, -0x8d,0xfa,0x19,0xc3,0x34,0xc3,0x2c,0xc3,0x73,0x86,0x17,0x0c,0x2f,0x1b,0x5e,0x33, -0x8c,0x51,0x24,0x19,0xd2,0x0d,0xcb,0x0c,0x2b,0x0d,0xeb,0x0c,0x39,0x86,0x02,0xc3, -0x2e,0x75,0x91,0x61,0xbb,0xa1,0xd4,0x50,0x61,0xa8,0x32,0xd4,0x21,0xec,0x51,0x2b, -0xbd,0x9b,0x0c,0x2d,0x86,0x56,0xc3,0x1b,0x86,0xe3,0x86,0x3a,0xcf,0x6a,0xf5,0x49, -0xc3,0xdb,0x86,0xf5,0xaa,0x0f,0x0c,0xe7,0x0d,0x97,0x94,0x3d,0x86,0x4b,0x86,0x22, -0xd5,0x65,0xc3,0x15,0xc3,0x55,0xc3,0xf7,0x08,0x16,0xb1,0xd6,0xb1,0x83,0x63,0xed, -0x63,0xc5,0xb1,0x4e,0xb1,0xc3,0x62,0x65,0xb1,0xf2,0x58,0x65,0xac,0x4f,0x6c,0x40, -0xac,0x36,0x36,0x2c,0x36,0x2a,0xd6,0x10,0x7b,0x5b,0xec,0x5d,0xb1,0xf7,0xc6,0xde, -0x1f,0xfb,0x70,0xec,0x13,0xb1,0xe3,0x63,0x27,0xc5,0x4e,0x8d,0x9d,0x19,0x3b,0x37, -0xb6,0x4e,0xf5,0x7c,0xec,0x4b,0xb1,0xaf,0xc5,0x26,0xc6,0xa6,0xc5,0x66,0xc4,0xae, -0x88,0x5d,0x13,0xbb,0x21,0x36,0x37,0x76,0x53,0xec,0x96,0xd8,0xed,0xb1,0xa5,0xb1, -0x15,0xb1,0x55,0xb1,0xea,0x98,0xfa,0xd8,0xe6,0xd8,0x03,0xb1,0x87,0x63,0x8f,0xc6, -0x1e,0x8f,0x3d,0x19,0xfb,0x76,0xec,0xfb,0xb1,0x67,0x63,0x3f,0x8a,0xed,0x8d,0x5d, -0xad,0xf8,0x24,0xf6,0xf3,0xd8,0xaf,0x62,0xbf,0x8d,0xfd,0x21,0xd6,0x22,0xae,0x55, -0x65,0x1d,0x37,0x38,0xce,0x2e,0x4e,0x14,0xe7,0x18,0x37,0x2c,0x4e,0x16,0xe7,0x19, -0xa7,0x8a,0xf3,0x8d,0x0b,0x8c,0x0b,0x8e,0x0b,0x8f,0x8b,0x8e,0x8b,0x8b,0xbb,0x23, -0xee,0x9e,0xb8,0x31,0x71,0x0f,0xc5,0x3d,0x16,0x57,0xec,0xf9,0x8f,0xb8,0xa7,0xe3, -0x26,0xc7,0x3d,0x1b,0x37,0x33,0x6e,0x6e,0xdc,0x49,0xf5,0xf3,0x71,0x2f,0xc6,0x2d, -0x8a,0x63,0xe2,0x92,0xe3,0xd2,0xe3,0x96,0xc5,0xad,0x88,0x1b,0x2d,0x59,0x13,0xb7, -0x31,0x2e,0x2f,0xae,0x30,0x6e,0x6b,0xdc,0x8e,0xb8,0xd2,0xb8,0x8a,0xb8,0xaa,0xb8, -0xba,0xb8,0xa6,0xb8,0x5e,0xf5,0xc7,0xea,0xe9,0x92,0x96,0xb8,0xc3,0x71,0x47,0xe3, -0x8e,0xc7,0x9d,0x8c,0x7b,0x3b,0xee,0x83,0xb8,0xf3,0x71,0xdd,0x71,0x17,0xe3,0x3e, -0x8d,0xbb,0x12,0xf7,0x99,0xfa,0x6a,0xdc,0xf7,0x71,0x16,0xf1,0xd6,0xf1,0x83,0xe3, -0xed,0xe2,0xc5,0xf1,0x4e,0xf1,0xc3,0xe2,0xdd,0xe2,0x3d,0xe2,0xbd,0xe2,0xbd,0xe3, -0xfd,0xe3,0xb5,0xf1,0xba,0xf8,0xc8,0x78,0x7d,0xfc,0x6d,0xf1,0x77,0xc5,0xdf,0x1b, -0x7f,0x7f,0xfc,0x43,0xf1,0x8f,0xc7,0x8f,0x8f,0x9f,0x18,0x3f,0x25,0x7e,0x7a,0xfc, -0x9c,0xf8,0xf9,0xf1,0x02,0x6f,0x81,0xf7,0x8b,0xf1,0x8b,0xe2,0x17,0xc7,0xa7,0xc4, -0x2f,0x89,0x5f,0x1e,0xbf,0x32,0x7e,0x6d,0xfc,0xc6,0xf8,0xbc,0x78,0x5b,0xef,0xcd, -0xf1,0xdb,0xe2,0x77,0xc6,0xef,0x8e,0xaf,0x88,0xdf,0x1f,0x5f,0x17,0xdf,0x14,0x7f, -0x20,0xbe,0x2d,0xde,0xdb,0xe1,0x58,0x7c,0x84,0xc3,0x89,0xf8,0xd3,0xf1,0xef,0xc6, -0x7f,0x18,0x3f,0xda,0xe1,0x7c,0xfc,0x9f,0xf1,0xfb,0x47,0x3f,0x7a,0x1b,0xc7,0xe6, -0xd2,0x64,0xd4,0x6c,0x34,0x5e,0x36,0xef,0x39,0xc3,0xc5,0x7a,0xf3,0x56,0xcf,0xcd, -0x77,0x36,0x1e,0xfd,0x68,0xaf,0xde,0x98,0x80,0x78,0x8c,0x91,0xfb,0xbf,0x42,0xcc, -0xf5,0x52,0xd7,0xd8,0xff,0x20,0x50,0xf0,0xad,0xd1,0xf8,0xc3,0xc1,0x2b,0x9f,0x36, -0x61,0xeb,0x87,0x4f,0xae,0x34,0x45,0x5a,0x50,0xbb,0x91,0xd7,0xff,0x29,0xb7,0xff, -0x93,0x2b,0x1f,0xb7,0x46,0x9f,0x96,0xb6,0x1a,0x5b,0x5a,0xa3,0x17,0xb3,0xda,0x7c, -0xe3,0x99,0x1f,0xb0,0xdf,0x78,0xe5,0x4c,0xf4,0xd3,0xba,0x76,0xb6,0x2d,0x6e,0xfb, -0x32,0xbb,0x3d,0xb7,0xdd,0x68,0x4f,0xdc,0x36,0x6b,0xdc,0x0f,0x9f,0x19,0xdb,0x91, -0x7e,0x6a,0x4c,0xf8,0x1e,0x4a,0x01,0x09,0xc9,0x4f,0x19,0x9b,0x8d,0xae,0xac,0x5d, -0x42,0xae,0x5f,0x67,0x8c,0x7c,0x2e,0x8d,0x4e,0x88,0xe6,0x2a,0x70,0x31,0xbb,0x91, -0x60,0xb6,0x2e,0xda,0x78,0x53,0x1f,0x64,0x6c,0xa7,0xcc,0x33,0x69,0xd9,0xcd,0xaf, -0x5d,0xd1,0x39,0x18,0x91,0x40,0x6c,0xb3,0xc6,0x33,0xec,0xff,0x85,0x41,0x6f,0x79, -0xa6,0xb2,0xc6,0xeb,0xff,0x47,0x49,0xcf,0xee,0x4f,0x30,0x0a,0x8d,0xc2,0xcf,0xb9, -0x4d,0x99,0xc9,0x3f,0x42,0xae,0xd5,0x56,0x73,0xa1,0x76,0x73,0xca,0x1a,0x50,0xc0, -0xb5,0xa1,0x37,0x7b,0x4f,0xc8,0xd9,0x7b,0xd3,0x47,0xf8,0x99,0x39,0xf3,0x8d,0xf1, -0xc2,0x4d,0xe7,0x07,0x8a,0x26,0xcf,0x9b,0xfe,0xcb,0x34,0x4e,0x03,0xdd,0xdc,0x74, -0x93,0x3b,0xd7,0x3c,0x25,0x98,0x3f,0xd7,0x8d,0xe3,0xde,0x51,0xdf,0xf8,0x70,0xd6, -0x9a,0x7b,0x99,0x70,0xf3,0xfb,0x58,0xe1,0xf5,0xf1,0x60,0x4a,0x65,0x46,0x9e,0xbe, -0xd9,0xdc,0x57,0xa3,0x90,0xe1,0xf6,0x34,0x19,0x5b,0xcc,0x7d,0x30,0xd5,0xd6,0x1b, -0xaf,0x98,0xfe,0x83,0x00,0x7d,0xc3,0x6e,0xbf,0xf6,0xda,0x6b,0x74,0x43,0xd9,0x54, -0x57,0xc8,0xfe,0xa7,0xe5,0x6f,0x06,0x14,0xcc,0x6f,0x81,0x2f,0x73,0x8d,0x54,0xa1, -0x8c,0xfe,0xa6,0x71,0xc8,0x4f,0xe3,0x92,0x2c,0xf6,0xa0,0xcc,0xe2,0xc6,0xfb,0x62, -0xfd,0x0d,0x8b,0xb9,0xbe,0x73,0x7f,0x01,0x2c,0x3c,0x7b,0xfd,0x1c,0xb0,0x71,0x93, -0xc9,0xe5,0xfa,0x1b,0xb5,0xe8,0x46,0xc7,0xb8,0x5e,0x63,0xbf,0x9e,0x1b,0x3e,0x4d, -0x2b,0xcd,0xc5,0xb9,0x4e,0x30,0xc6,0x81,0x37,0xd3,0xba,0x6f,0x4c,0xe2,0xc6,0x24, -0x93,0x77,0xaf,0xdb,0x85,0xf3,0xf1,0x3d,0x97,0x99,0xf3,0xd9,0x65,0xb8,0x52,0x9f, -0x86,0x91,0x61,0x71,0xf3,0x1b,0x6e,0xae,0xa1,0x6f,0x06,0xbc,0xa9,0xef,0x37,0x99, -0xcc,0x9a,0xc5,0x73,0xa3,0x7e,0x1c,0xb7,0xb8,0xf9,0xed,0x37,0xff,0x7b,0xb3,0x7d, -0xf0,0xef,0x8f,0xde,0x8c,0x9b,0x3c,0x17,0xc9,0xa3,0xef,0x4d,0x35,0xbe,0xbf,0x7e, -0x96,0x07,0xac,0xe1,0xce,0x11,0xeb,0x77,0xf6,0x90,0x7e,0xe0,0x62,0x3b,0xc3,0x27, -0x6e,0xb4,0xf2,0xae,0x5f,0xba,0x74,0xe3,0xec,0xb2,0xef,0x37,0x4d,0x57,0xa3,0xb9, -0x19,0x7c,0x12,0x1a,0xe0,0x80,0x81,0xff,0x47,0xc4,0x37,0xf2,0x4c,0xd7,0xe9,0x27, -0xd7,0x0a,0xc2,0x17,0x9f,0x6d,0x97,0xa1,0xfc,0x28,0xce,0x4f,0xc6,0xad,0x57,0x9b, -0x2c,0x06,0x99,0x94,0x2f,0xb3,0x75,0x59,0x9b,0x6f,0xf4,0x43,0xcf,0xb6,0xcc,0x43, -0x34,0xe0,0x27,0xee,0xca,0x10,0x9a,0x8e,0xf7,0xb0,0x67,0x29,0xe1,0xc7,0x6f,0xfd, -0xcd,0x7f,0xb9,0xcd,0x67,0x12,0xbe,0x98,0xc3,0xb6,0xff,0x6d,0x02,0x5d,0x37,0x89, -0xcc,0x2e,0xfe,0x5e,0x66,0xd2,0x31,0xde,0xf8,0xef,0x14,0x37,0x19,0xce,0x1d,0x33, -0xb5,0xc7,0x63,0xb5,0x7f,0x30,0x5d,0x52,0x37,0xc6,0x34,0x1a,0xe9,0xc7,0x35,0xc0, -0x75,0x37,0x61,0x2f,0x6b,0x6f,0x13,0x5b,0x26,0xe1,0xda,0x80,0x77,0x84,0x03,0x6d, -0x99,0xae,0x6f,0xae,0x81,0x56,0xd3,0x10,0x1e,0xf8,0x98,0xaf,0x2f,0x23,0xcf,0xe4, -0xdf,0x04,0x19,0x09,0x6f,0xf9,0xfd,0x05,0x9f,0x06,0x46,0x80,0xf9,0xaa,0x32,0x19, -0xd3,0x72,0xbd,0x40,0x02,0xa7,0xa9,0xd7,0x63,0x1e,0x68,0xbc,0x06,0x93,0xce,0x1a, -0xaf,0x8f,0x35,0x53,0x8b,0xc6,0x34,0x73,0x49,0xe6,0xc6,0x37,0x25,0x32,0x4a,0x20, -0xf3,0x65,0xcb,0x18,0x6f,0xb8,0x10,0x9f,0xcb,0x73,0x4c,0xaa,0x56,0xc8,0x2b,0xe9, -0xb3,0xeb,0x57,0xcd,0xc0,0xb5,0x21,0xfc,0xde,0x74,0x7d,0x58,0xd0,0xcd,0xf7,0xee, -0x84,0x7e,0x73,0x6b,0x66,0x5d,0xf3,0x15,0x69,0xea,0xbf,0x90,0xbe,0xe9,0x37,0xfd, -0xaf,0xe7,0x84,0xb3,0xac,0x07,0x98,0x9b,0x7a,0x29,0x43,0x0b,0x2d,0x37,0x8f,0xce, -0xeb,0xbd,0x4f,0xa2,0x1f,0xdf,0x81,0xd2,0x4c,0x63,0xd7,0x34,0x9a,0x13,0xe6,0x18, -0x13,0x0a,0xcc,0x66,0xc3,0xdb,0x2d,0x37,0xff,0x9f,0x16,0xd3,0x47,0x36,0x90,0x33, -0x9b,0xc5,0x33,0xd9,0x94,0x30,0x30,0x2f,0x37,0xed,0x6e,0x36,0x95,0xf9,0x1c,0x76, -0x98,0xee,0xcf,0x42,0x0b,0x1a,0x18,0x41,0xec,0x79,0xbd,0x7e,0x8d,0xe3,0x0e,0xca, -0x33,0x5f,0x61,0xdc,0x1e,0x0c,0xcb,0x81,0xbb,0x6e,0xc2,0x39,0xdc,0x6b,0xd9,0xab, -0xd5,0x78,0xf5,0xfa,0xed,0xef,0xc6,0x77,0x6a,0x37,0xee,0xe1,0x34,0x81,0x26,0xd2, -0x24,0x9a,0x4c,0xcf,0xd0,0x14,0x9a,0x4a,0xcf,0xd2,0x34,0x9a,0x4e,0x33,0x68,0x26, -0xcd,0xa2,0xd9,0x34,0x87,0xe6,0xd2,0x73,0x34,0x8f,0xe6,0xd3,0xf3,0xf4,0x02,0x2d, -0xa0,0x85,0xf4,0x22,0xbd,0x44,0x2f,0x93,0x3b,0xb9,0x1f,0x4d,0x07,0xc9,0xa4,0xc1, -0xd2,0xd1,0xfd,0xe8,0x62,0x3a,0x56,0x8f,0xa4,0x18,0x2b,0xc9,0xa0,0xf3,0x8b,0x93, -0x83,0xb9,0x18,0x87,0xb8,0x7c,0xcb,0x26,0x6a,0x29,0xa4,0x96,0xcd,0xd4,0x52,0x44, -0x2d,0x5b,0xa8,0x65,0x2b,0xb5,0x6c,0xa3,0x96,0x62,0x6a,0xd9,0x4e,0x2d,0x3b,0xa8, -0x65,0x27,0xb5,0x94,0x50,0xcb,0x2e,0x6a,0x29,0xa5,0x96,0xdd,0xd4,0x52,0x46,0x2d, -0x7b,0xa8,0xa5,0x9c,0x5a,0x2a,0xa8,0xa5,0x92,0x5a,0xf6,0x52,0xcb,0x3e,0x6a,0xd9, -0x4f,0x2d,0xd5,0xd4,0x52,0x43,0x2d,0xb5,0xd4,0x52,0x47,0x2d,0xf5,0xd4,0xd2,0x40, -0x2d,0x8d,0x74,0x60,0x31,0x1d,0x48,0xa2,0x03,0x29,0x74,0x20,0x8d,0x0e,0x2c,0xa1, -0x03,0x19,0x74,0x60,0x39,0x1d,0xc8,0xa2,0x03,0x2b,0xe9,0xc0,0x6a,0x3a,0xb0,0x96, -0x0e,0xac,0xa7,0x03,0x1b,0xe9,0x40,0x0e,0x1d,0xc8,0xa3,0x03,0x05,0x74,0xa0,0x90, -0x0e,0x14,0xd1,0x81,0xad,0x74,0xa0,0x98,0x0e,0xec,0xa0,0x03,0x25,0x74,0xa0,0x94, -0x0e,0x94,0xd1,0xb4,0xa3,0x69,0x34,0x6d,0x3a,0x1d,0xd8,0x47,0x07,0xaa,0xe8,0x40, -0x2d,0x1d,0xa8,0xa7,0x03,0x8d,0x34,0xf3,0xf5,0x2a,0x3a,0x98,0x48,0x07,0x93,0xe9, -0x60,0x2a,0x1d,0x4c,0xa7,0x23,0xf5,0xb3,0xe9,0x60,0x06,0x1d,0x5c,0x4e,0x07,0xb3, -0xe8,0xe0,0x4a,0x3a,0xb8,0x9a,0x0e,0xae,0xa5,0x83,0xeb,0xe9,0xe0,0x46,0x3a,0x98, -0x43,0x07,0xf3,0xe8,0x60,0x01,0x1d,0x2c,0xa4,0x83,0x45,0x74,0x70,0x2b,0x1d,0x2c, -0xa6,0x83,0x3b,0xe8,0x60,0x09,0x1d,0x2c,0xa5,0x83,0x65,0x74,0xb0,0x9c,0x0e,0x56, -0xd2,0xc1,0x7d,0x74,0xb0,0x8a,0x5a,0x9a,0xe8,0x60,0x2d,0x1d,0xac,0xa7,0x83,0x8d, -0xd4,0xb6,0x9a,0x0e,0x25,0xd1,0xa1,0x14,0x6a,0x5b,0x43,0x87,0xd2,0xa9,0x6d,0x1d, -0xb5,0xad,0xa7,0x43,0xcb,0xa8,0x35,0x8f,0xda,0x36,0x52,0x5b,0x0e,0x1d,0x5a,0x45, -0x6d,0x9b,0xa8,0xad,0x88,0xda,0x76,0x50,0xdb,0x76,0x3a,0x84,0x9d,0x65,0xd4,0x56, -0x41,0x6d,0xfb,0xe8,0x50,0x21,0x1d,0x2a,0xa2,0x43,0x5b,0xe9,0x08,0x43,0x87,0xb6, -0xd3,0x11,0xb4,0x53,0x4a,0x47,0xd2,0xe9,0xd0,0x1e,0x3a,0xb2,0x94,0x8e,0x64,0xd0, -0xa1,0xbd,0x74,0x68,0x3f,0x1d,0x59,0x45,0x87,0x6a,0xe8,0x50,0x03,0x4d,0x86,0xde, -0xcc,0xe9,0x34,0x7b,0x3a,0xb5,0x66,0x52,0xeb,0x0a,0x6a,0x5d,0x45,0xad,0x6b,0xa8, -0x75,0x1d,0xb5,0x6e,0xa0,0xd6,0x6c,0x6a,0xcd,0xa5,0xd6,0x02,0x6a,0x2d,0xa4,0xd6, -0x22,0x6a,0xdd,0x4a,0xad,0xc5,0xd4,0xba,0x83,0x5a,0x4b,0xa8,0xb5,0x94,0x5a,0xcb, -0x68,0xf2,0xcb,0xd4,0x0a,0xc9,0xb5,0x74,0xa8,0x89,0x5a,0x6b,0xa8,0xb5,0x8e,0x5a, -0x1b,0xa8,0xb5,0x89,0x0e,0x2f,0xa6,0xc3,0x49,0x74,0x38,0x85,0x0e,0xa7,0xd1,0xe1, -0x25,0x74,0x38,0x83,0x0e,0x2f,0xa7,0xc3,0x59,0x74,0x78,0x25,0x1d,0x5e,0x4d,0x87, -0xd7,0xd2,0xe1,0xf5,0x74,0x78,0x23,0x1d,0xce,0xa1,0xc3,0x79,0x74,0xb8,0x80,0x0e, -0xe5,0xd3,0xe1,0x22,0x3a,0xbc,0x95,0x0e,0x17,0xd3,0xe1,0x1d,0x74,0xb8,0x84,0x0e, -0x97,0xd2,0xe1,0x32,0x3a,0x5c,0x4e,0x87,0x2b,0xe9,0x7c,0x39,0x0e,0xd4,0xd3,0xa1, -0x6c,0xe4,0xb6,0x51,0x5b,0x22,0x1d,0x62,0xe8,0xc8,0x12,0x3a,0xb2,0x8c,0xda,0xd2, -0xa8,0x6d,0x09,0xb5,0x65,0x50,0xdb,0x72,0x6a,0xcb,0xa2,0x36,0x1c,0xad,0xa1,0xb6, -0x3a,0x3a,0xb2,0x18,0x03,0x2f,0x15,0xa4,0x81,0xa5,0x60,0x3b,0x48,0x02,0x19,0x74, -0x64,0x2d,0x1d,0x65,0x08,0x83,0xf3,0xe8,0x6a,0x3a,0x9a,0xce,0x0e,0xd2,0x1a,0x7a, -0xb3,0x9c,0xde,0xac,0xa4,0x23,0xc8,0x54,0x91,0x3b,0xb6,0x23,0xa9,0xbd,0x92,0x1d, -0xcc,0xec,0xd1,0x5d,0x84,0x33,0x7e,0xac,0x94,0x8e,0xed,0xa6,0x63,0x65,0xd4,0xbe, -0x8c,0xda,0x97,0x53,0x7b,0x26,0x1d,0x2b,0xa7,0x63,0x15,0x74,0xac,0x92,0x8e,0xed, -0xa5,0x63,0xfb,0xe8,0xd8,0x7e,0x3a,0x86,0x62,0xd5,0x74,0xac,0x96,0x8e,0xd5,0xd1, -0xb1,0x06,0x3a,0xd6,0x48,0xc7,0x9a,0xa8,0x9d,0xa1,0xf6,0xc5,0xd4,0x9e,0x44,0xed, -0xc9,0xd4,0x9e,0x42,0xed,0xa9,0xd4,0x9e,0x46,0xed,0xe9,0xd4,0xbe,0x84,0xda,0x97, -0x52,0x7b,0x06,0xb5,0xaf,0xa7,0xf6,0x8d,0xd4,0x9e,0x43,0xed,0x79,0xd4,0x5e,0x40, -0xed,0x85,0xd4,0x5e,0x44,0xed,0x5b,0xa9,0xbd,0x98,0xda,0x77,0x50,0x7b,0x09,0xb5, -0x97,0x52,0x3b,0x74,0xab,0xa9,0xbd,0x8e,0xde,0xc4,0x4f,0x3d,0xbd,0xd9,0x40,0x1d, -0x2b,0xa8,0x63,0x25,0x75,0xac,0xa2,0x8e,0xd5,0xd4,0xb1,0x86,0x3a,0xd6,0x52,0xc7, -0x3a,0xea,0x58,0x4f,0x1d,0x1b,0xa8,0x63,0x23,0x75,0x64,0x53,0x47,0x0e,0x75,0xe4, -0x52,0x47,0x1e,0x75,0xe4,0x53,0x47,0x01,0x1d,0xdf,0x43,0xc7,0xcb,0xe9,0x78,0x05, -0x1d,0xaf,0xa4,0xe3,0x7b,0xe9,0xf8,0x3e,0x3a,0xbe,0x9f,0x8e,0x57,0xd1,0xf1,0x6a, -0x3a,0x5e,0x43,0xc7,0x6b,0xe9,0x78,0x1d,0x1d,0xaf,0xa7,0xe3,0x0d,0x74,0xbc,0x91, -0x8e,0x37,0x51,0x07,0x43,0x1d,0x8b,0xa9,0x23,0x91,0x3a,0x92,0xa8,0x23,0x99,0x3a, -0x52,0xa8,0x23,0x95,0x3a,0xd2,0xa8,0x23,0x9d,0x3a,0x96,0x50,0xc7,0x52,0xea,0xc8, -0xa0,0x8e,0x65,0xd4,0xb1,0x9c,0x3a,0x32,0xa9,0x23,0x8b,0x3a,0x0a,0xa9,0xa3,0x88, -0x3a,0xb6,0x52,0x47,0x31,0x75,0xec,0xa0,0x8e,0x12,0xea,0x28,0xa5,0x8e,0x32,0xea, -0x28,0xa7,0x8e,0x4a,0xea,0xd8,0x47,0x1d,0x55,0xd4,0x51,0x43,0x1d,0x75,0xd4,0x01, -0xe3,0x9b,0xe8,0xc4,0x62,0x3a,0x91,0x41,0x27,0x96,0xd3,0x89,0x2c,0x3a,0xb1,0x92, -0x4e,0xac,0xa6,0x13,0x6b,0xe9,0xc4,0x7a,0x3a,0xb1,0x91,0x4e,0xe4,0xd0,0x89,0x3c, -0x3a,0x51,0x40,0x27,0x0a,0xe9,0x44,0x11,0x9d,0xd8,0x4a,0x27,0x8a,0xe9,0xc4,0x0e, -0x3a,0x51,0x42,0x27,0x4a,0xe9,0x44,0x19,0x9d,0x28,0xa7,0x13,0x95,0x74,0x62,0x1f, -0x9d,0xa8,0xa2,0x13,0x35,0x74,0xa2,0x8e,0x4e,0x34,0xd0,0x89,0x26,0xea,0xcc,0xa2, -0xce,0x44,0xea,0x4c,0xa6,0xce,0x54,0xea,0x4c,0xa7,0xce,0xa5,0xd4,0xb9,0x8c,0x3a, -0x33,0xa9,0x73,0x25,0x75,0xae,0xa6,0xce,0xb5,0xd4,0xb9,0x9e,0x3a,0x37,0x52,0x67, -0x0e,0x75,0xe6,0x51,0x67,0x01,0x75,0x16,0x52,0x67,0x11,0x75,0x6e,0xa5,0xce,0x62, -0xea,0xdc,0x41,0x9d,0x25,0xd4,0x59,0x4a,0x9d,0x65,0xd4,0x59,0x4e,0x9d,0x95,0xd4, -0xb9,0x8f,0x3a,0xab,0xa8,0xb3,0x86,0x3a,0xeb,0xa8,0xb3,0x81,0x3a,0x9b,0xe8,0xe4, -0x62,0x3a,0x99,0x44,0x27,0x53,0xe8,0x64,0x1a,0x9d,0x5c,0x42,0x27,0x33,0xe8,0xe4, -0x72,0x3a,0x99,0x45,0x27,0x57,0xd2,0xc9,0xd5,0x74,0x72,0x2d,0x9d,0x5c,0x4f,0x27, -0x37,0xd2,0xc9,0x1c,0x3a,0x99,0x47,0x27,0x0b,0xe8,0x64,0x21,0x9d,0x2c,0xa2,0x93, -0x5b,0xe9,0x64,0x31,0x9d,0xdc,0x41,0x27,0x4b,0xe8,0x64,0x29,0x9d,0x2c,0xa3,0x53, -0x85,0x74,0x6a,0x33,0x9d,0x2a,0xa2,0x53,0x5b,0xe8,0xd4,0x56,0x3a,0xb5,0x8d,0x4e, -0x15,0xd3,0xa9,0xed,0x74,0x6a,0x07,0x9d,0xda,0x49,0xa7,0x4a,0xe8,0xd4,0x2e,0x3a, -0x55,0x4a,0xa7,0x76,0xd3,0x29,0x14,0xde,0x43,0xa7,0xca,0xe9,0x54,0x05,0x9d,0xaa, -0xa4,0x53,0x7b,0xe9,0xd4,0x3e,0x3a,0xb5,0x9f,0x4e,0x55,0xd1,0xa9,0x6a,0x3a,0x55, -0x43,0xa7,0x6a,0xe9,0x54,0x1d,0x9d,0xaa,0xa7,0x53,0x0d,0x74,0xaa,0x91,0x4e,0x35, -0xd1,0x69,0x86,0x4e,0x2f,0xa6,0xd3,0x89,0x74,0x3a,0x89,0x4e,0x27,0xd3,0xe9,0x14, -0x3a,0x9d,0x0a,0x15,0xec,0x78,0xbb,0xf8,0x9d,0xbd,0xf4,0x4e,0x3a,0xa2,0xf7,0xd2, -0xd8,0xdc,0x52,0x44,0x67,0xb7,0xac,0x3d,0xbb,0xa5,0x1e,0xc9,0x3a,0x53,0xb2,0xde, -0x94,0xe4,0x9a,0x92,0x42,0x53,0xb2,0xd9,0x94,0x94,0x98,0x92,0x32,0x2e,0xd9,0x56, -0x78,0x76,0x1b,0x9b,0x6c,0x36,0x25,0x65,0x5c,0xb2,0xbd,0xe2,0xec,0x76,0x36,0xa9, -0x36,0x25,0xeb,0x4c,0xc9,0x7a,0x53,0x92,0x6b,0x4a,0x4a,0xb8,0x64,0x57,0xe1,0xd9, -0x5d,0x6c,0xb2,0x99,0x4b,0x6a,0x96,0x9f,0xad,0xae,0xa0,0xb3,0x75,0xcb,0xcf,0xd6, -0x22,0xa9,0x2d,0x39,0x5b,0xbb,0xd1,0x94,0x14,0xd2,0xd9,0xfa,0x0c,0x3a,0xdb,0x90, -0x78,0xb6,0xb1,0x0a,0xc9,0x32,0x53,0xb2,0xd2,0x94,0xac,0x33,0x25,0x39,0xa6,0x84, -0x39,0xdb,0xb8,0x0f,0x49,0xf9,0xd9,0x86,0x0a,0x53,0x82,0xce,0x35,0x56,0x9c,0x6d, -0x64,0xb8,0x04,0x3b,0xb8,0x5c,0xe5,0x40,0x62,0xde,0x31,0xb0,0x7f,0x15,0xd7,0x48, -0x63,0xae,0x29,0x29,0x34,0x25,0xdb,0x4c,0x49,0x89,0x29,0x59,0xc1,0x0a,0x9c,0xdf, -0xcb,0x80,0xc5,0x20,0x11,0x24,0x81,0x64,0x90,0x02,0x52,0x41,0x1a,0x48,0x07,0x4b, -0xc0,0x52,0x90,0x01,0x96,0x81,0xe5,0x20,0x13,0x64,0x81,0x15,0x60,0x25,0x58,0x05, -0x56,0x83,0x35,0x60,0x2d,0x58,0x07,0xd6,0x83,0x0d,0x60,0x23,0xc0,0xad,0x6e,0x6f, -0x0e,0xc8,0x05,0x79,0x20,0x1f,0x14,0x80,0x4d,0xa0,0x10,0x6c,0x06,0x45,0x60,0x0b, -0xd8,0x0a,0x8a,0x41,0x29,0x9d,0x4b,0xca,0xa5,0x73,0x59,0x7b,0x40,0x39,0xa8,0x00, -0x95,0x60,0x2f,0xd8,0x47,0x17,0x37,0x66,0xd0,0xb9,0x8a,0xa5,0x28,0xb3,0x02,0xac, -0x04,0xab,0xc0,0x6a,0xb0,0x06,0xac,0x05,0xeb,0xc0,0x7a,0xb0,0x01,0x6c,0x04,0xd9, -0x20,0x07,0xe4,0x81,0x7c,0x50,0x00,0x36,0x81,0x42,0xb0,0x19,0x14,0x81,0x2d,0x60, -0x2b,0xd8,0x06,0x8a,0xc1,0x76,0xb0,0x03,0xec,0x04,0x25,0x60,0x17,0x60,0xed,0xda, -0x0d,0xca,0x00,0x6c,0x4b,0x82,0x6d,0x49,0xb0,0x2d,0x09,0xb6,0x25,0xc1,0xb6,0xa4, -0x7d,0x60,0x3f,0xa8,0x02,0xd5,0xa0,0x06,0xd4,0x82,0x06,0xd0,0x08,0x9a,0xa8,0x6d, -0x05,0xb5,0xc1,0x62,0x78,0xbd,0x0d,0x1d,0x84,0xb3,0xce,0xc1,0x59,0xe7,0xe0,0x98, -0x73,0xe8,0x7c,0x1b,0x9e,0x85,0x6b,0xa9,0xad,0x80,0xda,0x0a,0xa9,0x0d,0x9b,0x90, -0xde,0x87,0x27,0x00,0x1e,0x95,0x10,0xde,0x9f,0x42,0x47,0x70,0xa4,0x9c,0xda,0xf6, -0x50,0x5b,0x25,0xb5,0xed,0xa5,0xb6,0x6a,0x3a,0x82,0x27,0x08,0x8c,0x83,0x73,0x8f, -0xac,0xa0,0x23,0x68,0xb8,0x7a,0x31,0x48,0x02,0x29,0x20,0x0d,0x2c,0x01,0x70,0x57, -0xf5,0x72,0x90,0x05,0xd8,0x32,0x70,0x55,0x35,0xdc,0x54,0x0d,0xe5,0x6a,0xb8,0xa7, -0x1a,0xae,0xa9,0x86,0x05,0xd5,0x70,0x4b,0x35,0x5c,0x52,0x0d,0x77,0x54,0xc3,0x15, -0xd5,0x70,0x43,0x35,0x5c,0x50,0x0d,0x85,0x6a,0x58,0x50,0x8d,0x6e,0x57,0xa3,0xcb, -0xd5,0xe8,0x6e,0x35,0xba,0x5a,0x8d,0x6e,0x56,0xa3,0x8b,0xd5,0x75,0x00,0x5d,0xac, -0x6e,0xa2,0x73,0x35,0xd0,0xaf,0x81,0x7e,0x0d,0xf4,0x6b,0xa0,0x5f,0x03,0xfd,0x1a, -0xe8,0xd7,0x40,0xbf,0x06,0xfa,0x35,0xd0,0xaf,0x81,0x7e,0x0d,0xf4,0x6b,0xa0,0x5f, -0x03,0xfd,0x1a,0xe8,0xd7,0x40,0xbf,0x06,0xfa,0x35,0xd0,0xaf,0x81,0x7e,0x0d,0xf4, -0x6b,0xa0,0x5f,0x03,0xfd,0x1a,0xe8,0xd7,0x40,0xbf,0x06,0xfa,0x35,0xd0,0xaf,0x81, -0x7e,0x0d,0xf4,0x6b,0xa0,0x5f,0x03,0xfd,0x1a,0xe8,0xd7,0x40,0xbf,0x06,0xfa,0xb5, -0xd0,0xaf,0x85,0x7e,0x2d,0xf4,0x6b,0xa1,0x5f,0x0b,0xfd,0x5a,0xe8,0xd7,0x42,0xbf, -0x16,0xfa,0xb5,0xd0,0xaf,0x85,0x7e,0xed,0x5a,0x9a,0x70,0xa4,0x91,0x5a,0x20,0x89, -0x8b,0xf3,0x5c,0x2d,0x24,0x6b,0x21,0x59,0x0b,0xc9,0x5a,0x48,0xd6,0x42,0xb2,0x16, -0x92,0xb5,0x90,0xac,0x85,0x64,0x2d,0x24,0x6b,0x21,0x59,0x0b,0xc9,0x5a,0x48,0xd6, -0x42,0xb2,0x16,0x92,0xb5,0x90,0xac,0x83,0x64,0x1d,0x24,0xeb,0x20,0x59,0x07,0xc9, -0x3a,0x48,0xd6,0x41,0xb2,0x0e,0x92,0x75,0x90,0xac,0x83,0x64,0x1d,0x24,0xeb,0xd0, -0xe5,0x3a,0x74,0xb9,0x0e,0x5d,0xae,0x43,0x97,0xeb,0xd0,0xe5,0x3a,0xe8,0xd7,0x41, -0xbf,0x0e,0xfa,0x75,0xd0,0xaf,0x83,0x7e,0x1d,0xf4,0xeb,0xa0,0x5f,0x07,0xfd,0x3a, -0xe8,0xd7,0x41,0xbf,0x0e,0xfa,0x75,0xd0,0xaf,0x83,0x7e,0x1d,0xf4,0xeb,0xa0,0x5f, -0x07,0xfd,0x3a,0xe8,0xd7,0x33,0x00,0x36,0xd4,0x27,0x02,0xd8,0x51,0x9f,0x0c,0x60, -0x4b,0x7d,0x2a,0x80,0x3d,0xf5,0xb8,0x4a,0xea,0x61,0x43,0x3d,0xae,0x92,0x7a,0xd8, -0x51,0x8f,0xab,0xa4,0x1e,0xb6,0xd4,0xe3,0x0a,0xa8,0x87,0x76,0x3d,0xae,0x80,0x7a, -0xe8,0xd7,0xe3,0x0a,0xa8,0x87,0x0d,0xf5,0xb8,0x02,0xea,0x61,0x47,0x3d,0x46,0x78, -0x3d,0xb4,0xeb,0x31,0xc2,0xeb,0xa1,0x5f,0x8f,0x11,0x5e,0x0f,0x1b,0xea,0x31,0xc2, -0xeb,0x61,0x47,0x03,0x74,0x1b,0xa0,0xdb,0x00,0xdd,0x06,0xe8,0x36,0x40,0xb7,0x01, -0xba,0x0d,0xd0,0x6a,0x80,0x4e,0x03,0x34,0x1a,0xd0,0xdf,0x06,0xe8,0x34,0x40,0xa7, -0x01,0x3a,0x0d,0xd0,0x69,0x80,0x4e,0x03,0x74,0x1a,0xa0,0xd3,0xc0,0xea,0x30,0x98, -0xa7,0xa0,0x07,0x5c,0x9c,0xc8,0xc5,0x49,0x5c,0x9c,0xcc,0xc5,0x29,0x5c,0x9c,0xca, -0xc5,0x69,0x5c,0xbc,0x89,0x8b,0x0b,0xb9,0x78,0x33,0x17,0x17,0x71,0xf1,0x16,0x2e, -0xde,0xca,0xc5,0xdb,0xb8,0xb8,0x98,0x8d,0x1b,0xb8,0xf2,0x0d,0x5c,0xf9,0x06,0xae, -0x7c,0x03,0x57,0xbe,0x81,0x2b,0xdf,0xc0,0x95,0x6f,0xe0,0xca,0x37,0x98,0xca,0xef, -0x41,0x7c,0xac,0x9c,0x8d,0x76,0xb1,0xdb,0x8d,0xfb,0xb9,0x18,0xde,0x68,0x2c,0x67, -0x8f,0x62,0x3a,0xb5,0x9a,0xdc,0xdf,0x4c,0x64,0xa7,0x55,0x6f,0xb2,0x7d,0xdf,0xcb, -0x16,0xad,0x62,0xa3,0xdd,0x6c,0xc9,0x26,0xce,0x56,0xdc,0xbd,0x71,0x84,0x2d,0x8b, -0x39,0x1a,0x1b,0xb3,0xf3,0xb8,0xd5,0x28,0x7f,0x0c,0xa7,0xa3,0x89,0x85,0x75,0xd3, -0x7e,0xec,0x5c,0xc3,0x15,0x58,0xc3,0x15,0x58,0xc3,0x16,0x80,0x52,0x13,0x7c,0xd6, -0xc4,0xfa,0x0c,0x77,0x8f,0xa6,0xad,0xdc,0x04,0x8e,0xa1,0xa7,0xb1,0x5d,0x8f,0xb6, -0xdb,0x97,0xb0,0x51,0x26,0xa7,0xc5,0xd9,0xd6,0x50,0xcd,0x1e,0x61,0xab,0xd3,0x79, -0x66,0x05,0x52,0x4c,0x34,0x99,0x0a,0xfc,0x98,0x13,0x53,0x6e,0x1f,0x7e,0xcc,0x09, -0x9b,0x1b,0x39,0x12,0x05,0x53,0x88,0x5d,0xc2,0x8e,0xa4,0x91,0xfa,0x1b,0x45,0xd9, -0xd2,0x81,0x14,0x4c,0x21,0x14,0x4a,0x3a,0x0a,0xa3,0x70,0xf2,0xa1,0xf3,0xe9,0xab, -0x28,0x9a,0x54,0xa4,0xa6,0x79,0xf3,0x69,0x42,0xc0,0x24,0x30,0x9f,0x5e,0xdf,0x33, -0x89,0x26,0x05,0xcc,0x01,0x2f,0x20,0x3f,0x85,0x66,0xcf,0xa1,0xf9,0xb3,0xe8,0xf9, -0x67,0x66,0xd2,0xf3,0xb3,0xe8,0x7c,0x4a,0x26,0xbd,0xb5,0x82,0xde,0x5a,0x49,0x6f, -0xad,0xa2,0xb7,0x56,0xd3,0x94,0x09,0x2f,0xa2,0x95,0x95,0xdc,0xca,0x49,0xc7,0xc5, -0xe1,0x5c,0x1c,0x14,0xc8,0x25,0x5a,0xd2,0x70,0x31,0x9b,0x0f,0xe1,0xf2,0x21,0xdc, -0xf2,0x2a,0x84,0x82,0xb9,0x98,0xdd,0x1f,0x4a,0x21,0x5c,0xcc,0xe6,0xc3,0xb8,0xa3, -0x61,0xdc,0x9e,0x30,0xd2,0x71,0x31,0xbb,0x9f,0xa6,0x4d,0xc3,0x0f,0x58,0x40,0x0b, -0xa6,0xe1,0x87,0x03,0x9b,0x2f,0xd2,0x8b,0xd3,0xf0,0x33,0x8d,0xce,0xa7,0x26,0x53, -0x20,0x27,0x75,0x3e,0xbd,0x04,0x3f,0xe6,0xc4,0x94,0xdb,0x8d,0x1f,0x73,0xc2,0xe6, -0x2e,0x30,0xe9,0x60,0x09,0xb1,0x36,0x62,0xe5,0xa7,0xa1,0x20,0x18,0x88,0xc5,0x1f, -0xcc,0x81,0x15,0xe8,0x05,0x34,0xc3,0x49,0x13,0x48,0xaa,0x20,0x35,0xa9,0x34,0x40, -0x0b,0x82,0x41,0x08,0x08,0x05,0x3a,0x10,0x06,0xc2,0x41,0x50,0x20,0x1b,0xb1,0x25, -0x83,0xd8,0xa2,0x41,0x6c,0xd9,0x20,0xb6,0x70,0x10,0x5b,0x3a,0x88,0x2d,0x1e,0xc4, -0x96,0x0f,0x62,0x2b,0x04,0xb1,0x35,0x34,0x6c,0x8d,0x09,0x60,0x22,0x98,0x04,0x26, -0x83,0x67,0xc0,0x14,0x30,0x15,0x3c,0x0b,0xa6,0x81,0xe9,0x60,0x06,0x98,0x09,0x66, -0x81,0xd9,0x60,0x0e,0x98,0x0b,0x9e,0x03,0xf3,0xc0,0x7c,0xf0,0x3c,0x78,0x01,0x2c, -0x00,0x0b,0xc1,0x8b,0xe0,0x25,0xf0,0xb2,0xfa,0x86,0x1b,0x58,0x4f,0x44,0x44,0x44, -0x53,0x34,0xfb,0x13,0x4d,0xe7,0x4b,0xf2,0x8e,0x56,0xd3,0xf9,0x3d,0x7b,0x40,0x39, -0xc0,0x48,0xda,0x83,0xe1,0xb5,0x67,0x2f,0xc0,0x70,0xda,0xb3,0x1f,0x54,0x01,0xb6, -0x4c,0x0d,0xa8,0x05,0x75,0xa0,0x1e,0x34,0x80,0x46,0xd0,0x84,0xd5,0x0e,0xa6,0x1b, -0xe5,0x98,0x6e,0x94,0x63,0xba,0x51,0x8e,0xe9,0x46,0x39,0xa6,0x1b,0xe5,0x98,0x6e, -0x94,0x63,0xba,0x51,0x8e,0xe9,0x46,0x39,0xa6,0x1b,0xe5,0x98,0x6e,0x94,0x63,0xba, -0x51,0x8e,0xe9,0x46,0x39,0xa6,0x1b,0xe5,0x98,0x6e,0x94,0x63,0xba,0x51,0x8e,0xe9, -0x46,0x39,0xa6,0x1b,0xe5,0x98,0x6e,0x94,0x63,0xba,0x51,0x8e,0xe9,0x46,0x39,0x86, -0x7a,0x39,0xa6,0x1b,0xe5,0x98,0x6e,0x94,0x63,0xba,0x51,0x8e,0xe9,0x46,0x39,0xa6, -0x1b,0xe5,0xec,0xca,0x0a,0xd3,0x8d,0x72,0x4c,0x37,0xca,0x31,0xdd,0x28,0xc7,0x74, -0xa3,0x1c,0xd3,0x8d,0x72,0x3c,0x51,0x71,0x4f,0xdd,0xd7,0x40,0x6d,0xb0,0xaa,0x7c, -0x3b,0xd8,0x09,0x76,0x01,0x76,0x55,0xb6,0x9f,0x0e,0xe3,0xd9,0x0c,0x1b,0x2b,0x60, -0x63,0x05,0xec,0xab,0x80,0x6d,0x15,0xb0,0xab,0x02,0x36,0x55,0xc0,0x9e,0x0a,0xd8, -0x52,0x01,0x3b,0x2a,0x60,0x43,0x05,0xf4,0x2b,0xa0,0x5d,0x01,0xdd,0x0a,0x68,0x56, -0x40,0xaf,0x02,0x5a,0x15,0xd0,0xa9,0xc0,0x94,0xa6,0x02,0xd3,0x99,0x0a,0x4c,0x65, -0x2a,0x30,0x95,0xa9,0xd8,0x01,0x30,0xb4,0x2a,0x4a,0x41,0x19,0x80,0x0f,0x2b,0xa0, -0x58,0x01,0xdf,0x55,0xc0,0x6f,0x15,0xf0,0x59,0x05,0xfc,0x55,0x01,0xab,0x2a,0xe0, -0xa7,0x4a,0xe8,0x57,0x42,0xbf,0x12,0xfa,0x95,0xd0,0xaf,0x84,0x7e,0x25,0xf4,0x2b, -0xa1,0x5f,0x09,0xfd,0x4a,0xe8,0x57,0x42,0xbf,0x12,0xfa,0x95,0xd0,0xaf,0x84,0x7e, -0x25,0xf4,0x2b,0xa1,0x5f,0x09,0xfd,0x4a,0xe8,0x57,0x42,0xbf,0x12,0x3d,0xab,0xc4, -0x58,0xae,0x84,0xd6,0xbe,0x42,0xea,0x2e,0x5b,0x4e,0x7d,0x8d,0x05,0xf4,0x51,0x35, -0x03,0xb6,0x83,0xfd,0xa0,0x89,0x3e,0xc2,0xf3,0xf7,0x23,0x3c,0x2b,0x3f,0xaa,0x5d, -0x06,0x36,0x81,0x5a,0xea,0x4a,0x6e,0xa2,0xae,0x94,0xad,0xa0,0x84,0xba,0x52,0x13, -0xc1,0x3a,0x80,0x7c,0xda,0x26,0xb0,0x8f,0xba,0xd2,0x19,0xea,0x5a,0x9a,0x43,0x5d, -0x19,0x35,0xd4,0xb5,0x6c,0x2d,0xc8,0x06,0xd5,0xd4,0xb5,0x7c,0x31,0xc8,0x05,0x3b, -0xa8,0x2b,0x13,0xf5,0x32,0xf7,0x53,0x57,0x56,0x3a,0x28,0xa2,0xae,0x9c,0xf5,0xd4, -0x95,0x5b,0x40,0x5d,0x45,0x68,0x67,0x0b,0x8e,0x6d,0x59,0x0a,0x50,0x77,0x4b,0x31, -0x75,0x6d,0xad,0xa4,0xae,0xd2,0x15,0xd4,0xb5,0x1b,0xed,0x96,0xa1,0x9d,0x3d,0x59, -0x60,0x33,0x40,0xbe,0x7c,0x37,0x28,0xa7,0xae,0x2a,0xe8,0x55,0xc1,0xa6,0x2a,0x36, -0xdf,0x48,0x5d,0x35,0x15,0x00,0xb6,0xe2,0x99,0xdf,0x55,0xb7,0x17,0x60,0x1f,0x66, -0xd8,0x5d,0x78,0xc0,0x75,0x35,0xa0,0x2d,0x3c,0x70,0xba,0x1a,0xd0,0x6e,0x53,0x12, -0x75,0xa7,0xa7,0x83,0xfd,0xd4,0xbd,0x24,0x83,0xba,0xd7,0x94,0x81,0xbd,0xd4,0xbd, -0x2e,0x0d,0xac,0x07,0x5b,0x40,0x0d,0x75,0xaf,0xdf,0x04,0xb6,0x52,0x77,0xce,0x1e, -0xea,0xce,0x45,0xf9,0xdc,0xed,0xd4,0xbd,0x0b,0xfb,0x4a,0x37,0x03,0x1c,0xdf,0x5d, -0x09,0x1f,0xa2,0x7e,0xd9,0x1a,0x90,0x43,0xdd,0xb0,0xb1,0x7b,0x0f,0xf2,0xb8,0x0e, -0x7a,0x16,0x97,0x50,0x4f,0xfa,0x4e,0xb0,0x1f,0xd4,0x81,0x26,0xea,0x59,0x92,0x06, -0x36,0x82,0x1c,0xea,0x59,0xba,0x8b,0x7a,0x32,0x93,0xc1,0x12,0xea,0x59,0x9d,0x0b, -0xb6,0x51,0xcf,0x9a,0x0d,0xa0,0x00,0x6c,0x07,0x7b,0xa8,0x67,0x5d,0x06,0x58,0x45, -0x3d,0x1b,0xd1,0xc6,0xc6,0x06,0xea,0xc9,0xde,0x0d,0xd0,0x4e,0x0e,0xd2,0x02,0xb4, -0x51,0xb0,0x19,0x54,0x52,0xcf,0x96,0x5a,0xea,0xd9,0x56,0x0d,0x1a,0xa9,0x67,0x07, -0xb4,0x4b,0x50,0xaf,0xa4,0x86,0x7a,0x30,0x8a,0x7b,0x2a,0xb1,0x1f,0x0f,0xe7,0x1e, -0x3c,0x80,0x7b,0x1a,0x97,0x82,0x06,0xea,0x65,0x16,0x83,0x65,0x60,0x15,0xa8,0xa4, -0xde,0xc5,0x4d,0xd4,0x9b,0xb8,0x84,0x7a,0xd3,0x8a,0xc0,0x4e,0x80,0x7d,0x69,0xf5, -0xd4,0x9b,0x8e,0x32,0xe9,0x39,0xa0,0x80,0x7a,0x97,0xec,0x06,0x15,0xa0,0x9a,0x7a, -0x37,0x2e,0x07,0x25,0xd4,0x5b,0xc8,0x00,0x94,0x29,0x44,0xbd,0xad,0x8d,0xd4,0xbb, -0x2d,0x83,0x7a,0x8b,0xd1,0xe6,0x76,0xec,0xc7,0xf5,0xdf,0x8b,0xeb,0xb8,0x17,0xd7, -0x63,0x6f,0x39,0xea,0xe0,0x5a,0xe8,0xdd,0xb7,0x05,0xec,0xa1,0xde,0xfd,0x68,0x1f, -0x93,0xa9,0xde,0xba,0xa5,0xd4,0xdb,0x88,0xf6,0xf1,0x18,0xed,0xc5,0xf2,0xa3,0x2f, -0x71,0x25,0xf5,0xa5,0x2c,0xa1,0xbe,0xb4,0x54,0xb0,0x0c,0x60,0x7b,0x6d,0x15,0xf5, -0xad,0x63,0xa8,0x6f,0x43,0x2e,0xf5,0x65,0xef,0x07,0x35,0xd4,0x97,0xb3,0x9d,0xfa, -0xf2,0x70,0x2c,0x2f,0x1f,0x6c,0x06,0x3b,0xa8,0xaf,0x20,0x03,0x94,0x82,0x4a,0xea, -0xdb,0xb4,0x98,0xfa,0x36,0xa3,0x4c,0x51,0x0e,0x28,0xa0,0xbe,0x6d,0xeb,0xc0,0x46, -0xb0,0x8b,0xfa,0x76,0x62,0x7f,0xc9,0x06,0x80,0xfd,0xbb,0xb6,0x82,0x32,0x50,0x01, -0xea,0xa9,0xaf,0x34,0x9b,0xfa,0x2a,0xb1,0x0f,0xf3,0xc8,0x3e,0x8c,0xa1,0x3e,0x4c, -0xfe,0xfa,0x30,0xa9,0xeb,0xc3,0x44,0xae,0x0f,0x13,0xb7,0x3e,0x4c,0xda,0xfa,0x30, -0xd1,0xea,0xc3,0x24,0xab,0xaf,0x3e,0x13,0xac,0x06,0x9b,0x00,0xca,0x34,0x2c,0x05, -0xab,0x70,0x3d,0xb1,0xd7,0x14,0x6c,0x6d,0xdc,0x44,0xfe,0x78,0x4e,0xac,0xc2,0xc8, -0x4f,0x06,0x29,0xe4,0x7e,0x21,0x71,0x23,0x1b,0x65,0xd3,0x85,0xc4,0xf4,0x0b,0x89, -0x4b,0x91,0x54,0x5e,0x48,0x4a,0xa7,0x73,0xc9,0x98,0x32,0x25,0x63,0xca,0x94,0x8a, -0x99,0x7d,0x32,0xa6,0x0e,0xa9,0x58,0x54,0xa4,0x62,0x9a,0x97,0x8c,0xe9,0x53,0x32, -0xa6,0x4f,0xc9,0x98,0x3e,0xa5,0xe2,0xf1,0x9f,0x8a,0x89,0x46,0x2a,0x26,0x10,0xa9, -0x98,0x76,0xa5,0x62,0xda,0x95,0x8a,0x69,0x57,0x32,0x16,0x31,0xc9,0x98,0xd2,0x25, -0x63,0x4a,0x97,0xcc,0xb6,0x85,0xe9,0x41,0x32,0xa6,0x9a,0xc9,0x58,0x08,0x25,0x63, -0xba,0x99,0xbc,0x0c,0x60,0xca,0x99,0x9c,0x09,0x30,0xed,0x4c,0xc6,0x1c,0x23,0x19, -0x73,0x8c,0x64,0x4c,0xfb,0x52,0x50,0x36,0x05,0xd3,0xaf,0x14,0x4c,0xbf,0x52,0x30, -0xfd,0xc2,0xd5,0x7d,0x2e,0x05,0xd3,0xaf,0x14,0x4c,0xbf,0x52,0xb0,0xd0,0x49,0xc1, -0x94,0x33,0x05,0x36,0xe1,0x8a,0x3f,0x97,0x02,0x9b,0x52,0x60,0x53,0x0a,0x16,0x3a, -0x29,0x98,0x7e,0xa6,0xc0,0x9e,0x14,0xd8,0x93,0x02,0x7b,0x52,0x60,0x4f,0x0a,0xec, -0x49,0x61,0xed,0xc1,0x34,0x32,0x19,0x53,0xbc,0x34,0xd8,0x93,0x06,0x7b,0x70,0x16, -0xcf,0xa5,0x41,0x3b,0x0d,0x53,0xbf,0x34,0x4c,0xfb,0xd2,0x30,0xcd,0x4d,0xc6,0x1a, -0x27,0x0d,0xd3,0xdc,0x34,0x4c,0x73,0x93,0x91,0x26,0x63,0xf1,0x95,0x8c,0xa9,0x4d, -0x32,0x6c,0x49,0x86,0x2d,0xc9,0xd0,0x4f,0x86,0x76,0x32,0x74,0x93,0xa1,0x9b,0xcc, -0xfa,0x02,0xba,0xc9,0xd0,0x4d,0x86,0x5e,0x32,0xa6,0x9a,0x29,0xf0,0x59,0x0a,0x34, -0x52,0xd8,0xf6,0x61,0x47,0x1a,0x6b,0x07,0xda,0x4f,0xc1,0xe2,0x2e,0x05,0x1a,0x78, -0x9e,0x9f,0x4b,0x65,0xfd,0x06,0x1b,0x52,0xd1,0xdf,0x54,0xf4,0x37,0x15,0xb6,0xa5, -0x42,0x2b,0xb5,0x10,0x77,0xb9,0x25,0xb8,0xfb,0xe4,0x20,0x5d,0x0a,0x4a,0x01,0xae, -0x98,0x35,0x15,0x48,0x71,0xe7,0xc3,0x6a,0xa9,0x6b,0x0b,0xee,0x56,0xb9,0x7b,0x48, -0x85,0x93,0xa3,0x66,0xe3,0x44,0x2e,0x4e,0xe2,0xe2,0x14,0x2e,0x4e,0xe5,0xe2,0x34, -0x2e,0x5e,0xc2,0xc5,0x19,0x5c,0xbc,0x8c,0x8b,0x33,0xb9,0x38,0x8b,0x8b,0x57,0x70, -0xf1,0x4a,0x2e,0x5e,0x85,0xf8,0xe2,0x1e,0xb6,0xcd,0x4b,0x89,0x1b,0xd8,0x38,0x63, -0x0b,0x1b,0xe7,0xd5,0xb3,0x71,0x71,0x3a,0x1b,0xef,0x59,0x83,0xf8,0xe3,0xc4,0x5d, -0x6c,0xbc,0x36,0x99,0x8d,0xf3,0xd9,0x16,0x3e,0xde,0xb3,0x9d,0x8d,0x6b,0xf6,0x22, -0xfe,0x24,0x89,0x6d,0xe1,0x93,0x65,0xac,0xd6,0x27,0x6b,0xd9,0x76,0x3e,0x2e,0x62, -0x5b,0xf8,0x78,0xc3,0x96,0x8f,0x37,0x25,0x9b,0x32,0x9f,0xe4,0xb0,0x19,0xdc,0xda, -0xd9,0xb8,0x76,0x19,0x97,0x67,0xed,0x44,0xb7,0xb9,0x3d,0xac,0x4a,0x57,0x4a,0x29, -0xb7,0x3f,0x89,0xcb,0x97,0xb0,0xf9,0x9a,0x3c,0x36,0xbf,0x7c,0x31,0x62,0xdc,0xf2, -0x10,0xe3,0x46,0xc6,0xe6,0xf7,0xec,0xe5,0xf6,0xb0,0x36,0xe0,0xb2,0x64,0xcb,0xe4, -0x16,0xb0,0x7b,0xd6,0x6f,0x65,0xe3,0x4d,0x3b,0xb9,0xa3,0x6c,0xfb,0x3d,0x5b,0x1a, -0xd9,0xa3,0x2b,0x96,0xb3,0xf9,0x25,0x35,0x88,0x7b,0x2b,0x0a,0xd9,0xfc,0x56,0xae, -0xe5,0xa5,0x6c,0x3b,0x1f,0xd5,0x15,0xb1,0xf9,0x95,0xac,0xcd,0x5d,0xa5,0xdb,0xd8, -0xa3,0x39,0xec,0x9e,0x8f,0xea,0x59,0xdd,0xde,0xca,0x34,0xce,0x86,0x35,0x5c,0x2d, -0xce,0xc2,0x7a,0x56,0x11,0xb7,0x26,0x2e,0xae,0x54,0x53,0xd7,0x5a,0x3c,0x0d,0x6a, -0xea,0xa9,0x67,0x77,0x16,0xcd,0x7d,0xfe,0x19,0xd2,0x04,0x91,0x46,0x43,0x1a,0xcc, -0x17,0x83,0x49,0x83,0x99,0x62,0x28,0x69,0x74,0xa4,0x09,0x23,0x4d,0x38,0x69,0x03, -0x49,0x1b,0x44,0x5a,0x0d,0x69,0xb5,0xa4,0x0d,0x26,0x6d,0x08,0xc1,0xff,0x04,0xef, -0x13,0x7c,0x4f,0xf0,0x3c,0xc1,0xef,0x04,0xaf,0x13,0x7c,0x4e,0xf0,0x38,0xc1,0xdf, -0x04,0x6f,0x13,0x7c,0x4d,0xf0,0x34,0xc1,0xcf,0x04,0x2f,0x63,0x5f,0xf5,0xc5,0xca, -0x4d,0x04,0x57,0x7f,0x9c,0x87,0xad,0xec,0x3d,0x78,0x30,0xae,0xc1,0x03,0x91,0x1d, -0x3e,0x49,0x78,0x68,0xe6,0x11,0xfc,0x80,0x87,0x04,0x6e,0xe8,0x5b,0xf0,0xe0,0x59, -0xb1,0x1c,0x37,0xfa,0x1a,0xdc,0xf8,0x0a,0x09,0x7d,0xc7,0xc3,0x11,0x37,0xe6,0x62, -0xf6,0x06,0x8f,0x1b,0xda,0x62,0x0c,0xaf,0xe4,0x9d,0xb8,0x2d,0xe0,0xc1,0x52,0x89, -0x1b,0xd2,0x26,0x3c,0x6c,0xb1,0x1a,0xeb,0x4a,0xdd,0x88,0x07,0x0b,0x1e,0x8c,0x55, -0xdb,0xf0,0x80,0xc4,0x83,0x6a,0x59,0x1d,0x1e,0x80,0x78,0x48,0x96,0xe2,0xe1,0x90, -0x53,0x84,0x32,0xb8,0x71,0x63,0x02,0x00,0xdf,0x60,0x88,0xe6,0x92,0x36,0x94,0xb4, -0x3a,0xd2,0x62,0xda,0x1b,0x4e,0xc1,0x98,0x9d,0x07,0x51,0xb0,0x86,0x82,0xb5,0x14, -0x1c,0x4c,0xc1,0x98,0x23,0x87,0x52,0xb0,0x8e,0x82,0xc3,0x28,0x38,0x9c,0x42,0x30, -0x59,0x65,0x1f,0x61,0x1a,0x36,0xd2,0xb2,0x51,0x30,0x1b,0x85,0xb0,0x51,0x28,0x1b, -0xe9,0xd8,0x28,0x8c,0x8d,0xc2,0xd9,0x28,0x28,0x90,0x8b,0xb9,0x3a,0x41,0x5c,0xa5, -0x67,0xa7,0xd2,0x33,0xf3,0xc0,0x02,0x9a,0xf9,0xfc,0x64,0xdc,0xbb,0x36,0x83,0x2d, -0x60,0x1b,0xd8,0x0e,0x76,0x82,0x12,0x50,0x0a,0xca,0x40,0x39,0x7b,0x7f,0x03,0xfb, -0x40,0x15,0xa8,0x01,0x75,0xa0,0x01,0x34,0xd1,0x85,0xa4,0xc5,0x20,0x19,0xa4,0x12, -0x7b,0x13,0xbc,0x90,0x84,0xfb,0x61,0x52,0x06,0x58,0x06,0x96,0x83,0x4c,0x90,0x05, -0x56,0x81,0xb5,0x60,0x03,0xc8,0x01,0xf9,0xa0,0x00,0x6c,0x02,0x85,0x00,0x76,0x24, -0xc1,0x8e,0x24,0xd8,0x91,0x04,0x3b,0x92,0x76,0x00,0xd8,0x92,0x04,0x5b,0x92,0x76, -0x01,0xd8,0x93,0x04,0x7b,0x92,0xf6,0x00,0xd8,0x94,0x84,0x8b,0xbc,0x6e,0x4b,0xd7, -0xaa,0x65,0x6c,0x17,0x2e,0x24,0xad,0xbc,0x90,0x54,0xcf,0xe9,0xb3,0x1b,0x25,0x50, -0xba,0x90,0x58,0x68,0xda,0xc0,0xbd,0x39,0xdb,0xd4,0xcb,0xcd,0x5c,0x19,0xb6,0x6f, -0x5b,0x60,0x22,0x7b,0x24,0x71,0x8f,0x69,0xa3,0x92,0xeb,0x46,0xe2,0xb6,0x0b,0x89, -0x3b,0xb0,0xc1,0x3a,0x02,0xfd,0x44,0xf7,0xb9,0x0a,0x4b,0xb8,0x6d,0xe4,0x12,0xd1, -0x6e,0x3d,0xeb,0x21,0xae,0x6a,0x8d,0x29,0x57,0xcf,0xf5,0x00,0x87,0xd8,0x76,0x38, -0xdf,0xc1,0xf4,0x0b,0x49,0x49,0x26,0x63,0xb0,0x51,0x8a,0xdd,0xa6,0x7a,0xbb,0xb8, -0x8d,0x4a,0x53,0xae,0x92,0xf3,0x40,0xe2,0x6e,0x6c,0x70,0x09,0x6b,0x10,0x5b,0x08, -0x25,0xb6,0x5e,0xaf,0xb1,0x9b,0xeb,0x0b,0x73,0xfd,0x48,0xa9,0x39,0x81,0xe1,0x9c, -0xca,0xa6,0x81,0xed,0xa4,0x42,0x53,0xff,0x4d,0xdd,0x33,0xed,0x2a,0xbb,0x6e,0xc5, -0x8d,0xc2,0xe6,0x1c,0x57,0x92,0x35,0xa4,0x8c,0x15,0x48,0xac,0xe7,0x8c,0x2f,0x35, -0x6d,0x97,0x72,0x2d,0x2d,0x67,0xcf,0x3c,0xd7,0xe9,0x1a,0xd3,0xf3,0xad,0x84,0x3b, -0xb3,0x6c,0x0e,0x47,0xf3,0xd8,0xf1,0xc0,0x56,0xe2,0x1a,0xe0,0x36,0x38,0x1f,0x5e, -0x77,0x47,0x15,0xd7,0x05,0xb3,0x83,0xeb,0xae,0x3b,0xd8,0x94,0x63,0x47,0x09,0x33, -0xd0,0x76,0x52,0x1a,0x37,0xac,0x92,0x96,0x98,0xce,0xb4,0xc9,0xae,0xa4,0xa5,0xa6, -0x71,0x93,0x39,0xd0,0x85,0xa4,0x2c,0xce,0x44,0x76,0x9c,0x71,0x67,0xfa,0xa6,0x96, -0x4c,0x67,0x9e,0x3d,0xb2,0x82,0xcb,0xed,0x32,0x35,0xb4,0x86,0x3d,0xdf,0x38,0x89, -0x66,0x9f,0xb0,0xdb,0x65,0xd7,0x73,0xac,0xc4,0x6a,0xd3,0x16,0x3b,0x50,0x06,0xce, -0xd8,0x12,0xd3,0x76,0xd1,0xc0,0x58,0x4a,0x5a,0xc7,0xee,0x46,0x67,0x12,0x8b,0x07, -0x0a,0x27,0x99,0x06,0x48,0xd2,0x06,0xb6,0xb9,0xc4,0xa6,0x81,0x11,0xc5,0x0e,0xb1, -0x06,0x2e,0x61,0xcf,0xe3,0x2a,0xae,0x00,0xeb,0xb2,0x64,0xd3,0x3e,0xd3,0x70,0x61, -0x73,0x30,0xbd,0x1a,0xb9,0x8d,0x5c,0x8e,0xbd,0x7c,0xf2,0x06,0x06,0x20,0xab,0x97, -0xcb,0x55,0x62,0x73,0x39,0x26,0x99,0x3c,0xee,0xd0,0x12,0xd3,0x0e,0xb3,0x94,0x29, -0xc7,0x1e,0xcd,0x1f,0x38,0x05,0xa5,0x03,0x1b,0x5c,0x81,0x7c,0xae,0x33,0x59,0xa6, -0x5c,0x09,0x77,0x15,0xb3,0xb9,0x4a,0xee,0xb4,0x14,0x9b,0xaa,0x16,0x0c,0x9c,0x6b, -0xd3,0x06,0x77,0xa5,0x15,0x70,0x27,0x6d,0xc5,0x75,0xa1,0x42,0x6e,0x6c,0x72,0xc9, -0xf5,0x13,0x73,0xf3,0x38,0x4b,0xda,0x32,0x70,0x7d,0x98,0x73,0xec,0xbe,0x6d,0xa6, -0x8b,0x8e,0xb8,0xb6,0x92,0xae,0x17,0xdd,0x69,0xba,0xa0,0x4b,0x58,0xf7,0xb3,0xa3, -0x99,0xcd,0xd5,0x73,0x0e,0xe6,0xae,0x6f,0xd3,0x6d,0xc0,0x74,0x59,0x60,0x4e,0x55, -0x61,0x6a,0x61,0x60,0x04,0x07,0xf6,0xc0,0x7d,0x41,0x6c,0xa4,0x61,0x23,0x2d,0x1b, -0x05,0xb3,0x51,0x08,0x1b,0x85,0xb2,0x91,0x8e,0x8d,0xc2,0xd8,0x28,0x9c,0x2b,0x6c, -0xaa,0xc2,0xd5,0x09,0xe2,0x2a,0x05,0x71,0xb5,0x82,0xb8,0x6a,0x41,0x5c,0xbd,0x20, -0xae,0x62,0x10,0x57,0x33,0x88,0xab,0x1a,0xc4,0xd5,0xd5,0x70,0x75,0x35,0x26,0x3d, -0xae,0xae,0x86,0xab,0xab,0xe1,0xea,0x3e,0x3b,0x77,0x02,0x4d,0x9e,0x40,0x13,0x5e, -0xa0,0x89,0x13,0xe6,0xd1,0x9c,0x05,0x34,0x77,0x12,0x4d,0x9e,0x85,0x1f,0x0d,0xd0, -0xd2,0xb4,0x17,0xf0,0x3c,0xab,0xec,0x4e,0x5f,0x41,0xdd,0x1b,0x4a,0xd9,0xbb,0x13, -0xd6,0x40,0xec,0x93,0xa0,0x7b,0x43,0x66,0x37,0x56,0x82,0x78,0xb0,0x74,0xd5,0x67, -0x7d,0x54,0x9f,0xcd,0x3e,0x59,0xd0,0xd2,0xec,0x09,0x74,0xac,0x7e,0x02,0xcd,0x9a, -0x40,0x33,0xf0,0x33,0x91,0x66,0x4d,0xa4,0xa9,0x13,0x69,0xd2,0x84,0x99,0x34,0x83, -0x8d,0xe6,0x4e,0xa1,0xd9,0x53,0x50,0x82,0x65,0x2a,0xcd,0x9a,0x4a,0x33,0xa6,0xd2, -0xb3,0x2f,0xd3,0x0c,0x30,0x0b,0x4c,0x05,0xcf,0x83,0x63,0xf5,0x33,0x69,0xd6,0x4c, -0x9a,0x8c,0x5a,0x33,0x69,0xca,0x2c,0x9a,0x3d,0x0b,0xbb,0x66,0xd1,0xac,0x59,0x34, -0x69,0x16,0xcd,0x60,0x33,0x1a,0xe4,0x34,0x84,0x9f,0x19,0x6c,0x02,0x3b,0x27,0x01, -0xfc,0xcc,0x60,0x93,0xf3,0xe9,0x6b,0xe7,0x9b,0x62,0x1c,0x87,0x51,0xb3,0xc0,0x54, -0x30,0x6f,0xc2,0x64,0x16,0xee,0xb0,0x39,0xd5,0xd0,0xdc,0xf9,0x34,0x7b,0x3e,0x9a, -0x47,0x8d,0xf9,0x34,0x77,0x01,0xcd,0x5e,0x80,0x8d,0x05,0x34,0x6b,0x01,0xcd,0x80, -0x2f,0x16,0xd2,0xec,0x85,0xd8,0x5e,0x48,0xb3,0x16,0xd2,0x0c,0xfc,0xb4,0x2f,0xa1, -0x59,0x60,0xe2,0x73,0x34,0x69,0x12,0x4d,0x9a,0x4c,0x93,0xd0,0x08,0xfa,0x30,0x19, -0xdd,0x7c,0x89,0x9e,0x9d,0x00,0x6f,0xd2,0xb4,0xd9,0x34,0x63,0x06,0xcd,0x78,0x9e, -0x66,0xce,0xa2,0x99,0xb3,0x69,0xe6,0x9c,0xa9,0x34,0xf3,0x45,0x9a,0x35,0x0d,0x5d, -0x9a,0x03,0x0f,0x3c,0x4b,0x73,0xe7,0xce,0xa2,0xb9,0xf3,0x68,0x3e,0x7e,0x16,0xd0, -0xc2,0x89,0xb4,0x00,0x6d,0xcc,0xa2,0x09,0x5c,0x1c,0xc4,0x2e,0xeb,0x34,0x6c,0xa4, -0x65,0xa3,0x60,0x36,0x0a,0x61,0xa3,0x50,0x36,0xd2,0xb1,0x51,0x18,0x1b,0x85,0xb3, -0x11,0x9e,0x85,0x6c,0xcc,0xd5,0x09,0xe2,0x2a,0x05,0x71,0xb5,0x82,0xb8,0x6a,0x41, -0x5c,0xbd,0x20,0xae,0x62,0x10,0x57,0x33,0x88,0xab,0x1a,0xc4,0xd5,0xd5,0x70,0x75, -0x35,0x26,0x3d,0xae,0xae,0x86,0xab,0xab,0xe1,0xea,0x6a,0xb8,0xba,0x1a,0xae,0xae, -0x86,0xab,0xab,0xe1,0xea,0x6a,0xb8,0xba,0x5a,0xae,0xae,0x96,0xab,0x3b,0x15,0x67, -0xf5,0xe2,0xc6,0xc5,0x20,0x09,0xa4,0x80,0x34,0xb0,0x04,0x2c,0x07,0x59,0x60,0x25, -0x58,0x0d,0xd6,0x82,0xf5,0x60,0x23,0xc8,0x01,0x79,0xa0,0x00,0x14,0x82,0x22,0xb0, -0x15,0x14,0x83,0x1d,0xa0,0x04,0x94,0xd2,0xc5,0x6c,0xb4,0x9b,0x8d,0x76,0xb3,0xd1, -0x6e,0x36,0xda,0xcd,0x46,0xbb,0xd9,0x19,0x00,0x6d,0x67,0xa3,0xed,0x6c,0xb4,0x9d, -0x8d,0xb6,0xb3,0xd1,0x76,0x36,0xda,0xce,0x46,0xdb,0xd9,0x68,0x3b,0x17,0xed,0xe5, -0xa2,0xbd,0x5c,0xb4,0x97,0x8b,0xf6,0x72,0xd1,0x5e,0x2e,0xda,0xcb,0x2d,0x03,0x95, -0x60,0x1f,0xa8,0x02,0x35,0xa0,0x0e,0x34,0x80,0x26,0xba,0x98,0x07,0xbd,0x3c,0xe8, -0xe5,0x41,0x2f,0x0f,0x7a,0x79,0xd0,0xcb,0x83,0x5e,0x1e,0xf4,0xf2,0xa0,0x87,0xa5, -0xdd,0xc5,0x3c,0xe8,0xe5,0x41,0x2f,0x0f,0x7a,0x79,0xd0,0xcb,0x83,0x5e,0x1e,0xfa, -0x92,0x87,0xbe,0xe4,0xa1,0x2f,0x79,0xd0,0xce,0x83,0x76,0x1e,0xb4,0xb1,0xfc,0xbb, -0x98,0x07,0xed,0x3c,0x68,0xe7,0x41,0x3b,0xaf,0x16,0xd4,0xd3,0xb9,0x7d,0xd0,0xcd, -0x83,0x5e,0x3e,0xf4,0xf2,0xa1,0x97,0x0f,0xbd,0x7c,0xe8,0xe5,0x2f,0x03,0xd0,0xc8, -0x87,0x46,0x3e,0xda,0xcf,0x47,0xfb,0xf9,0x68,0x3f,0x1f,0xed,0xe7,0xa3,0xfd,0x7c, -0xb4,0x9f,0x8f,0xf6,0xf3,0xd1,0x7e,0x3e,0xda,0xcf,0xdf,0x41,0x6d,0xbb,0xe8,0x48, -0x3e,0x1d,0x41,0xe5,0x52,0xb6,0x12,0x3a,0x96,0x8f,0x8e,0xe5,0x43,0x20,0x1f,0x1d, -0xcb,0x47,0xc7,0xf2,0x21,0x54,0x00,0xa1,0x02,0x56,0x08,0x2b,0x8d,0xfd,0x99,0xc8, -0xa7,0x83,0xa5,0xd4,0xb6,0x05,0x09,0xfa,0x56,0x80,0xbe,0x15,0x40,0xb7,0x00,0x4d, -0x14,0xa0,0x6f,0x05,0xd0,0x2e,0x80,0x36,0x96,0xf2,0x87,0x20,0x5f,0xb0,0x9f,0x2e, -0xee,0x42,0xab,0xa5,0xab,0xe8,0x08,0x96,0x6c,0x99,0x58,0x12,0x65,0x62,0x89,0x96, -0x89,0x65,0x51,0x26,0x96,0x45,0x99,0x58,0xa2,0x65,0x62,0x89,0x96,0x89,0x25,0x5a, -0x26,0x96,0x48,0x99,0x58,0xa2,0x65,0x62,0x99,0x94,0x89,0x25,0x5a,0x26,0x96,0x4a, -0x99,0x58,0x2a,0x65,0x62,0xa9,0x94,0x89,0xa5,0x52,0x26,0x96,0x4a,0x99,0x58,0xa2, -0x65,0x62,0x69,0x94,0x89,0xa5,0x51,0x26,0x96,0x68,0x99,0x58,0xa2,0x65,0x62,0x89, -0x96,0x89,0xe5,0x53,0x66,0x15,0xa8,0x06,0x35,0xa0,0x16,0xd4,0x01,0x78,0x2c,0xb3, -0x01,0x34,0x82,0x26,0x3a,0x97,0x85,0x25,0x56,0x16,0x96,0xa5,0x59,0x58,0x92,0x66, -0x61,0x29,0x9a,0x85,0x25,0x55,0x16,0x96,0x54,0x59,0x58,0x6e,0x66,0x61,0xe9,0x95, -0x85,0xa5,0x55,0x16,0x96,0x9a,0x59,0x58,0x6a,0x66,0x61,0xa9,0x99,0x85,0xa5,0x58, -0x16,0xec,0xce,0xc2,0x72,0x2f,0x0b,0x4b,0xcd,0x2c,0x2c,0x35,0xb3,0xb0,0xf4,0xca, -0xc2,0xd2,0x2b,0x0b,0xcb,0xbf,0x2c,0x38,0x25,0x0b,0x4b,0xc3,0xac,0x75,0x00,0x4b, -0xb5,0x2c,0x2c,0xd5,0xb2,0xb0,0x54,0xcb,0xc2,0x12,0x36,0x2b,0x07,0xb0,0xdf,0xed, -0x61,0x39,0x98,0x85,0x25,0x5a,0x16,0x96,0x86,0x59,0xe8,0x7f,0x16,0xfa,0x9f,0x85, -0xfe,0x67,0xa1,0xff,0x59,0xe8,0x7f,0x16,0xfa,0x9f,0x85,0xfe,0x67,0xa1,0xff,0x59, -0xe8,0x7f,0x16,0xfa,0x9f,0x85,0xfe,0x67,0xa1,0xff,0x59,0xe8,0x7f,0x16,0xfa,0x9f, -0x85,0xfe,0x67,0x95,0x51,0x6f,0x79,0x3a,0x75,0xe7,0xec,0xc5,0x7c,0x19,0x29,0x16, -0xee,0x1f,0x55,0x57,0x60,0x4e,0xbd,0x95,0xba,0xb6,0x62,0x9e,0xbd,0x0e,0x73,0xe8, -0xad,0xd8,0x9f,0xc6,0xbe,0x58,0xd9,0x41,0x3d,0x8d,0x29,0xd4,0xbb,0xa1,0x89,0x7a, -0xf3,0x6b,0xa9,0xb7,0xa8,0x9a,0xfa,0x12,0xb3,0xa8,0x7b,0x7b,0x22,0x75,0xef,0xcd, -0xa1,0x9e,0xa4,0x8d,0xd4,0x03,0x9b,0x7a,0x57,0x60,0xd1,0x8f,0xe5,0x70,0x5f,0x71, -0x39,0xe6,0xfd,0x89,0x98,0x83,0xef,0xa3,0xee,0x5d,0xc9,0xd4,0xc3,0xbe,0x20,0xd9, -0x50,0x4a,0x7d,0xd5,0xf9,0xd4,0xb5,0x0f,0xf7,0xef,0xa6,0x12,0xea,0x5d,0xbf,0x9c, -0x7a,0xb7,0x6e,0xa1,0xee,0x0c,0xf6,0xa5,0xca,0x06,0xea,0xdd,0x54,0x40,0x5d,0x75, -0x4b,0xb1,0x36,0x58,0x4f,0xdd,0xfb,0x76,0x52,0x4f,0x46,0x3d,0xf5,0x61,0xa6,0xf2, -0x51,0x63,0x2a,0xe6,0xfc,0x55,0xd4,0x95,0x91,0x4f,0xdd,0xab,0x93,0xa9,0xbb,0x64, -0x35,0xda,0x5b,0x81,0xb9,0x7e,0x31,0xda,0x4c,0xa5,0xde,0x8d,0xb9,0xd4,0x5b,0x55, -0x46,0x7d,0xb9,0x15,0xd4,0x57,0x8a,0xb4,0xaa,0x96,0x7a,0x36,0x2f,0xc3,0x7a,0xa2, -0x89,0x7a,0xf6,0x6f,0xa2,0x5e,0xf8,0xbf,0x6f,0x59,0x32,0xf5,0x96,0xac,0xa3,0xae, -0x22,0x68,0xd4,0xc3,0x9e,0x72,0xec,0x5f,0xdc,0x88,0xb5,0xc7,0x66,0xea,0x29,0x5a, -0x8a,0xfe,0x27,0x52,0x5f,0x0e,0x34,0x8a,0x36,0x50,0x57,0xf9,0x66,0xf4,0x6b,0x35, -0x75,0x57,0x65,0x53,0x77,0x7d,0x16,0xf5,0xec,0x2d,0xa3,0x9e,0x6a,0xac,0x47,0x36, -0x66,0xc0,0x86,0x55,0xd4,0x9b,0x88,0x34,0x2d,0x17,0x40,0x63,0x7b,0x2e,0x34,0x1a, -0xa1,0x51,0x8e,0xe3,0xfb,0xa8,0x77,0x37,0x83,0xfe,0x64,0x41,0xab,0x11,0xbe,0xac, -0xa1,0xae,0xb2,0x62,0xea,0x66,0x56,0x51,0x4f,0x66,0x1a,0xf5,0xac,0xdd,0x83,0xb5, -0xca,0x7a,0xe8,0xd5,0xa1,0x4f,0x4d,0xd4,0xd5,0xb8,0x03,0x65,0x96,0x63,0x6d,0xb3, -0x8c,0xba,0xd7,0x56,0x43,0x67,0x33,0x7c,0x9f,0x44,0x5d,0x85,0xf9,0xd4,0x93,0x8b, -0x35,0xcc,0x4a,0xf8,0x65,0x27,0xd6,0x4a,0xbb,0x6b,0xa9,0xbb,0x02,0xe7,0x21,0x03, -0x3a,0x19,0x5b,0xa9,0x1b,0x63,0xbe,0x67,0xed,0x56,0xf8,0x77,0x37,0xd6,0x55,0x58, -0x23,0xa5,0x2d,0xa1,0xee,0xcd,0x8b,0xa9,0xa7,0x12,0xfe,0x58,0x82,0x73,0x5a,0xb2, -0x8a,0xfa,0xd2,0xb0,0xf4,0xcb,0xd8,0x47,0x5d,0x2b,0x71,0x0e,0xea,0x76,0x51,0xf7, -0xfa,0x14,0xf8,0x1c,0xfd,0xdd,0x82,0x73,0xb3,0x0e,0xe7,0x61,0x07,0xd2,0xda,0xf5, -0xd4,0x57,0x97,0x89,0xf3,0x9f,0x8a,0xf5,0x54,0x15,0xf5,0x36,0x40,0x1f,0xab,0xb9, -0xee,0x54,0x9c,0x9f,0x74,0xf4,0x63,0x15,0xdb,0xff,0x22,0xea,0x49,0xc1,0x5a,0x6d, -0x15,0x28,0x5e,0x4a,0x3d,0x55,0x18,0x2b,0x8b,0xa1,0x51,0xbf,0x8d,0x7a,0x57,0xef, -0xa6,0x3e,0xa6,0x88,0xfa,0x96,0x2f,0xa5,0xae,0x74,0xac,0xbf,0x96,0x62,0x4d,0xb6, -0xaa,0x01,0x63,0x21,0x1d,0xe3,0x83,0xd5,0x5d,0x42,0x5d,0x4d,0x38,0xff,0xcb,0xf1, -0x68,0xde,0xbd,0x9b,0x7a,0xf6,0x40,0x1f,0xe3,0x01,0x2b,0x0b,0xea,0xca,0x81,0x6f, -0xf6,0xa3,0x5f,0x0c,0xfc,0x87,0x6b,0xaf,0x67,0x25,0x3b,0xbe,0xb2,0xd1,0x76,0x2a, -0xf5,0xa5,0xef,0xc5,0xb9,0xd8,0x4f,0x7d,0x79,0xe9,0x58,0xf3,0x6d,0x80,0xaf,0x32, -0xa8,0x67,0x1b,0xb4,0x52,0xb1,0x3f,0xbb,0x9a,0xba,0xb1,0xa2,0xf9,0xa8,0x16,0x63, -0x22,0x29,0x1b,0xfe,0x85,0x0f,0xf6,0x34,0x51,0xf7,0x3a,0x8c,0xbf,0xb4,0x4c,0x8c, -0xd3,0x44,0xd8,0x85,0x71,0xb3,0xb8,0x9e,0xba,0xb3,0x51,0x7f,0x03,0x8e,0x2f,0x85, -0x7f,0xf2,0x60,0x43,0x41,0x25,0xc6,0xd5,0x62,0xea,0xae,0x85,0xe6,0x0a,0xf4,0x67, -0x2d,0xea,0x14,0x96,0xc0,0xef,0x2b,0xa8,0x0f,0x3e,0xef,0x4e,0x87,0xc6,0xc6,0x6a, -0xea,0x62,0x4a,0xa8,0x6b,0x2f,0xd6,0x98,0x55,0x3b,0xd1,0x4f,0xac,0x3f,0xa1,0xdf, -0x9d,0x82,0x71,0xb7,0x09,0xfe,0xca,0x86,0x6e,0x3a,0x7c,0xbe,0x02,0xfe,0x2d,0xc7, -0xb9,0xd8,0x80,0x71,0x92,0x07,0x1f,0x6e,0xde,0x8e,0xf3,0xb8,0x15,0xed,0xa6,0x52, -0xcf,0x7a,0x8c,0xa9,0x06,0xac,0x69,0x8b,0x30,0x0e,0x8a,0x0a,0xd1,0x17,0xf8,0x75, -0x19,0x6c,0x64,0x5f,0x10,0xaf,0xc8,0x83,0x9d,0x18,0xbb,0xab,0x30,0xde,0xd7,0xd7, -0xfe,0x3f,0xf6,0xde,0x06,0xa8,0xa9,0x6b,0xed,0xfb,0x5e,0x51,0x50,0xa2,0x50,0x82, -0x82,0x82,0x82,0x05,0x25,0x0a,0x96,0x40,0x52,0x93,0x96,0x20,0xb4,0xa0,0xa0,0x04, -0x12,0xbe,0xa3,0x44,0x41,0xa1,0x05,0x4c,0x20,0x91,0x04,0x12,0x20,0x90,0x40,0x02, -0x49,0x48,0x42,0x62,0x49,0x05,0x0a,0x2d,0xb4,0xa0,0xa0,0x60,0xc1,0x8a,0x10,0x05, -0x0b,0x56,0xee,0x91,0xe7,0x96,0x3e,0x32,0x23,0x33,0x72,0x17,0x5a,0x79,0x47,0x4e, -0x09,0xc2,0x3d,0xf2,0x0e,0xcc,0xc8,0x8c,0xcc,0xc8,0xcc,0xbb,0x56,0xd8,0xf6,0xf5, -0x9c,0xe7,0x9c,0xfb,0x7c,0xf5,0x9c,0xe7,0x7c,0x90,0x5f,0xd7,0x7f,0xed,0xf5,0xb1, -0xf7,0x5e,0xeb,0xba,0xae,0xb5,0xf6,0x8e,0x61,0xa6,0xf0,0x7a,0xd0,0x7e,0xdd,0xf0, -0xba,0x77,0xbe,0x02,0xb3,0x70,0x97,0x9d,0xbd,0x5d,0x05,0xd7,0x42,0x3b,0x78,0xd6, -0x09,0x7d,0x7c,0x13,0xc6,0x4d,0x95,0x02,0x3c,0x2b,0x6f,0x00,0x73,0x5d,0xd0,0x0e, -0xdf,0x2a,0xc0,0x9c,0xe1,0x16,0xbc,0x3f,0xbc,0x8e,0x16,0xae,0xcd,0x3b,0x57,0x60, -0x4c,0xc0,0xb5,0xd0,0x03,0xe3,0xfa,0x3a,0xf4,0x6f,0x2f,0x9c,0xc3,0xe7,0xd0,0x3f, -0xe8,0x1f,0xda,0xe0,0x13,0xcf,0xf2,0xed,0x45,0x18,0x1f,0x0a,0x60,0xf9,0x0c,0xa6, -0x16,0xe8,0xcb,0x86,0x5e,0x18,0x2b,0x30,0xb6,0x0c,0x70,0x6d,0x7d,0x01,0x7d,0xd9, -0x3a,0x00,0xd7,0x08,0x2c,0xb7,0xc1,0x71,0x5f,0xf9,0x04,0xda,0xbb,0x0e,0xde,0x13, -0xc6,0x58,0x3d,0x8c,0xf1,0xaf,0xe1,0xfa,0x56,0xc0,0x54,0x01,0xd7,0xf3,0x65,0xb8, -0xa6,0x2e,0x43,0x9b,0xb6,0xc2,0xef,0xe0,0xb7,0xa0,0x0f,0xe0,0x77,0xab,0xf9,0xcf, -0x6f,0x43,0xbf,0x40,0xdb,0x56,0x5c,0x85,0xf7,0x80,0xf7,0x82,0x6b,0xd8,0x52,0x05, -0xfd,0x6a,0x44,0xfb,0x06,0xfc,0x1e,0x0f,0xd7,0xa5,0xa5,0xa1,0x1d,0xde,0x17,0xde, -0x1b,0xda,0xc6,0xd2,0x0d,0xd7,0x79,0x79,0x0d,0xb4,0x23,0xb4,0x93,0x06,0xda,0xab, -0xf2,0x26,0x8c,0x79,0xb8,0x7f,0x40,0x5b,0xcd,0xc2,0x27,0xe7,0xec,0x25,0x38,0x7e, -0xf3,0x6d,0x30,0xdb,0x87,0x62,0x0e,0xda,0x4f,0x0d,0xe3,0x45,0x0b,0x7d,0x53,0x0d, -0x8f,0x2f,0x99,0xe0,0x18,0xa1,0xbf,0xe0,0x5e,0xf4,0xac,0x0d,0xda,0xae,0x0d,0x8e, -0xf3,0x8a,0x11,0x26,0xe8,0xef,0x0e,0x68,0x9f,0x6b,0x30,0xf6,0xbf,0x81,0x79,0x1f, -0xec,0xd3,0x0f,0x8f,0x07,0x6f,0x82,0x39,0x05,0xdc,0xab,0x2a,0xbb,0xe0,0x7a,0x80, -0x7e,0x30,0x7d,0x06,0xe6,0x5a,0xab,0xc1,0xdc,0x95,0x3a,0x30,0xd7,0x09,0xd7,0x55, -0x27,0xac,0xeb,0xb9,0x08,0xe6,0x7a,0x61,0x9c,0xde,0x85,0x71,0xf6,0x05,0xf4,0xc1, -0x57,0xdf,0xc1,0x58,0x83,0x31,0xa2,0x34,0x83,0xc5,0xd6,0xaa,0xeb,0x60,0x4e,0x0b, -0x0d,0xde,0x77,0x05,0x6e,0x3c,0x70,0x11,0xc1,0x41,0xcf,0xdc,0x85,0x13,0x51,0xab, -0x61,0x19,0xe6,0x55,0xd0,0xf8,0x55,0xd0,0x09,0xb5,0xb5,0x70,0xb2,0x0d,0xd0,0xc0, -0xd0,0xc1,0x97,0x7a,0xe0,0x66,0xa9,0x82,0xa9,0x06,0x58,0x3a,0x60,0xb9,0x13,0x4e, -0xfa,0xdb,0x4f,0x61,0x82,0xed,0x83,0x30,0x60,0xee,0x76,0xc1,0xc0,0xad,0x87,0x06, -0x80,0x93,0x57,0xc1,0xc5,0xac,0x6e,0x86,0x01,0x07,0x17,0x1b,0x7c,0x60,0xcc,0x56, -0x37,0x82,0x59,0xd3,0xa7,0xd0,0x20,0x75,0x70,0xf3,0xfb,0x02,0x06,0x14,0xcc,0xbf, -0x85,0xf9,0xb7,0x30,0xf0,0x15,0xd0,0x41,0xf0,0xf1,0xfa,0x4c,0x6b,0x06,0xcf,0xf4, -0x70,0x23,0x32,0x75,0x83,0x67,0x35,0x30,0xc8,0x6a,0xbe,0x05,0xcf,0xea,0x61,0x30, -0xd5,0x7f,0x05,0x9d,0x09,0x37,0x60,0xb8,0x89,0x3f,0x6b,0x82,0x4e,0x6d,0x82,0x86, -0xb8,0x0c,0x37,0xa6,0x6b,0xb0,0x5f,0x37,0xec,0x63,0x86,0x6d,0x03,0xd0,0xa9,0x46, -0xe8,0x54,0xb4,0xe1,0x37,0xc1,0xc9,0xb7,0x41,0x23,0x5c,0x81,0x1b,0xe7,0x35,0xb8, -0x99,0x7f,0x0d,0x03,0xee,0x6b,0x68,0xa8,0xeb,0x70,0x13,0x1c,0x80,0x0e,0x57,0xc2, -0x4d,0x54,0xd3,0x00,0xe6,0x75,0xd0,0x30,0x0d,0xd0,0xe9,0xf0,0xb1,0x3b,0xdf,0x5c, -0x05,0xe6,0x3b,0x3f,0x05,0x8b,0x97,0x5a,0xf4,0x48,0x54,0x60,0xf1,0x32,0x7c,0x46, -0xfd,0x7c,0xbd,0x09,0xcc,0x28,0xa0,0x99,0x14,0xfd,0x60,0xb1,0x4d,0xa7,0x83,0xd2, -0x53,0x0d,0x16,0xaf,0xc2,0xb0,0x9b,0x1f,0xac,0x00,0xf3,0x77,0x2f,0x80,0xac,0x2c, -0x90,0xc5,0x03,0x59,0xf0,0xbd,0x19,0x65,0x30,0xcf,0x17,0x83,0xf1,0x5e,0xf4,0xc7, -0x4b,0xbd,0xe8,0x8f,0x9e,0x7a,0xd1,0xdf,0x39,0xdd,0x5d,0x2b,0x76,0x81,0x1f,0x1a, -0xfe,0xab,0x17,0xfc,0x70,0xf3,0xbf,0x6e,0x83,0x1f,0x2e,0x81,0x1f,0x6a,0xc0,0x0f, -0x5f,0x80,0x1f,0x1a,0xc1,0x0f,0x4d,0xe0,0x87,0x76,0xf0,0xc3,0x35,0xf0,0x43,0xc7, -0x0f,0x4a,0x24,0xe5,0x50,0xfe,0x6b,0xc0,0x7a,0x0c,0x33,0x58,0xaa,0x46,0x67,0x54, -0xff,0x57,0x1f,0x92,0x01,0xf0,0x83,0x09,0xc9,0xa7,0x48,0x2e,0x22,0xa9,0x41,0x52, -0x8b,0xa4,0x0e,0x49,0x3d,0x92,0x06,0x24,0x9f,0x23,0xf9,0x02,0x49,0x23,0x92,0x2f, -0x91,0x34,0x23,0x69,0x41,0x72,0x19,0x49,0x2b,0x92,0x2b,0x48,0xae,0x22,0x69,0x47, -0xd2,0x81,0xe4,0xda,0xda,0x45,0xfb,0xd1,0xdd,0xbe,0x43,0x57,0xf9,0x0e,0x75,0x86, -0x52,0x0d,0x47,0x3c,0xd9,0x0d,0x26,0xbf,0x05,0x93,0x77,0xc1,0x8f,0x0a,0x30,0x79, -0x07,0x4c,0x7e,0x07,0x26,0xfb,0xc1,0x8f,0xad,0xe0,0xc7,0x2b,0xe0,0x47,0x15,0xf8, -0xb1,0x02,0xfc,0xa8,0x01,0x3f,0x56,0x82,0x1f,0x8d,0xe0,0x47,0x03,0xf8,0xf1,0x02, -0xf8,0x51,0x0b,0x7e,0xac,0x07,0x3f,0x9a,0xc0,0x8f,0x1d,0xe0,0xc7,0x6f,0xc0,0x8f, -0x3d,0xe0,0xc7,0x6e,0xf0,0xe3,0x1d,0xf0,0xe3,0xb7,0xe0,0x27,0x05,0xf8,0x49,0x09, -0x7e,0xbc,0x0b,0x7e,0xfa,0x14,0xfc,0x74,0x11,0xfc,0xd8,0x05,0x7e,0xaa,0x04,0x3f, -0x69,0xc0,0x4f,0x5a,0xf0,0x53,0x15,0xf8,0x49,0x0d,0x7e,0xd2,0x81,0x9f,0xaa,0xc1, -0xa4,0x0e,0x4c,0x5c,0x99,0xb8,0x0a,0xe5,0xa7,0x5a,0x28,0x93,0x5a,0x74,0x54,0x89, -0x44,0x83,0xc4,0x5a,0xac,0x46,0x0d,0xb0,0xb7,0x01,0x75,0xed,0x44,0xd2,0x85,0x6a, -0xd4,0x48,0xf4,0x60,0xa2,0x1d,0xd5,0xb5,0xa3,0xba,0xf6,0x89,0xeb,0x50,0x50,0x43, -0x3b,0xba,0x6e,0x3b,0x6a,0xbd,0x86,0x5a,0xaf,0xa1,0xd6,0x6b,0xa8,0xf5,0x1a,0x6a, -0xbd,0x86,0x5a,0xaf,0xa1,0xd6,0xaf,0x51,0xeb,0xd7,0xa8,0xee,0x6b,0x54,0xf7,0x35, -0xaa,0xeb,0x44,0x9d,0x3b,0x51,0x5d,0x17,0x6a,0xed,0x42,0x47,0xd7,0xd1,0xd1,0x75, -0xd4,0x70,0x1d,0x15,0x7b,0x50,0xb1,0x07,0x15,0x7b,0xd0,0x45,0x7b,0x50,0x9d,0x19, -0x15,0xcd,0xe8,0xe8,0x16,0x6a,0xbd,0x85,0x8a,0xb7,0x50,0xeb,0x2d,0x54,0x77,0x1b, -0x15,0x6f,0xa3,0xa3,0x3e,0x24,0xfd,0xa8,0x4b,0x3f,0x3a,0xba,0x83,0x8e,0xee,0xc0, -0xa3,0x49,0x25,0x3c,0x82,0xd2,0x85,0xe4,0x3a,0x14,0x6b,0x1d,0x1c,0x15,0x14,0x3d, -0x98,0x2c,0x47,0x0d,0xe5,0xa8,0xae,0x1c,0xd5,0x95,0xa3,0xba,0x0a,0x68,0x38,0x28, -0x9d,0x48,0xba,0x90,0xc0,0xd3,0x2a,0x26,0x55,0x48,0xd4,0x48,0x74,0x48,0x60,0x3f, -0x15,0xea,0xa2,0x42,0x5d,0x54,0xa8,0x8b,0x0a,0xb5,0xaa,0x50,0xab,0x0a,0xb5,0xaa, -0x51,0xab,0x1a,0xb5,0xaa,0x51,0xab,0x1a,0xb5,0xaa,0x51,0xab,0x1a,0xb5,0x6a,0x50, -0xab,0x06,0xb5,0x6a,0x50,0xab,0x06,0xb5,0x6a,0x50,0xab,0x06,0xb5,0x56,0xa2,0xd6, -0x4a,0x54,0x57,0x89,0xea,0x2a,0x51,0x9d,0x1e,0xd5,0xe9,0xd1,0x19,0x7a,0x74,0x86, -0x1e,0xb5,0xea,0x51,0xab,0x1e,0x59,0xf7,0xc6,0xe4,0x0d,0x30,0xd1,0x0d,0x65,0x52, -0x07,0xc5,0x73,0xd2,0x30,0x69,0x82,0x6a,0xb4,0xea,0x05,0xab,0x7e,0x62,0xd5,0x6a, -0xab,0x9a,0x50,0xef,0x2b,0x13,0xdd,0x48,0x6e,0x22,0x7f,0x6b,0x90,0x97,0xbb,0x91, -0xdc,0x44,0x0e,0xd6,0x20,0xb7,0x76,0x23,0xb9,0x89,0x3c,0xaa,0x41,0x1e,0xed,0x46, -0x72,0x13,0x39,0x53,0x83,0xa6,0x74,0x15,0x8d,0xbc,0x1b,0xc9,0x4d,0x34,0x68,0x0d, -0x1a,0x56,0x37,0x92,0x9b,0x68,0x44,0x28,0xca,0x90,0x93,0xae,0x4c,0x56,0xa2,0xeb, -0x55,0xa2,0xab,0x54,0x22,0x67,0x5a,0xed,0x54,0x89,0xce,0xa8,0x44,0xf3,0xba,0x81, -0x3a,0x57,0xa2,0x8b,0x56,0x22,0x7f,0x43,0xe9,0x45,0xde,0xeb,0x45,0xad,0x0a,0xeb, -0xc0,0x61,0xf6,0xc9,0x5a,0x86,0x86,0x0e,0x7d,0xad,0x43,0xa2,0x47,0x6e,0xd6,0x21, -0xd1,0x23,0x37,0xeb,0x90,0xe8,0xd1,0x15,0x74,0x48,0xf4,0xe8,0x0a,0x3a,0x24,0x7a, -0x14,0x68,0x3a,0x24,0x28,0x02,0xd1,0x51,0x27,0x3a,0xba,0x8e,0x8e,0xae,0xa3,0x23, -0x33,0x3a,0x32,0xa3,0xa3,0x5b,0xe8,0xe8,0x96,0xf5,0x5c,0x14,0x3b,0xbd,0x28,0xb2, -0x7a,0xd1,0x1c,0x7a,0xd1,0xd4,0x7b,0x90,0x98,0x91,0xdc,0x42,0x72,0x75,0xb2,0xca, -0xba,0x06,0xac,0x61,0xbf,0x16,0xd0,0xd7,0xd6,0x42,0xfa,0xda,0x5a,0x28,0x5f,0xb3, -0xfa,0xdf,0x9a,0x75,0xad,0x65,0xd7,0xad,0xd1,0x8f,0x4a,0x5d,0xd6,0x10,0xb0,0x66, -0x3a,0x6b,0xbc,0xaf,0x85,0x7d,0xe7,0x5a,0xe0,0x77,0xae,0xcd,0xc1,0xda,0xb3,0x67, -0xed,0x2a,0x3d,0xd6,0xe8,0x41,0x4b,0x01,0x5b,0x0c,0x56,0x1b,0xad,0xdd,0xaf,0x77, -0x6d,0x46,0xbd,0x6b,0x77,0xe8,0x5d,0x6b,0xbb,0xb5,0x36,0xeb,0x5b,0x6b,0x63,0xb9, -0xbd,0x76,0xb1,0xdb,0x6b,0x6d,0xb7,0xd7,0xee,0xde,0xbf,0x36,0xf8,0xfe,0xb5,0xca, -0xfe,0xb5,0xb1,0xdc,0x59,0x2b,0xdd,0x59,0xeb,0x72,0x67,0x2d,0x5e,0x95,0xd6,0xab, -0xa0,0x45,0x82,0xad,0x15,0x6b,0x9c,0x5b,0xef,0x6e,0xcd,0xf4,0x6b,0x99,0xce,0xba, -0x20,0xd6,0x96,0xc4,0xf5,0xb5,0xa5,0x80,0x45,0x7f,0x17,0xb6,0x12,0xac,0x2b,0x60, -0xed,0x04,0xb5,0x75,0xb6,0x28,0x5b,0xab,0xbc,0xbe,0xd6,0xe5,0xfa,0x5a,0xa9,0x13, -0x85,0x49,0xe5,0xda,0x0a,0xaa,0x5c,0xbb,0x9f,0x66,0xed,0x74,0xcd,0xda,0x8d,0x34, -0x6b,0xe7,0x69,0xac,0xc6,0x42,0x8b,0xc7,0xba,0xa4,0xd6,0x86,0xab,0x5f,0x9b,0x43, -0xfb,0x9a,0x7f,0xaf,0xad,0x99,0xe7,0xda,0x9a,0x59,0xaf,0xbd,0xae,0xbc,0xbe,0xb6, -0x55,0x59,0xa7,0x79,0x6d,0x6d,0xee,0x9d,0x6b,0xa5,0xce,0x35,0xd3,0x75,0xbe,0x76, -0x8e,0xb5,0xa7,0x79,0x2d,0x80,0x7a,0xd7,0xb2,0x5b,0xaf,0x27,0xdd,0x69,0xcd,0xd6, -0xee,0xae,0x5f,0xab,0xd4,0xaf,0x55,0xea,0xd7,0x2a,0xd5,0x6b,0x59,0xf9,0xeb,0x35, -0xdf,0xb5,0x16,0xb8,0xd6,0x52,0xc5,0x2f,0x1b,0x01,0xb6,0x47,0xbc,0xbe,0xa6,0x7a, -0xad,0x4d,0xbd,0x36,0x16,0xbd,0x75,0xbf,0xc4,0x6c,0x86,0x32,0xe5,0xda,0x38,0xdb, -0xd7,0xda,0x7a,0xd6,0xce,0xd3,0xac,0x4d,0xd3,0x3c,0xa9,0x82,0xcf,0x05,0x78,0x3f, -0x94,0xc1,0x20,0x55,0x59,0x17,0x1b,0x3c,0xa8,0xb0,0x2e,0xef,0x35,0x27,0x4c,0x7c, -0x63,0xed,0xb8,0xb6,0x4f,0x76,0xc3,0xe0,0xd2,0xc2,0xbd,0x0d,0x96,0x54,0xd6,0xa5, -0x38,0xa9,0x85,0x35,0x2a,0x6b,0xb8,0x59,0x37,0xb3,0xb5,0xcc,0xf3,0xf5,0xc5,0x3c, -0x5f,0x77,0xf4,0xfc,0xff,0x3b,0x76,0x4e,0xaa,0x3c,0x91,0x58,0xfb,0xc0,0xdb,0x75, -0xff,0x64,0x40,0x87,0xc0,0x0f,0xfc,0xac,0x50,0x82,0x20,0xe0,0x05,0x3e,0x84,0x47, -0x75,0x30,0x7d,0x06,0x7e,0xa3,0x40,0x7f,0x56,0x75,0x11,0x9c,0x05,0x25,0x40,0x0e, -0x6b,0x6a,0x60,0x82,0x2f,0x0e,0x8a,0x6a,0x98,0x4c,0x30,0xe9,0x61,0xaa,0x82,0xc9, -0x00,0x93,0x11,0xa6,0x0b,0x30,0x7d,0x02,0x4e,0x83,0x54,0xb0,0x0f,0xec,0x07,0x07, -0x01,0x09,0x04,0x83,0x0f,0x40,0x0a,0xf0,0x06,0x44,0x10,0x0a,0x77,0xb2,0x2a,0xb4, -0x33,0x54,0xa1,0x0d,0x0f,0x6d,0x77,0x68,0xb3,0xb3,0x6e,0x1c,0x68,0xbf,0xb3,0x6e, -0x1d,0x68,0xcb,0xb3,0x6e,0x1e,0x68,0xd7,0x43,0x07,0x68,0xfb,0xfb,0x14,0x1d,0x40, -0x03,0xb5,0x80,0x89,0x4b,0x60,0xe2,0x32,0x98,0x68,0x05,0x13,0x6d,0x70,0xb3,0x02, -0xe8,0x89,0xd9,0x0e,0x26,0x3a,0x60,0x50,0xc0,0x3d,0x09,0x58,0x9f,0x53,0x00,0xad, -0x2b,0x68,0x32,0xb4,0xbf,0x02,0xb4,0x01,0xf6,0xc0,0x40,0x80,0x66,0x81,0x71,0x00, -0x17,0x13,0x7c,0xf0,0x40,0xdb,0xc1,0xb5,0x02,0x1d,0x03,0x0d,0x0f,0x5d,0x07,0xd0, -0x93,0x02,0x05,0x27,0xb0,0x5a,0x13,0xac,0xb9,0xf5,0x12,0x92,0xcb,0x48,0xda,0x90, -0x5c,0x05,0x7b,0xc1,0x01,0x10,0x00,0xce,0x00,0x19,0x28,0x05,0xbf,0xb9,0xa2,0x86, -0x09,0xfd,0xa2,0x83,0x7e,0xe9,0x41,0x3f,0xa4,0x5c,0x86,0xa9,0x0d,0xa6,0xab,0x30, -0xa1,0x7f,0xc8,0x85,0xe5,0x0a,0x58,0x86,0xef,0xd6,0x3f,0x57,0x54,0x80,0xb5,0x7f, -0xd2,0x45,0x3f,0x28,0x34,0x00,0xf4,0xc3,0xf8,0x7f,0x5c,0x02,0xff,0x71,0x19,0xfc, -0x47,0x27,0xf8,0x8f,0x2b,0xe0,0x3f,0xda,0xc0,0x6f,0xe0,0x29,0xbf,0xa9,0x29,0x07, -0xbf,0x81,0x5f,0xdb,0x7e,0xa3,0x31,0xc1,0xf4,0x29,0x4c,0x17,0xc1,0x6f,0xea,0x9a, -0xc1,0x6f,0x3e,0xab,0x02,0x8b,0xd5,0xd5,0xed,0x48,0x3a,0x90,0x5c,0x43,0xf2,0x35, -0x92,0x4e,0x24,0x5d,0x48,0xae,0x23,0xf9,0x06,0xc9,0x0d,0x24,0xdd,0x48,0x6e,0x22, -0xe9,0x41,0xd2,0x8b,0xc4,0x8c,0xe4,0x16,0x92,0xdb,0x48,0xfa,0x90,0xf4,0x23,0xb9, -0x83,0xe4,0x5b,0x24,0x03,0x48,0x06,0x91,0xdc,0x45,0xf2,0x1d,0x14,0x93,0x02,0x89, -0x12,0x49,0x39,0x92,0x0a,0x24,0x2a,0x24,0x6a,0x24,0x1a,0x24,0x95,0x48,0xb4,0x48, -0x74,0x48,0xf4,0x48,0xd0,0x98,0x4d,0x06,0x24,0x46,0x24,0x17,0x90,0x7c,0x02,0xe5, -0x62,0x3d,0x92,0x06,0x24,0x9f,0x23,0xf9,0x02,0x49,0x23,0x92,0x26,0x24,0x5f,0x22, -0xf9,0x0a,0x49,0x33,0x92,0x16,0x24,0x97,0x90,0x5c,0x46,0xd2,0x8a,0xa4,0x0d,0xc9, -0x15,0x24,0x57,0x91,0x20,0xbb,0x5c,0x44,0x76,0xb9,0x88,0xec,0x72,0x11,0xd9,0xe5, -0x22,0xb2,0xcb,0x45,0x64,0x97,0x8b,0xc8,0x2e,0x17,0x91,0x5d,0x2e,0x22,0xbb,0x5c, -0x44,0x76,0xb9,0x88,0xec,0x72,0x11,0xd9,0xe5,0x22,0xb2,0xcb,0x45,0x64,0x97,0x8b, -0xc8,0x2e,0x17,0x91,0x5d,0x2e,0x22,0xbb,0x5c,0x44,0x76,0xb9,0x88,0xec,0x72,0x11, -0xd9,0xa5,0xee,0x33,0x24,0x68,0xe0,0x75,0x68,0xe0,0x75,0x68,0xe0,0x75,0x68,0xe0, -0x75,0x68,0xe0,0x75,0x68,0xe0,0x75,0x68,0xe0,0x75,0x68,0xe0,0x75,0x68,0xe0,0x75, -0x68,0xe0,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68, -0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc, -0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75,0x68,0xcc,0x75, -0x68,0xb8,0x75,0x68,0xb8,0x75,0x68,0xb8,0x75,0x68,0xb8,0x75,0x68,0xb8,0x75,0x68, -0xb8,0x75,0x68,0xb8,0x75,0xd6,0x91,0x0e,0x80,0xff,0x5d,0x0d,0xfe,0xb7,0x09,0x7c, -0xdf,0x00,0xbe,0xbf,0x0c,0xfe,0xbb,0xeb,0x0a,0xf8,0xbe,0x0d,0x7c,0xdf,0x0a,0xa6, -0x6f,0x99,0xc0,0x48,0x3d,0x18,0xf9,0x12,0x7c,0xdf,0x01,0x46,0x2e,0x81,0xef,0xbf, -0x00,0xdf,0x37,0x82,0x91,0xab,0xe0,0x7b,0x15,0xf8,0xfe,0x1a,0xf8,0x1e,0x0e,0xa1, -0x69,0x40,0x05,0xfe,0xfb,0xcb,0x0b,0x60,0xe4,0x3a,0x3a,0x56,0x83,0xef,0x2f,0xa0, -0x5c,0x03,0x46,0x6e,0x81,0x91,0xdb,0x60,0xe4,0x0e,0x2a,0x69,0xc1,0xc8,0x5d,0xf0, -0x7d,0x3b,0xf8,0xfe,0x0a,0xbc,0x36,0x3c,0xf9,0x2a,0xf8,0x4d,0x77,0x37,0xf8,0xfe, -0x13,0xf0,0x7d,0x0b,0xf8,0x1e,0x5e,0xb5,0x1e,0xfc,0xa7,0x02,0xfc,0xa7,0x12,0xfc, -0x67,0x39,0xea,0xad,0x47,0x82,0x0c,0xd3,0x83,0x82,0xa8,0x07,0x05,0x51,0x0f,0x0a, -0xa2,0x1e,0x14,0x44,0x3d,0x28,0x88,0x7a,0x50,0x10,0xf5,0xa0,0x20,0xea,0x41,0x41, -0xd4,0x83,0x82,0xa8,0x07,0x05,0x51,0x0f,0x0a,0xa2,0x1e,0x14,0x44,0x3d,0x28,0x88, -0x7a,0x50,0x10,0xf5,0xa0,0x20,0xea,0x41,0x41,0x84,0xbe,0xa2,0x54,0xf7,0x98,0x90, -0x7c,0x8a,0xe4,0x22,0x92,0x1a,0x24,0xb5,0x48,0xea,0x90,0x20,0x9f,0xf5,0x20,0x9f, -0xf5,0x20,0x9f,0xf5,0x20,0x9f,0xf5,0x20,0x9f,0xf5,0x20,0x9f,0xf5,0x20,0x9f,0xf5, -0x58,0x87,0x86,0x7c,0xd6,0x83,0x7c,0xd6,0x83,0x7c,0xd6,0x83,0x82,0xad,0x07,0x39, -0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae, -0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07, -0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x39,0xae,0x07,0x05,0x9b,0x19,0x15, -0xcd,0xa8,0x68,0xb6,0x16,0x91,0x33,0xcd,0xc8,0x99,0x66,0xe4,0x4c,0x33,0x72,0xa6, -0x19,0x39,0xd3,0x8c,0x9c,0x69,0x46,0xce,0x34,0xa3,0xd8,0x33,0x23,0x8f,0x9a,0xd1, -0x9a,0x34,0xa3,0x35,0x69,0x46,0x6b,0xd2,0x8c,0xd6,0xe4,0x2d,0x64,0xce,0x5b,0xc8, -0x9c,0xb7,0x90,0x39,0x6f,0x21,0x73,0xde,0x42,0xe6,0xbc,0x05,0xcd,0x69,0xba,0xac, -0x40,0xa2,0x44,0x52,0x8e,0xa4,0x02,0x89,0x0a,0x89,0xb5,0x55,0x83,0xa4,0x12,0x89, -0x16,0x89,0x0e,0x89,0x1e,0x49,0x15,0x12,0x03,0x12,0x23,0x92,0x0b,0x48,0x3e,0x41, -0x52,0x8d,0xc4,0x84,0xe4,0x53,0x24,0x17,0x91,0xd4,0x20,0xa9,0x45,0x52,0x87,0xe4, -0x33,0x24,0xf5,0x48,0x1a,0x90,0x7c,0x8e,0xe4,0x0b,0x24,0x8d,0x48,0x9a,0x90,0x7c, -0x89,0x04,0x1a,0xbb,0xae,0xbf,0x19,0x49,0x0b,0x92,0x4b,0x48,0x2e,0x23,0x69,0x45, -0xd2,0x86,0xe4,0x0a,0x92,0xab,0x48,0xda,0x91,0x74,0x20,0xb9,0x86,0xe4,0x6b,0x24, -0x9d,0x48,0xba,0x90,0x5c,0x47,0xf2,0x0d,0x92,0x1b,0x48,0xba,0x91,0xdc,0x44,0xd2, -0x83,0xa4,0x17,0x89,0x19,0xc9,0x2d,0x24,0xb7,0x91,0xf4,0x21,0xe9,0x47,0x72,0x07, -0xc9,0xb7,0x48,0x06,0x90,0x0c,0x22,0xb9,0x8b,0x04,0x5a,0xb7,0xee,0x0e,0x6a,0xb8, -0x83,0x1a,0xee,0xa0,0x86,0x3b,0xa8,0xe1,0x0e,0x6a,0xf8,0x56,0x81,0x44,0x89,0xa4, -0x1c,0x49,0x05,0x12,0x15,0x12,0x35,0x12,0x0d,0x92,0x4a,0x24,0x5a,0x24,0x3a,0x24, -0x7a,0x24,0x55,0x48,0x0c,0x48,0x8c,0x48,0x2e,0x20,0xf9,0x04,0x49,0x35,0x12,0x13, -0x92,0x4f,0x91,0x40,0xc3,0x36,0xa9,0x3f,0x83,0xa9,0x0d,0x1d,0xd4,0xff,0xd6,0x01, -0x4c,0xd7,0xdf,0x2c,0x7c,0xf3,0x66,0xe1,0xc6,0x9b,0x85,0xee,0x37,0x0b,0xd0,0x20, -0x4d,0x9a,0x7e,0xec,0x4a,0x9a,0x3b,0xaf,0x0f,0xfa,0xdf,0xb8,0xe4,0x5a,0xed,0xeb, -0x42,0xff,0x1b,0xd7,0xff,0xa5,0xe5,0x1b,0x70,0xbf,0x1b,0x3c,0xb8,0x0d,0x7e,0xa3, -0xad,0x84,0x09,0xce,0xfd,0xcb,0xd6,0x4b,0x48,0x2e,0x23,0x69,0x45,0xd2,0x86,0xe4, -0x0a,0x92,0xab,0x48,0xda,0x91,0x74,0x20,0xb9,0x86,0xe4,0x6b,0x24,0x9d,0x48,0xba, -0x90,0x5c,0x47,0xf2,0x0d,0x92,0x1b,0x48,0xba,0x91,0xdc,0x44,0xd2,0x83,0xa4,0x17, -0x89,0x19,0xc9,0x2d,0x24,0xb7,0x91,0xf4,0x21,0xe9,0x47,0x72,0x07,0xc9,0xb7,0x48, -0x06,0x90,0x0c,0x22,0xb9,0x8b,0x04,0x3a,0xe8,0xcb,0x36,0x05,0x12,0x25,0x12,0x34, -0xc8,0x36,0xf8,0x60,0xbf,0x0e,0x7e,0x6c,0x01,0x93,0xdf,0x00,0xb2,0x1f,0xa0,0xf8, -0x81,0x77,0xfd,0xc0,0x21,0x3f,0x40,0xf5,0x03,0x34,0x3f,0xf0,0x9e,0x1f,0x78,0xdf, -0x0f,0x04,0xfa,0x01,0x3a,0x7a,0xe9,0xa9,0xc9,0x47,0x2f,0x35,0x85,0xc5,0x80,0x5b, -0x00,0xf2,0x33,0x40,0x7e,0x3e,0x10,0x0a,0x0b,0x40,0xe1,0xc7,0x40,0x00,0xff,0xcb, -0x00,0x82,0x3c,0x90,0x91,0x0d,0x7e,0x56,0x7e,0xfb,0xb3,0xb2,0xca,0xfa,0x1b,0x7f, -0x39,0xfa,0x9b,0xcd,0xcf,0x80,0xe5,0x13,0xf4,0x47,0x1c,0x95,0xc0,0xd2,0xfa,0x39, -0x98,0xbb,0x7a,0x19,0xcc,0xdc,0x69,0x05,0xb3,0xf5,0xcd,0xe0,0x99,0xaa,0x05,0x58, -0x74,0x46,0x60,0xb9,0x6b,0x00,0x16,0x8d,0x11,0xcc,0xc2,0xad,0xdc,0xa2,0x6d,0x02, -0xcf,0xcc,0xe5,0x60,0xee,0x66,0x07,0xb0,0x5c,0x86,0x65,0xf8,0xf4,0x9d,0x1d,0xa8, -0x01,0xb3,0xfa,0x5a,0x30,0x6b,0xac,0x05,0xf3,0x4a,0x3d,0x98,0x35,0x54,0x82,0x59, -0xdd,0x45,0xf0,0xec,0x8a,0x12,0x3c,0xeb,0x40,0xff,0xd4,0xa8,0x05,0xb3,0x77,0x14, -0xe0,0x59,0x0d,0xbc,0x9f,0xee,0x26,0xb0,0xc0,0x9d,0x7f,0x5e,0x8d,0xde,0xb2,0x6a, -0x66,0x1b,0x3b,0xd7,0xde,0xc3,0x6a,0x66,0xfa,0x74,0xaf,0x8f,0xee,0x18,0xb0,0x23, -0xcb,0xf5,0xd7,0x75,0xcf,0xca,0xfb,0xb1,0x23,0x78,0xe1,0xd7,0x75,0x5f,0x7c,0xf6, -0xba,0x5f,0x55,0xd3,0xeb,0xd6,0x5a,0x6b,0x9d,0xe5,0x2e,0x9a,0xd5,0x37,0x60,0xa6, -0x6f,0x10,0xa6,0x3e,0x30,0xd3,0x0f,0xed,0xd9,0xac,0xba,0x04,0x66,0x06,0x9b,0xc1, -0xcc,0xdd,0x6f,0x81,0x45,0x51,0x0e,0x2c,0x4a,0x38,0x34,0xf8,0x26,0x63,0x41,0xbf, -0xa1,0x7f,0x0a,0x57,0x76,0x73,0x3d,0xac,0x50,0xa3,0x3f,0x11,0x6a,0x85,0x09,0xfa, -0xac,0xb9,0xa6,0x11,0xb6,0xf4,0xc3,0x02,0x0c,0x88,0xe6,0xda,0x2a,0x68,0x05,0x78, -0x5d,0x4d,0x2b,0x98,0xf9,0x16,0x59,0x04,0x06,0x43,0x47,0xe5,0x57,0xd0,0x24,0xf0, -0xcd,0xe8,0x22,0xca,0xe1,0x75,0x75,0x1a,0x98,0x6e,0x83,0x9f,0x6b,0xd0,0x9f,0xd4, -0xaa,0x61,0x82,0x65,0xa3,0x0e,0xa6,0xcf,0x61,0x82,0xd3,0x37,0x0e,0xc2,0x04,0xdd, -0xdc,0xdc,0xde,0x09,0x9e,0x29,0xa1,0xfd,0x3e,0xd1,0xc3,0x04,0x77,0x94,0x66,0xf4, -0x77,0x44,0x9f,0x7c,0x0d,0x53,0x37,0x34,0x18,0x3c,0x0b,0x8e,0xc8,0x02,0xdf,0x6f, -0x2c,0xf0,0x9d,0xc4,0x62,0x6a,0x87,0xe5,0x4b,0xc0,0x02,0x9f,0xf0,0x96,0x9a,0x6a, -0x60,0xa9,0xbd,0x08,0x13,0xec,0x5f,0xa7,0x82,0x09,0x9e,0x03,0x9f,0xac,0x96,0xcf, -0xe0,0x8c,0x1a,0xeb,0x60,0x82,0x43,0x6c,0x30,0xc1,0xd4,0x0b,0x1d,0x74,0x1d,0x58, -0xbe,0xba,0x00,0x13,0x3c,0x6e,0x86,0x8e,0xfc,0x12,0x4e,0xa1,0x19,0x3a,0xa0,0x19, -0xde,0xe3,0x12,0x1c,0x7e,0x0b,0x7c,0x19,0xb1,0x5c,0x86,0x15,0x97,0x6f,0x43,0xef, -0xc3,0x9b,0xc2,0x25,0x61,0x69,0x83,0xcb,0xa2,0xe5,0x73,0x18,0xfd,0x2d,0x5f,0x5c, -0x03,0x96,0xab,0x9d,0xc0,0xd2,0xfe,0x05,0x4c,0x57,0xe1,0x71,0x3d,0xb0,0x74,0x5c, -0x01,0x3f,0x7f,0x71,0x1d,0xa6,0x01,0x60,0xe9,0x84,0x43,0xeb,0x84,0x81,0xdb,0x72, -0x15,0x1e,0x7c,0x53,0x01,0x13,0xec,0xf0,0x0d,0x5c,0x00,0x2d,0x9d,0xe8,0xe0,0x3b, -0x60,0xb9,0x01,0xaf,0x39,0x78,0x11,0xfc,0xfc,0xa5,0x12,0x58,0xba,0x9b,0x81,0x05, -0x3e,0x43,0x2d,0x37,0xe1,0x06,0xd2,0x72,0x1b,0xde,0xd8,0x5c,0x81,0x0e,0xae,0xc0, -0x03,0x38,0x4c,0x33,0x9c,0x06,0x7c,0x4c,0x58,0x6e,0xc3,0x98,0xbb,0x7d,0x09,0xfc, -0xdc,0xfc,0x0d,0xcc,0xa1,0xb1,0xfa,0xe0,0xd4,0xa1,0x17,0x2d,0x70,0x0f,0xfd,0xb9, -0x05,0x8e,0xec,0x92,0x06,0x5a,0xbb,0x05,0x76,0xbc,0x03,0x8d,0x77,0x07,0xce,0xfb, -0x0e,0x5c,0x61,0xd7,0x2e,0xa0,0x7f,0x13,0x34,0x74,0x83,0x39,0x2d,0xbc,0xd1,0x00, -0x3c,0x1d,0x3d,0x1c,0x2e,0xeb,0xe1,0x92,0xbb,0x52,0x09,0x2d,0x8f,0x7e,0x4f,0x18, -0xfc,0x1a,0xd6,0x42,0x9b,0xdd,0x85,0xe5,0xef,0xa0,0x2d,0xbe,0x83,0x76,0xfa,0x0e, -0x06,0x6f,0xb9,0x12,0xfc,0x0c,0x57,0xe4,0xcf,0xad,0x70,0x97,0xbf,0xf4,0x05,0x8c, -0x64,0xb8,0x12,0x66,0xd5,0xe8,0xdf,0x45,0x0d,0x30,0xdd,0x01,0xb3,0x9a,0x9b,0x30, -0xc1,0x15,0xa1,0xf9,0x06,0xcc,0x56,0xa2,0x7f,0x9c,0x47,0x3f,0x4a,0x7c,0x01,0x23, -0xbc,0x09,0x46,0x7d,0x37,0x98,0xad,0x82,0xe7,0x18,0xe1,0x2e,0x78,0xa9,0x13,0x9e, -0x60,0x18,0x84,0x15,0x03,0xb0,0xa2,0x1d,0xcc,0x5e,0xa8,0x00,0xb3,0x9f,0xc0,0x9d, -0xe5,0xd2,0x37,0xb0,0x57,0xf5,0x25,0x58,0x50,0xc3,0x4a,0x38,0x8f,0xf6,0xeb,0x60, -0xd6,0xd4,0x01,0xd3,0x5d,0x30,0xfb,0x29,0xbc,0x8a,0x09,0x06,0x4c,0x47,0x27,0x98, -0xad,0x85,0x2e,0xb9,0x0c,0xbf,0x8a,0xcc,0x7e,0x76,0x19,0xcc,0x7e,0xd1,0x07,0x66, -0x1b,0x74,0xe0,0xe7,0x4e,0xf8,0xae,0x0d,0x0d,0xfd,0xf3,0xd7,0xd0,0x62,0xf0,0x85, -0xda,0xa2,0x81,0x0b,0xad,0x51,0x01,0x66,0x3f,0x6f,0x04,0x73,0xe5,0x5d,0x60,0xe6, -0x93,0x06,0x58,0x6e,0x01,0xb3,0x4d,0x70,0x68,0x4d,0x17,0xd1,0x3f,0x88,0x42,0x03, -0x75,0xe9,0xc0,0xec,0x57,0x70,0xec,0x5f,0xc2,0x3b,0xb6,0x40,0xdb,0x5c,0x36,0x75, -0x81,0xd9,0x4b,0x17,0xc0,0x6c,0xf3,0x57,0x60,0xb6,0x15,0x0e,0x18,0xee,0x2d,0xb3, -0x57,0xe0,0x5d,0xae,0x5e,0x03,0xb3,0xd7,0xa0,0xaf,0x2f,0x7f,0x0e,0x0b,0x5f,0x7f, -0x01,0x7e,0xbe,0x51,0x0f,0x66,0x3b,0xe1,0x73,0xed,0xf2,0xa5,0xab,0x60,0xb6,0x0b, -0x7e,0x4d,0xea,0x86,0x67,0x75,0x41,0x13,0x5c,0x87,0x67,0x5e,0x87,0x9b,0xd6,0xe5, -0x6b,0xc8,0x7f,0xbd,0xf0,0x79,0x70,0xf9,0x6b,0x78,0x07,0xd8,0xbc,0x78,0xf9,0x26, -0x9c,0xf1,0xcd,0xef,0xc0,0x2c,0x7c,0xc3,0x99,0xed,0x86,0xe7,0xf5,0xd6,0xc1,0x04, -0x27,0x67,0x86,0x93,0x84,0xef,0x03,0xb3,0xe8,0x91,0x7e,0x19,0xfd,0x28,0xd0,0xdb, -0x0b,0x66,0xfb,0xa0,0x1d,0xe1,0x83,0xef,0xe7,0xde,0x1e,0x30,0x8b,0x1e,0x35,0x97, -0xe1,0xd3,0x63,0xf6,0x36,0xf4,0xc4,0x20,0xdc,0x53,0x2f,0x0f,0xc2,0x85,0x78,0xf9, -0x2e,0xbc,0xe7,0x77,0x1a,0xf0,0x4c,0xd1,0x0f,0x13,0x1c,0xd4,0xad,0x3a,0xb8,0x58, -0xf4,0x30,0x0d,0xc2,0x74,0x1b,0x3c,0x83,0xf3,0x86,0x8b,0xb3,0x0d,0x3c,0x53,0xc3, -0x0b,0xb7,0xea,0x2e,0x83,0x67,0x1a,0xe8,0xbd,0xd6,0x0b,0xe8,0x6f,0xb8,0xdb,0xc1, -0x33,0x1d,0xf4,0x46,0x6b,0xbd,0x16,0x3c,0xd3,0xc3,0x4d,0x07,0x6e,0x4c,0x8b,0xad, -0x8d,0xe8,0x1f,0xd3,0x9b,0x6b,0xc0,0x33,0xe3,0x1d,0xf0,0xec,0x02,0xdc,0xfd,0xee, -0xa0,0x3f,0xe7,0x86,0xdf,0x6c,0xee,0xf4,0x81,0x67,0xf0,0xd5,0xff,0x99,0xa9,0x11, -0x3c,0xfb,0x54,0x0d,0x9e,0x5d,0x84,0x5f,0x04,0x07,0xbe,0x80,0x5b,0x15,0xf4,0x5b, -0xeb,0x8d,0x5b,0xf0,0x00,0xc6,0x77,0xeb,0x4d,0x14,0x5e,0x9a,0xaf,0xc0,0xb3,0x7a, -0x74,0xe5,0xbb,0x2d,0x48,0x60,0x6c,0xb5,0x29,0x55,0xe0,0xe7,0xef,0x06,0xc0,0x8c, -0x02,0x3e,0x2c,0xdb,0xe0,0x17,0x23,0x28,0xf0,0x19,0xd0,0xa6,0x6a,0x40,0xd2,0x03, -0x9e,0x35,0xa2,0xdf,0xcf,0xe0,0x69,0x70,0xdd,0xcd,0x28,0x35,0x60,0xa6,0x1c,0xbe, -0x67,0xb4,0xa1,0x1f,0x5e,0x5a,0x2e,0x80,0x19,0xf8,0xe5,0x6a,0xb1,0xad,0x1e,0x3e, -0x6f,0xda,0x3e,0x87,0xaf,0x1a,0x6d,0x5f,0xc0,0xbb,0x5f,0xfd,0x1a,0xcc,0xa8,0xbe, -0x81,0x9b,0x26,0x9c,0x6b,0x07,0x7c,0xba,0xb6,0xb5,0xc1,0x07,0x44,0xdb,0xb5,0xab, -0xe0,0xd9,0xd7,0xd7,0xc1,0x0c,0x7a,0x68,0xb5,0x5d,0x87,0x73,0xfc,0x06,0x9a,0xe6, -0x1b,0x1d,0xac,0x80,0xdb,0x52,0xdb,0x4d,0x38,0x47,0xf8,0x02,0x39,0xa3,0x87,0x1b, -0x36,0x7c,0xb5,0x7b,0x06,0xdf,0xfb,0x9e,0xf5,0xc2,0x67,0x48,0xdb,0x5d,0xd8,0xcb, -0x0c,0x77,0x73,0x03,0xdc,0x85,0x6f,0xc3,0x29,0xf6,0x95,0x83,0x67,0xfd,0x6a,0x58, -0x86,0x0f,0xa7,0x2b,0xd0,0x4c,0x8b,0x57,0x74,0x95,0x60,0xc6,0x08,0xc7,0x7a,0xa5, -0xaa,0x01,0x3c,0x1b,0x84,0x4f,0xa5,0x2b,0x86,0xbb,0xe0,0xd9,0xdd,0x5a,0xf0,0xec, -0x3b,0x54,0x40,0xdf,0x6d,0xae,0xd4,0x5c,0x06,0x73,0x4a,0xf8,0xb2,0x73,0x05,0xbe, -0xef,0xcf,0xc1,0x0d,0x6b,0xf1,0xb2,0xf1,0x2b,0x18,0x74,0xb5,0x30,0xf8,0x6e,0x82, -0x39,0x55,0x05,0x98,0xa9,0xae,0x82,0xf9,0x5d,0x60,0xb9,0x06,0x6d,0x7c,0xe5,0xcb, -0xab,0x48,0xe0,0xd1,0xe5,0x0b,0x28,0x16,0x2f,0xc0,0x20,0xd5,0xaa,0xc0,0xdc,0xdd, -0x2f,0xc1,0x8c,0xe9,0x6b,0x30,0xa7,0x37,0xc1,0x04,0xfb,0xeb,0xd1,0x1f,0x58,0xf6, -0xc0,0x1c,0x2e,0x59,0x6b,0x3e,0x08,0xe6,0xaa,0xa0,0x29,0xae,0x74,0x0e,0x80,0x39, -0x43,0x13,0x98,0x33,0xc2,0xdb,0x5e,0xe8,0x82,0xc7,0x28,0x87,0x8d,0x9f,0x5c,0x85, -0x75,0x17,0x61,0xae,0x87,0xc9,0x00,0x13,0xf4,0xf7,0x15,0xe8,0x9f,0xc5,0x2b,0x70, -0xe3,0x58,0xbc,0x02,0x5f,0x99,0x67,0x6a,0xe0,0xf5,0xe1,0x77,0xa3,0x39,0xf8,0xad, -0x68,0xae,0x06,0x1a,0xfa,0x2a,0xea,0x5b,0x0b,0xfd,0x78,0x65,0xa0,0x13,0xcc,0xd4, -0x36,0xc1,0x04,0x1d,0x77,0xe5,0x2e,0x7c,0x54,0x5f,0xad,0x80,0x06,0xab,0x85,0xfd, -0x1b,0xaa,0x61,0x82,0x97,0x6e,0xe8,0x00,0x73,0x9f,0xa3,0xa4,0x05,0x73,0x8d,0x17, -0xc0,0xdc,0x17,0x97,0x60,0x0e,0x27,0xdc,0x04,0xfb,0x7c,0x09,0x1d,0xf7,0xd9,0x67, -0x60,0xee,0xab,0x16,0x30,0xd7,0xac,0x84,0xc7,0xfd,0x60,0xae,0x05,0x5a,0xe4,0x2a, -0x3a,0x11,0xbe,0x34,0xce,0xc1,0xb7,0xc2,0x99,0x7a,0x33,0xcc,0xe1,0x2e,0xf2,0x29, -0x5c,0x21,0x57,0x2f,0xc1,0x40,0xbe,0xda,0x76,0x05,0xcc,0x7c,0x7e,0x17,0xcc,0x7c, -0x51,0x09,0xe6,0xae,0xa1,0xee,0x37,0xe1,0xf5,0x6e,0xc2,0xeb,0xc1,0xaf,0x10,0x73, -0xbd,0x5f,0x80,0x39,0xf4,0xb6,0x7c,0x75,0x00,0xbe,0x42,0x34,0x37,0x43,0x0b,0xa1, -0x9f,0xdb,0x6e,0xc1,0xc9,0xdf,0x86,0x26,0x6e,0x86,0x57,0x9c,0xfb,0x16,0xbe,0x9d, -0xb6,0x7f,0xf6,0x29,0x92,0x2e,0x30,0x5f,0x5e,0x03,0xe6,0x2b,0xba,0xc1,0xbc,0x0a, -0xbe,0x69,0xb5,0x37,0x5e,0x87,0x07,0x5f,0x80,0x79,0x6d,0x1f,0x98,0xaf,0xfa,0x0c, -0xa6,0x7a,0x30,0xaf,0x1b,0x00,0xf3,0x9f,0xf4,0x83,0xf9,0x6a,0x18,0xee,0xed,0xdf, -0xdc,0x01,0xf3,0x75,0x30,0xe6,0xae,0xc0,0x07,0x32,0xfa,0x86,0xd5,0x0e,0xef,0x35, -0x73,0xf5,0x0a,0x98,0xff,0x02,0x6e,0xd2,0xb7,0x6e,0x82,0xf9,0x46,0x18,0x7b,0x1d, -0x68,0xf1,0x74,0x68,0x3f,0x07,0x33,0x1d,0x30,0xb8,0x3a,0xe0,0x33,0xf9,0x2b,0x38, -0xcc,0x8e,0x6a,0x3d,0x98,0xb9,0x06,0xa3,0xb8,0xe3,0xd3,0x3a,0x30,0xdf,0x0c,0xdf, -0x7f,0x3a,0xd0,0xe3,0xfa,0x32,0x7c,0x76,0x76,0xf6,0x80,0x79,0xf8,0x42,0x34,0x7f, -0xe5,0x2a,0x98,0xbf,0x5a,0x0e,0x13,0x9c,0xdd,0x37,0x70,0xf9,0x77,0x74,0xde,0x00, -0xf3,0xdd,0x0a,0x30,0xdf,0x03,0x9f,0xb6,0xf0,0xfb,0xcd,0x0c,0xfc,0x22,0x31,0x6f, -0x86,0x81,0x70,0x0d,0xfd,0x66,0xde,0x03,0x17,0xca,0x35,0x15,0xbc,0xd5,0x35,0x2d, -0xec,0x7b,0x0d,0x46,0xc0,0x8c,0x19,0x5e,0xf9,0xdb,0xbb,0x60,0x1e,0x7e,0x3f,0x9b, -0x1f,0xf8,0x04,0x26,0xe8,0xad,0x6b,0xf5,0x0a,0x00,0x70,0x1b,0x36,0xda,0xd8,0x6e, -0xda,0xbc,0xd9,0x0e,0xbf,0x65,0xab,0xbd,0xc3,0x5b,0x8e,0x9b,0x7f,0xf9,0x10,0x36, -0x3b,0x6d,0xdb,0xee,0xec,0xb2,0x63,0xa7,0xab,0xb5,0xe8,0xb6,0x79,0xd7,0x6e,0x77, -0x8f,0x3d,0x6f,0x7b,0x7a,0x6d,0xfe,0xbb,0x7f,0xf6,0xee,0x83,0xe2,0xfd,0xab,0x5e, -0x92,0xb8,0xff,0x80,0x8f,0xef,0xc1,0x77,0xb0,0x92,0xdf,0xeb,0x6a,0x92,0xff,0xe6, -0x00,0x32,0x05,0x1e,0xbc,0xbb,0xf9,0x9f,0xf3,0x73,0x68,0x33,0x95,0xf6,0xde,0xfb, -0x81,0xaf,0x8b,0xf4,0x37,0xdb,0x82,0x0e,0x6f,0x0e,0x0e,0xf9,0xe0,0x75,0xe9,0xc3, -0xcd,0xff,0xc6,0x9f,0xd0,0xb0,0x23,0x9b,0xd7,0x3f,0xeb,0x9f,0xf5,0xcf,0xfa,0x67, -0xfd,0xb3,0xfe,0x59,0xff,0xac,0x7f,0xd6,0x3f,0xeb,0x9f,0xf5,0xcf,0xfa,0x67,0xfd, -0xf3,0xef,0xf3,0x01,0x36,0xeb,0xfc,0x55,0xd8,0x02,0xbb,0xdf,0x02,0x0f,0x08,0xc0, -0x09,0x38,0x63,0xb8,0x00,0x77,0x88,0x07,0xf0,0x02,0x44,0x40,0x02,0x64,0x08,0x05, -0xd0,0x41,0x08,0x08,0x05,0x61,0x80,0x05,0x38,0x20,0x03,0xc2,0x07,0x62,0x20,0x03, -0x2a,0x60,0x00,0x35,0x90,0x46,0xd0,0x0a,0x3a,0x41,0x2f,0x18,0x00,0xf7,0xc1,0x43, -0xf0,0x18,0x3c,0x81,0xcc,0x80,0xe7,0xe0,0x05,0x78,0x05,0x6c,0x70,0xf6,0x56,0x9c, -0x71,0xee,0x38,0x6f,0x9c,0x1f,0x8e,0x6a,0x25,0x18,0x17,0x8e,0x63,0xe2,0x92,0x70, -0x29,0xb8,0x0c,0x08,0x1f,0x27,0xc6,0xc9,0x70,0x2a,0x9c,0x01,0x57,0x83,0x6b,0xc4, -0xb5,0xe2,0x3a,0x21,0xbd,0xb8,0x01,0xdc,0x7d,0xdc,0x43,0xdc,0x63,0xdc,0x13,0x2b, -0x33,0xb8,0xe7,0xb8,0x17,0xb8,0x57,0x38,0x9b,0x0d,0x36,0x1b,0xec,0x21,0xce,0x1b, -0xdc,0x37,0x78,0x6f,0xf0,0xdb,0x40,0xdd,0x10,0xbc,0x21,0x7c,0x03,0x73,0x43,0xd2, -0x86,0x94,0x0d,0x19,0x1b,0xf8,0x1b,0xc4,0x1b,0x64,0x1b,0x54,0x1b,0x0c,0x56,0x6a, -0x36,0x34,0x6e,0x68,0xdd,0xd0,0xb9,0xa1,0x77,0xc3,0xc0,0x86,0xfb,0x1b,0x1e,0x6e, -0x78,0xbc,0xe1,0xc9,0x86,0x99,0x0d,0xcf,0x37,0xbc,0xd8,0xf0,0x6a,0x83,0xcd,0x46, -0xfb,0x8d,0xce,0x1b,0xdd,0x37,0x7a,0x6f,0xf4,0xb3,0x42,0xdd,0x18,0xbc,0x31,0x7c, -0x23,0x73,0x63,0xd2,0xc6,0x94,0x8d,0x19,0x1b,0xf9,0x1b,0xc5,0x1b,0x25,0x1b,0xe5, -0x1b,0x55,0x1b,0x0d,0x1b,0x6b,0x36,0x36,0x6e,0x6c,0x85,0x74,0x6e,0xec,0xdd,0x38, -0xb0,0xf1,0xfe,0xc6,0x87,0x1b,0xc7,0x37,0x4e,0x6d,0x9c,0xb1,0xf2,0x7c,0xe3,0x8b, -0x8d,0xaf,0x36,0xda,0xd8,0xd8,0xdb,0xb8,0xd8,0xb8,0x5b,0xf1,0xb6,0xf1,0xb3,0xa1, -0xda,0x04,0xdb,0x84,0xdb,0x30,0xad,0x24,0xd9,0xa4,0xd8,0x64,0xd8,0xf0,0x6d,0xc4, -0x36,0x32,0x1b,0x95,0x8d,0xc1,0xa6,0xc6,0xa6,0xd1,0xa6,0xd5,0xa6,0xd3,0xa6,0xd7, -0x66,0xc0,0xe6,0xbe,0xcd,0x43,0x9b,0xc7,0x36,0x4f,0xac,0xcc,0xd8,0x3c,0xb7,0x79, -0x61,0xf3,0xca,0xc6,0xc6,0xd6,0xde,0xd6,0xd9,0xd6,0xdd,0xd6,0xdb,0xd6,0xcf,0x96, -0x6a,0x1b,0x6c,0x1b,0x6e,0xcb,0xb4,0x4d,0xb2,0x4d,0xb1,0xcd,0xb0,0xe5,0xdb,0x8a, -0x6d,0x65,0xb6,0x2a,0x5b,0x83,0x6d,0xad,0x6d,0xfd,0x5f,0x4d,0xb3,0x6d,0xfb,0xff, -0xc0,0x0d,0xdb,0xbe,0x3f,0x89,0x7e,0xdb,0x61,0xdb,0x31,0xdb,0x29,0x5b,0x8b,0xed, -0x82,0xed,0xb2,0xed,0xaa,0xad,0xed,0x26,0xfb,0x4d,0xce,0x9b,0xdc,0x37,0x79,0x6f, -0xf2,0xdb,0x44,0xdd,0x14,0xbc,0x29,0x7c,0x13,0x73,0x53,0xf2,0xa6,0x8c,0x4d,0xc2, -0x4d,0xb2,0x4d,0xda,0x4d,0x35,0x9b,0x9a,0x37,0x75,0x42,0xfa,0x36,0xdd,0xdf,0xf4, -0x68,0xd3,0x93,0x4d,0x33,0x9b,0x9e,0x5b,0x79,0xb1,0xe9,0xd5,0x26,0x9b,0xcd,0xf6, -0x9b,0x9d,0x37,0xbb,0x6f,0xf6,0xd9,0x4c,0xdd,0x1c,0xbc,0x39,0x7c,0x33,0x6b,0x33, -0x7b,0x73,0xea,0xe6,0x8c,0xcd,0xfc,0xcd,0x62,0x2b,0xb2,0xcd,0xaa,0xcd,0xd5,0x9b, -0x1b,0x37,0xb7,0x6e,0xee,0xdc,0xdc,0xbb,0x79,0x00,0x72,0x7f,0xf3,0xc3,0xcd,0x8f, -0x37,0x3f,0xd9,0x3c,0xb3,0xf9,0xf9,0xe6,0x17,0x9b,0x5f,0x6d,0xb6,0xb1,0xb3,0xb7, -0x73,0xb6,0x73,0xb7,0xf3,0xb6,0xf3,0xb3,0xa3,0xda,0x05,0xdb,0x85,0xdb,0x31,0xed, -0x92,0xec,0x52,0xec,0x32,0xec,0xf8,0x76,0x62,0x3b,0x99,0x9d,0xca,0xce,0x60,0x57, -0x63,0xd7,0x68,0xa5,0xd5,0xae,0xf3,0xb7,0xe8,0xb5,0x1b,0xb0,0xbb,0x6f,0xf7,0xd0, -0x6e,0xc2,0x6e,0xc6,0x6e,0xd1,0xee,0x95,0x9d,0x0d,0xde,0x1e,0xef,0x8a,0xf7,0xc6, -0xfb,0xe1,0xa9,0xff,0xa6,0xd0,0xf0,0x74,0x7c,0x08,0x3e,0x0c,0x1f,0x81,0x67,0xe0, -0x59,0xf8,0x78,0x3c,0x1b,0x9f,0xfc,0x57,0xc1,0xc1,0xa7,0xe3,0x79,0x78,0x11,0x5e, -0x8a,0x57,0xe2,0xb5,0x10,0x1d,0xde,0x88,0x37,0xe1,0x6b,0xf1,0x0d,0xf8,0xc6,0x7f, -0x5a,0x9a,0xf1,0xad,0x90,0x76,0xfc,0x8d,0xbf,0x31,0xbd,0xbf,0x60,0xc6,0x0f,0x60, -0xdc,0xfb,0x85,0x21,0xfc,0xf7,0xf8,0x47,0xf8,0x09,0xfc,0x13,0xfc,0x0c,0xfe,0x39, -0xfe,0x05,0xe4,0x15,0xc4,0x66,0x8b,0xfd,0x16,0xd7,0xbf,0x00,0xcf,0x2d,0x3e,0x5b, -0xc8,0x5b,0x02,0xb7,0x84,0x6e,0x89,0xdc,0x92,0xf4,0x17,0x90,0xb2,0x25,0x63,0x0b, -0x7f,0x8b,0x78,0x8b,0x6c,0x8b,0x7c,0x8b,0x72,0x8b,0x7a,0x8b,0x7e,0xcb,0xa7,0x5b, -0x1a,0xb6,0x34,0x6d,0x69,0xfe,0xb3,0x69,0xd9,0xd2,0xb6,0xa5,0x63,0x4b,0xe7,0x96, -0xae,0x2d,0xdd,0x5b,0x7a,0xad,0x98,0xb7,0xf4,0xfd,0x0e,0xf7,0xb6,0x3c,0x80,0x3c, -0xc2,0x98,0xf8,0x85,0xa7,0x5b,0xe6,0xb6,0x2c,0xfe,0x5e,0x5e,0xfe,0x95,0x80,0xad, -0x7f,0x0e,0x76,0x5b,0x09,0x10,0x57,0x0c,0xcf,0x5f,0xf0,0xd9,0x4a,0xde,0x1a,0xf8, -0x67,0x12,0xba,0x35,0xf2,0x1f,0x90,0xb8,0xad,0xc9,0xff,0x03,0x69,0x5b,0xb9,0x5b, -0x85,0x5b,0x8b,0xac,0x28,0xb6,0x6a,0xad,0x54,0x6f,0xad,0xdf,0xda,0xbc,0xb5,0x7d, -0xeb,0x8d,0xad,0x7d,0x56,0xee,0x6d,0x7d,0xb0,0xf5,0xd1,0xd6,0x89,0xad,0x4f,0xb7, -0xce,0x59,0x59,0xdc,0xfa,0x72,0x2b,0xb0,0xb7,0xb3,0x27,0xd8,0xbb,0xda,0x7b,0xda, -0xfb,0xd8,0x93,0xed,0x03,0xed,0x43,0xed,0x23,0xad,0xc4,0xd9,0x27,0xaf,0xf3,0x07, -0xe1,0xd8,0xa7,0xad,0xf3,0x17,0xc1,0xb5,0x17,0xda,0x17,0xd9,0x2b,0xec,0xb5,0xff, -0xc0,0xe8,0xec,0x4d,0xf6,0x0d,0xf6,0x2d,0xf6,0xed,0xff,0x60,0xdc,0x80,0xf4,0xfd, -0x51,0xee,0xfd,0xcd,0x00,0xeb,0x9f,0x7f,0xf3,0xcf,0xdf,0x22,0xaa,0x1e,0xfc,0x0e, -0x8f,0xac,0x4c,0xfc,0x4a,0x3c,0xb5,0x9f,0xb3,0x5f,0xb4,0x7f,0x69,0x0f,0x1c,0xec, -0x1c,0x08,0x0e,0xae,0xff,0xa2,0x78,0x3a,0xf8,0xfc,0x11,0xc8,0x0e,0x81,0x90,0xd0, -0x7f,0x1a,0x22,0xad,0xc4,0xfd,0x2a,0x24,0x3b,0xa4,0x39,0x70,0xad,0x08,0xd7,0xf9, -0x17,0xa4,0xc8,0x8a,0xc2,0x41,0xfb,0x47,0xa8,0x76,0xa8,0xff,0xbb,0xd2,0xfc,0x77, -0xa4,0xdd,0xe1,0x86,0x43,0xdf,0xbf,0x2d,0xf7,0xfe,0xcd,0x78,0xf0,0x5b,0x3c,0x72, -0x98,0x80,0x3c,0x75,0x78,0xfe,0x6f,0xce,0x0b,0x87,0x57,0x0e,0x36,0x6f,0xad,0xf3, -0x87,0xb0,0x87,0x38,0xbf,0xe5,0xf9,0x96,0xcf,0x3f,0x0d,0xbe,0x6f,0x91,0xff,0xad, -0xa1,0xbc,0x15,0xf8,0x2b,0x43,0x7f,0x2b,0xec,0xad,0xc8,0xbf,0x29,0x8c,0xb7,0x98, -0x7f,0x57,0x92,0x7e,0x0f,0x29,0xbf,0x90,0xf1,0x0b,0xfc,0x5f,0x10,0xff,0x41,0x64, -0xbf,0x07,0x15,0xc4,0xf0,0x56,0xed,0x5b,0xcd,0x6f,0x75,0xbc,0xd5,0xfb,0x3b,0x0c, -0xfc,0x09,0xdc,0xff,0x3d,0x3c,0xfc,0x85,0xc7,0xbf,0xf0,0xe4,0x17,0x66,0xfe,0x20, -0xcf,0xff,0x85,0x79,0xb1,0xce,0x3f,0x09,0xcb,0x6f,0xbd,0xfc,0xbd,0x00,0x47,0xe0, -0x68,0x07,0x21,0x40,0x5c,0x21,0x9e,0x18,0x3e,0xbf,0x3a,0x64,0x48,0xa0,0x95,0x50, -0xc7,0x48,0x48,0xdc,0x3a,0xff,0x62,0xc4,0x3b,0xb2,0x1d,0x39,0x8e,0x29,0x8e,0xa9, -0x8e,0xe9,0x8e,0x99,0x8e,0x3c,0x47,0x81,0xa3,0xc8,0x51,0xe2,0x28,0x75,0x94,0x3b, -0x2a,0x1d,0xd5,0x8e,0x5a,0x47,0x9d,0xa3,0xd1,0xd1,0xe4,0x58,0xeb,0xd8,0xe0,0xd8, -0xe4,0xd8,0xe2,0xd8,0xe6,0xd8,0xe1,0xd8,0xe5,0xd8,0xed,0x68,0x76,0xec,0x77,0x1c, -0x74,0x1c,0x72,0x1c,0x76,0x1c,0x71,0x1c,0x75,0x7c,0xe4,0x38,0xe6,0x38,0xee,0x38, -0xe9,0x38,0xe5,0x38,0xed,0x68,0x71,0x9c,0x77,0x5c,0x70,0x5c,0x72,0x5c,0x76,0x5c, -0x71,0x5c,0x75,0xc4,0x11,0x6c,0x09,0x78,0x82,0x03,0xc1,0x89,0xe0,0x42,0x70,0x23, -0x78,0x10,0xbc,0x08,0x44,0x82,0x2f,0x81,0x44,0xa0,0x10,0x68,0x04,0x3a,0x21,0x84, -0x10,0xfa,0x07,0x09,0x23,0x84,0xff,0x0a,0x44,0x10,0x18,0x04,0x16,0x21,0x9e,0xc0, -0x26,0x70,0x08,0xa9,0x84,0x74,0x42,0x26,0x81,0x47,0x10,0x10,0x44,0x04,0x09,0x41, -0x4a,0x90,0x13,0x94,0x04,0x35,0x41,0x47,0x30,0x12,0x4c,0x84,0x5a,0x42,0x03,0xa1, -0x89,0xd0,0x42,0x68,0x23,0x74,0x10,0xba,0x08,0xdd,0x04,0x33,0xa1,0x9f,0x30,0x48, -0x18,0x22,0x0c,0x13,0x46,0x08,0xa3,0x84,0x31,0xc2,0x38,0x61,0xe2,0x1f,0x80,0xa7, -0x84,0x39,0xc2,0x22,0xe1,0x25,0x01,0x38,0xd9,0x39,0x11,0x9c,0x5c,0x9d,0xbc,0x9d, -0xc8,0x4e,0x81,0x4e,0xa1,0x4e,0x91,0x4e,0x71,0x4e,0xc9,0x4e,0x69,0x4e,0x5c,0x27, -0xa1,0x53,0x91,0x93,0xc2,0xc9,0xe0,0x54,0xef,0xd4,0xea,0x74,0xc3,0xa9,0xcf,0xe9, -0x9e,0xd3,0x03,0xa7,0x47,0x4e,0x4f,0x9c,0xe6,0x9c,0x16,0x9d,0x5e,0x3a,0x81,0x6d, -0x76,0xdb,0x08,0xdb,0x5c,0xb7,0x79,0x6e,0xf3,0xd9,0x46,0xde,0x16,0xb8,0x2d,0x74, -0x5b,0xe4,0xb6,0xb8,0x6d,0xc9,0xdb,0xd2,0xb6,0x71,0xb7,0x89,0xb7,0x29,0xb6,0x69, -0xb7,0x55,0x6f,0xab,0xdf,0xd6,0xbc,0xad,0x7d,0xdb,0x8d,0x6d,0x7d,0xdb,0xee,0x6d, -0x7b,0xb8,0x6d,0x62,0xdb,0xd3,0x6d,0x73,0xdb,0x16,0xb7,0xbd,0xdc,0x06,0xb6,0xdb, -0x6d,0x27,0x6c,0x77,0xdd,0xee,0xb9,0xdd,0x67,0x3b,0x79,0x7b,0xe0,0xf6,0xd0,0xed, -0x91,0xdb,0xe3,0xb6,0x27,0x6f,0x4f,0xdb,0xce,0xdd,0x2e,0xde,0xae,0xd8,0x6e,0xd8, -0x5e,0xbf,0xbd,0x75,0xfb,0x8d,0xed,0x03,0xdb,0x1f,0x6c,0x7f,0xb4,0x7d,0x62,0xfb, -0xd3,0xed,0x73,0xdb,0x17,0xb7,0xbf,0xdc,0x0e,0x9c,0xed,0x9c,0x9d,0x9d,0x3d,0x9d, -0x7d,0x9c,0xc9,0xce,0x81,0xce,0xa1,0xce,0x91,0xce,0x71,0xce,0x29,0xce,0x5c,0x67, -0xb1,0xb3,0xc2,0xd9,0xe0,0x5c,0xef,0xdc,0xec,0xdc,0xee,0x7c,0xc3,0xb9,0xcf,0xf9, -0x9e,0xf3,0x03,0xe7,0x47,0xce,0x13,0xce,0x4f,0x9d,0xe7,0x9c,0x17,0x9d,0x5f,0x3a, -0x03,0x17,0x3b,0x17,0x67,0x17,0x4f,0x17,0x3f,0x97,0x40,0x97,0x50,0x97,0x48,0x97, -0x38,0x97,0x64,0x97,0x34,0x17,0xae,0x8b,0xd0,0xa5,0xc8,0x45,0xe1,0xa2,0x75,0xa9, -0x76,0xa9,0x77,0x69,0x76,0x69,0x77,0xb9,0xe1,0xd2,0xe7,0x72,0xcf,0xe5,0x81,0xcb, -0x23,0x97,0x09,0x97,0xa7,0x2e,0x73,0x2e,0x8b,0x2e,0x2f,0x5d,0xc0,0x0e,0xbb,0x1d, -0x84,0x1d,0xae,0x3b,0x3c,0x77,0xf8,0xee,0x78,0x77,0x47,0xe0,0x2f,0x84,0xee,0x88, -0xdc,0x11,0xb7,0x23,0x79,0x47,0xc6,0x0e,0xe1,0x0e,0xd9,0x0e,0xed,0x8e,0x9a,0x1d, -0xcd,0x3b,0x3a,0x77,0xf4,0xed,0xb8,0xbf,0xe3,0xd1,0x8e,0x27,0x3b,0xe6,0x76,0xbc, -0xd8,0x01,0x76,0xda,0xef,0x74,0xdd,0xe9,0xbd,0x93,0xbc,0x33,0x78,0x67,0xe4,0xce, -0xb8,0x9d,0xc9,0x3b,0xd3,0x76,0x72,0x77,0x0a,0x77,0x16,0xed,0x54,0xed,0xac,0xde, -0xd9,0xb8,0xb3,0x7d,0x67,0xef,0xce,0x7b,0x3b,0x1f,0xee,0x9c,0xd8,0x39,0xb3,0x73, -0x71,0xe7,0xcb,0x9d,0xc0,0xd5,0xce,0x95,0xe0,0xea,0xea,0xea,0xe9,0xea,0xe3,0x4a, -0x76,0x0d,0x76,0x8d,0x74,0x4d,0x72,0x4d,0x73,0xe5,0xbb,0x16,0xb9,0xaa,0x5c,0xab, -0x5d,0x1b,0x5d,0xdb,0x5d,0x7b,0x5d,0xef,0xb9,0x3e,0x74,0x9d,0x70,0x9d,0x71,0x5d, -0x74,0x7d,0xe5,0x6a,0xe7,0xe6,0xec,0xe6,0xe9,0xe6,0xe3,0x46,0x76,0x0b,0x74,0x0b, -0x75,0x63,0xba,0x25,0xbb,0x65,0xb8,0x09,0xdd,0x64,0x6e,0x5a,0xb7,0x1a,0xb7,0x66, -0xb7,0x4e,0xb7,0x3e,0xb7,0x7b,0x6e,0x0f,0xdc,0x1e,0xb9,0x4d,0xb8,0x3d,0x75,0x9b, -0x73,0x5b,0x74,0x7b,0xf9,0x3b,0x80,0x5d,0x76,0xbb,0x9c,0x77,0x79,0xee,0xf2,0xdb, -0x15,0xb8,0x2b,0x7c,0x57,0xdc,0xae,0xe4,0x5d,0x69,0xbb,0xf8,0xbb,0x8a,0x76,0xa9, -0x76,0x55,0xef,0x6a,0xdc,0xd5,0xbe,0xeb,0xc6,0xae,0xbe,0x5d,0xf7,0x77,0x3d,0xda, -0xf5,0x64,0xd7,0x9c,0x95,0xc5,0x5d,0x2f,0x77,0xd9,0xec,0x26,0xec,0x76,0xdf,0xed, -0x63,0x85,0xbc,0x3b,0x70,0x77,0xf8,0xee,0xb8,0xdd,0x29,0xbb,0xb9,0xbb,0xc5,0xbb, -0x15,0xbb,0xb5,0xbb,0xab,0x77,0x37,0xee,0x6e,0xdf,0xdd,0xbb,0xfb,0xde,0xee,0x87, -0xbb,0x27,0x76,0x3f,0xdd,0x3d,0xb7,0xfb,0xc5,0x6e,0xe0,0x6e,0xef,0xee,0xea,0xee, -0xed,0x4e,0x76,0x0f,0x74,0x0f,0x75,0x67,0xba,0x27,0xbb,0x67,0xb8,0x0b,0xdd,0x65, -0xee,0x5a,0xf7,0x6a,0xf7,0x7a,0xf7,0x56,0xf7,0x1b,0xee,0x03,0xee,0x0f,0xac,0x3c, -0x72,0x9f,0x70,0x9f,0x71,0x5f,0x74,0x7f,0xe5,0x6e,0xe7,0x81,0x20,0x78,0xb8,0x7a, -0x78,0x7b,0x90,0x3d,0x82,0x3d,0x22,0x3d,0x92,0x3c,0xd2,0x20,0x5c,0x88,0x18,0xa2, -0x80,0x18,0x3c,0x6a,0x3c,0x1a,0x3d,0xda,0x3d,0x7a,0x3d,0xee,0x79,0x3c,0xf4,0x98, -0xf0,0x98,0xf1,0x78,0xee,0xf1,0xc2,0x03,0xec,0xb1,0xdf,0xe3,0xba,0xc7,0x7b,0x0f, -0x79,0x4f,0xf0,0x9e,0xf0,0x3d,0xcc,0x3d,0x49,0x7b,0x52,0xf6,0x64,0xec,0xe1,0xef, -0x11,0xef,0x91,0xed,0x51,0xed,0x31,0xec,0xa9,0xd9,0xd3,0xb8,0xa7,0x75,0x4f,0xa7, -0x95,0xbe,0x3d,0xf7,0xf7,0x3c,0xde,0x33,0xb3,0xe7,0xc5,0x1e,0x9b,0xb7,0x9d,0xdf, -0xf6,0x7e,0x9b,0xfc,0x76,0xf0,0xdb,0xcc,0xb7,0x53,0xde,0xe6,0xbf,0x2d,0x7b,0xdb, -0xf0,0x76,0xe3,0xdb,0xed,0x6f,0xf7,0xbe,0x7d,0xff,0xed,0xc7,0x6f,0xcf,0xbc,0xfd, -0xe2,0x6d,0x1b,0x4f,0x67,0x4f,0x4f,0x4f,0x3f,0xcf,0x60,0x4f,0xa6,0x67,0x8a,0x27, -0xdf,0x53,0xe6,0x69,0xf0,0xac,0xf7,0x6c,0xf5,0xec,0xf5,0xbc,0xef,0xf9,0xd8,0x73, -0xc6,0xf3,0x85,0xa7,0x8d,0x17,0xc1,0xcb,0xdd,0xcb,0xcf,0x2b,0xd8,0x8b,0xe9,0x95, -0xe2,0xc5,0xf7,0x92,0x79,0xa9,0xbc,0x0c,0x5e,0xf5,0x5e,0xcd,0x5e,0x9d,0x90,0x5e, -0xaf,0x7b,0x5e,0x0f,0xbc,0x1e,0x79,0x4d,0x78,0x3d,0xf5,0x9a,0xf7,0x5a,0xf4,0x5a, -0xf6,0x5a,0xf5,0xda,0xb4,0xd7,0x7e,0xaf,0xeb,0x5e,0xcf,0xbd,0x7e,0x10,0xea,0xde, -0xd0,0xbd,0x91,0x7b,0xe3,0xf6,0x26,0xef,0x4d,0xdb,0x9b,0xbd,0x37,0x6f,0x6f,0xf1, -0x5e,0xc5,0x5e,0xed,0xde,0xea,0xbd,0x8d,0x7b,0xdb,0xad,0xdc,0xd8,0x3b,0xb0,0xf7, -0xfe,0xde,0x87,0x7b,0x1f,0xef,0x7d,0xb2,0xf7,0xff,0xd9,0x3b,0xbb,0xf7,0xff,0xdd, -0xfb,0x62,0xef,0xab,0xbd,0x36,0xfb,0x08,0xfb,0xdc,0xf7,0x79,0xef,0xf3,0xdb,0x47, -0xdd,0x17,0xba,0x2f,0x72,0x5f,0xdc,0xbe,0xe4,0x7d,0x69,0xfb,0xb2,0xf7,0xe5,0xed, -0x2b,0xda,0x27,0xb3,0xa2,0xdd,0x57,0xbd,0xaf,0x11,0xd2,0xba,0xef,0xc6,0xbe,0xbe, -0x7d,0xf7,0xf6,0x3d,0xd8,0xf7,0x68,0xdf,0x8f,0xfb,0xa6,0xf6,0xcd,0xec,0x9b,0x85, -0xfc,0xf7,0xbe,0x85,0x7d,0x4b,0xfb,0x96,0xf7,0xad,0xec,0x5b,0xdd,0x87,0xf3,0xb6, -0xf5,0xc6,0x7b,0x3b,0x78,0x13,0xde,0xc0,0xc9,0xdb,0xf9,0x17,0x5c,0xbc,0xdd,0xff, -0x00,0x1e,0xde,0x5e,0xde,0xbe,0xde,0xd4,0xdf,0x81,0xe6,0x1d,0x68,0x85,0xee,0x1d, -0xe6,0xcd,0xf4,0x66,0x79,0xb3,0xbd,0xd3,0x30,0xd2,0xbd,0xb9,0xde,0x3c,0x6f,0xe1, -0xef,0x20,0xf2,0x96,0x7a,0x2b,0xbd,0xb5,0x7f,0x10,0x9d,0x77,0xfd,0x6f,0xd1,0xe0, -0xdd,0xf8,0x47,0x68,0xf2,0x6e,0xf1,0x6e,0x85,0xb4,0x79,0x77,0x78,0x77,0x79,0x77, -0x7b,0x9b,0xbd,0xfb,0xbd,0x07,0xbd,0x87,0xbc,0x87,0xbd,0x47,0xbc,0x47,0xbd,0xc7, -0xbc,0xc7,0xbd,0x27,0xbd,0xa7,0xbc,0xa7,0xbd,0x2d,0xde,0xf3,0xde,0x0b,0xde,0x4b, -0xde,0xcb,0xde,0x2b,0xde,0xab,0xde,0x38,0xa2,0x2d,0x11,0x4f,0x74,0x20,0x12,0x88, -0x4e,0x44,0x17,0xa2,0x1b,0xd1,0x83,0xe8,0x45,0x24,0x12,0x7d,0x89,0x24,0x22,0x85, -0x48,0x23,0xd2,0x89,0x21,0xc4,0x30,0x62,0xf8,0xef,0x25,0x82,0xc8,0xfc,0xa7,0x82, -0x05,0xe1,0x10,0x33,0x89,0x3c,0xa2,0x90,0x28,0x22,0xca,0x89,0x3a,0xa2,0x81,0x68, -0x24,0xd6,0x12,0x1b,0x88,0x4d,0xc4,0x16,0x62,0x1b,0xb1,0x83,0xd8,0x45,0xec,0x26, -0x9a,0x89,0xfd,0xc4,0x01,0xe2,0x20,0x71,0x88,0xf8,0x00,0x32,0x42,0x1c,0x25,0x8e, -0x11,0xc7,0x89,0x93,0xc4,0x27,0x90,0x29,0xa2,0x85,0xb8,0x44,0x7c,0x49,0x5c,0x21, -0xbe,0x82,0x80,0xfd,0xb8,0xfd,0x36,0x10,0xbb,0xfd,0x4e,0xfb,0x5d,0xf6,0xbb,0xee, -0x77,0xdb,0xef,0xb1,0xdf,0x6b,0xbf,0xf7,0x7e,0xe2,0x7e,0xdf,0xfd,0xa4,0xfd,0x94, -0xfd,0xb4,0xfd,0xf4,0xfd,0x21,0xfb,0x43,0xf7,0x87,0xed,0x67,0xed,0x8f,0xdf,0xcf, -0xde,0xcf,0xd9,0x9f,0xba,0x3f,0x0d,0x23,0x7d,0x7f,0xe6,0x7e,0xde,0x7e,0xc1,0x7e, -0xd1,0x7e,0xf1,0x1b,0x48,0xf6,0x2b,0xf7,0x1b,0xf7,0x37,0xed,0xef,0xd8,0x6f,0xde, -0x3f,0xb4,0x7f,0x74,0xff,0xe4,0x7e,0xcb,0xfe,0xa5,0xfd,0xab,0xfb,0xf1,0x07,0x5c, -0x0e,0x78,0x1d,0x20,0x1d,0xa0,0x1d,0xa0,0x1f,0x08,0x3b,0xc0,0x3a,0xc0,0x3e,0xc0, -0x39,0x90,0x7e,0x40,0x70,0x40,0x7e,0x40,0x7d,0x40,0x77,0xc0,0x74,0xa0,0xe9,0x40, -0xcb,0x81,0xb6,0x03,0x1d,0x07,0xba,0x0e,0x74,0x1f,0xe8,0x3f,0x30,0x7c,0x60,0xf4, -0xc0,0xd8,0x81,0xc9,0x03,0x96,0x03,0xcb,0x07,0x56,0x0f,0xe0,0x7c,0xf0,0x3e,0x2e, -0x3e,0x6e,0x3e,0x1e,0x3e,0x5e,0x3e,0xde,0xff,0x07,0x44,0x1f,0xf2,0x1f,0x25,0xd0, -0x27,0xf4,0x4f,0x22,0xf2,0xaf,0x22,0xce,0x27,0xd9,0x27,0xed,0xef,0x08,0xf7,0x17, -0x84,0x56,0x8a,0xfe,0x4c,0x14,0x10,0xed,0x2f,0xe8,0x7c,0x4c,0x3e,0x8d,0x3e,0x4d, -0x3e,0x6d,0x3e,0x37,0xfe,0x28,0x7d,0x56,0xee,0x59,0x79,0x00,0x79,0xf4,0x67,0x31, -0x01,0x79,0xfa,0x7b,0x99,0xf3,0x59,0xf4,0x79,0xe9,0x03,0x7c,0xed,0xac,0x10,0x7c, -0x5d,0xad,0x78,0xfa,0xfa,0xfc,0x16,0x64,0xdf,0x40,0x2b,0xa1,0xbe,0x91,0x56,0xe2, -0x7c,0x93,0x7f,0x15,0xd2,0x7c,0xb9,0xbe,0x42,0xdf,0xa2,0xbf,0x39,0x0a,0x5f,0xad, -0x6f,0xb5,0x6f,0xfd,0x6f,0xd1,0xec,0xdb,0xee,0x7b,0xc3,0xb7,0xef,0xff,0x1a,0x03, -0xbe,0xf7,0xd6,0x59,0xe7,0xef,0xc4,0x90,0xef,0xb0,0xef,0x88,0xef,0xa8,0xef,0x98, -0xef,0xb8,0xef,0xa4,0xef,0x94,0xef,0xb4,0xaf,0xc5,0x77,0xc1,0x77,0xd9,0x77,0xd5, -0xd7,0xf6,0xa0,0xc3,0x41,0x97,0x83,0x1e,0x07,0x89,0x07,0x49,0x07,0x69,0x07,0x43, -0x0e,0x32,0x0e,0xb2,0x0f,0xa6,0x1f,0x14,0x1c,0x94,0x1e,0x54,0x1f,0x34,0x1d,0x6c, -0x3a,0xd8,0x71,0xb0,0xff,0xe0,0xc8,0xc1,0xc9,0x83,0xf3,0x07,0x57,0x0e,0xe2,0xdf, -0x71,0x7b,0xc7,0xf7,0x1d,0xfa,0x3b,0x8c,0x77,0x38,0xef,0xa4,0xbf,0xc3,0x7b,0x47, -0xf4,0x8e,0xf4,0x1d,0xe5,0x3b,0xba,0x77,0x4c,0xef,0x34,0xbc,0xd3,0xf2,0x4e,0xd7, -0x3b,0xfd,0xef,0x0c,0xbf,0x33,0xf6,0xce,0xd4,0x3b,0xf3,0xef,0x2c,0xbf,0x83,0xf3, -0x73,0xf0,0x73,0xf3,0x23,0xfa,0x51,0xfc,0x42,0xfc,0x18,0x7e,0x6c,0xbf,0x74,0x3f, -0x81,0x9f,0xd4,0x4f,0xed,0x67,0xf2,0x6b,0xf2,0xeb,0xf0,0x33,0xfb,0x0d,0xf9,0x8d, -0xfa,0x4d,0xfa,0x59,0xfc,0x96,0xfc,0x56,0xfd,0xf0,0x24,0x17,0x92,0x17,0x89,0x44, -0xa2,0x93,0x22,0x48,0xf1,0xa4,0x54,0x52,0x3a,0x29,0x93,0xc4,0x23,0x09,0x48,0x22, -0x92,0x84,0x24,0x25,0xc9,0x49,0x4a,0x92,0x9a,0xa4,0x23,0x19,0x49,0x26,0x52,0x2d, -0xa9,0x81,0xd4,0x44,0x6a,0x21,0xb5,0x91,0x3a,0x48,0x5d,0xa4,0x6e,0x92,0x99,0xd4, -0x4f,0x1a,0x24,0x0d,0x91,0x86,0x49,0x23,0xa4,0x51,0xd2,0x18,0x69,0x9c,0x34,0x49, -0x9a,0x22,0x4d,0x93,0x2c,0xa4,0x79,0xd2,0x02,0x69,0x89,0xb4,0x4c,0x5a,0x21,0xad, -0x92,0x70,0xfe,0xb6,0xfe,0x78,0x7f,0x07,0x7f,0x27,0x7f,0x17,0x7f,0x37,0x7f,0x0f, -0x7f,0x2f,0x7f,0xa2,0xbf,0xaf,0x3f,0xc9,0x9f,0xfc,0x2b,0x40,0xf1,0x0f,0xfd,0x97, -0x22,0xcc,0x9f,0xe5,0xcf,0xf6,0x4f,0xfb,0x17,0x82,0xbb,0xce,0x5f,0x05,0xcf,0x5f, -0xe0,0x2f,0x5c,0xe7,0x5f,0x1e,0x91,0xbf,0x78,0x9d,0x75,0xfe,0x86,0x48,0xfc,0x8b, -0xfe,0x29,0x90,0xfa,0xcb,0xfe,0x00,0x72,0x88,0xd2,0x5f,0xed,0xaf,0xf3,0x37,0xfa, -0x9b,0xfc,0x6b,0xfd,0x1b,0xfc,0x9b,0xfc,0x5b,0xfc,0xdb,0xfc,0x3b,0xfc,0xbb,0xfc, -0xbb,0xfd,0xcd,0xfe,0xfd,0xfe,0x83,0xfe,0x43,0xfe,0xc3,0xfe,0x23,0xfe,0xa3,0xfe, -0x63,0xfe,0xe3,0xfe,0x93,0xfe,0x53,0xfe,0xd3,0xfe,0x16,0xff,0x79,0xff,0x05,0xff, -0x25,0xff,0x65,0xff,0x15,0xff,0x55,0x7f,0x5c,0x80,0x6d,0x00,0x3e,0xc0,0x21,0xc0, -0x29,0xc0,0x25,0xc0,0x2d,0xc0,0x23,0xc0,0x2b,0x80,0x18,0xe0,0x1b,0x40,0x0a,0xa0, -0x04,0xd0,0x02,0xe8,0x01,0x21,0x01,0x61,0x01,0x11,0x01,0x8c,0x00,0x56,0x40,0x7c, -0x00,0x3b,0x80,0x13,0x90,0x1a,0x90,0x1e,0x90,0x19,0xc0,0x0b,0x10,0x04,0x88,0x02, -0x24,0x01,0xd2,0x00,0x79,0x80,0x32,0x40,0x1d,0xa0,0x0b,0x30,0x06,0x98,0x02,0x6a, -0x03,0x1a,0x02,0x9a,0x02,0x5a,0x02,0xda,0x02,0x3a,0x02,0xba,0x02,0xba,0x03,0xcc, -0x01,0xfd,0x01,0x83,0x01,0x43,0x01,0xc3,0x01,0x23,0x01,0xa3,0x01,0x63,0x01,0xe3, -0x01,0x93,0x01,0x53,0x01,0xd3,0x01,0x96,0x80,0xf9,0x80,0x85,0x80,0xa5,0x80,0xe5, -0x80,0x95,0x80,0xd5,0x00,0x1c,0xd9,0x96,0x8c,0x27,0x3b,0x90,0x9d,0xc8,0x2e,0x64, -0x37,0xb2,0x07,0xd9,0x8b,0x4c,0x24,0xfb,0x92,0x49,0x64,0x0a,0x99,0x46,0xa6,0x93, -0x43,0xc8,0x61,0xe4,0x08,0x32,0x83,0xcc,0x22,0xc7,0x93,0xd9,0x64,0x0e,0x39,0x95, -0x9c,0x4e,0xce,0x24,0xf3,0xc8,0x02,0xb2,0x88,0x2c,0x21,0x4b,0xc9,0x72,0xb2,0x92, -0xac,0x26,0xeb,0xc8,0x46,0xb2,0x89,0x5c,0x4b,0x6e,0x20,0x37,0x91,0x5b,0xc8,0x6d, -0xe4,0x0e,0x72,0x17,0xb9,0x9b,0x6c,0x26,0xf7,0x93,0x07,0xc9,0x43,0xe4,0x61,0xf2, -0x08,0x79,0x94,0x3c,0x46,0x1e,0x27,0x4f,0x92,0xa7,0xc8,0xd3,0x64,0x0b,0x79,0x9e, -0xbc,0x40,0x5e,0x22,0x2f,0x93,0x57,0xc8,0xab,0x64,0x1c,0xc5,0x96,0x82,0xa7,0x38, -0x50,0x9c,0x28,0x2e,0x14,0x37,0x8a,0x07,0xc5,0x8b,0x42,0xa4,0xf8,0x52,0x48,0x14, -0x0a,0x85,0x46,0xa1,0x53,0x42,0x28,0x61,0x94,0x08,0x0a,0x83,0xc2,0xa2,0xc4,0x53, -0xd8,0x14,0x0e,0x25,0x95,0x92,0x4e,0xc9,0xa4,0xf0,0x28,0x02,0x8a,0x88,0x22,0xa1, -0x48,0x29,0x72,0x8a,0x92,0xa2,0xa6,0xe8,0x28,0x46,0x8a,0x89,0x52,0x4b,0x69,0xa0, -0x34,0x51,0x5a,0x28,0x6d,0x94,0x0e,0x4a,0x17,0xa5,0x9b,0x62,0xa6,0xf4,0x53,0x06, -0x29,0x43,0x94,0x61,0xca,0x08,0x65,0x94,0x32,0x46,0x79,0xfc,0x37,0x60,0x1c,0x32, -0xf1,0x77,0x63,0x92,0xf2,0x84,0x32,0x45,0x99,0xa6,0x58,0x28,0x73,0x7f,0x02,0x8b, -0x90,0x97,0x10,0xf0,0x2e,0x78,0xd7,0x0e,0x42,0x80,0xb8,0x42,0x3c,0x21,0x3e,0x10, -0x32,0x24,0x10,0x12,0x0a,0x89,0xb4,0x12,0x07,0x49,0x86,0xa4,0xfd,0x16,0xdc,0x77, -0x85,0x90,0xa2,0x77,0x15,0x10,0xed,0xbb,0xd5,0x90,0xfa,0x77,0x9b,0x21,0xed,0xef, -0xde,0xf8,0xb3,0xe8,0x7b,0x83,0xfe,0x77,0x87,0xde,0x7d,0x00,0x19,0x7b,0x77,0xe2, -0x0f,0xf2,0x14,0x32,0x07,0x59,0x84,0xbc,0x84,0x80,0x43,0xe0,0x90,0x1d,0x84,0x00, -0x71,0x85,0x78,0x42,0x7c,0x20,0x64,0x48,0xa0,0x95,0x50,0x48,0x24,0x24,0xee,0xb7, -0x48,0x3e,0x94,0x06,0xe1,0x1e,0x12,0x42,0x8a,0x0e,0x29,0x20,0xda,0x43,0xd5,0x90, -0xfa,0x43,0xcd,0x7f,0x16,0xed,0x56,0x6e,0x1c,0xea,0x3b,0x74,0xef,0xd0,0x03,0x8c, -0xb1,0x43,0x13,0x7f,0x07,0x26,0x0f,0x4d,0x1d,0x9a,0x3e,0x64,0x39,0x34,0x7f,0x68, -0xe1,0xd0,0xd2,0xa1,0xe5,0x43,0x2b,0x87,0x56,0x0f,0xe1,0xa8,0xb6,0x54,0x3c,0xd5, -0x81,0xea,0x44,0x75,0xa1,0xba,0x51,0x3d,0xa8,0x5e,0x54,0x22,0xd5,0x97,0x4a,0xa2, -0x52,0xa8,0x34,0x2a,0x9d,0x1a,0x42,0x0d,0xa3,0x46,0x50,0x19,0x54,0x16,0x35,0x9e, -0xca,0xa6,0x72,0xa8,0xa9,0xd4,0x74,0x6a,0x26,0x95,0x47,0x15,0x50,0x45,0x54,0x09, -0x55,0x4a,0x95,0x53,0x95,0x54,0x35,0x55,0x47,0x35,0x52,0x4d,0xd4,0x5a,0x6a,0x03, -0xb5,0x89,0xda,0x42,0x6d,0xa3,0x76,0x50,0xbb,0xa8,0xdd,0x54,0x33,0xb5,0x9f,0x3a, -0x48,0x1d,0xa2,0x0e,0x53,0x47,0xa8,0xa3,0xd4,0x31,0xea,0x38,0x75,0x92,0x3a,0x45, -0x9d,0xa6,0x5a,0xa8,0xf3,0xd4,0x05,0xea,0x12,0x75,0x99,0xba,0x42,0x5d,0xa5,0xe2, -0x68,0xb6,0x34,0x3c,0xcd,0x81,0xe6,0x44,0x73,0xa1,0xb9,0xd1,0x3c,0x68,0x5e,0x34, -0x22,0xcd,0x97,0x46,0xa2,0x51,0x68,0x34,0x1a,0x9d,0x16,0x42,0x0b,0xa3,0x45,0xd0, -0x18,0x34,0x16,0x2d,0xce,0x4a,0x3c,0x8d,0x4d,0xe3,0xd0,0x52,0x69,0xe9,0xb4,0x4c, -0x1a,0x8f,0x26,0xa0,0x89,0x68,0x12,0x9a,0x94,0x26,0xa7,0x29,0x69,0x6a,0x9a,0xf6, -0x9f,0x1c,0x1d,0xa4,0x96,0xd6,0x42,0xeb,0xa2,0xf5,0xd3,0x86,0x69,0x63,0xb4,0x29, -0xda,0x3c,0x6d,0x99,0x86,0x7b,0xcf,0xe1,0x3d,0xb7,0xf7,0x88,0xef,0x51,0xde,0x0b, -0x7b,0x2f,0xfe,0xbd,0xf4,0xf7,0x44,0xef,0x29,0xdf,0x33,0xbd,0xd7,0xf2,0x5e,0xf7, -0x7b,0x43,0xef,0x8d,0xbd,0x37,0xfd,0xde,0xd2,0x7b,0xb8,0xf7,0x9d,0xde,0xf7,0x7a, -0x3f,0xe4,0xfd,0xa4,0xf7,0xd9,0xef,0xa7,0xbf,0x2f,0x78,0x5f,0xfa,0xbe,0xfa,0x7d, -0xd3,0xfb,0x4d,0xef,0x77,0xbc,0x6f,0x7e,0x7f,0xe8,0xfd,0xd1,0xf7,0x27,0xdf,0xb7, -0xbc,0xbf,0xf4,0xfe,0xea,0xfb,0xf8,0x40,0x97,0x40,0xaf,0x40,0x52,0x20,0x3d,0x30, -0x22,0x30,0x3e,0x30,0x35,0x90,0x17,0x28,0x09,0x54,0x06,0x1a,0x03,0x1b,0x02,0xdb, -0x02,0xbb,0x03,0x07,0x03,0x47,0x02,0xc7,0x03,0xa7,0x03,0x17,0x02,0x57,0x02,0x6d, -0xe9,0x78,0xba,0x03,0xdd,0x89,0xee,0xfc,0x3b,0xb8,0xd0,0xbd,0xe8,0xbe,0x74,0x0a, -0x9d,0x4e,0x0f,0xa3,0x33,0xe8,0xf1,0x74,0x0e,0x3d,0x9d,0xce,0xa3,0x8b,0xe8,0x52, -0xba,0x92,0xae,0xa3,0x9b,0xe8,0x0d,0xf4,0x26,0x7a,0x0b,0xbd,0x8d,0xde,0x41,0xef, -0xa2,0x77,0xd3,0xcd,0xf4,0x7e,0xfa,0x20,0x7d,0x88,0x3e,0x4c,0x1f,0xa1,0x8f,0xd2, -0xc7,0xe8,0x93,0xf4,0x69,0xfa,0x3c,0x7d,0x89,0xbe,0x42,0xc7,0x05,0xe1,0x83,0x9c, -0x82,0xdc,0x82,0xbc,0x82,0x7c,0x83,0x28,0x41,0xf4,0xa0,0xb0,0x20,0x76,0x50,0x66, -0x10,0x3f,0x48,0x10,0x24,0x0a,0x92,0x04,0x49,0x83,0xe4,0x41,0xca,0x20,0x75,0x90, -0x2e,0xc8,0x18,0x64,0x0a,0xaa,0x0d,0x6a,0x08,0x6a,0x0a,0x6a,0x09,0x6a,0x0b,0xea, -0x08,0xea,0x0a,0xea,0x0e,0x32,0x07,0xf5,0x07,0x0d,0x06,0x0d,0x05,0x0d,0x07,0x8d, -0x04,0x8d,0x06,0x8d,0x05,0x8d,0x07,0x4d,0x06,0x4d,0x05,0x4d,0x07,0x59,0x82,0xe6, -0x83,0x16,0x82,0x96,0x82,0x96,0x83,0x56,0x82,0x56,0x83,0x70,0x87,0x6d,0x0f,0xe3, -0x0f,0x3b,0x1c,0x76,0x3a,0xec,0x72,0xd8,0xed,0xb0,0xc7,0x61,0xaf,0xc3,0xc4,0xc3, -0xbe,0x87,0x49,0x87,0x29,0x87,0xe9,0x87,0xc3,0x0e,0x33,0x0e,0xc7,0x1f,0xe6,0x1c, -0x4e,0x3f,0xcc,0x3b,0x2c,0x3a,0x2c,0x3d,0xac,0x3c,0xac,0x3b,0x6c,0x3a,0xdc,0x70, -0xb8,0xe5,0x70,0xc7,0xe1,0xee,0xc3,0xfd,0x87,0x87,0x0e,0x8f,0x1c,0x1e,0x3b,0x3c, -0x79,0x78,0xfa,0xf0,0xfc,0xe1,0xa5,0xc3,0xab,0x87,0xf1,0xc1,0x2e,0xc1,0x1e,0xc1, -0xbe,0xc1,0x94,0xe0,0x90,0xe0,0xb0,0xe0,0x88,0x60,0x46,0x30,0x2b,0x38,0x3e,0x98, -0x1d,0xcc,0x09,0x4e,0x0d,0x4e,0x0f,0xce,0x0c,0xe6,0x05,0x0b,0x82,0x45,0xc1,0x92, -0x60,0x69,0xb0,0x3c,0x58,0x19,0xac,0x0e,0xd6,0x05,0x1b,0x83,0x4d,0xc1,0xb5,0xc1, -0x0d,0xc1,0x4d,0xc1,0x2d,0xc1,0x6d,0xc1,0x1d,0xc1,0x5d,0xc1,0xdd,0xc1,0xe6,0xe0, -0xfe,0xe0,0xc1,0xe0,0xa1,0xe0,0xe1,0xe0,0x91,0xe0,0xd1,0xe0,0xb1,0xe0,0xf1,0xe0, -0xc9,0xe0,0xa9,0xe0,0xe9,0x60,0x4b,0xf0,0x7c,0xf0,0x42,0xf0,0x52,0xf0,0x72,0xf0, -0x4a,0x30,0x2e,0x04,0x17,0xe2,0x12,0x42,0x0c,0xa1,0x87,0x44,0x84,0x70,0x42,0x32, -0x43,0x44,0x21,0xba,0x90,0x86,0x90,0xb6,0x90,0xee,0x90,0xc1,0x90,0xd1,0x90,0xa9, -0x90,0x85,0x90,0xd5,0x10,0x87,0x0f,0x3c,0x3e,0x20,0x7d,0x10,0xf1,0x01,0xeb,0x83, -0xcc,0x0f,0xe4,0x1f,0x98,0x3e,0x68,0xf9,0xa0,0xff,0x83,0xb1,0x0f,0xa6,0x3f,0x58, -0xf8,0x60,0xe5,0x03,0xfc,0x87,0x6e,0x1f,0x92,0x3e,0x0c,0xfb,0x90,0xf5,0x21,0xe7, -0x43,0xde,0x87,0x92,0x0f,0x95,0x1f,0xea,0x3e,0x34,0x7d,0xd8,0xf4,0x61,0xc7,0x87, -0x43,0x1f,0x8e,0x7d,0x68,0xf9,0x70,0xf5,0x43,0x87,0x50,0xb7,0x50,0x62,0x68,0x48, -0x28,0x2b,0x34,0x33,0x54,0x14,0xaa,0x0e,0x35,0x85,0xb6,0x84,0x76,0x85,0x0e,0x86, -0x8e,0x85,0x4e,0x87,0x2e,0x87,0xda,0x86,0x39,0x84,0x79,0x85,0x91,0xc2,0xe8,0x61, -0x8c,0x30,0x76,0x58,0x7a,0x98,0x20,0x4c,0x19,0x66,0x0a,0x6b,0x08,0xeb,0x0e,0x1b, -0x0c,0x1b,0x0b,0x9b,0x0e,0x5b,0x0a,0x5b,0x0d,0xc3,0x1f,0x71,0x3b,0xe2,0x75,0x84, -0x72,0x24,0xe2,0x08,0xeb,0x48,0xe6,0x11,0xd1,0x11,0xe9,0x11,0xe5,0x11,0xdd,0x11, -0xd3,0x91,0x86,0x23,0x2d,0x47,0x3a,0x8e,0x74,0x1f,0xe9,0x3f,0x32,0x74,0x64,0xf4, -0xc8,0xe4,0x11,0xcb,0x91,0xa5,0x23,0xab,0x47,0xf0,0x47,0x5d,0x8e,0x7a,0x1d,0x25, -0x1d,0xa5,0x1f,0x8d,0x38,0x1a,0x7f,0x34,0xf5,0x28,0xef,0xa8,0xe4,0xa8,0xf2,0xa8, -0xee,0xa8,0xe9,0x68,0xd3,0xd1,0xb6,0xa3,0x5d,0x47,0xcd,0x47,0x87,0x8e,0x8e,0x1e, -0x1d,0x3f,0x3a,0x75,0xd4,0x72,0x74,0xe1,0xe8,0xf2,0x51,0xdb,0x70,0x87,0x70,0x97, -0x70,0x8f,0x70,0x62,0x38,0x29,0x9c,0x16,0x1e,0x12,0x1e,0x11,0x1e,0x1f,0x9e,0x1e, -0xce,0x0b,0x17,0x85,0x4b,0xc3,0x95,0xe1,0xba,0x70,0x53,0x78,0x43,0x78,0x5b,0x78, -0x77,0xf8,0x60,0xf8,0x48,0xf8,0x58,0xf8,0x64,0xf8,0x74,0xf8,0x7c,0xf8,0x52,0xf8, -0x4a,0x38,0x2e,0x02,0x1f,0xe1,0x14,0xe1,0x16,0x41,0x8c,0xa0,0x44,0xd0,0x23,0x22, -0x22,0xe2,0x23,0x52,0x23,0x32,0x23,0x44,0x11,0xf2,0x08,0x63,0x44,0x6d,0x44,0x4b, -0x44,0x57,0x44,0x7f,0xc4,0x70,0xc4,0x64,0xc4,0x52,0xc4,0x4a,0x04,0xee,0x18,0xfe, -0x98,0xd3,0x31,0xb7,0x63,0x5e,0xc7,0x7c,0x8f,0x51,0x8e,0xd1,0x8f,0x85,0x1d,0x63, -0x1c,0x8b,0x3f,0xc6,0x39,0x96,0x7e,0x8c,0x77,0x4c,0x74,0x4c,0x7a,0x4c,0x79,0xcc, -0x74,0xac,0xe1,0x58,0xcb,0xb1,0x8e,0x63,0xfd,0xc7,0x86,0x8f,0x8d,0x1e,0x1b,0x3f, -0x36,0x75,0xcc,0x72,0x6c,0xe1,0xd8,0xf2,0xb1,0xd5,0x63,0xb6,0xc7,0x1d,0x8e,0xbb, -0x1c,0xf7,0x3a,0xee,0x7b,0x9c,0x72,0x3c,0xe4,0x38,0xe3,0x78,0xfc,0xf1,0xf4,0xe3, -0x82,0xe3,0x92,0xe3,0xf2,0xe3,0xea,0xe3,0xc6,0xe3,0x0d,0xc7,0xdb,0x8e,0x77,0x1d, -0x37,0x1f,0x1f,0x3c,0x3e,0x7c,0x7c,0xf4,0xf8,0xf8,0xf1,0xa9,0xe3,0x96,0xe3,0x0b, -0xc7,0x57,0x8e,0xdb,0x46,0x3a,0x45,0x7a,0x44,0xfa,0x46,0xd2,0x22,0xc3,0x22,0x59, -0x91,0x9c,0xc8,0xcc,0x48,0x51,0xa4,0x3c,0x52,0x17,0x59,0x1b,0xd9,0x12,0xd9,0x15, -0xd9,0x1f,0x39,0x1c,0x39,0x16,0x39,0x15,0x39,0x1f,0xb9,0x1c,0x09,0x18,0xeb,0xfc, -0x65,0xe0,0x18,0xb6,0x0c,0xbb,0x75,0xfe,0x09,0xc0,0x33,0xec,0xff,0xed,0x71,0x60, -0x38,0x31,0x5c,0x18,0xae,0x56,0xdc,0x18,0x1e,0x0c,0xcf,0x7f,0x09,0xbc,0x18,0x44, -0x86,0x2f,0x83,0xc4,0x20,0xff,0x1f,0x50,0x18,0xd4,0x75,0xfe,0x2c,0x02,0x19,0xc1, -0x8c,0x50,0x46,0x38,0x23,0x92,0xc1,0x64,0xc4,0x31,0x92,0x18,0xc9,0x8c,0x14,0x46, -0x1a,0x23,0x83,0xc1,0x65,0xf0,0x19,0x42,0x86,0x98,0x51,0xc4,0x90,0x31,0x14,0x0c, -0x15,0x43,0xcb,0x30,0x30,0xaa,0x19,0x35,0x8c,0x7a,0x46,0x23,0xa3,0x99,0xd1,0xca, -0x68,0x67,0x74,0x32,0x6e,0x30,0x7a,0x19,0x7d,0x8c,0x01,0xc6,0x3d,0xc6,0x7d,0xc6, -0x03,0xc6,0x43,0xc6,0x23,0xc6,0x63,0xc6,0x04,0xe3,0x09,0xe3,0x29,0x63,0x86,0x31, -0xc7,0x78,0xce,0x58,0x64,0xbc,0x60,0xbc,0x64,0xbc,0x62,0x80,0x28,0x9b,0x28,0xbb, -0x28,0xfb,0x28,0x42,0x94,0x73,0x94,0x6b,0x94,0x7b,0x94,0x67,0x94,0x77,0x94,0x4f, -0x94,0x5f,0x14,0x39,0x8a,0x1a,0x15,0x18,0x15,0x1c,0x15,0x1a,0x15,0x1e,0x15,0x19, -0xc5,0x8c,0x8a,0x8b,0x4a,0x8a,0x4a,0x8e,0x4a,0x89,0x4a,0x8b,0xca,0x88,0xe2,0x46, -0xf1,0xa3,0x84,0x51,0xe2,0xa8,0xa2,0x28,0x59,0x94,0x22,0x4a,0x15,0xa5,0x8d,0x32, -0x44,0x55,0x47,0xd5,0x44,0xd5,0x47,0x35,0x46,0x35,0x47,0xb5,0x46,0xb5,0x47,0x75, -0x46,0xdd,0x88,0xea,0x8d,0xea,0x8b,0x1a,0x88,0xba,0x17,0x75,0x3f,0xea,0x41,0xd4, -0xc3,0xa8,0x47,0x51,0x8f,0xa3,0x26,0xa2,0x9e,0x44,0x3d,0x8d,0x9a,0x89,0x9a,0x8b, -0x7a,0x1e,0xb5,0x18,0xf5,0x22,0xea,0x65,0xd4,0xab,0x28,0x10,0x6d,0x13,0x6d,0x17, -0x6d,0x1f,0x4d,0x88,0x76,0x8e,0x76,0x8d,0x76,0x8f,0xf6,0x8c,0xf6,0x8e,0xf6,0x89, -0xf6,0x8b,0x26,0x47,0x53,0xa3,0x03,0xa3,0x83,0xa3,0x43,0xa3,0xc3,0xa3,0x23,0xa3, -0x99,0xd1,0x71,0xd1,0x49,0xd1,0xc9,0xd1,0x29,0xd1,0x69,0xd1,0x19,0xd1,0xdc,0x68, -0x7e,0xb4,0x30,0x5a,0x1c,0x5d,0x14,0x2d,0x8b,0x56,0x44,0xab,0xa2,0xb5,0xd1,0x86, -0xe8,0xea,0xe8,0x9a,0xe8,0xfa,0xe8,0xc6,0xe8,0xe6,0xe8,0xd6,0xe8,0xf6,0xe8,0xce, -0xe8,0x1b,0xd1,0xbd,0xd1,0x7d,0xd1,0x03,0xd1,0xf7,0xa2,0xef,0x47,0x3f,0x88,0x7e, -0x18,0xfd,0x28,0xfa,0x71,0xf4,0x44,0xf4,0x93,0xe8,0xa7,0xd1,0x33,0xd1,0x73,0xd1, -0xcf,0xa3,0x17,0xa3,0x5f,0x44,0xbf,0x8c,0x7e,0x15,0x0d,0x98,0x36,0x4c,0x3b,0xa6, -0x3d,0x93,0xc0,0x74,0x66,0xba,0x32,0xdd,0x99,0x9e,0x4c,0x6f,0xa6,0x0f,0xd3,0x8f, -0x49,0x66,0x52,0x99,0x81,0xcc,0x60,0x66,0x28,0x33,0x9c,0x19,0xc9,0x64,0x32,0xe3, -0x98,0x49,0xcc,0x64,0x66,0x0a,0x33,0x8d,0x99,0xc1,0xe4,0x32,0xf9,0x4c,0x21,0x53, -0xcc,0x2c,0x62,0xca,0x98,0x0a,0xa6,0x8a,0xa9,0x65,0x1a,0x98,0xd5,0xcc,0x1a,0x66, -0x3d,0xb3,0x91,0xd9,0xcc,0x6c,0x65,0xb6,0x33,0x3b,0x99,0x37,0x98,0xbd,0xcc,0x3e, -0xe6,0x00,0xf3,0x1e,0xf3,0x3e,0xf3,0x01,0xf3,0x21,0xf3,0x11,0xf3,0x31,0x73,0x82, -0xf9,0x84,0xf9,0x94,0x39,0xc3,0x9c,0x63,0x3e,0x67,0x2e,0x32,0x5f,0x30,0x5f,0x32, -0x5f,0x31,0x01,0xcb,0x86,0x65,0xc7,0xb2,0x67,0x11,0x58,0xce,0x2c,0x57,0x96,0x3b, -0xcb,0x93,0xe5,0xcd,0xf2,0x61,0xf9,0xb1,0xc8,0x2c,0xf8,0x85,0x8b,0x15,0xc8,0x0a, -0x66,0x85,0xb2,0xc2,0x59,0x91,0x2c,0x26,0x2b,0x8e,0x95,0xc4,0x4a,0x66,0xa5,0xb0, -0xd2,0x58,0x19,0x2c,0x2e,0x8b,0x6f,0x45,0x08,0x11,0x5b,0x29,0x62,0xc9,0x58,0x0a, -0x96,0x8a,0xa5,0x65,0x19,0x58,0xd5,0xac,0x1a,0x56,0x3d,0xab,0x11,0xd2,0x0c,0x69, -0xb5,0xd2,0xce,0xea,0xc4,0xb8,0xc1,0xea,0x65,0xf5,0xb1,0x06,0x58,0xf7,0x58,0xf7, -0x59,0x0f,0x58,0x0f,0x59,0x8f,0x58,0x8f,0x59,0x13,0xac,0x27,0xac,0xa7,0xac,0x19, -0xd6,0x1c,0xeb,0x39,0x6b,0x91,0xf5,0x82,0xf5,0x92,0xf5,0x8a,0x05,0x62,0x6c,0x62, -0xec,0x62,0xec,0x63,0x08,0x31,0xce,0x31,0xae,0x31,0xee,0x31,0x9e,0x31,0xde,0x31, -0x3e,0x31,0x7e,0x31,0xe4,0x18,0x6a,0x4c,0x60,0x4c,0x70,0x4c,0x68,0x4c,0x78,0x4c, -0x64,0x0c,0x33,0x26,0x2e,0x26,0x29,0x26,0x39,0x26,0x25,0x26,0x2d,0x26,0x23,0x86, -0x1b,0xc3,0x8f,0x11,0xc6,0x88,0x63,0x8a,0x62,0x64,0x31,0x8a,0x18,0x55,0x8c,0x36, -0xc6,0x10,0x53,0x1d,0x53,0x13,0x53,0x1f,0xd3,0x18,0xd3,0x1c,0xd3,0x1a,0xd3,0x1e, -0xd3,0x19,0x73,0x23,0xa6,0x37,0xa6,0x2f,0x66,0xc0,0xca,0xbd,0x98,0xfb,0x31,0x0f, -0x62,0x1e,0xc6,0x3c,0x8a,0x79,0x1c,0x33,0x11,0xf3,0x24,0xe6,0x69,0xcc,0x4c,0xcc, -0x5c,0xcc,0xf3,0x98,0xc5,0x98,0x17,0x31,0x2f,0x63,0x5e,0xc5,0x80,0x58,0x9b,0x58, -0xbb,0x58,0xfb,0x58,0x42,0xac,0x73,0xac,0x6b,0xac,0x7b,0xac,0x67,0xac,0x77,0xac, -0x4f,0xac,0x5f,0x2c,0x39,0x96,0x1a,0x1b,0x18,0x1b,0x1c,0x1b,0x1a,0x1b,0x1e,0x1b, -0x19,0xcb,0x8c,0x8d,0x8b,0x4d,0x8a,0x4d,0x8e,0x4d,0x89,0x4d,0x8b,0xcd,0x88,0xe5, -0xc6,0xf2,0x63,0x85,0xb1,0xe2,0xd8,0xa2,0x58,0x59,0xac,0x22,0x56,0x15,0xab,0x8d, -0x35,0xc4,0x56,0xc7,0xd6,0xc4,0xd6,0xc7,0x36,0xc6,0x36,0xc7,0xb6,0xc6,0xb6,0xc7, -0x76,0xc6,0xde,0x88,0xed,0x8d,0xed,0x8b,0x1d,0x88,0xbd,0x17,0x7b,0x3f,0xf6,0x41, -0xec,0xc3,0xd8,0x47,0xb1,0x8f,0x63,0x27,0x62,0x9f,0xc4,0x3e,0x8d,0x9d,0x89,0x9d, -0x8b,0x7d,0x1e,0xbb,0x18,0xfb,0x22,0xf6,0x65,0xec,0xab,0x58,0x10,0x67,0x13,0x67, -0x17,0x67,0x1f,0x47,0x88,0x73,0x8e,0x73,0x8d,0x73,0x8f,0xf3,0x8c,0xf3,0x8e,0xf3, -0x89,0xf3,0x8b,0x23,0xc7,0x51,0xe3,0x02,0xe3,0x82,0xe3,0x42,0xe3,0xc2,0xe3,0x22, -0xe3,0x98,0x71,0x71,0x71,0x49,0x71,0xc9,0x71,0x29,0x71,0x69,0x71,0x19,0xbf,0x22, -0x99,0x10,0x41,0x9c,0x24,0x4e,0x1e,0xa7,0x8b,0xab,0x8d,0x6b,0x8a,0x6b,0xfd,0x03, -0xb4,0xc5,0x75,0xc5,0x99,0xe3,0x06,0xe3,0x86,0xe3,0x1e,0xbe,0xc1,0x63,0xc8,0x54, -0xdc,0x74,0x9c,0x25,0x6e,0x3e,0x6e,0x21,0x6e,0x29,0x6e,0x39,0x6e,0x25,0x6e,0x35, -0x0e,0x17,0x6f,0x13,0x6f,0x1f,0xef,0x1c,0xef,0x19,0xef,0x17,0x4f,0x8d,0x0f,0x8e, -0x0f,0x8f,0x67,0xc6,0x27,0xc5,0xa7,0xc4,0x67,0xc4,0xf3,0xe3,0xc5,0x10,0x59,0xbc, -0x2a,0xde,0x10,0x5f,0x13,0xdf,0x08,0x69,0x85,0x74,0xc6,0xf7,0x42,0x06,0xe2,0xef, -0x43,0x1e,0xc6,0x3f,0x8e,0x7f,0x12,0x3f,0x13,0xff,0x3c,0xfe,0x45,0xfc,0xab,0x78, -0x9b,0x04,0x87,0x04,0x97,0x04,0xb7,0x04,0x8f,0x04,0xaf,0x04,0x62,0x82,0x6f,0x02, -0x29,0x81,0x92,0x40,0x4b,0xa0,0x27,0x84,0x24,0x84,0x25,0x44,0x24,0x30,0x12,0x58, -0x09,0xf1,0x09,0xec,0x04,0x4e,0x42,0x6a,0x42,0x7a,0x42,0x66,0x02,0x2f,0x41,0x90, -0x20,0x4a,0x90,0x24,0x48,0x13,0xe4,0x09,0xca,0x04,0x75,0x82,0x2e,0xc1,0x98,0x60, -0x4a,0xa8,0x4d,0x68,0x48,0x68,0x4a,0x68,0x49,0x68,0x4b,0xe8,0x48,0xe8,0x4a,0xe8, -0x4e,0x30,0x27,0xf4,0x27,0x0c,0x26,0x0c,0x25,0x0c,0x27,0x8c,0x24,0x8c,0x26,0x8c, -0x25,0x8c,0x27,0x4c,0x26,0x4c,0x25,0x4c,0x27,0x58,0x12,0xe6,0x13,0x16,0x12,0x96, -0x12,0x96,0x13,0x56,0x12,0x56,0x13,0x70,0x89,0xb6,0x89,0xf8,0x44,0x87,0x44,0xa7, -0x44,0x97,0x44,0xb7,0x44,0x8f,0x44,0xaf,0x44,0x62,0xa2,0x6f,0x22,0x29,0x91,0x92, -0x48,0x4b,0xa4,0x27,0x86,0x24,0x86,0x25,0x46,0x24,0x32,0x12,0x59,0x89,0xf1,0x89, -0xec,0x44,0x4e,0x62,0x6a,0x62,0x7a,0x62,0x66,0xa2,0x20,0x51,0x92,0x28,0x4d,0x94, -0x27,0x2a,0x13,0xd5,0x89,0xba,0x44,0x63,0xa2,0x29,0xb1,0x36,0xb1,0x21,0xb1,0x29, -0xb1,0x2d,0xb1,0xf3,0xaf,0xa6,0x2b,0xb1,0x3b,0xd1,0x9c,0xd8,0x9f,0x38,0x98,0x38, -0x94,0x38,0x9c,0x38,0x92,0x38,0x9a,0x38,0x96,0x38,0x9e,0x38,0x95,0x38,0x9d,0x68, -0x49,0x9c,0x4f,0x5c,0x48,0x5c,0x4a,0x5c,0x4e,0x5c,0x49,0x5c,0x4d,0xc4,0x25,0xd9, -0x26,0xe1,0x93,0x1c,0x92,0xdc,0x92,0x88,0x49,0x94,0xa4,0x90,0x24,0x46,0x12,0x3b, -0x29,0x3d,0x49,0x90,0x24,0x4d,0x52,0x27,0x99,0x92,0x9a,0x92,0x3a,0x92,0xcc,0x49, -0x43,0x49,0xa3,0x49,0x93,0x49,0x96,0xa4,0xf9,0xa4,0x85,0xa4,0xa5,0xa4,0x65,0x08, -0x8e,0xed,0xc0,0x76,0x63,0x13,0xd9,0x14,0x36,0x9d,0x1d,0xc6,0x66,0xb0,0xe3,0xd9, -0x1c,0x76,0x3a,0x9b,0xc7,0x16,0xb1,0xa5,0x6c,0x25,0x5b,0xc7,0x36,0xb1,0x1b,0xd8, -0x2d,0xec,0x0e,0x76,0x37,0xbb,0x9f,0x3d,0xc4,0x1e,0x61,0x8f,0xb1,0x27,0xd9,0xd3, -0xec,0x79,0xf6,0x12,0x7b,0x85,0x8d,0x3b,0x81,0x3f,0xe1,0x74,0xc2,0xed,0x84,0xd7, -0x09,0xdf,0x13,0x94,0x13,0xf4,0x13,0x61,0x27,0x18,0x27,0xe2,0x4f,0x70,0x4e,0xa4, -0x9f,0xe0,0x9d,0x10,0x9d,0x90,0x9e,0x50,0x9e,0xd0,0x9d,0x30,0x9d,0x68,0x38,0xd1, -0x72,0xa2,0xe3,0x44,0xf7,0x89,0xfe,0x13,0x43,0x27,0x46,0x4e,0x8c,0x9d,0x98,0x3c, -0x31,0x7d,0x62,0xfe,0xc4,0xd2,0x89,0x95,0x13,0xb8,0x93,0xf8,0x93,0x4e,0x27,0xdd, -0x4e,0x7a,0x9d,0xf4,0x3d,0x49,0x39,0x49,0x3f,0x19,0x76,0x92,0x71,0x32,0xfe,0x24, -0xe7,0x64,0xfa,0x49,0xde,0x49,0xd1,0x49,0xe9,0x49,0xe5,0x49,0xdd,0x49,0xd3,0xc9, -0x86,0x93,0x2d,0x27,0x3b,0x4e,0x76,0x9f,0xec,0x3f,0x39,0x74,0x72,0xe4,0xe4,0xd8, -0xc9,0xc9,0x93,0xd3,0x27,0xe7,0x4f,0x2e,0x9d,0x5c,0x39,0x69,0x9b,0xec,0x94,0xec, -0x91,0xec,0x9b,0x4c,0x4b,0x0e,0x4b,0x66,0x25,0x73,0x92,0x33,0x93,0x45,0xc9,0xf2, -0x64,0x5d,0xb2,0x29,0xb9,0x21,0xb9,0x25,0xb9,0x23,0xb9,0x3b,0xb9,0x3f,0x79,0x28, -0x79,0x24,0x79,0x2c,0x79,0x32,0x79,0x3a,0x79,0x3e,0x79,0x29,0x79,0x25,0x19,0xc7, -0xc1,0x73,0x9c,0x38,0x6e,0x1c,0x2f,0x8e,0x2f,0x87,0xc2,0xa1,0x73,0xc2,0x38,0x0c, -0x4e,0x3c,0x87,0xc3,0x49,0xe7,0xf0,0x38,0x22,0x8e,0x94,0xa3,0xe4,0xe8,0x38,0x26, -0x4e,0x03,0xa7,0x85,0xd3,0xc1,0xe9,0xe6,0xf4,0x73,0x86,0x38,0x23,0x9c,0x31,0xce, -0x24,0x67,0x9a,0x33,0xcf,0x59,0xe2,0xac,0x72,0xf0,0xa7,0x5c,0x4e,0x79,0x9d,0x22, -0x9d,0xa2,0x9d,0x0a,0x39,0x15,0x71,0x8a,0x75,0x8a,0x7d,0x2a,0xf5,0x54,0xe6,0x29, -0xc1,0x29,0xc9,0x29,0xf9,0x29,0xf5,0x29,0xe3,0xa9,0xda,0x53,0x4d,0xa7,0xda,0x4e, -0x75,0x9d,0x32,0x9f,0x1a,0x3c,0x35,0x7c,0x6a,0xf4,0xd4,0xf8,0xa9,0xa9,0x53,0x96, -0x53,0x0b,0xa7,0x96,0x4f,0xad,0x9e,0xb2,0x3d,0xed,0x70,0xda,0xe5,0xb4,0xc7,0x69, -0xe2,0x69,0xd2,0x69,0xda,0xe9,0x90,0xd3,0x11,0xa7,0x59,0xa7,0xd9,0xa7,0x53,0x4f, -0x67,0x9e,0x16,0x9c,0x96,0x9c,0x96,0x9f,0x56,0x9f,0x36,0x9e,0xae,0x3d,0xdd,0x74, -0xba,0xed,0x74,0xd7,0x69,0xf3,0xe9,0xc1,0xd3,0xc3,0xa7,0x47,0x4f,0x8f,0x9f,0x9e, -0x3a,0x6d,0x39,0xbd,0x70,0x7a,0xf9,0xf4,0xea,0x69,0xdb,0x14,0x87,0x14,0x97,0x14, -0x8f,0x14,0x62,0x0a,0x29,0x85,0x96,0x12,0x92,0x12,0x91,0x12,0x9f,0x92,0x9a,0x92, -0x99,0x22,0x48,0x91,0xa4,0xc8,0x53,0xd4,0x29,0xc6,0x94,0xda,0x94,0xa6,0x94,0xb6, -0x94,0xae,0x14,0x73,0xca,0x60,0xca,0x70,0xca,0x68,0xca,0x78,0xca,0x54,0x8a,0x25, -0x65,0x29,0x65,0x35,0x05,0x9f,0xea,0x94,0xea,0x96,0xea,0x95,0xea,0x9b,0x4a,0x49, -0xa5,0xa7,0x86,0xa5,0x32,0x52,0xe3,0x53,0x39,0xa9,0xe9,0x10,0x5e,0xaa,0x28,0x55, -0x9a,0xaa,0x4c,0xd5,0xa5,0x9a,0x52,0x1b,0x52,0x5b,0x52,0x3b,0x52,0xbb,0x53,0xfb, -0x53,0x87,0x52,0x47,0x52,0xc7,0x52,0x27,0x53,0xa7,0x53,0xe7,0x53,0x97,0x52,0x57, -0x52,0x71,0x67,0xf0,0x67,0x9c,0xce,0xb8,0x9d,0xf1,0x3a,0xe3,0x7b,0x86,0x72,0x86, -0x7e,0x26,0xec,0x0c,0xe3,0x4c,0xfc,0x19,0xce,0x99,0xf4,0x33,0xbc,0x33,0xa2,0x33, -0xd2,0x33,0xca,0x33,0xba,0x33,0xa6,0x33,0x0d,0x67,0x5a,0xce,0x74,0x9c,0xe9,0x3e, -0xd3,0x7f,0x66,0xe8,0xcc,0xc8,0x99,0xb1,0x33,0x93,0x67,0xa6,0xcf,0xcc,0x9f,0x59, -0x3a,0xb3,0x72,0x06,0x77,0x16,0x7f,0xd6,0xe9,0xac,0xdb,0x59,0xaf,0xb3,0xbe,0x67, -0x29,0x67,0xe9,0x67,0xc3,0xce,0x32,0xce,0xc6,0xfd,0x09,0xc4,0x9f,0x4d,0x3d,0xcb, -0x3b,0x2b,0x39,0xab,0x3c,0x6b,0x3c,0xdb,0x70,0xb6,0xed,0x6c,0xf7,0xd9,0xc1,0xb3, -0x23,0x67,0xc7,0xcf,0x4e,0x9f,0x5d,0x38,0xbb,0x72,0xd6,0x36,0xcd,0x29,0xcd,0x23, -0xcd,0x37,0x8d,0x96,0x16,0x96,0xc6,0x4a,0xe3,0xa4,0x65,0xa6,0x89,0xd2,0xe4,0x69, -0xba,0xb4,0xda,0xb4,0x96,0xb4,0xae,0xb4,0xfe,0xb4,0xe1,0xb4,0xb1,0xb4,0xa9,0xb4, -0xf9,0xb4,0xe5,0x34,0x5c,0xba,0x43,0xba,0x5b,0x3a,0x31,0x9d,0x92,0x1e,0x92,0xce, -0x48,0x67,0xa7,0xa7,0xa7,0x0b,0xd2,0xa5,0xe9,0xea,0x74,0x53,0x7a,0x53,0x7a,0x47, -0xba,0x39,0x7d,0x28,0x7d,0x34,0x7d,0x32,0xdd,0x92,0xbe,0x94,0xbe,0x9a,0x8e,0xff, -0xc8,0xe5,0x23,0xaf,0x8f,0x48,0x1f,0xd1,0x3f,0x8a,0xf8,0x28,0x0e,0x12,0xff,0x51, -0xea,0x47,0xbc,0x8f,0x24,0x1f,0x29,0x3f,0x32,0x7e,0xd4,0xf0,0x51,0xdb,0x47,0xdd, -0x1f,0x0d,0x7e,0x34,0xf2,0xd1,0xf8,0x47,0xd3,0x1f,0x2d,0x7c,0xb4,0xf2,0x91,0xed, -0xc7,0x4e,0x1f,0x7b,0x7c,0xec,0xfb,0x31,0xed,0xe3,0xb0,0x8f,0x59,0x1f,0x73,0x3e, -0xce,0xfc,0x58,0xf4,0xb1,0xfc,0x63,0xdd,0xc7,0xb5,0x1f,0xb7,0x7c,0xdc,0xf5,0x71, -0xff,0xc7,0xc3,0x1f,0x8f,0x7d,0x3c,0xf5,0xf1,0xfc,0xc7,0xcb,0x1f,0xe3,0x32,0x1c, -0x32,0xdc,0x32,0x88,0x19,0x94,0x8c,0x90,0x0c,0x46,0x06,0x3b,0x23,0x3d,0x43,0x90, -0x21,0xcd,0x50,0x67,0x98,0x32,0x9a,0x32,0x3a,0x32,0xcc,0x19,0x43,0x19,0xa3,0x19, -0x13,0xbf,0x32,0x93,0x19,0x96,0x8c,0xa5,0x0c,0x5c,0xa6,0x53,0xa6,0x57,0x26,0x25, -0x33,0x2c,0x33,0x3e,0x33,0x3d,0x53,0x90,0x69,0xce,0x1c,0xcf,0x9c,0xf9,0x13,0xb1, -0x64,0xce,0x67,0x2e,0x64,0x2e,0x65,0x2e,0x67,0xae,0x64,0xae,0x66,0xe2,0xb2,0x6c, -0xb3,0xf0,0x59,0xce,0x7f,0x06,0x2e,0x59,0x1e,0x59,0x5e,0x59,0xc4,0x2c,0xdf,0x2c, -0x52,0x16,0x25,0x8b,0x96,0x45,0xcf,0x0a,0xc9,0x0a,0xcb,0x8a,0xc8,0x62,0x64,0xb1, -0xb2,0xe2,0xb3,0xd8,0x59,0x9c,0xac,0xd4,0xac,0xf4,0xac,0xcc,0x2c,0x5e,0x16,0x1f, -0x22,0xc8,0x12,0x65,0x49,0xb2,0xe4,0x59,0xea,0x2c,0x63,0x56,0x6d,0x56,0x43,0x56, -0x53,0x56,0x4b,0x56,0x5b,0x56,0x47,0x56,0x67,0x56,0x57,0x56,0x77,0x96,0x39,0xab, -0x3f,0x6b,0x30,0x6b,0x28,0x6b,0x38,0x6b,0x24,0x6b,0x34,0x6b,0x2c,0x6b,0x3c,0x6b, -0x32,0x6b,0x2a,0x6b,0x3a,0xcb,0x92,0x35,0x9f,0xb5,0x90,0xb5,0x94,0xb5,0x9c,0xf5, -0x32,0x6b,0x25,0x6b,0x35,0x0b,0x77,0xce,0xf6,0x9c,0x1d,0x06,0xfe,0x9c,0xd3,0x39, -0xb7,0x73,0x9e,0xe7,0xbc,0xce,0xf9,0x9c,0xf3,0x3d,0x47,0x39,0x47,0x3f,0x17,0x76, -0x8e,0x71,0x2e,0xfe,0x1c,0xe7,0x5c,0xfa,0x39,0xde,0x39,0xd1,0x39,0xe9,0x39,0xf9, -0x39,0xf5,0x39,0xe3,0xb9,0xda,0x73,0x4d,0xe7,0xda,0xce,0x75,0x9d,0x33,0x9f,0x1b, -0x3c,0x37,0x7c,0x6e,0xf4,0xdc,0xf8,0xb9,0xa9,0x73,0xd3,0xe7,0x2c,0xe7,0xe6,0xcf, -0x2d,0x9c,0x5b,0x3a,0xb7,0x7c,0x6e,0xe5,0xdc,0xea,0x39,0x1c,0xd7,0x96,0x8b,0xe7, -0x3a,0x70,0x9d,0xb8,0x2e,0x5c,0x37,0xae,0x07,0xd7,0x8b,0x4b,0xe4,0xfa,0x72,0x49, -0x5c,0x0a,0x97,0xc6,0xa5,0x73,0x43,0xb8,0x61,0xdc,0x08,0x2e,0x83,0xcb,0xe2,0xc6, -0x73,0xd9,0x5c,0x0e,0x37,0x95,0x9b,0xce,0xcd,0xe4,0xf2,0xb8,0x02,0xae,0x88,0x2b, -0xe1,0x4a,0xb9,0x72,0xae,0x92,0xab,0xe6,0xea,0xb8,0x46,0xae,0x89,0x5b,0xcb,0x6d, -0xe0,0x36,0x71,0x5b,0xb8,0x6d,0xdc,0x0e,0x6e,0x17,0xb7,0x9b,0x6b,0xe6,0xf6,0x73, -0x07,0xb9,0x43,0xdc,0x61,0xee,0x08,0x77,0x94,0x3b,0xc6,0x1d,0xe7,0x4e,0x72,0xa7, -0xb8,0xd3,0x5c,0x0b,0x77,0x9e,0xbb,0xc0,0x5d,0xe2,0x2e,0x73,0x57,0xb8,0xab,0x5c, -0x1c,0xcf,0x96,0x87,0xe7,0x39,0xf0,0x9c,0x78,0x2e,0x3c,0x37,0x9e,0x07,0xcf,0x8b, -0x47,0xe4,0xf9,0xf2,0x48,0x3c,0x0a,0x8f,0xc6,0xa3,0xf3,0x42,0x78,0x61,0xbc,0x08, -0x1e,0x83,0xc7,0xe2,0xc5,0xf3,0xd8,0x3c,0x0e,0x2f,0x95,0x97,0xce,0xcb,0xe4,0xf1, -0x78,0x02,0x9e,0x88,0x27,0xe1,0x49,0x79,0x72,0x9e,0x8e,0x57,0xcb,0x6b,0xe1,0x75, -0xf1,0xfa,0x79,0xc3,0xbc,0x51,0xde,0xe3,0x5f,0x18,0xe7,0x4d,0xf2,0xa6,0x78,0xd3, -0x3c,0x0b,0x6f,0x9e,0xb7,0xc0,0x5b,0xe2,0x2d,0xf3,0x56,0x78,0xab,0x3c,0x5c,0xb6, -0x6d,0x36,0x3e,0xdb,0x21,0xdb,0x29,0xdb,0x25,0xdb,0x2d,0xdb,0x23,0xdb,0x2b,0x9b, -0x98,0xed,0x9b,0x4d,0xca,0xa6,0x64,0xd3,0xb2,0xe9,0xd9,0x21,0xd9,0x61,0xd9,0x11, -0xd9,0x8c,0x6c,0x56,0x76,0x7c,0x36,0x3b,0x9b,0x93,0x9d,0x9a,0x9d,0x9e,0x9d,0x99, -0xcd,0xcb,0x16,0x64,0x8b,0xb2,0x25,0xd9,0xd2,0x6c,0x79,0xb6,0x32,0x5b,0x9d,0xad, -0xcb,0x36,0x66,0x9b,0xb2,0x6b,0xb3,0x1b,0xb2,0x9b,0xb2,0x5b,0xb2,0xdb,0xb2,0x3b, -0xb2,0xbb,0xb2,0xbb,0xb3,0xcd,0xd9,0xfd,0xd9,0x83,0xd9,0x43,0xd9,0xc3,0xd9,0x23, -0xd9,0xa3,0xd9,0x63,0xd9,0xe3,0xd9,0x93,0xd9,0x53,0xd9,0xd3,0xd9,0x96,0xec,0xf9, -0xec,0x85,0xec,0xa5,0xec,0xe5,0xec,0x95,0xec,0xd5,0x6c,0x5c,0x8e,0x6d,0x0e,0x3e, -0xc7,0x21,0xc7,0x29,0xc7,0x25,0xc7,0x2d,0xc7,0x23,0xc7,0x2b,0x87,0x98,0xe3,0x9b, -0x43,0xca,0xa1,0xe4,0xd0,0x72,0xe8,0x39,0x21,0x39,0x61,0x39,0x11,0x39,0x8c,0x1c, -0x56,0x4e,0x7c,0x0e,0x3b,0x87,0x93,0x93,0x9a,0x93,0x9e,0x93,0x99,0xc3,0xcb,0x11, -0xe4,0x88,0x72,0x24,0x39,0xd2,0x1c,0x79,0x8e,0x32,0x47,0x9d,0xa3,0xcb,0x31,0xe6, -0x98,0x72,0x6a,0x73,0x1a,0x72,0x9a,0x72,0x5a,0x72,0xda,0x72,0x3a,0x72,0xba,0x72, -0xba,0x73,0xcc,0x39,0xfd,0x39,0x83,0x39,0x43,0x39,0xc3,0x39,0x23,0x39,0xa3,0x39, -0x63,0x39,0xe3,0x39,0x93,0x39,0x53,0x39,0xd3,0x39,0x96,0x9c,0xf9,0x9c,0x85,0x9c, -0xa5,0x9c,0xe5,0x9c,0x95,0x9c,0xd5,0x1c,0x1c,0xdf,0x96,0x8f,0xe7,0x3b,0xf0,0x9d, -0xf8,0x2e,0x7c,0x37,0xbe,0x07,0xdf,0x8b,0x4f,0xe4,0xfb,0xf2,0x49,0x7c,0x0a,0x9f, -0xc6,0xa7,0xf3,0x43,0xf8,0x61,0xfc,0x08,0x3e,0x03,0xbe,0x28,0xc7,0xf3,0xd9,0x7c, -0x0e,0x3f,0x95,0x9f,0xce,0xcf,0xe4,0xf3,0xf8,0x02,0xbe,0x88,0x2f,0xe1,0x4b,0xf9, -0x72,0xbe,0x92,0xaf,0xe6,0xeb,0xf8,0x46,0xbe,0x89,0x5f,0xcb,0x6f,0xe0,0x37,0xf1, -0x5b,0xf8,0x6d,0xfc,0x0e,0x7e,0x17,0xbf,0x9b,0x6f,0xe6,0xf7,0xf3,0x07,0xf9,0x43, -0xfc,0x61,0xfe,0x08,0xff,0xa1,0x95,0x51,0xfe,0x18,0x7f,0x9c,0x3f,0xc9,0x9f,0xe2, -0x4f,0xf3,0x67,0x20,0x16,0xfe,0x3c,0x7f,0x81,0xbf,0xc4,0x5f,0xe6,0xaf,0xf0,0x5f, -0x41,0x56,0xf9,0x38,0x81,0xad,0x00,0x2f,0x70,0x10,0x38,0x09,0x9c,0x21,0x2e,0x02, -0x37,0x81,0x87,0xc0,0xd3,0x8a,0x97,0x80,0x28,0xf0,0x15,0x90,0x04,0x34,0x01,0x5d, -0x10,0x22,0x08,0x15,0x84,0x09,0x22,0x04,0x0c,0x01,0x4b,0x10,0x2f,0x60,0x0b,0x38, -0x82,0x94,0x75,0xfe,0x04,0x52,0x05,0xe9,0x82,0x4c,0x01,0x4f,0x20,0x10,0x08,0x05, -0x22,0x81,0x44,0x20,0x15,0xc8,0x05,0x4a,0x81,0x5a,0xa0,0x13,0x18,0x05,0x26,0x41, -0xad,0xa0,0x41,0xd0,0x24,0x68,0x11,0xb4,0x09,0x3a,0x04,0x5d,0x82,0x6e,0x81,0x59, -0xd0,0x2f,0x18,0x14,0x0c,0x09,0x86,0x05,0x23,0x82,0x51,0xc1,0x98,0x60,0x5c,0x30, -0x29,0x98,0x12,0x4c,0x0b,0x2c,0x82,0x79,0xc1,0x82,0x60,0x49,0xb0,0x2c,0x58,0x11, -0xac,0x0a,0x70,0xe7,0x6d,0xcf,0xe3,0xcf,0x3b,0x9c,0x77,0x3a,0xef,0x72,0xde,0xf5, -0xbc,0xdb,0x79,0x8f,0xf3,0x5e,0xe7,0x89,0xe7,0x7d,0xcf,0x93,0xce,0x53,0xce,0xd3, -0xce,0xd3,0xcf,0x07,0xaf,0xf3,0x4f,0x4e,0x38,0x84,0xf9,0x3f,0x92,0xb4,0x8e,0x95, -0x94,0xf3,0x19,0xeb,0xac,0xf3,0x17,0xc1,0x3f,0x2f,0x5e,0x67,0x9d,0x7f,0x61,0x64, -0xe7,0x55,0x10,0xc3,0x3a,0xff,0xa6,0xd4,0x9c,0x6f,0xfc,0x37,0xa6,0x75,0x9d,0x75, -0x7e,0x35,0x3a,0xcf,0xf7,0x9e,0xbf,0x77,0xfe,0xe1,0xf9,0x89,0xf3,0x33,0xe7,0x17, -0xff,0xa9,0x78,0x79,0x1e,0xe4,0xda,0xe7,0xba,0xe6,0x7a,0xe7,0x92,0xd7,0x59,0xe7, -0xff,0x22,0x14,0x08,0x2d,0x97,0x9e,0x1b,0x92,0x1b,0x96,0x1b,0x91,0xcb,0xc8,0x65, -0xe5,0xc6,0xe7,0xb2,0x73,0x39,0xb9,0xa9,0xb9,0xe9,0xb9,0x99,0xb9,0xbc,0x5c,0x41, -0xae,0x28,0x57,0x92,0x2b,0xcd,0x95,0xe7,0x2a,0x73,0xd5,0xb9,0xba,0x5c,0x63,0xae, -0x29,0xb7,0x36,0xb7,0x21,0xb7,0x29,0xb7,0x25,0xb7,0x2d,0xb7,0x23,0xb7,0x2b,0xb7, -0x3b,0xd7,0x9c,0xdb,0x9f,0x3b,0x98,0x3b,0x94,0x3b,0x9c,0x3b,0x92,0x3b,0x9a,0x3b, -0x96,0x3b,0x9e,0x3b,0x99,0x3b,0x95,0x3b,0x9d,0x6b,0xc9,0x9d,0xcf,0x5d,0xc8,0x5d, -0xca,0x5d,0xce,0x5d,0xc9,0x5d,0xcd,0xc5,0x09,0x6d,0x85,0x78,0xa1,0x83,0xd0,0x49, -0xe8,0x22,0x74,0x13,0x7a,0x08,0xbd,0x84,0x44,0xa1,0xaf,0x90,0x24,0xa4,0x08,0x69, -0x42,0xba,0x30,0x44,0x18,0x26,0x8c,0x10,0x32,0x84,0x2c,0x61,0xbc,0x90,0x2d,0xe4, -0x08,0x53,0x85,0xe9,0xc2,0x4c,0x21,0x4f,0x28,0x10,0x8a,0x84,0x12,0xa1,0x54,0x28, -0x17,0x2a,0x85,0x2a,0xa1,0x5a,0xa8,0x13,0x1a,0x85,0x26,0x61,0xad,0xb0,0x41,0xd8, -0x24,0x6c,0x11,0xb6,0x09,0x3b,0x84,0x5d,0xc2,0x6e,0xa1,0x59,0xd8,0x2f,0x1c,0x14, -0x0e,0x09,0x87,0x85,0x23,0xc2,0x51,0xe1,0x98,0x70,0x5c,0x38,0x29,0x9c,0x12,0x4e, -0x0b,0x2d,0xc2,0x79,0xe1,0x82,0x70,0x49,0xb8,0x2c,0x5c,0x11,0xae,0x0a,0x71,0x22, -0x5b,0x11,0x5e,0xe4,0x20,0x72,0x12,0xb9,0x88,0xdc,0x44,0x1e,0x22,0x2f,0x11,0x51, -0xe4,0x2b,0x22,0x89,0x28,0x22,0x9a,0x88,0x2e,0x0a,0x11,0x85,0x89,0x22,0x44,0x0c, -0x11,0x4b,0x14,0x2f,0x62,0x8b,0x38,0xa2,0x54,0x51,0xba,0x28,0x53,0xc4,0x13,0x09, -0x44,0x22,0x91,0x44,0x24,0x15,0xc9,0x45,0x4a,0x91,0x5a,0xa4,0x13,0x19,0x45,0x26, -0x51,0xad,0xa8,0x41,0xd4,0x24,0x6a,0x16,0xb5,0x88,0xda,0x44,0xed,0x90,0x0e,0x51, -0x27,0xa4,0x4b,0xd4,0x2d,0xea,0x85,0x98,0x45,0xfd,0xa2,0x41,0xd1,0x90,0xe8,0xbe, -0x68,0x58,0x34,0x22,0x1a,0x15,0x8d,0x89,0xc6,0x45,0x93,0xa2,0x29,0xd1,0xb4,0xc8, -0x22,0x9a,0x17,0x2d,0x88,0x96,0x44,0x2f,0x44,0xcb,0xa2,0x97,0xa2,0x15,0xd1,0xaa, -0x08,0x97,0x67,0x9b,0x87,0xcf,0x73,0xc8,0x73,0xca,0x73,0xce,0x73,0xc9,0x73,0xcb, -0xf3,0xc8,0xf3,0xca,0x23,0xe6,0xf9,0xe6,0x91,0xf2,0x28,0x79,0xb4,0x3c,0x7a,0x5e, -0x48,0x5e,0x58,0x5e,0x44,0x1e,0x23,0x8f,0x95,0x17,0x9f,0xc7,0xce,0xe3,0xe4,0xa5, -0xe6,0xa5,0xe7,0x65,0xe6,0xf1,0xf2,0x04,0x79,0xa2,0x3c,0x49,0x9e,0x34,0x4f,0x9e, -0xa7,0xcc,0x53,0xe7,0xe9,0xf2,0x8c,0x79,0xa6,0xbc,0xda,0xbc,0x86,0xbc,0xa6,0xbc, -0x96,0xbc,0xb6,0xbc,0x8e,0xbc,0xae,0xbc,0xee,0x3c,0x73,0x5e,0x7f,0xde,0x60,0xde, -0x50,0xde,0x70,0xde,0x48,0xde,0x68,0xde,0x58,0xde,0x78,0xde,0x64,0xde,0x54,0xde, -0x74,0x9e,0x25,0x6f,0x3e,0x6f,0x21,0x6f,0x29,0x6f,0x39,0x6f,0x25,0x6f,0x35,0x0f, -0x97,0x8f,0xcb,0xb7,0xcd,0xc7,0xe7,0x3b,0xe4,0x3b,0xe5,0xbb,0xe4,0xbb,0xe6,0xbb, -0xe5,0x7b,0xe4,0x7b,0xe5,0x13,0xf3,0x7d,0x20,0xbe,0xf9,0xa4,0x7c,0x4a,0x3e,0x2d, -0x9f,0x9e,0x1f,0x92,0x1f,0x96,0x1f,0x91,0x1f,0x99,0xcf,0xc8,0x67,0xe5,0xc7,0xe7, -0xb3,0xf3,0x39,0xf9,0xa9,0xf9,0xe9,0xf9,0x19,0xf9,0x99,0xf9,0xbc,0x7c,0x41,0xbe, -0x28,0x5f,0x92,0x2f,0xcd,0x97,0xe7,0x2b,0xf3,0xd5,0xf9,0xba,0x7c,0x63,0xbe,0x29, -0xbf,0x36,0xbf,0x21,0xbf,0x29,0xbf,0x25,0xbf,0x2d,0xbf,0x23,0xbf,0x2b,0xbf,0x3b, -0xdf,0x9c,0xdf,0x9f,0x3f,0x98,0x3f,0x94,0x3f,0x9c,0x3f,0x92,0x3f,0x9a,0x3f,0x96, -0xff,0x38,0x7f,0x3c,0x7f,0x32,0x7f,0x2a,0x7f,0x3a,0x7f,0x26,0xdf,0x92,0x3f,0x9f, -0xbf,0x90,0xbf,0x94,0xbf,0x9c,0xff,0x32,0x7f,0x25,0xff,0x95,0x95,0xd5,0x7c,0x9c, -0xd8,0x56,0x8c,0x17,0x3b,0x88,0x9d,0xc4,0x2e,0x62,0x57,0xb1,0x9b,0xd8,0x43,0xec, -0x25,0x26,0x8a,0x7d,0xc5,0x24,0x31,0x45,0x4c,0x13,0xd3,0xc5,0x21,0xe2,0x30,0x71, -0x84,0x98,0x21,0x66,0x89,0xe3,0xc5,0x6c,0x31,0x47,0x9c,0x2a,0x4e,0x17,0x67,0x8a, -0x79,0x62,0x81,0x58,0x24,0x96,0x88,0xa5,0x62,0xb9,0x58,0x29,0x56,0x8b,0x75,0x62, -0xa3,0xd8,0x24,0xae,0x15,0x37,0x88,0x9b,0xc4,0x2d,0xe2,0x36,0x71,0x87,0xb8,0x4b, -0xdc,0x2d,0x36,0x8b,0xfb,0xc5,0x83,0xe2,0x21,0xf1,0xb0,0x78,0x44,0x3c,0x2a,0x1e, -0x13,0x8f,0x8b,0x27,0xc5,0x53,0xe2,0x69,0xb1,0x45,0x3c,0x2f,0x5e,0x10,0x2f,0x89, -0x97,0xc5,0x2b,0xe2,0x55,0x31,0x4e,0x62,0x2b,0xc1,0x4b,0x1c,0x24,0x4e,0x12,0x17, -0x89,0x9b,0xc4,0x43,0xe2,0x25,0x21,0x4a,0x7c,0x25,0x24,0x09,0x45,0x42,0x93,0xd0, -0x25,0x21,0x92,0x30,0x49,0x84,0x84,0x21,0x61,0x49,0xe2,0x25,0x6c,0x09,0x47,0x92, -0x2a,0x49,0x97,0x64,0x4a,0x78,0x12,0x81,0x44,0x24,0x91,0x48,0xa4,0x12,0xb9,0x44, -0x29,0x51,0x4b,0x74,0x12,0xa3,0xc4,0x24,0xa9,0x95,0x34,0x48,0x9a,0x24,0x2d,0x92, -0x36,0x49,0x87,0xa4,0x4b,0xd2,0x2d,0x31,0x4b,0xfa,0x25,0x83,0x92,0x21,0xc9,0xb0, -0x64,0x44,0x32,0x2a,0x19,0x93,0x8c,0x4b,0x26,0x25,0x53,0x92,0x69,0x89,0x45,0x32, -0x2f,0x59,0x90,0x2c,0x49,0x96,0x25,0x2b,0x92,0x55,0x09,0xae,0xc0,0xb6,0x00,0x5f, -0xe0,0x50,0xe0,0x54,0xe0,0x52,0xe0,0x56,0xe0,0x51,0xe0,0x55,0x40,0x2c,0xf0,0x2d, -0x20,0x15,0x50,0x0a,0x68,0x05,0xf4,0x82,0x90,0x82,0xb0,0x82,0x88,0x02,0x46,0x01, -0xab,0x20,0xbe,0x80,0x5d,0xc0,0x29,0x48,0x2d,0x48,0x2f,0xc8,0x2c,0xe0,0x15,0x08, -0x0a,0x44,0x05,0x92,0x02,0x69,0x81,0xbc,0x40,0x59,0xa0,0x2e,0xd0,0x15,0x18,0x0b, -0x4c,0x05,0xb5,0x05,0x0d,0x05,0x4d,0x05,0x2d,0x05,0x6d,0x05,0x1d,0x05,0x5d,0x05, -0xdd,0x05,0xe6,0x82,0xfe,0x82,0xc1,0x82,0xa1,0x82,0xe1,0x82,0x91,0x82,0xd1,0x82, -0x31,0xc8,0x78,0xc1,0x64,0xc1,0x54,0xc1,0x74,0x81,0xa5,0x60,0xbe,0x60,0xa1,0x60, -0xa9,0x60,0xb9,0x60,0xa5,0x60,0xb5,0x00,0x57,0x68,0x5b,0x88,0x2f,0x74,0x28,0x74, -0x2a,0x74,0x29,0x74,0x2b,0xf4,0x28,0xf4,0x2a,0x24,0x16,0xfa,0x16,0x92,0x0a,0x29, -0x85,0xb4,0x42,0x7a,0x61,0x48,0x61,0x58,0x61,0x44,0x21,0xa3,0x90,0x55,0x18,0x5f, -0xc8,0x2e,0xe4,0x14,0xa6,0x16,0xa6,0x17,0x66,0x16,0xf2,0x0a,0x05,0x85,0xa2,0x42, -0x49,0xa1,0xb4,0x50,0x5e,0xa8,0x2c,0x54,0x17,0xea,0x0a,0x8d,0x85,0xa6,0xc2,0xda, -0xc2,0x86,0xc2,0xa6,0xc2,0x96,0xc2,0xb6,0xc2,0x8e,0xc2,0xae,0xc2,0xee,0x42,0x73, -0x61,0x7f,0xe1,0x60,0xe1,0x50,0xe1,0x70,0xe1,0x48,0xe1,0x68,0xe1,0x58,0xe1,0x78, -0xe1,0x64,0xe1,0x54,0xe1,0x74,0xa1,0xa5,0x70,0xbe,0x70,0xa1,0x70,0xa9,0x70,0xb9, -0x70,0xa5,0x70,0xb5,0x10,0x57,0x64,0x5b,0x84,0x2f,0x72,0x28,0x72,0x2a,0x72,0x29, -0x72,0x2b,0xf2,0x28,0xf2,0x2a,0x22,0x16,0xf9,0x16,0x91,0x8a,0x28,0x45,0xb4,0x22, -0x7a,0x51,0x48,0x51,0x58,0x51,0x44,0x11,0xa3,0x88,0x55,0x14,0x5f,0xc4,0x2e,0xe2, -0x14,0xa5,0x16,0xa5,0x17,0x65,0x16,0xf1,0x8a,0x04,0x45,0xa2,0x22,0x49,0x91,0xb4, -0x48,0x5e,0xa4,0x2c,0x52,0x17,0xe9,0x8a,0x8c,0x45,0xa6,0xa2,0xda,0xa2,0x86,0xa2, -0xa6,0xa2,0x96,0xa2,0xb6,0xa2,0x8e,0xa2,0xae,0xa2,0xee,0x22,0x73,0x51,0x7f,0xd1, -0x60,0xd1,0x50,0xd1,0x70,0xd1,0x48,0xd1,0x68,0xd1,0x58,0xd1,0x78,0xd1,0x64,0xd1, -0x54,0xd1,0x74,0x91,0xa5,0x68,0xbe,0x68,0xa1,0x68,0xa9,0x68,0xb9,0x68,0xa5,0x68, -0xb5,0x08,0x27,0xb5,0x95,0xe2,0xa5,0x0e,0x52,0x27,0xa9,0x8b,0xd4,0x4d,0xea,0x21, -0xf5,0x92,0x12,0xa5,0xbe,0x52,0x92,0x94,0x22,0xa5,0x49,0xe9,0xd2,0x10,0x69,0x98, -0x34,0x42,0xca,0x90,0xb2,0xa4,0xf1,0x52,0xb6,0x94,0x23,0x4d,0x95,0xa6,0x4b,0x33, -0xa5,0x5c,0x08,0x4f,0x2a,0x90,0x8a,0xa4,0x12,0xa9,0x54,0x2a,0x97,0x2a,0xa5,0x6a, -0xa9,0x4e,0x6a,0x94,0x9a,0xa4,0xb5,0xd2,0x06,0x69,0x93,0xb4,0x45,0xda,0x26,0xed, -0x90,0x76,0x49,0xbb,0xa5,0x66,0x69,0xbf,0x74,0x50,0x3a,0x24,0x1d,0x96,0x8e,0x48, -0x47,0xa5,0x63,0xd2,0x71,0xe9,0xa4,0x74,0x4a,0x3a,0x2d,0xb5,0x48,0xe7,0xa5,0x0b, -0xd2,0x25,0xe9,0xb2,0x74,0x45,0xba,0x2a,0xc5,0x15,0xdb,0x16,0xe3,0x8b,0x1d,0x8a, -0x9d,0x8a,0x5d,0x8a,0xdd,0x8a,0x3d,0x8a,0xbd,0x8a,0x89,0xc5,0xbe,0xc5,0xa4,0x62, -0x4a,0x31,0xad,0x98,0x5e,0x1c,0x52,0x1c,0x56,0x1c,0x51,0xcc,0x28,0x66,0x15,0xc7, -0x17,0xb3,0x8b,0x39,0xc5,0xa9,0xc5,0xe9,0xc5,0x99,0xc5,0xbc,0x62,0x41,0xb1,0xa8, -0x58,0x52,0x2c,0x2d,0x96,0x17,0x2b,0x8b,0xd5,0xc5,0xba,0x62,0x63,0xb1,0xa9,0xb8, -0xb6,0xb8,0xa1,0xb8,0xa9,0xb8,0xa5,0xb8,0xad,0xb8,0xa3,0xb8,0xab,0xb8,0xbb,0xd8, -0x5c,0xdc,0x5f,0x3c,0x58,0x3c,0x54,0x3c,0x5c,0x3c,0x02,0x19,0x2d,0x1e,0x2b,0x1e, -0x2f,0x9e,0x2c,0x9e,0x2a,0x9e,0x2e,0xb6,0x14,0xcf,0x17,0x2f,0x14,0x2f,0x15,0x2f, -0x17,0xaf,0x14,0xaf,0x16,0xe3,0x4a,0x6c,0x4b,0xf0,0x25,0x0e,0x25,0x4e,0x25,0x2e, -0x25,0x6e,0x25,0x1e,0x25,0x5e,0x25,0xc4,0x12,0xdf,0x12,0x52,0x09,0xa5,0x84,0x56, -0x42,0x2f,0x09,0x29,0x09,0x2b,0x89,0x28,0x61,0x94,0xb0,0x4a,0xe2,0x4b,0xd8,0x25, -0x9c,0x92,0xd4,0x92,0xf4,0x92,0xcc,0x12,0x5e,0x89,0xa0,0x44,0x54,0x22,0x29,0x91, -0x96,0xc8,0x4b,0x94,0x25,0xea,0x12,0x5d,0x89,0xb1,0xc4,0x54,0x52,0x5b,0xd2,0x50, -0xd2,0x54,0xd2,0x52,0xd2,0x56,0xd2,0x51,0xd2,0x55,0xd2,0x5d,0x62,0x2e,0xe9,0x2f, -0x19,0x2c,0x19,0x2a,0x19,0x2e,0x19,0x29,0x19,0x2d,0x19,0x2b,0x19,0x2f,0x99,0x2c, -0x99,0x2a,0x99,0x2e,0xb1,0x94,0xcc,0x97,0x2c,0x94,0x2c,0x95,0x2c,0x97,0xac,0x94, -0xac,0x96,0xe0,0x64,0xb6,0x32,0xbc,0xcc,0x41,0xe6,0x24,0x73,0x91,0xb9,0xc9,0x3c, -0x64,0x5e,0x32,0xa2,0xcc,0x57,0x46,0x92,0x51,0x64,0x34,0x19,0x5d,0x16,0x22,0x0b, -0x93,0x45,0xc8,0x18,0x32,0x96,0x2c,0x5e,0xc6,0x96,0x71,0x64,0xa9,0xb2,0x74,0x59, -0xa6,0x8c,0x27,0x13,0xc8,0x44,0x32,0x89,0x4c,0x2a,0x93,0xcb,0x94,0x32,0xb5,0x4c, -0x27,0x33,0xca,0x4c,0xb2,0x5a,0x59,0x83,0xac,0x49,0xd6,0x22,0x6b,0x93,0x75,0xc8, -0xba,0x64,0xdd,0x32,0xb3,0xac,0x5f,0x36,0x28,0x1b,0x92,0x0d,0xcb,0x46,0x64,0xa3, -0xb2,0x31,0xd9,0xb8,0x6c,0x52,0x36,0x25,0x9b,0x96,0x59,0x64,0xf3,0xb2,0x05,0xd9, -0x92,0x6c,0x59,0xb6,0x22,0x5b,0x95,0xe1,0xe4,0xb6,0x72,0xbc,0xdc,0x41,0xee,0x24, -0x77,0x91,0xbb,0xc9,0x3d,0xe4,0x5e,0x72,0xa2,0xdc,0x57,0x4e,0x92,0x53,0xe4,0x34, -0x39,0x5d,0x1e,0x22,0x0f,0x93,0x47,0xc8,0x19,0x72,0x96,0x3c,0x5e,0xce,0x96,0x73, -0xe4,0xa9,0xf2,0x74,0x79,0xa6,0x9c,0x27,0x17,0xc8,0x45,0x72,0x89,0x5c,0x2a,0x97, -0xcb,0x95,0x72,0xb5,0x5c,0x27,0x37,0xca,0x4d,0xf2,0x5a,0x79,0x83,0xbc,0x49,0xde, -0x22,0x6f,0x93,0x77,0xc8,0xbb,0xe4,0xdd,0x72,0xb3,0xbc,0x5f,0x3e,0x28,0x1f,0x92, -0x0f,0xcb,0x47,0xe4,0xa3,0xf2,0x31,0xf9,0xb8,0x7c,0x52,0x3e,0x25,0x9f,0x96,0x5b, -0xe4,0xf3,0xf2,0x05,0xf9,0x92,0x7c,0x59,0xfe,0x12,0xb2,0x22,0x5f,0x95,0xe3,0x4a, -0x6d,0x4b,0xf1,0xa5,0x0e,0xa5,0x4e,0xa5,0x2e,0xa5,0x6e,0xa5,0x1e,0xa5,0x5e,0xa5, -0xc4,0x52,0xdf,0x52,0x52,0x29,0xa5,0x94,0x56,0x4a,0x2f,0x0d,0x29,0x0d,0x2b,0x8d, -0x28,0x65,0x94,0xb2,0x4a,0xe3,0x4b,0xd9,0xa5,0x9c,0xd2,0xd4,0xd2,0xf4,0xd2,0xcc, -0x52,0x5e,0xa9,0xa0,0x54,0x54,0x2a,0x29,0x95,0x96,0xca,0x4b,0x95,0xa5,0xea,0x52, -0x5d,0xa9,0xb1,0xd4,0x54,0x5a,0x5b,0xda,0x50,0xda,0x54,0xda,0x52,0xda,0x56,0xda, -0x51,0xda,0x55,0xda,0x5d,0x6a,0x2e,0xed,0x2f,0x1d,0x2c,0xbd,0xf7,0x37,0x67,0xa8, -0x74,0xb8,0x74,0xa4,0x74,0xb4,0x74,0xac,0x74,0xbc,0x74,0xb2,0x74,0xaa,0x74,0xba, -0xd4,0x52,0x3a,0x5f,0xba,0x50,0xba,0x54,0xba,0x5c,0xba,0x52,0xba,0x5a,0x8a,0x2b, -0xb3,0x2d,0xc3,0x97,0x39,0x94,0x39,0x95,0xb9,0x94,0xb9,0x95,0x79,0x94,0x79,0x95, -0x11,0xcb,0x7c,0xcb,0x48,0x65,0x94,0x32,0x5a,0x19,0xbd,0x2c,0xa4,0x2c,0xac,0x2c, -0xa2,0x8c,0x51,0xc6,0x2a,0x8b,0x2f,0x63,0x97,0x71,0xca,0x52,0xcb,0xd2,0xcb,0x32, -0xcb,0x78,0x65,0x82,0x32,0x51,0x99,0xa4,0x4c,0x5a,0x26,0x2f,0x53,0x96,0xa9,0xcb, -0x74,0x65,0xc6,0x32,0x53,0x59,0x6d,0x59,0x43,0x59,0x53,0x59,0x4b,0x59,0x5b,0x59, -0x47,0x59,0x57,0x59,0x77,0x99,0xb9,0xac,0x6f,0x9d,0x75,0xd6,0xf9,0x23,0xf4,0x43, -0x06,0xcb,0x86,0xca,0x86,0xcb,0x1e,0x94,0x8d,0x94,0x8d,0x96,0x8d,0x95,0x8d,0x97, -0x4d,0x96,0x4d,0x95,0x4d,0x97,0x59,0xca,0xe6,0xcb,0x16,0xca,0x96,0xca,0x96,0xcb, -0x56,0xca,0x56,0xcb,0x70,0x0a,0x5b,0x05,0x5e,0xe1,0xa0,0x70,0x52,0xb8,0x28,0xdc, -0x14,0x1e,0x0a,0x2f,0x05,0x51,0xe1,0xab,0x20,0x29,0x28,0x0a,0xaa,0x82,0xa6,0xa0, -0x2b,0x82,0x15,0x21,0x8a,0x50,0x48,0x98,0x22,0x5c,0x11,0xa1,0x60,0x28,0x58,0x8a, -0x78,0x05,0x5b,0xc1,0x51,0xa4,0x2a,0xd2,0x15,0x99,0x0a,0x9e,0x82,0xaf,0x10,0x28, -0x44,0x0a,0x89,0x42,0xaa,0x90,0x29,0xe4,0x0a,0x85,0x42,0xa9,0x50,0xbd,0x81,0x5a, -0xa1,0xc5,0xd0,0x29,0x0c,0x0a,0xa3,0xa2,0x5a,0x61,0x52,0xd4,0x28,0x6a,0x15,0x0d, -0x8a,0x26,0x45,0xb3,0xa2,0x45,0xd1,0xa6,0x68,0x57,0x74,0x28,0x3a,0x21,0x5d,0x8a, -0x1b,0x8a,0x6e,0x45,0xaf,0xc2,0xac,0xe8,0x53,0xf4,0x2b,0x06,0x14,0x83,0x8a,0x7b, -0x8a,0x21,0xc5,0xb0,0xe2,0x81,0x62,0x44,0xf1,0x10,0x32,0xaa,0x18,0x53,0x8c,0x2b, -0x26,0x15,0x4f,0x14,0x53,0x8a,0x69,0x85,0x45,0x31,0xaf,0x58,0x50,0x2c,0x29,0x96, -0x15,0x2f,0x15,0x2b,0x8a,0x55,0x05,0x4e,0x69,0xab,0xb4,0x53,0xe2,0x95,0x0e,0x4a, -0x27,0xa5,0x8b,0xd2,0x55,0xe9,0xa6,0x74,0x57,0x7a,0x28,0xbd,0x94,0x44,0xa5,0xaf, -0x92,0xa4,0xa4,0x28,0x69,0x4a,0xba,0x32,0x44,0x19,0xa6,0x0c,0x57,0x46,0x28,0x19, -0x4a,0x96,0x32,0x5e,0xc9,0x56,0x72,0x94,0xa9,0xca,0x74,0x65,0xa6,0x92,0xa7,0x14, -0x28,0x45,0x4a,0x89,0x52,0xaa,0x94,0x2b,0x95,0x4a,0xb5,0x52,0xfb,0x0b,0x3a,0xa5, -0x51,0x69,0x52,0xd6,0x28,0x6b,0x95,0x0d,0xca,0x26,0x65,0x8b,0xb2,0x4d,0xd9,0xae, -0xec,0x50,0x76,0x29,0xbb,0x95,0x66,0x65,0xbf,0x72,0x50,0x39,0xa4,0x1c,0x56,0x8e, -0x28,0x47,0x95,0x63,0xca,0x71,0xe5,0xa4,0x72,0x4a,0x39,0xad,0xb4,0x28,0xe7,0x95, -0xcf,0xd7,0x59,0xe7,0x1f,0x82,0x05,0xc8,0xb2,0x72,0x55,0x69,0x5b,0xee,0x50,0xee, -0x52,0xee,0x51,0x4e,0x2c,0x27,0x95,0xd3,0xca,0x43,0xca,0xc3,0x7f,0x21,0xa2,0x3c, -0xbe,0x3c,0xb5,0x9c,0x57,0x2e,0x29,0x57,0x96,0x1b,0xcb,0x1b,0xca,0xdb,0xca,0xbb, -0xcb,0x07,0xcb,0x47,0xca,0xc7,0xcb,0xa7,0xcb,0x17,0xca,0x57,0xca,0x6d,0x2b,0x9c, -0x2a,0x3c,0x2a,0x7c,0x2b,0x68,0x15,0x61,0x15,0xac,0x0a,0x4e,0x45,0x66,0x85,0xa8, -0x42,0x5e,0xa1,0xab,0x30,0x56,0x98,0x2a,0x1a,0x2a,0x9a,0x2b,0x5a,0x2a,0xda,0x2a, -0x3a,0x2a,0xba,0x2a,0xba,0x2b,0xcc,0x15,0xfd,0x15,0x83,0x15,0x43,0x15,0xc3,0x15, -0x23,0x15,0xa3,0x15,0x63,0x15,0xe3,0x15,0x93,0x15,0x53,0x15,0xd3,0x15,0x96,0x8a, -0xf9,0x8a,0x85,0x8a,0xa5,0x8a,0xe5,0x8a,0x95,0x8a,0xd5,0x0a,0x9c,0xca,0x56,0x85, -0x57,0x39,0xa9,0xdc,0x54,0x5e,0x2a,0x5f,0x15,0x4d,0x15,0xfc,0x17,0x10,0xa2,0x8a, -0x50,0xb1,0x54,0x49,0xbf,0x0a,0x6c,0x55,0xca,0xbf,0x34,0xa9,0x90,0x4c,0x95,0x40, -0x25,0xfc,0x83,0x88,0x54,0x12,0x95,0x54,0x25,0x57,0xa9,0x55,0x3a,0x95,0x51,0x65, -0x52,0xd5,0xaa,0x1a,0x54,0x4d,0xaa,0x16,0x55,0x9b,0xaa,0x43,0xd5,0xa5,0xea,0x56, -0x99,0x55,0xfd,0xaa,0x41,0xd5,0x90,0x6a,0x58,0x35,0xa2,0x1a,0x55,0x8d,0xa9,0xc6, -0x55,0x93,0xaa,0x29,0xd5,0xb4,0xca,0xa2,0x9a,0x57,0x2d,0xa8,0x96,0x54,0xcb,0xaa, -0x15,0xd5,0xaa,0x0a,0xa7,0xb6,0x55,0xe3,0xd5,0x0e,0x6a,0x27,0xb5,0x8b,0xda,0x4d, -0xed,0xa1,0xf6,0x52,0x7b,0x63,0x10,0xd5,0x14,0x75,0x88,0x9a,0xa1,0x66,0xab,0xd3, -0xd5,0x02,0xb5,0x54,0xad,0x56,0x57,0xff,0x16,0x26,0x75,0xad,0xba,0x7e,0x9d,0x75, -0xd6,0xf9,0x0b,0x69,0x50,0x37,0xa9,0x5b,0xd4,0x6d,0xea,0x0e,0x75,0x97,0xba,0x5b, -0x6d,0x56,0xf7,0xab,0x07,0xd5,0xf7,0x7e,0x87,0xfb,0xea,0x07,0xea,0x87,0xea,0x47, -0xea,0xc7,0xea,0x09,0xf5,0x13,0xf5,0x53,0xf5,0x8c,0x7a,0x4e,0xfd,0x5c,0xbd,0xa8, -0x7e,0xa1,0x7e,0xa9,0x7e,0xa5,0x06,0x1a,0x1b,0x8d,0x9d,0xc6,0x5e,0x43,0xd0,0x38, -0x6b,0x5c,0x35,0xee,0x1a,0x4f,0x8d,0xb7,0xc6,0x47,0xe3,0xa7,0x21,0x6b,0xa8,0x9a, -0x40,0x4d,0xb0,0x26,0x54,0x13,0xae,0x89,0xd4,0x30,0x35,0x71,0x9a,0x24,0x4d,0xb2, -0x26,0x45,0x93,0xa6,0xc9,0xd0,0x70,0x35,0x7c,0x8d,0x50,0x23,0xd6,0x14,0x69,0x64, -0x1a,0x85,0x46,0xa5,0xd1,0x6a,0x0c,0x9a,0x6a,0x4d,0x8d,0xa6,0x5e,0xd3,0xa8,0x69, -0xd6,0xb4,0x6a,0xda,0x35,0x9d,0x9a,0x1b,0x9a,0x5e,0x4d,0x9f,0x66,0x40,0x73,0x4f, -0x73,0x5f,0xf3,0x40,0xf3,0x50,0xf3,0x48,0xf3,0x58,0x33,0xa1,0x79,0xa2,0x79,0xaa, -0x99,0xd1,0xcc,0x69,0x9e,0x6b,0x16,0x35,0x2f,0x34,0x2f,0x35,0xaf,0x34,0xa0,0xd2, -0xa6,0xd2,0xae,0xd2,0xbe,0x92,0x50,0xe9,0x5c,0xe9,0x5a,0xe9,0x5e,0xe9,0x59,0xe9, -0x5d,0xe9,0x53,0xe9,0x57,0x49,0xae,0xa4,0x56,0x06,0x56,0x06,0x57,0x86,0x56,0x86, -0x57,0x46,0x56,0x32,0x2b,0xe3,0x2a,0x93,0x2a,0x93,0x2b,0x53,0x2a,0xd3,0x2a,0x33, -0x2a,0xb9,0x95,0xfc,0x4a,0x61,0xa5,0xb8,0xb2,0xa8,0x52,0x56,0xa9,0xa8,0x54,0x55, -0x6a,0x2b,0x0d,0x95,0xd5,0x95,0x35,0x95,0xf5,0x95,0x8d,0x95,0xcd,0x95,0xad,0x95, -0xed,0x95,0x9d,0x95,0x37,0x2a,0x7b,0x2b,0xfb,0x2a,0x07,0x2a,0xef,0x55,0xde,0xaf, -0x7c,0x50,0xf9,0xb0,0xf2,0x51,0xe5,0xe3,0xca,0x89,0xca,0x27,0x95,0x4f,0x2b,0x67, -0x2a,0xe7,0x2a,0x9f,0x57,0x2e,0x56,0xbe,0xa8,0x7c,0x59,0xf9,0xaa,0x12,0x68,0x6d, -0xb4,0x76,0x5a,0x7b,0x2d,0x41,0xeb,0xac,0x75,0xd5,0xba,0x6b,0x3d,0xb5,0xde,0x5a, -0x1f,0xad,0x9f,0x96,0xac,0xa5,0x6a,0x03,0xb5,0xc1,0xda,0x50,0x6d,0xb8,0x36,0x52, -0xcb,0xd4,0xc6,0x69,0x93,0xb4,0xc9,0xda,0x14,0x6d,0x9a,0x36,0x43,0xcb,0xd5,0xf2, -0xb5,0x42,0xad,0x58,0x5b,0xa4,0x95,0xc1,0xd7,0x5b,0x95,0x56,0xab,0x35,0x68,0xab, -0xb5,0x35,0xda,0x7a,0x6d,0xa3,0xb6,0x59,0xdb,0xaa,0x6d,0xd7,0x76,0x6a,0x6f,0x68, -0x7b,0xb5,0x7d,0xda,0x01,0xed,0x3d,0xed,0x7d,0xed,0x03,0xed,0x43,0xed,0x23,0xed, -0x63,0xed,0x84,0xf6,0x89,0xf6,0xa9,0x76,0x46,0x3b,0xa7,0x7d,0xae,0x5d,0xd4,0xbe, -0xd0,0xbe,0xd4,0xbe,0xd2,0x02,0x9d,0x8d,0xce,0x4e,0x67,0xaf,0x23,0xe8,0x9c,0x75, -0xae,0x3a,0x77,0x9d,0xa7,0xce,0x5b,0xe7,0xa3,0xf3,0xd3,0x91,0x75,0x54,0x5d,0xa0, -0x2e,0x58,0x17,0xaa,0x0b,0xd7,0x45,0xea,0x98,0xba,0x38,0x5d,0x92,0x2e,0x59,0x97, -0xa2,0x4b,0xd3,0x65,0xe8,0xb8,0x3a,0xbe,0x4e,0xa8,0x13,0xeb,0x8a,0x74,0x32,0x9d, -0x42,0xa7,0xd2,0x69,0x75,0x06,0x5d,0xb5,0xae,0x46,0x57,0xaf,0x6b,0xd4,0x35,0xeb, -0x5a,0x75,0xed,0xba,0x4e,0xdd,0x0d,0x5d,0xaf,0xae,0x4f,0x37,0xa0,0xbb,0x07,0xb9, -0xaf,0x7b,0xa0,0x7b,0xa8,0x7b,0xa4,0x7b,0xac,0x9b,0xd0,0x3d,0xd1,0x3d,0xd5,0xcd, -0xe8,0xe6,0x74,0xcf,0x75,0x8b,0xba,0x17,0xba,0x97,0xba,0x57,0x3a,0xa0,0xb7,0xd1, -0xdb,0xe9,0xed,0xf5,0x04,0xbd,0xb3,0xde,0x55,0xef,0xae,0xf7,0xd4,0x7b,0xeb,0x7d, -0xf4,0x7e,0x7a,0xb2,0x9e,0xaa,0x0f,0xd4,0x07,0xeb,0x43,0xf5,0xe1,0xfa,0x48,0x3d, -0x53,0x1f,0xa7,0x4f,0xd2,0x27,0xeb,0x53,0xf4,0x69,0xfa,0x0c,0x3d,0x57,0xcf,0xd7, -0x0b,0xf5,0x62,0x7d,0x91,0x5e,0xa6,0x57,0xe8,0x55,0x7a,0xad,0xde,0xa0,0xaf,0xd6, -0xd7,0xe8,0xeb,0xf5,0x8d,0xfa,0x66,0x7d,0xab,0xbe,0x5d,0xdf,0xa9,0xbf,0xa1,0xef, -0xd5,0xf7,0xe9,0x07,0xf4,0xf7,0xf4,0xf7,0xf5,0x0f,0xf4,0x0f,0xf5,0x8f,0xf4,0x8f, -0xf5,0x13,0xfa,0x27,0xfa,0xa7,0xfa,0x19,0xfd,0x9c,0xfe,0xb9,0x7e,0x51,0xff,0x42, -0xff,0x52,0xff,0x4a,0x0f,0xaa,0x6c,0xaa,0xec,0xaa,0xec,0xab,0x08,0x55,0xce,0x55, -0xae,0x55,0xee,0x55,0x9e,0x55,0xde,0x55,0x3e,0x55,0x7e,0x55,0xe4,0x2a,0x6a,0x55, -0x60,0x55,0x70,0x55,0x68,0x55,0x78,0x55,0x64,0x15,0xb3,0x2a,0xae,0x2a,0xa9,0x2a, -0xb9,0x2a,0xa5,0x2a,0xad,0x2a,0xa3,0x8a,0x5b,0xc5,0xaf,0x12,0x56,0x89,0xab,0x8a, -0xaa,0x64,0x55,0x8a,0x2a,0x55,0x95,0xb6,0xca,0x50,0x55,0x5d,0x55,0x53,0x55,0x5f, -0xd5,0x58,0xd5,0x5c,0xd5,0x5a,0xd5,0x5e,0xd5,0x59,0x75,0xa3,0xaa,0xb7,0xaa,0xaf, -0x6a,0xa0,0xea,0x5e,0xd5,0xfd,0xaa,0x07,0x55,0x0f,0xab,0x1e,0x55,0x3d,0xae,0x9a, -0xa8,0x7a,0x52,0xf5,0xb4,0x6a,0xa6,0x6a,0xae,0xea,0x79,0xd5,0x62,0xd5,0x8b,0xaa, -0x97,0x55,0xaf,0xaa,0x80,0xc1,0xc6,0x60,0x67,0xb0,0x37,0x10,0x0c,0xce,0x06,0x57, -0x83,0xbb,0xc1,0xd3,0xe0,0x6d,0xf0,0x31,0xf8,0x19,0xc8,0x06,0xaa,0x21,0xd0,0x10, -0x6c,0x08,0x35,0x84,0x1b,0x22,0x0d,0x4c,0x43,0x9c,0x21,0xc9,0x90,0x6c,0x48,0x31, -0xa4,0x19,0x32,0x0c,0x5c,0x03,0xdf,0x20,0x34,0x88,0x0d,0x45,0x06,0x99,0x41,0x61, -0x50,0x19,0xb4,0x06,0x83,0xa1,0xda,0x50,0x63,0xa8,0x37,0x34,0x1a,0x9a,0x0d,0xad, -0x86,0x76,0x43,0xa7,0xe1,0x86,0xa1,0xd7,0xd0,0x67,0x18,0x30,0xdc,0x33,0xdc,0x37, -0x3c,0x30,0x3c,0x34,0x3c,0x32,0x3c,0x36,0x4c,0x18,0x9e,0x18,0x9e,0x1a,0x66,0x0c, -0x73,0x86,0xe7,0x86,0x45,0xc3,0x0b,0xc3,0x4b,0xc3,0x2b,0x03,0x30,0xda,0x18,0xed, -0x8c,0xf6,0x46,0x82,0xd1,0xd9,0xe8,0x6a,0x74,0x37,0x7a,0x1a,0xbd,0x8d,0x3e,0x46, -0x3f,0x23,0xd9,0x48,0x35,0x06,0x1a,0x83,0x8d,0xa1,0xc6,0x70,0x63,0xa4,0x91,0x69, -0x8c,0x33,0x26,0x19,0x93,0x8d,0x29,0xc6,0x34,0x63,0x86,0x91,0x6b,0xe4,0x1b,0x85, -0x46,0xb1,0xb1,0xc8,0x28,0x33,0x2a,0x8c,0x2a,0xa3,0xd6,0x68,0x30,0x56,0x1b,0x6b, -0x8c,0xf5,0xc6,0x46,0x63,0xb3,0xb1,0xd5,0xd8,0x6e,0xec,0x34,0xde,0x30,0xf6,0x1a, -0xfb,0x8c,0x03,0xc6,0x7b,0x90,0xfb,0xc6,0x07,0xc6,0x87,0xc6,0x47,0xc6,0xc7,0xc6, -0x09,0xe3,0x13,0xe3,0x53,0xe3,0x8c,0x71,0xce,0xf8,0xdc,0xb8,0x68,0x7c,0x61,0x7c, -0x69,0x7c,0x65,0x04,0x17,0x6c,0x2e,0xd8,0x5d,0xb0,0xbf,0x40,0xb8,0xe0,0x7c,0xc1, -0xf5,0x82,0xfb,0x05,0xcf,0x0b,0xde,0x17,0x7c,0x2e,0xf8,0x5d,0x20,0x5f,0xa0,0x5e, -0x08,0x5c,0xe7,0x5f,0x80,0xb5,0xff,0xf7,0xac,0x27,0x96,0xec,0x36,0x02,0x90,0x8e, -0x1d,0xdb,0xc0,0xe3,0x77,0x61,0x7e,0x08,0x2b,0xe3,0x60,0x79,0x60,0xe3,0xda,0xf1, -0x01,0x98,0x53,0x60,0x9e,0x0b,0xd6,0xf2,0x70,0x58,0x49,0x7d,0xe3,0xf8,0xf5,0x79, -0xaf,0xeb,0xc3,0xd0,0x4d,0x36,0xae,0xe5,0x38,0x2c,0xdf,0x80,0xe5,0x1b,0xb1,0xdc, -0x0e,0xcb,0xb7,0xc0,0xfc,0x28,0x76,0x8f,0x08,0xec,0xbc,0x08,0xec,0xbc,0x08,0xec, -0xbc,0x08,0xac,0x3f,0x03,0x6b,0x67,0x60,0xed,0x0c,0xac,0x9d,0x81,0xb5,0xc7,0x60, -0xd7,0x8f,0xc5,0xfa,0xc5,0x62,0xfd,0x62,0xb1,0x7e,0xb1,0x6f,0xb4,0xa3,0xfe,0x6c, -0xac,0x1f,0x1b,0xeb,0xc7,0xc6,0xfa,0xb1,0xb1,0x76,0x0e,0x56,0x9f,0x8e,0xf5,0x4b, -0x7f,0xa3,0xbc,0x01,0xcb,0x37,0x62,0xf9,0x6b,0x5b,0xa2,0xf9,0x7c,0x8c,0xcd,0x27, -0x13,0x3b,0x2f,0x13,0x3b,0x2f,0x13,0x3b,0x2f,0x13,0xeb,0xcf,0xc3,0xda,0x79,0x58, -0x3b,0x0f,0x6b,0xe7,0x61,0xed,0xe7,0xb1,0xeb,0xe7,0x62,0xfd,0x72,0xb1,0x7e,0xb9, -0x58,0xbf,0xdc,0x37,0xda,0x51,0x7f,0x09,0xd6,0x4f,0x82,0xf5,0x93,0x60,0xfd,0x24, -0x58,0xbb,0x14,0xab,0x97,0xbe,0x61,0x7f,0x1b,0x6c,0xdc,0x36,0x58,0x79,0x13,0x56, -0xde,0x84,0x95,0x7d,0xb0,0xb2,0x0f,0xe6,0x27,0x1c,0x36,0x3f,0x1c,0x56,0xde,0x80, -0x95,0x37,0x60,0xe5,0xcd,0x58,0x79,0x33,0x56,0xb6,0xc7,0xca,0x28,0x0f,0xc7,0xf2, -0x0c,0x2c,0x8f,0xc0,0xee,0x9b,0x89,0xe5,0x11,0xd8,0x7d,0x33,0xb1,0x3c,0x02,0xbb, -0x4e,0x26,0x96,0x47,0x60,0xe3,0xc8,0xc4,0xf2,0x08,0xec,0x3a,0x99,0x58,0x7e,0x1c, -0x1b,0xc7,0x39,0x2c,0x3f,0x8e,0x5d,0xe7,0x1c,0x96,0x1f,0xc7,0xae,0x73,0x0e,0xcb, -0x8f,0x63,0x7e,0x3a,0x87,0xe5,0x91,0xd8,0x79,0xdc,0x37,0xe2,0x6a,0x23,0xe6,0x8f, -0x8d,0x58,0xd9,0x06,0x2b,0xdb,0x60,0xe5,0x4d,0x58,0x79,0x13,0x56,0xf6,0xc1,0xca, -0x3e,0x58,0x79,0x33,0x96,0x47,0x81,0xb5,0xfa,0x6c,0xec,0x18,0x5d,0x3f,0x1b,0xcb, -0xa3,0xb1,0xfb,0xe7,0x60,0x39,0x13,0xb3,0x2f,0x1f,0xcb,0x99,0x58,0x3d,0xff,0x8d, -0x76,0x7b,0xac,0x6c,0x8f,0x95,0x6f,0x83,0xb5,0x32,0xca,0x63,0xb0,0xf3,0xce,0x63, -0x79,0x0c,0x76,0xde,0x79,0x2c,0x8f,0xc1,0xce,0x3b,0x8f,0xe5,0x03,0x1b,0xd6,0x8e, -0x63,0xb1,0x79,0xe5,0x62,0x79,0x2c,0x36,0xaf,0x5c,0x2c,0x47,0xe5,0xad,0x58,0x19, -0xe5,0x09,0xd8,0xf5,0xf3,0xb0,0x3c,0x01,0xbb,0x7e,0x1e,0x96,0x27,0x60,0xd7,0xcf, -0xc3,0xf2,0x44,0xac,0x5f,0x3e,0x96,0x27,0x62,0xf3,0xcf,0xc7,0xf2,0x44,0xec,0xbc, -0x7c,0x2c,0x4f,0xc4,0xce,0xcb,0xc7,0xf2,0x24,0xac,0x5e,0x8c,0xe5,0x49,0x58,0xbd, -0x18,0xcb,0xd9,0x98,0x9f,0x24,0x58,0xce,0xc6,0xe6,0x21,0xc1,0x72,0x36,0x36,0x0f, -0x09,0x96,0xb3,0xb1,0xf5,0x2a,0xc1,0x72,0x36,0x36,0x2f,0x09,0x96,0xb3,0x31,0x3f, -0x4a,0xb0,0xfc,0x24,0x36,0xce,0x42,0x2c,0xe7,0x60,0xb9,0xf4,0x8d,0x32,0x5a,0x57, -0xa7,0xb0,0xf9,0x15,0x63,0xf9,0x29,0x2c,0x0e,0x8a,0xb1,0xfc,0x14,0x36,0xde,0xe2, -0x37,0xe6,0x87,0x6c,0xbb,0x1b,0xb3,0xed,0x6e,0xec,0xde,0xbb,0xb1,0x7b,0xef,0xc6, -0xd6,0xce,0xa9,0x37,0xd6,0x51,0xf1,0x1b,0x6b,0xa9,0xf8,0x8d,0x38,0x40,0xb1,0xc5, -0xc4,0xe2,0x8c,0x8f,0xe5,0x31,0x58,0x7d,0x0c,0x56,0x3e,0x8f,0xe5,0x61,0xd8,0x79, -0xe9,0x58,0xce,0xc0,0x72,0x1e,0x96,0xc7,0x62,0x79,0xee,0x1b,0xf6,0xb5,0xc7,0xc6, -0x64,0xff,0xc6,0x3e,0x69,0xf3,0xc6,0x1e,0x63,0xf3,0x46,0x3d,0xee,0x8d,0x7a,0xdc, -0x1b,0xf5,0xf6,0x6f,0xd4,0xbf,0x79,0x1d,0xf0,0x46,0x3d,0x78,0xe3,0x39,0x61,0xf3, -0xc6,0x1e,0xfb,0x7a,0x9f,0xda,0xfc,0x46,0xfd,0xeb,0xe3,0xff,0x85,0xf9,0x79,0x16, -0xcb,0x8f,0x63,0xe3,0x3c,0x87,0xe5,0xd1,0x58,0x9e,0xf3,0xc6,0xfc,0x7c,0xb0,0xf9, -0xf9,0xbc,0x51,0xb6,0x79,0xa3,0x0e,0x1d,0xdf,0xc6,0xad,0xf5,0xff,0x74,0xc3,0x5a, -0x9e,0xfd,0x86,0x1f,0x90,0x4f,0x5e,0xfb,0xe3,0xb5,0x2f,0x8e,0x63,0x7e,0x3f,0xf7, -0xc6,0xfa,0x03,0xd8,0x7a,0x03,0x6f,0x3c,0xf7,0x70,0x6f,0x3c,0x33,0x70,0xd8,0xf8, -0x71,0xd8,0xf8,0x51,0x3e,0x81,0xe5,0x2f,0xff,0x3f,0xf6,0xce,0x04,0x40,0xc7,0xe2, -0xf1,0xe3,0xcf,0xfb,0xee,0xbe,0xbb,0xd6,0x7d,0x76,0x29,0xd7,0x16,0xc2,0xae,0xbd, -0x59,0xe7,0x5a,0xd6,0x99,0x90,0x90,0x54,0xae,0x90,0x5c,0xeb,0x08,0x95,0xca,0x91, -0xd2,0x49,0x87,0x4a,0x27,0x9d,0xa4,0x72,0x25,0x54,0xce,0x0a,0x91,0x12,0x15,0x65, -0x8b,0x90,0x44,0x42,0xba,0x48,0xf5,0xff,0xcc,0x3e,0xdf,0x27,0xe3,0xf9,0xdb,0x1f, -0xaa,0x5f,0xbf,0x8e,0xf7,0x19,0x1f,0xdf,0x99,0x79,0xe6,0x99,0x67,0x66,0x9e,0x99, -0x79,0x9e,0xf7,0x99,0x79,0x66,0xad,0xfb,0x68,0x11,0x1d,0x53,0x44,0xee,0x62,0x72, -0x17,0x53,0x7f,0x58,0x44,0xfd,0x61,0x11,0xb9,0x8b,0xc9,0x5d,0x4c,0xd7,0xb7,0x88, -0xae,0x6f,0x11,0xb9,0x8b,0xc9,0x5d,0x4c,0xf9,0x2f,0xa2,0xbc,0x17,0x91,0xbb,0x98, -0xdc,0xc5,0xd4,0x9e,0x8b,0xa8,0x3d,0x17,0x91,0xbb,0x98,0xdc,0xc5,0x74,0x1d,0x8b, -0xe8,0x1a,0x16,0x91,0xbb,0x98,0xdc,0xc5,0xd4,0x9e,0x2b,0xaa,0xbe,0x57,0x54,0xfb, -0xad,0xa8,0xf6,0x5b,0x51,0xfd,0x6f,0x41,0xf5,0xbf,0x05,0xad,0xeb,0xdc,0xd5,0xea, -0xff,0xbd,0xfb,0x6a,0x25,0xeb,0x3e,0x1e,0x69,0xdd,0x03,0x23,0xad,0xfb,0x7c,0xa4, -0x75,0x8f,0xf4,0xfc,0xa3,0xe5,0x17,0x6d,0xb9,0x23,0x2d,0xbf,0x48,0xb5,0xe3,0x48, -0xb5,0xeb,0x48,0xa5,0xa7,0x67,0xd0,0xbd,0xee,0x26,0xaf,0x57,0x63,0xbf,0x16,0x46, -0x05,0xdd,0xbe,0xe0,0x6a,0x3d,0x33,0x45,0xe9,0x79,0x29,0x5a,0x9a,0x5f,0x5a,0x59, -0x1a,0x21,0x35,0x7d,0x4b,0xf7,0xa0,0xdb,0x3e,0x4d,0x59,0x0c,0x87,0x89,0x41,0xb7, -0x6e,0x98,0xeb,0x5c,0x22,0xe2,0x48,0x9b,0x59,0x10,0x74,0x8f,0xc9,0xc2,0x5e,0xdb, -0x7a,0x36,0x9b,0x2a,0xbd,0x4b,0xe1,0x4c,0xbf,0x3f,0x51,0xf6,0xfb,0xa5,0x93,0xa4, -0x8f,0x49,0x9f,0x92,0x4e,0xf3,0xe2,0xb6,0xce,0x33,0x49,0xf6,0xa7,0xa4,0xb3,0xe4, -0x3f,0xd7,0x8b,0xdf,0x3a,0xc6,0xe8,0x1b,0x76,0x1a,0x65,0xf7,0xfc,0x16,0x5b,0x61, -0x8c,0xbe,0x29,0x9d,0x0d,0xf3,0x75,0x8e,0x75,0xf2,0x5b,0xa7,0x63,0x96,0xc3,0x12, -0x78,0x19,0x96,0xc2,0x32,0x78,0x40,0xe7,0x7f,0x02,0x4a,0x45,0xba,0xe5,0x63,0xd4, -0x84,0x2f,0x11,0xe9,0x1e,0x1f,0x25,0xf7,0x19,0x72,0x9f,0xa6,0x70,0xb1,0x91,0xee, -0xf5,0x38,0x4d,0x5a,0x53,0x9a,0xaa,0xfd,0xa9,0x3a,0x2e,0x59,0xc7,0xb5,0x97,0xbb, -0x96,0xdc,0x35,0x15,0xae,0xa1,0x8e,0x1b,0x12,0xa9,0xba,0x2d,0x3d,0x45,0xfe,0x69, -0xd2,0xa2,0xd2,0x04,0xcb,0x9d,0x4f,0xee,0x7c,0x4a,0xb7,0x77,0x7e,0xa3,0x1b,0xe5, -0xff,0xa1,0xf4,0x14,0x69,0x9a,0xf4,0x54,0x69,0x0d,0xa9,0xc9,0x47,0xa4,0xd2,0x15, -0x29,0x77,0x3e,0xb9,0x8d,0x96,0x91,0xd6,0x93,0xee,0x94,0x7e,0x21,0x8d,0x93,0xb6, -0x94,0xc6,0x2a,0x9e,0x86,0xd2,0x58,0xf9,0x37,0xb4,0xf6,0x17,0x90,0xdb,0x68,0x25, -0xf9,0x37,0x91,0x56,0x95,0xb6,0x90,0xf6,0x08,0x39,0xce,0x68,0xa8,0x14,0x45,0x3b, -0x8f,0x72,0xf5,0x42,0x68,0x2a,0x35,0xee,0x76,0xd0,0xdc,0x72,0x0f,0xd1,0x7e,0xa3, -0x2b,0xa4,0xcd,0xa5,0xef,0x2b,0xdc,0x52,0xe9,0x3a,0x69,0xe5,0x18,0xc7,0xa9,0x03, -0x09,0xd2,0x64,0x69,0x29,0xe9,0x29,0xd2,0x53,0xa5,0x67,0x4a,0xcb,0x4b,0x2b,0x48, -0xab,0x4a,0xab,0x4b,0x57,0xc0,0x22,0xe9,0x06,0x98,0x02,0xaf,0xc2,0xe3,0xd2,0x19, -0xd2,0xa4,0xfc,0x84,0x87,0x9a,0xd2,0x53,0xa4,0xa7,0x4a,0xcf,0x94,0x56,0x95,0x36, -0x29,0x40,0xdd,0x2a,0xe0,0x6a,0x3d,0x69,0x07,0x28,0x0f,0x75,0xa0,0x82,0xf4,0x6e, -0xd8,0x00,0xcb,0x61,0x11,0xac,0x90,0x2e,0x97,0x7f,0xe3,0x82,0xc4,0x03,0x8b,0xe1, -0x7d,0x58,0x6e,0xe9,0x07,0xd2,0x65,0x96,0x9a,0x7d,0x8d,0x0b,0x71,0x4e,0x68,0x22, -0x35,0xee,0x0e,0xf0,0x21,0xac,0x92,0xbe,0x63,0xa9,0xe7,0xb7,0x19,0x5a,0x16,0x26, -0xaf,0xb0,0x06,0x66,0xc3,0x74,0x98,0x24,0x9d,0x02,0x05,0x8a,0x38,0x4e,0x26,0xcc, -0x83,0xf3,0xa4,0x6d,0xa4,0xed,0xa5,0x9d,0xa4,0x19,0x30,0x17,0x06,0xc2,0x20,0xe9, -0x10,0x98,0x0d,0x23,0xa5,0x03,0x65,0x9f,0x63,0xa9,0xe7,0xe7,0xe9,0xdd,0x8a,0xef, -0x61,0xe9,0x14,0xe9,0xd3,0xd2,0xe9,0xd2,0x09,0x3a,0xdf,0x39,0x45,0x1d,0x27,0x1e, -0x72,0x20,0x54,0x9a,0xb6,0x07,0xd1,0xd2,0x18,0x69,0x01,0x69,0x21,0x69,0x31,0x69, -0x2d,0x69,0x1d,0x69,0x3d,0x69,0x7d,0x69,0xa6,0xd4,0xdc,0xa3,0xcc,0x7d,0x3b,0x53, -0xcf,0x01,0xe6,0x1e,0x75,0x67,0xc0,0x7d,0x06,0x30,0xf7,0x33,0xef,0xd9,0xbf,0x85, -0x9e,0xcb,0x5a,0xea,0x39,0xcc,0xdc,0x7b,0x2a,0xd0,0xb7,0xb7,0x76,0xdc,0xfb,0xa8, -0xb9,0x17,0x9a,0xfb,0xa5,0x79,0xc6,0x34,0xf7,0xbb,0xd6,0xec,0x6b,0x03,0xc1,0xb3, -0x1c,0xa7,0x9b,0x9e,0x2d,0xcc,0x7d,0xaf,0x23,0x7e,0x9d,0xe0,0x92,0xa0,0xfb,0x7c, -0x61,0x9e,0x65,0xfa,0x99,0xf8,0x03,0xee,0x3d,0xec,0x42,0xfc,0x4f,0xe1,0x98,0x53, -0x21,0xdb,0x71,0xef,0xad,0xe6,0xfe,0x7b,0x16,0xee,0x01,0xec,0x1b,0x8a,0xfd,0x9c, -0xb3,0xdc,0xbe,0x78,0x0e,0xbc,0xa8,0xbe,0x77,0x85,0x9e,0x03,0x06,0x29,0xbc,0x09, -0xe7,0x85,0x59,0x6a,0x85,0xa9,0x4b,0x7b,0xbf,0x20,0xe8,0xfe,0xa6,0x6b,0x87,0xee, -0x73,0xdc,0xb4,0xf4,0x44,0x3b,0xa3,0x5d,0xa1,0x07,0x5c,0x01,0xbd,0xcd,0x3d,0xd3, -0xdc,0x2b,0x39,0xdf,0x23,0x68,0x3f,0x18,0x8b,0xfd,0x31,0x74,0x20,0x64,0xc3,0x20, -0x18,0x0c,0x43,0xe0,0x2a,0x18,0x0e,0xa3,0x61,0x0c,0x4c,0x27,0x4f,0xb7,0xa0,0xb7, -0xc2,0x99,0x1c,0x77,0x1b,0x7a,0x7b,0xd0,0x7d,0xc6,0x9a,0x80,0xde,0x15,0x74,0x9f, -0xc9,0xe6,0xeb,0x59,0xe1,0x1c,0x3d,0x2b,0x18,0xcd,0xd4,0x7d,0xb8,0x9b,0xd4,0xb8, -0x63,0xe5,0x8e,0x95,0x3b,0x51,0xee,0x44,0xeb,0x1d,0x44,0xc0,0xfa,0xfd,0x1e,0xd0, -0x33,0x5e,0xb4,0x9e,0xb3,0xa3,0xe5,0x8e,0x95,0x3b,0x56,0xee,0x44,0xb9,0x13,0xe5, -0xae,0x24,0x77,0x25,0xb9,0xe3,0xe4,0x8e,0xb3,0x7e,0xf3,0x3a,0xd6,0xef,0x5e,0xc7, -0xf2,0x0f,0x58,0xfe,0xde,0x3b,0x90,0x38,0xf9,0x79,0xc7,0x27,0xc8,0x9d,0x60,0x3d, -0x13,0x45,0x59,0xcf,0x45,0xc6,0xde,0x58,0xe9,0xed,0x69,0xfd,0xd6,0x8d,0xd4,0x33, -0x69,0xa4,0x9e,0xb5,0xa2,0xf5,0x6c,0x13,0x2d,0x77,0xac,0xdc,0xb1,0x72,0xe7,0x93, -0x3b,0x9f,0xdc,0x95,0xe4,0xf6,0x7e,0x2b,0xc7,0xcb,0x1d,0xaf,0x67,0xc9,0x04,0xd5, -0xa1,0x04,0xeb,0x9d,0x4c,0xc0,0x7a,0x9f,0x11,0xd0,0x33,0x78,0x40,0xcf,0xe0,0x9e, -0x3b,0x56,0xee,0x58,0xb9,0x13,0xe5,0x4e,0xd4,0x6f,0x9a,0x58,0xfd,0xd6,0x8d,0xb5, -0xdc,0x91,0x96,0x5f,0xa4,0xfc,0x13,0xe5,0xe7,0x1d,0x17,0x27,0xb7,0xd1,0x96,0x3a, -0x5f,0x3f,0x69,0x4b,0xe5,0xbb,0x9f,0xb4,0xa5,0xe2,0xea,0x27,0x3d,0x5f,0xfe,0xfd, -0xa5,0xe7,0xcb,0xbf,0xbf,0xb5,0x3f,0x51,0xee,0x44,0xb9,0xe3,0xe4,0x8e,0xb3,0x9e, -0x41,0x03,0xd6,0x33,0x68,0xc0,0xf2,0xcf,0x67,0xf9,0xe7,0x8b,0x38,0xf2,0xdb,0xdb, -0xb1,0x7e,0x7f,0x3b,0x96,0x7f,0xc0,0xf2,0x37,0xf6,0xd6,0xca,0x47,0xb6,0x73,0xc4, -0x1d,0x2d,0x77,0xb4,0x9e,0xcd,0xa3,0xf5,0x6c,0xee,0xb9,0x63,0xe5,0x8e,0xb5,0xdc, -0x91,0x96,0x5f,0xa4,0xfc,0x13,0xe5,0x97,0xa8,0x67,0xf6,0x68,0x3d,0xb3,0x47,0xcb, -0x1d,0x2b,0x77,0xac,0xf5,0x9b,0x3e,0xda,0xfa,0x5d,0x1f,0x6d,0xfd,0x76,0x8f,0xb6, -0x7e,0xbf,0xdb,0xc7,0x47,0x5b,0x71,0x44,0xeb,0xb7,0x40,0xb4,0x7e,0x0b,0x78,0xee, -0x58,0xb9,0x63,0xe5,0x4e,0x94,0x3b,0x51,0xee,0x38,0xb9,0xe3,0xf4,0x5b,0xe3,0x6c, -0xfd,0xd6,0x38,0x5b,0xee,0x04,0xb9,0x13,0xe4,0x8e,0x93,0x3b,0xce,0x7a,0x57,0x10, -0xb0,0xde,0x17,0x04,0xac,0x77,0x06,0xf9,0xac,0xf7,0x06,0xc6,0xde,0x5e,0x61,0x86, -0x4a,0xdb,0x2b,0x5d,0x43,0xa5,0x1d,0xf4,0xdb,0x6e,0x98,0xb4,0x83,0xca,0x62,0x98, -0xb4,0x83,0xda,0xc0,0x30,0x69,0x07,0xe5,0x73,0x98,0xb4,0x83,0xe2,0x19,0x26,0xbd, -0x48,0xfe,0xc3,0xa5,0x17,0xe9,0xb8,0xe1,0xd6,0xbb,0xc9,0x68,0xfd,0x46,0xf1,0xde, -0x29,0x04,0xf5,0x4e,0x21,0x28,0x77,0xac,0xdc,0xb1,0x72,0x27,0xca,0x9d,0xa8,0x76, -0xeb,0x95,0xa7,0x97,0xae,0xfc,0x8a,0x2f,0xbf,0xfa,0xd1,0x45,0xf4,0xad,0x37,0x04, -0xdc,0xf8,0x1b,0x28,0x9e,0xae,0x52,0xe3,0x8e,0x91,0x3b,0xc6,0x7a,0xc7,0x6b,0xbf, -0x1f,0xb5,0xdf,0xfd,0x3a,0x96,0xbf,0x63,0xf9,0xc7,0x58,0xfe,0x76,0x3c,0x11,0x96, -0x7f,0x84,0x75,0xfe,0xa0,0x95,0x86,0xa0,0xf5,0xce,0x32,0x60,0xbd,0xb7,0x0c,0x58, -0xfe,0x8e,0xe5,0xef,0x58,0xfe,0x31,0x96,0x7f,0x8c,0xe5,0x1f,0x61,0xf9,0xdb,0xe7, -0x8d,0xb2,0xce,0xeb,0xbd,0x9b,0x8c,0x55,0x9f,0x1b,0x2b,0x77,0x8c,0xdc,0x31,0x72, -0x47,0xc8,0x1d,0x61,0xbd,0xcb,0xb6,0xdf,0x03,0xdb,0xef,0xb8,0x1d,0xcb,0xdf,0xb1, -0xfc,0x63,0x2c,0xff,0x18,0xcb,0x3f,0xc2,0xf2,0x8f,0xb0,0xd2,0x13,0xb4,0xd2,0xe4, -0xbd,0xc3,0x8c,0x51,0x1f,0x1c,0x23,0x77,0xac,0xdc,0xb1,0xea,0x5f,0x62,0xd5,0xb7, -0x78,0xee,0x18,0xb9,0x63,0xac,0x77,0xe7,0xf6,0x7b,0x67,0xfb,0x9d,0xba,0x63,0xf9, -0x3b,0x96,0x7f,0x8c,0xe5,0x6f,0xc7,0x13,0x61,0xf9,0x47,0x58,0xe7,0x0f,0x5a,0x69, -0xf0,0xde,0xd5,0x97,0xb6,0xce,0x5b,0xda,0x3a,0x6f,0x69,0xeb,0xbc,0xa5,0xad,0xf3, -0x96,0xb6,0xce,0x5b,0xda,0x3a,0x6f,0x69,0xeb,0xbc,0xa5,0xad,0xf3,0x1a,0x7b,0xac, -0xe5,0x1f,0xab,0xf6,0x1f,0xab,0xb6,0xef,0xb9,0x63,0xe4,0x8e,0xb1,0xde,0xcb,0x05, -0xac,0x77,0x73,0x01,0xcb,0xdf,0xb1,0xfc,0x1d,0xcb,0x3f,0xc6,0xf2,0xb7,0xe3,0x89, -0xb0,0xfc,0x23,0x2c,0xff,0x58,0xcb,0x3f,0x56,0xed,0xdd,0x51,0xfb,0x74,0xe4,0x8e, -0x95,0xdb,0xdb,0x1f,0x23,0x77,0x8c,0xdc,0x11,0x72,0x47,0xe8,0xf7,0x7c,0x09,0x69, -0x49,0xcb,0xed,0x58,0x7e,0x8e,0xe5,0x1f,0xb0,0xfc,0x03,0x96,0x7f,0xa6,0xe5,0x9f, -0xa9,0xf7,0x0f,0x25,0xa4,0x25,0x2d,0xb7,0x63,0xf9,0x39,0x96,0x7f,0xc0,0xf2,0x0f, -0x58,0xfe,0x99,0x96,0x7f,0xa6,0x7e,0xf7,0x97,0x90,0x96,0xb4,0xdc,0x8e,0xe5,0xe7, -0x58,0xfe,0x01,0xcb,0xdf,0xd8,0x27,0xca,0x7f,0xa2,0xfc,0x26,0x5a,0xc7,0x4f,0xb4, -0x8e,0x9f,0x68,0x1d,0x3f,0xd1,0x3a,0x7e,0x9e,0xfc,0xe7,0xc9,0x6f,0x9e,0x75,0xfc, -0x3c,0xeb,0xf8,0x79,0xd6,0xf1,0xf3,0x8e,0x71,0x7c,0xa6,0xe5,0x9f,0xa9,0x77,0x33, -0x25,0xa4,0x25,0x2d,0xb7,0x63,0xf9,0x39,0x96,0x7f,0xc0,0xf2,0x0f,0x58,0xfe,0x99, -0x96,0x7f,0xa6,0xde,0xc1,0x94,0x90,0x96,0xb4,0xdc,0x8e,0xe5,0xe7,0x58,0xfe,0x01, -0xcb,0x3f,0x60,0xf9,0x67,0x5a,0xfe,0x99,0x7a,0x2f,0x54,0x42,0x5a,0xd2,0x72,0x3b, -0x96,0x9f,0x63,0xf9,0x07,0x2c,0xff,0x80,0xe5,0x9f,0x69,0xf9,0x67,0xea,0x1d,0x51, -0x09,0x69,0x49,0xcb,0xed,0x58,0x7e,0x8e,0xe5,0x1f,0xb0,0xfc,0xbd,0x77,0x5a,0x25, -0xa4,0x25,0x2d,0xb7,0x63,0xf9,0x39,0x96,0x7f,0xc0,0xf2,0xf7,0xde,0x4d,0x95,0x90, -0x96,0xb4,0xdc,0x8e,0xe5,0xe7,0x58,0xfe,0x01,0xcb,0xdf,0x3e,0x3e,0xd3,0xf2,0xcf, -0xd4,0xbb,0xad,0x92,0x96,0x3a,0x96,0x3d,0x60,0xd9,0x33,0xf5,0x5e,0xac,0x84,0xb4, -0xa4,0xe5,0x76,0x2c,0x3f,0xc7,0xf2,0x0f,0x58,0xfe,0x01,0xcb,0x3f,0xd3,0xf2,0xcf, -0xd4,0xfb,0xbd,0x12,0xd2,0x92,0x96,0xdb,0xb1,0xfc,0x1c,0xcb,0x3f,0x60,0xf9,0x07, -0x2c,0xff,0x4c,0xcb,0xdf,0x6b,0xf7,0x8e,0xef,0xdd,0xa0,0xe3,0x7b,0x47,0xe8,0xf8, -0xde,0x15,0x3a,0xd6,0x3b,0xc3,0xc5,0xd6,0x35,0xf5,0xca,0xd0,0xf1,0xbd,0x27,0x74, -0xac,0xf7,0x85,0x5e,0x9f,0x93,0x65,0xf5,0x39,0x59,0xbe,0xbe,0x2b,0xcb,0xd7,0x7f, -0x65,0xf9,0xfa,0xb0,0x2c,0x5f,0x3f,0x96,0xe5,0xeb,0xcb,0xb2,0x7c,0xfd,0x59,0x96, -0xd5,0x27,0x65,0x59,0x7d,0x52,0x96,0xaf,0x6f,0xcb,0xf2,0xf5,0x6f,0x59,0xbe,0x3e, -0x2e,0xcb,0xd7,0xcf,0x65,0xf9,0xfa,0xba,0x2c,0x5f,0x7f,0x97,0x65,0xf5,0x19,0x59, -0x56,0x9f,0x91,0xe5,0xeb,0x7b,0xb2,0x7c,0xfd,0x4f,0x96,0xaf,0x0f,0xca,0xf2,0xf5, -0x43,0x59,0xbe,0xbe,0x28,0xcb,0xd7,0x1f,0x65,0x59,0x7d,0x4a,0x96,0xd5,0xa7,0x64, -0xf9,0xfa,0xa6,0x2c,0x5f,0xff,0x94,0xe5,0xeb,0xa3,0xb2,0x7c,0xfd,0x54,0x96,0xaf, -0xaf,0xca,0xf2,0xf5,0x57,0x59,0x56,0xfd,0xcd,0xb2,0xea,0x6f,0x96,0xaf,0x1d,0x64, -0xf9,0xda,0x42,0x96,0xaf,0x3d,0x64,0xf9,0xda,0x44,0x96,0xaf,0x5d,0x64,0xf9,0xda, -0x46,0x96,0x55,0xbf,0xb3,0xac,0xfa,0x9d,0xe5,0x6b,0x27,0x59,0xbe,0xb6,0x92,0xe5, -0x6b,0x2f,0x59,0xbe,0x36,0x93,0xe5,0x6b,0x37,0x59,0xbe,0xb6,0xe3,0xd5,0xb3,0x28, -0x69,0xa4,0xd5,0x9e,0xbc,0x7d,0x59,0x56,0xdb,0xf2,0xec,0x99,0x96,0x7a,0x75,0x26, -0x4a,0x1a,0x29,0x75,0xac,0x71,0x80,0xbb,0x14,0xce,0x8c,0x13,0x78,0xf7,0x03,0xcf, -0x6e,0x34,0x53,0x63,0x07,0x99,0x56,0x7b,0xf5,0xea,0x44,0x96,0xd5,0x76,0x3d,0x7b, -0xa6,0xa5,0x59,0xba,0x47,0x7a,0xf7,0x4d,0xef,0x7e,0xe4,0x58,0x63,0x0e,0xf7,0x2b, -0xdc,0xe2,0xb2,0x6a,0xeb,0x65,0xd5,0xf6,0xcb,0x1e,0xb9,0xb7,0x44,0x49,0x23,0xad, -0xb1,0x03,0xc7,0x37,0x1e,0xb1,0xc0,0xba,0x17,0xe5,0xb3,0xee,0x1f,0x51,0xd2,0x48, -0xa9,0x63,0x8d,0x73,0xfc,0xac,0x73,0xfe,0x5c,0xf6,0x88,0xdb,0xeb,0x9f,0xa3,0xa4, -0x91,0xd6,0x38,0x85,0xe3,0x1b,0xc7,0x58,0xaa,0xeb,0xb7,0xd4,0xba,0x27,0x64,0x5a, -0x61,0xbc,0xfe,0x3d,0x4a,0x1a,0x29,0x75,0xac,0xb1,0x95,0x29,0x3a,0x76,0xaa,0x9e, -0xd3,0xbc,0x71,0x9a,0x2e,0xce,0x91,0xfe,0xcd,0xab,0x93,0x59,0x56,0x5f,0xe7,0xd9, -0x33,0x2d,0xcd,0xd2,0x3d,0xcb,0xb1,0xc6,0x70,0xa6,0x59,0x7d,0xb7,0xe7,0x36,0xe1, -0x5e,0xd4,0xd8,0x90,0x39,0x77,0x10,0x4b,0x44,0xb9,0x23,0xf3,0x7c,0xfe,0x13,0x45, -0x15,0x2e,0x99,0xe3,0xe2,0x9d,0x63,0x63,0xf6,0x27,0x95,0xfb,0xff,0xa4,0x1e,0x83, -0xf2,0x8e,0x8b,0x39,0x26,0x44,0x9c,0xf5,0x9d,0x23,0x78,0xfb,0xea,0x1f,0x23,0x3e, -0x13,0x3e,0x41,0x73,0x24,0xcc,0x1c,0xa2,0x54,0x48,0x83,0x1a,0x50,0x13,0xd2,0xa1, -0x2a,0x14,0xaf,0xe0,0x38,0x75,0xcd,0x98,0x1a,0x9c,0xab,0xb1,0xec,0x04,0xcd,0x45, -0xf2,0xe6,0x21,0x9d,0xcc,0xf1,0x5d,0xf5,0x5e,0x75,0x80,0xc6,0xe1,0xcc,0xfb,0xd5, -0x2b,0xf4,0x5e,0xb5,0xaf,0xde,0xad,0x9a,0x73,0x64,0x6b,0xac,0x6e,0x88,0xde,0xd7, -0x0e,0xd6,0xb1,0xd5,0x1d,0xf7,0x7d,0xa1,0x67,0x37,0xfe,0xe6,0x5d,0xe2,0x4c,0x69, -0x77,0xf9,0x0f,0xb0,0xec,0xe6,0xb9,0x7e,0x42,0xc0,0x0d,0x63,0xde,0xd3,0xf5,0xd2, -0xbb,0x62,0x8f,0xdc,0x77,0x6c,0x01,0xf7,0xb7,0x5a,0x33,0xbd,0x37,0xee,0xab,0xf7, -0xc6,0xe7,0x2b,0x2e,0xf3,0xbe,0xa7,0x8d,0xd2,0xe2,0xd1,0x56,0xef,0x97,0x2f,0xd4, -0x7b,0xe8,0xf3,0x64,0x6f,0xa9,0x71,0xea,0x69,0x7a,0x0f,0xd0,0xc2,0x39,0x32,0x06, -0x9d,0xa9,0x74,0xf6,0xd0,0x31,0x8d,0x15,0xde,0x9c,0x63,0x6d,0xc8,0x71,0xde,0x85, -0x75,0xb0,0x3e,0xe4,0x5e,0x9b,0xc6,0x3a,0xd6,0xbc,0x8f,0x58,0xa2,0xf7,0xc3,0xf7, -0xc0,0x64,0x28,0x56,0xc1,0x7d,0xdf,0xe9,0xbd,0xab,0xf6,0xe6,0xbb,0x78,0xf3,0xc4, -0x6a,0x58,0xf6,0x74,0xcb,0x9e,0x68,0x5d,0x43,0xe3,0x4e,0xd6,0xb5,0xf4,0xec,0x9e, -0x7f,0xaa,0xe5,0x9f,0x6a,0xcd,0x39,0x4b,0xd5,0x35,0xf7,0xec,0x5e,0xf8,0x34,0xd5, -0x03,0xcf,0xee,0xf9,0xd7,0xb4,0x8e,0xad,0x69,0x85,0xa9,0xa9,0xfa,0xe2,0xd9,0xbd, -0xf0,0xde,0x75,0x38,0x16,0xed,0x2d,0x9a,0x59,0xea,0xb7,0x7b,0x5c,0x64,0xd1,0xcc, -0x52,0xef,0x3a,0x37,0xd4,0x38,0x42,0x4b,0x95,0x5f,0x5e,0x0c,0xb5,0xe8,0x6d,0xa9, -0xdf,0xee,0x31,0xdc,0xa2,0xb7,0xa5,0xbd,0x55,0xb7,0xba,0xeb,0xba,0xf5,0xd3,0xb5, -0xf0,0xca,0x7e,0x02,0x0d,0xb6,0xa6,0x99,0xfb,0x20,0xbd,0x57,0xba,0x56,0xfa,0x9e, -0x74,0x9d,0x34,0xa2,0x82,0xab,0xf9,0xa4,0x05,0xa4,0xb1,0xd2,0x73,0xa4,0x55,0x2b, -0xfc,0x7f,0xe2,0x8f,0x41,0x1d,0x85,0x6f,0x28,0xcd,0x92,0x36,0x95,0x9a,0xb6,0x6c, -0xb4,0xab,0xdc,0x2d,0xa5,0x75,0xe4,0x5f,0x4f,0x7a,0xb9,0xfc,0x7b,0x48,0x07,0x49, -0x07,0x4b,0x87,0x4a,0x87,0x49,0xaf,0x91,0x5e,0x2b,0x1d,0x2d,0x1d,0x23,0xbd,0x49, -0x7a,0xb3,0xf4,0x71,0xe9,0x54,0xe9,0x34,0xe9,0x74,0xe9,0x08,0xe9,0x75,0xd2,0xbb, -0xa4,0x77,0x4b,0x67,0x4b,0xe7,0x48,0x5f,0x94,0xce,0xf5,0xca,0x93,0x8b,0x12,0x93, -0x70,0x62,0xfd,0x9c,0xdd,0x9e,0x12,0xad,0xbe,0x31,0xd1,0x39,0xd2,0x9e,0xbc,0xf9, -0x9b,0x5e,0x7b,0xf1,0xda,0x87,0xd7,0x46,0xbd,0xfa,0x9f,0xae,0x63,0x13,0xd4,0x67, -0x26,0xaa,0xdf,0xac,0x2c,0x7f,0xcf,0x9e,0x6c,0xd9,0x53,0x2c,0x7b,0xaa,0x65,0x4f, -0xb3,0xec,0x35,0x2c,0x7b,0x4d,0xcb,0x9e,0x6e,0xd9,0xbd,0x7c,0xd8,0xee,0x44,0x9f, -0x3b,0xc9,0xe7,0x4e,0xf6,0xb9,0x53,0x7c,0xee,0x54,0x9f,0x3b,0xcd,0xe7,0xae,0xe1, -0x73,0xd7,0xf4,0xb9,0xd3,0x7d,0xf9,0xf7,0xd2,0x97,0xa8,0x7b,0x65,0x92,0x34,0x59, -0x9a,0x22,0x4d,0x95,0xa6,0x49,0x6b,0x48,0x6b,0x4a,0xd3,0xa5,0x5e,0x9e,0xe3,0xad, -0xfc,0xc6,0x5b,0x79,0x8d,0xb7,0xf2,0x19,0x6f,0xe5,0x31,0xde,0xca,0x5f,0xbc,0x95, -0xb7,0x78,0x2b,0x5f,0xf1,0x56,0x9e,0xe2,0xad,0xfc,0xc4,0x5b,0x79,0x89,0x57,0xde, -0xba,0x5a,0xf9,0xec,0x66,0xd9,0xbb,0x5b,0xf6,0xcb,0x2d,0x7b,0x0f,0xcb,0xde,0xd3, -0xb2,0xf7,0xb2,0xec,0x57,0x58,0xf6,0xde,0x96,0xfd,0x4a,0xcb,0xde,0xc7,0xb2,0xf7, -0xb5,0xec,0xfd,0x2c,0x7b,0x7f,0xcb,0x3e,0xc0,0xb2,0x67,0x5b,0xf6,0x81,0x96,0x7d, -0x90,0x65,0x1f,0x6c,0xd9,0x87,0x58,0xf6,0xab,0x2c,0xfb,0x50,0xcb,0x3e,0xcc,0xb2, -0x0f,0xb7,0xec,0x57,0x5b,0xf6,0x6b,0x64,0x6f,0xe0,0x1c,0xb9,0x9f,0x36,0xb2,0xee, -0xa7,0x27,0x32,0x16,0x6c,0xdf,0xcf,0xdb,0x5a,0xe3,0xc1,0xed,0x35,0x26,0x6c,0xee, -0x13,0x1d,0x75,0x1f,0xef,0xaa,0x6b,0xd2,0xdd,0xba,0xd7,0xf6,0x54,0x59,0x5f,0x61, -0xdd,0x77,0xfd,0xcf,0x30,0x03,0x54,0x46,0x03,0x55,0x26,0x83,0xad,0xf1,0xe1,0xa1, -0xca,0xeb,0x70,0xe5,0xed,0x1a,0xd5,0x07,0x7f,0x1f,0x5d,0xcb,0x71,0xa9,0x9b,0x07, -0x1f,0x57,0x71,0xfb,0xab,0x2b,0x95,0xf6,0xae,0x71,0x8e,0xb3,0x86,0x84,0x3c,0xf6, -0x98,0x9b,0xd9,0xca,0x90,0x06,0xf5,0xa1,0x23,0x8c,0x85,0xdb,0x61,0x32,0xbc,0x6c, -0xfc,0x29,0x84,0x1e,0xd0,0x07,0x46,0xc3,0x7d,0x30,0x1d,0xb6,0xc0,0xb7,0x2a,0xa0, -0x87,0xe0,0x20,0x94,0xa2,0xa0,0xce,0x80,0x9a,0xd0,0x00,0x2e,0x81,0xde,0x30,0x1a, -0x5e,0x82,0x95,0xb0,0x0d,0x36,0x90,0x90,0xb2,0x14,0xe2,0x6e,0x0a,0x31,0x48,0x21, -0xe6,0x87,0x52,0x50,0x09,0x06,0x43,0xeb,0x4e,0x8e,0x33,0x12,0x7e,0x80,0x22,0x97, -0x38,0x4e,0x05,0xa8,0x09,0xfd,0x61,0x20,0x6c,0xba,0xd4,0x71,0x3e,0x83,0xfd,0xf0, -0x33,0x0c,0xba,0x8c,0xf2,0x81,0x1b,0xe0,0x1b,0xf8,0x19,0x0a,0x74,0xe6,0xb7,0x03, -0xb4,0x86,0xae,0x30,0x18,0x5e,0x83,0x7c,0x5c,0xa8,0x34,0x68,0x01,0xd5,0xb9,0x50, -0x29,0x70,0x33,0xdc,0x0f,0x4f,0xc2,0x02,0xd8,0x02,0x9f,0xc1,0x3e,0x2e,0x62,0x3e, -0x2e,0x62,0x65,0x28,0x47,0x99,0x75,0x83,0xab,0x61,0x0e,0xac,0x86,0xf7,0x60,0x13, -0x14,0xe1,0xa2,0x96,0x84,0x14,0xe8,0xc3,0x05,0xad,0xc2,0xc5,0x4c,0x83,0xda,0x50, -0x1f,0x9a,0x40,0x47,0xe8,0x04,0xcf,0xc1,0x8d,0x5c,0xe8,0x5b,0x20,0x87,0x8b,0xbd, -0x03,0x4e,0xe3,0x82,0x97,0x85,0xca,0x90,0x00,0xb7,0xc1,0xdd,0x30,0x94,0x4a,0x70, -0x1d,0xbc,0x00,0x8b,0xe1,0x2b,0xd8,0x44,0xa5,0xd8,0x0a,0x07,0xa0,0x16,0x15,0x63, -0x3e,0x2c,0x82,0x21,0x54,0x90,0xd5,0x70,0x10,0x06,0x8f,0xa0,0xec,0x20,0xed,0x06, -0xea,0x30,0xdc,0x0a,0x0b,0x21,0x30,0xd2,0x71,0x0a,0x42,0x71,0x48,0x86,0x1b,0xe0, -0x16,0xd8,0x36,0xca,0x71,0x76,0xc1,0x01,0x38,0x0c,0x05,0x47,0x3b,0x4e,0x69,0x28, -0x0b,0xfd,0x61,0x10,0x0c,0x87,0x96,0x37,0x91,0x3f,0xc8,0x18,0x47,0x7b,0x81,0xee, -0x70,0xfd,0x2d,0xa4,0x17,0xd6,0x81,0x73,0x2b,0xfd,0xda,0xed,0xb4,0x31,0x68,0x07, -0xc3,0xe1,0x11,0xb8,0xfc,0x0e,0xea,0x39,0xcc,0x81,0xe9,0x77,0x3a,0xce,0x2a,0x78, -0x68,0x3c,0xcf,0xdb,0x30,0x17,0xee,0x9a,0x40,0x5b,0xbc,0xcb,0x71,0x96,0xc3,0x5b, -0xf0,0x2e,0x0c,0x9b,0x48,0x1d,0x80,0x33,0xef,0xa3,0xee,0xc0,0x02,0xd8,0x09,0x6d, -0xee,0x77,0x9c,0xcb,0xa0,0x1b,0xf4,0x86,0xd5,0xb0,0x07,0x0e,0x40,0xe0,0x01,0xee, -0xf7,0xb0,0x09,0x36,0xc3,0x7d,0x93,0xf8,0x2d,0x0a,0xcf,0xc1,0xd4,0x07,0x1d,0x67, -0x23,0x6c,0x86,0x73,0x1e,0xe2,0xda,0x43,0x12,0xd4,0x81,0x8b,0xe1,0xb3,0x87,0x69, -0x77,0x8f,0x52,0x26,0xf0,0x14,0xbc,0x02,0xaf,0xc1,0x1a,0x78,0x17,0x0e,0xc2,0x4f, -0x50,0x98,0x76,0x53,0x02,0xca,0x41,0x6d,0x68,0x0e,0x17,0xc0,0x1d,0xf0,0x30,0x4c, -0x7e,0xcc,0xfd,0xed,0x54,0x3c,0xc1,0x6d,0x03,0x8d,0x20,0x0b,0x5a,0xe0,0x9e,0x04, -0x2d,0xa5,0xad,0xa4,0x6d,0xa4,0x6d,0xa5,0xed,0xa4,0x1d,0xa4,0x1d,0xa5,0x9d,0xa4, -0x97,0x4a,0x3b,0x4b,0xbb,0x4a,0x2f,0x97,0xf6,0x94,0x5e,0x21,0x1d,0x60,0xe9,0x83, -0x30,0x48,0xee,0x41,0x72,0x5f,0x25,0xf7,0x55,0x72,0x0f,0x97,0x7b,0xb8,0xdc,0xd7, -0xca,0x7d,0xad,0xdc,0x8d,0xe5,0x36,0x79,0xf4,0xd4,0xf8,0x3f,0x22,0xff,0x71,0x70, -0x2b,0x4c,0x97,0xfb,0x79,0xe9,0x0c,0xe9,0x2c,0xe9,0x1c,0xe9,0x5c,0xe9,0x3c,0xe9, -0x02,0xe9,0x2b,0xd2,0x85,0xd2,0xc5,0xd2,0xa5,0xd2,0xd7,0xa5,0xcb,0xa5,0x2b,0xa5, -0xef,0x58,0x6a,0xd2,0xb5,0x4e,0xee,0x75,0x72,0xbf,0x2f,0xf7,0xfb,0x72,0x6f,0x94, -0x7b,0xa3,0xdc,0x9b,0xe4,0xde,0x24,0xf7,0xd3,0x72,0xef,0x95,0xee,0x93,0xee,0x97, -0x7e,0x2d,0xfd,0xc9,0xca,0x97,0x49,0x8b,0x53,0x8c,0xfa,0x08,0xcf,0x42,0x10,0x9e, -0x83,0xe7,0x21,0x02,0x22,0x21,0x04,0x33,0x61,0x16,0xcc,0x86,0x39,0xf0,0x22,0xcc, -0x85,0x33,0x20,0x0a,0xa2,0x21,0x1f,0x94,0x87,0x18,0xc8,0x0f,0x05,0xa0,0x20,0x14, -0x82,0xc2,0x50,0x04,0x8a,0x42,0x31,0x28,0x0e,0x5d,0xa1,0x1b,0x74,0x87,0xcb,0xa1, -0x07,0xf4,0x84,0x5e,0x70,0x05,0xf4,0x86,0x2b,0xa1,0x0f,0xf4,0x85,0x7e,0xd0,0x1f, -0x06,0x40,0x36,0x0c,0x84,0x41,0x30,0x18,0x86,0xc0,0x55,0xd0,0x05,0x4a,0x42,0x29, -0x58,0x01,0x2b,0xe1,0x2d,0x78,0x1b,0xd6,0xc3,0x06,0xf8,0x10,0xce,0x84,0x8f,0x61, -0x33,0x9c,0x05,0x65,0xa0,0x1c,0x54,0x80,0x58,0xa8,0x04,0xe7,0x42,0x55,0xa8,0x06, -0x71,0x10,0x0f,0xd5,0x21,0x09,0xd2,0x20,0x03,0x9a,0xc0,0x79,0xb0,0x1f,0xbe,0x86, -0x0e,0x70,0x11,0x74,0x84,0x1b,0x61,0x2c,0x8c,0x83,0xbb,0xe0,0x6e,0xb8,0x17,0x1e, -0x85,0x29,0xc5,0xdc,0x7b,0x9a,0xb9,0x7f,0xc5,0xc0,0x26,0xee,0x33,0xf9,0xd1,0x38, -0x28,0x00,0x49,0x57,0xb9,0xf7,0xb8,0xd3,0x21,0x00,0xe7,0x72,0xaf,0x49,0xe8,0xe0, -0xde,0xe7,0xcc,0x73,0x83,0xb9,0x66,0xde,0x33,0x44,0xd0,0xb2,0x47,0x58,0xf6,0x90, -0x65,0x8f,0xb2,0xec,0xd1,0x96,0x3d,0xc6,0xb2,0x17,0xb0,0xec,0x05,0x2d,0x7b,0x61, -0xcb,0x5e,0xc4,0xb2,0x17,0xb5,0xec,0xc5,0x2c,0x7b,0x71,0xcb,0xee,0xe8,0x3a,0xdb, -0x69,0xed,0xea,0x4b,0x6f,0x57,0x5f,0x9a,0xbb,0xfa,0xd2,0xdd,0xd5,0x97,0xf6,0xae, -0xbe,0xf4,0x77,0xf5,0xe5,0xa1,0xab,0x2f,0x1f,0x5d,0x7d,0x79,0xe9,0xea,0xcb,0x4f, -0x57,0x5f,0x9e,0xba,0xfa,0xf2,0xd5,0xd5,0x97,0x37,0x7f,0xfc,0xfd,0x7d,0xe7,0xef, -0xad,0x7a,0x6f,0xea,0xf3,0xf4,0x63,0xec,0x2b,0xee,0x3b,0xc6,0xd4,0x03,0xcf,0x7e, -0xbb,0x65,0x8f,0xb1,0xec,0xa6,0x7e,0x78,0xf6,0x7b,0x2d,0xff,0x7e,0x6d,0xac,0xb2, -0xb4,0xfc,0xfb,0x58,0xfe,0x97,0x5a,0xfe,0xa6,0x9f,0xf7,0xec,0xe6,0x79,0xe1,0xd7, -0xf0,0xd9,0xd6,0x6f,0xb0,0xbe,0x47,0xec,0x95,0xad,0x30,0xe6,0x5e,0xe7,0xd9,0xcd, -0x33,0x91,0x67,0x37,0xcf,0x1f,0x9e,0xbd,0xca,0x15,0x56,0xfa,0xad,0x63,0xeb,0x5d, -0x7d,0xc4,0x5e,0xe8,0x42,0xeb,0x59,0x78,0xd0,0x11,0xfb,0x94,0xdb,0xad,0x34,0x5b, -0xe1,0x5f,0xbc,0xe0,0x88,0x7d,0x9b,0x95,0x97,0x11,0x56,0x3c,0x3d,0x3b,0x59,0x61, -0x86,0x1e,0xb1,0x07,0x5a,0x1d,0xb1,0xaf,0xb0,0xe2,0xbf,0xd0,0x2a,0x87,0x7e,0xd6, -0xb9,0x8a,0x59,0xe1,0xcd,0x73,0x86,0x67,0x3f,0x20,0x7b,0x2b,0x1e,0xac,0x47,0x5c, -0xe6,0x3e,0x87,0x8d,0xbf,0xd6,0x7d,0xf6,0xf6,0xde,0x99,0x25,0x59,0xbf,0xb5,0x92, -0xac,0xdf,0xda,0x49,0xfa,0x9d,0x95,0xe4,0x1c,0x79,0x0f,0x95,0x26,0xad,0x21,0xad, -0x29,0x4d,0xd7,0x31,0x09,0xd6,0xef,0xed,0x64,0x2b,0xae,0x64,0xeb,0xf7,0x7b,0xb2, -0xe2,0x73,0xd4,0x6f,0x47,0xa8,0xaf,0xf6,0xfa,0xe3,0x18,0xab,0x0f,0x3e,0x56,0xff, -0xeb,0xb5,0x4d,0xaf,0x4d,0x7a,0x6d,0xd1,0x6b,0x83,0x5e,0xdb,0xf3,0xda,0x9c,0xd7, -0xd6,0xbc,0x36,0xe6,0xb5,0x2d,0xaf,0x4d,0x79,0x6d,0xc9,0x6b,0x43,0x5e,0xdb,0x29, -0xee,0x0b,0x37,0x4f,0xe7,0xf6,0xda,0x49,0x7f,0xc5,0x39,0x44,0xda,0xff,0x18,0x7d, -0xa3,0xa9,0xf3,0xa6,0xae,0x9b,0x3a,0x6e,0xea,0xb6,0xa9,0xd3,0xa6,0x2e,0x9b,0x3a, -0x6c,0xea,0xae,0xa9,0xb3,0xa6,0xae,0x9a,0x3a,0x6a,0xea,0xa6,0xa9,0x93,0xa6,0x2e, -0x9a,0x3a,0x68,0xea,0x9e,0xa9,0x73,0xa6,0xae,0x99,0x3a,0x66,0xea,0x96,0xa9,0x53, -0x1b,0xd1,0x1a,0x57,0xb9,0xcf,0xf1,0xbd,0x79,0xae,0xab,0x42,0xbc,0xd9,0xc4,0xb9, -0x93,0xb8,0x42,0x0f,0xb8,0x75,0x61,0x12,0x7e,0xdd,0xfb,0x1c,0xdd,0x37,0xef,0xe0, -0x39,0xfe,0x00,0xe1,0x6a,0xc3,0xfd,0x9d,0xdc,0xba,0x67,0xea,0x9c,0xa9,0x6b,0xa6, -0x8e,0x99,0xba,0x75,0x66,0x47,0xf7,0xfa,0xa4,0xe9,0x3a,0xd5,0x90,0x7a,0xef,0x0f, -0xd3,0x75,0x0d,0x13,0xac,0xf7,0x29,0x29,0x56,0x5d,0xf1,0xae,0x71,0x8a,0xf5,0xae, -0x26,0x45,0xf1,0xa5,0x28,0xbe,0x14,0xc5,0x97,0xa2,0xf8,0x52,0xad,0x77,0x37,0xa6, -0x5c,0x92,0xa4,0xc9,0xd2,0x14,0x69,0xaa,0x34,0x4d,0x5a,0x43,0x5a,0x53,0x9a,0x2e, -0xf5,0xde,0x27,0x78,0xf6,0x44,0xcb,0xee,0xc5,0xdd,0x54,0xbf,0x1d,0x7b,0xe8,0x77, -0xa1,0x67,0x6f,0x6f,0xbd,0x43,0x36,0xbf,0x67,0x1f,0x27,0xa2,0x27,0xf5,0x8c,0x32, -0x15,0x9e,0xd5,0x73,0xd7,0xf3,0x7a,0xde,0x9a,0xa5,0xe7,0x91,0xb9,0x7a,0xbe,0x5a, -0xa0,0xe7,0xaa,0x85,0x7a,0x9e,0x5a,0xaa,0xe7,0xa8,0xe5,0x7a,0x66,0x59,0x05,0xab, -0xe1,0x2d,0x58,0x03,0x6f,0xeb,0x39,0x6a,0x9d,0x9e,0x97,0x36,0xea,0xb9,0xe8,0x13, -0xd8,0x0c,0x5b,0xe0,0x53,0xd8,0x0a,0xdb,0x61,0x07,0xec,0x84,0x2f,0x60,0x17,0xec, -0x86,0x2f,0x61,0x8f,0x9e,0x9f,0xf6,0xe9,0xb9,0xc9,0x3c,0x33,0x6d,0x37,0x75,0xf1, -0x42,0x37,0x0f,0xde,0xb3,0xda,0x61,0xa5,0xe3,0x71,0x1d,0x6b,0xce,0x39,0x45,0xee, -0x03,0xd6,0x33,0xda,0xe3,0xe2,0xb0,0xc2,0x3d,0xa9,0x74,0x1f,0xb0,0x9e,0x33,0x9f, -0x94,0x7b,0xa9,0xca,0x67,0x9a,0xdc,0x53,0x55,0x0e,0x33,0xac,0xf3,0x4d,0x92,0xff, -0x61,0x95,0xdf,0x61,0x95,0xe5,0x01,0x85,0x7d,0x56,0x7e,0x5b,0xb4,0xff,0x49,0xe5, -0x6f,0xba,0xf2,0xfa,0x9a,0xe2,0x99,0xae,0xbc,0xee,0xb2,0xe2,0x9a,0x24,0xbf,0x03, -0x96,0xfb,0x80,0xca,0xd0,0x7b,0x36,0xf6,0x3f,0x2b,0xaf,0xd6,0xf1,0xc6,0xfd,0x99, -0x15,0x9f,0xb7,0x7f,0xb7,0xf5,0x3c,0xec,0xf9,0xef,0xb1,0xd4,0x2b,0x83,0x2f,0x94, -0x66,0xcf,0xff,0x53,0x2b,0xcf,0xbb,0x2d,0xff,0xbd,0x56,0x1e,0xfc,0xc7,0xfa,0xdd, -0x2b,0xad,0x72,0xde,0x6d,0x3d,0xa7,0x3f,0x69,0x9d,0x7b,0x8f,0xce,0xb3,0x46,0x75, -0xf0,0xb0,0xca,0x71,0x8e,0x8e,0x59,0x25,0xfb,0x61,0xeb,0x39,0x7b,0xae,0xe2,0xf0, -0xe2,0x9d,0x6b,0x5d,0x3f,0xaf,0xec,0xe7,0xa9,0x2c,0xec,0xeb,0xfc,0x8a,0x15,0xce, -0xb3,0xaf,0xf4,0x95,0xcf,0x02,0xeb,0x77,0xc2,0x3c,0xeb,0x9a,0xef,0xb6,0xf2,0xb3, -0x4a,0xf5,0xfd,0x6d,0xab,0x2c,0xde,0x51,0x9a,0x5e,0xf7,0xd5,0x51,0xfb,0x3c,0xb6, -0xff,0xeb,0xd6,0xef,0x8f,0xc3,0xaa,0xfb,0xbb,0xad,0xdf,0x1e,0x8f,0x2b,0x2d,0x2b, -0x7d,0xfe,0x33,0x7c,0xee,0x39,0xd6,0xef,0x16,0xbb,0x1d,0xd8,0x75,0xcd,0xfb,0x0d, -0xf3,0x84,0x75,0x4d,0xbd,0xdf,0x36,0xaf,0x29,0x9f,0xcf,0x58,0xc7,0x7f,0x61,0xb5, -0xa1,0x19,0x2a,0x1f,0xaf,0xfd,0x78,0xed,0x6a,0xa1,0x65,0x5f,0xad,0x34,0x6c,0x54, -0x98,0xd7,0xad,0x7d,0x5e,0xbb,0xd8,0x68,0xe5,0x7d,0x9e,0xf5,0x3b,0xca,0xcb,0xff, -0x62,0xeb,0xfa,0x3e,0x69,0x95,0xd9,0x26,0x5f,0xf9,0x6f,0xd2,0xbe,0x4d,0x56,0xfc, -0x2b,0xad,0xdf,0x61,0x5e,0x3a,0x3d,0xbb,0xd7,0x7e,0x9e,0xb4,0xea,0x9b,0xe7,0xde, -0x2d,0xfb,0x6b,0xba,0x16,0x9f,0xc8,0x6f,0x86,0xec,0x07,0x94,0xd6,0xcf,0x65,0xdf, -0x6c,0xc5,0xe1,0xb9,0x77,0x59,0x6a,0x5f,0xcf,0xdd,0x6a,0x43,0x5e,0x7b,0xb5,0xed, -0x5e,0x3d,0xf2,0xb7,0xb1,0xed,0xbe,0x7e,0x66,0xbb,0x15,0xd7,0x0e,0xab,0x6f,0xdb, -0x65,0x5d,0xdb,0xdd,0x72,0x7f,0xe1,0xab,0x17,0xde,0x71,0x87,0xad,0x6b,0xed,0xf5, -0xb1,0x5b,0xa4,0x5e,0xd9,0xcd,0xb2,0xfa,0x1a,0xbb,0x7d,0x9b,0x7b,0xcf,0x82,0x6c, -0xf7,0x7e,0x63,0x34,0x49,0x9a,0x2c,0x4d,0x91,0xa6,0x4a,0xd3,0xa4,0x35,0xa4,0x35, -0xa5,0xe9,0x56,0x3c,0x76,0x9c,0x89,0x96,0x3d,0xc9,0xb2,0x27,0x5b,0xf6,0x14,0xcb, -0x9e,0x6a,0xd9,0xd3,0x2c,0x7b,0x0d,0xcb,0x5e,0xd3,0xb2,0xa7,0x5b,0xe9,0x4e,0xb0, -0xec,0x76,0x7e,0x92,0x2c,0x7b,0xb2,0x65,0xf7,0xce,0x7b,0x85,0xde,0xef,0x76,0xd5, -0xfb,0xda,0xae,0x7a,0x57,0xdc,0x4e,0xef,0x71,0xbb,0xea,0xbe,0x3b,0x40,0xf7,0xdc, -0x6c,0xdf,0x78,0x9c,0xa7,0x49,0x96,0x3d,0x59,0xef,0x93,0x4d,0x1c,0x83,0x79,0x96, -0x2d,0x4a,0x44,0x71,0x3d,0xdd,0x7b,0x9c,0x79,0xaf,0x69,0x9e,0x77,0x0a,0xe3,0x7e, -0xa5,0xaf,0xfb,0x0c,0x55,0xa4,0x33,0xbf,0xf3,0x5b,0xb9,0xcf,0x51,0xd9,0x3a,0x7f, -0x7f,0xe9,0xab,0x9a,0xe7,0xdc,0x4f,0xee,0x3e,0xd2,0x16,0x7a,0x9f,0xdd,0x52,0xda, -0x44,0xea,0x8d,0x77,0xf7,0x55,0x58,0xdb,0x9d,0xad,0x77,0xde,0xfd,0xa5,0xaf,0x46, -0x1c,0xd1,0x5e,0x3a,0x87,0xf7,0xdd,0x92,0x37,0xee,0x7d,0x8d,0xdc,0x9e,0xbd,0xa5, -0x65,0x6f,0x62,0xd9,0x2f,0xb4,0xec,0xaf,0x46,0x1c,0x79,0xb7,0xdd,0x57,0xe5,0xd2, -0xd7,0x7a,0xe7,0xdd,0xd3,0x7a,0xef,0xdd,0x4f,0xe1,0xfb,0x39,0x47,0xe8,0x2e,0xed, -0xe3,0x1c,0xed,0x9f,0x64,0xed,0x4b,0xb2,0xb4,0x8f,0xcf,0xcf,0xbb,0x06,0xdd,0x2d, -0xbb,0xa7,0x7d,0x7c,0x7e,0xa5,0x2a,0xb8,0xef,0xda,0x6d,0x7b,0x92,0x55,0x27,0xfa, -0x58,0xf6,0x96,0x96,0xbd,0x89,0x65,0x1f,0x64,0xd5,0x1f,0xdb,0xee,0xc5,0x77,0x2c, -0xbf,0x24,0x6b,0xde,0x42,0x7f,0xe9,0xab,0x11,0x47,0xd2,0x32,0x58,0xfb,0xdb,0x6b, -0x7f,0x7b,0xed,0x6f,0xaf,0xfd,0xed,0x95,0xb6,0xf6,0x4a,0x97,0x57,0x37,0x3b,0x28, -0x7c,0x07,0x85,0xef,0xa0,0xf0,0x1d,0x14,0xbe,0x83,0xc2,0x7b,0xee,0x69,0xfa,0xbe, -0x66,0x9a,0xe6,0xd0,0xc7,0x2b,0x7c,0xbc,0xea,0xd3,0x40,0x95,0x63,0x77,0xab,0xde, -0x37,0x54,0x3e,0xbc,0x7a,0xd2,0x50,0xed,0x23,0x5e,0xfb,0xbd,0xfa,0x78,0xb5,0xc6, -0x3c,0xba,0xaa,0x6e,0xb4,0xd6,0xf8,0x47,0x7f,0xd5,0x5f,0x8f,0x96,0x4a,0xcb,0x10, -0xe7,0xe8,0x7a,0xd3,0x5f,0x3a,0x40,0xe7,0xe9,0xab,0x31,0x90,0x7e,0x6a,0x9b,0xfd, -0xac,0x31,0xf2,0x7e,0x0a,0xd7,0x57,0xe7,0x69,0xaa,0xf2,0xb0,0xf3,0xd3,0x5a,0x78, -0xd7,0xd1,0x9b,0x2b,0x32,0x48,0xe3,0x3a,0x43,0x55,0x2e,0xdd,0x54,0x9e,0x26,0x8f, -0x5e,0xbb,0xf3,0xec,0x89,0xfa,0x3d,0x9e,0x24,0x4d,0x96,0xa6,0x48,0x53,0xa5,0x69, -0xd2,0x1a,0xd2,0x9a,0xd2,0x74,0xa9,0xd7,0x57,0x7a,0xf6,0x44,0xcb,0x9e,0x64,0xd9, -0x93,0x2d,0x7b,0x8a,0x65,0x4f,0xb5,0xec,0x69,0x96,0xbd,0x86,0x65,0xaf,0x69,0xd9, -0xd3,0xad,0x74,0x27,0x58,0x76,0x3b,0x3f,0x49,0x96,0x3d,0xd9,0xb2,0xa7,0x58,0xf6, -0x54,0xcb,0x9e,0x66,0xd9,0x6b,0x58,0xf6,0x9a,0x96,0x3d,0xdd,0x2a,0xa7,0x04,0xcb, -0xee,0x9d,0xb7,0x97,0xd5,0x3f,0x35,0x8f,0xe4,0x37,0x0d,0x0c,0x78,0xc6,0xad,0x53, -0x8d,0x35,0xee,0x56,0x31,0xc0,0xf5,0x81,0x4a,0xf8,0xd7,0x98,0x4e,0x5d,0x09,0x72, -0xed,0xd0,0x3b,0xd0,0xa6,0xfc,0x1e,0xfc,0xa6,0xa7,0x3b,0x8e,0xb0,0x12,0xfb,0xbb, -0x54,0x9a,0x24,0x7e,0x4f,0x7c,0xd6,0xd6,0x7d,0x07,0x6f,0x68,0xd3,0xd1,0xfd,0xdd, -0x7a,0x33,0x1d,0x72,0x53,0xec,0xdf,0x71,0xc2,0xde,0x5c,0xec,0xb1,0x37,0x38,0x4e, -0xfd,0x9b,0x1c,0xe7,0xe5,0x9b,0x1d,0xe7,0x87,0x71,0x8e,0x33,0x9e,0xdf,0xa8,0xc1, -0x2b,0x1d,0xa7,0x34,0x17,0xfa,0x43,0x2a,0xcf,0x27,0x54,0x8e,0xba,0x37,0x3a,0xce, -0x95,0x1c,0xbb,0x7f,0x92,0xe3,0x8c,0x26,0xde,0xab,0x88,0x37,0x92,0xca,0x5a,0x9a, -0x4a,0x13,0xc7,0xb1,0x65,0x1e,0xa5,0x2e,0xf1,0x7b,0x75,0x37,0x95,0x6f,0xcd,0x58, -0xea,0xff,0x2d,0x8e,0xf3,0x26,0x15,0x68,0xe3,0x28,0x7e,0x27,0x13,0xe7,0x2a,0xfa, -0xff,0x53,0xc9,0xe4,0x95,0xc4,0x79,0x98,0x63,0xde,0xe6,0x1c,0x37,0xd1,0xd7,0xcf, -0x23,0x63,0x9f,0x50,0x01,0x5f,0xa7,0xc1,0xac,0xe7,0x9c,0x45,0xd9,0xf7,0xf9,0x50, -0x77,0x5c,0x26,0x8a,0x78,0x2f,0x81,0xbd,0x77,0x90,0x97,0xfb,0x29,0x8f,0x87,0x1c, -0xe7,0xd0,0x23,0xdc,0x3f,0xa8,0xfc,0x37,0xd0,0xa8,0x26,0x5f,0xc7,0x39,0xc6,0x90, -0x8e,0x7b,0xdc,0x31,0x8a,0x0f,0x6e,0xe5,0x19,0xe5,0x22,0xdc,0xdc,0x47,0xba,0x8c, -0x70,0x9c,0xeb,0x89,0xa3,0x1b,0xf1,0xad,0x22,0xfc,0x32,0xca,0xe4,0xfb,0xfb,0x48, -0x0f,0xfb,0xbb,0x5d,0xe2,0x38,0x25,0x38,0xd7,0x47,0xa4,0xa5,0x08,0xe9,0xad,0x4e, -0x3c,0x35,0xae,0xa7,0x0d,0xb2,0x7f,0x1d,0x69,0xb9,0x8d,0xe3,0x72,0x48,0xd7,0x5b, -0x70,0x26,0x1d,0xf9,0x22,0xf6,0xef,0xe7,0x3c,0x57,0xb1,0x7f,0xe4,0x6d,0x3c,0xff, -0x74,0x73,0xcb,0xe7,0x67,0xce,0x97,0x4e,0x59,0x7c,0xce,0xef,0xf2,0xe2,0x5d,0x28, -0x57,0xca,0x29,0x9b,0xdf,0xf9,0xa7,0x52,0x36,0x3f,0x72,0xce,0xc5,0xe4,0x6f,0x1a, -0x69,0x29,0x44,0x98,0xb7,0xb8,0xb0,0xc3,0xb9,0xce,0x15,0x88,0xeb,0x35,0xf6,0x5f, -0x46,0x3a,0x02,0xc3,0xb8,0x3f,0x52,0xae,0xcf,0x12,0xcf,0xcb,0x7d,0xdc,0xb1,0x9f, -0x71,0x84,0xfb,0x99,0xf8,0x3f,0x1b,0x4d,0xfe,0xe8,0x68,0x7a,0x10,0xdf,0x0b,0x1c, -0xdf,0x9b,0xb4,0xbc,0x09,0xa3,0xe8,0x48,0x3e,0x27,0x6f,0x03,0xd8,0xbf,0x8e,0xe3, -0xde,0xe1,0x9a,0x7c,0x4b,0x9a,0x33,0x2f,0x74,0xdf,0x2f,0x7c,0x49,0x39,0xbf,0xc1, -0x79,0x7e,0x26,0x5f,0x55,0x28,0xa3,0xe7,0x27,0x52,0xe6,0x0f,0x3a,0xce,0xfd,0x94, -0xcf,0xdb,0x8f,0xba,0xe3,0x92,0xcb,0xa9,0x27,0xc3,0xcc,0xd8,0x1d,0xe7,0x1b,0x42, -0xf8,0x09,0x1a,0xac,0x7d,0x90,0x6b,0x10,0xcb,0x71,0xcd,0xe8,0xec,0x6e,0xa1,0x13, -0x5a,0xc5,0xf1,0xdf,0x90,0xde,0x01,0x94,0x47,0x45,0xc2,0x7e,0x45,0x5a,0x63,0xb9, -0x6e,0xcd,0x29,0xef,0x53,0x89,0xe7,0x09,0x58,0xc8,0x79,0x57,0x52,0x47,0x96,0x8d, -0x73,0xf3,0xf1,0x26,0xf1,0x7d,0x4b,0x9e,0xaf,0x25,0xed,0x2f,0x70,0x8e,0x7a,0xa4, -0xf5,0xaa,0xc1,0xee,0x6f,0xdb,0x83,0xed,0xdd,0xb2,0x7a,0x99,0x7a,0x72,0x26,0xe5, -0x35,0x1b,0xff,0xde,0x9c,0xe7,0x41,0xea,0x5f,0x63,0xce,0x91,0x72,0x37,0x75,0x92, -0x32,0x6e,0xca,0xb5,0x3e,0xed,0x01,0xae,0x03,0xe5,0xf7,0x82,0x79,0x87,0x42,0x1e, -0xe7,0xe3,0xff,0x29,0xc7,0xdc,0x44,0x3c,0x1f,0xd1,0x69,0x7d,0x45,0x3c,0xf5,0xb9, -0x8e,0x93,0x7a,0xb8,0xd7,0xe2,0x6d,0xd2,0x9c,0x49,0xbd,0x39,0x4c,0x1a,0x47,0x4c, -0x70,0xc7,0x9e,0xc6,0x91,0xcf,0x78,0x8e,0xbb,0x85,0x74,0x5e,0x4b,0xdd,0x3b,0x00, -0x0d,0xb8,0xe6,0x0f,0xd3,0x36,0x62,0x38,0x6f,0x47,0xca,0xb6,0x0f,0xd7,0xa9,0x28, -0x79,0xbc,0xec,0x01,0xf7,0xbd,0x4f,0x3c,0xe5,0x30,0x9c,0x63,0xaa,0x72,0x8e,0xd3, -0x49,0xe7,0x2e,0xc2,0x57,0x21,0xef,0xb7,0x91,0xa7,0x46,0x94,0xd3,0xa9,0x57,0xb8, -0xd7,0xf9,0x26,0xc2,0x9c,0x4b,0xbc,0x45,0xc8,0x7f,0x0f,0x53,0x9f,0x38,0xd7,0xf9, -0xc4,0x3f,0x95,0xfd,0x9f,0x71,0x0d,0xa3,0x88,0x7f,0x2b,0xf1,0x5f,0x4d,0xde,0xde, -0xa1,0x6c,0x3e,0x1d,0xe7,0x8e,0xe3,0x6d,0xe5,0xb8,0xfa,0x5c,0xff,0x97,0x49,0xc3, -0x59,0x1c,0xbb,0x96,0x74,0x3f,0x40,0xd8,0x43,0xb4,0x99,0x27,0xd8,0xd7,0xe1,0x61, -0xc7,0x79,0x8c,0xeb,0x74,0x3f,0x71,0xad,0xe6,0xdc,0x3b,0x47,0xb9,0x63,0x98,0xe5, -0xae,0x75,0x9c,0xbb,0x29,0xcb,0x25,0xd4,0xe7,0x49,0x94,0xf7,0x45,0xd4,0xc3,0x25, -0x66,0x9c,0x9a,0x7a,0x59,0x8b,0x74,0x44,0xc3,0xd3,0x94,0xfd,0x7a,0xd2,0xff,0x01, -0xe1,0xc6,0x52,0x3e,0x65,0x38,0xfe,0x45,0xd2,0x55,0x7b,0xbc,0x3b,0x36,0xd9,0xf2, -0x3e,0x77,0x5c,0x72,0x3d,0xe1,0x32,0xc8,0xeb,0x12,0xfa,0x81,0x8b,0xa9,0x87,0x43, -0x08,0xf3,0x09,0xee,0x2a,0xe4,0x6b,0x15,0x65,0x58,0x87,0x32,0xbf,0x0c,0x7a,0xc0, -0x78,0x68,0xcf,0xf5,0x58,0x34,0xc2,0x1d,0x2f,0x2d,0x4e,0x7b,0xfc,0x81,0xeb,0x5c, -0x93,0x32,0xfe,0x09,0xf6,0x52,0x6e,0x87,0xa1,0x32,0xfd,0xc2,0x8b,0xb4,0xcd,0x4f, -0xf0,0x9b,0xc7,0x79,0x5e,0xe0,0xda,0x7d,0x4e,0x19,0xb6,0x34,0xf5,0x96,0xb4,0x6d, -0x87,0x36,0xe4,0xfb,0x11,0xce,0x19,0x45,0x3d,0xb8,0x02,0x32,0x38,0xf7,0x54,0xb8, -0x5c,0xe3,0xd8,0xf7,0x72,0x7d,0xaf,0xa0,0x8c,0xef,0x84,0xaf,0xa1,0x15,0xd7,0x76, -0x2b,0xdc,0x45,0xfa,0xc6,0x52,0xae,0xc3,0xc8,0xcf,0x19,0x94,0x5b,0x05,0xea,0x68, -0x7f,0xca,0xad,0x2a,0x65,0x51,0x81,0xb2,0xbb,0x6b,0xb8,0xfb,0xcc,0xd8,0x0c,0x9a, -0x42,0x6b,0x93,0xe6,0xab,0xdd,0xf7,0x6f,0x77,0xc0,0x9d,0x90,0x41,0x19,0x8e,0x82, -0x25,0xd7,0xba,0x75,0x3a,0x86,0xf6,0xde,0x00,0x06,0x91,0xa7,0x10,0x75,0x6f,0x0f, -0xe5,0x7c,0xf5,0x68,0x97,0x0e,0xd4,0xa1,0xa2,0x94,0xd3,0x7d,0x10,0xa0,0x9c,0xd3, -0x61,0x3d,0xfd,0x47,0x3e,0xfa,0xa1,0x97,0xc6,0xbb,0x79,0xdf,0x46,0x1e,0x7f,0xa1, -0x5c,0x6a,0x93,0xf7,0xab,0x48,0xf7,0x57,0x99,0x41,0xe7,0x34,0x8e,0xad,0xc8,0x35, -0x99,0x4b,0x9e,0xaf,0x80,0x91,0x94,0x41,0x96,0x51,0x58,0x41,0xfe,0x0f,0x5d,0xe0, -0xe6,0xbf,0x1d,0x4c,0x82,0xad,0x2a,0xff,0x39,0xd0,0x88,0xb6,0x7b,0x21,0x74,0xbb, -0x98,0xb2,0xe4,0xda,0xad,0xe3,0x1a,0x7d,0x08,0xbd,0xa9,0x77,0xcf,0xc3,0x46,0xce, -0x71,0x3e,0x65,0x92,0x4f,0xe5,0xd3,0x45,0x65,0x94,0xc2,0xf5,0x7e,0x9d,0xb6,0x76, -0x26,0x6d,0xf7,0x02,0x68,0xdf,0xc3,0xbd,0x9e,0xa6,0xaf,0x2d,0x0d,0xed,0x7b,0xb9, -0xe3,0xf3,0xa6,0x7f,0x31,0x75,0x7f,0x13,0xe5,0xb7,0x5a,0x65,0x58,0x26,0xdb,0x2d, -0xc7,0x67,0xe8,0x87,0x52,0x29,0xcb,0x19,0x83,0xdc,0x3a,0x30,0x90,0x32,0x8d,0xa2, -0xee,0xd6,0x86,0xb3,0xa8,0x0f,0x65,0x87,0xba,0xf5,0x62,0x13,0x7c,0x03,0xcd,0x09, -0x9f,0x01,0x6f,0x51,0xee,0xb3,0x54,0xa6,0xd7,0x52,0x3f,0x3a,0x5d,0xe7,0x96,0x6d, -0x3d,0x95,0xe9,0x05,0xd4,0xcd,0xbd,0x70,0x35,0xf7,0x95,0x06,0x94,0xe5,0x4d,0x2a, -0xd3,0xc5,0xb7,0xba,0x75,0x68,0xf5,0xad,0x6e,0xf9,0x9a,0x7e,0x74,0xcf,0xad,0x6e, -0x39,0xdf,0x7a,0x9b,0x5b,0xce,0x35,0x29,0xe7,0x41,0x94,0xf3,0x24,0xda,0xcc,0x50, -0xfa,0x85,0x11,0xf7,0xb9,0x65,0x6e,0xea,0xe2,0xa6,0xfb,0xdd,0xb2,0x2f,0xf0,0x80, -0x5b,0xfe,0xc5,0x1f,0x72,0xef,0x6f,0xcd,0x2b,0x07,0x9d,0x46,0xf0,0x7e,0x72,0xd0, -0x79,0xa4,0x36,0x7d,0x48,0x06,0x71,0x42,0xb3,0x0b,0x82,0xce,0xda,0x4b,0x82,0xce, -0xfa,0xeb,0x83,0x4e,0x43,0xc2,0xdd,0xf9,0x98,0xfb,0xec,0xed,0xd1,0x5b,0xda,0xf7, -0x18,0x7e,0x9e,0xbf,0x37,0x0f,0xc5,0xd3,0x21,0x21,0xd2,0x15,0x72,0xb5,0x87,0xb4, -0x0f,0x5c,0x6f,0xf9,0xf7,0x83,0x0f,0xe1,0x45,0xf8,0x1a,0xe6,0xc1,0xd6,0x90,0x3b, -0x67,0xd5,0xcc,0x55,0x7d,0x0f,0x36,0x41,0x0e,0x7c,0x0c,0x3b,0x61,0x57,0xc8,0x9d, -0x03,0xfc,0x05,0xba,0x34,0xe4,0xea,0x32,0xe9,0xab,0x96,0xdf,0xab,0xf2,0x5f,0xab, -0x78,0x8d,0xce,0x97,0xbe,0xaa,0x39,0xb1,0xaf,0x6a,0x5e,0xec,0xab,0x3a,0xdf,0xab, -0x3a,0xa7,0xd1,0xf7,0xa5,0x1f,0x48,0x37,0x4a,0x3f,0x94,0x7e,0x24,0xdd,0x24,0xcd, -0x91,0x7e,0x22,0xdd,0x22,0xfd,0x54,0xba,0x4d,0xba,0x5d,0xba,0x43,0xfa,0xb9,0x74, -0xa7,0xd4,0x4b,0xfb,0x2e,0x2b,0x1d,0x0b,0x94,0xde,0xc5,0x3a,0xdf,0x62,0xc5,0xb3, -0x58,0xf9,0x31,0xe7,0x1e,0x18,0xe5,0x72,0xad,0x8f,0xeb,0x7d,0x8c,0xf4,0x71,0x8d, -0x8f,0x1b,0x7c,0x5c,0xed,0xe3,0x49,0x1f,0x4f,0xfb,0xb8,0xd1,0xc7,0x18,0x1f,0x37, -0xf9,0xb8,0xd9,0xc7,0x1d,0xe2,0x76,0x71,0xa7,0x18,0x27,0x1e,0x10,0x77,0x89,0x69, -0x3e,0x66,0xf8,0x98,0xe3,0x63,0x96,0x8f,0x97,0xc5,0x2b,0x3e,0xbc,0xb5,0x6b,0xec, -0x35,0x6c,0x6c,0x16,0xf9,0x58,0x67,0x61,0xaf,0x75,0xf3,0xbc,0x8f,0x15,0x62,0xb9, -0x58,0x29,0xbc,0xb5,0x73,0x56,0x8b,0x37,0xc4,0x9b,0x62,0xad,0x8f,0x66,0xc2,0x5e, -0x8b,0xe7,0x58,0xf6,0xf7,0xf3,0xb0,0x37,0xcd,0xc3,0xbe,0x22,0x0f,0xfb,0xf2,0x3c, -0xec,0x2b,0xf3,0xb0,0xaf,0x3d,0x01,0x7b,0xb3,0xff,0x60,0x7f,0xcb,0x87,0xb7,0xaf, -0x9a,0x65,0x8f,0xb3,0xec,0x59,0x79,0xc4,0x65,0xb4,0xb2,0x8e,0xab,0xac,0x63,0x8c, -0xc6,0x4b,0xb3,0xa4,0xcd,0xa4,0x26,0x7c,0x15,0x85,0xaf,0xa2,0xf0,0x55,0x14,0xbe, -0x8a,0xc2,0x57,0x51,0xf8,0x2a,0x0a,0x5f,0x55,0xe1,0xab,0x6a,0x7f,0x55,0xed,0xaf, -0xaa,0xfd,0xd5,0x14,0x4f,0x35,0xed,0x8f,0xb3,0xfc,0xb2,0x14,0x77,0x35,0x69,0x9c, -0xd4,0xf8,0x27,0xcb,0x3f,0x59,0xfe,0xc9,0xda,0x97,0xac,0xfd,0xa9,0xf2,0x4f,0x95, -0x3b,0x4d,0xe1,0xd3,0xe4,0x9f,0xa6,0xf0,0x69,0xda,0x5f,0x43,0xfe,0x35,0xe4,0xae, -0x29,0x4d,0xd7,0x71,0xe9,0x72,0xd7,0x92,0xbb,0x96,0xdc,0x0d,0xe4,0x6e,0xa0,0xe3, -0x1b,0x28,0xde,0x06,0xd6,0xfe,0x66,0x52,0x93,0xdf,0x4c,0x85,0xcb,0xd4,0xfe,0x4c, -0xed,0xcf,0xd4,0xfe,0x86,0xaa,0xa3,0x0d,0x15,0x6f,0x43,0x85,0x6f,0xa8,0x78,0x8d, -0x36,0x92,0x66,0x49,0x9b,0x49,0x9b,0x6b,0x5f,0x35,0x69,0x9c,0x34,0x5e,0x9a,0x25, -0x6d,0x26,0x6d,0x2e,0x3f,0xbb,0xec,0xb3,0xac,0x32,0xf6,0x68,0x66,0xd5,0xa1,0xc6, -0x0a,0xdf,0x58,0xe1,0x1b,0x2b,0x7c,0x63,0x85,0x69,0xac,0xf0,0x8d,0x15,0xbe,0x89, -0xc2,0x37,0xd1,0xfe,0x26,0xda,0xdf,0x44,0xfb,0x9b,0x5b,0xf5,0xd6,0xab,0xb3,0xf1, -0x56,0xda,0x9a,0x5b,0x75,0xd6,0x90,0x00,0xd9,0x90,0x28,0x6d,0x26,0x35,0xf3,0xa6, -0xce,0x43,0xdb,0xc8,0xde,0xd2,0xb2,0x9f,0x6f,0xd9,0x5b,0x59,0xf6,0xd6,0x96,0xbd, -0x8d,0xe2,0xf1,0xda,0x46,0xa2,0x65,0x4f,0xca,0xa3,0x2d,0x35,0x3e,0x4e,0xbb,0x4a, -0x94,0x26,0xf9,0xda,0x53,0xe3,0x3c,0xda,0x55,0xa2,0x34,0xc9,0xd7,0x9e,0x1a,0xe7, -0xd1,0xae,0x12,0xa5,0x49,0xbe,0xf6,0xd5,0xf8,0x18,0xed,0xec,0x58,0xf5,0x30,0xaf, -0x7a,0x77,0xbc,0xfa,0x95,0x57,0x3d,0xaa,0x64,0xd5,0x99,0xc6,0x4a,0x5f,0x63,0xa5, -0xcf,0xae,0x1f,0x8d,0x8f,0x51,0x4f,0x9a,0x59,0xe5,0xef,0x95,0x7d,0x92,0xaf,0x1e, -0x34,0x3e,0x46,0x7d,0x38,0x5e,0xdf,0x17,0x9f,0xc7,0xb5,0x6b,0x72,0x82,0x7d,0x5f, -0x93,0x13,0xec,0xf3,0x9a,0x58,0xd7,0xe0,0x64,0xfa,0xb4,0x93,0xed,0xcb,0xe2,0x7f, -0x43,0x9f,0xf6,0x47,0xf5,0x65,0xfe,0xbe,0xeb,0x44,0xfb,0xa8,0x13,0xe9,0x93,0x9a, -0x9c,0x40,0x5f,0x74,0x22,0x7d,0x4f,0x93,0x3c,0xfa,0x9c,0xec,0x13,0xec,0x6b,0x9a, -0x1c,0xa7,0xbe,0xf8,0xeb,0xc5,0xb1,0xae,0x7f,0x55,0xb9,0x93,0xad,0xeb,0x6b,0xdc, -0x29,0x72,0xa7,0xc8,0x7d,0xac,0x72,0xca,0x3a,0x46,0x7e,0xfc,0xe9,0xcb,0x88,0x3a, -0xd2,0xa7,0x19,0x7b,0x2b,0xcb,0xee,0xf5,0x69,0x35,0xd4,0x4e,0x6a,0xe8,0xb8,0x74, -0xb9,0xd3,0xe5,0xae,0x25,0x77,0x2d,0xb9,0x93,0xe5,0x4e,0x96,0x3b,0x45,0xee,0x14, -0xab,0xbc,0x9a,0x59,0xe5,0x56,0x4d,0xee,0x6a,0x56,0x39,0x36,0xb3,0xca,0x33,0x55, -0xee,0x54,0xb9,0xd3,0xe4,0x4e,0xb3,0xe2,0xaf,0x26,0x8d,0x93,0xc6,0xfb,0xca,0x28, -0x51,0xe9,0x49,0x54,0x3c,0x89,0x3a,0x3e,0xf1,0x5f,0x90,0xbf,0xe3,0x1d,0x6f,0xd7, -0xa9,0x26,0xd6,0x73,0x4b,0x5e,0x7d,0x49,0x5e,0xf1,0xf9,0x9f,0x77,0x4c,0x1f,0xde, -0xc2,0xd2,0x2a,0x56,0x9b,0xac,0x62,0xf5,0x65,0xc7,0xb2,0x67,0x59,0x7d,0x63,0x96, -0xd5,0x3e,0xaa,0x59,0xf6,0x38,0xcb,0x1e,0x7f,0x8c,0x3e,0xd2,0xb3,0xc7,0x59,0x75, -0x3e,0xce,0x7a,0x0e,0x49,0xb6,0xc2,0x26,0x5b,0xfd,0x6a,0xb2,0x75,0xcd,0x92,0xad, -0x78,0x8e,0x65,0xaf,0x66,0xd9,0xb3,0x7c,0xf6,0x54,0xeb,0xfc,0x7e,0x7b,0x96,0x75, -0xed,0xe2,0xf2,0xb0,0x57,0xb3,0xae,0x7f,0x96,0xef,0x9a,0xd9,0xf6,0x2c,0x9f,0x3d, -0xcd,0xaa,0x83,0x69,0x56,0x19,0xda,0xf6,0x1a,0x56,0x5e,0x8e,0x65,0xb7,0xc3,0x34, -0xb7,0xfa,0xfb,0x2c,0xab,0xcf,0xcf,0xcb,0xde,0xcc,0xba,0x0f,0x64,0x59,0xf6,0xe6, -0x96,0xbd,0x99,0x75,0x4f,0xc8,0xf2,0xd9,0x33,0xad,0xf4,0x64,0x5a,0xf1,0x34,0xb2, -0xca,0xa7,0x91,0xd5,0xbe,0x1a,0x59,0xf9,0xf5,0xee,0x0f,0xd5,0x8e,0x61,0x8f,0xb7, -0x8e,0x8d,0xf7,0xf5,0x93,0x71,0x3e,0xfb,0xb1,0xea,0x51,0x96,0x65,0x6f,0xee,0xbb, -0x3f,0xdb,0xf7,0x1b,0x7f,0x7d,0xb4,0xef,0x3b,0xde,0xbd,0xda,0xbe,0x47,0x35,0xf1, -0xdd,0x97,0xe2,0x7c,0xf6,0x66,0xd6,0xfd,0x2a,0x2b,0x0f,0x7b,0x33,0xab,0xaf,0x6f, -0x6e,0xd9,0x9b,0x59,0x69,0xcd,0xf2,0xd9,0x2b,0x5b,0x7d,0x50,0x15,0xab,0xbe,0x55, -0xb1,0xe2,0xac,0xe2,0x0b,0x13,0x6f,0xf9,0x67,0x59,0xfe,0x59,0x56,0x1f,0x97,0xe5, -0xfb,0x3d,0x66,0xfb,0x37,0xb3,0xfa,0x91,0x66,0x56,0xbb,0x68,0x6e,0xd5,0xff,0xe6, -0x56,0x1d,0xb6,0x7f,0x87,0xd8,0xcf,0x8c,0xcd,0x7d,0xf7,0x5e,0xfb,0x7e,0xdc,0xfc, -0x18,0x79,0x6d,0x6e,0xd5,0xa5,0xe6,0x56,0xd9,0x36,0xf7,0xd5,0x37,0xbb,0x8e,0xa5, -0x5b,0xe1,0x1b,0x1e,0xe3,0x77,0x8b,0xfd,0x5c,0x61,0xa7,0x33,0xcb,0x0a,0x9f,0xe5, -0xf3,0x6f,0xec,0x7b,0xa6,0x8b,0xb3,0xf2,0x51,0xcd,0x57,0xaf,0x9a,0x5b,0xcf,0x4b, -0xcd,0xad,0x67,0xcb,0xe6,0xbe,0xf8,0xed,0xb6,0x99,0xea,0xeb,0x8b,0xe2,0x7d,0x69, -0xf6,0xee,0x37,0x8d,0xf4,0xee,0x24,0xd3,0xb2,0x57,0x92,0xdd,0x7b,0x86,0xf2,0x9e, -0xe1,0xbd,0xdf,0x1f,0x76,0x3b,0xa8,0x6e,0xc5,0xe3,0x95,0x93,0x77,0x0f,0x6a,0x6a, -0xf9,0x37,0xb2,0x9e,0x35,0x9a,0x6a,0x7f,0x23,0x2b,0xad,0x8d,0xac,0x7a,0xe0,0xd9, -0x73,0xff,0x8e,0x8a,0x2f,0x2d,0xc6,0xcf,0x1f,0x9f,0xf1,0xf3,0xc7,0xe9,0xb5,0x75, -0xb3,0xcf,0xb3,0x57,0xb2,0xe2,0x49,0xd4,0x7b,0x0e,0xcf,0xaf,0x9a,0x99,0x33,0x9d, -0xe0,0x38,0xc1,0x04,0xf7,0x5b,0xbd,0xda,0xd6,0xba,0x0b,0xa7,0xe0,0x77,0x6a,0xc2, -0xb1,0xd7,0x79,0x28,0xc9,0x09,0x4a,0x40,0x67,0xc7,0xc5,0x5b,0x23,0xe1,0x5a,0xb8, -0xce,0xec,0xe7,0xb8,0x52,0x50,0x14,0x8a,0x41,0x7e,0x28,0x90,0x70,0xe4,0xdb,0xe4, -0x82,0x50,0x08,0x0a,0x43,0x11,0xf3,0x0d,0x91,0x99,0x03,0x6e,0xad,0x05,0x91,0x97, -0x7a,0xe7,0x33,0x78,0x69,0x8f,0x57,0xba,0x6b,0x59,0x6b,0x47,0x98,0xf4,0xe5,0x95, -0xa6,0x58,0x33,0x86,0x6b,0xe6,0xae,0x6b,0x9d,0x87,0x38,0xc7,0xfd,0x06,0xbc,0x9e, -0xbe,0x4b,0xbc,0xc4,0xac,0x0b,0x69,0xd6,0x86,0x85,0x0c,0xa5,0xa9,0x85,0x9e,0x09, -0x5b,0x58,0xbf,0x8f,0xbd,0xdf,0xc6,0xde,0xef,0x62,0xef,0x59,0xd2,0xfb,0x6d,0xec, -0x3d,0x4f,0x7a,0xbf,0x8f,0xbd,0x67,0x4a,0xef,0x37,0x72,0x86,0xf5,0x9b,0xf9,0x02, -0xb9,0x8d,0x96,0x8f,0x3a,0x7a,0x6d,0x6c,0x7b,0x8d,0x6c,0xff,0x5a,0xd9,0xfe,0x35, -0xb3,0xfd,0x6b,0x67,0xe7,0xa5,0x95,0x44,0x65,0x1f,0xe7,0x8a,0x2a,0x3e,0xaa,0xfa, -0xa8,0xe6,0x23,0xce,0x47,0xbc,0x8f,0xea,0x22,0x41,0x24,0x8a,0x24,0x91,0xec,0x23, -0xc5,0x47,0xaa,0x8f,0x34,0x1f,0x35,0x7c,0xd4,0xf4,0x91,0xee,0xa3,0x96,0x8f,0x06, -0x3e,0x32,0x7d,0x34,0xf4,0xd1,0xc8,0x47,0x96,0x8f,0xc6,0x3e,0x9a,0xf8,0x68,0x2a, -0x9a,0x1d,0xe3,0x37,0x78,0x73,0xab,0xdd,0xb6,0xcd,0xc3,0x7e,0x61,0x1e,0xf6,0x76, -0x79,0xd8,0x3d,0x35,0x6d,0xa3,0x82,0xe3,0xb6,0x01,0xaf,0x8e,0x9b,0xb6,0x50,0xc9, -0x6a,0xc3,0x5e,0xbb,0xa8,0xa6,0xb6,0x11,0xaf,0xb5,0x48,0x4e,0x74,0xed,0x14,0xaf, -0x1f,0xa9,0xa3,0xf6,0x54,0x4f,0xed,0x32,0xe3,0x4f,0xfc,0xee,0xb9,0x93,0xda,0xb1, -0xe9,0x53,0x2e,0x53,0x7f,0xd1,0xe5,0x4f,0xfc,0x1e,0xda,0xf4,0x39,0x23,0xd4,0xef, -0x5c,0x0f,0x63,0x29,0xd8,0x9b,0xce,0x75,0xfb,0x59,0xaf,0xef,0x33,0x7d,0xd7,0x8f, -0x9a,0x73,0x3e,0x45,0xf3,0x65,0x9f,0xd2,0xdc,0x58,0x33,0x27,0x7c,0x9b,0xe6,0x58, -0x7f,0xae,0x39,0x92,0x87,0x13,0xfe,0x7e,0xf3,0xea,0x0f,0x68,0xae,0xa7,0x99,0x23, -0xda,0xa5,0xd8,0x3f,0xeb,0xdb,0xc3,0xc7,0xc9,0xce,0x13,0xf0,0x9c,0xf5,0x77,0xd1, -0x9e,0x46,0x9f,0x32,0xf3,0xd7,0xf0,0x08,0x9e,0xe3,0xae,0x89,0x72,0x57,0x79,0x77, -0x4d,0x94,0x7b,0x60,0x32,0x7e,0xab,0x61,0x6d,0xd0,0x71,0xde,0x0d,0xba,0x7f,0xbf, -0x65,0x12,0xda,0x16,0x9e,0x30,0x6b,0xa3,0x4f,0xe7,0x78,0xf4,0x49,0x68,0x0f,0x1d, -0xcc,0xda,0xe5,0x67,0x51,0xb7,0xd1,0xcb,0x60,0x1a,0x5c,0x0e,0xdd,0xa0,0x0b,0x3c, -0x14,0x74,0xd7,0xff,0x79,0xd8,0xac,0xf1,0x0e,0x37,0xc2,0xb3,0x66,0x3d,0x74,0xf3, -0x37,0x43,0x20,0x72,0x73,0xc0,0xb9,0xf3,0x19,0xf2,0x82,0x3d,0x64,0xec,0x68,0x14, -0x6a,0xfe,0x4e,0xcc,0x50,0xf3,0x37,0x49,0x82,0x6e,0x9d,0xbe,0x06,0xcd,0x87,0xff, -0x75,0xe8,0xf5,0x30,0x12,0xa6,0xc2,0xd3,0x26,0x5e,0xd2,0xf4,0x0c,0x3a,0xce,0x84, -0xa5,0x53,0x18,0x8f,0x4e,0x81,0xc7,0xe1,0x01,0x58,0xc2,0xf9,0x97,0xc2,0x32,0xc8, -0x4f,0x1c,0x65,0x60,0x52,0xd1,0x80,0xf3,0x32,0x3c,0x24,0x7d,0x4a,0x9a,0x58,0x2c, -0xe0,0x54,0x82,0x24,0x69,0x93,0x12,0x01,0xa7,0x5e,0x09,0x57,0x3b,0xc0,0x82,0x92, -0x84,0x2b,0xe9,0xea,0x4c,0xe9,0x42,0x98,0x5f,0x2a,0xe0,0xcc,0x80,0x05,0xd2,0xd4, -0xd3,0x03,0x4e,0x02,0x74,0x78,0x37,0xe0,0xf4,0x80,0x8b,0x7c,0xda,0xdf,0xb2,0x0f, -0xb0,0xec,0xd9,0x96,0x7d,0xa0,0x65,0x1f,0x04,0x0b,0x64,0x7f,0x59,0xba,0xc0,0x8a, -0xeb,0x65,0xcb,0xbe,0xc0,0x8a,0xf7,0x65,0xcb,0xfe,0x4f,0x5b,0xd3,0xe1,0xaf,0x90, -0x9f,0x7f,0x52,0x5e,0xfe,0xe8,0x6b,0xe3,0xe5,0xa5,0x89,0x95,0x76,0x7f,0x9a,0x4f, -0x36,0xbd,0x3d,0x8f,0x93,0xce,0x7f,0x7b,0x99,0x67,0x1e,0x23,0x2f,0xc7,0xcb,0xc3, -0x7f,0x4a,0xff,0xff,0x3a,0xed,0xc7,0xba,0x06,0xad,0xfe,0x62,0x69,0x0e,0xf7,0xa7, -0xe1,0xfc,0x84,0xf3,0x13,0xce,0xcf,0x3f,0x2d,0x3f,0x89,0x3c,0x2f,0xd7,0x08,0xba, -0xeb,0xf0,0xde,0xad,0xb5,0x31,0xef,0xd5,0x7a,0xb9,0xf7,0x69,0x7d,0xdc,0x07,0xb4, -0x66,0xed,0x83,0xf0,0x10,0x3c,0x0c,0x8f,0xc0,0xa3,0x5a,0xe7,0x75,0xb2,0xd6,0x91, -0x7d,0x40,0x7f,0xfb,0xee,0x49,0xad,0x2f,0xfb,0x34,0x3c,0x63,0xd6,0x18,0xd4,0xba, -0xaf,0xd1,0x15,0xdc,0xb5,0x81,0xed,0xbf,0xe5,0x64,0xd6,0xe2,0x7e,0x49,0x6b,0xf6, -0xce,0xd7,0xfa,0xb9,0xe6,0x6f,0xea,0xbd,0xa2,0xbf,0xe7,0xbc,0x10,0x16,0x69,0x0d, -0xee,0x25,0xd6,0xdf,0xda,0x7b,0x0d,0x5e,0xd7,0x5a,0xb7,0xde,0xdf,0x7a,0x5a,0xa9, -0xb5,0x68,0x83,0x15,0xdc,0x78,0xe7,0x2b,0xae,0xe5,0x3a,0x6e,0x49,0x44,0x38,0x9f, -0xe1,0x7c,0x86,0xf3,0xf9,0x57,0xce,0x67,0x0e,0x7c,0x1c,0x71,0xe2,0xef,0xf9,0xfe, -0xf2,0xe1,0x22,0x09,0x07,0x49,0x91,0xee,0xdf,0xee,0x4c,0x89,0x74,0xff,0xa6,0xa6, -0xf9,0xbb,0x99,0xe6,0x6f,0x65,0x9a,0xbf,0x87,0x69,0xfe,0x86,0x67,0x6d,0xa8,0x13, -0xe9,0xfe,0x3d,0xcc,0xfa,0x90,0x01,0x0d,0x20,0x33,0xd2,0xfd,0x9b,0x96,0x8d,0x20, -0x0b,0x1a,0x47,0xba,0x7f,0xd3,0xb2,0x69,0xa4,0xfb,0xb7,0x2c,0xcd,0xdf,0xc7,0x3c, -0x1f,0x6e,0x79,0xda,0xfd,0xdb,0x9c,0xe6,0xef,0x82,0x5e,0x14,0xe9,0xfe,0x1d,0xcd, -0x19,0xf0,0x4e,0xe4,0xc9,0xa5,0xc1,0x3b,0x7f,0x83,0xff,0x70,0xde,0xe6,0x3a,0xf7, -0x5d,0x3a,0x5f,0x3b,0xe8,0x0c,0xd3,0xa1,0x0d,0xe9,0x98,0x15,0x79,0xe4,0xbd,0x7e, -0x35,0xbd,0x73,0x6f,0xaa,0x77,0xec,0x71,0xd6,0x1c,0x8f,0x86,0xbe,0x39,0x32,0xc9, -0x7a,0x2f,0xde,0x40,0xef,0xd7,0x33,0xad,0xb9,0x13,0xde,0xfb,0xff,0x78,0xbd,0xbb, -0x4f,0xd3,0x3b,0x75,0xf3,0xde,0xbc,0xbf,0xe6,0x35,0x4f,0x81,0x01,0xd6,0x79,0x9b, -0xf8,0xc6,0xea,0x8e,0x77,0x2e,0xff,0x79,0xd2,0xac,0x39,0x55,0xf6,0x38,0xa8,0x7d, -0xbc,0x7d,0xac,0x7d,0xcc,0xcb,0x79,0xa4,0xa5,0x86,0x6f,0xec,0xf0,0x64,0xd2,0x62, -0xe7,0x77,0x4a,0xd4,0xff,0x2f,0xe3,0x26,0x79,0x94,0xf3,0x1f,0x51,0xc6,0xc7,0xbb, -0x96,0x7f,0xc4,0x39,0xbc,0xf5,0x5c,0x13,0xf4,0xde,0x3f,0x51,0x9a,0x24,0x4d,0x96, -0xa6,0x48,0x53,0xa5,0x69,0xd2,0x1a,0xd2,0x9a,0xd2,0x74,0x69,0x65,0x3d,0xb7,0x79, -0xeb,0x2a,0x65,0x5a,0xf6,0x86,0x96,0xbd,0x91,0x65,0xcf,0xb2,0xec,0x8d,0x2d,0x7b, -0x13,0xcb,0xde,0xd4,0xb2,0x37,0xb3,0xec,0xcd,0x2d,0x7b,0x0b,0xcb,0x7e,0x9e,0x65, -0x6f,0x69,0xd9,0xcf,0xb7,0xec,0xad,0x2c,0x7b,0x6b,0xcb,0xde,0xc6,0xb2,0x5f,0x60, -0xd9,0xdb,0x5a,0xf6,0x0b,0x2d,0x7b,0x3b,0xcb,0xde,0xde,0xb2,0x77,0xb0,0xec,0x17, -0x59,0xf6,0x8e,0x96,0xfd,0x62,0xd9,0xcd,0x58,0xa5,0x89,0xdf,0x8c,0x57,0x36,0xd4, -0x79,0xbd,0xe7,0xde,0x0e,0x0a,0xf7,0x67,0x3d,0x0f,0x37,0xf5,0x7d,0xd7,0xdd,0x56, -0xe7,0x6b,0x2b,0xbc,0xef,0x98,0xbd,0x63,0x1b,0x2a,0xac,0xa7,0xde,0x1a,0xe5,0x17, -0xc8,0x6e,0xd2,0x64,0xd6,0x6c,0x6c,0xa1,0xb1,0x0a,0x6f,0xbc,0xc2,0xac,0x7b,0xda, -0xa1,0x93,0xe3,0xbc,0xd5,0xf6,0xc8,0x9a,0x28,0x66,0x4d,0xa8,0x33,0x48,0xc8,0xb6, -0x5b,0xdc,0x35,0xf1,0x9e,0xc4,0x5d,0x8e,0x87,0xf1,0xf2,0x03,0xdd,0x6f,0x0e,0x5b, -0x12,0xe9,0xed,0x9d,0x1d,0xe7,0x0e,0x32,0x33,0x13,0xf7,0x59,0xe6,0x1b,0xcc,0xeb, -0xdc,0x35,0x4b,0xa7,0xdd,0xee,0x38,0xfb,0xc8,0x48,0x3a,0x99,0x2b,0xc9,0xc3,0xf9, -0x44,0xe2,0x6f,0xd7,0xdd,0x2d,0x04,0xb3,0xce,0x54,0xf3,0x09,0xee,0xda,0x4e,0x66, -0x9d,0x27,0xb3,0xbe,0x53,0x74,0x77,0x77,0x2d,0xd0,0xb6,0x84,0x1b,0xc5,0x43,0xfa, -0x35,0x3c,0xa4,0xe7,0xe3,0xd8,0x91,0xfd,0xdd,0x75,0xa5,0x72,0xd7,0x56,0x22,0xde, -0x41,0x9c,0xe3,0x25,0x0a,0xab,0xe5,0x5d,0xee,0x75,0xaa,0xd6,0xcb,0xbd,0x4e,0xc6, -0x6e,0xe2,0xf5,0xec,0xb7,0x5b,0xf6,0x5b,0x3a,0x1d,0xb1,0x9b,0x75,0x1e,0x3c,0xbb, -0x39,0x97,0x67,0xdf,0x30,0xf4,0x88,0xfd,0xe3,0x0b,0x8e,0xd8,0x3b,0xf4,0x70,0xed, -0xf7,0x93,0xcf,0xbd,0x6d,0x4f,0xfc,0xfe,0x57,0x97,0xf3,0xd7,0x34,0xeb,0x66,0x41, -0x85,0x40,0xd0,0xe9,0xd2,0xca,0xfd,0x1e,0xf0,0x15,0x08,0x72,0xc1,0xae,0x81,0x49, -0xad,0xdd,0xef,0x03,0xdf,0x81,0x47,0x53,0x4c,0x5f,0x10,0xcc,0xfd,0x56,0xf0,0x42, -0xb8,0x1c,0x86,0x99,0x6f,0x6f,0x43,0x41,0x67,0x01,0xfb,0x7a,0xb5,0x71,0xcb,0xb9, -0x05,0xee,0xfb,0xd1,0x27,0xcd,0xb7,0xb9,0xc4,0xfd,0x5c,0x1b,0xf7,0x5b,0xbb,0xcd, -0x77,0x05,0x73,0xd7,0xdd,0x8d,0x20,0xed,0x9b,0x09,0x5f,0x1b,0x6d,0x7c,0x81,0x5b, -0x5e,0xc3,0x4c,0x7e,0x52,0xdd,0xef,0xef,0xbc,0xef,0x10,0xcd,0x77,0x78,0x21,0xf2, -0x13,0xd5,0xd6,0xfd,0x56,0xb7,0x59,0x5b,0xf7,0xdb,0xc4,0x8b,0x61,0x30,0x5c,0x07, -0x37,0x58,0x54,0xcb,0x1f,0x74,0xb2,0x29,0xbb,0x55,0xd8,0x37,0x43,0xf7,0x02,0x41, -0x67,0x37,0xba,0xdf,0xc4,0xc1,0x75,0x7a,0x14,0x6a,0x42,0x53,0xb8,0xc2,0xac,0xdf, -0x04,0xdf,0x41,0x51,0xae,0x55,0x5b,0xe8,0x0e,0x37,0x0a,0xf3,0xdd,0xdf,0x74,0x98, -0x03,0xcb,0xe0,0x94,0x0e,0xee,0xf7,0x90,0xa7,0xa2,0x6d,0xd0,0x21,0xed,0xdd,0x6f, -0x39,0xbf,0xa2,0xfe,0xbc,0x8d,0xdf,0x37,0x50,0x08,0xfb,0x6d,0x68,0x12,0x9a,0x08, -0xcf,0xc1,0xf6,0x92,0x41,0xe7,0x6b,0xf4,0x7b,0x88,0xea,0xe8,0xd6,0xd5,0x0a,0xe6, -0xfb,0x41,0x98,0x7a,0x4a,0xd0,0xd9,0x05,0x5f,0x62,0x2f,0x4d,0x43,0xaa,0x04,0x1b, -0xb1,0xf7,0x44,0xbf,0xe2,0x62,0x1d,0x86,0x7c,0xd4,0x8b,0x7a,0x62,0xe5,0xe9,0x41, -0xe7,0x35,0x74,0x63,0x27,0xf7,0xfb,0xdc,0x03,0x70,0x5a,0xe9,0xa0,0xf3,0x0b,0x1a, -0x75,0x09,0x79,0xe8,0xec,0xae,0x37,0x3c,0x8a,0x8b,0xdc,0x45,0xdf,0x6a,0x2e,0x81, -0x3b,0x60,0xfb,0x59,0x41,0xa7,0x21,0x75,0x79,0x07,0xda,0x1f,0xed,0x03,0x23,0xe0, -0x53,0xd8,0x0a,0xd5,0xa9,0x14,0x3f,0xa1,0x95,0x2f,0xa3,0x5e,0x98,0xef,0x19,0xa1, -0x1b,0x7e,0x63,0xca,0x07,0x9d,0x11,0xe8,0x4c,0xdc,0x73,0xe0,0xa5,0xcb,0xdc,0x6f, -0xc4,0xef,0x7e,0x22,0x98,0xbb,0x56,0x71,0x62,0x6c,0x30,0x97,0xc0,0x68,0xce,0xdd, -0xd9,0x65,0x05,0xe1,0xe7,0x27,0x05,0x9d,0x8f,0xba,0x06,0x9d,0x6e,0xb8,0xfb,0xc0, -0x36,0xfc,0x1e,0x44,0xd7,0xc0,0x06,0x38,0x08,0xa3,0xba,0x50,0x9f,0xa8,0x88,0x67, -0xa6,0xbb,0xdf,0x94,0xbe,0x57,0x31,0xe8,0xac,0x40,0x9b,0xf2,0x03,0xfb,0xbc,0xae, -0xee,0x77,0x94,0x46,0xaf,0xd1,0xb7,0x94,0xb3,0xe1,0x49,0x98,0x01,0x9f,0xe8,0xbb, -0x4a,0xf3,0x6d,0xbc,0x59,0xaf,0xa4,0x34,0x5c,0x0a,0xb3,0xe0,0x3d,0x68,0x4d,0x5b, -0x2d,0x58,0x35,0xe8,0xd4,0x45,0x0f,0xe3,0xbe,0x02,0x1d,0x03,0xdb,0x61,0x3f,0xfe, -0x15,0xf8,0xe1,0xfe,0x06,0xf6,0x69,0x10,0x5f,0x8b,0xfc,0xe2,0xbe,0x1e,0x1e,0x81, -0x61,0xd0,0xb7,0x96,0xfb,0x5d,0x6b,0xdf,0x1e,0x66,0x6e,0x4a,0xd0,0xd9,0x86,0xfe, -0x40,0xbf,0xd2,0x0c,0x4e,0xaf,0xed,0x7e,0xeb,0x9a,0x0f,0xdd,0x4e,0xb8,0xbb,0xa9, -0xd3,0x13,0xdb,0xb8,0x2f,0x00,0x1e,0xc6,0xff,0xf9,0x91,0x3c,0xef,0x35,0x3c,0xf2, -0x6d,0x67,0x79,0xb8,0x0c,0xda,0xc2,0x6b,0xc9,0x41,0xa7,0x19,0xc7,0x1d,0xc2,0x3e, -0x16,0x2e,0xb8,0xc2,0xfd,0x66,0xb6,0x5f,0x4a,0xd0,0xb9,0x13,0x2d,0x0b,0x25,0x7b, -0x3b,0xb9,0xdf,0x45,0x66,0xa2,0x4f,0xc0,0x2e,0x98,0x7a,0x25,0xf6,0xb4,0xa0,0xb3, -0x09,0x3d,0xad,0x0e,0x71,0xf6,0xe1,0xf7,0x41,0xcd,0xa0,0x73,0x21,0x7a,0x3e,0xee, -0x41,0x66,0x5d,0x6c,0x7d,0x3f,0xfa,0x0a,0xdc,0x51,0x2b,0xe8,0x14,0xe0,0x5a,0x1f, -0x42,0xcf,0xef,0xcb,0xef,0x81,0x3a,0x41,0x67,0x31,0xba,0x06,0x7a,0xc1,0x29,0xfd, -0xa8,0x53,0xfd,0xdc,0x6f,0x75,0xcd,0xf7,0xa6,0xbd,0x61,0x38,0x8c,0x85,0x32,0x75, -0x83,0x4e,0x0a,0x5a,0x9d,0x7e,0xad,0x3f,0x24,0xd7,0x75,0xbf,0x45,0x5d,0x01,0xef, -0xd6,0x0b,0x3a,0x07,0xd9,0xd7,0x9f,0xfe,0xf2,0xb2,0xfa,0xa4,0x19,0x96,0x63,0x4f, -0xcf,0x76,0xbf,0x53,0x2d,0x0d,0xf7,0x11,0xbe,0x39,0x7a,0x1d,0x0c,0x83,0xe7,0xe1, -0x1c,0xfa,0x84,0x2c,0xfa,0xe4,0xee,0x99,0x41,0xe7,0x61,0x74,0x7a,0xc3,0xa0,0x53, -0x79,0x90,0xfb,0x3d,0x6b,0x6b,0xc8,0xd7,0x38,0xe8,0x8c,0x44,0x27,0x1a,0xbf,0x26, -0x41,0xa7,0x64,0xd3,0xa0,0x73,0xcd,0x60,0xda,0x13,0x3c,0x57,0x8f,0xdf,0x44,0xe8, -0xfc,0x7a,0x2e,0x4d,0x86,0x38,0xce,0x25,0x43,0xdc,0xef,0x5f,0xc7,0x9a,0xef,0xdf, -0xa1,0x74,0x7d,0xc7,0x39,0x9b,0xbe,0x37,0xed,0xbc,0xa0,0x53,0xcf,0xac,0x41,0x8d, -0x9a,0x6f,0xfc,0x1f,0xa3,0xfe,0x16,0xa5,0xdf,0x9c,0xd2,0x2a,0xe8,0xcc,0x87,0x46, -0xad,0x83,0xce,0x61,0xc2,0xe6,0xcb,0x70,0xbf,0x91,0x3d,0x80,0xfb,0x6b,0x38,0xbd, -0x4d,0xd0,0x49,0x86,0x32,0xc3,0xb8,0x06,0xc2,0x7c,0x3f,0x6b,0xbe,0x4b,0xbd,0x0d, -0x6d,0x8c,0xde,0x97,0x61,0xbe,0x05,0xa7,0x3c,0x87,0xbb,0x6b,0x1b,0x98,0xef,0x6a, -0xb7,0xe1,0x57,0xb1,0x7d,0x30,0xf7,0x3b,0xe6,0x07,0xd1,0x37,0xc0,0x7c,0x8b,0xbd, -0x1b,0xaa,0x37,0x70,0xbf,0x67,0x6e,0x0e,0xad,0x60,0x44,0xc7,0xa0,0xf3,0xcc,0xc5, -0x2e,0x5f,0xe1,0x0e,0x72,0x73,0x9e,0xde,0x89,0xf2,0xbb,0x96,0x3e,0x0f,0x2a,0xe1, -0x1e,0x79,0x49,0xd0,0x59,0x37,0x82,0xdf,0x85,0xd8,0x77,0xa2,0xdb,0x34,0x49,0xe0, -0xa6,0xce,0x41,0xa7,0x3b,0x1a,0xa0,0x5e,0xad,0x40,0x83,0xd7,0x73,0x3f,0x87,0x14, -0xdc,0x95,0xbb,0x05,0x9d,0x26,0xd0,0x11,0xfb,0x87,0xe8,0x35,0x37,0x70,0x6d,0xbb, -0x07,0x9d,0x89,0xe8,0x21,0x7d,0xe3,0xfb,0xd1,0xe5,0x41,0x27,0xb6,0x07,0x7d,0x3c, -0xf6,0xa9,0x68,0x36,0xda,0x99,0xba,0xf8,0x3e,0xc7,0xcc,0xc6,0x1e,0x31,0xca,0x71, -0x0a,0x70,0xf3,0xad,0x87,0xce,0xa5,0xff,0x79,0xa6,0x57,0xd0,0x99,0x0b,0xf7,0x10, -0xe6,0xe1,0x64,0xb7,0x7d,0x47,0xc2,0xa3,0xe3,0x69,0xcf,0x84,0xbb,0x0b,0xfb,0x6d, -0xf0,0x08,0xcc,0xa1,0x1f,0x9c,0x65,0x14,0x16,0xc2,0x0e,0xa8,0xd3,0x27,0x98,0xbb, -0x36,0xc2,0x59,0x63,0xb8,0xe6,0xf0,0x3c,0xc4,0xc2,0x42,0xf8,0x7c,0x8c,0xfb,0xed, -0x76,0x5b,0x74,0x15,0xbc,0x05,0x39,0x90,0xd6,0x37,0xe8,0xf4,0xe9,0x47,0x9a,0xd0, -0xaa,0x3c,0xac,0xec,0x27,0xcc,0x01,0x38,0x65,0x2c,0xe1,0x06,0x07,0x9d,0xcb,0xd1, -0x6a,0x03,0x82,0xce,0xa5,0xec,0xeb,0x0a,0xb3,0xb0,0xaf,0xcb,0xe6,0x18,0xec,0xad, -0xb5,0x06,0x45,0x2f,0xe8,0x0d,0xd3,0x60,0x1c,0x14,0xbe,0x99,0xfe,0xed,0x26,0xf7, -0xdb,0xe6,0xca,0xd0,0x07,0x6e,0x82,0x0d,0x1c,0xf3,0x29,0x1a,0x18,0x47,0x5f,0x84, -0xbd,0x8b,0xd6,0x04,0xef,0x35,0x34,0xe8,0x6c,0x40,0x3f,0x81,0x54,0x1e,0x96,0x0e, -0xa1,0xaf,0x70,0xaf,0x7a,0x61,0x78,0xd0,0xe9,0x79,0x75,0xd0,0x59,0x84,0xdf,0x0a, -0x98,0xac,0x6f,0xa1,0xcd,0x77,0xd0,0x66,0xbd,0xf0,0xa9,0x23,0x82,0xce,0x74,0x74, -0x29,0x94,0xe6,0x19,0x61,0x18,0x54,0xbf,0x21,0xe8,0x44,0xe6,0x0b,0x3a,0xab,0xb1, -0xbf,0x0a,0xfb,0xe0,0x13,0xdc,0xef,0xdd,0xe9,0x7e,0x37,0xbd,0x6e,0x6c,0xd0,0xd9, -0x03,0xf7,0xf2,0x9c,0xb1,0x1f,0x8a,0xf1,0xbc,0x10,0x7f,0x73,0xd0,0x29,0x8d,0xd6, -0xbc,0x9b,0x34,0xc2,0x46,0xf3,0x4d,0x35,0x1c,0xbc,0x87,0xfb,0xe3,0xbd,0xa4,0xe7, -0xb6,0xa0,0x73,0xdb,0x44,0xda,0x09,0x0f,0x6f,0xf3,0x26,0x9a,0xf3,0x04,0x9d,0x1d, -0xd8,0x5f,0xbb,0x8f,0xeb,0x48,0xff,0x1d,0x7b,0x3f,0xf5,0x82,0xfb,0xec,0x19,0x77, -0xd3,0xcf,0xf3,0x20,0x35,0x14,0xb6,0xe0,0x97,0xff,0xde,0xa0,0x33,0x1b,0xfb,0x7d, -0xa8,0xf9,0x1e,0xdb,0x70,0x2e,0xbc,0x34,0x31,0xe8,0x6c,0x45,0x93,0x79,0x08,0x3c, -0x77,0x12,0xd7,0x1d,0x96,0xc1,0xcf,0xf0,0x36,0x7e,0x09,0x0f,0x05,0x73,0xbf,0xdb, -0xce,0x78,0x98,0xfb,0x03,0xbc,0xcd,0x83,0xe2,0x1e,0xe8,0xf5,0x08,0xf6,0xc9,0x41, -0xe7,0x07,0xec,0xa1,0x29,0x41,0xa7,0xf0,0xe3,0x41,0xe7,0x2e,0x30,0x6b,0x8f,0xb7, -0x6f,0xe9,0xae,0x39,0xfe,0x33,0x84,0x1e,0x73,0x9c,0x22,0x70,0x8a,0xd6,0x1c,0xcf, -0x9d,0x30,0x40,0xb1,0x07,0x23,0x22,0x43,0x51,0xd1,0xf9,0x62,0xf2,0x17,0x28,0x58, -0xa8,0x30,0x41,0x72,0x97,0xda,0x2d,0x5e,0xa2,0x64,0xa9,0x53,0xe8,0x37,0x9d,0x23, -0xdb,0x69,0xa7,0x73,0xcf,0x74,0xfe,0x17,0x5b,0x69,0xe7,0xcc,0xb3,0xca,0x94,0xfd, -0x83,0x23,0x75,0xff,0xaa,0x55,0x85,0xd8,0xb3,0x5d,0xe7,0x39,0x74,0x1e,0x95,0x2a, -0x9f,0x5b,0xa5,0x6a,0xb5,0xb8,0xf8,0xea,0x09,0x89,0x49,0xc9,0x4e,0x4a,0x6a,0xee, -0x63,0xda,0x1f,0xb4,0xd5,0xf0,0x1e,0xf5,0xfe,0xb0,0xad,0xd6,0xb1,0x3c,0x6b,0xd7, -0x39,0xca,0x59,0xb7,0x66,0x3d,0x77,0x3e,0x57,0x03,0x27,0xbc,0x85,0xb7,0xf0,0x16, -0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b, -0x78,0x0b,0x6f,0xff,0xd2,0x6d,0xc7,0xb1,0xb6,0x9d,0x39,0x6c,0x3b,0x37,0x9a,0xff, -0x73,0x56,0xad,0x3a,0xf2,0xbf,0xb6,0x80,0xd9,0x8c,0xc5,0x0b,0xbf,0x6f,0x87,0xeb, -0xc8,0xc9,0x71,0x72,0xff,0xc7,0x41,0x14,0x3b,0xbe,0xd8,0xb5,0xcb,0xfc,0x3b,0x2a, -0xea,0x70,0x89,0x1f,0xfb,0x12,0x84,0x8b,0x41,0x5b,0x6e,0xa5,0x32,0xff,0x7d,0xa2, -0xea,0xe5,0xd6,0xb9,0x5c,0xbf,0x4f,0xb6,0xef,0xc8,0x7d,0x47,0x59,0xc4,0xbc,0x9e, -0xcc,0x7d,0x3f,0x79,0xaa,0x79,0x29,0xb9,0x23,0xc7,0x29,0xfe,0x47,0xb7,0x87,0x32, -0x65,0xcb,0x9d,0xec,0xa1,0xa5,0xcd,0x3b,0xca,0x72,0xe5,0x2b,0x98,0x24,0xef,0x50, -0x2e,0x72,0xfc,0x81,0x62,0xff,0xcc,0x56,0xed,0x5a,0x72,0x72,0xd5,0x39,0x56,0x6a, -0x8e,0xb1,0x9d,0x7d,0x9c,0x98,0xdd,0x2b,0x61,0xae,0x88,0xf9,0x97,0x1b,0xbd,0x6b, -0x76,0xfc,0x4d,0xba,0xba,0x9c,0x23,0x09,0xcd,0xf9,0x4f,0x41,0x8f,0x74,0x8d,0xce, -0xaf,0xae,0x93,0xa9,0xc7,0x39,0x39,0xbf,0x2b,0xb1,0xea,0x5e,0x4f,0x32,0x7b,0x39, -0x76,0xb7,0xac,0xd4,0xeb,0x8a,0xb9,0x7b,0xa9,0xa4,0xaa,0x99,0x39,0xbf,0xbb,0xdf, -0x89,0xf6,0x7b,0xc4,0xa8,0x82,0xfc,0x49,0x17,0xf4,0x98,0xe7,0x3f,0xb9,0x52,0xfe, -0x1f,0x9f,0xdf,0x09,0x9f,0xff,0x8f,0xdc,0xfe,0x2a,0xf9,0xbf,0xb0,0xd3,0xbf,0xa1, -0xfc,0x63,0x62,0xfe,0x52,0xd7,0xff,0xcf,0x3d,0x79,0xaf,0x5e,0x47,0x9f,0xaf,0x0f, -0xdb,0x9f,0x79,0xfe,0xa1,0x43,0x8f,0x3e,0xff,0x35,0xd7,0x98,0x19,0x70,0x7f,0xe0, -0x63,0xd8,0x7f,0xba,0x7b,0xe5,0x60,0x36,0xfe,0xc6,0x88,0x47,0x8d,0x76,0x6e,0xcc, -0xb5,0x8c,0x66,0xe3,0x7f,0xee,0x5b,0x31,0xce,0xef,0xbd,0x65,0xe4,0xfc,0xd9,0x95, -0x2d,0xfa,0x18,0xb5,0xff,0x0f,0x28,0xf4,0x7f,0xde,0x2f,0x9b,0xbf,0x63,0xb2,0x8f, -0x77,0x69,0x63,0xfe,0xda,0xa9,0xff,0xa3,0x1e,0x6f,0xfe,0x5a,0xdb,0xf6,0xbf,0x58, -0x7a,0x3e,0xd9,0xf1,0x4f,0x6b,0xad,0x47,0xff,0xfc,0xf8,0x6f,0xde,0x95,0x8f,0xbc, -0x0c,0x3a,0xe9,0xd3,0x1c,0xfd,0x4a,0xc0,0xc9,0xc9,0xb1,0x7f,0x6d,0xff,0x49,0xdd, -0x4d,0xf4,0x6f,0xeb,0x0f,0x76,0xe4,0xfc,0xae,0x77,0x60,0xbf,0xe7,0x86,0x13,0xfd, -0x07,0xc5,0x93,0xf7,0xa3,0xe7,0x5f,0xe9,0x96,0x13,0xf0,0xaa,0x87,0xf9,0xdf,0x09, -0xb8,0x9b,0x75,0x7f,0xdd,0x91,0x1b,0xee,0x9f,0x72,0x8b,0xcd,0xf9,0xf5,0xbd,0xc6, -0x8e,0x5d,0x1f,0xe4,0xac,0xda,0x71,0x22,0xdb,0xce,0xed,0xdb,0x73,0x3e,0xda,0xf1, -0x45,0xce,0x3f,0xaf,0x0f,0xdb,0xb1,0x23,0x10,0x30,0x39,0x0c,0x18,0xf1,0x2e,0x36, -0xf6,0x9c,0x1d,0x7a,0x65,0xed,0xfc,0xab,0xb7,0xbf,0xda,0x13,0x59,0xcc,0x9f,0x90, -0xe1,0xff,0xb8,0x9d,0x78,0x4c,0x1f,0x6d,0xdf,0xf9,0xc9,0x96,0x2d,0x7f,0x5a,0xc9, -0xe4,0xcb,0xf7,0x8f,0xac,0x7f,0x47,0x5f,0x93,0xe3,0x5f,0xbd,0x3f,0xbe,0x3e,0xfc, -0xf6,0x6a,0xf8,0x57,0x7d,0xee,0x8f,0x39,0xa9,0xfa,0xff,0x3b,0x5e,0x09,0xe4,0xfc, -0x25,0x8a,0x29,0xfa,0xef,0xf3,0x5b,0xec,0xbf,0xd1,0x80,0x72,0x87,0x71,0x74,0x19, -0xcd,0xb8,0xc4,0x5f,0xb2,0xdb,0xfd,0xfb,0x35,0x94,0x63,0x6e,0x67,0x38,0xff,0xf6, -0x6d,0x87,0x3d,0x08,0x9b,0x13,0x1e,0xa7,0xfe,0x47,0x6d,0x39,0xe1,0x2c,0xfe,0x0f, -0x5b,0xd5,0x5f,0xb1,0xb0,0x4e,0x38,0x51,0x3b,0x02,0x39,0x7f,0x9f,0x1b,0xc9,0xef, -0xfc,0x99,0x1b,0x9e,0x9f,0xf3,0x17,0x69,0x34,0x7f,0x78,0x7d,0xcf,0xc9,0xf9,0xa3, -0x22,0x72,0x27,0xc2,0xfc,0x96,0x08,0x77,0xfc,0x03,0xde,0xc2,0xc4,0xfc,0xd6,0x90, -0xbf,0xbd,0xff,0x88,0x89,0xfe,0x83,0x2a,0xd5,0x49,0xd7,0xaa,0xdf,0xfb,0x02,0x35, -0xfa,0xbf,0x53,0xf2,0x7f,0xe2,0x2b,0xd9,0x98,0xe8,0xbf,0x5f,0x15,0x8d,0xfe,0xc3, -0x6f,0x55,0xd1,0x27,0x3e,0xff,0xe2,0xc8,0x4f,0xef,0x7f,0xde,0x64,0xcf,0x98,0xff, -0x50,0xf7,0x76,0x84,0x2b,0xe3,0x3f,0x3b,0xf3,0x31,0xff,0xa2,0x6c,0xc7,0x38,0xe1, -0x2d,0xdc,0xec,0xfe,0xff,0x93,0xc0,0x5f,0xba,0x20,0x62,0xfe,0x9c,0x38,0x63,0xfe, -0xa5,0xad,0x2d,0xdc,0x27,0xfc,0x77,0xa6,0xc2,0xc5,0xfc,0xcd,0xfa,0x80,0xf0,0xf6, -0x37,0xd9,0x7e,0x9d,0x0b,0xf2,0xdb,0x7f,0x2f,0x86,0x5f,0x07,0xfd,0x8d,0xb7,0xa8, -0xa8,0xbf,0x55,0x6d,0xfd,0x67,0x14,0xfa,0xc6,0x8d,0x6e,0xa3,0xdb,0xba,0xd1,0x6c, -0x9e,0xaf,0xf7,0x39,0xe9,0xaf,0x93,0xfa,0x8e,0xdf,0xf2,0xfe,0x8a,0x99,0xfb,0x7b, -0xf7,0x06,0xd6,0x27,0x6e,0xfe,0x69,0x02,0x47,0x76,0xfd,0x79,0x6f,0x2d,0xc2,0x1d, -0xeb,0x7f,0xb1,0x78,0xfe,0xed,0x37,0xae,0x9d,0x3b,0xff,0xf9,0xd3,0xff,0x7e,0xed, -0x53,0xff,0xf4,0x3e,0x24,0xfc,0x3e,0xe0,0xef,0xf9,0x40,0x9c,0xbb,0xfa,0x67,0x28, -0x14,0x1d,0xca,0x17,0x93,0x3f,0x14,0x2a,0x10,0xfa,0x75,0x2b,0x18,0x0a,0x15,0x2a, -0x1c,0x2a,0x52,0x34,0x64,0x6f,0xc5,0x42,0xff,0xec,0xad,0x78,0x89,0x50,0xc9,0x52, -0xa7,0x60,0x39,0x35,0x14,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7, -0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0x7f,0xc8,0x16, -0x70,0xfe,0x57,0x26,0xc2,0x09,0x39,0xd1,0xb9,0xa6,0x92,0x13,0xe7,0xd4,0x70,0x1a, -0x3a,0x3d,0x9c,0x5e,0xce,0x55,0xce,0x18,0x67,0x8a,0xf3,0x84,0xf3,0xbc,0xb3,0xc0, -0x59,0xec,0xbc,0xeb,0xec,0x77,0xbe,0xc5,0x84,0x02,0x25,0x02,0xe5,0x03,0x4d,0x02, -0x2d,0x02,0x1d,0x02,0x9d,0x02,0xfd,0x02,0x57,0x1f,0x65,0x26,0x05,0x1e,0x0b,0x4c, -0x0b,0xcc,0x0d,0x6c,0x08,0x7c,0x18,0xf8,0x3c,0xf0,0x7d,0xa0,0x44,0xf0,0xd4,0x60, -0xf9,0x60,0x5c,0x30,0x39,0x98,0x15,0xec,0x11,0xec,0x8d,0xb9,0x3a,0x78,0x73,0xf0, -0xfe,0xe0,0xc2,0xe0,0xd2,0xe0,0x3b,0xc1,0xf5,0xc1,0xcf,0x83,0x07,0xfe,0x8b,0xe6, -0xe0,0x6f,0x30,0x81,0x88,0x40,0x44,0x44,0x44,0x28,0x22,0x26,0xa2,0x40,0xae,0x29, -0xf1,0xab,0x29,0xf5,0xab,0x39,0x2b,0x22,0x36,0xe2,0x1c,0x4c,0x25,0x99,0xea,0x96, -0xa9,0x81,0x49,0x8f,0xa8,0x1d,0x51,0x3f,0xa2,0x41,0xae,0x69,0xf6,0xab,0x69,0xf1, -0xab,0x69,0x1b,0xd1,0x31,0xa2,0x13,0xe6,0x52,0x99,0x1e,0x47,0x99,0x7e,0x11,0x57, -0xfd,0x47,0x73,0x75,0xc4,0x75,0x7f,0xba,0x19,0x15,0x31,0x36,0x4f,0x73,0x73,0xc4, -0x2d,0xbf,0x9a,0xdb,0x22,0xee,0x38,0xca,0x8c,0x8f,0xb8,0x2b,0xe2,0x9e,0x88,0x89, -0x27,0x68,0xee,0xff,0xdd,0x66,0x4a,0xc4,0xf4,0xff,0x60,0xe6,0x46,0x2c,0x3e,0xca, -0x2c,0xfd,0x53,0xcc,0x6b,0x11,0x6f,0xfc,0x8f,0xcc,0x8a,0x88,0x37,0x23,0x56,0x47, -0xac,0xf9,0xd5,0xbc,0x13,0xf1,0xee,0x3f,0xc2,0xac,0x0f,0x9b,0x7f,0xb5,0xf9,0x54, -0xe6,0x0b,0xcc,0x01,0x4c,0x20,0xf2,0x88,0x89,0xc1,0x14,0x90,0x39,0x55,0xa6,0x7c, -0xae,0x89,0xc5,0x9c,0x93,0x6b,0x52,0x65,0xea,0x62,0x9a,0x61,0x3a,0x5a,0xa6,0x2b, -0xa6,0x87,0xcc,0x55,0x32,0xa3,0x22,0x6f,0x8e,0xbc,0x23,0xd7,0x8c,0x97,0xb9,0xe7, -0x4f,0x33,0x13,0x8f,0x61,0x26,0xe5,0x9a,0x87,0x30,0x53,0x22,0x9f,0x8a,0x7c,0x26, -0x72,0x06,0x66,0x96,0xcc,0x9c,0x5f,0xcd,0x2b,0x32,0x0b,0x65,0x16,0x63,0x96,0xe6, -0x9a,0x37,0x72,0xcd,0x0a,0xcc,0x9a,0xc8,0x77,0x23,0xd7,0x47,0x6e,0xc2,0x7c,0x2c, -0xb3,0xf9,0x57,0xf3,0xb9,0xcc,0x17,0x32,0xbb,0x31,0x7b,0xfe,0x9f,0xd9,0xfb,0x3b, -0xcc,0xfe,0xc8,0x03,0xff,0x08,0xf3,0x6d,0xe4,0xf7,0xff,0xd1,0x1c,0x8c,0xfc,0xf1, -0xa4,0x4c,0x78,0x28,0xf0,0xdf,0xbe,0xfd,0x18,0xf9,0xc7,0x9a,0x40,0xe8,0xf7,0x1a, -0x1e,0x89,0xff,0x65,0xbf,0xc7,0xa2,0x8f,0x6b,0x62,0x72,0x4d,0x81,0xbf,0x89,0x29, -0xe4,0x33,0x45,0x72,0x4d,0xb1,0xb0,0x39,0x21,0x53,0xea,0x5f,0x6f,0x4a,0xff,0x25, -0xcd,0x59,0x7f,0x92,0x29,0x1f,0x8a,0xfd,0x97,0x9b,0x73,0xfe,0x75,0xa6,0xd2,0x51, -0x26,0x4e,0xa6,0xfa,0xbf,0xd6,0x24,0x87,0x52,0xc3,0xe6,0xb8,0xa6,0x76,0xae,0xa9, -0xfb,0x17,0x34,0xf5,0xc3,0xe6,0x6f,0x6e,0x1a,0x60,0x1a,0x62,0xb2,0x30,0x4d,0x30, -0xcd,0x64,0x5a,0xfc,0xe1,0xa6,0x25,0xa6,0x15,0xa6,0x4d,0xa8,0x6d,0xae,0x69,0x17, -0x36,0xff,0x12,0xd3,0x21,0xd4,0xf1,0x0f,0x31,0x9d,0x42,0x97,0x1e,0x65,0x3a,0x87, -0xba,0xfe,0xcf,0x4c,0x8f,0x50,0xef,0x93,0x32,0x7d,0x42,0xfd,0x4e,0xc8,0x0c,0x08, -0x0d,0xfc,0x93,0xcc,0xd5,0xa1,0x51,0xa1,0x31,0xa1,0xb1,0xa1,0x9b,0x43,0xb7,0x84, -0x6e,0x0b,0xdd,0x11,0x9a,0x18,0x7a,0x24,0xf4,0x58,0x68,0x4a,0xe8,0x89,0xd0,0x53, -0xa1,0x67,0x42,0xd3,0x42,0xcf,0x87,0x66,0x1d,0x65,0xe6,0x86,0x16,0x1c,0x65,0x96, -0x86,0xde,0x0c,0xad,0x0e,0xad,0x09,0xbd,0x13,0x7a,0x37,0xb4,0x3e,0xf4,0x7e,0xe8, -0xe3,0xd0,0x67,0xa1,0xcf,0x43,0x5f,0x84,0x76,0x87,0xf6,0x84,0xf6,0x86,0xf6,0x87, -0xbe,0x0f,0xfd,0x74,0x94,0x89,0x88,0x8a,0x39,0xca,0x14,0x8a,0x2a,0x76,0x94,0x29, -0x15,0x75,0xfa,0x51,0xa6,0x6c,0xd4,0x39,0x3e,0x53,0xf5,0x28,0x93,0x1c,0x55,0x3b, -0xaa,0x6e,0x54,0xfd,0xa8,0x06,0x51,0x0d,0xa3,0xb2,0xa2,0x9a,0x44,0xb5,0x8a,0xea, -0x10,0xd5,0x31,0xaa,0x53,0xd4,0xa5,0x51,0x9d,0xa3,0xba,0x46,0x75,0x8f,0xea,0x21, -0xd3,0xcb,0x67,0x7a,0xff,0x0f,0x4c,0x9f,0xff,0x67,0x06,0x1e,0x65,0x06,0xff,0x97, -0xcd,0x55,0x3e,0x73,0x6d,0xd8,0x9c,0x80,0xb9,0x0e,0x73,0x03,0x66,0xd4,0xff,0xc8, -0x8c,0xc1,0x8c,0xc5,0xdc,0xfc,0x5f,0x36,0xb7,0xfc,0x6a,0x6e,0xcb,0x35,0x77,0x9c, -0xa4,0x19,0x8f,0xb9,0xeb,0xa4,0xcc,0x3d,0x47,0x99,0x89,0x98,0xfb,0x73,0xcd,0xa4, -0x5f,0xcd,0x43,0x27,0x68,0x1e,0xc9,0x35,0x8f,0x45,0x4d,0xc9,0xd3,0x3c,0x11,0xf5, -0x54,0xae,0x79,0x26,0x6a,0x5a,0xae,0x99,0x1e,0xf5,0x7c,0xd4,0x8c,0xa8,0x59,0xbf, -0x9a,0x39,0x51,0x73,0x73,0xcd,0xbc,0xa8,0x05,0xc7,0x30,0xaf,0x44,0x2d,0x3c,0x8e, -0x59,0x6c,0x99,0xa5,0x51,0xaf,0x61,0xde,0x38,0xca,0xac,0x88,0x7a,0x33,0x6a,0x75, -0xd4,0x9a,0xb0,0x39,0xae,0x79,0xe7,0x57,0xf3,0x2e,0x66,0x3d,0xe6,0x7d,0xcc,0x06, -0xcc,0x87,0x98,0x4d,0x98,0x8f,0x31,0x9b,0x31,0x9f,0x62,0xb6,0x61,0x3e,0xc3,0x7c, -0x9e,0x6b,0xbe,0xc0,0xec,0xc6,0xec,0x39,0xca,0xec,0xcf,0x35,0xdf,0xe6,0x9a,0x83, -0xb9,0xe6,0xa7,0x5c,0x13,0x88,0xfe,0xfd,0x26,0xe2,0xff,0x99,0x23,0x2f,0x72,0xcd, -0x16,0x83,0x29,0x80,0x29,0x84,0x29,0x82,0x29,0x86,0x29,0x81,0x29,0x85,0x39,0x15, -0x73,0x3a,0xa6,0x34,0xe6,0xac,0x5c,0x53,0x16,0x53,0x1e,0x13,0x7b,0x94,0xa9,0x94, -0x6b,0xaa,0xe6,0x9a,0xea,0xb9,0x26,0x39,0xd7,0xd4,0x38,0x41,0x93,0x1e,0x5d,0x3b, -0xba,0x6e,0x74,0xfd,0x63,0x9a,0x06,0x61,0xf3,0x1b,0x4d,0x43,0x4c,0xd6,0x31,0x4c, -0x93,0xb0,0xc9,0xc3,0x34,0x8b,0x6e,0x11,0x36,0x61,0x73,0x82,0xa6,0x55,0xd8,0x84, -0xcd,0x3f,0xde,0xb4,0x0b,0x9b,0x7f,0xb5,0xe9,0x10,0xdd,0xf1,0x5f,0x6b,0x3a,0x85, -0xcd,0x5f,0xd0,0x84,0x67,0x49,0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f, -0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc, -0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0, -0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2, -0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b, -0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d, -0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7, -0xf0,0x16,0xde,0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde, -0xc2,0x5b,0x78,0x0b,0x6f,0xe1,0x2d,0xbc,0x85,0xb7,0xf0,0x16,0xde,0xc2,0xdb,0xdf, -0x6f,0xab,0x19,0xe1,0x38,0xfd,0x2b,0xb8,0xda,0x45,0x3a,0x00,0x75,0xd0,0x25,0x48, -0x00,0x5d,0x8a,0x06,0xd1,0x65,0xc6,0x1b,0x7d,0x0d,0x8d,0x8c,0x30,0x3b,0x1d,0x27, -0x0a,0x0d,0xa2,0xd1,0x68,0xc5,0xa0,0xe3,0xe4,0x43,0x5f,0x67,0x57,0x0c,0xfa,0x78, -0x19,0xc7,0xc9,0x8f,0xbe,0x81,0xbb,0x20,0xba,0x86,0x70,0x45,0xcc,0x71,0x84,0x2b, -0x66,0x8e,0x43,0x63,0xd1,0xc9,0x84,0x3b,0xc7,0xf8,0xa3,0x95,0xd1,0x48,0xc5,0x17, -0x2c,0xe3,0xee,0x8f,0x44,0x13,0xd1,0xa8,0x32,0x6e,0x7a,0xa2,0x02,0x6e,0x7a,0xf2, -0x29,0x5c,0xfe,0x80,0x1b,0x7f,0x41,0xb4,0x12,0xba,0xc2,0x91,0x7f,0x19,0xd7,0xbf, -0x70,0xc0,0x8d,0xa7,0x60,0x19,0x77,0x7f,0x51,0x34,0x0e,0x2d,0xae,0x78,0x0b,0x97, -0x71,0xf3,0xbb,0x52,0xf9,0x7d,0x53,0xf9,0x5d,0xa5,0xfc,0xbe,0x5a,0xc6,0xcd,0x6f, -0x71,0xe5,0xb7,0xa4,0xce,0x7b,0x4a,0xc0,0xcd,0xef,0x6a,0xe5,0xf7,0x65,0x9d,0xef, -0x0c,0xe5,0x33,0x52,0xf9,0x8c,0x52,0x3e,0xe7,0xeb,0xfc,0x95,0x83,0x6e,0x3e,0x4f, -0x0b,0xb8,0xe9,0x38,0x0d,0xff,0x04,0x73,0x5c,0x19,0x37,0xde,0x32,0xca,0xe7,0x37, -0xca,0xe7,0x99,0x01,0xf7,0xfc,0xe5,0xca,0xb8,0xe7,0x2f,0xa3,0xf3,0x97,0x53,0xbe, -0x77,0x28,0xdf,0x15,0x14,0xfe,0x6c,0xed,0xaf,0x50,0xc6,0x4d,0x5f,0x45,0xa5,0xab, -0x8c,0xd2,0x71,0xb6,0x97,0x0e,0x34,0x1e,0xad,0xa2,0xfc,0xbf,0xa5,0xfc,0xaf,0x51, -0xfe,0xdf,0x56,0xfe,0x2b,0xeb,0xfc,0x55,0x94,0xff,0x6a,0x8a,0x3f,0x41,0xf9,0x7f, -0x47,0xf9,0x5f,0xa9,0xf3,0xbc,0xa3,0xfc,0xe7,0x53,0xfe,0xf3,0xeb,0xbc,0xab,0x74, -0x7d,0xe3,0x03,0x6e,0x7e,0xab,0x95,0x71,0xcf,0x93,0x12,0x70,0xcf,0x9b,0xa0,0xe3, -0x77,0xea,0x7a,0x25,0x29,0x9d,0x69,0x01,0xf7,0x3a,0xa5,0xa8,0x5c,0xd2,0x95,0xef, -0xba,0x0a,0x97,0xa6,0x70,0xb5,0x55,0x9e,0x75,0x74,0x5d,0x6b,0x29,0x7c,0x3d,0x95, -0x6b,0x86,0xea,0x53,0xa6,0xf2,0xfb,0x83,0xce,0xdb,0x30,0xe0,0xe6,0xf3,0x5d,0xd5, -0x9b,0x46,0x4a,0x47,0x13,0xc5,0xdf,0x58,0xf1,0x67,0x29,0xfe,0xe6,0x8a,0xbf,0xa9, -0xe2,0x59,0xa7,0x72,0x5b,0xaf,0x72,0x7b,0x4f,0xe5,0xf6,0xbe,0xda,0xc9,0x79,0x2a, -0xb7,0xf3,0x55,0x6e,0xf1,0x6a,0x27,0x1f,0xa8,0xdc,0xde,0x26,0x9e,0x02,0x68,0x6b, -0xe5,0xeb,0x5d,0x95,0x5f,0x41,0x95,0x5f,0x61,0xb4,0xb4,0x69,0x27,0x4a,0xcf,0x5b, -0x2a,0xc7,0x5d,0x4a,0xff,0x85,0xca,0x5f,0x7b,0xe5,0xf7,0x42,0x9d,0xe7,0x22,0xe5, -0xe3,0x22,0xc5,0x57,0x54,0xf1,0x15,0xd7,0xf5,0xb8,0x58,0xf9,0x6a,0xaf,0xf2,0xbd, -0x4c,0xc7,0x5f,0xac,0x7a,0x74,0x89,0xe2,0xe9,0xa2,0x78,0xba,0xe8,0xfc,0xdd,0x70, -0x57,0x34,0xf5,0x36,0xe8,0x1e,0x7f,0x99,0xc2,0x5d,0xa9,0x70,0x97,0x2b,0x5c,0x5f, -0x85,0x3b,0x43,0xe1,0xba,0xa9,0xfc,0xb2,0x55,0x7e,0xfd,0x55,0x7e,0x1f,0xaa,0xfc, -0x3e,0x52,0xf9,0x6d,0x52,0xf9,0x5d,0xa1,0x7a,0x77,0xa5,0xca,0xaf,0xaf,0xea,0x5b, -0x8e,0xca,0x6d,0x87,0xfa,0x97,0xfe,0x01,0xb7,0xfc,0xb2,0x55,0x7e,0xeb,0x95,0xdf, -0x92,0xca,0xef,0x29,0x2a,0xbf,0x19,0x4a,0xd7,0x76,0x8e,0x3b,0x1b,0x1d,0xa4,0x72, -0x1c,0xa4,0x74,0x0d,0x55,0x3b,0x1c,0x52,0xc6,0x3d,0xff,0x08,0xd5,0x97,0xeb,0x95, -0xce,0x91,0x2a,0x9f,0xd1,0xaa,0xb7,0x43,0x94,0xef,0x9b,0xd4,0xce,0x6e,0x54,0xf8, -0x71,0xba,0x1e,0xb7,0xca,0xff,0x76,0x1d,0xff,0xb5,0x8e,0xff,0x58,0xf9,0x1c,0xaa, -0x7a,0xf7,0x83,0xfa,0x97,0xa4,0xa0,0x7b,0xdc,0x9d,0x3a,0x6e,0x78,0xc0,0xcd,0xe7, -0x77,0x8a,0xf7,0x1b,0x1d,0x7f,0xb5,0xae,0xcf,0x04,0x9d,0xe7,0x5a,0xe5,0xfb,0x3a, -0xe5,0xef,0x6e,0x95,0xef,0xbd,0x3a,0xef,0x16,0x95,0xef,0xa7,0x3a,0xef,0x56,0x95, -0xef,0x36,0xd5,0xcf,0x80,0xca,0x37,0x42,0xf9,0xa9,0xa4,0xfa,0xb9,0x5d,0xe5,0xfc, -0x84,0xca,0xf9,0x33,0xf5,0xe3,0x6f,0xab,0x7c,0x03,0x2a,0xdf,0x08,0xd5,0xa7,0x29, -0xea,0xc7,0x03,0x2a,0xd7,0x90,0xe2,0x8b,0x50,0xfa,0x43,0x4a,0x57,0xb4,0xf2,0x11, -0xad,0x7c,0xc4,0x28,0x5c,0x01,0xe5,0xa3,0x90,0xfa,0xb3,0xcf,0xd5,0x1e,0x0b,0xa8, -0x5e,0x15,0x51,0xfe,0x0a,0xa9,0xde,0x16,0x53,0x3f,0x5e,0x42,0xf1,0x16,0x51,0x7e, -0x77,0x2a,0xbf,0x5f,0x28,0xbf,0xbb,0x94,0xdf,0x85,0x2a,0xe7,0x12,0xca,0x6f,0x29, -0x9d,0xf7,0x54,0xd5,0xab,0xdd,0xca,0xef,0x2b,0x3a,0x5f,0x69,0xe5,0x33,0xa4,0x7c, -0x46,0x2b,0x9f,0x0b,0x74,0xfe,0x73,0xd5,0x8f,0x9f,0xae,0xfa,0x73,0xba,0xea,0x4f, -0x69,0x5d,0x97,0xb2,0xca,0xe7,0xb7,0xca,0xe7,0x59,0xaa,0xcf,0xe5,0xd5,0x8f,0x97, -0xd5,0xf9,0xcb,0x2b,0xdf,0x9f,0x2b,0xdf,0xb1,0x0a,0x7f,0x8e,0xf6,0xc7,0xaa,0x3e, -0x54,0x52,0xba,0xca,0x2a,0x1d,0xe7,0x78,0xe9,0x50,0x3f,0x5e,0x55,0xe5,0x70,0x9f, -0xca,0xe1,0x4b,0x95,0xc3,0x1e,0x95,0xc3,0x57,0x2a,0x87,0x73,0x95,0x8e,0xaa,0x2a, -0x87,0x38,0xe5,0x7f,0xaf,0xf2,0xff,0xa6,0xce,0xb3,0x56,0xf9,0x8f,0x51,0xfe,0x0b, -0xe8,0xbc,0xab,0x75,0x7d,0xab,0xab,0x1f,0x8f,0x53,0x7b,0x48,0x55,0x3e,0xf6,0xa9, -0x5f,0x4a,0x54,0x3c,0x5f,0xe8,0xba,0x25,0x2b,0xbd,0x35,0xd4,0xdf,0xa4,0xaa,0x7c, -0x6a,0xe9,0xb8,0x7a,0x0a,0x57,0x43,0xe1,0xea,0xa8,0x5c,0xeb,0x2a,0x5f,0xb5,0x15, -0xbe,0xbe,0xca,0xb7,0x81,0xea,0x55,0x43,0xe5,0xf7,0xa0,0xce,0xdb,0x48,0xed,0x6a, -0xbf,0xea,0x4f,0x96,0xd2,0xd1,0x54,0xf1,0x37,0x51,0xfc,0x8d,0x15,0x7f,0x0b,0xc5, -0xdf,0xcc,0x6b,0xa7,0x2a,0xb7,0x03,0x2a,0xb7,0x6f,0x54,0x6e,0xdf,0xaa,0xbd,0xb4, -0x54,0xb9,0xb5,0xd2,0xf5,0xa9,0xae,0xf6,0xf2,0x9d,0xca,0xef,0x1d,0xf5,0xe7,0x6d, -0x94,0xaf,0x75,0x2a,0xc7,0x42,0x2a,0xc7,0x22,0xea,0x8f,0xa6,0x28,0x3d,0x6b,0x54, -0x9e,0xbb,0x95,0xfe,0x76,0xca,0x5f,0x07,0xe5,0xb7,0x9d,0xce,0xd3,0x51,0xf9,0xe8, -0xa8,0xf8,0x8a,0x29,0xbe,0x12,0xba,0x2e,0x9d,0x94,0xaf,0x0e,0x2a,0xdf,0xce,0x3a, -0xbe,0x93,0xea,0xd3,0xa5,0x8a,0xa7,0xab,0xe2,0xe9,0xaa,0xf3,0x77,0x57,0x3f,0x7d, -0xba,0xfa,0xe9,0xce,0x0a,0xd7,0x47,0xf5,0xee,0x7e,0x85,0xef,0xa1,0xf0,0xfd,0x14, -0xbe,0xb4,0xc2,0x77,0x57,0x39,0x0e,0x54,0x39,0x0e,0xf0,0xae,0x87,0xca,0xf1,0x90, -0xca,0xf1,0x47,0x95,0x63,0x6f,0xd5,0xbf,0x3e,0x2a,0xc7,0x7e,0xaa,0x7f,0x87,0x55, -0x7e,0x9f,0xab,0xbf,0x19,0xa0,0x7e,0x7d,0xa0,0xca,0xf1,0x3d,0xe5,0xbb,0x94,0xf2, -0x7d,0xaa,0xca,0x71,0xa6,0xd2,0xf5,0x99,0xfa,0xf5,0xc1,0x2a,0xcf,0xc1,0x4a,0xd7, -0x30,0xb5,0xcb,0xab,0xd4,0xaf,0x5f,0xa7,0x7a,0x73,0x83,0xd2,0x39,0x4a,0xe5,0x34, -0x46,0xf5,0xf8,0x2a,0xe5,0xff,0x66,0xe5,0x7f,0xac,0xd2,0xf3,0x80,0x8e,0xbb,0x45, -0xd7,0xe7,0x36,0xed,0xbf,0x43,0xf1,0x1c,0x50,0x3c,0x3f,0x29,0xbf,0xc3,0x54,0x0f, -0x0f,0xaa,0xdf,0x49,0x56,0xff,0x3e,0x5e,0xc7,0xfd,0xa2,0xfc,0x7e,0xaf,0xf8,0x27, -0x29,0xfe,0x6f,0x15,0xcf,0x35,0xba,0x6e,0x77,0xe9,0x7c,0x23,0x54,0x0e,0xd7,0x2b, -0xbf,0xf7,0xa8,0xbc,0x27,0xea,0xfc,0x7b,0xca,0xba,0xc7,0x8d,0x35,0xcf,0x38,0xe6, -0xb9,0xbd,0xac,0xeb,0xff,0xb4,0xe2,0x7b,0x52,0xf9,0x7f,0xb6,0x8c,0x7b,0xde,0xa9, -0x4a,0xd7,0x27,0xaa,0x77,0x87,0xa4,0x87,0x75,0x7d,0xb6,0xca,0x9d,0x4f,0xf1,0x2f, -0x51,0x3c,0x8b,0x14,0xcf,0xeb,0x8a,0x67,0x99,0xfc,0xe3,0xbd,0xe7,0x21,0xb9,0xd7, -0x2a,0xdc,0x07,0x0a,0xf7,0x9e,0xfc,0xcf,0xd3,0x79,0xab,0xa9,0xdd,0x9c,0x2f,0x77, -0x95,0xa0,0xbb,0xff,0xe7,0x80,0x1b,0xcf,0x26,0x9d,0x7f,0x83,0xd2,0xf3,0xbe,0xf2, -0xff,0xa1,0xf6,0x3f,0xa3,0xf8,0x9e,0xd2,0x79,0xa6,0xeb,0x3c,0xd3,0x14,0xdf,0x66, -0x1d,0xff,0xa3,0xf4,0x27,0xc5,0xb3,0x4d,0xee,0x18,0xa5,0x77,0xa9,0xe2,0x59,0xac, -0x78,0xde,0x50,0x3c,0xaf,0xc9,0xbf,0xba,0xc2,0xad,0x97,0xfb,0x5d,0x85,0xdb,0xa0, -0x70,0xef,0xcb,0xbf,0xa5,0xce,0x1b,0xa7,0x7c,0xb5,0x92,0xbb,0xaa,0xf2,0xf5,0x8b, -0xd2,0x9d,0xa3,0xf3,0x6f,0x54,0x7a,0x3e,0x50,0xbe,0x3e,0xd2,0xfe,0x99,0x8a,0xef, -0x05,0x9d,0xe7,0x45,0x9d,0x67,0xb6,0xd2,0x31,0x4b,0xfb,0x67,0x68,0xff,0x5c,0xed, -0x9f,0xa3,0xfd,0x25,0xb5,0xff,0x14,0xb9,0x4b,0xc9,0x7d,0xaa,0xdc,0xad,0xe5,0xbe, -0x40,0xee,0x36,0x72,0xb7,0x55,0x3d,0xbb,0x5c,0xda,0x43,0xda,0x53,0xda,0x4b,0xe1, -0x86,0x7b,0xcf,0x21,0xaa,0xc7,0xd7,0x48,0xaf,0x55,0xb8,0x87,0x14,0x6f,0x8e,0xc2, -0x7d,0xa4,0x74,0x6e,0x51,0x3a,0x3f,0x51,0x3d,0xdf,0xaa,0x70,0x1f,0x2b,0xdc,0x26, -0x85,0xfb,0x54,0xe1,0x36,0x2b,0xdc,0x36,0x85,0xdb,0xa5,0x70,0x3b,0x15,0xee,0x2b, -0x85,0xfb,0x52,0xe1,0xf6,0x29,0xdc,0x6e,0x85,0xfb,0x42,0xe1,0xf6,0x2a,0xdc,0x1e, -0x85,0xdb,0xaf,0xfe,0xec,0x2b,0x95,0xff,0x97,0xd2,0x3d,0xd2,0x2d,0xd2,0x4f,0xd5, -0x3f,0x9d,0xa9,0xfb,0xf5,0x59,0xba,0x9e,0x09,0x41,0x57,0x13,0x83,0x6e,0x3c,0x7b, -0x75,0xdd,0x5e,0x56,0xfb,0xbb,0x29,0xc2,0xdd,0xbf,0xa0,0xac,0x7b,0xdc,0x7c,0xb4, -0x84,0x69,0x47,0x68,0x49,0x53,0xef,0xd0,0x2c,0xf3,0xbb,0x52,0xed,0x73,0xa5,0x8e, -0x1b,0x17,0xe1,0x86,0x3b,0x4d,0xe1,0x4e,0xd7,0xfe,0x55,0xda,0x7f,0x8b,0xf6,0x57, -0xd6,0xfe,0x73,0x15,0xcf,0x5b,0x0a,0xf7,0x91,0xc2,0xdd,0xaa,0xf3,0x7f,0xa8,0xf3, -0x6f,0x2c,0xeb,0x5e,0x9f,0x67,0x75,0x7c,0x4d,0x1d,0x9f,0xae,0xe3,0x7e,0xd0,0x71, -0xb7,0x6b,0x7f,0x53,0xed,0x6f,0x26,0xfd,0x52,0xe1,0x76,0x29,0xdc,0x9d,0x8a,0xff, -0x0b,0xc5,0xbf,0x53,0xf1,0x4f,0x8f,0x70,0xc3,0x77,0x54,0xf8,0x43,0x0a,0x3f,0x5e, -0xf1,0x5e,0xa1,0xf8,0x7a,0x2b,0xdd,0x87,0xa5,0x2f,0x4a,0x5f,0xd7,0x71,0xd9,0x3a, -0xee,0x39,0x9d,0x67,0x96,0xce,0x33,0x53,0xe5,0xe8,0x28,0x9e,0x40,0x59,0xb7,0x9f, -0x7b,0x49,0xc7,0xcf,0xd1,0xf1,0x83,0x74,0xfc,0xf3,0x3a,0x6f,0x51,0x85,0x2f,0xa6, -0xfd,0x43,0xb4,0xff,0x05,0xed,0x2f,0xa7,0xfd,0xe5,0x15,0xdf,0x72,0xc5,0xf7,0x9a, -0xc2,0x0f,0x55,0xf8,0x19,0x4a,0xcf,0xbb,0x4a,0xcf,0x5a,0xe5,0x7b,0x95,0xe2,0x49, -0x50,0x3c,0x89,0x8a,0xe7,0x03,0x1d,0x3f,0x5c,0xc7,0xbf,0xa5,0x70,0x19,0x0a,0xd7, -0x40,0xf9,0xd9,0x2e,0xf7,0x67,0x0a,0x7f,0x8d,0xc2,0xaf,0xd1,0xf9,0x3e,0xd5,0xf9, -0xb6,0xe8,0x7c,0xab,0x15,0x4f,0x6b,0x1d,0xd7,0x46,0xe7,0xdb,0xa1,0xe3,0x47,0xe8, -0xf8,0xb7,0x15,0xae,0x8b,0xc2,0x75,0x55,0xb8,0xef,0x94,0xbf,0x03,0x0a,0xbf,0x4e, -0xe1,0x27,0xe8,0xbe,0xb1,0x41,0xfe,0x5b,0xe5,0x3f,0x53,0xfe,0x9f,0xeb,0xb8,0x6f, -0xe4,0xbf,0x3e,0xc2,0x4d,0xcf,0x7b,0xd2,0xe8,0x48,0x95,0x4b,0xa4,0xeb,0x5e,0x17, -0xe9,0x86,0x8b,0x88,0x74,0xe3,0x73,0xb4,0xff,0x03,0xed,0x0f,0xc8,0xbd,0x54,0xee, -0x1c,0xe9,0x27,0x0a,0x5f,0x28,0x52,0xf7,0x21,0x85,0x3b,0x5d,0xfb,0xb7,0x2b,0xde, -0x82,0x72,0xef,0x50,0xb8,0xaf,0x14,0xae,0xb0,0xfc,0xf7,0x45,0xba,0xcf,0x0f,0x5f, -0xcb,0xfd,0x8d,0xf4,0x07,0xe9,0x97,0x0a,0xff,0xae,0xdc,0xeb,0x15,0x6f,0x5b,0x9d, -0xbf,0xb5,0xf6,0x6f,0xd0,0xfe,0x36,0x72,0x2f,0x93,0xfb,0x63,0xe9,0x66,0x85,0xbf, -0x54,0xe9,0xd8,0xa6,0x70,0xe9,0xda,0xff,0x99,0xe2,0xbd,0x44,0xee,0xcf,0x15,0x6e, -0xaf,0xc2,0x5d,0x26,0xff,0xfd,0x4a,0xef,0x01,0xb9,0xbf,0x95,0x1e,0x94,0xee,0x91, -0x76,0x88,0x74,0x9f,0x83,0x86,0x4a,0x87,0xc9,0xff,0x23,0xe9,0x26,0xe9,0x2e,0xe9, -0x6e,0xb4,0x6d,0x94,0xe3,0x54,0x80,0x0b,0x21,0x16,0xda,0xc1,0x39,0x72,0x9f,0x2d, -0xad,0x28,0x5d,0x26,0x5d,0x2f,0x5d,0x02,0x75,0x62,0xe8,0x6f,0x62,0x5c,0x4d,0x94, -0xa6,0xc0,0x22,0x58,0x0d,0x1b,0xe0,0x2d,0xa8,0x57,0x80,0xe7,0x69,0x68,0x0f,0x4d, -0xa1,0x03,0x9c,0x07,0x1b,0xe0,0x5e,0x58,0x04,0xab,0xe4,0x7e,0x4b,0xee,0xd5,0x26, -0x7c,0x41,0xc2,0xc3,0xfb,0xb0,0x04,0x96,0xc1,0x2a,0xb9,0x57,0xc0,0x07,0xb0,0x52, -0xee,0xd5,0x50,0xaf,0x90,0xe3,0x34,0x87,0x0e,0x70,0x5e,0x21,0xd7,0xdd,0x02,0x56, -0xc1,0x47,0xf0,0x0e,0xe4,0xc0,0x66,0xf8,0x44,0xfe,0x1f,0x43,0x7c,0x51,0xf2,0x09, -0xa9,0xa5,0x29,0xfb,0xd2,0xae,0xe6,0x93,0xe6,0x97,0x16,0x94,0x16,0x96,0x16,0x97, -0xd6,0x96,0xd6,0x95,0x66,0x48,0x1b,0x48,0x1b,0x96,0x76,0xaf,0x6b,0x4d,0xdd,0x27, -0xd2,0xa5,0x97,0x48,0x2f,0xd5,0xfd,0xf1,0x0a,0x69,0x6f,0x3d,0x6f,0x3e,0xa7,0xfb, -0xcb,0x4b,0x72,0x3f,0x2f,0xf7,0x3c,0xb9,0x97,0x4b,0x57,0x48,0x37,0x4a,0x3f,0xd4, -0xfd,0x2e,0xa8,0xf6,0x18,0xa9,0xf6,0x1d,0xa5,0x76,0x3a,0x52,0xed,0x38,0x42,0xfb, -0x43,0xda,0x1f,0xad,0xfd,0xa3,0xa4,0xa3,0xa5,0x63,0xa4,0x37,0x4a,0xc7,0x4a,0x6f, -0x92,0xde,0xac,0xf8,0xf2,0x2b,0xbe,0x82,0x8a,0xaf,0xb0,0xf6,0x8f,0xd3,0xfe,0x02, -0xda,0x5f,0x48,0xfb,0x8b,0x68,0xff,0x2d,0xd2,0x5b,0xa5,0xb7,0x49,0x6f,0x97,0xde, -0x21,0xbd,0x53,0x3a,0x5e,0xf1,0x15,0x57,0x7c,0x25,0xe5,0x2e,0x21,0x77,0x29,0xb9, -0xcf,0x90,0xfb,0x4c,0xb9,0x4b,0xcb,0x7d,0x96,0xdc,0x15,0xe4,0x3e,0x5b,0xe9,0xa9, -0xa8,0xf8,0x27,0x68,0x7f,0xac,0xf6,0x9f,0xa3,0xfd,0x95,0xb4,0xff,0x2e,0xe9,0xdd, -0xd2,0x7b,0xa4,0xf7,0x4a,0x27,0x4a,0xef,0x93,0xde,0xaf,0xf8,0xaa,0x28,0xbe,0x6a, -0x8a,0x2f,0x5e,0xfb,0x1f,0xd0,0xfe,0xaa,0xda,0x1f,0xa7,0xfd,0xd5,0xb5,0x7f,0x92, -0xf4,0x41,0xe9,0x43,0xd2,0x87,0xa5,0x8f,0x48,0x1f,0x95,0x3e,0xa6,0xf8,0x92,0x14, -0x5f,0x8a,0xe2,0x4b,0x93,0x7f,0xb2,0xfc,0x53,0xe5,0x5f,0x43,0xfe,0xb5,0xe4,0x5f, -0x47,0xfe,0xf5,0xe4,0x5f,0x5b,0xfe,0x75,0xe5,0x5f,0x5f,0xfe,0x99,0xf2,0x6f,0x24, -0x77,0x43,0xb9,0xb3,0xe4,0x6e,0x2e,0xf7,0x79,0x72,0xb7,0x90,0xbb,0xa5,0xdc,0x17, -0xc8,0x7d,0xa1,0xe2,0x6d,0x2f,0xff,0xb6,0xf2,0x6f,0x27,0xff,0x0e,0xf2,0xef,0x24, -0xff,0x4b,0xe5,0xdf,0x59,0xfe,0xdd,0xe4,0x7f,0xb9,0xfc,0x7b,0xaa,0x1c,0x26,0x6b, -0x7f,0x77,0xed,0xef,0xa1,0xfd,0xbd,0xb4,0x7f,0x8a,0xf4,0x71,0xe9,0x13,0xd2,0x27, -0xa5,0x4f,0x49,0x9f,0x96,0x3e,0xa3,0xf8,0xae,0x54,0x7c,0x7d,0x15,0x5f,0x7f,0xed, -0x9f,0xaa,0xfd,0x7d,0xb4,0xbf,0x9f,0xf6,0x0f,0xd0,0xfe,0x69,0xd2,0x67,0xa5,0xd3, -0xa5,0xcf,0x49,0x9f,0x97,0xbe,0x20,0x9d,0x21,0x9d,0x2d,0x5d,0x26,0xfd,0x5a,0x3a, -0x4f,0xe7,0x5b,0xe3,0xdd,0xd7,0x75,0xbe,0x77,0xb4,0x7f,0x85,0xf4,0x7b,0x85,0xfb, -0x58,0xe1,0x3e,0x51,0xb8,0xcd,0x65,0xdd,0x71,0x9d,0x07,0xcb,0xbb,0xfa,0x90,0xf4, -0x05,0xe9,0x1a,0xe9,0x3b,0xd2,0xb7,0xa5,0x91,0x1a,0x0f,0x8a,0x91,0x16,0x94,0x9e, -0x2d,0xad,0x28,0x6d,0x20,0x6d,0x24,0x6d,0x22,0x6d,0x26,0xed,0x27,0xed,0x26,0xcd, -0x96,0x0e,0x94,0x0e,0x91,0x5e,0x25,0x1d,0x2e,0xbd,0x5a,0x3a,0x52,0x3a,0x4a,0xba, -0x45,0xfa,0xa9,0xf4,0x46,0xe9,0x58,0xe9,0x38,0xe9,0x2d,0xd2,0xad,0xd2,0x6d,0xd2, -0xe7,0xa4,0xcf,0x4b,0x5f,0x90,0xce,0x90,0xee,0x92,0xee,0x96,0x7e,0x29,0xdd,0x83, -0x4e,0x4a,0xa0,0xfd,0x27,0xb8,0x7a,0x9e,0xf4,0x7c,0x69,0x6b,0xe9,0x05,0xd2,0x0b, -0xa5,0xed,0xa5,0x17,0x49,0x2f,0x96,0x5e,0x22,0xbd,0x4c,0xda,0x45,0xda,0x4d,0xda, -0x43,0xda,0x4b,0xda,0x5b,0x9a,0x0d,0x0f,0xc2,0x40,0xb9,0x07,0xcb,0x3d,0x44,0xee, -0xa1,0x72,0x0f,0x93,0xfb,0x6a,0xb9,0xaf,0x91,0x7b,0x84,0xdc,0xd7,0xc9,0xfd,0xa8, -0xf4,0x1b,0xe9,0x73,0xd2,0x17,0xa4,0x33,0xa5,0xb3,0xa5,0x2f,0x4a,0x5f,0x92,0xce, -0x97,0xbe,0x2c,0x7d,0x55,0xba,0x48,0xba,0x44,0xba,0x4c,0xfa,0x86,0x74,0x85,0xf4, -0x4d,0xe9,0x5a,0xa5,0xeb,0x5d,0xb9,0xd7,0xcb,0xfd,0x9e,0xdc,0x1f,0xc8,0xbd,0x41, -0xee,0x0f,0xe5,0xfe,0x48,0xee,0x1c,0xb9,0x3f,0x96,0xfb,0x7b,0xe9,0x0f,0xd2,0x83, -0xd2,0x43,0xd2,0x9f,0x4d,0x9a,0x8b,0x06,0x9c,0x07,0xc1,0xe8,0xc3,0xd2,0xe9,0x50, -0xa9,0x58,0xc0,0x89,0x2f,0xe6,0x6a,0x75,0xa8,0x57,0x22,0xe0,0xb4,0x80,0x0e,0x70, -0x1e,0xcc,0x2c,0x19,0x70,0x5e,0x85,0x97,0xe1,0x15,0x58,0x08,0x8b,0x60,0x46,0x29, -0xfc,0x4a,0xb9,0xfa,0x0a,0x24,0x9c,0x1e,0x70,0x6a,0x02,0xa7,0x72,0xd2,0xa1,0x01, -0x5c,0x0c,0x9d,0x45,0x57,0xb8,0x06,0x9e,0x15,0x73,0xc5,0x3c,0xf1,0xb2,0x30,0x63, -0xb1,0x66,0xbc,0x68,0xa3,0xe3,0xbe,0x67,0xfc,0xc1,0xf8,0xf3,0xbb,0xf3,0x15,0xf1, -0x2a,0x2c,0x86,0x25,0xf0,0x1a,0xbc,0x0e,0xf7,0xf0,0xfb,0xf4,0x5e,0x98,0x08,0xf7, -0xc1,0x0c,0x98,0x09,0x4b,0x61,0x39,0xbc,0x0b,0x5b,0x60,0x3b,0x7c,0x29,0xbe,0x12, -0xa6,0x1f,0x19,0x60,0xda,0xaa,0x79,0x8f,0x66,0xda,0xa8,0x18,0x6a,0x9e,0x37,0xcd, -0x6f,0x14,0x71,0xad,0x79,0x9f,0x66,0xde,0xa5,0x89,0x9b,0xc4,0xcd,0x62,0x9c,0x7e, -0x8f,0xde,0x2e,0xcc,0x6f,0xc7,0x29,0xa6,0x0f,0x36,0xcf,0xb7,0xa6,0xcf,0x32,0xed, -0x9a,0xe7,0xd3,0x31,0x70,0x33,0xdc,0x0a,0xd5,0x43,0x3c,0x63,0x42,0x7b,0xe8,0x28, -0xba,0xc0,0x38,0xb8,0x0b,0x16,0xc2,0x62,0xb1,0x14,0x96,0xc1,0xeb,0xf0,0x06,0xac, -0x10,0x6b,0x61,0x17,0xec,0x85,0xaf,0xa1,0x28,0xcf,0xb0,0x67,0x40,0x39,0xa8,0x0f, -0x19,0xa2,0x01,0x34,0x87,0x16,0xd0,0x19,0xba,0x40,0x6f,0xe8,0x0f,0x03,0x20,0x5b, -0x0c,0xd4,0xf3,0xf0,0xfb,0xe2,0x03,0xc8,0x81,0xcd,0xb0,0x1d,0x3e,0x83,0x1d,0xf0, -0x39,0xec,0x84,0x5d,0xb0,0x07,0xbe,0x82,0xbd,0xb0,0x0f,0x0e,0xc2,0x21,0x38,0x0c, -0xbf,0x88,0xa2,0xd1,0x2e,0xc5,0x44,0x71,0xa8,0x0e,0x09,0xd0,0x1c,0x5a,0xc2,0x53, -0xf0,0x34,0xcc,0x84,0x59,0x62,0x09,0xbc,0x09,0xab,0x60,0x17,0xec,0x86,0x03,0xf0, -0x0d,0x7c,0x0b,0x87,0xc4,0x4f,0xc2,0xc9,0xc7,0xf3,0x12,0x9c,0x02,0xa7,0xc3,0x19, -0xa2,0x34,0xc4,0xc2,0xd9,0xe2,0x1c,0xa8,0x04,0x95,0xc5,0xb9,0x10,0x07,0x19,0x70, -0x11,0x74,0x84,0x4e,0xd0,0x05,0xae,0x84,0x6c,0xb8,0x19,0x6e,0x81,0xf1,0x70,0x3f, -0x3c,0x06,0x93,0x61,0x25,0xbc,0x29,0x56,0xc1,0xa7,0xb0,0x0d,0x82,0xfc,0x4e,0x88, -0x10,0x91,0x90,0x0e,0xb5,0x44,0x6d,0x51,0x47,0xd4,0x15,0xf5,0x20,0x03,0x1a,0x40, -0x53,0x68,0x06,0xe7,0x41,0x4b,0x71,0x3e,0xb4,0x82,0xd6,0xa2,0x0d,0x74,0x80,0x8b, -0xa0,0x2b,0x74,0x83,0xee,0xd0,0x13,0x06,0xc0,0x40,0x31,0x08,0x46,0xc2,0x28,0x31, -0x1a,0xc6,0xc0,0x58,0xb8,0x1d,0xc6,0xc3,0x04,0xb8,0x07,0xa6,0xc2,0xb3,0x30,0x13, -0x66,0x8b,0x97,0x60,0x01,0xbc,0x2a,0x16,0x0a,0xf3,0xbb,0x68,0x09,0x2c,0x85,0xd7, -0x61,0x05,0xac,0xd4,0x6f,0x25,0xf3,0x3b,0x69,0x8d,0x78,0x5b,0x6c,0x10,0x39,0xf0, -0x31,0x6c,0x86,0x4f,0x61,0x2b,0x6c,0x83,0x1d,0xb0,0x17,0xf6,0xc1,0x7e,0x38,0x2c, -0x7e,0x16,0x81,0xfc,0x94,0x2b,0x44,0x88,0x10,0xe4,0x87,0x22,0x50,0x14,0x4a,0x40, -0x65,0xa8,0x02,0x09,0x90,0x04,0xc9,0x90,0x0a,0x69,0x50,0x13,0xd2,0xa1,0x8e,0xa8, -0x07,0x19,0xd0,0x00,0x32,0xa1,0x09,0x34,0x85,0x16,0xd0,0x12,0xda,0x88,0x8e,0x70, -0x09,0x5c,0x26,0x7a,0xc2,0x00,0xc8,0x86,0x81,0x30,0x08,0x86,0xc0,0x55,0x62,0x14, -0x8c,0x86,0x31,0x62,0x2c,0xdc,0x01,0xe3,0xe1,0x2e,0xb8,0x07,0xa6,0xc2,0xb3,0x30, -0x13,0x66,0xc3,0x1c,0x98,0x0b,0x0b,0xe0,0x55,0xb1,0x50,0x2c,0x82,0x25,0xb0,0x14, -0xde,0x80,0x15,0xb0,0x12,0xde,0x14,0xab,0xe1,0x2d,0x58,0x23,0xd6,0x8a,0x2d,0xf0, -0x29,0x6c,0x85,0x6d,0xb0,0x03,0xf6,0xc2,0x41,0x71,0x08,0x7e,0x81,0x40,0x01,0x97, -0x20,0x44,0x40,0x08,0x0a,0x42,0x11,0x28,0x0a,0x25,0xa0,0x32,0x54,0x81,0x04,0x48, -0x82,0x64,0x48,0x85,0x74,0xa8,0x23,0xea,0x8a,0x7a,0xa2,0xbe,0xc8,0x10,0x0d,0xa0, -0x11,0x34,0xd1,0xef,0xe6,0x16,0xfa,0xdd,0xdc,0x52,0xb4,0xd3,0x6f,0xea,0x0e,0xe2, -0x12,0xb8,0x14,0x3a,0x43,0x57,0xe8,0x06,0xdd,0xa1,0x27,0x0c,0x80,0x81,0x62,0xb4, -0x18,0x23,0xc6,0xc2,0xad,0x70,0x27,0x4c,0x80,0xbb,0x61,0x22,0x4c,0x82,0x07,0xe1, -0x61,0xf1,0x28,0x3c,0x06,0x4f,0xc0,0x93,0x30,0x15,0x9e,0x85,0x17,0x60,0x81,0x7e, -0xb7,0x2f,0x86,0x25,0x62,0x29,0x2c,0x83,0xe5,0xb0,0x52,0xbf,0xf1,0xcd,0xef,0xfb, -0x35,0x62,0xad,0xd8,0x20,0x76,0xc0,0xde,0x02,0x66,0x40,0xd9,0x25,0x00,0x11,0x10, -0x29,0x42,0x50,0x10,0x0a,0x43,0x51,0x28,0x0e,0x95,0xa1,0x0a,0xa4,0x43,0x1d,0x51, -0x57,0xd4,0x83,0x0c,0x68,0x00,0x8d,0xa0,0xb1,0xde,0x27,0x34,0x87,0x96,0xd0,0xae, -0xa0,0xfb,0x8e,0xe1,0x22,0xb8,0x18,0x2e,0x81,0x4b,0xa1,0x0b,0x74,0x85,0x6e,0xd0, -0x1d,0x7a,0xc2,0x00,0x18,0x29,0x46,0x89,0xd1,0x30,0x06,0xc6,0xc2,0xed,0x70,0x27, -0x4c,0x80,0xbb,0x61,0x2a,0x3c,0x0b,0x73,0x60,0x2e,0x2c,0x80,0x57,0xc5,0x42,0xb1, -0x48,0x2c,0x16,0xe6,0x1d,0xc7,0xeb,0xb0,0x5c,0xac,0xd0,0xfb,0x8d,0x55,0x7a,0xbf, -0xf1,0x16,0xac,0xd1,0xfb,0x0e,0xf3,0xee,0x23,0x07,0x3e,0x81,0x2d,0xf0,0x29,0x6c, -0x85,0x6d,0xb0,0x03,0xf6,0xc2,0x7e,0xf8,0x1a,0x0e,0x08,0xa7,0x10,0x65,0x0a,0x41, -0x88,0x80,0x48,0x28,0x08,0x85,0xa1,0x28,0x14,0x87,0x5a,0x50,0x1b,0xea,0x40,0x5d, -0x61,0xde,0xa3,0x64,0x40,0x03,0x68,0x04,0x8d,0xa1,0xa9,0xde,0xb5,0x98,0xf7,0x2c, -0x2d,0xc5,0xf9,0xe2,0x42,0x68,0xaf,0xf7,0x30,0x86,0xce,0xd0,0x15,0xba,0x41,0x77, -0xe8,0x09,0x03,0xe0,0x1a,0xb8,0x01,0x46,0x89,0xd1,0x30,0x06,0xc6,0xc2,0x7d,0xf0, -0x20,0xcc,0x82,0x39,0xf0,0x0a,0x2c,0x14,0x4b,0x60,0xb9,0xde,0xdf,0xac,0xd2,0x7b, -0x9d,0x77,0x61,0x1d,0xbc,0x07,0x1f,0x88,0x8d,0x7a,0xd7,0xb3,0x03,0xf6,0xc2,0xd7, -0x70,0xc0,0x94,0x41,0x61,0xda,0x25,0x24,0x8a,0x24,0x48,0x86,0x14,0xa8,0x05,0x19, -0x90,0x05,0x8d,0x45,0x13,0x38,0x1f,0x5a,0x43,0x47,0x18,0x05,0xa3,0xe1,0x46,0x71, -0x13,0xdc,0x0a,0xb7,0xc3,0x13,0xf0,0x94,0x78,0x06,0x66,0xc2,0x2c,0x31,0x1b,0xe6, -0xc0,0x8b,0xf0,0x2a,0x2c,0x14,0x4b,0xe0,0x75,0x58,0x2e,0x56,0xc2,0xdb,0xb0,0x16, -0x3e,0x84,0x1c,0xd8,0x0c,0x4e,0x11,0x97,0xd3,0xe0,0x74,0x28,0x07,0xe7,0x42,0xaa, -0xa8,0x21,0xd2,0x45,0x3d,0xa8,0x0f,0x19,0xd0,0x04,0x9a,0x41,0x5f,0x18,0x08,0xd7, -0xc3,0x0d,0x62,0x24,0xdc,0x08,0x37,0xc1,0xcd,0x30,0x0e,0x6e,0x87,0x3b,0xe0,0x7e, -0x98,0x04,0xaf,0xc2,0x72,0xe1,0x14,0xa5,0xcd,0x41,0x55,0xa8,0x06,0x71,0x90,0x00, -0x89,0x22,0x09,0x6a,0x40,0x4d,0x91,0x0e,0xb5,0xa0,0x36,0xd4,0x81,0xba,0x50,0x0f, -0xea,0x8b,0x0c,0x68,0x06,0xad,0xa1,0x1d,0xb4,0x87,0x0e,0x70,0x11,0x74,0x84,0x8b, -0xe1,0x52,0xb8,0x0c,0xba,0x40,0x57,0xd1,0x0d,0x2e,0x87,0x1e,0xd0,0x13,0x7a,0x41, -0x3f,0xe8,0x0f,0xd9,0x30,0x10,0x86,0xc0,0x55,0x30,0x0a,0x46,0x8b,0x31,0x70,0x23, -0x8c,0x85,0x9b,0xe0,0x66,0xb8,0x1d,0xee,0x10,0x77,0x8a,0xf1,0x62,0x02,0x4c,0x82, -0x07,0xe1,0x61,0x78,0x44,0x4c,0x86,0x37,0x60,0x85,0x58,0x23,0xd6,0xc2,0x21,0x38, -0x2c,0x7e,0x82,0x5f,0xc0,0x29,0x46,0xbb,0x29,0x4e,0xbb,0x81,0x96,0xd0,0x1a,0xda, -0xc3,0x45,0xe2,0x62,0xb8,0x14,0xba,0xc0,0x38,0xb8,0x15,0xee,0x80,0x09,0x30,0x13, -0x66,0xc3,0x5c,0x98,0x0f,0x8b,0x60,0x89,0x58,0x06,0x6f,0xc0,0x4a,0xf8,0x00,0x36, -0x42,0xd1,0x12,0xb4,0x67,0x28,0x05,0xa7,0xc1,0xc5,0x70,0x29,0x74,0x86,0xde,0x30, -0x10,0x46,0xc2,0x78,0x98,0x0c,0xdf,0xc2,0x0f,0xf0,0x13,0x04,0x4a,0x52,0x4f,0x4e, -0xa1,0x8d,0xc2,0x0d,0x30,0x0a,0x1e,0x84,0xc9,0xb0,0x0b,0xbe,0x82,0x7d,0xb0,0x1f, -0x7e,0x00,0xe7,0x54,0xb2,0x06,0xc5,0xa1,0x24,0x94,0x12,0x65,0x21,0x11,0x92,0x20, -0x19,0x52,0x44,0x06,0xb4,0x81,0x0b,0xa0,0x2d,0x74,0x81,0xbe,0xd0,0x1f,0xb2,0x61, -0x10,0x0c,0x86,0x91,0x30,0x07,0x5e,0x84,0xb9,0xf0,0x92,0x98,0x07,0x0b,0x61,0x11, -0xbc,0x01,0xcb,0xc5,0x0a,0x58,0x09,0x6f,0xc2,0x7a,0xd8,0x20,0x72,0xc4,0xc7,0x62, -0x0b,0x7c,0x01,0x05,0x4e,0xa3,0x4b,0x84,0x22,0xa2,0x28,0x9c,0x0e,0xe5,0x20,0x13, -0x1a,0x8a,0x46,0x30,0x1c,0x46,0xc2,0x8d,0x30,0x16,0x6e,0x82,0x9b,0x61,0x2a,0x4c, -0x13,0xcf,0x8a,0x99,0xf0,0x2d,0x38,0xa7,0x3b,0x4e,0x19,0x28,0x07,0x15,0x20,0x16, -0x2a,0x42,0x25,0xa8,0x0c,0xe7,0x42,0x55,0x48,0x80,0x44,0x48,0x12,0xc9,0x50,0x13, -0xd2,0xa1,0x36,0x34,0x86,0x56,0xd0,0x1a,0xfa,0x41,0x36,0x0c,0x81,0x91,0x30,0x1d, -0x66,0xc2,0x9b,0xb0,0x16,0x3e,0x84,0x8f,0x84,0x73,0x06,0xcf,0xf6,0x70,0x2a,0x9c, -0x06,0xa7,0xc3,0x19,0x50,0x5a,0x94,0x83,0x0b,0xa1,0x9d,0x68,0x2f,0x3a,0x88,0x8b, -0xe0,0x32,0xe8,0x22,0xba,0x8a,0x6e,0xa2,0x3b,0x5c,0x0e,0x3d,0xa0,0x2f,0xf4,0x83, -0x41,0x30,0x18,0x46,0xc0,0x0d,0x62,0x24,0xdc,0x02,0x13,0x60,0x12,0x3c,0x23,0x66, -0xc2,0x42,0x58,0x0c,0x1f,0xc3,0x16,0xd8,0x0d,0x4e,0x69,0xee,0x4f,0xe6,0xbd,0xb9, -0x08,0x41,0x32,0xa4,0x88,0x54,0x91,0x06,0xb5,0xf4,0x0e,0xde,0x50,0x47,0xd4,0xd5, -0xfb,0xf7,0x4c,0x61,0xde,0xc1,0x37,0x82,0x2c,0x38,0x0f,0x5a,0x43,0x47,0xe8,0x03, -0x83,0x61,0x24,0x8c,0x82,0xd1,0x62,0x0c,0x4c,0x86,0x29,0xe2,0x71,0x78,0x0a,0x9e, -0x86,0x67,0x60,0x2a,0x4c,0x83,0x67,0xc5,0x74,0x78,0x1e,0x5e,0x80,0x19,0x30,0x13, -0x16,0xc0,0xcb,0xf0,0x19,0xec,0x10,0x9f,0x8b,0x9d,0xf0,0x05,0xec,0x82,0x2f,0x61, -0x8f,0xf8,0x4a,0xec,0x85,0xfd,0xf0,0x35,0x1c,0x00,0xe7,0x4c,0xea,0x0e,0x9c,0x0d, -0x55,0xa1,0x1a,0x24,0x43,0x0a,0xa4,0x42,0x1a,0xd4,0x80,0x0c,0x68,0x06,0x2d,0xa1, -0x15,0xb4,0x86,0x8e,0x70,0x31,0x0c,0x83,0xe1,0x70,0x1d,0x8c,0x84,0x5b,0x61,0x02, -0xbc,0x0c,0x0b,0x61,0x31,0xac,0x85,0x75,0xf0,0x1e,0x6c,0x81,0x4f,0xc5,0x56,0xd8, -0x09,0x5f,0xc0,0x97,0xb0,0x47,0x7c,0x05,0x07,0xe0,0x1b,0xf1,0x2d,0x7c,0x07,0xdf, -0x8b,0x1f,0xe0,0x20,0x1c,0x12,0xce,0x59,0xa4,0x1f,0xaa,0xc1,0x95,0xd0,0x07,0x86, -0xc1,0x70,0x71,0x35,0x3c,0x08,0x0f,0xc1,0x62,0x58,0x02,0xbf,0x9c,0xe5,0xce,0x01, -0x37,0xef,0xf5,0xcd,0x18,0xb8,0x79,0x7f,0x6f,0xc6,0x66,0xcd,0x7b,0x66,0x33,0x26, -0x6d,0xde,0x27,0x9b,0x31,0x4f,0xf3,0x9e,0xb8,0xa3,0xe8,0x24,0x2e,0x15,0xe6,0x5d, -0xf1,0x75,0x65,0xdd,0xb1,0x0f,0x33,0xae,0x6c,0xc6,0x86,0xcd,0x38,0xfb,0x22,0x61, -0xde,0xad,0x9a,0x71,0x66,0x33,0xc6,0x6b,0xc6,0xcd,0xcd,0x18,0xee,0xfa,0xb2,0xee, -0x58,0xed,0xa6,0xb2,0xee,0x18,0xab,0x19,0xef,0x36,0xef,0x5e,0xcd,0x38,0xa7,0x19, -0x2b,0x35,0xe3,0xd4,0x05,0xcb,0xd1,0x7f,0x40,0x7d,0xc8,0x80,0x0b,0xc5,0x40,0x71, -0x83,0x98,0x00,0x0f,0xc3,0x5a,0xc8,0x81,0x4f,0xc5,0x67,0xb0,0x0f,0x82,0xe5,0x5d, -0xa2,0x45,0x7e,0x28,0x01,0xa5,0xc4,0x69,0xe2,0x74,0x38,0x0b,0xce,0x16,0x15,0x45, -0x65,0x51,0x05,0xe2,0x20,0x5e,0x54,0x87,0x14,0x48,0x35,0xef,0x6e,0x21,0x5d,0xd4, -0x81,0xfa,0x90,0x05,0xcd,0xe0,0x7c,0xd1,0x05,0x46,0xc3,0x18,0xb8,0x11,0xc6,0xc2, -0x38,0x70,0xaa,0x39,0xce,0xb5,0x30,0x02,0xae,0x83,0xeb,0x61,0x3b,0xec,0x86,0xaf, -0x60,0x2f,0xec,0x87,0xaf,0xe1,0x00,0x38,0x71,0x8e,0x73,0x0e,0x54,0x12,0x71,0x22, -0x01,0x7a,0xc1,0x00,0x71,0x83,0x18,0x09,0xf7,0xc1,0x64,0x78,0x1a,0xa6,0xc2,0x0b, -0x30,0x13,0x5e,0x82,0xf9,0xb0,0x08,0x96,0xc0,0x72,0x58,0x09,0x6f,0xc3,0x5a,0xf8, -0x00,0x36,0xc2,0x27,0xb0,0x05,0x7e,0x81,0x50,0x82,0x4b,0x94,0x88,0x16,0xe5,0xe1, -0x5c,0xa8,0x02,0x71,0x10,0x0f,0xd5,0x21,0x11,0x52,0xa1,0x26,0xd4,0x82,0xda,0xa2, -0x8e,0x68,0x00,0xf7,0xe9,0xfd,0xa0,0x79,0x97,0xf8,0x10,0x3c,0x0c,0x8f,0xe8,0x3d, -0xe9,0x63,0x62,0x8a,0xde,0x23,0xfe,0x28,0x0e,0xeb,0x7d,0xe2,0x2f,0x22,0x94,0xc8, -0xf9,0x20,0x11,0xee,0x84,0xc9,0xb0,0x18,0xf6,0xc1,0x2f,0xe0,0xa4,0xe0,0x6e,0x89, -0x9e,0xcf,0x7d,0x74,0x32,0xd7,0x5e,0x9c,0x02,0xb7,0x3f,0x49,0x7e,0xe1,0x7b,0xf8, -0x01,0x7e,0x02,0xe7,0x29,0xee,0x61,0x4f,0x53,0x0f,0x45,0x51,0x28,0x0b,0xe5,0xe0, -0x5c,0xa8,0x02,0x55,0x21,0x03,0xfa,0x41,0x7f,0x31,0x40,0x0c,0x81,0xeb,0xe0,0x06, -0x31,0x12,0x1e,0x82,0x87,0xe1,0x11,0x78,0x14,0x1e,0x83,0xc9,0xf0,0x19,0xec,0x80, -0xbd,0xb0,0x0f,0xf6,0xc3,0xa9,0xcf,0x70,0x3e,0xa8,0x00,0x03,0x20,0x5b,0x0c,0x84, -0x9b,0x61,0x9c,0xb8,0x0d,0xee,0x84,0xf1,0x62,0x02,0xe4,0xc0,0x7e,0xf8,0x06,0xbe, -0x85,0xef,0xe0,0x7b,0xf1,0x03,0x1c,0x84,0x43,0xe2,0x47,0x08,0x4c,0xa5,0x8d,0x88, -0x08,0x08,0x41,0x94,0x88,0x86,0xfc,0x50,0x40,0x14,0x84,0x0a,0x10,0x0b,0x67,0xc3, -0x39,0x50,0x11,0x2a,0x89,0x6a,0x22,0x03,0x06,0xc3,0x48,0x18,0x05,0xa3,0x61,0x0e, -0xbc,0x08,0xaf,0xc1,0xeb,0xf0,0x06,0xac,0x85,0x0f,0x61,0x0b,0xec,0x87,0xaf,0xe1, -0x7b,0xf8,0x51,0xfc,0x04,0x3f,0xc3,0x2f,0xc2,0x99,0xe6,0x38,0x31,0x90,0x1f,0xce, -0x81,0x8a,0x10,0x07,0x09,0xd0,0x18,0x9a,0x40,0x1b,0xb8,0x00,0xda,0x42,0x17,0x18, -0x01,0x23,0x61,0x34,0x8c,0x11,0x37,0xc2,0x6c,0x98,0x23,0x5e,0x84,0xb9,0xf0,0x12, -0x2c,0x80,0x97,0xe1,0x15,0x78,0x15,0x16,0xc2,0x22,0x58,0x02,0xef,0x88,0xb5,0xf0, -0x21,0x6c,0x81,0xed,0xf0,0x99,0xd8,0x21,0x3e,0x87,0xbd,0xb0,0x0f,0x0e,0xc2,0x21, -0xf8,0x79,0x9a,0xfb,0xd2,0xb9,0x32,0x9c,0x0b,0xf1,0x50,0x1d,0x12,0x20,0x11,0x92, -0x20,0x19,0x52,0x20,0x15,0xd2,0x20,0x03,0x32,0xa1,0xa1,0x68,0x04,0x2d,0xe0,0x3c, -0xd1,0x52,0xb4,0x86,0x8e,0xd0,0x05,0x06,0x40,0xb6,0x18,0x08,0x43,0xe1,0x1a,0x71, -0xad,0x18,0x21,0xae,0x13,0xd7,0xc3,0x0c,0x98,0x29,0x66,0x89,0xd9,0xf0,0x22,0xcc, -0x85,0x97,0x60,0x1e,0xcc,0x87,0x05,0xb0,0x10,0x16,0xc1,0x62,0x58,0x22,0x96,0x8a, -0x65,0x62,0x13,0xe4,0xc0,0xc7,0x62,0x0b,0xec,0x82,0xdd,0xe2,0x4b,0xd8,0x03,0x5f, -0xc1,0x5e,0xf8,0x5a,0x1c,0x80,0x6f,0xe0,0x5b,0xf1,0x9d,0x08,0x4c,0xa7,0x5e,0x42, -0x0c,0x14,0x86,0x62,0x70,0x0a,0x94,0x83,0x8a,0x50,0x19,0xe2,0x21,0x01,0x2e,0x86, -0x4b,0xa0,0x33,0x74,0x81,0x2b,0xa0,0xb7,0xc8,0x86,0xc5,0xb0,0x04,0xb6,0xc2,0x36, -0xd8,0x0e,0x9f,0x89,0x1d,0xf0,0x39,0xec,0x14,0x5f,0xc0,0x2e,0xf8,0x52,0xec,0x11, -0xfb,0xe0,0x20,0x38,0xcf,0xf1,0xfb,0x74,0x03,0x65,0x07,0xcb,0x61,0x35,0xfc,0x08, -0x66,0x52,0x6e,0xbf,0x43,0x9c,0x0f,0x3e,0x34,0x13,0x73,0x7f,0x24,0xfd,0x50,0x02, -0x4e,0x85,0xb3,0x44,0x19,0x51,0x16,0x2a,0x43,0x15,0x48,0x83,0x9a,0x50,0x07,0xea, -0x89,0x0c,0x68,0x00,0x0d,0xa1,0x11,0x34,0x86,0x59,0xb0,0x1e,0xea,0xfe,0x44,0x3d, -0x80,0xf1,0x70,0x37,0xac,0x80,0x7d,0xf0,0xa3,0x99,0x20,0xfb,0x33,0xcf,0xe1,0x50, -0x0e,0xaa,0x43,0x32,0xa4,0x40,0x4b,0x68,0x05,0xd9,0x30,0x04,0x86,0xc2,0x61,0x28, -0xfa,0x0b,0xf7,0x44,0x28,0x0f,0xb5,0xa0,0xbe,0x68,0x00,0x17,0x43,0x0f,0xd1,0x13, -0x06,0x40,0xb6,0x18,0x08,0x8f,0xc0,0xa3,0xf0,0x18,0x4c,0x86,0x45,0xb0,0x0c,0x56, -0xc0,0x2a,0x78,0x07,0xd6,0xc1,0x06,0xf8,0x08,0x72,0xcc,0xc4,0x5c,0x27,0xe0,0x14, -0x80,0x42,0x50,0x11,0x2a,0x43,0x2d,0xa8,0x03,0x75,0xa1,0x3e,0xb4,0x84,0xd6,0x70, -0x29,0x8c,0x84,0x43,0x90,0x11,0x08,0x38,0x43,0xe0,0x27,0x31,0x32,0x18,0x70,0x1e, -0x86,0xc9,0xb0,0x16,0xb6,0x08,0xf3,0xb1,0x45,0x59,0x88,0x83,0x0c,0x68,0x20,0x32, -0xa1,0x19,0x34,0x17,0xad,0xe1,0x2a,0x18,0x0a,0xd7,0xc0,0x48,0x78,0x04,0x26,0xc3, -0x6b,0xb0,0x12,0xde,0x81,0x77,0xe1,0x7d,0xf3,0x01,0x47,0x64,0xc0,0x69,0x05,0xad, -0xe1,0x11,0x98,0x0c,0xd3,0x60,0x26,0xac,0x87,0x8d,0xf0,0xa3,0xf9,0xc0,0x2e,0x14, -0x70,0x2a,0x41,0x02,0x74,0x87,0x6c,0xb8,0x06,0x46,0xc0,0xad,0x70,0x3b,0xdc,0x0d, -0xf7,0xc2,0x44,0xb8,0x1f,0xa6,0xc0,0x13,0x30,0x0b,0xe6,0xc0,0x02,0x78,0x05,0x5e, -0x85,0x25,0x70,0xc0,0x7c,0xf0,0x11,0x15,0x70,0xd2,0xa2,0xc9,0x13,0xb4,0x83,0x2e, -0xd0,0x0b,0x46,0xc2,0x58,0xb8,0x19,0x66,0xc2,0x6c,0x78,0xd9,0x7c,0x00,0x92,0x2f, -0xe0,0x84,0x20,0x9f,0xc8,0x0f,0xa9,0x50,0x03,0x6a,0x42,0x1d,0x51,0x1f,0xda,0x41, -0x17,0x18,0x0a,0x23,0xe1,0x51,0xd8,0x02,0x5f,0xc3,0x37,0xf0,0xad,0xf9,0x20,0x30, -0x26,0xe0,0x94,0x82,0x72,0x90,0x0e,0x19,0xd0,0x11,0x46,0xc2,0x3c,0x58,0x04,0x8b, -0xcd,0x07,0x27,0xf9,0x5d,0x02,0x10,0x01,0x21,0x88,0x82,0x82,0x50,0x04,0x8a,0x42, -0x09,0x28,0x05,0xa7,0xc2,0xe9,0x90,0x0a,0x35,0xa1,0x16,0xd4,0x17,0x5d,0x60,0x04, -0x8c,0x84,0x87,0x61,0x09,0xac,0x80,0x37,0x61,0x3b,0x7c,0x06,0x3b,0xcc,0xb9,0x0a, -0x70,0x3c,0x64,0x40,0x3b,0xe8,0x02,0x83,0x60,0x24,0xdc,0x95,0xfb,0xe1,0x4b,0xc0, -0x69,0x0a,0x23,0x61,0x36,0x2c,0x81,0xaf,0xcd,0x07,0x8d,0x85,0x02,0x4e,0x2c,0x9c, -0x0d,0x95,0x20,0x01,0xd2,0x21,0x03,0x9a,0x41,0x73,0x68,0x09,0xe7,0x8b,0x56,0xa2, -0x35,0xf4,0x80,0xde,0xd0,0x0f,0x06,0x88,0x6c,0x18,0x0b,0x23,0x0b,0x53,0x2f,0x60, -0x3a,0x3c,0x07,0x33,0x61,0x16,0x2c,0x83,0xd7,0xe1,0x0d,0xb1,0x1c,0x56,0xc0,0x21, -0xf8,0xc5,0x7c,0x48,0x59,0x24,0xe0,0x9c,0x09,0x95,0x44,0x02,0x64,0x41,0x63,0x68, -0x0d,0xd9,0x30,0x0a,0x46,0xc3,0x58,0x98,0x09,0xaf,0xc3,0x16,0xf8,0xce,0x7c,0xc0, -0x53,0xd4,0x25,0x20,0x82,0x22,0x02,0x6a,0x40,0x4d,0x68,0x0c,0x3d,0x61,0x00,0x64, -0x8b,0x81,0x30,0x08,0x06,0xc3,0x10,0xb8,0x4a,0xdc,0x00,0xa3,0x60,0xb4,0x18,0x03, -0x33,0x60,0x26,0xcc,0x86,0x39,0xf0,0x12,0xcc,0x83,0xf9,0xb0,0x40,0x63,0xbc,0xcb, -0xc4,0x5a,0xd8,0x09,0xfb,0xe0,0xa0,0x49,0x5b,0x31,0xd2,0x03,0x11,0x50,0x51,0xe3, -0xbf,0x55,0xa1,0x9a,0x88,0x83,0x14,0x48,0x83,0xfa,0xd0,0x48,0x64,0x41,0x63,0x68, -0x22,0x5a,0xc3,0x20,0x18,0x2c,0x86,0x8a,0x91,0x30,0x0a,0x46,0x8b,0x31,0x30,0x1b, -0xe6,0xc0,0x5c,0x78,0x09,0x16,0xc1,0x62,0x58,0x02,0x4b,0xe1,0x75,0x78,0x13,0xde, -0x82,0xb7,0xc5,0x3b,0x62,0x2d,0x7c,0x08,0x1f,0x89,0x1c,0x61,0x3e,0x56,0x2d,0x06, -0x25,0xa0,0x2a,0x54,0x83,0x78,0xa8,0x0e,0x89,0x90,0x04,0xc9,0x90,0x22,0x52,0x45, -0x1a,0xd4,0x80,0x7a,0xa2,0x3e,0x64,0x40,0x03,0x31,0x12,0x6e,0x82,0x71,0xe2,0x56, -0xb8,0x03,0xc6,0xc3,0x23,0xf0,0x18,0x4c,0x85,0x99,0xf0,0x09,0x6c,0x16,0x5b,0x60, -0x2b,0x6c,0x83,0x5d,0xb0,0x0f,0x0e,0x9a,0x0f,0x6a,0x4b,0x50,0x0f,0x20,0x08,0x11, -0x10,0x82,0x82,0x50,0x04,0x8a,0x42,0x09,0xa8,0x0c,0x55,0x20,0x01,0x92,0x20,0x19, -0x52,0x21,0x1d,0x6a,0x43,0x1d,0xa8,0x2b,0xcc,0x38,0x7d,0x7d,0xc8,0x10,0x0d,0xa0, -0x11,0x34,0x81,0xa6,0x1a,0xc3,0x6f,0x09,0xad,0x45,0x07,0x71,0x29,0x74,0x85,0x6e, -0xd0,0x1d,0x7a,0x42,0x5f,0xc8,0x86,0x21,0x30,0x12,0x6e,0x81,0xdb,0xc4,0x9d,0x62, -0x02,0xcc,0x85,0x79,0x62,0x3e,0xbc,0x0c,0xaf,0xc0,0x12,0x58,0x26,0xde,0x10,0x2b, -0x60,0x15,0xbc,0x05,0x6b,0xe0,0x6d,0xf1,0x8e,0x58,0x2b,0xde,0x15,0xeb,0xc4,0x7a, -0xf1,0x29,0x6c,0x35,0x1f,0xb0,0x95,0xe4,0xba,0x41,0x2a,0xd4,0x80,0x9a,0x50,0x1f, -0x32,0xa0,0x01,0x64,0x42,0x23,0xc8,0x12,0x8d,0x45,0x13,0x68,0x0e,0xad,0xa1,0x23, -0x5c,0x26,0x3a,0x43,0x57,0x18,0x69,0xe6,0x30,0x94,0x74,0xe7,0x37,0xcc,0x86,0x39, -0x30,0x1f,0x16,0x88,0x97,0xad,0x39,0x0f,0x66,0xbe,0xc3,0x62,0x58,0x02,0x4b,0xc5, -0x32,0x78,0x0d,0x5e,0x87,0x37,0x60,0x85,0x58,0x0b,0x1f,0x9a,0xf8,0x4b,0x05,0x9c, -0x17,0x34,0x3f,0x62,0x16,0xcc,0x86,0xb9,0x30,0x5f,0xf3,0x25,0x5e,0x85,0x85,0xb0, -0x48,0x2c,0x86,0x25,0xb0,0x11,0x36,0x41,0x0e,0x7c,0x6c,0x3e,0xdc,0x3b,0x85,0xba, -0x0c,0x09,0x90,0x04,0xc9,0x50,0x0b,0x6a,0x43,0x1d,0xa8,0x2b,0xea,0x89,0xfa,0x90, -0x01,0x8d,0x44,0x6b,0xe8,0x08,0x23,0xe1,0x59,0x98,0x2e,0x9e,0x13,0xcf,0x8b,0x17, -0x60,0x06,0xcc,0x84,0xb9,0xf0,0x92,0x98,0x27,0xe6,0x8b,0x25,0xf0,0x26,0xac,0x85, -0x6d,0xe6,0xc3,0xf0,0x53,0x03,0xce,0x19,0x70,0x96,0x28,0x23,0xca,0x8a,0x72,0x50, -0x1e,0x2a,0xc0,0x39,0x50,0x51,0x54,0x82,0xaa,0x90,0x00,0xe9,0x90,0x01,0x8d,0xcd, -0x07,0x8a,0xa7,0xe1,0x0f,0xd5,0x20,0x1e,0xaa,0x43,0x0d,0xa8,0x29,0xd2,0xa1,0x16, -0x4c,0x86,0xcd,0xb0,0x05,0xbe,0x80,0x5f,0x20,0xea,0xf4,0x80,0x13,0x23,0x0a,0x42, -0x09,0x28,0x05,0xa7,0xc0,0x69,0x50,0xfd,0x74,0x77,0x9e,0x4a,0x2a,0xd4,0xd0,0x7c, -0x95,0xda,0x50,0x07,0xea,0x8a,0x7a,0xa2,0xbe,0xc8,0x10,0x0d,0x44,0xa6,0x68,0x28, -0x5a,0x43,0x47,0x98,0x0c,0xcf,0xc0,0xb3,0xb0,0x16,0xde,0x85,0xf5,0xf0,0x1e,0x6c, -0x80,0x8f,0x60,0x13,0xe4,0xc0,0x66,0xd8,0x22,0x3e,0x15,0xdb,0xc4,0x76,0x91,0xfb, -0x71,0x3d,0x04,0x20,0x3f,0x14,0x80,0x24,0x48,0x86,0x9a,0x90,0x2e,0x6a,0x89,0xda, -0x50,0x0f,0x9a,0x88,0xd6,0xa2,0x0d,0xb4,0x87,0x0e,0x70,0x11,0x74,0x84,0x4e,0x70, -0x09,0xdc,0x02,0xb7,0xc2,0x7d,0x70,0xbf,0x78,0x00,0x26,0xc1,0x23,0x62,0x26,0xfc, -0x00,0x5d,0x4a,0xbb,0x74,0x15,0xdd,0xe0,0x72,0xe8,0x21,0x7a,0x8a,0x5e,0x62,0x09, -0x6c,0x81,0x7d,0x70,0xd0,0x7c,0x60,0x7a,0x26,0xcf,0x3d,0x90,0x1f,0xe2,0xa1,0xba, -0x48,0x80,0x34,0xa8,0x09,0x75,0xa1,0x9e,0xa8,0x2f,0x32,0x44,0x6b,0xe8,0x08,0x83, -0x60,0x3c,0xdc,0x0d,0xcf,0xc0,0x34,0xf1,0x2c,0xcc,0x84,0x59,0x62,0x36,0xcc,0x81, -0x17,0xc5,0x5c,0x78,0xc9,0x2c,0x30,0x70,0x16,0xf5,0x04,0xf2,0x41,0x0c,0x14,0x80, -0x04,0x48,0x84,0x34,0xa8,0x25,0xea,0x40,0x5d,0xa8,0x0f,0x59,0xd0,0x58,0x34,0x11, -0xad,0xa1,0x23,0x74,0x81,0x1e,0xd0,0x0b,0xae,0x80,0x2b,0xe1,0x16,0xb8,0x15,0xee, -0x84,0x09,0x70,0x17,0xdc,0x03,0xf7,0xc2,0x44,0x71,0x9f,0xb8,0x5f,0x3c,0x20,0x26, -0xc3,0x34,0x98,0x09,0x1b,0xe1,0x43,0xf1,0x11,0x6c,0x82,0x2d,0xf0,0x85,0xd1,0x32, -0x3c,0x37,0xc1,0x01,0xf8,0x06,0xbe,0x85,0xef,0xcc,0x82,0x09,0x65,0xa9,0x33,0x10, -0x14,0x11,0x22,0x12,0x8a,0x42,0x71,0x48,0x86,0x14,0x48,0x85,0x34,0xa8,0x05,0xf5, -0xa0,0x3e,0x64,0x88,0x06,0x22,0x53,0xb4,0x86,0x8e,0x70,0xb1,0x98,0x29,0xcc,0xe2, -0x0c,0x93,0xcc,0x87,0xbc,0x67,0x07,0x9c,0xfe,0x30,0x12,0x1a,0x9e,0x43,0xbe,0xab, -0x73,0xed,0x73,0x3f,0x9c,0xe5,0x3a,0xa7,0x10,0xa7,0x68,0x00,0x8d,0xa1,0x09,0xb4, -0x83,0x2e,0x70,0xc8,0x2c,0x4c,0xd0,0x08,0x7f,0xf3,0xa1,0x6a,0x57,0xfa,0x11,0x28, -0x03,0xe7,0x42,0x15,0xa8,0x06,0x71,0x50,0x1d,0x12,0x20,0xdd,0x7c,0xd0,0x79,0x05, -0x75,0xe6,0x4a,0xe2,0x84,0xcb,0xa0,0x0b,0xf4,0x86,0x6c,0x58,0x04,0x4b,0xe0,0x4d, -0x58,0x0b,0x7b,0x60,0x1f,0x7c,0x63,0x3e,0xb0,0xef,0x43,0x3c,0x90,0x00,0x69,0x90, -0x01,0x0d,0xa1,0x35,0x74,0x84,0xee,0x30,0x0c,0xae,0x83,0xf1,0x66,0x7f,0x3f,0xf2, -0x0f,0x0d,0xe1,0x4a,0xe8,0x03,0x7d,0x21,0x1b,0xae,0x86,0x8c,0xfe,0x3c,0x9b,0xc1, -0x64,0x98,0x0f,0xaf,0xc0,0x7a,0xf3,0xa1,0xfe,0x00,0xee,0x3f,0xd0,0x4a,0xb4,0x16, -0x6d,0xe0,0x66,0x18,0x0f,0x77,0xc3,0x3d,0xf0,0x18,0x6c,0x81,0x4f,0x61,0x9b,0xd8, -0x2e,0xf6,0xc1,0x7e,0xf8,0x1a,0x0e,0xc0,0x37,0xf0,0x9d,0xf9,0x60,0x34,0x9b,0xdf, -0x41,0xb7,0xd3,0xef,0x41,0x99,0x3b,0x28,0x77,0xf8,0x1a,0xf6,0xcd,0x20,0x1c,0x7c, -0x0b,0x3f,0xc2,0x4f,0xf0,0xb3,0x59,0x10,0x60,0x26,0x7d,0xf0,0x2c,0xfa,0x10,0xd1, -0x1a,0x2e,0x80,0x76,0xe2,0x72,0xe8,0x05,0xd9,0xf0,0xe3,0x6c,0x77,0xa2,0xdd,0x95, -0x90,0x0d,0x23,0x60,0x24,0x8c,0x83,0x09,0x30,0x09,0x1e,0x81,0x47,0x61,0xdf,0x5b, -0xb4,0x6f,0x30,0x8b,0x61,0xc4,0x41,0x02,0x34,0x86,0x1e,0xef,0xd2,0x1f,0x40,0x2f, -0xe8,0x0d,0xfd,0x60,0x10,0x5c,0x0b,0xa3,0x61,0x2c,0xdc,0x06,0xcf,0xc2,0xf3,0x90, -0xb9,0x8e,0xfb,0x95,0xf9,0x80,0xf8,0xbd,0x80,0x73,0x21,0xb4,0x87,0x87,0xe1,0x51, -0x78,0x0c,0x1e,0x17,0x4f,0xc1,0xd3,0x30,0x0d,0x9e,0x83,0x17,0x60,0x01,0xbc,0x22, -0x16,0xc2,0x6b,0xf0,0x06,0x84,0xde,0x0f,0x38,0xd1,0x90,0x1f,0x0a,0x41,0x49,0x38, -0x05,0xce,0x84,0x32,0x90,0x0e,0xb5,0xa1,0x1e,0x64,0x40,0x23,0x68,0x2c,0x9a,0x43, -0x6b,0xb8,0x00,0x9e,0xfa,0x80,0x67,0x3d,0x58,0x02,0xcb,0xe0,0x23,0xc8,0x81,0x43, -0x70,0x18,0x4a,0x6e,0x20,0x5e,0x48,0x81,0x34,0x38,0x1f,0x5a,0x43,0x7f,0xc8,0x86, -0x71,0x70,0x2b,0x4c,0x85,0x67,0x61,0x19,0xbc,0x0e,0xab,0xe1,0x6d,0xf8,0xc5,0x7c, -0xf8,0xf9,0x11,0xc7,0x42,0x6d,0xe8,0x0b,0x57,0x89,0x1b,0xc5,0x43,0xf0,0x18,0x4c, -0x81,0x19,0xe6,0x03,0xca,0xcd,0xf4,0x5f,0x90,0x5f,0x14,0x80,0x32,0x70,0x0e,0x54, -0x31,0x1f,0x84,0x6e,0xa1,0x9f,0x83,0x7c,0x70,0x1a,0x94,0x86,0xf2,0x10,0x0b,0x67, -0x43,0x45,0xa8,0x02,0x09,0xd0,0x0f,0xc6,0x0b,0xf3,0x31,0x60,0x35,0x48,0x80,0x34, -0xa8,0x01,0x75,0x21,0x03,0x9a,0xc1,0xf9,0x62,0xc2,0x56,0xae,0x1b,0xbc,0x20,0x96, -0xc0,0x6e,0xf8,0x12,0xf6,0xc2,0x3e,0x38,0x08,0x6b,0xb7,0xf3,0x1c,0x0c,0xbb,0xc5, -0x97,0xb0,0x17,0xf6,0xc1,0x41,0x58,0xfb,0x19,0xed,0x13,0xbe,0x82,0xbd,0xb0,0x4f, -0xec,0x87,0x43,0xb0,0x64,0x07,0xcf,0xd7,0xb0,0x05,0xb6,0xc2,0x36,0xb1,0x1d,0x3e, -0x83,0x1d,0xe2,0x73,0xd8,0x03,0x5f,0xc1,0x5e,0xd8,0x07,0xdf,0xc0,0xb7,0xe2,0x67, -0xf1,0x8b,0xd8,0xf2,0x39,0xc7,0xc1,0x4e,0xd8,0x0d,0x7b,0xe0,0x2b,0xd8,0x07,0x3f, -0x9b,0x05,0x0a,0x76,0x72,0x8d,0x60,0x2d,0x7c,0x60,0x16,0x6a,0xf9,0x82,0x3e,0x00, -0x1a,0x41,0x73,0x68,0x21,0x5a,0x43,0x47,0xf3,0xe1,0xff,0x57,0xf4,0xb3,0x10,0x82, -0xb2,0x50,0x1e,0x2a,0xc0,0xd9,0xa2,0x92,0x38,0x17,0x92,0x20,0x05,0x6a,0x40,0xba, -0xa8,0x2d,0x32,0x45,0x13,0xd1,0x4c,0xb4,0x10,0x2d,0xa1,0x15,0xb4,0x81,0x0b,0xe0, -0x42,0xd1,0x41,0x74,0x14,0x9d,0xc4,0xa5,0xa2,0xb3,0xe8,0x0a,0xdd,0xe0,0x72,0xd1, -0x0b,0xae,0x84,0xbe,0x30,0x08,0x86,0xc0,0x30,0xb8,0x1a,0x46,0xc0,0xf5,0x62,0x24, -0xdc,0x02,0xb7,0xc1,0x43,0x30,0x05,0x9e,0x80,0xa7,0x60,0x1a,0x4c,0x87,0x57,0x60, -0xdf,0x8f,0x5c,0x57,0xf3,0x21,0xb2,0x13,0x74,0x36,0x3f,0x1d,0x74,0x9c,0x67,0x82, -0xce,0x59,0xf3,0x83,0x4e,0x39,0x78,0xfe,0xed,0xa0,0x33,0x13,0xb6,0xec,0x0e,0x3a, -0xfb,0xe0,0x52,0x33,0x01,0xf7,0x07,0xf6,0x1f,0x72,0x27,0xe2,0x36,0x2f,0x11,0xc1, -0x6f,0x95,0x08,0xe7,0xea,0x94,0x88,0xdc,0x09,0x2d,0x7b,0xc1,0x9b,0x47,0x9c,0xd7, -0xfc,0xe1,0xff,0xd5,0xbc,0xe1,0xdf,0x3a,0x5f,0xf8,0x44,0xe7,0x09,0x1f,0x6f,0x7e, -0xb0,0x7f,0xde,0x6f,0x5e,0xf3,0x7d,0xbd,0xf9,0xbd,0xfe,0x79,0xbd,0xde,0x3c,0x5e, -0x6f,0xde,0x6e,0x5e,0xf3,0x75,0xbd,0x79,0xba,0xde,0xfc,0x5c,0x6f,0x5e,0xae,0x37, -0x1f,0xd7,0x3f,0x0f,0xd7,0x9b,0x7f,0xeb,0xcd,0xbb,0xf5,0xe6,0xdb,0x7a,0xf3,0x6c, -0xbd,0xf9,0xb5,0x79,0xcd,0xab,0xf5,0xcf,0xa7,0xf5,0xe6,0xcd,0x7a,0xf3,0x63,0xbd, -0x79,0xaf,0xde,0x7c,0x57,0xff,0xfc,0xd6,0x93,0x9d,0xcf,0xea,0xcd,0x63,0xf5,0xe6, -0xa7,0xfa,0xe7,0xa5,0xfa,0xe7,0xa1,0xfe,0xd6,0xf9,0xa6,0xfe,0xf9,0xa3,0xde,0x7c, -0xd1,0x93,0x9d,0x17,0xea,0xcd,0x07,0xf5,0xcf,0xef,0xf4,0xe6,0x73,0x9e,0xec,0xbc, -0x4d,0x6f,0xbe,0xa6,0x7f,0x9e,0xa5,0x37,0xaf,0xf2,0x8f,0x9e,0x4f,0xe9,0xcd,0x8f, -0x3c,0xde,0xbc,0x47,0x6f,0xbe,0x63,0x5e,0xf3,0x18,0xbd,0x79,0x8b,0x27,0x3a,0x4f, -0xd1,0x9b,0x9f,0xe8,0x9f,0x5f,0xe8,0xcd,0x27,0xcc,0x6b,0xde,0xa0,0x37,0x5f,0xd0, -0x3f,0x0f,0xd0,0x9b,0xff,0xe7,0xcd,0xf7,0x3b,0xd1,0xf9,0x7d,0xde,0x3c,0x3d,0x6f, -0x7e,0x9e,0x7f,0x5e,0xde,0xc9,0xce,0xc3,0xf3,0xe6,0xdf,0xe5,0x35,0xef,0xce,0x3f, -0xcf,0xce,0x3f,0x6f,0xce,0x9b,0x17,0xe7,0xcd,0x7f,0xf3,0xe6,0xaf,0x79,0xf3,0xd6, -0xbc,0x79,0x6a,0xde,0xfc,0x34,0x6f,0x5e,0x9a,0x37,0x1f,0xcd,0x9b,0x87,0xe6,0xcd, -0x2f,0xfb,0xbb,0xcd,0x1f,0xfb,0xa3,0xe7,0x8b,0x79,0xf3,0xc3,0xbc,0xf9,0x60,0xfe, -0x79,0x60,0xde,0xfc,0x2f,0xff,0x3c,0xae,0x93,0x9d,0x9f,0x95,0xd7,0xbc,0xac,0xe3, -0xcd,0xa7,0xf2,0xe6,0x4f,0x79,0xf3,0xa4,0xbc,0x79,0x4b,0xde,0x7c,0x24,0x6f,0x3e, -0x91,0x37,0x5f,0xc8,0x9b,0xff,0xe3,0xcd,0xfb,0xf1,0xe6,0xed,0x78,0xf3,0x71,0x4e, -0x74,0x1e,0x8e,0x37,0xcf,0xc6,0x9b,0x5f,0xe3,0xcd,0xab,0xf9,0xad,0xf3,0x68,0xfe, -0xaa,0xf3,0x67,0xf2,0x9a,0x27,0xf3,0x77,0x9f,0x0f,0x73,0xb2,0xf3,0x60,0xfe,0xa8, -0xf9,0x2f,0xc7,0x9b,0xf7,0x72,0xa2,0xf3,0x5c,0x8e,0x37,0xbf,0xe5,0x7f,0x35,0xaf, -0x25,0xaf,0xf9,0x2c,0x79,0xcd,0x5f,0xf1,0xcf,0x5b,0xf9,0xb3,0xe7,0xab,0x78,0xf3, -0x54,0xbc,0xf9,0x29,0xde,0xbc,0x14,0x6f,0xde,0x89,0x37,0xdf,0x24,0xaf,0xf9,0x24, -0xde,0xfc,0x11,0x6f,0xde,0x88,0x37,0x4f,0xc4,0x9b,0x17,0xe2,0xcd,0xf3,0xf0,0xe6, -0x77,0xf8,0xe7,0x75,0xf8,0xe7,0x73,0x78,0xf3,0x34,0xbc,0xf9,0x19,0xc7,0x9b,0x87, -0xe1,0xcd,0xb7,0xf0,0xe6,0x59,0x78,0xf3,0x2b,0xbc,0x79,0x15,0xde,0x7c,0x8a,0xe3, -0xcd,0xa3,0xf0,0xcf,0x97,0xf8,0x6f,0xcd,0x8f,0xf8,0xbd,0xf3,0x1b,0xfe,0x5b,0xf3, -0x1a,0xfc,0xf3,0x16,0xbc,0xf9,0x0a,0xde,0x3c,0x85,0xbf,0xca,0xfc,0x04,0xff,0xbc, -0x84,0x93,0x9d,0x8f,0x70,0xb2,0xf3,0x10,0xc2,0xf3,0x0f,0x8e,0x3d,0xff,0x20,0xaf, -0x79,0x06,0xc7,0x9b,0x57,0xf0,0x7b,0xe7,0x13,0x1c,0x6f,0xde,0x40,0x5e,0xf3,0x05, -0x8e,0x37,0x4f,0x20,0xaf,0xf9,0x01,0xde,0xbc,0x00,0xff,0x7c,0x00,0x6f,0xdc,0xdf, -0x1b,0xdf,0xf7,0xc6,0xf5,0xbd,0x71,0x7c,0x6f,0x7c,0xde,0x1b,0x8f,0xf7,0xc6,0xdb, -0xfd,0xe3,0xec,0xde,0xf8,0xba,0x37,0x9e,0xee,0x8d,0x9b,0x1f,0x6f,0x5c,0xdc,0x1b, -0xdf,0x3e,0xd9,0x71,0xed,0x93,0x1d,0xa7,0xf6,0xc6,0xa7,0xbd,0x71,0x67,0x6f,0xbc, -0xf9,0x44,0xc7,0x95,0xbd,0xf1,0x60,0x6f,0x1c,0xd8,0x1b,0xff,0xf5,0xc6,0x73,0xfd, -0xe3,0xb7,0xfe,0xf1,0x57,0x6f,0xdc,0xd5,0x1b,0x47,0xf5,0xc6,0x4f,0xbd,0xf1,0x50, -0x6f,0x1c,0xd4,0x1b,0xcf,0xf4,0xc6,0x2f,0xfd,0xe3,0x8f,0xde,0xf8,0xe2,0x89,0x8e, -0x2b,0x7a,0xe3,0x85,0xde,0x38,0xa1,0x7f,0x3c,0xd0,0x3f,0xce,0xe7,0x8d,0xef,0x79, -0xe3,0x7a,0xde,0x38,0x9e,0x37,0x6e,0xe7,0x8d,0xcf,0xf9,0xc7,0xe1,0xbc,0x71,0x35, -0xff,0x78,0x9a,0x37,0x4e,0xe6,0x8d,0x8f,0xe5,0x35,0xce,0xe5,0x8d,0x63,0xe5,0x35, -0x6e,0xe5,0x8d,0x43,0xe5,0x39,0xfe,0xa4,0x71,0x27,0x6f,0x7c,0xc9,0x3f,0xae,0x74, -0xb2,0xe3,0x42,0xde,0x78,0x50,0x5e,0xe3,0x40,0xfe,0x71,0x1e,0x6f,0x7c,0xc7,0x1b, -0xd7,0xf1,0xc6,0x73,0xbc,0x71,0x9c,0x13,0x1d,0xbf,0x39,0xde,0xb8,0x8d,0x37,0x5e, -0x93,0xd7,0xf8,0xcc,0x5f,0x6d,0x3c,0xe6,0xdf,0x36,0x6e,0x72,0xbc,0xf1,0x0f,0x6f, -0x5c,0xc3,0x1b,0xcf,0xf0,0xc6,0x31,0xfc,0xe3,0x17,0xde,0xb8,0x85,0x7f,0x9c,0xc2, -0x1b,0x97,0xf0,0xc6,0x23,0xfc,0xe3,0x10,0xde,0xf8,0x83,0x37,0xee,0xe0,0x8d,0x37, -0xfc,0xd6,0x71,0x06,0x6f,0x7c,0xc1,0x1b,0x57,0x08,0x8f,0x27,0xb8,0xe3,0x09,0xbf, -0x7b,0x1c,0xc1,0xac,0x3d,0x0b,0xf9,0xc1,0xfb,0x0c,0xbb,0x10,0x14,0x76,0x5f,0x65, -0x98,0x25,0x16,0x1d,0x7e,0x9e,0x3b,0xe6,0x27,0x70,0x2c,0x54,0x84,0xaa,0xee,0xcf, -0x61,0xf3,0x53,0xd7,0x89,0x07,0xb3,0xa6,0x49,0x2d,0xa8,0xad,0x75,0x4d,0x3a,0x69, -0x3c,0xe2,0x5a,0xb8,0x01,0xc6,0xc2,0x4d,0xc0,0x4f,0x3f,0x67,0x0a,0x3c,0x0e,0x4f, -0x6b,0x9c,0xc2,0x3c,0xc2,0x3f,0x0f,0x2f,0xc0,0x4c,0x98,0x0d,0x2f,0x6a,0xec,0xe2, -0x25,0x58,0xa0,0xf1,0x8b,0x57,0x34,0x86,0xb1,0x41,0x63,0x18,0xdf,0x6b,0x0c,0x63, -0x01,0x59,0x7a,0x05,0x96,0xc1,0x5a,0x58,0xa7,0x31,0x89,0xcf,0x34,0x0e,0xb1,0x57, -0x6b,0x98,0x64,0x5b,0xe3,0x10,0x66,0xfc,0xe1,0x7a,0x8d,0x3b,0xdc,0x68,0xad,0x53, -0x32,0xce,0x1a,0x73,0x30,0xe3,0x0d,0xdf,0x69,0xbc,0x61,0x8c,0xc6,0x1b,0x12,0x35, -0xc6,0x70,0x2b,0xdc,0x01,0xe3,0xb5,0x0e,0xc9,0x22,0xad,0x41,0xb2,0x44,0xeb,0x90, -0xbc,0xa6,0x75,0x48,0x96,0x5b,0x6b,0x90,0x98,0x71,0x08,0x27,0x8a,0x7f,0x90,0x0f, -0x62,0xa0,0x00,0x14,0x84,0x42,0x50,0x38,0xca,0x5d,0x9b,0xa4,0x34,0x94,0xd1,0x3a, -0x24,0x66,0x0d,0x92,0x2b,0xa1,0x0f,0xf4,0xb3,0xd6,0x1f,0x31,0x6b,0x8e,0x7c,0x0c, -0x9f,0x68,0xdd,0x91,0xcf,0xb4,0xde,0xc8,0x17,0x5a,0x6f,0xe4,0x2b,0xad,0x33,0x62, -0xc6,0x2c,0x8a,0x58,0xeb,0x89,0x24,0x68,0x9c,0xe2,0x69,0x6b,0xed,0x90,0xdd,0x1a, -0xa3,0xf8,0xce,0x5a,0x27,0xe4,0xe7,0x68,0x77,0x7d,0x10,0x6f,0x5d,0x10,0x6f,0x3d, -0x10,0x6f,0x1d,0x90,0x04,0x8d,0x53,0x98,0x35,0x40,0x2e,0xd3,0x58,0x85,0x19,0x97, -0x78,0x09,0xd6,0xc3,0x56,0xad,0xef,0x11,0x61,0xad,0xe5,0xe1,0xad,0xe1,0xd1,0x40, -0x6b,0x76,0x78,0x6b,0x75,0xb4,0xd1,0x58,0x85,0x59,0x97,0xe3,0x72,0x6b,0xed,0x8d, -0xb1,0x1a,0xa3,0xb8,0x47,0x63,0x13,0xde,0x98,0x84,0xb7,0xb6,0xc6,0x52,0xad,0xa5, -0xb1,0xda,0x5a,0x3f,0xc3,0x5b,0x37,0x63,0xb3,0xd6,0xcb,0x30,0x6b,0x65,0x7c,0x0d, -0xdf,0xc0,0x8f,0xd6,0x3a,0x19,0x01,0x6b,0x6d,0x8c,0x22,0x1a,0xa3,0xa8,0xa2,0xb1, -0x89,0x54,0x8d,0x49,0x78,0x6b,0x5f,0x34,0xd0,0x9a,0x17,0x2d,0xac,0x75,0x2e,0xbc, -0xf5,0x2d,0xb2,0x35,0x4e,0x61,0xd6,0xb3,0x18,0xaa,0x35,0x2d,0xbc,0xb5,0x2c,0xc6, -0x6b,0xac,0xe2,0x59,0x8d,0x51,0xcc,0xb5,0xd6,0xab,0x58,0xaa,0xf5,0x29,0xbc,0x75, -0x29,0xd6,0x58,0x63,0x15,0x5b,0xb5,0x06,0xc5,0x7e,0x6b,0xfd,0x09,0x6f,0xdd,0x89, -0x90,0xc6,0x2d,0x4a,0x68,0xbc,0x22,0x49,0xe3,0x14,0xde,0xba,0x12,0xf5,0xad,0x35, -0x24,0x9a,0x68,0xed,0x08,0x6f,0xcd,0x08,0x6f,0xad,0x88,0xce,0x5a,0x23,0xa2,0xa7, -0xb5,0x1e,0xc4,0x58,0x8d,0x5b,0xdc,0xad,0xf1,0x0a,0x6f,0x9c,0xe2,0x09,0x8d,0x4f, -0xbc,0xa0,0x75,0x1e,0xbc,0xf5,0x1d,0x96,0x6b,0x5d,0x87,0x35,0xd6,0x5a,0x0e,0x66, -0x1d,0x87,0x03,0x70,0x10,0x0e,0x15,0x38,0xb2,0x96,0x83,0xb7,0x86,0x43,0x61,0x8d, -0x61,0x54,0xb1,0xd6,0x69,0x68,0xa0,0xf5,0x19,0x9a,0x6b,0x5d,0x86,0x8b,0x34,0x7e, -0xd1,0x4d,0xeb,0x2f,0x0c,0x83,0xe1,0x70,0x83,0xb5,0xf6,0xc2,0x9d,0x1a,0xc3,0x78, -0x56,0x63,0x17,0xde,0xda,0x0a,0xcb,0xb5,0x66,0xc2,0x5b,0x5a,0x2b,0xc1,0x1b,0xc3, -0xd8,0xaa,0xb5,0x11,0xf6,0xab,0x63,0x0b,0x6a,0x0c,0xa3,0xb8,0xd6,0x3c,0xa8,0xab, -0x35,0x0e,0x1a,0x6b,0x6d,0x83,0x96,0xd6,0x38,0x46,0x37,0xad,0x5b,0xe0,0xad,0x53, -0x30,0x56,0xe3,0x17,0x73,0xac,0x31,0x0b,0x6f,0x2d,0x82,0x75,0xd6,0xfa,0x03,0x3b, -0xb4,0xee,0x40,0xc0,0x5a,0x6b,0xc0,0xac,0x33,0x50,0x5f,0xe3,0x19,0x66,0x6d,0x81, -0x56,0x1a,0xcb,0xf0,0xc6,0x30,0x6e,0xb7,0xc6,0x2d,0xbc,0x75,0x03,0x5e,0xb4,0xc6, -0x2c,0xbc,0x35,0x02,0xd6,0x6a,0xcc,0xc2,0x5b,0x13,0xe0,0x0c,0xad,0x03,0x90,0xa6, -0x75,0x00,0x6a,0x6a,0x1d,0x80,0x5a,0x5a,0x0b,0xa0,0x99,0xbe,0xfd,0xf7,0xbe,0xf9, -0x1f,0xab,0x6f,0xfe,0xc7,0xe9,0x5b,0x7f,0xf3,0x9d,0xff,0x22,0x7d,0xe3,0xbf,0x02, -0xde,0xd6,0x58,0x47,0x9c,0xf5,0x5d,0xbf,0xf7,0x3d,0x7f,0x6d,0x7d,0xc7,0x5f,0x5f, -0xdf,0xe9,0x5f,0xac,0xef,0xf3,0xbb,0xea,0xbb,0xfb,0xab,0xac,0x6f,0xed,0xc7,0xea, -0x1b,0x7b,0xef,0xdb,0x7a,0xf3,0xed,0xfc,0xa3,0xd6,0x98,0x87,0x19,0xef,0x68,0xae, -0xf1,0x0c,0x6f,0x1c,0xa3,0x8b,0xc6,0x2f,0x26,0x68,0xdc,0x62,0xbe,0x35,0x56,0xb1, -0x52,0x63,0x14,0xc5,0x35,0x36,0x61,0xbe,0x6b,0xef,0xa2,0xf1,0x89,0x09,0x1a,0x9f, -0x30,0x63,0x13,0x4e,0x49,0x77,0x6c,0x62,0xa4,0xc6,0x24,0x1e,0xd2,0x98,0x84,0x19, -0x7f,0x28,0xac,0xef,0xd5,0xcb,0xe9,0xfb,0xf4,0x54,0x8d,0x43,0x5c,0xa0,0x71,0x88, -0xfe,0xff,0x57,0xd9,0xb9,0x40,0x47,0x59,0x98,0x69,0x78,0x26,0x4c,0x92,0x49,0xc2, -0x25,0x56,0x11,0xaf,0x88,0x82,0x02,0xa2,0x24,0x87,0x84,0x8b,0x5c,0xe3,0x0a,0xca, -0xd9,0x2e,0x27,0x41,0x2b,0x95,0x65,0x95,0xb8,0x8a,0x56,0x6a,0x0b,0xd5,0x54,0xad, -0x5a,0x8c,0xa0,0x45,0x50,0x2c,0xa7,0xab,0xed,0xd1,0xe2,0x82,0x6b,0xbd,0x50,0x45, -0xc2,0xa2,0xd6,0xae,0x0a,0x51,0xcb,0x6a,0xab,0x14,0x14,0xad,0xbb,0x5a,0x05,0xb4, -0xed,0x52,0x95,0xca,0x4d,0x91,0x9b,0xd9,0xf7,0x9b,0xff,0xf9,0x92,0x2f,0x63,0x50, -0x3b,0x39,0x6f,0x12,0x26,0x21,0x33,0xf9,0x33,0x13,0x7e,0xde,0xef,0x7d,0x9f,0x8f, -0x1e,0x7a,0x23,0xfd,0x73,0xef,0x9d,0xaf,0x0a,0x5d,0x73,0xeb,0x99,0xbf,0x26,0xbd, -0x15,0xfa,0xe5,0xd6,0x2d,0xdf,0x26,0xa5,0x8e,0x48,0xfa,0xe5,0x5d,0xe9,0x95,0xf7, -0x62,0x3e,0x71,0x33,0x73,0x89,0xa5,0x61,0x26,0x91,0xa2,0x23,0xde,0x9b,0x6e,0x78, -0x7f,0x3a,0xe1,0xde,0x05,0xb7,0x1e,0x78,0x8d,0x34,0x96,0x2e,0xf8,0x4c,0x66,0x15, -0x4d,0x74,0xbe,0xdf,0x61,0x56,0xd1,0x83,0x8e,0xb7,0x75,0xbb,0x7b,0x86,0x2e,0xf7, -0x79,0xa1,0xbf,0xed,0xbd,0xed,0x69,0xf4,0xb5,0xaf,0x0c,0x1d,0x6d,0xeb,0x67,0x2f, -0xa1,0x97,0x9d,0xa2,0x7b,0x5d,0x1d,0xba,0xd6,0x23,0x42,0xbf,0xda,0x7b,0xd5,0x75, -0xf4,0xa8,0x1b,0xe8,0x51,0xcf,0xa6,0x2f,0xfd,0x00,0x3d,0x69,0xef,0x47,0x3f,0x1a, -0x3a,0xd0,0x5b,0xe8,0x3e,0x6f,0x0d,0x7d,0x67,0xeb,0x3a,0xef,0x3f,0x3a,0xe9,0x36, -0x57,0xd3,0x69,0x1e,0xc1,0xec,0x64,0x21,0xb3,0x11,0xeb,0x27,0x6f,0xa0,0x9f,0xec, -0xbd,0xe4,0xbf,0x86,0x2e,0xb2,0x77,0x90,0x3f,0x0d,0x73,0x12,0xeb,0x10,0xef,0x63, -0x06,0x72,0x04,0xb3,0x8f,0xf1,0xcc,0x3c,0x26,0x87,0x39,0xc7,0x54,0xe6,0x1b,0x36, -0xdb,0x78,0x86,0xb9,0xc6,0x6a,0x66,0x1b,0x36,0xd7,0x58,0xcb,0x5c,0xc3,0x66,0x1a, -0x6f,0x33,0xd3,0xd8,0xca,0x4c,0xc3,0xe6,0x19,0x07,0x38,0x39,0x2a,0x93,0xba,0x4a, -0xdd,0xa4,0x72,0xa9,0x9f,0xd4,0x5f,0x3a,0x59,0x1a,0x20,0x9d,0x22,0x9d,0x2a,0x0d, -0x94,0x2a,0xa4,0x4a,0x69,0xb0,0x34,0x56,0x1a,0x27,0x4d,0x95,0xea,0xa5,0x4b,0xa4, -0x4b,0xa5,0x6f,0x49,0x97,0x49,0xd3,0xa5,0x99,0xcc,0x4b,0x1a,0xa4,0x6b,0xa5,0xeb, -0x99,0x9b,0x34,0x4a,0xf3,0xa5,0xdb,0x98,0xa1,0x2c,0xa1,0x6b,0x9c,0x62,0x8e,0xd2, -0x89,0x39,0x4a,0x96,0x59,0xca,0x61,0xcc,0x51,0x0e,0x67,0x86,0xd2,0x93,0xf9,0xc9, -0x89,0xcc,0x4f,0xfa,0x32,0x3f,0xe9,0xcf,0x0c,0xc5,0x67,0x27,0xc3,0x99,0x95,0xd4, -0x30,0x2b,0xf9,0x47,0xe6,0x24,0x75,0xcc,0x4a,0xe6,0x49,0x0b,0xa5,0xaf,0x9d,0xa0, -0x7f,0x97,0xa4,0xc3,0xa4,0x21,0x3a,0x51,0xbc,0x4e,0xba,0x53,0xfa,0xa9,0x54,0xd3, -0x47,0x9f,0x2b,0xdd,0x22,0x2d,0x92,0xde,0x97,0x1e,0xd1,0x89,0xe4,0x32,0x3b,0x99, -0xec,0x27,0xf5,0xd7,0xbf,0x9f,0x3a,0xa9,0xfc,0x19,0x27,0x96,0x95,0x52,0xbd,0xf4, -0x67,0xe6,0x2b,0x1f,0x31,0x5f,0xd9,0xc3,0x49,0x67,0xdf,0x30,0x53,0x99,0x11,0xfa, -0xc4,0x4b,0x98,0xa3,0x34,0x31,0x3f,0x69,0x66,0x6e,0xb2,0x9e,0x79,0xc9,0x26,0x4e, -0x58,0x1b,0xa5,0x9f,0xdb,0x89,0x6b,0x85,0x9e,0xb7,0x15,0xc9,0x09,0x6c,0x9a,0x39, -0x4a,0x96,0xd9,0xc9,0xc9,0x74,0x85,0x2b,0x99,0xa1,0x0c,0x61,0x86,0x32,0x8a,0x99, -0xc9,0x5d,0xcc,0x4c,0x6c,0x5e,0xb2,0x84,0x39,0x89,0xf7,0x7f,0x0b,0x99,0x8b,0x2c, -0x94,0x9a,0x99,0x89,0x1c,0x3b,0x48,0x0f,0x15,0xa9,0x4e,0xaa,0x97,0xae,0x97,0x6e, -0x90,0x56,0x48,0xcd,0xd2,0xcb,0xd2,0x7a,0xe9,0xea,0x2a,0x1d,0x3b,0xe9,0x1d,0x69, -0x93,0xd4,0x52,0x95,0xcc,0x52,0x9a,0x99,0xa5,0xd8,0x49,0xf4,0x42,0xe6,0x27,0x5d, -0x98,0x93,0xd4,0xd0,0xed,0xbd,0x92,0x7e,0xef,0x2c,0xe6,0x22,0xff,0xce,0x3c,0xc4, -0xba,0xbb,0x3b,0xec,0x84,0x9b,0x39,0x88,0xcd,0x3b,0xe6,0x49,0xcf,0x31,0xd7,0xf0, -0x7e,0xad,0xf7,0x6a,0xbd,0x4f,0x7b,0x22,0xdd,0xd9,0x7e,0x74,0x67,0x2b,0xa5,0x61, -0xcc,0x32,0x1a,0xe8,0xd0,0x5a,0x5f,0xf6,0xf7,0xf4,0x64,0x77,0x84,0x2e,0xac,0xf5, -0x5f,0x07,0xd2,0x7b,0xb5,0xce,0xeb,0x54,0xba,0xae,0x73,0x42,0xaf,0xf5,0x09,0xfa, -0xac,0x4f,0xd3,0x63,0xb5,0xb9,0xc6,0x3b,0xa1,0xaf,0x9a,0xa2,0x8f,0x5a,0x41,0x0f, -0xb5,0x8a,0xfe,0x69,0x4d,0xe8,0x9c,0xfe,0x53,0xe8,0x99,0xfe,0x4b,0xe8,0x8e,0x36, -0x85,0x9e,0xe8,0xe3,0xf4,0x43,0x7f,0x4d,0x2f,0xb4,0x39,0x74,0x41,0xdf,0xa2,0xeb, -0xf9,0xb7,0xd0,0xe3,0x2c,0x61,0x3e,0xd1,0x8b,0xb9,0x84,0xcd,0x24,0xa6,0x33,0x7b, -0xf0,0x0e,0xa6,0x77,0x2f,0xbd,0x6b,0x69,0x73,0x88,0x26,0xe6,0x0f,0xf6,0x1f,0x94, -0x99,0xcc,0x1c,0x0e,0x0d,0x3d,0x4a,0x9b,0x35,0xf4,0x67,0xd6,0x30,0x2c,0xcc,0x17, -0xce,0x60,0xae,0x50,0xc7,0x5c,0x61,0x34,0x73,0x85,0x9f,0x30,0x4f,0x38,0x40,0x4f, -0xb2,0x9c,0x9e,0x64,0xa5,0x54,0x27,0x4d,0x94,0xce,0x96,0xbe,0x21,0x9d,0x2b,0x4d, -0x92,0xa6,0x4a,0xf5,0xd2,0xbf,0x4a,0x17,0x4b,0xdf,0x92,0x2e,0x93,0xbe,0x4d,0x97, -0xd2,0xe6,0x11,0x2d,0x52,0xba,0x45,0x3f,0x73,0xa9,0x48,0x2a,0x93,0x3a,0x4b,0x5d, -0xa4,0xae,0x2d,0x49,0xc7,0xf2,0x28,0xe9,0x68,0x7a,0x96,0x23,0x98,0x5b,0x4c,0x61, -0x6e,0xf1,0x1c,0xf3,0x88,0x57,0x99,0x43,0x6c,0x92,0xde,0x95,0xfe,0x2c,0x6d,0x91, -0xf6,0x30,0x97,0xe8,0xc2,0x3c,0x62,0x24,0x73,0x88,0x3a,0xe6,0x0f,0xb6,0x68,0x2a, -0x2d,0x15,0x48,0xc5,0xd2,0xd0,0x74,0x32,0x8f,0x58,0x20,0x2d,0x94,0x96,0x48,0xeb, -0x43,0x6f,0x72,0x09,0x5d,0xc9,0xcd,0xcc,0x27,0x4e,0x65,0xfe,0x70,0x15,0xf3,0x87, -0xc5,0xcc,0x1d,0x52,0xf4,0x1c,0x9b,0x98,0x2f,0xa4,0x98,0x2b,0xcc,0x60,0x1e,0x50, -0xc3,0x1c,0x20,0x15,0x3c,0xfe,0xa1,0xc1,0xd7,0xff,0xa6,0xf4,0x20,0x5e,0xbe,0xf9, -0xf8,0xfb,0xa4,0xe3,0xf0,0xf0,0xc7,0xe0,0xdd,0x3f,0x2d,0xbd,0x5a,0x92,0xf4,0x05, -0x0b,0xe9,0x09,0x96,0xe3,0xdb,0x1f,0x49,0x2f,0xd0,0xfa,0x80,0x35,0x52,0x1d,0xfe, -0x7d,0x33,0xfd,0xbf,0x0f,0xf0,0xeb,0x4f,0xc3,0xaf,0x3f,0x4f,0xba,0x46,0xba,0x4b, -0x5a,0x8a,0x57,0xdf,0x88,0x47,0xbf,0x17,0x8f,0xbe,0x0f,0x5d,0xbf,0xfa,0xae,0x89, -0xf7,0x6e,0x3d,0xbd,0x65,0xf8,0xef,0x29,0x7c,0x75,0xeb,0xdc,0x4d,0xc4,0x53,0xdf, -0xd4,0xad,0xad,0x57,0x57,0x40,0x8f,0xee,0x2c,0xe9,0x6c,0xba,0x74,0x37,0xd0,0x8f, -0x5b,0x49,0x17,0xce,0x7a,0x70,0x4f,0x49,0x6b,0xf1,0xd8,0xb7,0xd1,0x7d,0xeb,0x44, -0xe7,0xcd,0xbb,0x6e,0x43,0xf0,0xd8,0xad,0xc3,0xd6,0x40,0x7f,0x6d,0x36,0x3d,0xb5, -0xd5,0xf4,0xd2,0xd6,0x86,0x2e,0xda,0x66,0x3c,0xf6,0x43,0xe9,0x9a,0x0d,0x0a,0xfd, -0x32,0xeb,0x96,0x0d,0xa3,0x57,0xd6,0x18,0x7c,0xf6,0x1f,0xe3,0xaf,0x37,0x85,0xae, -0xd8,0x7b,0x74,0xc4,0x52,0x74,0xc3,0x0a,0xf1,0xda,0x0f,0xc5,0x63,0x1f,0x84,0xb7, -0x3e,0x02,0x5f,0xdd,0xbb,0x5e,0x67,0xd1,0xf1,0xaa,0x0b,0xbd,0xae,0x4b,0xe8,0x71, -0xa5,0xe8,0x49,0xd5,0xd0,0x8f,0x1a,0x17,0x3a,0x51,0xe7,0x87,0x1e,0x54,0x23,0xbd, -0xa7,0x5f,0x87,0xae,0xd3,0xea,0xd0,0x6f,0xfa,0x0d,0x7d,0xa6,0x46,0xfa,0x4b,0x4f, -0xd2,0x5b,0xf2,0xbe,0xd2,0xb3,0xf4,0x94,0x52,0xf4,0x92,0x46,0x84,0x2e,0x92,0xf5, -0x90,0x4e,0xa7,0x7f,0x54,0x8f,0xef,0xfe,0x70,0xe8,0x1c,0x3d,0x4a,0xd7,0xe8,0x89, -0xd0,0x2f,0x6a,0xc6,0x8f,0x3f,0x96,0xde,0xd0,0x09,0xa1,0x2b,0x54,0x89,0x37,0x5f, -0x11,0x7a,0x40,0x23,0xf0,0xe9,0xcd,0xa3,0x77,0x6f,0xbe,0x3b,0x9e,0xfc,0x50,0xba, -0x3d,0xa3,0x42,0x9f,0xc7,0xfa,0x3a,0x63,0xe9,0xec,0x2c,0xc1,0x9b,0x7f,0x8d,0x6e, -0xce,0x1f,0x43,0x1f,0xc7,0x7b,0x37,0xc5,0x52,0x09,0x1e,0x7d,0x15,0x7d,0x1b,0xeb, -0xd9,0x8c,0x09,0x1d,0x9b,0x89,0x74,0x6b,0x26,0xe3,0xd9,0xcf,0x0f,0x3d,0x9a,0xbb, -0xcd,0xaf,0x3f,0x2a,0xf1,0xea,0x4b,0xe9,0xba,0x0c,0xa3,0xdf,0x62,0x7d,0x96,0x99, -0xf4,0x58,0xbc,0xbf,0xe2,0xbd,0x15,0xef,0xab,0xa4,0xf0,0xef,0xcb,0xe8,0xa7,0x78, -0x2f,0x65,0x4c,0xe8,0xa2,0xd4,0xe1,0xe5,0x5f,0x8a,0x87,0xbf,0x90,0xae,0xc9,0x9d, -0xa1,0x5f,0xe2,0xbd,0x92,0x4d,0xf4,0x45,0xac,0x2b,0x62,0x1e,0x7d,0xb3,0xf4,0xba, -0xf4,0xb6,0xb4,0x59,0x6a,0x39,0x2e,0xf1,0xeb,0x67,0xe2,0xd7,0x9b,0x47,0x5f,0x59, -0x9d,0x78,0xf2,0xe6,0xc3,0xd7,0xd0,0xb3,0xf8,0x2e,0xfe,0xbb,0x75,0x2a,0x3e,0xc6, -0x83,0xb7,0x3e,0xc5,0x50,0x7a,0x14,0x53,0xf0,0xe1,0xcd,0x83,0xaf,0xc1,0x53,0xf7, -0x0e,0xc4,0x8f,0xf1,0xd4,0xcd,0x4f,0x7f,0x37,0x74,0x1d,0xb6,0xe1,0xa1,0xa7,0x6e, -0x91,0x16,0x24,0xfe,0x78,0x1d,0x3e,0xb8,0x79,0xe0,0x29,0xbc,0xef,0x46,0x3c,0xef, -0x45,0x74,0x10,0x16,0x4b,0x4b,0xcc,0xff,0xb6,0x25,0x6e,0xaf,0xa4,0x53,0xfd,0xe9, -0x1c,0x4c,0x97,0xae,0xa4,0x6f,0x30,0x87,0xbe,0xc1,0x6d,0xf4,0x0d,0x1e,0xb5,0xc5, -0x93,0xaf,0x26,0x9d,0x83,0x71,0xd2,0x26,0xeb,0x1d,0x6c,0xd0,0xf7,0xb6,0x21,0xf1, -0xd1,0x27,0xe1,0x9f,0xbb,0x6f,0xbe,0x14,0xbf,0xdc,0x7d,0x72,0xf3,0xc8,0x8b,0xf1, -0xc6,0x0f,0xc7,0x13,0x1f,0x81,0x17,0xee,0x1e,0xb8,0xf9,0xdf,0xe6,0x7d,0xbf,0x85, -0xe7,0xdd,0x1d,0xaf,0xbb,0x16,0x8f,0x7b,0x1e,0xde,0xf6,0xf3,0x78,0xda,0xd6,0x03, -0xc8,0x2d,0x4a,0x7a,0x53,0xc7,0x91,0x1e,0xc0,0x77,0xe8,0x00,0x5c,0x45,0x07,0xe0, -0x66,0x7a,0x00,0xd6,0x01,0x48,0x91,0xed,0x3f,0x9a,0x4c,0xff,0x49,0x78,0xdd,0x95, -0x78,0xdd,0x35,0xf8,0xdc,0xcd,0x64,0xf2,0x2d,0x8f,0xdf,0xf2,0x6e,0xe2,0x37,0xbf, -0x40,0xae,0x3d,0x45,0x9e,0xdd,0x73,0xec,0x17,0x48,0xdf,0xfb,0x50,0x8f,0xfd,0xad, -0x89,0xff,0x5c,0x88,0xef,0xdc,0x27,0x78,0xcd,0xd5,0xc1,0x5f,0xfe,0x87,0xe0,0x29, -0xbb,0x97,0x3c,0x31,0xf8,0xc7,0x93,0x83,0x67,0xec,0x5e,0xb1,0x7b,0xc4,0x97,0xe3, -0x0d,0xff,0x20,0xf8,0xc1,0xb7,0xe2,0x03,0xdf,0x8f,0xff,0xbb,0xcd,0xfc,0xdf,0x6d, -0xfa,0x9e,0xa4,0x25,0xd2,0x0a,0xe9,0x59,0xe9,0x15,0x5b,0xbc,0xb7,0x5d,0xcf,0x0b, -0xa9,0x5c,0xaa,0x90,0x2a,0xa5,0xe9,0xd2,0x4c,0x69,0x99,0xf4,0x17,0xfb,0xd8,0x0e, -0x1d,0x07,0xa9,0x4e,0xaa,0x97,0x52,0x3b,0xf5,0xb3,0xfe,0x44,0x5f,0x57,0x4a,0xed, -0xd6,0x6d,0x4a,0x9b,0x24,0x5b,0x90,0x5a,0x2e,0xd5,0x49,0xf5,0xd2,0x42,0xa9,0xc9, -0x96,0xa6,0xee,0x49,0xa7,0xae,0x95,0xd6,0x4a,0xf5,0x7b,0xf5,0xb5,0xa5,0x6b,0xa4, -0x46,0x69,0xa1,0xd4,0x24,0x35,0x4b,0xeb,0x6d,0xa1,0xd8,0xbe,0x74,0xea,0x8e,0x7d, -0x49,0xfe,0x3d,0xb7,0xf8,0xf7,0xc1,0x82,0x54,0xcd,0xaf,0x12,0xbf,0xda,0xbc,0x6a, -0xf3,0xa7,0xcd,0xa8,0x4d,0xa7,0xba,0xea,0xbf,0x54,0x5d,0x13,0xf3,0x24,0x95,0x98, -0x11,0xd9,0x83,0x78,0xd4,0x47,0x27,0xff,0xc5,0x6b,0xf5,0xa8,0x6d,0x5d,0xf1,0x89, -0xd2,0xc9,0x79,0x1e,0x75,0x05,0xcc,0x6d,0xf3,0xa8,0x6b,0xc8,0xcb,0xd7,0x93,0x97, -0x9f,0x25,0xdd,0x84,0x47,0xbd,0x38,0x78,0xd4,0xf7,0x27,0xa7,0xb1,0x39,0x8f,0xfa, -0x11,0x3c,0xea,0xe5,0xd2,0x0a,0x69,0x65,0xf0,0xa8,0x9f,0x0c,0x1e,0xf5,0x6a,0x32, -0xf6,0x6f,0x90,0xb1,0x37,0x8f,0x5a,0xdf,0x5e,0xea,0xbf,0xc8,0xcc,0xaf,0x23,0x33, -0xbf,0x91,0xcc,0xfc,0x56,0x3c,0xea,0x96,0x82,0x84,0xb1,0x6d,0x79,0xf9,0xef,0x93, -0x93,0xff,0x61,0xf0,0xa8,0x6f,0x0e,0x1e,0xb5,0x67,0xe3,0xef,0x25,0x0f,0x6f,0x1e, -0xf5,0xec,0x8c,0xce,0xe7,0xc9,0xc3,0x5b,0x16,0x7e,0x1e,0x1e,0xf5,0xed,0x78,0xd4, -0xcf,0xe4,0x79,0xd4,0xcf,0xe1,0x51,0xaf,0x09,0x1e,0xb5,0x65,0xe5,0x77,0x49,0x85, -0x45,0xfa,0x3f,0x00,0x1e,0x75,0x69,0x9e,0x47,0xdd,0x0d,0x76,0xf6,0x31,0x64,0xe7, -0xa7,0xc2,0xc8,0x36,0x8f,0xfa,0xf2,0xc0,0xc9,0x7e,0x1d,0x26,0xb6,0x7b,0xd4,0x7f, -0x22,0x4b,0xbf,0x05,0x8f,0x7a,0x2b,0x99,0x7a,0xe3,0x60,0x77,0x09,0xdc,0xeb,0x0a, -0x58,0xd7,0xf7,0x07,0xc6,0xf5,0xfb,0xf0,0xac,0x2d,0x43,0xbf,0x27,0x78,0xd4,0xdd, -0x03,0xbf,0xda,0xb9,0xd5,0xce,0xab,0x36,0x4e,0xf5,0x68,0x72,0xf4,0x53,0xf0,0xa8, -0x2d,0x4b,0xff,0x98,0xf4,0x02,0xfc,0xe9,0x77,0xe1,0x4f,0x9f,0x96,0xe7,0x51,0xd7, -0xc0,0x95,0x76,0xa6,0x74,0x1d,0xfc,0xe8,0x0b,0xe1,0x46,0x3b,0x23,0x7a,0x0e,0x39, -0xfa,0x85,0xe4,0xe7,0x3d,0x3b,0xef,0x0c,0xe8,0x66,0x78,0xcf,0x2f,0x06,0xce,0xb3, -0x7b,0xd4,0x6f,0x93,0x9d,0x7f,0x8f,0xec,0xfc,0x4e,0x69,0x2f,0x1e,0xf5,0x01,0x3c, -0x6a,0x67,0x38,0x97,0x92,0xa3,0xef,0x47,0x7e,0xbe,0x8a,0xdc,0xbc,0x33,0x9a,0x6b, -0xe0,0x32,0x8f,0x0f,0x3c,0x66,0xcf,0xd1,0xcf,0x80,0xbd,0xdc,0x10,0x3c,0x6a,0x67, -0x2e,0x2f,0x20,0x4f,0xff,0x10,0x39,0xfa,0x95,0x81,0xab,0xdc,0x0c,0x43,0xd9,0xf9, -0xc9,0x2f,0x07,0x8f,0x7a,0x33,0x9c,0xe4,0x6d,0xc1,0xa3,0x76,0x3e,0x72,0x27,0xb2, -0xf5,0xe5,0x64,0xea,0x2b,0xc9,0xd2,0x3b,0xff,0x78,0x74,0xf0,0xa8,0xc7,0xc2,0x37, -0x76,0xb6,0xb1,0x33,0x8d,0xcf,0x27,0x5f,0x6f,0x0c,0xe3,0xab,0x83,0x47,0x3d,0x9f, -0x5c,0xfd,0x9d,0x21,0x4f,0xbf,0x98,0x0c,0xfd,0x2f,0xc9,0xce,0x3b,0x87,0xf8,0x39, -0xd8,0xc3,0x2f,0xe7,0x79,0xd4,0x3b,0xa4,0x4f,0xf3,0x3c,0x6a,0x67,0x0d,0x77,0x26, -0x67,0xdf,0x2f,0xe4,0xeb,0x6b,0x60,0x08,0x8f,0x87,0x1d,0x3c,0x89,0xac,0xfd,0x85, -0x30,0x82,0x67,0xe0,0x51,0xcf,0x0a,0x8c,0xe0,0xdb,0xc8,0xd9,0x3f,0x44,0xbe,0xde, -0x19,0xc0,0xbf,0x81,0xeb,0xfb,0x12,0x3c,0xdf,0x3f,0xe0,0x51,0x6f,0x86,0xdf,0xfb, -0x11,0x39,0xfb,0x34,0x39,0xfb,0x72,0xf2,0xf5,0x23,0xe1,0xf0,0x8e,0x85,0xbf,0xeb, -0xec,0xdd,0x5a,0xf2,0xf5,0x17,0x05,0x9e,0xee,0x1c,0x32,0xf6,0x2b,0x42,0xb6,0xde, -0x99,0xb9,0xaf,0x04,0x4e,0xee,0x46,0xd8,0xb8,0xbb,0xba,0xb4,0x31,0x71,0xab,0xe0, -0xe0,0x8e,0x21,0x67,0x6f,0xec,0xdb,0x29,0x21,0x6b,0x3f,0x3f,0xe4,0xeb,0x9d,0x6f, -0xbb,0x32,0x30,0x6d,0x3d,0x53,0xbf,0x16,0x7e,0xad,0x65,0xea,0x7b,0xc0,0xac,0xad, -0x3e,0x88,0x47,0x3d,0x8a,0xac,0xfd,0xe5,0x81,0x4d,0x7b,0x13,0x1e,0xf5,0x5c,0x72, -0xf7,0x3f,0x83,0x43,0xbb,0x06,0x8f,0xfa,0x65,0xe9,0xcd,0x6e,0x09,0x83,0xd6,0xf9, -0xb3,0xce,0x9d,0x1d,0x0e,0x6b,0x76,0x34,0x2c,0xd9,0xc9,0x64,0xf3,0xeb,0xc9,0xe2, -0x37,0x04,0x26,0xec,0x4d,0x70,0x60,0x9d,0x01,0xeb,0x8c,0xd7,0x35,0xc1,0xa3,0x1e, -0x4f,0xe6,0xde,0xf3,0xf6,0xe7,0x93,0xb1,0x5f,0x40,0xb6,0xfe,0xf1,0x90,0xa9,0x5f, -0x43,0x8e,0xbe,0x9c,0xfc,0xfc,0x3f,0xc3,0x5d,0xbd,0x9e,0xfc,0xfc,0x5d,0xe4,0xe7, -0x0f,0xe0,0x51,0x5b,0x66,0xbe,0x91,0xcc,0xfc,0x22,0x32,0xf2,0x9d,0xc9,0xc8,0x1f, -0x46,0x46,0xde,0xb8,0xa9,0x43,0xc8,0xc9,0x9f,0x43,0x4e,0x7e,0x26,0x9c,0xd4,0x95, -0x81,0x8f,0xfa,0x4c,0x60,0xa2,0xbe,0x08,0x07,0xf5,0xcd,0x3c,0x8f,0xda,0xd8,0xa7, -0x7b,0xa4,0x52,0xf8,0xa7,0x5d,0xe1,0x9e,0x5e,0x43,0x7e,0xfe,0x47,0x81,0x6b,0xfa, -0x4b,0x32,0xf3,0x3d,0x61,0x98,0x9e,0x04,0xbb,0x74,0x40,0x60,0x96,0x0e,0x83,0x53, -0x6a,0x1e,0xf5,0x38,0x32,0xf5,0x0d,0x64,0xe9,0x7f,0x0b,0x8b,0xb4,0xe5,0xc8,0x24, -0x4b,0x7f,0x44,0x60,0x90,0x9e,0x1b,0x3c,0xea,0x0b,0xf2,0x3c,0xea,0x8b,0x61,0x8a, -0x5e,0x11,0x58,0xa2,0xf3,0xe0,0x87,0x2e,0x93,0x9a,0x8f,0x4a,0x18,0xa1,0x55,0xc1, -0xa3,0x1e,0x1e,0x3c,0x6a,0xe7,0x7f,0x7e,0x5d,0x9a,0x0e,0xe7,0xf3,0x7a,0x38,0x9f, -0xf7,0xc2,0xf3,0x7c,0x30,0x70,0x3c,0x97,0x05,0x56,0xe7,0xff,0xc1,0xe7,0xfc,0x30, -0x78,0xd4,0xdb,0x61,0x71,0x0e,0x80,0xbd,0x39,0x18,0xe6,0xa6,0xf1,0x36,0x6f,0x21, -0xbf,0xff,0x02,0xfc,0xcc,0x0d,0x81,0x9f,0xb9,0x25,0x30,0x33,0x9d,0x95,0xb9,0x3b, -0x70,0x31,0x2d,0x9f,0xbf,0x07,0xce,0x65,0x77,0xf2,0xf9,0xe3,0xc8,0xe5,0x7f,0x33, -0xcf,0xa3,0xfe,0x21,0xf9,0xfb,0xa7,0x83,0x47,0xfd,0x2c,0xd9,0x7b,0xcb,0xdd,0xaf, -0x23,0x77,0x6f,0x99,0xfb,0x8d,0x64,0xee,0x3f,0x22,0x73,0x6f,0x79,0xfb,0xcf,0xa4, -0x52,0x98,0x95,0xee,0x51,0xf7,0xfd,0x0a,0x1e,0x75,0xb5,0x74,0x06,0x1e,0xf5,0x05, -0x78,0xd4,0x17,0x77,0xe0,0x51,0xcf,0x08,0x1e,0xf5,0x0f,0xa4,0xeb,0xa4,0x59,0x78, -0xd4,0xf3,0xa4,0x5b,0xa5,0xdb,0xc9,0xf8,0xaf,0x86,0x7d,0x99,0xc6,0xa3,0x2e,0xc2, -0xa3,0x2e,0x21,0xeb,0x7f,0x58,0xc8,0xfa,0x5b,0xc6,0xbf,0xf7,0x17,0x78,0xd4,0x9e, -0xf1,0xb7,0x2c,0xff,0x08,0xb2,0xfc,0x63,0xc9,0xf2,0x4f,0x90,0x6a,0xa5,0xa9,0x64, -0xf7,0x6f,0x95,0x0e,0x39,0xa1,0xcd,0xa3,0x1e,0xdc,0x5b,0xe7,0x7e,0xd2,0x1d,0x78, -0xd4,0x27,0xf5,0xd1,0x79,0x87,0x34,0x57,0xfa,0xb9,0xf4,0x57,0xe9,0xe1,0x93,0x12, -0x8f,0xba,0xa5,0xaf,0xd4,0x4f,0x8f,0x25,0x9d,0x50,0xde,0x29,0xb5,0x48,0x03,0x75, -0x62,0x79,0x01,0xf9,0xff,0xf7,0xc9,0xff,0x6f,0x27,0xff,0xdf,0x72,0x4a,0x5b,0xfe, -0xff,0x54,0x32,0xff,0x33,0xf1,0xa8,0x7f,0x4a,0xd6,0xff,0x51,0x32,0xfe,0xab,0xc8, -0xf6,0xff,0x9e,0x4c,0xff,0x3b,0xb0,0x2c,0xcf,0x1e,0xa8,0xc7,0xbc,0xb4,0x53,0x7a, -0x5d,0x27,0xaf,0xfb,0xf0,0xa8,0x33,0x64,0xfb,0x7b,0x91,0xed,0x3f,0x15,0x96,0x65, -0x25,0x19,0xff,0xa1,0x64,0xfa,0xc7,0x90,0xe9,0xbf,0x9b,0x4c,0xff,0x62,0x3c,0x6a, -0xe7,0x54,0x5a,0x7e,0xbf,0x82,0xdc,0xbe,0x65,0xf6,0xdf,0x93,0x8e,0x19,0xa4,0xe7, -0xbb,0x54,0x2b,0x4d,0x95,0xae,0x93,0x66,0x49,0x4d,0xd2,0x6a,0xe9,0x25,0x69,0x9d, -0x74,0x55,0x95,0x8e,0x9d,0xf4,0xb6,0xb4,0x51,0xfa,0x0c,0x8f,0xda,0xb2,0xfe,0x2d, -0xd2,0xfe,0xc5,0x49,0xbe,0xff,0xbf,0x7f,0x91,0xe4,0xf8,0xbb,0x91,0xdf,0xb7,0xcc, -0xfe,0x15,0x78,0xd4,0xce,0x9f,0xbc,0x07,0xd6,0xe4,0x47,0xb0,0x25,0x77,0x4b,0xc7, -0x87,0x5c,0xfe,0x6a,0xe9,0x77,0x81,0x03,0xe9,0xfc,0x47,0xe7,0x3e,0xf6,0x09,0x8c, -0xc7,0x01,0x78,0xd4,0x43,0xa5,0xd3,0xc8,0xda,0x5f,0x0d,0xd3,0x71,0x0d,0x2c,0xc7, -0xed,0x81,0xd9,0x78,0x22,0x7c,0xc6,0x33,0xe1,0x32,0x9e,0x43,0xce,0x7e,0x76,0xe0, -0x2f,0x3e,0x0e,0x73,0xf1,0x29,0x58,0x8b,0x6b,0x61,0x2b,0x3a,0x57,0xd1,0x72,0xf6, -0xfd,0xe0,0x25,0x56,0xc2,0x49,0xac,0x86,0x8f,0xe8,0x6c,0xc4,0xaf,0x07,0x8f,0x7a, -0x72,0x60,0x1c,0x2e,0x0f,0x1e,0xf5,0x63,0x64,0xec,0x9f,0x24,0x63,0xbf,0x3a,0xcf, -0xa3,0xfe,0x00,0x16,0xa1,0xf3,0x06,0x8b,0xc8,0xd0,0x1f,0x4e,0x76,0x7e,0x20,0xd9, -0xf8,0x6f,0x07,0x56,0xa0,0x33,0x02,0x9d,0x09,0x68,0x2c,0xc0,0xfb,0xc8,0xc8,0x5b, -0x3e,0xfe,0x3b,0xe4,0xe2,0x8b,0x42,0x1e,0xbe,0x27,0x59,0xf8,0xfe,0x64,0xe1,0x3d, -0x07,0x7f,0x3a,0xd9,0xf7,0x5a,0xe9,0x59,0x72,0xef,0x63,0xc8,0xbd,0x5b,0xe6,0x7d, -0x3f,0x1e,0xb5,0x71,0xfc,0x8e,0x84,0xe3,0x57,0x7b,0x10,0x8f,0xfa,0x02,0x3c,0xea, -0x0b,0xa5,0x8b,0xa4,0x4b,0xf0,0xa8,0xa7,0xe3,0x51,0x37,0x90,0x97,0x37,0x8f,0xba, -0xa0,0x45,0x3f,0x73,0xa9,0x34,0xcf,0xa3,0xee,0x06,0x03,0xd0,0x3c,0xea,0x5e,0xe4, -0xea,0x6b,0x02,0xff,0x6f,0x15,0x99,0xf9,0x75,0x64,0xe5,0x2d,0x27,0xbf,0x59,0xfa, -0x93,0xf4,0x17,0xe9,0x6f,0xd2,0x01,0xfb,0x9a,0x64,0xe6,0x87,0x93,0x95,0x9f,0x40, -0x46,0x7e,0x6f,0x9e,0x47,0x5d,0x25,0x8d,0x91,0x6e,0x93,0x6e,0x97,0x16,0xe1,0x51, -0xef,0xc7,0xa3,0x5e,0x14,0x98,0x7e,0xfb,0xa4,0xde,0x64,0xe4,0xbf,0x0f,0xab,0xef, -0x1e,0xb2,0xf1,0x96,0x87,0xbf,0x07,0x0e,0xdf,0x06,0xf2,0xef,0x7d,0xc9,0xbd,0x9b, -0x47,0x3d,0x84,0xac,0xba,0xe5,0xd4,0x3d,0x8b,0x3e,0x38,0xe4,0xcf,0xcf,0x25,0x6b, -0xbe,0x9c,0xac,0xb9,0xe5,0xcc,0x8f,0x2e,0x49,0x3c,0xea,0xd3,0xf0,0xa8,0x2d,0x5b, -0xbe,0x8e,0x5c,0x79,0x27,0x58,0x76,0xdd,0xc8,0x96,0xf7,0x20,0x53,0xee,0xdc,0xba, -0xf1,0xd2,0x79,0xd2,0x62,0xb2,0xe4,0xc6,0xa7,0xfb,0xa4,0x34,0xc9,0x93,0x8f,0x21, -0x4f,0x7e,0x05,0x39,0xf2,0x45,0xd2,0xf2,0xb2,0x24,0x4b,0xfe,0x9f,0x64,0xc8,0x5b, -0x3a,0x27,0x19,0xf2,0xbe,0xb0,0xe8,0x66,0x91,0x0d,0x7f,0x04,0x8f,0xda,0xf2,0xe1, -0x7d,0xc9,0x7d,0x1b,0x13,0x6e,0xb2,0xf4,0x52,0xe0,0xbf,0xa5,0xc9,0x7c,0x1b,0xe7, -0x6d,0x82,0x34,0x0d,0xd6,0xdb,0x8d,0x30,0xdb,0x9e,0x80,0xd5,0x66,0x9c,0xb6,0x67, -0xf1,0xa8,0xb7,0xc0,0x67,0x2b,0x20,0x07,0xee,0x4c,0xb6,0x6a,0x72,0xe0,0x57,0x04, -0xce,0xda,0x8d,0xb0,0xd4,0x8c,0xa3,0xf6,0x62,0xe0,0xa6,0x99,0x47,0xbd,0x51,0xda, -0x75,0x48,0x92,0x01,0x1f,0x08,0x0b,0xad,0x2a,0x78,0xd4,0xc6,0x3e,0x1b,0x85,0x47, -0xed,0x79,0xf0,0x05,0x64,0xc0,0x97,0x06,0xa6,0xd9,0xbb,0x70,0xcc,0x8c,0x61,0x96, -0x86,0x5d,0xd6,0x99,0x2c,0x78,0x3f,0x32,0xe0,0x55,0x64,0xbf,0x47,0x06,0x26,0xd9, -0x58,0x38,0x64,0x13,0x82,0x47,0x7d,0x11,0xac,0x31,0xe3,0x8c,0x0d,0x85,0xe3,0x75, -0x7a,0xe0,0x77,0x99,0x47,0x3d,0x25,0x78,0xd4,0x17,0xc2,0xe6,0x7a,0x32,0x78,0xd4, -0xab,0x02,0x87,0xeb,0x79,0x32,0xdf,0xc6,0xdb,0x5a,0x01,0x5f,0xeb,0xa9,0xc0,0xd5, -0x6a,0x26,0x03,0x6e,0x1c,0xad,0x41,0x70,0xb3,0x46,0x06,0x8f,0xda,0x39,0x59,0xc6, -0xc7,0xba,0x3c,0xf0,0xb1,0xdc,0xa3,0x5e,0x1e,0x58,0x58,0xee,0x51,0x1b,0xfb,0xea, -0x28,0xd8,0x56,0xc7,0x07,0xa6,0x95,0xb1,0xac,0xc6,0xf4,0x48,0x98,0x55,0xce,0xab, -0x1a,0x8e,0x47,0xbd,0x23,0x64,0xc8,0x0f,0x25,0x37,0x3e,0x18,0xfe,0xd4,0xc8,0xe0, -0x51,0x3b,0x57,0xea,0x4c,0x98,0x52,0x0f,0xc2,0x90,0x7a,0x03,0x76,0x94,0x73,0xa3, -0x9c,0x0f,0x55,0x24,0x65,0xe1,0x43,0x0d,0x82,0x0b,0x35,0x1c,0x0e,0xd4,0x99,0xc1, -0xa3,0x9e,0x04,0xfb,0x69,0x0a,0x99,0x72,0xe7,0x3d,0x19,0xe7,0xe9,0x3f,0xc8,0x95, -0x67,0xe1,0x31,0x0d,0x81,0xc3,0x34,0x4e,0xba,0x9c,0x2c,0xb9,0x73,0x96,0x9c,0xaf, -0xe4,0x5c,0x25,0xe3,0x29,0x15,0x91,0x2f,0xaf,0x0c,0xfc,0xa4,0x51,0x81,0x99,0x74, -0x16,0x9c,0xa4,0x69,0xe4,0xcc,0x6f,0x87,0x87,0x74,0x47,0x07,0x1e,0xb5,0x71,0x8f, -0x76,0xc0,0x33,0xfa,0x14,0x8f,0xfa,0x35,0xe9,0x8f,0xd2,0x26,0x69,0xfb,0x71,0x49, -0xa6,0xdc,0xf2,0xe4,0x0d,0xe4,0xc9,0x07,0x56,0xeb,0xfb,0xae,0x4e,0x32,0xe3,0xc3, -0xc8,0x88,0x1b,0x07,0x68,0x06,0xf9,0xf0,0x5d,0x78,0xd4,0x15,0xf0,0x7e,0xc6,0xc1, -0xf9,0xb9,0x90,0x9c,0xb8,0x65,0xc4,0xef,0xfe,0x6e,0x1b,0xab,0x67,0x2e,0x5c,0x9e, -0xc5,0xe4,0xbd,0xa3,0x47,0x6d,0x1c,0x9e,0xdd,0x73,0x93,0x9c,0x77,0x76,0x41,0x3a, -0xd5,0x93,0xcc,0xf6,0xa5,0x64,0xb4,0xa7,0x93,0xcd,0xbe,0x85,0x4c,0xf6,0x22,0x18, -0x39,0xe6,0x51,0xdf,0x27,0x7d,0xbc,0x5e,0x8f,0x15,0xb8,0x38,0x97,0xc1,0xc3,0xb9, -0x16,0x1e,0xce,0x4d,0xf0,0x70,0x96,0xc2,0xc3,0xd9,0x22,0x9d,0x0e,0x13,0xc7,0x3c, -0xea,0x9d,0xd2,0xa4,0x0d,0xe9,0xd4,0x35,0x1b,0x92,0x9c,0xf7,0xa2,0x90,0xef,0x7e, -0x80,0x4c,0xb7,0xe7,0xb9,0x9f,0x27,0xc3,0x5d,0x4a,0x76,0xfb,0x18,0x32,0xdb,0xa3, -0x43,0x56,0xbb,0x8e,0x7c,0xf6,0x9b,0x78,0xd4,0x96,0xc5,0xee,0x4e,0x06,0xbb,0x96, -0xec,0xf5,0x3c,0x32,0xd7,0xcf,0x93,0xb9,0x36,0x4e,0x4d,0xcb,0xff,0x26,0x9c,0x9a, -0xe1,0x70,0x6a,0x1a,0xf0,0xa8,0xe7,0xe0,0x51,0xdf,0x0a,0xa7,0xc6,0x18,0x35,0x45, -0xb0,0x67,0x8e,0x87,0x39,0x63,0xbc,0x99,0x53,0xe0,0xca,0x8c,0x82,0x27,0x53,0xbb, -0x39,0xc9,0x5f,0x7f,0x04,0x2f,0xc6,0x3c,0x6a,0xcb,0x43,0xaf,0x83,0xbb,0x72,0x46, -0xe0,0xad,0x18,0x67,0x65,0xaa,0xf4,0xd8,0x87,0xfa,0xfb,0x5b,0x93,0x7c,0xf4,0x71, -0x21,0x17,0xdd,0x97,0x2c,0xf4,0xd0,0x2f,0xf0,0xa8,0x6b,0x43,0xce,0xb9,0x23,0x8f, -0xda,0xb3,0xcc,0xd3,0xc9,0x2f,0x5f,0x1d,0x72,0xcb,0xf3,0xc8,0x2a,0xdf,0x47,0x46, -0xd9,0xf2,0xc9,0xdb,0xa5,0x01,0xdb,0xd2,0xa9,0x7f,0x93,0x1e,0x95,0x56,0x4b,0xeb, -0xa4,0x8f,0xa5,0xd2,0xed,0xfa,0xb7,0x49,0x1a,0x88,0x47,0x7d,0x99,0x34,0x43,0x7a, -0x04,0x8f,0xba,0x60,0x87,0xee,0xa3,0x34,0x5e,0x9a,0x28,0x4d,0x93,0xde,0xf8,0x24, -0x9d,0xfa,0x50,0xda,0x2f,0x5d,0xb9,0x3b,0x9d,0xfa,0x1f,0xe9,0x03,0xa9,0xec,0x53, -0x7d,0x1f,0xd2,0x64,0x69,0xae,0xb4,0x4c,0x5a,0x21,0x5d,0xb3,0x47,0x3f,0x1b,0xe9, -0x9c,0xbd,0x69,0x9d,0xf3,0xe8,0xe7,0x21,0x5d,0x2b,0xfd,0x48,0x7a,0x48,0x7a,0x42, -0x7a,0x4e,0xfa,0x83,0xf4,0x93,0x7d,0xe9,0xd4,0xef,0xe0,0xb3,0xbc,0xfd,0x40,0x41, -0xaa,0xfa,0x57,0x49,0xa6,0xfa,0x5e,0xb2,0xd4,0x96,0x9f,0xb6,0xec,0xb4,0x79,0xd4, -0x37,0xa4,0x12,0xfe,0xca,0x81,0x96,0x6e,0x52,0x79,0x2a,0x7d,0x78,0xf7,0xc3,0x3b, -0x77,0x4f,0xeb,0x55,0xa6,0x2c,0xd3,0xa9,0xa0,0x53,0x41,0x61,0x41,0xa6,0x48,0x2f, -0xe9,0xee,0xe9,0x02,0x5d,0xa3,0x77,0x32,0x65,0x85,0x7a,0x53,0x58,0x94,0x69,0xff, -0x92,0xd5,0x2b,0xde,0x2f,0xb2,0x77,0xb2,0xf6,0x3a,0x53,0x96,0x2d,0xf6,0x97,0xe2, -0xd2,0x4c,0x49,0x59,0x49,0x41,0x49,0x26,0x5b,0x52,0x92,0x2d,0x2d,0x2b,0x2d,0xc9, -0xea,0x8d,0xfe,0x90,0x95,0x0a,0x4b,0xec,0x4f,0x7a,0xd1,0xe7,0x65,0x8a,0xc3,0xdf, -0xc9,0x16,0x97,0xd8,0x67,0x67,0x8b,0xda,0xbf,0x24,0x97,0xdc,0x7b,0xd9,0xe4,0xbd, -0x32,0xbd,0x64,0xfd,0x03,0xf6,0x91,0x6c,0xee,0x4f,0x79,0x9f,0x9e,0x7c,0x7e,0x36, -0x7e,0x2e,0x9f,0xd1,0xfa,0xd9,0x6d,0x97,0x2c,0xd2,0xb7,0xed,0x1f,0xc8,0x7d,0xdd, -0xdc,0xb5,0x99,0xd6,0x6b,0x5a,0x95,0x0d,0x77,0xa9,0xf5,0x2b,0x65,0xb9,0x25,0xbd, -0x2d,0x6b,0x7f,0xc7,0x8b,0xda,0xee,0x5f,0x36,0xd3,0xfa,0x5e,0x36,0x5e,0xdd,0xe1, -0x77,0x5d,0xd4,0xfe,0x2e,0xe6,0x8e,0x7a,0xe7,0xdc,0x51,0x8f,0x37,0x9e,0xbc,0xd8, -0x4d,0x66,0x32,0xd9,0x74,0xb8,0xcd,0xe4,0xe3,0x99,0xdc,0x4f,0x29,0xf7,0x5d,0xb4, -0xde,0x2c,0x77,0x35,0x1b,0x6e,0xb0,0x83,0xfb,0x90,0xf5,0x23,0x1e,0xaf,0x8b,0xf7, -0x2d,0x93,0xdc,0x25,0xde,0x76,0x4e,0x17,0x15,0x67,0x3a,0x77,0xef,0x51,0xde,0xad, -0x6b,0x97,0x82,0xb2,0x4c,0x81,0xae,0x38,0xec,0x90,0xaf,0x1d,0x9a,0x2e,0x2c,0x2a, -0xec,0x94,0xc9,0xa9,0x2c,0x9b,0xc9,0x7f,0x2c,0xe9,0x7e,0xb7,0xbe,0x9f,0xe9,0x54, -0x96,0xbc,0xf5,0x87,0x5c,0xee,0xae,0xdb,0x1d,0xcf,0xb4,0xfe,0x00,0x38,0x18,0x19, -0xbb,0x74,0xce,0xf0,0xc1,0xdc,0xdb,0xf0,0x80,0xfc,0xfc,0x4b,0xf2,0x4a,0x8f,0xbf, -0xe4,0x30,0x64,0x5a,0x0f,0x62,0xee,0x4f,0xd9,0xdc,0xbd,0xe8,0xe0,0x60,0xe4,0x1d, -0xe3,0xa2,0x0e,0x8f,0x4f,0xdb,0xb1,0xe5,0xa2,0x87,0x72,0xa7,0xe2,0xdc,0xa5,0xa4, -0x24,0xa3,0x07,0x75,0xc6,0x6e,0xb7,0x20,0xf7,0xa2,0x27,0x9a,0x9e,0x29,0xba,0x32, -0xdd,0xfe,0x39,0x97,0xfb,0x7b,0x65,0xba,0x2e,0xd3,0x76,0x6c,0x73,0x77,0x3a,0xf7, -0xe0,0xcb,0x16,0xb6,0xfb,0xee,0x73,0xb7,0xd1,0x76,0xc9,0xe4,0x5e,0xeb,0xd1,0x9a, -0x7b,0x9d,0x7b,0xdf,0xaf,0xcc,0xdd,0x87,0x6c,0x69,0xa9,0x5d,0x53,0x6c,0xcf,0xb9, -0x62,0xbe,0xfd,0x6c,0xfb,0x1f,0x6e,0xfb,0x47,0x5e,0xbb,0x07,0xe1,0xe7,0x9e,0x85, -0x45,0xf1,0x81,0x93,0x7f,0xb0,0xda,0x1d,0x9f,0xcc,0xe7,0x3e,0x14,0x8f,0x52,0xdb, -0xcd,0x1d,0xec,0xf1,0x17,0x9e,0xaa,0x7c,0x91,0x6c,0xb6,0xfd,0xdd,0xf6,0x27,0x6d, -0xde,0x13,0xa5,0xdd,0xfd,0xcd,0xe4,0x1d,0xb9,0x6c,0x51,0xf2,0x1c,0x49,0xfb,0x0f, -0xdf,0x0f,0x7f,0xfe,0xa5,0xed,0xd1,0x53,0xd8,0x76,0xaf,0x92,0x4b,0xeb,0xaf,0xa2, -0xe2,0xe4,0x77,0x97,0xfd,0xea,0xfa,0xb2,0x4b,0x26,0xb9,0x14,0x66,0xc2,0xaf,0xce, -0x83,0x5e,0x0a,0xb9,0x0f,0x69,0xdd,0xcb,0xa2,0x54,0xdb,0x4e,0xdb,0x39,0xcc,0xcd, -0xbe,0xea,0x2e,0x59,0xdf,0x1d,0xeb,0x3b,0x62,0x7d,0x27,0x6c,0xfe,0x0e,0xd8,0xfc, -0xdd,0xaf,0xbe,0xd3,0xd5,0x77,0xb9,0xfa,0x0e,0x57,0xdf,0xd5,0xea,0xbb,0x59,0x7d, -0x27,0xab,0xef,0x62,0xf5,0x1d,0xac,0xbe,0x7b,0xd5,0x77,0xae,0x6e,0x08,0x3b,0x54, -0xbd,0x63,0x31,0x92,0xdd,0xa8,0xb5,0x61,0xe7,0xa9,0xef,0x3a,0xf5,0x79,0x95,0xef, -0x26,0x5d,0xd5,0xc1,0x2e,0xd2,0xb5,0x61,0xf7,0xa8,0xef,0x1a,0xfd,0x2c,0xaf,0x2f, -0x11,0x77,0x82,0x7e,0xd1,0x2e,0x50,0xdf,0xf9,0x19,0x77,0x7d,0xce,0x09,0x3b,0x3b, -0x57,0x85,0x1d,0x9d,0xb1,0x03,0xe1,0x3b,0x38,0xf7,0xe6,0xcd,0x94,0x46,0x86,0xf9, -0x51,0xfe,0x6e,0xcc,0xd8,0x6f,0xf0,0xdd,0x97,0xbe,0xeb,0x72,0x55,0x07,0xbb,0x28, -0xd7,0x86,0xdd,0x93,0x29,0x66,0x3f,0xa3,0xbf,0x64,0x57,0xa4,0xef,0x84,0xbc,0x91, -0xdd,0x8f,0xbe,0xd3,0x71,0x55,0xd8,0xdd,0xe8,0x3b,0x1b,0xe3,0xae,0x46,0xdf,0xcd, -0x98,0x62,0xf7,0xa2,0xef,0x58,0x1c,0xdd,0xc1,0x4e,0xc5,0x09,0x61,0x77,0xa2,0xef, -0x4c,0xf4,0x99,0x4e,0xdc,0x79,0xd8,0xd1,0xae,0x43,0xdf,0x71,0xe8,0xbb,0x0d,0x7d, -0xa7,0xa1,0xef,0x30,0xf4,0x5d,0x84,0xbe,0x83,0xf0,0x45,0xe6,0x34,0xbe,0x53,0xf0, -0xcb,0x76,0x09,0x7e,0x2f,0x6f,0x57,0xe0,0xc1,0x76,0x04,0x0e,0x60,0xf6,0x32,0x89, -0xd9,0x8b,0xef,0xee,0xf3,0x9d,0x7d,0xbe,0xab,0xcf,0x77,0xf4,0xcd,0x66,0xf6,0xe2, -0x3b,0xf7,0xee,0x66,0x06,0xe3,0x3b,0xeb,0x7c,0xb7,0xdc,0x20,0x66,0x21,0xbe,0x3b, -0xce,0x77,0xc5,0x3d,0xc6,0xac,0xc3,0x77,0xbc,0xf9,0x6e,0x37,0x67,0x01,0xf9,0x8e, -0xb6,0x5e,0x61,0xe7,0x9a,0xcd,0x2c,0x7a,0x84,0x59,0x84,0xcf,0x20,0xe2,0xee,0xb2, -0x26,0x66,0x0d,0x31,0x0f,0x3f,0x36,0x6f,0x77,0xd8,0x6c,0x66,0x0a,0xcb,0x98,0x1f, -0xd8,0x9c,0xa0,0x0f,0xf3,0x81,0xb8,0x53,0x6b,0x4b,0xd8,0x9d,0xb5,0x2b,0x6f,0x16, -0xd0,0xcc,0x1c,0x60,0x1f,0xb3,0x80,0xf5,0xf8,0xe3,0xbe,0x5b,0xc9,0x77,0x25,0xf9, -0x8e,0xa3,0x93,0xf1,0x7b,0x7d,0x27,0xd1,0x0c,0x3c,0x55,0xdf,0xe9,0xe3,0xbb,0x7b, -0x7c,0x57,0x8f,0xef,0xce,0xf1,0x9d,0x39,0xbe,0x2b,0xa7,0x77,0xde,0x4e,0x1c,0xdf, -0x85,0xf3,0x18,0x5e,0xa9,0xef,0xbc,0xf1,0xdd,0x36,0xbe,0xcb,0xe6,0x2c,0x7c,0xd2, -0x98,0xe3,0x6d,0x0e,0x3b,0x66,0xfa,0xe3,0x7d,0xfa,0x4e,0x18,0xf7,0x3d,0xaf,0x0b, -0x99,0xdc,0xa6,0xb0,0xbb,0xc5,0x77,0xb6,0xf8,0xae,0x16,0xdf,0xd1,0xf2,0x01,0x3b, -0x57,0xdc,0xef,0x7c,0x2f,0xec,0x38,0x31,0x2f,0xd3,0x77,0x92,0xe4,0xef,0x0e,0xf1, -0x5d,0x1b,0xbe,0x5b,0xc3,0x77,0x65,0x1c,0x6c,0xb7,0x43,0xfe,0x0e,0x07,0xdf,0xd1, -0xe0,0x3b,0x16,0x7c,0x27,0x82,0xef,0x34,0x48,0x85,0x4c,0xe5,0x99,0x21,0x4b,0xd9, -0x84,0x47,0xe5,0x3b,0x03,0xfa,0xe2,0x47,0xf9,0x2e,0x00,0x67,0xff,0x7b,0x66,0x72, -0x25,0x4c,0xff,0xdf,0xe6,0x79,0x52,0xa7,0xe4,0x31,0xf7,0x37,0xe2,0x2d,0xa5,0xf0, -0x95,0x9c,0x71,0x3f,0xfa,0x20,0x2c,0x7b,0x67,0xd8,0x1f,0x8c,0x5d,0x3f,0x18,0x0f, -0xc9,0x59,0xef,0x4d,0x78,0x44,0xcb,0x03,0x7b,0xbd,0x39,0xb0,0xd5,0x2b,0xf1,0x80, -0x1e,0xc6,0xdf,0x39,0x16,0x1f,0xe7,0x14,0xbc,0x9b,0x8e,0x58,0xe1,0xa3,0x03,0x13, -0xdc,0xfd,0x9a,0x57,0x02,0xe3,0x7b,0x53,0x60,0x78,0x3b,0xbb,0x3b,0x66,0x07,0x23, -0xa3,0x7b,0x22,0x9e,0xcc,0x7c,0xbc,0x98,0x8a,0xc0,0xc2,0x1e,0x93,0xc7,0xb8,0x36, -0xaf,0xe5,0xcb,0x18,0xd5,0xce,0xa6,0xee,0x88,0x3d,0x6d,0x1e,0xcb,0x4e,0x18,0xd1, -0xce,0x42,0x76,0x06,0xb2,0x33,0x8a,0xf3,0xd9,0xc4,0xf9,0x0c,0x62,0x67,0xfe,0x4e, -0xfb,0x7b,0x99,0xbe,0x7f,0x2f,0xdb,0xf6,0xab,0xb2,0x6b,0xf1,0x12,0x9c,0x39,0xeb, -0xac,0x56,0x67,0xb4,0x3a,0x43,0xf4,0xff,0x01, -}; -} // namespace ada::idna::table_blob -#endif -/* end file src/table_blob.inc */ - -#include -#include -#include -#include -#include - -namespace ada::idna { - -// table_blob.inc is packed little-endian (see scripts/pack_tables.py). After -// inflate the multi-byte sections must be converted to host endianness so -// big-endian platforms (s390x) can load uint16_t/uint32_t/uint64_t natively. -// CRC is checked on the raw little-endian payload before any conversion. -namespace detail { - -template -inline void bswap_inplace(T* data, size_t count) noexcept { - if constexpr (std::endian::native == std::endian::little) { - (void)data; - (void)count; - return; - } - for (size_t i = 0; i < count; ++i) { - if constexpr (sizeof(T) == 2) { -#if defined(__GNUC__) || defined(__clang__) - data[i] = - static_cast(__builtin_bswap16(static_cast(data[i]))); -#else - const auto v = static_cast(data[i]); - data[i] = static_cast(static_cast((v >> 8) | (v << 8))); -#endif - } else if constexpr (sizeof(T) == 4) { -#if defined(__GNUC__) || defined(__clang__) - data[i] = - static_cast(__builtin_bswap32(static_cast(data[i]))); -#else - auto v = static_cast(data[i]); - v = ((v & 0x000000FFu) << 24) | ((v & 0x0000FF00u) << 8) | - ((v & 0x00FF0000u) >> 8) | ((v & 0xFF000000u) >> 24); - data[i] = static_cast(v); -#endif - } else if constexpr (sizeof(T) == 8) { -#if defined(__GNUC__) || defined(__clang__) - data[i] = - static_cast(__builtin_bswap64(static_cast(data[i]))); -#else - auto v = static_cast(data[i]); - v = ((v & 0x00000000000000FFull) << 56) | - ((v & 0x000000000000FF00ull) << 40) | - ((v & 0x0000000000FF0000ull) << 24) | - ((v & 0x00000000FF000000ull) << 8) | - ((v & 0x000000FF00000000ull) >> 8) | - ((v & 0x0000FF0000000000ull) >> 24) | - ((v & 0x00FF000000000000ull) >> 40) | - ((v & 0xFF00000000000000ull) >> 56); - data[i] = static_cast(v); -#endif - } else { - static_assert(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); - } - } -} - -// Convert every multi-byte section of the inflated little-endian blob to host -// order. Single-byte sections are left unchanged. Safe no-op on little-endian. -inline void convert_table_blob_to_host_endian(uint8_t* buffer) noexcept { - if constexpr (std::endian::native == std::endian::little) { - (void)buffer; - return; - } - auto u16 = [&](size_t off, size_t count) { - bswap_inplace(reinterpret_cast(buffer + off), count); - }; - auto u32 = [&](size_t off, size_t count) { - bswap_inplace(reinterpret_cast(buffer + off), count); - }; - auto u64 = [&](size_t off, size_t count) { - bswap_inplace(reinterpret_cast(buffer + off), count); - }; - - u16(table_blob::off_idna_stage1, table_blob::count_idna_stage1); - u16(table_blob::off_idna_stage2, table_blob::count_idna_stage2); - u64(table_blob::off_idna_bool_blocks, table_blob::count_idna_bool_blocks); - u16(table_blob::off_decomposition_block, - table_blob::count_decomposition_block); - u32(table_blob::off_decomposition_data, table_blob::count_decomposition_data); - u16(table_blob::off_composition_block, table_blob::count_composition_block); - u32(table_blob::off_composition_data, table_blob::count_composition_data); - u32(table_blob::off_id_continue_flat, table_blob::count_id_continue_flat); - u32(table_blob::off_id_start_flat, table_blob::count_id_start_flat); - u32(table_blob::off_dir_start, table_blob::count_dir_start); - u32(table_blob::off_dir_final, table_blob::count_dir_final); - u32(table_blob::off_combining_flat, table_blob::count_combining_flat); -} - -} // namespace detail - -// --- Blob layout invariants -------------------------------------------------- -static_assert(table_blob::count_decomposition_index == 4352); -static_assert(table_blob::count_ccc_index == 4352); -static_assert(table_blob::count_composition_index == 4352); -static_assert(table_blob::count_decomposition_block == - table_blob::decomposition_block_rows * - table_blob::decomposition_block_cols); -static_assert(table_blob::count_ccc_block == - table_blob::ccc_block_rows * table_blob::ccc_block_cols); -static_assert(table_blob::count_composition_block == - table_blob::composition_block_rows * - table_blob::composition_block_cols); -static_assert(table_blob::count_dir_start == table_blob::dir_table_count && - table_blob::count_dir_final == table_blob::dir_table_count && - table_blob::count_dir_value == table_blob::dir_table_count); -static_assert(table_blob::count_id_continue_flat == - table_blob::id_continue_count * 2); -static_assert(table_blob::count_id_start_flat == - table_blob::id_start_count * 2); -static_assert(table_blob::count_combining_flat == - table_blob::combining_range_count * 2); -static_assert(table_blob::off_idna_stage1 % alignof(uint16_t) == 0); -static_assert(table_blob::off_idna_stage2 % alignof(uint16_t) == 0); -static_assert(table_blob::off_idna_bool_blocks % alignof(uint64_t) == 0); -static_assert(table_blob::off_decomposition_block % alignof(uint16_t) == 0); -static_assert(table_blob::off_decomposition_data % alignof(char32_t) == 0); -static_assert(table_blob::off_composition_block % alignof(uint16_t) == 0); -static_assert(table_blob::off_composition_data % alignof(char32_t) == 0); -static_assert(table_blob::off_id_continue_flat % alignof(uint32_t) == 0); -static_assert(table_blob::off_id_start_flat % alignof(uint32_t) == 0); -static_assert(table_blob::off_dir_start % alignof(uint32_t) == 0); -static_assert(table_blob::off_dir_final % alignof(uint32_t) == 0); -static_assert(table_blob::off_combining_flat % alignof(uint32_t) == 0); -static_assert(table_blob::compressed_size == sizeof(table_blob::compressed)); - -// Every section must lie entirely inside the uncompressed buffer. -#define ADA_IDNA_SECTION_IN_BOUNDS(off, count, width) \ - static_assert((off) + (count) * (width) <= table_blob::uncompressed_size, \ - #off " overflows uncompressed buffer") -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_idna_stage1, - table_blob::count_idna_stage1, 2); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_idna_stage2, - table_blob::count_idna_stage2, 2); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_idna_bool_blocks, - table_blob::count_idna_bool_blocks, 8); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_idna_utf8_mappings, - table_blob::count_idna_utf8_mappings, 1); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_decomposition_index, - table_blob::count_decomposition_index, 1); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_decomposition_block, - table_blob::count_decomposition_block, 2); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_decomposition_data, - table_blob::count_decomposition_data, 4); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_ccc_index, - table_blob::count_ccc_index, 1); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_ccc_block, - table_blob::count_ccc_block, 1); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_composition_index, - table_blob::count_composition_index, 1); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_composition_block, - table_blob::count_composition_block, 2); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_composition_data, - table_blob::count_composition_data, 4); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_id_continue_flat, - table_blob::count_id_continue_flat, 4); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_id_start_flat, - table_blob::count_id_start_flat, 4); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_dir_start, - table_blob::count_dir_start, 4); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_dir_final, - table_blob::count_dir_final, 4); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_dir_value, - table_blob::count_dir_value, 1); -ADA_IDNA_SECTION_IN_BOUNDS(table_blob::off_combining_flat, - table_blob::count_combining_flat, 4); -#undef ADA_IDNA_SECTION_IN_BOUNDS - -// --- Mapping ----------------------------------------------------------------- -inline const uint16_t* idna_stage1 = nullptr; -inline const uint16_t* idna_stage2 = nullptr; -inline const uint64_t* idna_bool_blocks = nullptr; -inline const uint8_t* idna_utf8_mappings = nullptr; - -// --- Normalization (O(1) multi-stage) ---------------------------------------- -inline const uint8_t* decomposition_index = nullptr; -inline const uint16_t* decomposition_block_flat = nullptr; -inline const char32_t* decomposition_data = nullptr; -inline const uint8_t* ccc_index = nullptr; -inline const uint8_t* ccc_block_flat = nullptr; -inline const uint8_t* composition_index = nullptr; -inline const uint16_t* composition_block_flat = nullptr; -inline const char32_t* composition_data = nullptr; - -// --- Identifier -------------------------------------------------------------- -// Pointer-to-array alias. CF17 vs CF22 disagree on spacing inside (*)[2]. -// clang-format off -using range_pair_ptr = const uint32_t(*)[2]; -// clang-format on -inline range_pair_ptr id_continue = nullptr; -inline range_pair_ptr id_start = nullptr; - -// --- Validity (const SoA) ---------------------------------------------------- -inline const uint32_t* dir_start = nullptr; -inline const uint32_t* dir_final = nullptr; -inline const uint8_t* dir_value = nullptr; -inline range_pair_ptr combining_ranges = nullptr; - -inline constexpr size_t id_continue_count = table_blob::id_continue_count; -inline constexpr size_t id_start_count = table_blob::id_start_count; -inline constexpr size_t dir_table_count = table_blob::dir_table_count; -inline constexpr size_t combining_range_count = - table_blob::combining_range_count; -inline constexpr size_t idna_utf8_mappings_size = - table_blob::count_idna_utf8_mappings; -inline constexpr size_t decomposition_data_size = - table_blob::count_decomposition_data; -inline constexpr size_t composition_data_size = - table_blob::count_composition_data; - -inline constexpr size_t kDecompBlockCols = - table_blob::decomposition_block_cols; // 257 -inline constexpr size_t kCccBlockCols = table_blob::ccc_block_cols; // 256 -inline constexpr size_t kCompBlockCols = - table_blob::composition_block_cols; // 257 -inline constexpr size_t kDecompBlockRows = table_blob::decomposition_block_rows; -inline constexpr size_t kCccBlockRows = table_blob::ccc_block_rows; -inline constexpr size_t kCompBlockRows = table_blob::composition_block_rows; - -// Init state: 0=uninit, 1=in progress, 2=ready, 3=failed. -inline constexpr uint8_t kTablesUninit = 0; -inline constexpr uint8_t kTablesInProgress = 1; -inline constexpr uint8_t kTablesReady = 2; -inline constexpr uint8_t kTablesFailed = 3; - -inline std::atomic tables_init_state{kTablesUninit}; -// Process-lifetime allocation published only by the winning init thread. -// Never freed: shared read-only data for the process. Do not dlclose a DSO -// that owns this buffer while other code may still call into ada::idna. -inline uint8_t* tables_buffer = nullptr; - -// Cap spin-wait so a stuck peer cannot hang the process forever. -inline constexpr uint64_t kTablesSpinLimit = 1'000'000'000ull; - -// ISO HDLC / zlib CRC-32 of the uncompressed table payload. -[[nodiscard]] inline uint32_t crc32_ieee(const uint8_t* data, - size_t len) noexcept { - uint32_t c = 0xFFFFFFFFu; - for (size_t i = 0; i < len; ++i) { - c ^= data[i]; - for (int k = 0; k < 8; ++k) { - const uint32_t mask = 0u - (c & 1u); - c = (c >> 1) ^ (0xEDB88320u & mask); - } - } - return ~c; -} - -[[nodiscard]] inline bool tables_are_ready() noexcept { - return tables_init_state.load(std::memory_order_acquire) == kTablesReady; -} - -// Returns true only when all table pointers are safe to use. -[[nodiscard]] inline bool ensure_tables() noexcept { - uint8_t state = tables_init_state.load(std::memory_order_acquire); - if (state == kTablesReady) { - return true; - } - if (state == kTablesFailed) { - return false; - } - - uint8_t expected = kTablesUninit; - if (tables_init_state.compare_exchange_strong(expected, kTablesInProgress, - std::memory_order_acq_rel, - std::memory_order_acquire)) { - uint8_t* buffer = new (std::nothrow) uint8_t[table_blob::uncompressed_size]; - if (buffer == nullptr) { - tables_init_state.store(kTablesFailed, std::memory_order_release); - return false; - } - - const size_t n = deflate::inflate_raw(table_blob::compressed, - table_blob::compressed_size, buffer, - table_blob::uncompressed_size); - if (n != table_blob::uncompressed_size || - crc32_ieee(buffer, n) != table_blob::uncompressed_crc32) { - delete[] buffer; - tables_init_state.store(kTablesFailed, std::memory_order_release); - return false; - } - - // LE payload verified; convert multi-byte fields for big-endian hosts. - detail::convert_table_blob_to_host_endian(buffer); - - auto at = [&](size_t off) noexcept -> const uint8_t* { - return buffer + off; - }; - - // Publish all non-atomic pointers before READY. Waiters synchronize via - // acquire on tables_init_state and then observe these writes. - idna_stage1 = - reinterpret_cast(at(table_blob::off_idna_stage1)); - idna_stage2 = - reinterpret_cast(at(table_blob::off_idna_stage2)); - idna_bool_blocks = - reinterpret_cast(at(table_blob::off_idna_bool_blocks)); - idna_utf8_mappings = at(table_blob::off_idna_utf8_mappings); - - decomposition_index = at(table_blob::off_decomposition_index); - decomposition_block_flat = reinterpret_cast( - at(table_blob::off_decomposition_block)); - decomposition_data = reinterpret_cast( - at(table_blob::off_decomposition_data)); - ccc_index = at(table_blob::off_ccc_index); - ccc_block_flat = at(table_blob::off_ccc_block); - composition_index = at(table_blob::off_composition_index); - composition_block_flat = reinterpret_cast( - at(table_blob::off_composition_block)); - composition_data = - reinterpret_cast(at(table_blob::off_composition_data)); - - id_continue = - reinterpret_cast(at(table_blob::off_id_continue_flat)); - id_start = - reinterpret_cast(at(table_blob::off_id_start_flat)); - - dir_start = - reinterpret_cast(at(table_blob::off_dir_start)); - dir_final = - reinterpret_cast(at(table_blob::off_dir_final)); - dir_value = at(table_blob::off_dir_value); - combining_ranges = - reinterpret_cast(at(table_blob::off_combining_flat)); - - tables_buffer = buffer; - tables_init_state.store(kTablesReady, std::memory_order_release); - return true; - } - - // Another thread owns init (or finished between our loads). - for (uint64_t spins = 0; spins < kTablesSpinLimit; ++spins) { - state = tables_init_state.load(std::memory_order_acquire); - if (state == kTablesReady) { - return true; - } - if (state == kTablesFailed) { - return false; - } -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || \ - defined(_M_IX86) -#if defined(__GNUC__) || defined(__clang__) - __builtin_ia32_pause(); -#endif -#elif defined(__aarch64__) || defined(_M_ARM64) -#if defined(__GNUC__) || defined(__clang__) - asm volatile("yield" ::: "memory"); -#endif -#endif - } - // Timed out waiting for a peer - treat as failure rather than hang. - return false; -} - -// O(1) multi-stage accessors. Block indices from the tables are uint8_t and -// theoretically can be out of range if the blob is corrupt; clamp to a valid -// empty/default row (index 0) instead of reading OOB. -inline const uint16_t* decomposition_block_row(uint8_t bi) noexcept { - if (bi >= kDecompBlockRows) { - bi = 0; - } - return decomposition_block_flat + static_cast(bi) * kDecompBlockCols; -} - -inline const uint8_t* ccc_block_row(uint8_t bi) noexcept { - if (bi >= kCccBlockRows) { - bi = 0; - } - return ccc_block_flat + static_cast(bi) * kCccBlockCols; -} - -inline const uint16_t* composition_block_row(uint8_t bi) noexcept { - if (bi >= kCompBlockRows) { - bi = 0; - } - return composition_block_flat + static_cast(bi) * kCompBlockCols; -} - -} // namespace ada::idna -/* end file src/table_store.hpp */ -/* begin file src/mapping_tables.cpp */ -// IDNA 17.0.0 -// Two-level compressed mapping table. -// All constants are derived from the table data; no hardcoded boundaries. -// Total binary size: 48659 bytes (47.5 KB) -// stage1: 6564 bytes (3282 uint16_t entries) -// stage2: 22912 bytes (179 mixed blocks x 64) -// bool_blocks: 1800 bytes (225 uint64_t words) -// utf8 maps: 17383 bytes - -// clang-format off -#ifndef ADA_IDNA_MAPPING_TABLE_H -#define ADA_IDNA_MAPPING_TABLE_H -#include - -namespace ada::idna { - -// Block size for two-level table (2^6 = 64 code points per block). -constexpr uint32_t IDNA_BLOCK_BITS = 6u; -constexpr uint32_t IDNA_BLOCK_SIZE = 64u; -constexpr uint32_t IDNA_BLOCK_MASK = 63u; - -// Sentinel values stored in stage2 / returned by lookup. -constexpr uint16_t IDNA_VALID = 0xFFFF; // code point is valid as-is -constexpr uint16_t IDNA_DISALLOWED = 0xFFFE; // code point is disallowed -constexpr uint16_t IDNA_IGNORED = 0x0000; // ignored (index into empty UTF-8 entry) - -// Bit 15 of a stage1 entry: set = boolean block, clear = mixed block. -constexpr uint16_t IDNA_BOOL_FLAG = 0x8000; - -// Two-level table covers code points [0, IDNA_LOW_RANGE_END). -// Derived from the highest non-disallowed code point below the high-ignored range, -// rounded up to the next 64-code-point block boundary. -constexpr uint32_t IDNA_LOW_RANGE_END = 0x00033480; - -// Variation selectors supplement: U+E0100..U+E01EF are all ignored. -// These are handled with a simple range check; everything else above -// IDNA_LOW_RANGE_END is disallowed. -constexpr uint32_t IDNA_HIGH_IGNORED_START = 0x000E0100; -constexpr uint32_t IDNA_HIGH_IGNORED_END = 0x000E01F0; // exclusive - -// idna_stage1[cp >> 6]: one entry per 64-code-point block. -// Bit 15 set -> lower 15 bits = index into idna_bool_blocks[]. -// Bit 15 clear -> value = base offset into idna_stage2[] for this block. - -} // namespace ada::idna -#endif // ADA_IDNA_MAPPING_TABLE_H -/* end file src/mapping_tables.cpp */ - -namespace ada::idna { - -// --- O(1) two-level table lookup --------------------------------------------- -// -// Returns one of: -// IDNA_VALID - keep code point in output unchanged -// IDNA_DISALLOWED - code point is not allowed (map() returns error) -// IDNA_IGNORED - code point is ignored (index 0 = empty UTF-8 entry) -// other - byte offset into idna_utf8_mappings[] (null-terminated) -// -// Assumes ensure_tables() has already been called by the public entry point. -static uint16_t idna_lookup(uint32_t cp) noexcept { - if (cp < IDNA_LOW_RANGE_END) { - uint16_t ref = idna_stage1[cp >> IDNA_BLOCK_BITS]; - if (ref & IDNA_BOOL_FLAG) { - uint32_t bit_idx = - static_cast(ref & ~IDNA_BOOL_FLAG) * IDNA_BLOCK_SIZE + - (cp & IDNA_BLOCK_MASK); - bool is_valid = (idna_bool_blocks[bit_idx >> 6] >> (bit_idx & 63u)) & 1u; - return is_valid ? IDNA_VALID : IDNA_DISALLOWED; - } - return idna_stage2[ref + (cp & IDNA_BLOCK_MASK)]; - } - - if (cp >= IDNA_HIGH_IGNORED_START && cp < IDNA_HIGH_IGNORED_END) { - return IDNA_IGNORED; - } - - return IDNA_DISALLOWED; -} - -// Advances *ptr past one well-formed UTF-8 code point from the mapping table. -static char32_t utf8_next(const uint8_t*& ptr) noexcept { - uint8_t b0 = *ptr++; - if (b0 < 0x80u) return static_cast(b0); - if (b0 < 0xE0u) { - uint32_t cp = (b0 & 0x1Fu) << 6; - cp |= (*ptr++ & 0x3Fu); - return static_cast(cp); - } - if (b0 < 0xF0u) { - uint32_t cp = (b0 & 0x0Fu) << 12; - cp |= ((*ptr++ & 0x3Fu) << 6); - cp |= (*ptr++ & 0x3Fu); - return static_cast(cp); - } - uint32_t cp = (b0 & 0x07u) << 18; - cp |= ((*ptr++ & 0x3Fu) << 12); - cp |= ((*ptr++ & 0x3Fu) << 6); - cp |= (*ptr++ & 0x3Fu); - return static_cast(cp); -} - -// Count UTF-8 code points in a null-terminated mapping string (trusted table). -static size_t utf8_count_codepoints(const uint8_t* ptr) noexcept { - size_t n = 0; - while (*ptr != 0) { - ++n; - if (*ptr < 0x80u) { - ++ptr; - } else if (*ptr < 0xE0u) { - ptr += 2; - } else if (*ptr < 0xF0u) { - ptr += 3; - } else { - ptr += 4; - } - } - return n; -} - -// --- ASCII fast path --------------------------------------------------------- -void ascii_map(char* input, size_t length) { - auto broadcast = [](uint8_t v) -> uint64_t { - return 0x101010101010101ull * v; - }; - uint64_t broadcast_80 = broadcast(0x80); - uint64_t broadcast_Ap = broadcast(128 - 'A'); - uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1); - size_t i = 0; - - for (; i + 7 < length; i += 8) { - uint64_t word{}; - std::memcpy(&word, input + i, sizeof(word)); - word ^= - (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2; - std::memcpy(input + i, &word, sizeof(word)); - } - if (i < length) { - uint64_t word{}; - std::memcpy(&word, input + i, length - i); - word ^= - (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2; - std::memcpy(input + i, &word, length - i); - } -} - -// Two-pass map: first validate + exact size, then write once (no growth -// realloc). -bool map(std::u32string_view input, std::u32string& out) { - // [Map](https://www.unicode.org/reports/tr46/#ProcessingStepMap). - out.clear(); - if (!ensure_tables()) { - return false; - } - - // Pass 1: validate and compute exact output length. - size_t out_size = 0; - for (char32_t x : input) { - uint16_t status = idna_lookup(static_cast(x)); - if (status == IDNA_DISALLOWED) { - return false; - } - if (status == IDNA_VALID) { - ++out_size; - continue; - } - if (static_cast(status) >= idna_utf8_mappings_size) { - return false; - } - out_size += utf8_count_codepoints(idna_utf8_mappings + status); - } - - // Pass 2: write into a single allocation. - out.resize(out_size); - size_t w = 0; - for (char32_t x : input) { - uint16_t status = idna_lookup(static_cast(x)); - if (status == IDNA_VALID) { - out[w++] = x; - continue; - } - // IGNORED or mapped (status already validated in pass 1). - const uint8_t* ptr = idna_utf8_mappings + status; - while (*ptr != 0) { - out[w++] = utf8_next(ptr); - } - } - return true; -} - -std::u32string map(std::u32string_view input) { - std::u32string answer; - if (!map(input, answer)) { - return {}; - } - return answer; -} - -} // namespace ada::idna -/* end file src/mapping.cpp */ -/* begin file src/normalization.cpp */ -/* begin file include/ada/idna/normalization.h */ -#ifndef ADA_IDNA_NORMALIZATION_H -#define ADA_IDNA_NORMALIZATION_H - -#include -#include - -namespace ada::idna { - -// Returns true if `input` is already in Unicode Normalization Form C. -// Requires that internal tables have been loaded (call ensure via normalize -// or map first, or this returns false if tables are unavailable). -[[nodiscard]] bool is_already_nfc(std::u32string_view input) noexcept; - -// Normalize the characters according to IDNA (Unicode Normalization Form C). -// Returns false if the internal Unicode tables could not be loaded; in that -// case `input` is left unchanged. Skips work when the string is already NFC. -[[nodiscard]] bool normalize(std::u32string& input); - -} // namespace ada::idna -#endif -/* end file include/ada/idna/normalization.h */ -/* begin file src/normalization_tables.cpp */ -// Normalization table helpers. -// Array payloads live in the DEFLATE blob (table_store.hpp). -// clang-format off -#ifndef ADA_IDNA_NORMALIZATION_TABLES_H -#define ADA_IDNA_NORMALIZATION_TABLES_H - -namespace ada::idna { - -// Accessors and multi-stage tables are provided by table_store.hpp after -// ensure_tables(). Lookup code matches the classic O(1) uni-algo layout. - -} // namespace ada::idna -#endif // ADA_IDNA_NORMALIZATION_TABLES_H -/* end file src/normalization_tables.cpp */ - -#include - -namespace ada::idna { - -// See -// https://github.com/uni-algo/uni-algo/blob/c612968c5ed3ace39bde4c894c24286c5f2c7fe2/include/uni_algo/impl/impl_norm.h#L467 -constexpr char32_t hangul_sbase = 0xAC00; -constexpr char32_t hangul_tbase = 0x11A7; -constexpr char32_t hangul_vbase = 0x1161; -constexpr char32_t hangul_lbase = 0x1100; -constexpr char32_t hangul_lcount = 19; -constexpr char32_t hangul_vcount = 21; -constexpr char32_t hangul_tcount = 28; -constexpr char32_t hangul_ncount = hangul_vcount * hangul_tcount; -constexpr char32_t hangul_scount = - hangul_lcount * hangul_vcount * hangul_tcount; - -// Canonical decomposition length (0 if none / compatibility-only / Hangul -// syllable). Length 1 means a singleton mapping (character is not NFC form). -// Length >= 2 means a primary composite that is already the NFC form of its -// decomposition (e.g. U+00E9 LATIN SMALL LETTER E WITH ACUTE). -static size_t canonical_decomp_length(char32_t current_character) noexcept { - if (current_character >= hangul_sbase && - current_character < hangul_sbase + hangul_scount) { - // Hangul precomposed syllables are NFC. - return 0; - } - if (current_character >= 0x110000) { - return 0; - } - const uint8_t di = decomposition_index[current_character >> 8]; - const uint16_t* const decomposition = - decomposition_block_row(di) + (current_character % 256); - size_t decomposition_length = - (decomposition[1] >> 2) - (decomposition[0] >> 2); - if ((decomposition_length > 0) && (decomposition[0] & 1)) { - decomposition_length = 0; // compatibility-only: ignored for NFC - } - return decomposition_length; -} - -std::pair compute_decomposition_length( - const std::u32string_view input) noexcept { - bool decomposition_needed{false}; - size_t additional_elements{0}; - for (char32_t current_character : input) { - size_t decomposition_length{0}; - - if (current_character >= hangul_sbase && - current_character < hangul_sbase + hangul_scount) { - // Full NFD-style Hangul decomp for the decompose() step. - decomposition_length = 2; - if ((current_character - hangul_sbase) % hangul_tcount) { - decomposition_length = 3; - } - } else if (current_character < 0x110000) { - const uint8_t di = decomposition_index[current_character >> 8]; - const uint16_t* const decomposition = - decomposition_block_row(di) + (current_character % 256); - decomposition_length = (decomposition[1] >> 2) - (decomposition[0] >> 2); - if ((decomposition_length > 0) && (decomposition[0] & 1)) { - decomposition_length = 0; - } - } - if (decomposition_length != 0) { - decomposition_needed = true; - additional_elements += decomposition_length - 1; - } - } - return {decomposition_needed, additional_elements}; -} - -void decompose(std::u32string& input, size_t additional_elements) { - input.resize(input.size() + additional_elements); - for (size_t descending_idx = input.size(), - input_count = descending_idx - additional_elements; - input_count--;) { - if (input[input_count] >= hangul_sbase && - input[input_count] < hangul_sbase + hangul_scount) { - // Hangul decomposition. - char32_t s_index = input[input_count] - hangul_sbase; - if (s_index % hangul_tcount != 0) { - input[--descending_idx] = hangul_tbase + s_index % hangul_tcount; - } - input[--descending_idx] = - hangul_vbase + (s_index % hangul_ncount) / hangul_tcount; - input[--descending_idx] = hangul_lbase + s_index / hangul_ncount; - } else if (input[input_count] < 0x110000) { - const uint16_t* decomposition = - decomposition_block_row( - decomposition_index[input[input_count] >> 8]) + - (input[input_count] % 256); - uint16_t decomposition_length = - (decomposition[1] >> 2) - (decomposition[0] >> 2); - if (decomposition_length > 0 && (decomposition[0] & 1)) { - decomposition_length = 0; - } - if (decomposition_length > 0) { - const size_t base = static_cast(decomposition[0] >> 2); - if (base + decomposition_length > decomposition_data_size) { - input[--descending_idx] = input[input_count]; - } else { - while (decomposition_length-- > 0) { - input[--descending_idx] = - decomposition_data[base + decomposition_length]; - } - } - } else { - input[--descending_idx] = input[input_count]; - } - } else { - input[--descending_idx] = input[input_count]; - } - } -} - -uint8_t get_ccc(char32_t c) noexcept { - if (c >= 0x110000) { - return 0; - } - return ccc_block_row(ccc_index[c >> 8])[c % 256]; -} - -void sort_marks(std::u32string& input) { - for (size_t idx = 1; idx < input.size(); idx++) { - uint8_t ccc = get_ccc(input[idx]); - if (ccc == 0) { - continue; - } - auto current_character = input[idx]; - size_t back_idx = idx; - while (back_idx != 0 && get_ccc(input[back_idx - 1]) > ccc) { - input[back_idx] = input[back_idx - 1]; - back_idx--; - } - input[back_idx] = current_character; - } -} - -void decompose_nfc(std::u32string& input) { - /** - * Decompose the domain_name string to Unicode Normalization Form C. - * @see https://www.unicode.org/reports/tr46/#ProcessingStepDecompose - */ - auto [decomposition_needed, additional_elements] = - compute_decomposition_length(input); - if (decomposition_needed) { - decompose(input, additional_elements); - } - sort_marks(input); -} - -// Returns true if a composition step would change the string. -static bool would_compose(std::u32string_view input) noexcept { - for (size_t input_count = 0; input_count < input.size();) { - const char32_t current = input[input_count]; - if (current >= hangul_lbase && current < hangul_lbase + hangul_lcount) { - if (input_count + 1 < input.size() && - input[input_count + 1] >= hangul_vbase && - input[input_count + 1] < hangul_vbase + hangul_vcount) { - return true; - } - ++input_count; - continue; - } - if (current >= hangul_sbase && current < hangul_sbase + hangul_scount) { - if ((current - hangul_sbase) % hangul_tcount && - input_count + 1 < input.size() && - input[input_count + 1] > hangul_tbase && - input[input_count + 1] < hangul_tbase + hangul_tcount) { - return true; - } - ++input_count; - continue; - } - if (current < 0x110000) { - const uint16_t* composition = - composition_block_row(composition_index[current >> 8]) + - (current % 256); - int32_t previous_ccc = -1; - size_t j = input_count; - for (; j + 1 < input.size(); ++j) { - uint8_t ccc = get_ccc(input[j + 1]); - if (composition[1] != composition[0] && previous_ccc < ccc) { - int left = composition[0]; - int right = composition[1]; - if (left < 0 || right < left || - static_cast(right) > composition_data_size) { - break; - } - while (left + 2 < right) { - int middle = left + (((right - left) >> 1) & ~1); - if (composition_data[static_cast(middle)] <= input[j + 1]) { - left = middle; - } - if (composition_data[static_cast(middle)] >= input[j + 1]) { - right = middle; - } - } - if (static_cast(left + 1) < composition_data_size && - composition_data[static_cast(left)] == input[j + 1]) { - return true; - } - } - if (ccc == 0) { - break; - } - previous_ccc = ccc; - } - input_count = j + 1; - continue; - } - ++input_count; - } - return false; -} - -bool is_already_nfc(std::u32string_view input) noexcept { - if (input.empty()) { - return true; - } - if (!tables_are_ready() && !ensure_tables()) { - return false; - } - // 1) Singleton decompositions are never NFC (e.g. U+212B ANGSTROM SIGN). - // Multi-code-point decomps are primary composites that are NFC as-is. - for (char32_t c : input) { - if (canonical_decomp_length(c) == 1) { - return false; - } - } - // 2) Combining marks already in canonical order. - uint8_t prev_ccc = 0; - for (char32_t c : input) { - uint8_t ccc = get_ccc(c); - if (ccc != 0 && prev_ccc > ccc) { - return false; - } - prev_ccc = ccc; - } - // 3) No pairwise composition would apply (including Hangul L+V / LV+T). - return !would_compose(input); -} - -void compose(std::u32string& input) { - /** - * Compose the domain_name string to Unicode Normalization Form C. - * @see https://www.unicode.org/reports/tr46/#ProcessingStepCompose - */ - size_t input_count{0}; - size_t composition_count{0}; - for (; input_count < input.size(); input_count++, composition_count++) { - input[composition_count] = input[input_count]; - if (input[input_count] >= hangul_lbase && - input[input_count] < hangul_lbase + hangul_lcount) { - if (input_count + 1 < input.size() && - input[input_count + 1] >= hangul_vbase && - input[input_count + 1] < hangul_vbase + hangul_vcount) { - input[composition_count] = - hangul_sbase + - ((input[input_count] - hangul_lbase) * hangul_vcount + - input[input_count + 1] - hangul_vbase) * - hangul_tcount; - input_count++; - if (input_count + 1 < input.size() && - input[input_count + 1] > hangul_tbase && - input[input_count + 1] < hangul_tbase + hangul_tcount) { - input[composition_count] += input[++input_count] - hangul_tbase; - } - } - } else if (input[input_count] >= hangul_sbase && - input[input_count] < hangul_sbase + hangul_scount) { - if ((input[input_count] - hangul_sbase) % hangul_tcount && - input_count + 1 < input.size() && - input[input_count + 1] > hangul_tbase && - input[input_count + 1] < hangul_tbase + hangul_tcount) { - input[composition_count] += input[++input_count] - hangul_tbase; - } - } else if (input[input_count] < 0x110000) { - const uint16_t* composition = - composition_block_row(composition_index[input[input_count] >> 8]) + - (input[input_count] % 256); - size_t initial_composition_count = composition_count; - for (int32_t previous_ccc = -1; input_count + 1 < input.size(); - input_count++) { - uint8_t ccc = get_ccc(input[input_count + 1]); - - if (composition[1] != composition[0] && previous_ccc < ccc) { - int left = composition[0]; - int right = composition[1]; - if (left < 0 || right < left || - static_cast(right) > composition_data_size) { - break; - } - while (left + 2 < right) { - int middle = left + (((right - left) >> 1) & ~1); - if (composition_data[static_cast(middle)] <= - input[input_count + 1]) { - left = middle; - } - if (composition_data[static_cast(middle)] >= - input[input_count + 1]) { - right = middle; - } - } - if (static_cast(left + 1) < composition_data_size && - composition_data[static_cast(left)] == - input[input_count + 1]) { - char32_t composed = composition_data[static_cast(left + 1)]; - input[initial_composition_count] = composed; - composition = - composition_block_row(composition_index[composed >> 8]) + - (composed % 256); - continue; - } - } - - if (ccc == 0) { - break; - } - previous_ccc = ccc; - input[++composition_count] = input[input_count + 1]; - } - } - } - - if (composition_count < input_count) { - input.resize(composition_count); - } -} - -bool normalize(std::u32string& input) { - /** - * Normalize the characters according to IDNA (Unicode Normalization Form C). - * @see https://www.unicode.org/reports/tr46/#ProcessingStepNormalize - */ - if (!ensure_tables() || decomposition_index == nullptr || - composition_index == nullptr) { - return false; - } - if (is_already_nfc(input)) { - return true; - } - decompose_nfc(input); - compose(input); - return true; -} - -} // namespace ada::idna -/* end file src/normalization.cpp */ -/* begin file src/punycode.cpp */ -/* begin file include/ada/idna/punycode.h */ -#ifndef ADA_IDNA_PUNYCODE_H -#define ADA_IDNA_PUNYCODE_H - -#include -#include - -namespace ada::idna { - -bool punycode_to_utf32(std::string_view input, std::u32string& out); -bool verify_punycode(std::string_view input); -bool utf32_to_punycode(std::u32string_view input, std::string& out); - -} // namespace ada::idna - -#endif // ADA_IDNA_PUNYCODE_H -/* end file include/ada/idna/punycode.h */ - -#include - -namespace ada::idna { - -constexpr int32_t base = 36; -constexpr int32_t tmin = 1; -constexpr int32_t tmax = 26; -constexpr int32_t skew = 38; -constexpr int32_t damp = 700; -constexpr int32_t initial_bias = 72; -constexpr uint32_t initial_n = 128; - -static constexpr int32_t char_to_digit_value(char value) { - if (value >= 'a' && value <= 'z') return value - 'a'; - if (value >= '0' && value <= '9') return value - '0' + 26; - return -1; -} - -static constexpr char digit_to_char(int32_t digit) { - return digit < 26 ? char(digit + 97) : char(digit + 22); -} - -static constexpr int32_t adapt(int32_t d, int32_t n, bool firsttime) { - if (firsttime) { - d = d / damp; - } else { - d = d / 2; - } - d += d / n; - int32_t k = 0; - while (d > ((base - tmin) * tmax) / 2) { - d /= base - tmin; - k += base; - } - return k + (((base - tmin + 1) * d) / (d + skew)); -} - -bool punycode_to_utf32(std::string_view input, std::u32string &out) { - int32_t written_out{0}; - out.reserve(out.size() + input.size()); - uint32_t n = initial_n; - int32_t i = 0; - int32_t bias = initial_bias; - // grab ascii content - size_t end_of_ascii = input.find_last_of('-'); - if (end_of_ascii != std::string_view::npos) { - for (uint8_t c : input.substr(0, end_of_ascii)) { - if (c >= 0x80) { - return false; - } - out.push_back(c); - written_out++; - } - input.remove_prefix(end_of_ascii + 1); - } - while (!input.empty()) { - int32_t oldi = i; - int32_t w = 1; - for (int32_t k = base;; k += base) { - if (input.empty()) { - return false; - } - uint8_t code_point = input.front(); - input.remove_prefix(1); - int32_t digit = char_to_digit_value(code_point); - if (digit < 0) { - return false; - } - if (digit > (0x7fffffff - i) / w) { - return false; - } - i = i + digit * w; - int32_t t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias; - if (digit < t) { - break; - } - if (w > 0x7fffffff / (base - t)) { - return false; - } - w = w * (base - t); - } - bias = adapt(i - oldi, written_out + 1, oldi == 0); - if (i / (written_out + 1) > int32_t(0x7fffffff - n)) { - return false; - } - n = n + i / (written_out + 1); - i = i % (written_out + 1); - if (n < 0x80) { - return false; - } - out.insert(out.begin() + i, n); - written_out++; - ++i; - } - // See https://github.com/whatwg/url/issues/803 - // Reject labels whose decoded form begins with "xn--" (double-encoded ACE). - if (out.size() >= 4 && out[0] == U'x' && out[1] == U'n' && out[2] == U'-' && - out[3] == U'-') { - return false; - } - return true; -} - -bool verify_punycode(std::string_view input) { - // Track only the first 4 decoded code points to check for the "xn--" prefix. - // This avoids heap allocation while still detecting double-encoded ACE - // labels. - uint32_t first4[4]{}; - size_t written_out{0}; - uint32_t n = initial_n; - int32_t i = 0; - int32_t bias = initial_bias; - // grab ascii content - size_t end_of_ascii = input.find_last_of('-'); - if (end_of_ascii != std::string_view::npos) { - for (uint8_t c : input.substr(0, end_of_ascii)) { - if (c >= 0x80) { - return false; - } - if (written_out < 4) { - first4[written_out] = c; - } - written_out++; - } - input.remove_prefix(end_of_ascii + 1); - } - while (!input.empty()) { - int32_t oldi = i; - int32_t w = 1; - for (int32_t k = base;; k += base) { - if (input.empty()) { - return false; - } - uint8_t code_point = input.front(); - input.remove_prefix(1); - int32_t digit = char_to_digit_value(code_point); - if (digit < 0) { - return false; - } - if (digit > (0x7fffffff - i) / w) { - return false; - } - i = i + digit * w; - int32_t t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias; - if (digit < t) { - break; - } - if (w > 0x7fffffff / (base - t)) { - return false; - } - w = w * (base - t); - } - bias = adapt(i - oldi, int32_t(written_out + 1), oldi == 0); - if (i / (written_out + 1) > 0x7fffffff - n) { - return false; - } - n = n + i / int32_t(written_out + 1); - i = i % int32_t(written_out + 1); - if (n < 0x80) { - return false; - } - // Simulate insert at position i, maintaining only the first 4 slots. - size_t insert_pos = size_t(i); - if (insert_pos < 4) { - for (size_t j = 3; j > insert_pos; j--) { - first4[j] = first4[j - 1]; - } - first4[insert_pos] = n; - } - written_out++; - ++i; - } - // See https://github.com/whatwg/url/issues/803 - // Reject labels whose decoded form begins with "xn--" (double-encoded ACE). - if (written_out >= 4 && first4[0] == U'x' && first4[1] == U'n' && - first4[2] == U'-' && first4[3] == U'-') { - return false; - } - return true; -} - -bool utf32_to_punycode(std::u32string_view input, std::string &out) { - out.reserve(input.size() + out.size()); - uint32_t n = initial_n; - int32_t d = 0; - int32_t bias = initial_bias; - size_t h = 0; - // first push the ascii content - for (uint32_t c : input) { - if (c < 0x80) { - ++h; - out.push_back(char(c)); - } - if (c > 0x10ffff || (c >= 0xd800 && c < 0xe000)) { - return false; - } - } - size_t b = h; - if (b > 0) { - out.push_back('-'); - } - while (h < input.size()) { - uint32_t m = 0x10FFFF; - for (auto code_point : input) { - if (code_point >= n && code_point < m) m = code_point; - } - - if ((m - n) > (0x7fffffff - d) / (h + 1)) { - return false; - } - d = d + int32_t((m - n) * (h + 1)); - n = m; - for (auto c : input) { - if (c < n) { - if (d == 0x7fffffff) { - return false; - } - ++d; - } - if (c == n) { - int32_t q = d; - for (int32_t k = base;; k += base) { - int32_t t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias; - - if (q < t) { - break; - } - out.push_back(digit_to_char(t + ((q - t) % (base - t)))); - q = (q - t) / (base - t); - } - out.push_back(digit_to_char(q)); - bias = adapt(d, int32_t(h + 1), h == b); - d = 0; - ++h; - } - } - ++d; - ++n; - } - return true; -} - -} // namespace ada::idna -/* end file src/punycode.cpp */ -/* begin file src/validity.cpp */ - -#include -#include -#include - -namespace ada::idna { - -enum direction : uint8_t { - NONE, - BN, - CS, - ES, - ON, - EN, - L, - R, - NSM, - AL, - AN, - ET, - WS, - RLO, - LRO, - PDF, - RLE, - RLI, - FSI, - PDI, - LRI, - B, - S, - LRE -}; - -// Bidi direction ranges live in the compressed blob as const SoA arrays -// (dir_start / dir_final / dir_value). See table_store.hpp layout asserts. - -// CheckJoiners and CheckBidi are true for URL specification. - -// Assumes ensure_tables() has already been called by is_label_valid(). -inline static direction find_direction(uint32_t code_point) noexcept { - // Binary search on dir_final (sorted upper bounds). - size_t lo = 0, hi = dir_table_count; - while (lo < hi) { - size_t mid = lo + (hi - lo) / 2; - if (dir_final[mid] < code_point) { - lo = mid + 1; - } else { - hi = mid; - } - } - if (lo == dir_table_count) { - return direction::NONE; - } - return code_point >= dir_start[lo] ? static_cast(dir_value[lo]) - : direction::NONE; -} - -inline static size_t find_last_not_of_nsm( - const std::u32string_view label) noexcept { - for (int i = static_cast(label.size() - 1); i >= 0; i--) - if (find_direction(label[i]) != direction::NSM) return i; - - return std::u32string_view::npos; -} - -// An RTL label is a label that contains at least one character of type R, AL, -// or AN. https://www.rfc-editor.org/rfc/rfc5893#section-2 -inline static bool is_rtl_label(const std::u32string_view label) noexcept { - const size_t mask = - (1u << direction::R) | (1u << direction::AL) | (1u << direction::AN); - - size_t directions = 0; - for (size_t i = 0; i < label.size(); i++) { - directions |= 1u << find_direction(label[i]); - } - return (directions & mask) != 0; -} - -bool is_label_valid(const std::u32string_view label) { - if (label.empty()) { - return true; - } - if (!ensure_tables() || combining_ranges == nullptr || dir_start == nullptr || - dir_final == nullptr || dir_value == nullptr) { - return false; - } - - /////////////// - // We have a normalization step which ensures that we are in NFC. - // If we receive punycode, we normalize and check that the normalized - // version matches the original. - // -------------------------------------- - // The label must be in Unicode Normalization Form NFC. - - // Current URL standard indicatest that CheckHyphens is set to false. - // --------------------------------------- - // If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character - // in both the third and fourth positions. If CheckHyphens, the label must - // neither begin nor end with a U+002D HYPHEN-MINUS character. - - // This is not necessary because we segment the - // labels by '.'. - // --------------------------------------- - // The label must not contain a U+002E ( . ) FULL STOP. - // if (label.find('.') != std::string_view::npos) return false; - - // The label must not begin with a combining mark. - // Range membership via lower_bound on range end. - const std::span comb_span{combining_ranges, - combining_range_count}; - const auto comb_it = std::ranges::lower_bound( - comb_span, label.front(), {}, [](const auto& range) { return range[1]; }); - if (comb_it != comb_span.end() && label.front() >= (*comb_it)[0]) { - return false; - } - // We verify this next step as part of the mapping: - // --------------------------------------------- - // Each code point in the label must only have certain status values - // according to Section 5, IDNA Mapping Table: - // - For Transitional Processing, each value must be valid. - // - For Nontransitional Processing, each value must be either valid or - // deviation. - - // If CheckJoiners, the label must satisfy the ContextJ rules from Appendix - // A, in The Unicode Code Points and Internationalized Domain Names for - // Applications (IDNA) [IDNA2008]. - constexpr static uint32_t virama[] = { - 0x094D, 0x09CD, 0x0A4D, 0x0ACD, 0x0B4D, 0x0BCD, 0x0C4D, 0x0CCD, - 0x0D3B, 0x0D3C, 0x0D4D, 0x0DCA, 0x0E3A, 0x0EBA, 0x0F84, 0x1039, - 0x103A, 0x1714, 0x1734, 0x17D2, 0x1A60, 0x1B44, 0x1BAA, 0x1BAB, - 0x1BF2, 0x1BF3, 0x2D7F, 0xA806, 0xA82C, 0xA8C4, 0xA953, 0xA9C0, - 0xAAF6, 0xABED, 0x10A3F, 0x11046, 0x1107F, 0x110B9, 0x11133, 0x11134, - 0x111C0, 0x11235, 0x112EA, 0x1134D, 0x11442, 0x114C2, 0x115BF, 0x1163F, - 0x116B6, 0x1172B, 0x11839, 0x1193D, 0x1193E, 0x119E0, 0x11A34, 0x11A47, - 0x11A99, 0x11C3F, 0x11D44, 0x11D45, 0x11D97}; - constexpr static uint32_t R[] = { - 0x622, 0x623, 0x624, 0x625, 0x627, 0x629, 0x62f, 0x630, 0x631, - 0x632, 0x648, 0x671, 0x672, 0x673, 0x675, 0x676, 0x677, 0x688, - 0x689, 0x68a, 0x68b, 0x68c, 0x68d, 0x68e, 0x68f, 0x690, 0x691, - 0x692, 0x693, 0x694, 0x695, 0x696, 0x697, 0x698, 0x699, 0x6c0, - 0x6c3, 0x6c4, 0x6c5, 0x6c6, 0x6c7, 0x6c8, 0x6c9, 0x6ca, 0x6cb, - 0x6cd, 0x6cf, 0x6d2, 0x6d3, 0x6d5, 0x6ee, 0x6ef, 0x710, 0x715, - 0x716, 0x717, 0x718, 0x719, 0x71e, 0x728, 0x72a, 0x72c, 0x72f, - 0x74d, 0x759, 0x75a, 0x75b, 0x854, 0x8aa, 0x8ab, 0x8ac}; - constexpr static uint32_t L[] = {0xa872}; - constexpr static uint32_t D[] = { - 0x620, 0x626, 0x628, 0x62a, 0x62b, 0x62c, 0x62d, 0x62e, 0x633, - 0x634, 0x635, 0x636, 0x637, 0x638, 0x639, 0x63a, 0x63b, 0x63c, - 0x63d, 0x63e, 0x63f, 0x641, 0x642, 0x643, 0x644, 0x645, 0x646, - 0x647, 0x649, 0x64a, 0x66e, 0x66f, 0x678, 0x679, 0x67a, 0x67b, - 0x67c, 0x67d, 0x67e, 0x67f, 0x680, 0x681, 0x682, 0x683, 0x684, - 0x685, 0x686, 0x687, 0x69a, 0x69b, 0x69c, 0x69d, 0x69e, 0x69f, - 0x6a0, 0x6a1, 0x6a2, 0x6a3, 0x6a4, 0x6a5, 0x6a6, 0x6a7, 0x6a8, - 0x6a9, 0x6aa, 0x6ab, 0x6ac, 0x6ad, 0x6ae, 0x6af, 0x6b0, 0x6b1, - 0x6b2, 0x6b3, 0x6b4, 0x6b5, 0x6b6, 0x6b7, 0x6b8, 0x6b9, 0x6ba, - 0x6bb, 0x6bc, 0x6bd, 0x6be, 0x6bf, 0x6c1, 0x6c2, 0x6cc, 0x6ce, - 0x6d0, 0x6d1, 0x6fa, 0x6fb, 0x6fc, 0x6ff, 0x712, 0x713, 0x714, - 0x71a, 0x71b, 0x71c, 0x71d, 0x71f, 0x720, 0x721, 0x722, 0x723, - 0x724, 0x725, 0x726, 0x727, 0x729, 0x72b, 0x72d, 0x72e, 0x74e, - 0x74f, 0x750, 0x751, 0x752, 0x753, 0x754, 0x755, 0x756, 0x757, - 0x758, 0x75c, 0x75d, 0x75e, 0x75f, 0x760, 0x761, 0x762, 0x763, - 0x764, 0x765, 0x766, 0x850, 0x851, 0x852, 0x853, 0x855, 0x8a0, - 0x8a2, 0x8a3, 0x8a4, 0x8a5, 0x8a6, 0x8a7, 0x8a8, 0x8a9, 0x1807, - 0x1820, 0x1821, 0x1822, 0x1823, 0x1824, 0x1825, 0x1826, 0x1827, 0x1828, - 0x1829, 0x182a, 0x182b, 0x182c, 0x182d, 0x182e, 0x182f, 0x1830, 0x1831, - 0x1832, 0x1833, 0x1834, 0x1835, 0x1836, 0x1837, 0x1838, 0x1839, 0x183a, - 0x183b, 0x183c, 0x183d, 0x183e, 0x183f, 0x1840, 0x1841, 0x1842, 0x1843, - 0x1844, 0x1845, 0x1846, 0x1847, 0x1848, 0x1849, 0x184a, 0x184b, 0x184c, - 0x184d, 0x184e, 0x184f, 0x1850, 0x1851, 0x1852, 0x1853, 0x1854, 0x1855, - 0x1856, 0x1857, 0x1858, 0x1859, 0x185a, 0x185b, 0x185c, 0x185d, 0x185e, - 0x185f, 0x1860, 0x1861, 0x1862, 0x1863, 0x1864, 0x1865, 0x1866, 0x1867, - 0x1868, 0x1869, 0x186a, 0x186b, 0x186c, 0x186d, 0x186e, 0x186f, 0x1870, - 0x1871, 0x1872, 0x1873, 0x1874, 0x1875, 0x1876, 0x1877, 0x1887, 0x1888, - 0x1889, 0x188a, 0x188b, 0x188c, 0x188d, 0x188e, 0x188f, 0x1890, 0x1891, - 0x1892, 0x1893, 0x1894, 0x1895, 0x1896, 0x1897, 0x1898, 0x1899, 0x189a, - 0x189b, 0x189c, 0x189d, 0x189e, 0x189f, 0x18a0, 0x18a1, 0x18a2, 0x18a3, - 0x18a4, 0x18a5, 0x18a6, 0x18a7, 0x18a8, 0x18aa, 0xa840, 0xa841, 0xa842, - 0xa843, 0xa844, 0xa845, 0xa846, 0xa847, 0xa848, 0xa849, 0xa84a, 0xa84b, - 0xa84c, 0xa84d, 0xa84e, 0xa84f, 0xa850, 0xa851, 0xa852, 0xa853, 0xa854, - 0xa855, 0xa856, 0xa857, 0xa858, 0xa859, 0xa85a, 0xa85b, 0xa85c, 0xa85d, - 0xa85e, 0xa85f, 0xa860, 0xa861, 0xa862, 0xa863, 0xa864, 0xa865, 0xa866, - 0xa867, 0xa868, 0xa869, 0xa86a, 0xa86b, 0xa86c, 0xa86d, 0xa86e, 0xa86f, - 0xa870, 0xa871}; - - for (size_t i = 0; i < label.size(); i++) { - uint32_t c = label[i]; - if (c == 0x200c) { - if (i > 0) { - if (std::ranges::binary_search(virama, label[i - 1])) { - return true; - } - } - if ((i == 0) || (i + 1 >= label.size())) { - return false; - } - // we go backward looking for L or D - auto is_l_or_d = [](uint32_t code) { - return std::ranges::binary_search(L, code) || - std::ranges::binary_search(D, code); - }; - auto is_r_or_d = [](uint32_t code) { - return std::ranges::binary_search(R, code) || - std::ranges::binary_search(D, code); - }; - std::u32string_view before = label.substr(0, i); - std::u32string_view after = label.substr(i + 1); - return (std::find_if(before.begin(), before.end(), is_l_or_d) != - before.end()) && - (std::find_if(after.begin(), after.end(), is_r_or_d) != - after.end()); - } else if (c == 0x200d) { - if (i > 0) { - if (std::ranges::binary_search(virama, label[i - 1])) { - return true; - } - } - return false; - } - } - - // If CheckBidi, and if the domain name is a Bidi domain name, then the label - // must satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, - // Section 2. - - // The following rule, consisting of six conditions, applies to labels - // in Bidi domain names. The requirements that this rule satisfies are - // described in Section 3. All of the conditions must be satisfied for - // the rule to be satisfied. - // - // 1. The first character must be a character with Bidi property L, R, - // or AL. If it has the R or AL property, it is an RTL label; if it - // has the L property, it is an LTR label. - // - // 2. In an RTL label, only characters with the Bidi properties R, AL, - // AN, EN, ES, CS, ET, ON, BN, or NSM are allowed. - // - // 3. In an RTL label, the end of the label must be a character with - // Bidi property R, AL, EN, or AN, followed by zero or more - // characters with Bidi property NSM. - // - // 4. In an RTL label, if an EN is present, no AN may be present, and - // vice versa. - // - // 5. In an LTR label, only characters with the Bidi properties L, EN, - // ES, CS, ET, ON, BN, or NSM are allowed. - // - // 6. In an LTR label, the end of the label must be a character with - // Bidi property L or EN, followed by zero or more characters with - // Bidi property NSM. - - size_t last_non_nsm_char = find_last_not_of_nsm(label); - if (last_non_nsm_char == std::u32string_view::npos) { - return false; - } - - // A "Bidi domain name" is a domain name that contains at least one RTL label. - // The following rule, consisting of six conditions, applies to labels in Bidi - // domain names. - if (is_rtl_label(label)) { - // The first character must be a character with Bidi property L, R, - // or AL. If it has the R or AL property, it is an RTL label; if it - // has the L property, it is an LTR label. - - if (find_direction(label[0]) == direction::L) { - // Eval as LTR - - // In an LTR label, only characters with the Bidi properties L, EN, - // ES, CS, ET, ON, BN, or NSM are allowed. - for (size_t i = 0; i <= last_non_nsm_char; i++) { - const direction d = find_direction(label[i]); - if (!(d == direction::L || d == direction::EN || d == direction::ES || - d == direction::CS || d == direction::ET || d == direction::ON || - d == direction::BN || d == direction::NSM)) { - return false; - } - } - - const direction last_dir = find_direction(label[last_non_nsm_char]); - if (!(last_dir == direction::L || last_dir == direction::EN)) { - return false; - } - - return true; - - } else { - // Eval as RTL - - // The first character must be R or AL; a leading AN (or any other - // direction) does not start a valid RTL label. - const direction first_dir = find_direction(label[0]); - if (first_dir != direction::R && first_dir != direction::AL) { - return false; - } - - bool has_an = false; - bool has_en = false; - for (size_t i = 0; i <= last_non_nsm_char; i++) { - const direction d = find_direction(label[i]); - - // In an RTL label, if an EN is present, no AN may be present, and vice - // versa. - if ((d == direction::EN && ((has_en = true) && has_an)) || - (d == direction::AN && ((has_an = true) && has_en))) { - return false; - } - - if (!(d == direction::R || d == direction::AL || d == direction::AN || - d == direction::EN || d == direction::ES || d == direction::CS || - d == direction::ET || d == direction::ON || d == direction::BN || - d == direction::NSM)) { - return false; - } - - if (i == last_non_nsm_char && - !(d == direction::R || d == direction::AL || d == direction::AN || - d == direction::EN)) { - return false; - } - } - - return true; - } - } - - return true; -} - -} // namespace ada::idna -/* end file src/validity.cpp */ -/* begin file src/to_ascii.cpp */ -/* begin file include/ada/idna/to_ascii.h */ -#ifndef ADA_IDNA_TO_ASCII_H -#define ADA_IDNA_TO_ASCII_H - -#include -#include - -/* begin file include/ada/idna/limits.h */ -#ifndef ADA_IDNA_LIMITS_H -#define ADA_IDNA_LIMITS_H - -#include - -namespace ada::idna { - -// Maximum accepted UTF-8 domain length for to_ascii / to_unicode. -// Bounds heap growth under untrusted input (DoS resistance). DNS wire limits -// are smaller; this allows long Unicode labels used in URL tests/fixtures. -inline constexpr size_t max_domain_input_bytes = 16384; - -} // namespace ada::idna - -#endif // ADA_IDNA_LIMITS_H -/* end file include/ada/idna/limits.h */ - -namespace ada::idna { - -// Converts a domain (e.g., www.google.com) possibly containing international -// characters to an ascii domain (with punycode). It will not do percent -// decoding: percent decoding should be done prior to calling this function. We -// do not remove tabs and spaces, they should have been removed prior to calling -// this function. We also do not trim control characters. We also assume that -// the input is not empty. We return "" on error. Inputs longer than -// max_domain_input_bytes are rejected. -// -// This function may accept or even produce invalid domains (WHATWG carve-outs). -std::string to_ascii(std::string_view ut8_string); - -// Same as to_ascii, but writes into `out` and returns false on error without -// relying on empty-string ambiguity. -[[nodiscard]] bool to_ascii(std::string_view ut8_string, std::string& out); - -// Returns true if the string contains a forbidden code point according to the -// WHATGL URL specification: -// https://url.spec.whatwg.org/#forbidden-domain-code-point -bool contains_forbidden_domain_code_point(std::string_view ascii_string); - -bool constexpr is_ascii(std::u32string_view view); -bool constexpr is_ascii(std::string_view view); - -} // namespace ada::idna - -#endif // ADA_IDNA_TO_ASCII_H -/* end file include/ada/idna/to_ascii.h */ - -#include -#include -#include -#include - -/* begin file include/ada/idna/validity.h */ -#ifndef ADA_IDNA_VALIDITY_H -#define ADA_IDNA_VALIDITY_H - -#include -#include - -namespace ada::idna { - -/** - * @see https://www.unicode.org/reports/tr46/#Validity_Criteria - */ -bool is_label_valid(std::u32string_view label); - -} // namespace ada::idna - -#endif // ADA_IDNA_VALIDITY_H -/* end file include/ada/idna/validity.h */ - -#ifdef ADA_USE_SIMDUTF -#include "simdutf.h" -#endif - -namespace ada::idna { - -bool constexpr is_ascii(std::u32string_view view) { - for (uint32_t c : view) { - if (c >= 0x80) { - return false; - } - } - return true; -} - -bool constexpr is_ascii(std::string_view view) { - for (uint8_t c : view) { - if (c >= 0x80) { - return false; - } - } - return true; -} - -constexpr static uint8_t is_forbidden_domain_code_point_table[] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - -static_assert(sizeof(is_forbidden_domain_code_point_table) == 256); - -inline bool is_forbidden_domain_code_point(const char c) noexcept { - return is_forbidden_domain_code_point_table[uint8_t(c)]; -} - -bool contains_forbidden_domain_code_point(std::string_view view) { - return std::ranges::any_of(view, is_forbidden_domain_code_point); -} - -// Per the WHATWG URL "domain to ASCII" algorithm, when beStrict is false and -// the input domain is an ASCII string, the result is the input lowercased, -// regardless of the outcome of Unicode ToASCII. -// -// See https://url.spec.whatwg.org/#concept-domain-to-ascii -static void from_ascii_to_ascii(std::string_view ut8_string, std::string& out) { - out.assign(ut8_string); - ascii_map(out.data(), out.size()); -} - -// Append ASCII code units from a UTF-32 label (all values < 0x80). -static void append_ascii_label(std::string& out, std::u32string_view label) { - const size_t old = out.size(); - out.resize(old + label.size()); - char* dest = out.data() + old; - for (char32_t c : label) { - *dest++ = static_cast(c); - } -} - -// True if label begins with "xn--" (already lowercased by mapping). -static bool is_ace_prefix(std::u32string_view label) noexcept { - return label.size() >= 4 && label[0] == U'x' && label[1] == U'n' && - label[2] == U'-' && label[3] == U'-'; -} - -[[nodiscard]] bool to_ascii(std::string_view ut8_string, std::string& out) { - out.clear(); - if (ut8_string.size() > max_domain_input_bytes) { - return false; - } - if (is_ascii(ut8_string)) { - from_ascii_to_ascii(ut8_string, out); - return true; - } - -#ifdef ADA_USE_SIMDUTF - size_t utf32_length = - simdutf::utf32_length_from_utf8(ut8_string.data(), ut8_string.size()); - if (utf32_length == 0 && !ut8_string.empty()) { - return false; - } - std::u32string working(utf32_length, U'\0'); - size_t actual_utf32_length = simdutf::convert_utf8_to_utf32( - ut8_string.data(), ut8_string.size(), working.data()); -#else - size_t utf32_length = - ada::idna::utf32_length_from_utf8(ut8_string.data(), ut8_string.size()); - if (utf32_length == 0 && !ut8_string.empty()) { - return false; - } - std::u32string working(utf32_length, U'\0'); - size_t actual_utf32_length = ada::idna::utf8_to_utf32( - ut8_string.data(), ut8_string.size(), working.data()); -#endif - if (actual_utf32_length == 0 || actual_utf32_length != utf32_length) { - return false; - } - working.resize(actual_utf32_length); - - // Map into a second buffer with exact sizing (no growth reallocs). - std::u32string mapped; - if (!ada::idna::map(working, mapped)) { - return false; - } - // Drop UTF-32 input; reuse `working` as scratch for ACE validation below. - working.clear(); - - // Skip NFC when already normalized (ASCII is a fast subset of this check). - if (!is_ascii(mapped) && !is_already_nfc(mapped)) { - if (!normalize(mapped)) { - return false; - } - } - - // Estimate ASCII output size (punycode may expand non-ASCII labels). - out.reserve(mapped.size() + 8); - - // Walk labels with a single pointer scan (no repeated string::find). - const char32_t* p = mapped.data(); - const char32_t* const end = p + mapped.size(); - std::u32string post_map; - - while (p < end) { - const char32_t* label_begin = p; - while (p < end && *p != U'.') { - ++p; - } - const size_t label_size = static_cast(p - label_begin); - std::u32string_view label_view(label_begin, label_size); - const bool is_last_label = (p == end); - if (p < end) { - ++p; // skip dot - } - - if (label_size == 0) { - // empty label - } else if (is_ace_prefix(label_view)) { - for (char32_t c : label_view) { - if (c >= 0x80) { - out.clear(); - return false; - } - } - append_ascii_label(out, label_view); - std::string_view puny_segment_ascii( - out.data() + (out.size() - label_size) + 4, label_size - 4); - working.clear(); - if (!ada::idna::punycode_to_utf32(puny_segment_ascii, working)) { - out.clear(); - return false; - } - if (is_ascii(working)) { - out.clear(); - return false; - } - if (!ada::idna::map(working, post_map) || working != post_map) { - out.clear(); - return false; - } - // Mapping must be stable; NFC must not change the mapped form either. - if (!is_ascii(post_map) && !is_already_nfc(post_map)) { - if (!normalize(post_map) || post_map != working) { - out.clear(); - return false; - } - } - if (post_map.empty() || !is_label_valid(post_map)) { - out.clear(); - return false; - } - } else if (is_ascii(label_view)) { - append_ascii_label(out, label_view); - } else { - if (!is_label_valid(label_view)) { - out.clear(); - return false; - } - out.append("xn--"); - if (!ada::idna::utf32_to_punycode(label_view, out)) { - out.clear(); - return false; - } - } - if (!is_last_label) { - out.push_back('.'); - } - } - return true; -} - -std::string to_ascii(std::string_view ut8_string) { - std::string out; - if (!to_ascii(ut8_string, out)) { - return {}; - } - return out; -} -} // namespace ada::idna -/* end file src/to_ascii.cpp */ -/* begin file src/to_unicode.cpp */ -/* begin file include/ada/idna/to_unicode.h */ -#ifndef ADA_IDNA_TO_UNICODE_H -#define ADA_IDNA_TO_UNICODE_H - -#include -#include - - -namespace ada::idna { - -// UTS #46 ToUnicode. Never fails per the standard: on step failure the original -// label is kept. Inputs longer than max_domain_input_bytes are returned -// unchanged as a safety measure under untrusted input. -std::string to_unicode(std::string_view input); - -// Writes into `out`. Returns false only if the input exceeds -// max_domain_input_bytes (out is left empty). Otherwise always returns true -// (ToUnicode does not fail). -[[nodiscard]] bool to_unicode(std::string_view input, std::string& out); - -} // namespace ada::idna - -#endif // ADA_IDNA_TO_UNICODE_H -/* end file include/ada/idna/to_unicode.h */ - -#include -#include - -/* begin file include/idna.h */ -#ifndef ADA_IDNA_H -#define ADA_IDNA_H - -/* begin file include/ada/idna/identifier.h */ -#ifndef ADA_IDNA_IDENTIFIER_H -#define ADA_IDNA_IDENTIFIER_H - -#include -#include - -namespace ada::idna { - -// Verify if it is valid name code point given a Unicode code point and a -// boolean first: If first is true return the result of checking if code point -// is contained in the IdentifierStart set of code points. Otherwise return the -// result of checking if code point is contained in the IdentifierPart set of -// code points. Returns false if the input is empty or the code point is not -// valid. There is minimal Unicode error handling: the input should be valid -// UTF-8. https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point -bool valid_name_code_point(char32_t code_point, bool first); - -} // namespace ada::idna - -#endif -/* end file include/ada/idna/identifier.h */ - -#endif -/* end file include/idna.h */ - -#ifdef ADA_USE_SIMDUTF -#include "simdutf.h" -#endif - -namespace ada::idna { - -[[nodiscard]] bool to_unicode(std::string_view input, std::string& output) { - output.clear(); - if (input.size() > max_domain_input_bytes) { - return false; - } - output.reserve(input.size()); - - size_t label_start = 0; - std::u32string tmp_buffer; - std::u32string post_map; - while (label_start < input.size()) { - size_t loc_dot = input.find('.', label_start); - bool is_last_label = (loc_dot == std::string_view::npos); - size_t label_size = - is_last_label ? input.size() - label_start : loc_dot - label_start; - auto label_view = std::string_view(input.data() + label_start, label_size); - - if (label_view.starts_with("xn--") && ada::idna::is_ascii(label_view)) { - label_view.remove_prefix(4); - tmp_buffer.clear(); - if (ada::idna::punycode_to_utf32(label_view, tmp_buffer)) { - // Per UTS #46, the decoded label must be re-validated. Reject decodings - // that are pure ASCII (xn-- encoding of an ASCII-only label), or whose - // mapping/normalization is not stable, or that fail label validity. - bool accept_decoded = true; - if (ada::idna::is_ascii(tmp_buffer)) { - accept_decoded = false; - } else { - post_map.clear(); - if (!ada::idna::map(tmp_buffer, post_map) || post_map != tmp_buffer) { - accept_decoded = false; - } else if (!ada::idna::normalize(post_map) || - post_map != tmp_buffer || post_map.empty() || - !ada::idna::is_label_valid(post_map)) { - accept_decoded = false; - } - } - - if (accept_decoded) { -#ifdef ADA_USE_SIMDUTF - auto utf8_size = simdutf::utf8_length_from_utf32(tmp_buffer.data(), - tmp_buffer.size()); - size_t old_size = output.size(); - output.resize(old_size + utf8_size); - size_t written = simdutf::convert_utf32_to_utf8( - tmp_buffer.data(), tmp_buffer.size(), output.data() + old_size); - if (written != utf8_size) { - output.resize(old_size); - output.append( - std::string_view(input.data() + label_start, label_size)); - } -#else - auto utf8_size = ada::idna::utf8_length_from_utf32(tmp_buffer.data(), - tmp_buffer.size()); - size_t old_size = output.size(); - output.resize(old_size + utf8_size); - size_t written = ada::idna::utf32_to_utf8( - tmp_buffer.data(), tmp_buffer.size(), output.data() + old_size); - if (written != utf8_size) { - output.resize(old_size); - output.append( - std::string_view(input.data() + label_start, label_size)); - } -#endif - } else { - // ToUnicode never fails. If any step fails, return the original - // input sequence for the label. - output.append( - std::string_view(input.data() + label_start, label_size)); - } - } else { - output.append(std::string_view(input.data() + label_start, label_size)); - } - } else { - output.append(label_view); - } - - if (!is_last_label) { - output.push_back('.'); - } - - label_start += label_size + 1; - } - - return true; -} - -std::string to_unicode(std::string_view input) { - std::string output; - if (!to_unicode(input, output)) { - // Over-limit input: return original (ToUnicode never fails in the sense of - // producing no answer for valid-length inputs). - return std::string(input); - } - return output; -} -} // namespace ada::idna -/* end file src/to_unicode.cpp */ -/* begin file src/identifier.cpp */ - -#include -#include -#include -#include - -/* begin file src/id_tables.cpp */ -// IDNA 17.0.0 -// Identifier range tables are stored in the compressed blob (table_store.hpp). - -// clang-format off -#ifndef ADA_IDNA_IDENTIFIER_TABLES_H -#define ADA_IDNA_IDENTIFIER_TABLES_H -#include - -namespace ada::idna { - -// id_continue / id_start pointers and counts are provided by table_store.hpp. - -} // namespace ada::idna -#endif // ADA_IDNA_IDENTIFIER_TABLES_H -/* end file src/id_tables.cpp */ - -namespace ada::idna { -constexpr bool is_ascii_letter(char32_t c) noexcept { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); -} - -constexpr bool is_ascii_letter_or_digit(char32_t c) noexcept { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9'); -} - -bool valid_name_code_point(char32_t code_point, bool first) { - // https://tc39.es/ecma262/#prod-IdentifierStart - - // Fast paths (no table needed) - if (first && (code_point == U'$' || code_point == U'_' || - is_ascii_letter(code_point))) { - return true; - } - if (!first && (code_point == U'$' || is_ascii_letter_or_digit(code_point))) { - return true; - } - - // Minimal error handling for invalid code point - if (code_point > 0x10FFFF) { - return false; - } - if (code_point >= 0xD800 && code_point <= 0xDFFF) { - return false; - } - - if (!ensure_tables() || id_start == nullptr || id_continue == nullptr) { - return false; - } - - // Slow path: binary search through the appropriate Unicode range table. - // Each entry is a [low, high] inclusive range. - const std::span ranges = - first ? std::span{ada::idna::id_start, - ada::idna::id_start_count} - : std::span{ada::idna::id_continue, - ada::idna::id_continue_count}; - - const auto iter = std::ranges::lower_bound( - ranges, code_point, {}, - [](const auto& range) { return range[1]; }); // project to range-high - - return iter != ranges.end() && code_point >= (*iter)[0]; -} -} // namespace ada::idna -/* end file src/identifier.cpp */ -/* end file src/idna.cpp */ -/* end file src/ada_idna.cpp */ -ADA_POP_DISABLE_WARNINGS - -#include -#include -#include -#if ADA_SSSE3 -#include -#elif ADA_NEON -#include -#elif ADA_SSE2 -#include -#elif ADA_LSX -#include -#elif ADA_RVV -#include -#endif - -#include - -namespace ada::unicode { - -constexpr bool is_tabs_or_newline(char c) noexcept { - return c == '\r' || c == '\n' || c == '\t'; -} - -constexpr uint64_t broadcast(uint8_t v) noexcept { - return 0x101010101010101ull * v; -} - -constexpr bool to_lower_ascii(char* input, size_t length) noexcept { - uint64_t broadcast_80 = broadcast(0x80); - uint64_t broadcast_Ap = broadcast(128 - 'A'); - uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1); - uint64_t non_ascii = 0; - size_t i = 0; - - for (; i + 7 < length; i += 8) { - uint64_t word{}; - memcpy(&word, input + i, sizeof(word)); - non_ascii |= (word & broadcast_80); - word ^= - (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2; - memcpy(input + i, &word, sizeof(word)); - } - if (i < length) { - uint64_t word{}; - memcpy(&word, input + i, length - i); - non_ascii |= (word & broadcast_80); - word ^= - (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2; - memcpy(input + i, &word, length - i); - } - return non_ascii == 0; -} -#if ADA_SSSE3 -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept { - // first check for short strings in which case we do it naively. - if (user_input.size() < 16) { // slow path - return std::ranges::any_of(user_input, is_tabs_or_newline); - } - // fast path for long strings (expected to be common) - // Using SSSE3's _mm_shuffle_epi8 for table lookup (same approach as NEON) - size_t i = 0; - // Lookup table where positions 9, 10, 13 contain their own values - // Everything else is set to 1 so it won't match - const __m128i rnt = - _mm_setr_epi8(1, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 0, 0, 13, 0, 0); - __m128i running = _mm_setzero_si128(); - for (; i + 15 < user_input.size(); i += 16) { - __m128i word = _mm_loadu_si128((const __m128i*)(user_input.data() + i)); - // Shuffle the lookup table using input bytes as indices - __m128i shuffled = _mm_shuffle_epi8(rnt, word); - // Compare: if shuffled value matches input, we found \t, \n, or \r - __m128i matches = _mm_cmpeq_epi8(shuffled, word); - running = _mm_or_si128(running, matches); - } - if (i < user_input.size()) { - __m128i word = _mm_loadu_si128( - (const __m128i*)(user_input.data() + user_input.length() - 16)); - __m128i shuffled = _mm_shuffle_epi8(rnt, word); - __m128i matches = _mm_cmpeq_epi8(shuffled, word); - running = _mm_or_si128(running, matches); - } - return _mm_movemask_epi8(running) != 0; -} -#elif ADA_NEON -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept { - // first check for short strings in which case we do it naively. - if (user_input.size() < 16) { // slow path - return std::ranges::any_of(user_input, is_tabs_or_newline); - } - // fast path for long strings (expected to be common) - size_t i = 0; - /** - * The fastest way to check for `\t` (==9), '\n'(== 10) and `\r` (==13) relies - * on table lookup instruction. We notice that these are all unique numbers - * between 0..15. Let's prepare a special register, where we put '\t' in the - * 9th position, '\n' - 10th and '\r' - 13th. Then we shuffle this register by - * input register. If the input had `\t` in position X then this shuffled - * register will also have '\t' in that position. Comparing input with this - * shuffled register will mark us all interesting characters in the input. - * - * credit for algorithmic idea: @aqrit, credit for description: - * @DenisYaroshevskiy - */ - static uint8_t rnt_array[16] = {1, 0, 0, 0, 0, 0, 0, 0, - 0, 9, 10, 0, 0, 13, 0, 0}; - const uint8x16_t rnt = vld1q_u8(rnt_array); - // m['0xd', '0xa', '0x9'] - uint8x16_t running{0}; - for (; i + 15 < user_input.size(); i += 16) { - uint8x16_t word = vld1q_u8((const uint8_t*)user_input.data() + i); - - running = vorrq_u8(running, vceqq_u8(vqtbl1q_u8(rnt, word), word)); - } - if (i < user_input.size()) { - uint8x16_t word = - vld1q_u8((const uint8_t*)user_input.data() + user_input.length() - 16); - running = vorrq_u8(running, vceqq_u8(vqtbl1q_u8(rnt, word), word)); - } - return vmaxvq_u32(vreinterpretq_u32_u8(running)) != 0; -} -#elif ADA_SSE2 -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept { - // first check for short strings in which case we do it naively. - if (user_input.size() < 16) { // slow path - return std::ranges::any_of(user_input, is_tabs_or_newline); - } - // fast path for long strings (expected to be common) - size_t i = 0; - const __m128i mask1 = _mm_set1_epi8('\r'); - const __m128i mask2 = _mm_set1_epi8('\n'); - const __m128i mask3 = _mm_set1_epi8('\t'); - // If we supported SSSE3, we could use the algorithm that we use for NEON. - __m128i running{0}; - for (; i + 15 < user_input.size(); i += 16) { - __m128i word = _mm_loadu_si128((const __m128i*)(user_input.data() + i)); - running = _mm_or_si128( - _mm_or_si128(running, _mm_or_si128(_mm_cmpeq_epi8(word, mask1), - _mm_cmpeq_epi8(word, mask2))), - _mm_cmpeq_epi8(word, mask3)); - } - if (i < user_input.size()) { - __m128i word = _mm_loadu_si128( - (const __m128i*)(user_input.data() + user_input.length() - 16)); - running = _mm_or_si128( - _mm_or_si128(running, _mm_or_si128(_mm_cmpeq_epi8(word, mask1), - _mm_cmpeq_epi8(word, mask2))), - _mm_cmpeq_epi8(word, mask3)); - } - return _mm_movemask_epi8(running) != 0; -} -#elif ADA_LSX -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept { - // first check for short strings in which case we do it naively. - if (user_input.size() < 16) { // slow path - return std::ranges::any_of(user_input, is_tabs_or_newline); - } - // fast path for long strings (expected to be common) - size_t i = 0; - const __m128i mask1 = __lsx_vrepli_b('\r'); - const __m128i mask2 = __lsx_vrepli_b('\n'); - const __m128i mask3 = __lsx_vrepli_b('\t'); - // If we supported SSSE3, we could use the algorithm that we use for NEON. - __m128i running{0}; - for (; i + 15 < user_input.size(); i += 16) { - __m128i word = __lsx_vld((const __m128i*)(user_input.data() + i), 0); - running = __lsx_vor_v( - __lsx_vor_v(running, __lsx_vor_v(__lsx_vseq_b(word, mask1), - __lsx_vseq_b(word, mask2))), - __lsx_vseq_b(word, mask3)); - } - if (i < user_input.size()) { - __m128i word = __lsx_vld( - (const __m128i*)(user_input.data() + user_input.length() - 16), 0); - running = __lsx_vor_v( - __lsx_vor_v(running, __lsx_vor_v(__lsx_vseq_b(word, mask1), - __lsx_vseq_b(word, mask2))), - __lsx_vseq_b(word, mask3)); - } - if (__lsx_bz_v(running)) return false; - return true; -} -#elif ADA_RVV -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept { - uint8_t* src = (uint8_t*)user_input.data(); - for (size_t vl, n = user_input.size(); n > 0; n -= vl, src += vl) { - vl = __riscv_vsetvl_e8m1(n); - vuint8m1_t v = __riscv_vle8_v_u8m1(src, vl); - vbool8_t m1 = __riscv_vmseq(v, '\r', vl); - vbool8_t m2 = __riscv_vmseq(v, '\n', vl); - vbool8_t m3 = __riscv_vmseq(v, '\t', vl); - vbool8_t m = __riscv_vmor(__riscv_vmor(m1, m2, vl), m3, vl); - long idx = __riscv_vfirst(m, vl); - if (idx >= 0) return true; - } - return false; -} -#else -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept { - auto has_zero_byte = [](uint64_t v) { - return ((v - 0x0101010101010101) & ~(v) & 0x8080808080808080); - }; - size_t i = 0; - uint64_t mask1 = broadcast('\r'); - uint64_t mask2 = broadcast('\n'); - uint64_t mask3 = broadcast('\t'); - uint64_t running{0}; - for (; i + 7 < user_input.size(); i += 8) { - uint64_t word{}; - memcpy(&word, user_input.data() + i, sizeof(word)); - uint64_t xor1 = word ^ mask1; - uint64_t xor2 = word ^ mask2; - uint64_t xor3 = word ^ mask3; - running |= has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3); - } - if (i < user_input.size()) { - uint64_t word{}; - memcpy(&word, user_input.data() + i, user_input.size() - i); - uint64_t xor1 = word ^ mask1; - uint64_t xor2 = word ^ mask2; - uint64_t xor3 = word ^ mask3; - running |= has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3); - } - return running; -} -#endif - -// A forbidden host code point is U+0000 NULL, U+0009 TAB, U+000A LF, U+000D CR, -// U+0020 SPACE, U+0023 (#), U+002F (/), U+003A (:), U+003C (<), U+003E (>), -// U+003F (?), U+0040 (@), U+005B ([), U+005C (\), U+005D (]), U+005E (^), or -// U+007C (|). -constexpr static std::array is_forbidden_host_code_point_table = - []() consteval { - std::array result{}; - for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<', - '>', '?', '@', '[', '\\', ']', '^', '|'}) { - result[c] = true; - } - return result; - }(); - -ada_really_inline constexpr bool is_forbidden_host_code_point( - const char c) noexcept { - return is_forbidden_host_code_point_table[uint8_t(c)]; -} - -constexpr static std::array is_forbidden_domain_code_point_table = - []() consteval { - std::array result{}; - for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<', - '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) { - result[c] = true; - } - for (uint8_t c = 0; c <= 32; c++) { - result[c] = true; - } - for (size_t c = 127; c < 256; c++) { - result[c] = true; - } - return result; - }(); - -static_assert(sizeof(is_forbidden_domain_code_point_table) == 256); - -ada_really_inline constexpr bool is_forbidden_domain_code_point( - const char c) noexcept { - return is_forbidden_domain_code_point_table[uint8_t(c)]; -} - -ada_really_inline constexpr bool contains_forbidden_domain_code_point( - const char* input, size_t length) noexcept { - size_t i = 0; - uint8_t accumulator{}; - for (; i + 4 <= length; i += 4) { - accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i])]; - accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 1])]; - accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 2])]; - accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 3])]; - } - for (; i < length; i++) { - accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i])]; - } - return accumulator; -} - -constexpr static std::array - is_forbidden_domain_code_point_table_or_upper = []() consteval { - std::array result{}; - for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<', - '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) { - result[c] = 1; - } - for (uint8_t c = 'A'; c <= 'Z'; c++) { - result[c] = 2; - } - for (uint8_t c = 0; c <= 32; c++) { - result[c] = 1; - } - for (size_t c = 127; c < 256; c++) { - result[c] = 1; - } - return result; - }(); - -ada_really_inline constexpr uint8_t -contains_forbidden_domain_code_point_or_upper(const char* input, - size_t length) noexcept { - size_t i = 0; - uint8_t accumulator{}; - for (; i + 4 <= length; i += 4) { - accumulator |= - is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i])]; - accumulator |= - is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i + 1])]; - accumulator |= - is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i + 2])]; - accumulator |= - is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i + 3])]; - } - for (; i < length; i++) { - accumulator |= - is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i])]; - } - return accumulator; -} - -// std::isalnum(c) || c == '+' || c == '-' || c == '.') is true for -constexpr static std::array is_alnum_plus_table = []() consteval { - std::array result{}; - for (size_t c = 0; c < 256; c++) { - result[c] = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || c == '+' || c == '-' || c == '.'; - } - return result; -}(); - -ada_really_inline constexpr bool is_alnum_plus(const char c) noexcept { - return is_alnum_plus_table[uint8_t(c)]; - // A table is almost surely much faster than the - // following under most compilers: return - // return (std::isalnum(c) || c == '+' || c == '-' || c == '.'); -} - -ada_really_inline constexpr bool is_ascii_hex_digit(const char c) noexcept { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f'); -} - -ada_really_inline constexpr bool is_ascii_digit(const char c) noexcept { - // An ASCII digit is a code point in the range U+0030 (0) to U+0039 (9), - // inclusive. - return (c >= '0' && c <= '9'); -} - -ada_really_inline constexpr bool is_ascii(const char32_t c) noexcept { - // If code point is between U+0000 and U+007F inclusive, then return true. - return c <= 0x7F; -} - -ada_really_inline constexpr bool is_c0_control_or_space(const char c) noexcept { - return (unsigned char)c <= ' '; -} - -ada_really_inline constexpr bool is_ascii_tab_or_newline( - const char c) noexcept { - return c == '\t' || c == '\n' || c == '\r'; -} - -constexpr std::string_view table_is_double_dot_path_segment[] = { - "..", "%2e.", ".%2e", "%2e%2e"}; - -ada_really_inline constexpr bool is_double_dot_path_segment( - std::string_view input) noexcept { - // This will catch most cases: - // The length must be 2,4 or 6. - // We divide by two and require - // that the result be between 1 and 3 inclusively. - uint64_t half_length = uint64_t(input.size()) / 2; - if (half_length - 1 > 2) { - return false; - } - // We have a string of length 2, 4 or 6. - // We now check the first character: - if ((input[0] != '.') && (input[0] != '%')) { - return false; - } - // We are unlikely the get beyond this point. - int hash_value = (input.size() + (unsigned)(input[0])) & 3; - const std::string_view target = table_is_double_dot_path_segment[hash_value]; - if (target.size() != input.size()) { - return false; - } - // We almost never get here. - // Optimizing the rest is relatively unimportant. - auto prefix_equal_unsafe = [](std::string_view a, std::string_view b) { - uint16_t A, B; - memcpy(&A, a.data(), sizeof(A)); - memcpy(&B, b.data(), sizeof(B)); - return A == B; - }; - if (!prefix_equal_unsafe(input, target)) { - return false; - } - for (size_t i = 2; i < input.size(); i++) { - char c = input[i]; - if ((uint8_t((c | 0x20) - 0x61) <= 25 ? (c | 0x20) : c) != target[i]) { - return false; - } - } - return true; - // The above code might be a bit better than the code below. Compilers - // are not stupid and may use the fact that these strings have length 2,4 and - // 6 and other tricks. - // return input == ".." || - // input == ".%2e" || input == ".%2E" || - // input == "%2e." || input == "%2E." || - // input == "%2e%2e" || input == "%2E%2E" || input == "%2E%2e" || input == - // "%2e%2E"; -} - -ada_really_inline constexpr bool is_single_dot_path_segment( - std::string_view input) noexcept { - return input == "." || input == "%2e" || input == "%2E"; -} - -ada_really_inline constexpr bool is_lowercase_hex(const char c) noexcept { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); -} - -constexpr static char hex_to_binary_table[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, - 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15}; -unsigned constexpr convert_hex_to_binary(const char c) noexcept { - return hex_to_binary_table[c - '0']; -} - -std::string percent_decode(const std::string_view input, size_t first_percent) { - // next line is for safety only, we expect users to avoid calling - // percent_decode when first_percent is outside the range. - if (first_percent == std::string_view::npos) { - return std::string(input); - } - std::string dest; - dest.reserve(input.length()); - dest.append(input.substr(0, first_percent)); - const char* pointer = input.data() + first_percent; - const char* end = input.data() + input.size(); - // Optimization opportunity: if the following code gets - // called often, it can be optimized quite a bit. - while (pointer < end) { - const char ch = pointer[0]; - size_t remaining = end - pointer - 1; - if (ch != '%' || remaining < 2 || - ( // ch == '%' && // It is unnecessary to check that ch == '%'. - (!is_ascii_hex_digit(pointer[1]) || - !is_ascii_hex_digit(pointer[2])))) { - dest += ch; - pointer++; - } else { - unsigned a = convert_hex_to_binary(pointer[1]); - unsigned b = convert_hex_to_binary(pointer[2]); - char c = static_cast(a * 16 + b); - dest += c; - pointer += 3; - } - } - return dest; -} - -// 0..15 for hex digits, 0xFF otherwise - validate and decode with two loads. -constexpr static std::array unhex_table = []() consteval { - std::array t{}; - for (size_t i = 0; i < 256; ++i) { - t[i] = 0xFF; - } - for (uint8_t i = 0; i < 10; ++i) { - t[static_cast('0') + i] = i; - } - for (uint8_t i = 0; i < 6; ++i) { - t[static_cast('A') + i] = static_cast(10 + i); - t[static_cast('a') + i] = static_cast(10 + i); - } - return t; -}(); - -std::string form_urlencoded_decode(const std::string_view input) { - const size_t len = input.size(); - if (len == 0) [[unlikely]] { - return {}; - } - - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - const char* const src = input.data(); - const char* const end = src + len; - const char* p = src; - - // Advance over the untransformed prefix. - while (p < end && *p != '+' && *p != '%') { - ++p; - } - if (p == end) { - return std::string(input); - } - - // Output is always at most as long as the input: write into a single - // pre-sized buffer, then shrink to the final length. - std::string out(len, '\0'); - char* d = out.data(); - char* const d0 = d; - - const size_t prefix = static_cast(p - src); - std::memcpy(d, src, prefix); - d += prefix; - - while (p < end) { - const char c = *p; - if (c == '+') { - *d++ = ' '; - ++p; - } else if (c == '%') { - // Decode runs of valid %XX tightly (common for nested URL query values). - while (p + 2 < end && *p == '%') { - const uint8_t hi = unhex_table[static_cast(p[1])]; - const uint8_t lo = unhex_table[static_cast(p[2])]; - if ((hi | lo) >= 16) { - break; - } - *d++ = static_cast((hi << 4) | lo); - p += 3; - } - if (p < end && *p == '%') { - // Invalid escape: copy '%' literally and continue. - *d++ = *p++; - } - } else { - // Copy a plain run until the next '+' or '%'. - const char* start = p; - ++p; - while (p < end && *p != '+' && *p != '%') { - ++p; - } - const size_t n = static_cast(p - start); - std::memcpy(d, start, n); - d += n; - } - } - - out.resize(static_cast(d - d0)); - return out; -} - -std::string percent_encode(const std::string_view input, - const uint8_t character_set[]) { - auto pointer = std::ranges::find_if(input, [character_set](const char c) { - return character_sets::bit_at(character_set, c); - }); - // Optimization: Don't iterate if percent encode is not required - if (pointer == input.end()) { - return std::string(input); - } - - std::string result; - result.reserve(input.length()); // in the worst case, percent encoding might - // produce 3 characters. - result.append(input.substr(0, std::distance(input.begin(), pointer))); - - for (; pointer != input.end(); pointer++) { - if (character_sets::bit_at(character_set, *pointer)) { - result.append(character_sets::hex + uint8_t(*pointer) * 4, 3); - } else { - result += *pointer; - } - } - - return result; -} - -template -bool percent_encode(const std::string_view input, const uint8_t character_set[], - std::string& out) { - ada_log("percent_encode ", input, " to output string while ", - append ? "appending" : "overwriting"); - auto pointer = std::ranges::find_if(input, [character_set](const char c) { - return character_sets::bit_at(character_set, c); - }); - ada_log("percent_encode done checking, moved to ", - std::distance(input.begin(), pointer)); - - // Optimization: Don't iterate if percent encode is not required - if (pointer == input.end()) { - ada_log("percent_encode encoding not needed."); - return false; - } - if constexpr (!append) { - out.clear(); - } - ada_log("percent_encode appending ", std::distance(input.begin(), pointer), - " bytes"); - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - out.append(input.data(), std::distance(input.begin(), pointer)); - ada_log("percent_encode processing ", std::distance(pointer, input.end()), - " bytes"); - for (; pointer != input.end(); pointer++) { - if (character_sets::bit_at(character_set, *pointer)) { - out.append(character_sets::hex + uint8_t(*pointer) * 4, 3); - } else { - out += *pointer; - } - } - return true; -} - -bool to_ascii(std::optional& out, const std::string_view plain, - size_t first_percent) { - std::string percent_decoded_buffer; - std::string_view input = plain; - if (first_percent != std::string_view::npos) { - percent_decoded_buffer = unicode::percent_decode(plain, first_percent); - input = percent_decoded_buffer; - } - // input is a non-empty UTF-8 string, must be percent decoded - std::string idna_ascii = ada::idna::to_ascii(input); - if (idna_ascii.empty() || contains_forbidden_domain_code_point( - idna_ascii.data(), idna_ascii.size())) { - return false; - } - out = std::move(idna_ascii); - return true; -} - -std::string percent_encode(const std::string_view input, - const uint8_t character_set[], size_t index) { - std::string out; - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - out.append(input.data(), index); - auto pointer = input.begin() + index; - for (; pointer != input.end(); pointer++) { - if (character_sets::bit_at(character_set, *pointer)) { - out.append(character_sets::hex + uint8_t(*pointer) * 4, 3); - } else { - out += *pointer; - } - } - return out; -} - -} // namespace ada::unicode -/* end file src/unicode.cpp */ -/* begin file src/serializers.cpp */ - -#include -#include -#include -#include - -namespace ada::serializers { -namespace { - -// Digit pair LUT for fast decimal write: index 0..99 -> two chars. -constexpr std::array make_digit_pairs() noexcept { - std::array t{}; - for (size_t i = 0; i < 100; ++i) { - t[i * 2] = static_cast('0' + i / 10); - t[i * 2 + 1] = static_cast('0' + i % 10); - } - return t; -} - -constexpr auto digit_pairs = make_digit_pairs(); - -ada_really_inline char* write_u8(char* p, uint8_t v) noexcept { - if (v < 10) { - *p++ = static_cast('0' + v); - } else if (v < 100) { - std::memcpy(p, &digit_pairs[static_cast(v) * 2], 2); - p += 2; - } else { - *p++ = static_cast('0' + v / 100); - std::memcpy(p, &digit_pairs[static_cast(v % 100) * 2], 2); - p += 2; - } - return p; -} - -ada_really_inline char* write_hex_u16(char* p, uint16_t v) noexcept { - static constexpr char kHex[] = "0123456789abcdef"; - if (v >= 0x1000) { - *p++ = kHex[(v >> 12) & 0xf]; - *p++ = kHex[(v >> 8) & 0xf]; - *p++ = kHex[(v >> 4) & 0xf]; - *p++ = kHex[v & 0xf]; - } else if (v >= 0x100) { - *p++ = kHex[(v >> 8) & 0xf]; - *p++ = kHex[(v >> 4) & 0xf]; - *p++ = kHex[v & 0xf]; - } else if (v >= 0x10) { - *p++ = kHex[(v >> 4) & 0xf]; - *p++ = kHex[v & 0xf]; - } else { - *p++ = kHex[v & 0xf]; - } - return p; -} - -} // namespace - -void find_longest_sequence_of_ipv6_pieces( - const std::array& address, size_t& compress, - size_t& compress_length) noexcept { - compress = 0; - compress_length = 0; - size_t i = 0; - while (i < 8) { - if (address[i] != 0) { - ++i; - continue; - } - size_t next = i + 1; - while (next < 8 && address[next] == 0) { - ++next; - } - const size_t count = next - i; - if (count > compress_length) { - compress_length = count; - compress = i; - } - i = next; - } -} - -std::string ipv6(const std::array& address) { - size_t compress = 0; - size_t compress_length = 0; - find_longest_sequence_of_ipv6_pieces(address, compress, compress_length); - - // Skip compression when the longest zero run is a single piece. - if (compress_length <= 1) { - compress = compress_length = 8; - } - - // Max: '[' + 8*4 hex + 7 ':' + ']' = 41 - std::string output(4 * 8 + 7 + 2, '\0'); - char* point = output.data(); - *point++ = '['; - size_t piece_index = 0; - while (true) { - if (piece_index == compress) { - *point++ = ':'; - // If we skip a value initially, we need to write '::'. - if (piece_index == 0) { - *point++ = ':'; - } - piece_index += compress_length; - if (piece_index == 8) { - break; - } - } - point = write_hex_u16(point, address[piece_index]); - ++piece_index; - if (piece_index == 8) { - break; - } - *point++ = ':'; - } - *point++ = ']'; - output.resize(static_cast(point - output.data())); - return output; -} - -std::string ipv4(const uint64_t address) { - std::string output(15, '\0'); - char* point = output.data(); - point = write_u8(point, static_cast(address >> 24)); - *point++ = '.'; - point = write_u8(point, static_cast(address >> 16)); - *point++ = '.'; - point = write_u8(point, static_cast(address >> 8)); - *point++ = '.'; - point = write_u8(point, static_cast(address)); - output.resize(static_cast(point - output.data())); - return output; -} - -} // namespace ada::serializers -/* end file src/serializers.cpp */ -/* begin file src/implementation.cpp */ - -#include -#include -#include -#include - - -namespace ada { - -static std::atomic max_input_length_{ - std::numeric_limits::max()}; - -void set_max_input_length(uint32_t length) { - max_input_length_.store(length, std::memory_order_relaxed); -} - -uint32_t get_max_input_length() { - return max_input_length_.load(std::memory_order_relaxed); -} - -namespace { - -// @private -// Fast-path validator for can_parse. -// -// Validates absolute special (non-file) URLs without constructing any -// url_aggregator object and without running the state machine. -// Performs a single forward scan over the input bytes. -// -// Returns: -// true -- URL is structurally valid -// false -- URL is definitely invalid -// nullopt -- edge case; fall through to the full parser -// (credentials, IDNA, IPv4/6, tabs/newlines, relative URLs, ...) -std::optional try_can_parse_absolute_fast( - std::string_view input) noexcept { - const uint8_t* b = reinterpret_cast(input.data()); - size_t len = input.size(); - - // -- Inline C0 whitespace trim (no allocation) -------------------------- - // Note: \t (0x09), \n (0x0a), \r (0x0d) are all <= 0x20, so any - // leading/trailing tabs or newlines are correctly stripped here, matching - // the WHATWG spec's "remove leading/trailing C0 control and space" step. - while (len > 0 && b[0] <= 0x20) { - b++; - len--; - } - while (len > 0 && b[len - 1] <= 0x20) { - len--; - } - if (len == 0) return false; - - // -- Scheme detection ----------------------------------------------------- - // Fast path for HTTP and HTTPS (covers ~90%+ of real-world URLs). - // Avoids the general scheme loop, buffer copy, and perfect hash lookup. - // We know HTTP and HTTPS are special non-file schemes, so no further - // scheme_type checks are needed on the fast path -- only `pos` matters. - size_t pos; - - if (len >= 7 && (b[0] | 0x20) == 'h' && (b[1] | 0x20) == 't' && - (b[2] | 0x20) == 't' && (b[3] | 0x20) == 'p') { - if (b[4] == ':' && b[5] == '/' && b[6] == '/') { - pos = 7; - goto skip_extra_slashes; - } - if (len >= 8 && (b[4] | 0x20) == 's' && b[5] == ':' && b[6] == '/' && - b[7] == '/') { - pos = 8; - goto skip_extra_slashes; - } - // Fall through: could be "httpe://", tabs in scheme, etc. - } - - { - // General scheme detection for ws, wss, ftp, and edge cases. - if (!checkers::is_alpha(static_cast(b[0]))) return false; - - // Scan for ':' within the first 7 bytes. All special schemes are <= 5 - // chars ("https"), so any URL whose first ':' is beyond byte 6 is either - // non-special or relative -- both require the full parser. - size_t colon_pos = 0; - for (size_t i = 1;; ++i) { - if (i >= 7 || i >= len) return std::nullopt; - const char c = static_cast(b[i]); - if (c == ':') { - colon_pos = i; - break; - } - // Tabs/newlines in the scheme require the full parser to strip them. - if (c == '\t' || c == '\n' || c == '\r') return std::nullopt; - if (!unicode::is_alnum_plus(c)) return false; - } - - // Lowercase scheme bytes inline and classify via the existing perfect - // hash. - char scheme_buf[6]; - scheme_buf[0] = static_cast(b[0] | 0x20); - for (size_t i = 1; i < colon_pos; ++i) - scheme_buf[i] = static_cast(b[i] | 0x20); - - const ada::scheme::type scheme_type = - ada::scheme::get_scheme_type({scheme_buf, colon_pos}); - - // Only handle special, non-file schemes. - if (scheme_type == ada::scheme::NOT_SPECIAL) return std::nullopt; - if (scheme_type == ada::scheme::FILE) return std::nullopt; - - // Per WHATWG, special URLs don't require "//": "http:example.com" is valid - // (SPECIAL_AUTHORITY_IGNORE_SLASHES just skips leading slashes and - // proceeds to AUTHORITY). Defer to the inline fallback for any input - // without "://". - pos = colon_pos + 1; - if (pos + 2 > len || b[pos] != '/' || b[pos + 1] != '/') { - return std::nullopt; - } - pos += 2; - } - -skip_extra_slashes: - // SPECIAL_AUTHORITY_IGNORE_SLASHES: the full parser skips any additional - // leading '/' or '\' after the initial "//". Mirror that here so we don't - // mis-identify the host as empty when there are extra slashes. - while (pos < len && (b[pos] == '/' || b[pos] == '\\')) { - ++pos; - } - - // Early IPv6 bail-out: if the authority starts with '[', it's an IPv6 - // literal which requires the full parser. Checking here avoids scanning - // the entire bracketed address only to bail out afterward. - if (pos < len && b[pos] == '[') return std::nullopt; - - // -- Merged authority + host scan ------------------------------------------ - // A single forward pass over the authority bytes that simultaneously: - // - finds the authority end and port colon - // - validates host characters (forbidden domain code points) - // - tracks IPv4 indicators (all-decimal-dots, last non-dot char) - // - detects xn-- prefixes (IDNA punycode) - // - detects tabs/newlines (which require the full parser to strip) - // This replaces 4 separate scans over the host bytes. - const size_t auth_start = pos; - size_t auth_end = pos; - size_t port_colon = SIZE_MAX; - bool all_dec_dots = true; - uint8_t last_non_dot = 0; - - for (; auth_end < len; ++auth_end) { - const uint8_t c = b[auth_end]; - - // Non-ASCII -> needs IDNA processing -> full parser. - if (c >= 0x80) return std::nullopt; - - // Authority delimiters. - if (c == '/' || c == '?' || c == '#' || c == '\\') break; - - // Port separator. - if (c == ':') { - if (port_colon == SIZE_MAX) port_colon = auth_end; - continue; - } - - // Credentials or percent-encoding -> full parser. - if (c == '@' || c == '%') return std::nullopt; - - // Tabs/newlines anywhere in the authority require the full parser to - // strip them before validation. Without this, a tab in the port (e.g. - // "http://host:8\t0/") would be mis-rejected by port validation. - if (c == '\t' || c == '\n' || c == '\r') return std::nullopt; - - // Skip remaining host-specific checks for port bytes. Port digits are - // validated separately below, and no forbidden-domain-code-point check - // is needed on port characters. - if (port_colon != SIZE_MAX) continue; - - // -- Host byte validation (inlined) ------------------------------------ - // Forbidden domain code points that are not already caught above: - // C0 controls and space (0x00-0x20), DEL (0x7F), <, >, [, ], ^, |. - // At this stage, the input may still be userinfo or be normalized later - // (e.g., percent-encoded), so we do not reject here and defer to the - // parser. Characters already caught: >= 0x80 (non-ASCII), '/' '?' '#' '\\' - // (delimiters), ':' (port), '@' '%' (bail), '\t' '\n' '\r' (bail). - if (c <= 0x20 || c == 0x7F || c == '<' || c == '>' || c == '[' || - c == ']' || c == '^' || c == '|') { - return std::nullopt; - } - - // Track whether host is all decimal digits and dots (potential IPv4). - if (c != '.' && (c < '0' || c > '9')) all_dec_dots = false; - - // Track last non-dot character for the IPv4 hex/octal heuristic. - if (c != '.') last_non_dot = c; - - // Detect xn-- prefix inline (IDNA punycode -> needs full parser). - // Checking at every position mirrors the original behavior: any - // occurrence of "xn--" in the host (not just at label boundaries) - // triggers a bail-out to the full IDNA validator. - if ((c | 0x20) == 'x' && auth_end + 4 <= len && - (b[auth_end + 1] | 0x20) == 'n' && b[auth_end + 2] == '-' && - b[auth_end + 3] == '-') { - return std::nullopt; - } - } - - const size_t host_end = (port_colon != SIZE_MAX) ? port_colon : auth_end; - - // Empty host is invalid for special URLs. - if (auth_start == host_end) return false; - - // -- IPv4 handling --------------------------------------------------------- - const char* host_ptr = reinterpret_cast(b + auth_start); - const size_t host_len = host_end - auth_start; - - if (all_dec_dots) { - // Host is all decimal digits and dots -> try the fast IPv4 parser. - if (checkers::try_parse_ipv4_fast({host_ptr, host_len}) != - checkers::ipv4_fast_fail) { - // Valid decimal IPv4 host. Do NOT return true yet: the port still - // needs to be validated below before we can declare the URL valid. - goto validate_port; - } - // Fast IPv4 parsing failed (e.g. host is ".", "..", "1.2.3.500"). - // Such hosts may still be valid domain names; defer to the full parser. - return std::nullopt; - } - - // Last-significant-character heuristic for non-decimal IPv4 (hex/octal): - // if the last non-dot char is a digit, 'a'-'f', or 'x' the host might be - // an IPv4 address that the fast path can't validate -- fall through. - // last_non_dot was tracked during the authority scan above. - { - const uint8_t lc = last_non_dot | 0x20; - if ((last_non_dot >= '0' && last_non_dot <= '9') || - (lc >= 'a' && lc <= 'f') || lc == 'x') { - return std::nullopt; - } - } - - // -- Port validation ------------------------------------------------------- -validate_port: - if (port_colon != SIZE_MAX) { - const uint8_t* pp = b + port_colon + 1; - size_t pl = auth_end - port_colon - 1; - if (pl > 0) { - // Strip leading zeros: "0000001" == 1, "0000000000000" == 0, both valid. - // Only the significant digits count toward the 5-digit maximum. - while (pl > 0 && *pp == '0') { - ++pp; - --pl; - } - if (pl > 5) return false; // significant digits > 99999 - uint32_t pv = 0; - for (size_t i = 0; i < pl; ++i) { - if (pp[i] < '0' || pp[i] > '9') return false; - pv = pv * 10 + (pp[i] - '0'); - } - if (pv > 65535) return false; - } - } - - // Path, query, and fragment are structurally always valid for can_parse -- - // the parser would encode whatever is there. - return true; -} - -} // namespace - -template -ada_warn_unused tl::expected parse( - std::string_view input, const result_type* base_url) { - result_type u = ada::parser::parse_url_impl(input, base_url); - if (!u.is_valid) { - return tl::unexpected(errors::type_error); - } - return u; -} - -template ada::result parse(std::string_view input, - const url* base_url = nullptr); -template ada::result parse( - std::string_view input, const url_aggregator* base_url = nullptr); - -std::string href_from_file(std::string_view input) { - // Match ada::parse / setters: refuse inputs that already exceed the limit. - // Path percent-encoding can still expand the result, so we also check the - // final href below. - const uint32_t max_length = ada::get_max_input_length(); - if (input.size() > max_length) { - return {}; - } - - // This is going to be much faster than constructing a URL. - std::string tmp_buffer; - std::string_view internal_input; - if (unicode::has_tabs_or_newline(input)) { - tmp_buffer = input; - helpers::remove_ascii_tab_or_newline(tmp_buffer); - internal_input = tmp_buffer; - } else { - internal_input = input; - } - std::string path; - if (internal_input.empty()) { - path = "/"; - } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) { - helpers::parse_prepared_path(internal_input.substr(1), - ada::scheme::type::FILE, path); - } else { - helpers::parse_prepared_path(internal_input, ada::scheme::type::FILE, path); - } - std::string result = "file://" + path; - if (result.size() > max_length) { - return {}; - } - return result; -} - -bool can_parse(std::string_view input, const std::string_view* base_input) { - // Must match parse().has_value(), including post-normalization max length. - // Percent-encoding expands a byte by at most 3x, but IDNA expands more: a - // 3-byte UTF-8 label such as U+337F becomes the 17-byte "xn--6oqv20b1zgzxr", - // so a dotted host sustains 4.5x. When the input (plus base, if any) fits in - // max_length/5, the normalized href cannot exceed max_length, so - // validation-only parsing (store_values=false) is safe. - - // Hot path first: absolute special URLs, no base. Avoid loading max_length - // until we need it (common absolute-fast true/false cases). - if (base_input == nullptr) { - if (const auto r = try_can_parse_absolute_fast(input)) { - if (!*r) { - return false; - } - // size <= max/5 => normalized href cannot exceed max (4.5x expansion). - // Check this first: default max is ~4GB so almost all URLs return true. - const uint32_t max_length = ada::get_max_input_length(); - if (input.size() <= static_cast(max_length) / 5) { - return true; - } - if (input.size() > max_length) { - return false; - } - return ada::parser::parse_url_impl(input, - nullptr) - .is_valid; - } - } - - const uint32_t max_length = ada::get_max_input_length(); - if (input.size() > max_length) { - return false; - } - if (base_input != nullptr && base_input->size() > max_length) { - return false; - } - - // Relative resolution combines base + input; bound the sum so 4.5x expansion - // of either side cannot push the final href past max_length. - const size_t combined = - input.size() + (base_input == nullptr ? 0 : base_input->size()); - const bool size_safe = combined <= static_cast(max_length) / 5; - - if (size_safe) { - // Validation-only: no buffer build, host still fully checked. - ada::url_aggregator base_agg; - ada::url_aggregator* base_ptr = nullptr; - if (base_input != nullptr) { - base_agg = ada::parser::parse_url_impl( - *base_input, nullptr); - if (!base_agg.is_valid) { - return false; - } - base_ptr = &base_agg; - } - return ada::parser::parse_url_impl(input, - base_ptr) - .is_valid; - } - - // Near the limit: full parse so post-normalization length matches parse(). - if (base_input == nullptr) { - return ada::parser::parse_url_impl(input, - nullptr) - .is_valid; - } - ada::url_aggregator base_agg = - ada::parser::parse_url_impl(*base_input, - nullptr); - if (!base_agg.is_valid) { - return false; - } - return ada::parser::parse_url_impl(input, - &base_agg) - .is_valid; -} - -ada_warn_unused std::string_view to_string(ada::encoding_type type) { - switch (type) { - case ada::encoding_type::UTF8: - return "UTF-8"; - case ada::encoding_type::UTF_16LE: - return "UTF-16LE"; - case ada::encoding_type::UTF_16BE: - return "UTF-16BE"; - default: - unreachable(); - } -} - -} // namespace ada -/* end file src/implementation.cpp */ -/* begin file src/helpers.cpp */ -#include -#include -#include - - -#if ADA_SSSE3 -#include -#endif - -namespace ada::helpers { - -template -void encode_json(std::string_view view, out_iter out) { - // trivial implementation. could be faster. - const char* hexvalues = - "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; - for (uint8_t c : view) { - if (c == '\\') { - *out++ = '\\'; - *out++ = '\\'; - } else if (c == '"') { - *out++ = '\\'; - *out++ = '"'; - } else if (c <= 0x1f) { - *out++ = '\\'; - *out++ = 'u'; - *out++ = '0'; - *out++ = '0'; - *out++ = hexvalues[2 * c]; - *out++ = hexvalues[2 * c + 1]; - } else { - *out++ = c; - } - } -} - -ada_unused std::string get_state(ada::state s) { - switch (s) { - case ada::state::AUTHORITY: - return "Authority"; - case ada::state::SCHEME_START: - return "Scheme Start"; - case ada::state::SCHEME: - return "Scheme"; - case ada::state::HOST: - return "Host"; - case ada::state::NO_SCHEME: - return "No Scheme"; - case ada::state::FRAGMENT: - return "Fragment"; - case ada::state::RELATIVE_SCHEME: - return "Relative Scheme"; - case ada::state::RELATIVE_SLASH: - return "Relative Slash"; - case ada::state::FILE: - return "File"; - case ada::state::FILE_HOST: - return "File Host"; - case ada::state::FILE_SLASH: - return "File Slash"; - case ada::state::PATH_OR_AUTHORITY: - return "Path or Authority"; - case ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES: - return "Special Authority Ignore Slashes"; - case ada::state::SPECIAL_AUTHORITY_SLASHES: - return "Special Authority Slashes"; - case ada::state::SPECIAL_RELATIVE_OR_AUTHORITY: - return "Special Relative or Authority"; - case ada::state::QUERY: - return "Query"; - case ada::state::PATH: - return "Path"; - case ada::state::PATH_START: - return "Path Start"; - case ada::state::OPAQUE_PATH: - return "Opaque Path"; - case ada::state::PORT: - return "Port"; - default: - return "unknown state"; - } -} - -ada_really_inline std::optional prune_hash( - std::string_view& input) noexcept { - // compiles down to 20--30 instructions including a class to memchr (C - // function). this function should be quite fast. - size_t location_of_first = input.find('#'); - if (location_of_first == std::string_view::npos) { - return std::nullopt; - } - std::string_view hash = input; - hash.remove_prefix(location_of_first + 1); - input.remove_suffix(input.size() - location_of_first); - return hash; -} - -ada_really_inline bool shorten_path(std::string& path, ada::scheme::type type) { - // Let path be url's path. - // If url's scheme is "file", path's size is 1, and path[0] is a normalized - // Windows drive letter, then return. - if (type == ada::scheme::type::FILE && - path.find('/', 1) == std::string_view::npos && !path.empty()) { - if (checkers::is_normalized_windows_drive_letter( - helpers::substring(path, 1))) { - return false; - } - } - - // Remove path's last item, if any. - size_t last_delimiter = path.rfind('/'); - if (last_delimiter != std::string::npos) { - path.erase(last_delimiter); - return true; - } - - return false; -} - -ada_really_inline bool shorten_path(std::string_view& path, - ada::scheme::type type) { - // Let path be url's path. - // If url's scheme is "file", path's size is 1, and path[0] is a normalized - // Windows drive letter, then return. - if (type == ada::scheme::type::FILE && - path.find('/', 1) == std::string_view::npos && !path.empty()) { - if (checkers::is_normalized_windows_drive_letter( - helpers::substring(path, 1))) { - return false; - } - } - - // Remove path's last item, if any. - if (!path.empty()) { - size_t slash_loc = path.rfind('/'); - if (slash_loc != std::string_view::npos) { - path.remove_suffix(path.size() - slash_loc); - return true; - } - } - - return false; -} - -ada_really_inline void remove_ascii_tab_or_newline(std::string& input) { - // if this ever becomes a performance issue, we could use an approach similar - // to has_tabs_or_newline - std::erase_if(input, ada::unicode::is_ascii_tab_or_newline); -} - -ada_really_inline constexpr std::string_view substring(std::string_view input, - size_t pos) { - ADA_ASSERT_TRUE(pos <= input.size()); - // The following is safer but unneeded if we have the above line: - // return pos > input.size() ? std::string_view() : input.substr(pos); - return input.substr(pos); -} - -ada_really_inline void resize(std::string_view& input, size_t pos) noexcept { - ADA_ASSERT_TRUE(pos <= input.size()); - input.remove_suffix(input.size() - pos); -} - -// computes the number of trailing zeroes -// this is a private inline function only defined in this source file. -ada_really_inline int trailing_zeroes(uint32_t input_num) noexcept { -#ifdef ADA_REGULAR_VISUAL_STUDIO - unsigned long ret; - // Search the mask data from least significant bit (LSB) - // to the most significant bit (MSB) for a set bit (1). - _BitScanForward(&ret, input_num); - return (int)ret; -#else // ADA_REGULAR_VISUAL_STUDIO - return __builtin_ctzl(input_num); -#endif // ADA_REGULAR_VISUAL_STUDIO -} - -// starting at index location, this finds the next location of a character -// :, /, \\, ? or [. If none is found, view.size() is returned. -// For use within get_host_delimiter_location. -#if ADA_SSSE3 -ada_really_inline size_t find_next_host_delimiter_special( - std::string_view view, size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '\\' || - view[i] == '?' || view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - // fast path for long strings (expected to be common) - // Using SSSE3's _mm_shuffle_epi8 for table lookup (same approach as NEON) - size_t i = location; - const __m128i low_mask = - _mm_setr_epi8(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x04, 0x04, 0x00, 0x00, 0x03); - const __m128i high_mask = - _mm_setr_epi8(0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - const __m128i fmask = _mm_set1_epi8(0xf); - const __m128i zero = _mm_setzero_si128(); - for (; i + 15 < view.size(); i += 16) { - __m128i word = _mm_loadu_si128((const __m128i*)(view.data() + i)); - __m128i lowpart = _mm_shuffle_epi8(low_mask, _mm_and_si128(word, fmask)); - __m128i highpart = _mm_shuffle_epi8( - high_mask, _mm_and_si128(_mm_srli_epi16(word, 4), fmask)); - __m128i classify = _mm_and_si128(lowpart, highpart); - __m128i is_zero = _mm_cmpeq_epi8(classify, zero); - // _mm_movemask_epi8 returns a 16-bit mask in bits 0-15, with bits 16-31 - // zero. After NOT (~), bits 16-31 become 1. We must mask to 16 bits to - // avoid false positives. - int mask = ~_mm_movemask_epi8(is_zero) & 0xFFFF; - if (mask != 0) { - return i + trailing_zeroes(static_cast(mask)); - } - } - if (i < view.size()) { - __m128i word = - _mm_loadu_si128((const __m128i*)(view.data() + view.length() - 16)); - __m128i lowpart = _mm_shuffle_epi8(low_mask, _mm_and_si128(word, fmask)); - __m128i highpart = _mm_shuffle_epi8( - high_mask, _mm_and_si128(_mm_srli_epi16(word, 4), fmask)); - __m128i classify = _mm_and_si128(lowpart, highpart); - __m128i is_zero = _mm_cmpeq_epi8(classify, zero); - // _mm_movemask_epi8 returns a 16-bit mask in bits 0-15, with bits 16-31 - // zero. After NOT (~), bits 16-31 become 1. We must mask to 16 bits to - // avoid false positives. - int mask = ~_mm_movemask_epi8(is_zero) & 0xFFFF; - if (mask != 0) { - return view.length() - 16 + trailing_zeroes(static_cast(mask)); - } - } - return size_t(view.size()); -} -#elif ADA_NEON -// The ada_make_uint8x16_t macro is necessary because Visual Studio does not -// support direct initialization of uint8x16_t. See -// https://developercommunity.visualstudio.com/t/error-C2078:-too-many-initializers-whe/402911?q=backend+neon -#ifndef ada_make_uint8x16_t -#define ada_make_uint8x16_t(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, \ - x13, x14, x15, x16) \ - ([=]() { \ - static uint8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8, \ - x9, x10, x11, x12, x13, x14, x15, x16}; \ - return vld1q_u8(array); \ - }()) -#endif - -ada_really_inline size_t find_next_host_delimiter_special( - std::string_view view, size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '\\' || - view[i] == '?' || view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - auto to_bitmask = [](uint8x16_t input) -> uint16_t { - uint8x16_t bit_mask = - ada_make_uint8x16_t(0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, - 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80); - uint8x16_t minput = vandq_u8(input, bit_mask); - uint8x16_t tmp = vpaddq_u8(minput, minput); - tmp = vpaddq_u8(tmp, tmp); - tmp = vpaddq_u8(tmp, tmp); - return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0); - }; - - // fast path for long strings (expected to be common) - size_t i = location; - uint8x16_t low_mask = - ada_make_uint8x16_t(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x04, 0x04, 0x00, 0x00, 0x03); - uint8x16_t high_mask = - ada_make_uint8x16_t(0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - uint8x16_t fmask = vmovq_n_u8(0xf); - uint8x16_t zero{0}; - for (; i + 15 < view.size(); i += 16) { - uint8x16_t word = vld1q_u8((const uint8_t*)view.data() + i); - uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask)); - uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4)); - uint8x16_t classify = vandq_u8(lowpart, highpart); - if (vmaxvq_u32(vreinterpretq_u32_u8(classify)) != 0) { - uint8x16_t is_zero = vceqq_u8(classify, zero); - uint16_t is_non_zero = static_cast(~to_bitmask(is_zero)); - return i + trailing_zeroes(is_non_zero); - } - } - - if (i < view.size()) { - uint8x16_t word = - vld1q_u8((const uint8_t*)view.data() + view.length() - 16); - uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask)); - uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4)); - uint8x16_t classify = vandq_u8(lowpart, highpart); - if (vmaxvq_u32(vreinterpretq_u32_u8(classify)) != 0) { - uint8x16_t is_zero = vceqq_u8(classify, zero); - uint16_t is_non_zero = static_cast(~to_bitmask(is_zero)); - return view.length() - 16 + trailing_zeroes(is_non_zero); - } - } - return size_t(view.size()); -} -#elif ADA_SSE2 -ada_really_inline size_t find_next_host_delimiter_special( - std::string_view view, size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '\\' || - view[i] == '?' || view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - // fast path for long strings (expected to be common) - size_t i = location; - const __m128i mask1 = _mm_set1_epi8(':'); - const __m128i mask2 = _mm_set1_epi8('/'); - const __m128i mask3 = _mm_set1_epi8('\\'); - const __m128i mask4 = _mm_set1_epi8('?'); - const __m128i mask5 = _mm_set1_epi8('['); - - for (; i + 15 < view.size(); i += 16) { - __m128i word = _mm_loadu_si128((const __m128i*)(view.data() + i)); - __m128i m1 = _mm_cmpeq_epi8(word, mask1); - __m128i m2 = _mm_cmpeq_epi8(word, mask2); - __m128i m3 = _mm_cmpeq_epi8(word, mask3); - __m128i m4 = _mm_cmpeq_epi8(word, mask4); - __m128i m5 = _mm_cmpeq_epi8(word, mask5); - __m128i m = _mm_or_si128( - _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m3, m4)), m5); - int mask = _mm_movemask_epi8(m); - if (mask != 0) { - return i + trailing_zeroes(mask); - } - } - if (i < view.size()) { - __m128i word = - _mm_loadu_si128((const __m128i*)(view.data() + view.length() - 16)); - __m128i m1 = _mm_cmpeq_epi8(word, mask1); - __m128i m2 = _mm_cmpeq_epi8(word, mask2); - __m128i m3 = _mm_cmpeq_epi8(word, mask3); - __m128i m4 = _mm_cmpeq_epi8(word, mask4); - __m128i m5 = _mm_cmpeq_epi8(word, mask5); - __m128i m = _mm_or_si128( - _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m3, m4)), m5); - int mask = _mm_movemask_epi8(m); - if (mask != 0) { - return view.length() - 16 + trailing_zeroes(mask); - } - } - return size_t(view.length()); -} -#elif ADA_LSX -ada_really_inline size_t find_next_host_delimiter_special( - std::string_view view, size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '\\' || - view[i] == '?' || view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - // fast path for long strings (expected to be common) - size_t i = location; - const __m128i mask1 = __lsx_vrepli_b(':'); - const __m128i mask2 = __lsx_vrepli_b('/'); - const __m128i mask3 = __lsx_vrepli_b('\\'); - const __m128i mask4 = __lsx_vrepli_b('?'); - const __m128i mask5 = __lsx_vrepli_b('['); - - for (; i + 15 < view.size(); i += 16) { - __m128i word = __lsx_vld((const __m128i*)(view.data() + i), 0); - __m128i m1 = __lsx_vseq_b(word, mask1); - __m128i m2 = __lsx_vseq_b(word, mask2); - __m128i m3 = __lsx_vseq_b(word, mask3); - __m128i m4 = __lsx_vseq_b(word, mask4); - __m128i m5 = __lsx_vseq_b(word, mask5); - __m128i m = - __lsx_vor_v(__lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m3, m4)), m5); - int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); - if (mask != 0) { - return i + trailing_zeroes(mask); - } - } - if (i < view.size()) { - __m128i word = - __lsx_vld((const __m128i*)(view.data() + view.length() - 16), 0); - __m128i m1 = __lsx_vseq_b(word, mask1); - __m128i m2 = __lsx_vseq_b(word, mask2); - __m128i m3 = __lsx_vseq_b(word, mask3); - __m128i m4 = __lsx_vseq_b(word, mask4); - __m128i m5 = __lsx_vseq_b(word, mask5); - __m128i m = - __lsx_vor_v(__lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m3, m4)), m5); - int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); - if (mask != 0) { - return view.length() - 16 + trailing_zeroes(mask); - } - } - return size_t(view.length()); -} -#elif ADA_RVV -ada_really_inline size_t find_next_host_delimiter_special( - std::string_view view, size_t location) noexcept { - // The LUT approach was a bit slower on the SpacemiT X60, but I could see it - // being faster on future hardware. -#if 0 - // LUT generated using: s=":/\\?["; list(zip([((ord(c)>>2)&0xF)for c in s],s)) - static const uint8_t tbl[16] = { - 0xF, 0, 0, 0, 0, 0, '[', '\\', 0, 0, 0, '/', 0, 0, ':', '?' - }; - vuint8m1_t vtbl = __riscv_vle8_v_u8m1(tbl, 16); -#endif - uint8_t* src = (uint8_t*)view.data() + location; - for (size_t vl, n = view.size() - location; n > 0; - n -= vl, src += vl, location += vl) { - vl = __riscv_vsetvl_e8m1(n); - vuint8m1_t v = __riscv_vle8_v_u8m1(src, vl); -#if 0 - vuint8m1_t vidx = __riscv_vand(__riscv_vsrl(v, 2, vl), 0xF, vl); - vuint8m1_t vlut = __riscv_vrgather(vtbl, vidx, vl); - vbool8_t m = __riscv_vmseq(v, vlut, vl); -#else - vbool8_t m1 = __riscv_vmseq(v, ':', vl); - vbool8_t m2 = __riscv_vmseq(v, '/', vl); - vbool8_t m3 = __riscv_vmseq(v, '?', vl); - vbool8_t m4 = __riscv_vmseq(v, '[', vl); - vbool8_t m5 = __riscv_vmseq(v, '\\', vl); - vbool8_t m = __riscv_vmor( - __riscv_vmor(__riscv_vmor(m1, m2, vl), __riscv_vmor(m3, m4, vl), vl), - m5, vl); -#endif - long idx = __riscv_vfirst(m, vl); - if (idx >= 0) return location + idx; - } - return size_t(view.size()); -} -#else -// : / [ \\ ? -static constexpr std::array special_host_delimiters = - []() consteval { - std::array result{}; - for (int i : {':', '/', '[', '\\', '?'}) { - result[i] = 1; - } - return result; - }(); -// credit: @the-moisrex recommended a table-based approach -ada_really_inline size_t find_next_host_delimiter_special( - std::string_view view, size_t location) noexcept { - auto const str = view.substr(location); - for (auto pos = str.begin(); pos != str.end(); ++pos) { - if (special_host_delimiters[(uint8_t)*pos]) { - return pos - str.begin() + location; - } - } - return size_t(view.size()); -} -#endif - -// starting at index location, this finds the next location of a character -// :, /, ? or [. If none is found, view.size() is returned. -// For use within get_host_delimiter_location. -#if ADA_SSSE3 -ada_really_inline size_t find_next_host_delimiter(std::string_view view, - size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '?' || - view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - // fast path for long strings (expected to be common) - size_t i = location; - // Lookup tables for bit classification: - // ':' (0x3A): low[0xA]=0x01, high[0x3]=0x01 -> match - // '/' (0x2F): low[0xF]=0x02, high[0x2]=0x02 -> match - // '?' (0x3F): low[0xF]=0x01, high[0x3]=0x01 -> match - // '[' (0x5B): low[0xB]=0x04, high[0x5]=0x04 -> match - const __m128i low_mask = - _mm_setr_epi8(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x04, 0x00, 0x00, 0x00, 0x03); - const __m128i high_mask = - _mm_setr_epi8(0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - const __m128i fmask = _mm_set1_epi8(0xf); - const __m128i zero = _mm_setzero_si128(); - - for (; i + 15 < view.size(); i += 16) { - __m128i word = _mm_loadu_si128((const __m128i*)(view.data() + i)); - __m128i lowpart = _mm_shuffle_epi8(low_mask, _mm_and_si128(word, fmask)); - __m128i highpart = _mm_shuffle_epi8( - high_mask, _mm_and_si128(_mm_srli_epi16(word, 4), fmask)); - __m128i classify = _mm_and_si128(lowpart, highpart); - __m128i is_zero = _mm_cmpeq_epi8(classify, zero); - // _mm_movemask_epi8 returns a 16-bit mask in bits 0-15, with bits 16-31 - // zero. After NOT (~), bits 16-31 become 1. We must mask to 16 bits to - // avoid false positives. - int mask = ~_mm_movemask_epi8(is_zero) & 0xFFFF; - if (mask != 0) { - return i + trailing_zeroes(static_cast(mask)); - } - } - - if (i < view.size()) { - __m128i word = - _mm_loadu_si128((const __m128i*)(view.data() + view.length() - 16)); - __m128i lowpart = _mm_shuffle_epi8(low_mask, _mm_and_si128(word, fmask)); - __m128i highpart = _mm_shuffle_epi8( - high_mask, _mm_and_si128(_mm_srli_epi16(word, 4), fmask)); - __m128i classify = _mm_and_si128(lowpart, highpart); - __m128i is_zero = _mm_cmpeq_epi8(classify, zero); - // _mm_movemask_epi8 returns a 16-bit mask in bits 0-15, with bits 16-31 - // zero. After NOT (~), bits 16-31 become 1. We must mask to 16 bits to - // avoid false positives. - int mask = ~_mm_movemask_epi8(is_zero) & 0xFFFF; - if (mask != 0) { - return view.length() - 16 + trailing_zeroes(static_cast(mask)); - } - } - return size_t(view.size()); -} -#elif ADA_NEON -ada_really_inline size_t find_next_host_delimiter(std::string_view view, - size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '?' || - view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - auto to_bitmask = [](uint8x16_t input) -> uint16_t { - uint8x16_t bit_mask = - ada_make_uint8x16_t(0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, - 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80); - uint8x16_t minput = vandq_u8(input, bit_mask); - uint8x16_t tmp = vpaddq_u8(minput, minput); - tmp = vpaddq_u8(tmp, tmp); - tmp = vpaddq_u8(tmp, tmp); - return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0); - }; - - // fast path for long strings (expected to be common) - size_t i = location; - uint8x16_t low_mask = - ada_make_uint8x16_t(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x03); - uint8x16_t high_mask = - ada_make_uint8x16_t(0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - uint8x16_t fmask = vmovq_n_u8(0xf); - uint8x16_t zero{0}; - for (; i + 15 < view.size(); i += 16) { - uint8x16_t word = vld1q_u8((const uint8_t*)view.data() + i); - uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask)); - uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4)); - uint8x16_t classify = vandq_u8(lowpart, highpart); - if (vmaxvq_u32(vreinterpretq_u32_u8(classify)) != 0) { - uint8x16_t is_zero = vceqq_u8(classify, zero); - uint16_t is_non_zero = static_cast(~to_bitmask(is_zero)); - return i + trailing_zeroes(is_non_zero); - } - } - - if (i < view.size()) { - uint8x16_t word = - vld1q_u8((const uint8_t*)view.data() + view.length() - 16); - uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask)); - uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4)); - uint8x16_t classify = vandq_u8(lowpart, highpart); - if (vmaxvq_u32(vreinterpretq_u32_u8(classify)) != 0) { - uint8x16_t is_zero = vceqq_u8(classify, zero); - uint16_t is_non_zero = static_cast(~to_bitmask(is_zero)); - return view.length() - 16 + trailing_zeroes(is_non_zero); - } - } - return size_t(view.size()); -} -#elif ADA_SSE2 -ada_really_inline size_t find_next_host_delimiter(std::string_view view, - size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '?' || - view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - // fast path for long strings (expected to be common) - size_t i = location; - const __m128i mask1 = _mm_set1_epi8(':'); - const __m128i mask2 = _mm_set1_epi8('/'); - const __m128i mask4 = _mm_set1_epi8('?'); - const __m128i mask5 = _mm_set1_epi8('['); - - for (; i + 15 < view.size(); i += 16) { - __m128i word = _mm_loadu_si128((const __m128i*)(view.data() + i)); - __m128i m1 = _mm_cmpeq_epi8(word, mask1); - __m128i m2 = _mm_cmpeq_epi8(word, mask2); - __m128i m4 = _mm_cmpeq_epi8(word, mask4); - __m128i m5 = _mm_cmpeq_epi8(word, mask5); - __m128i m = _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m4, m5)); - int mask = _mm_movemask_epi8(m); - if (mask != 0) { - return i + trailing_zeroes(mask); - } - } - if (i < view.size()) { - __m128i word = - _mm_loadu_si128((const __m128i*)(view.data() + view.length() - 16)); - __m128i m1 = _mm_cmpeq_epi8(word, mask1); - __m128i m2 = _mm_cmpeq_epi8(word, mask2); - __m128i m4 = _mm_cmpeq_epi8(word, mask4); - __m128i m5 = _mm_cmpeq_epi8(word, mask5); - __m128i m = _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m4, m5)); - int mask = _mm_movemask_epi8(m); - if (mask != 0) { - return view.length() - 16 + trailing_zeroes(mask); - } - } - return size_t(view.length()); -} -#elif ADA_LSX -ada_really_inline size_t find_next_host_delimiter(std::string_view view, - size_t location) noexcept { - // first check for short strings in which case we do it naively. - if (view.size() - location < 16) { // slow path - for (size_t i = location; i < view.size(); i++) { - if (view[i] == ':' || view[i] == '/' || view[i] == '?' || - view[i] == '[') { - return i; - } - } - return size_t(view.size()); - } - // fast path for long strings (expected to be common) - size_t i = location; - const __m128i mask1 = __lsx_vrepli_b(':'); - const __m128i mask2 = __lsx_vrepli_b('/'); - const __m128i mask4 = __lsx_vrepli_b('?'); - const __m128i mask5 = __lsx_vrepli_b('['); - - for (; i + 15 < view.size(); i += 16) { - __m128i word = __lsx_vld((const __m128i*)(view.data() + i), 0); - __m128i m1 = __lsx_vseq_b(word, mask1); - __m128i m2 = __lsx_vseq_b(word, mask2); - __m128i m4 = __lsx_vseq_b(word, mask4); - __m128i m5 = __lsx_vseq_b(word, mask5); - __m128i m = __lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m4, m5)); - int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); - if (mask != 0) { - return i + trailing_zeroes(mask); - } - } - if (i < view.size()) { - __m128i word = - __lsx_vld((const __m128i*)(view.data() + view.length() - 16), 0); - __m128i m1 = __lsx_vseq_b(word, mask1); - __m128i m2 = __lsx_vseq_b(word, mask2); - __m128i m4 = __lsx_vseq_b(word, mask4); - __m128i m5 = __lsx_vseq_b(word, mask5); - __m128i m = __lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m4, m5)); - int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); - if (mask != 0) { - return view.length() - 16 + trailing_zeroes(mask); - } - } - return size_t(view.length()); -} -#elif ADA_RVV -ada_really_inline size_t find_next_host_delimiter(std::string_view view, - size_t location) noexcept { - uint8_t* src = (uint8_t*)view.data() + location; - for (size_t vl, n = view.size() - location; n > 0; - n -= vl, src += vl, location += vl) { - vl = __riscv_vsetvl_e8m1(n); - vuint8m1_t v = __riscv_vle8_v_u8m1(src, vl); - vbool8_t m1 = __riscv_vmseq(v, ':', vl); - vbool8_t m2 = __riscv_vmseq(v, '/', vl); - vbool8_t m3 = __riscv_vmseq(v, '?', vl); - vbool8_t m4 = __riscv_vmseq(v, '[', vl); - vbool8_t m = - __riscv_vmor(__riscv_vmor(m1, m2, vl), __riscv_vmor(m3, m4, vl), vl); - long idx = __riscv_vfirst(m, vl); - if (idx >= 0) return location + idx; - } - return size_t(view.size()); -} -#else -// : / [ ? -static constexpr std::array host_delimiters = []() consteval { - std::array result{}; - for (int i : {':', '/', '?', '['}) { - result[i] = 1; - } - return result; -}(); -// credit: @the-moisrex recommended a table-based approach -ada_really_inline size_t find_next_host_delimiter(std::string_view view, - size_t location) noexcept { - auto const str = view.substr(location); - for (auto pos = str.begin(); pos != str.end(); ++pos) { - if (host_delimiters[(uint8_t)*pos]) { - return pos - str.begin() + location; - } - } - return size_t(view.size()); -} -#endif - -ada_really_inline std::pair get_host_delimiter_location( - const bool is_special, std::string_view& view) noexcept { - /** - * The spec at https://url.spec.whatwg.org/#hostname-state expects us to - * compute a variable called insideBrackets but this variable is only used - * once, to check whether a ':' character was found outside brackets. Exact - * text: "Otherwise, if c is U+003A (:) and insideBrackets is false, then:". - * It is conceptually simpler and arguably more efficient to just return a - * Boolean indicating whether ':' was found outside brackets. - */ - const size_t view_size = view.size(); - size_t location = 0; - bool found_colon = false; - /** - * Performance analysis: - * - * We are basically seeking the end of the hostname which can be indicated - * by the end of the view, or by one of the characters ':', '/', '?', '\\' - * (where '\\' is only applicable for special URLs). However, these must - * appear outside a bracket range. E.g., if you have [something?]fd: then the - * '?' does not count. - * - * So we can skip ahead to the next delimiter, as long as we include '[' in - * the set of delimiters, and that we handle it first. - * - * So the trick is to have a fast function that locates the next delimiter. - * Unless we find '[', then it only needs to be called once! Ideally, such a - * function would be provided by the C++ standard library, but it seems that - * find_first_of is not very fast, so we are forced to roll our own. - * - * We do not break into two loops for speed, but for clarity. - */ - if (is_special) { - // We move to the next delimiter. - location = find_next_host_delimiter_special(view, location); - // Unless we find '[' then we are going only going to have to call - // find_next_host_delimiter_special once. - for (; location < view_size; - location = find_next_host_delimiter_special(view, location)) { - if (view[location] == '[') { - location = view.find(']', location); - if (location == std::string_view::npos) { - // performance: view.find might get translated to a memchr, which - // has no notion of std::string_view::npos, so the code does not - // reflect the assembly. - location = view_size; - break; - } - } else { - found_colon = view[location] == ':'; - break; - } - } - } else { - // We move to the next delimiter. - location = find_next_host_delimiter(view, location); - // Unless we find '[' then we are going only going to have to call - // find_next_host_delimiter_special once. - for (; location < view_size; - location = find_next_host_delimiter(view, location)) { - if (view[location] == '[') { - location = view.find(']', location); - if (location == std::string_view::npos) { - // performance: view.find might get translated to a memchr, which - // has no notion of std::string_view::npos, so the code does not - // reflect the assembly. - location = view_size; - break; - } - } else { - found_colon = view[location] == ':'; - break; - } - } - } - // performance: remove_suffix may translate into a single instruction. - view.remove_suffix(view_size - location); - return {location, found_colon}; -} - -void trim_c0_whitespace(std::string_view& input) noexcept { - while (!input.empty() && - ada::unicode::is_c0_control_or_space(input.front())) { - input.remove_prefix(1); - } - while (!input.empty() && ada::unicode::is_c0_control_or_space(input.back())) { - input.remove_suffix(1); - } -} - -ada_really_inline void parse_prepared_path(std::string_view input, - ada::scheme::type type, - std::string& path) { - ada_log("parse_prepared_path ", input); - uint8_t accumulator = checkers::path_signature(input); - // Let us first detect a trivial case. - // If it is special, we check that we have no dot, no %, no \ and no - // character needing percent encoding. Otherwise, we check that we have no %, - // no dot, and no character needing percent encoding. - constexpr uint8_t need_encoding = 1; - constexpr uint8_t backslash_char = 2; - constexpr uint8_t dot_char = 4; - constexpr uint8_t percent_char = 8; - bool special = type != ada::scheme::NOT_SPECIAL; - bool may_need_slow_file_handling = (type == ada::scheme::type::FILE && - checkers::is_windows_drive_letter(input)); - bool trivial_path = - (special ? (accumulator == 0) - : ((accumulator & (need_encoding | dot_char | percent_char)) == - 0)) && - (!may_need_slow_file_handling); - if (accumulator == dot_char && !may_need_slow_file_handling) { - // '4' means that we have at least one dot, but nothing that requires - // percent encoding or decoding. The only part that is not trivial is - // that we may have single dots and double dots path segments. - // If we have such segments, then we either have a path that begins - // with '.' (easy to check), or we have the sequence './'. - // Note: input cannot be empty, it must at least contain one character ('.') - // Note: we know that '\' is not present. - if (input[0] != '.') { - size_t slashdot = 0; - bool dot_is_file = true; - for (;;) { - slashdot = input.find("/.", slashdot); - if (slashdot == std::string_view::npos) { // common case - break; - } else { // uncommon - // only three cases matter: /./, /.. or a final / - slashdot += 2; - dot_is_file &= !(slashdot == input.size() || input[slashdot] == '.' || - input[slashdot] == '/'); - } - } - trivial_path = dot_is_file; - } - } - if (trivial_path) { - ada_log("parse_path trivial"); - path += '/'; - path += input; - return; - } - // We are going to need to look a bit at the path, but let us see if we can - // ignore percent encoding *and* backslashes *and* percent characters. - // Except for the trivial case, this is likely to capture 99% of paths out - // there. - bool fast_path = - (special && - (accumulator & (need_encoding | backslash_char | percent_char)) == 0) && - (type != ada::scheme::type::FILE); - if (fast_path) { - ada_log("parse_prepared_path fast"); - // Here we don't need to worry about \ or percent encoding. - // We also do not have a file protocol. We might have dots, however, - // but dots must as appear as '.', and they cannot be encoded because - // the symbol '%' is not present. - size_t previous_location = 0; // We start at 0. - do { - size_t new_location = input.find('/', previous_location); - // std::string_view path_view = input; - // We process the last segment separately: - if (new_location == std::string_view::npos) { - std::string_view path_view = input.substr(previous_location); - if (path_view == "..") { // The path ends with .. - // e.g., if you receive ".." with an empty path, you go to "/". - if (path.empty()) { - path = '/'; - return; - } - // Fast case where we have nothing to do: - if (path.back() == '/') { - return; - } - // If you have the path "/joe/myfriend", - // then you delete 'myfriend'. - path.resize(path.rfind('/') + 1); - return; - } - path += '/'; - if (path_view != ".") { - path.append(path_view); - } - return; - } else { - // This is a non-final segment. - std::string_view path_view = - input.substr(previous_location, new_location - previous_location); - previous_location = new_location + 1; - if (path_view == "..") { - size_t last_delimiter = path.rfind('/'); - if (last_delimiter != std::string::npos) { - path.erase(last_delimiter); - } - } else if (path_view != ".") { - path += '/'; - path.append(path_view); - } - } - } while (true); - } else { - ada_log("parse_path slow"); - // we have reached the general case - bool needs_percent_encoding = (accumulator & 1); - std::string path_buffer_tmp; - do { - size_t location = (special && (accumulator & 2)) - ? input.find_first_of("/\\") - : input.find('/'); - std::string_view path_view = input; - if (location != std::string_view::npos) { - path_view.remove_suffix(path_view.size() - location); - input.remove_prefix(location + 1); - } - // path_buffer is either path_view or it might point at a percent encoded - // temporary file. - std::string_view path_buffer = - (needs_percent_encoding && - ada::unicode::percent_encode( - path_view, character_sets::PATH_PERCENT_ENCODE, path_buffer_tmp)) - ? path_buffer_tmp - : path_view; - if (unicode::is_double_dot_path_segment(path_buffer)) { - helpers::shorten_path(path, type); - if (location == std::string_view::npos) { - path += '/'; - } - } else if (unicode::is_single_dot_path_segment(path_buffer) && - (location == std::string_view::npos)) { - path += '/'; - } - // Otherwise, if path_buffer is not a single-dot path segment, then: - else if (!unicode::is_single_dot_path_segment(path_buffer)) { - // If url's scheme is "file", url's path is empty, and path_buffer is a - // Windows drive letter, then replace the second code point in - // path_buffer with U+003A (:). - if (type == ada::scheme::type::FILE && path.empty() && - checkers::is_windows_drive_letter(path_buffer)) { - path += '/'; - path += path_buffer[0]; - path += ':'; - path_buffer.remove_prefix(2); - path.append(path_buffer); - } else { - // Append path_buffer to url's path. - path += '/'; - path.append(path_buffer); - } - } - if (location == std::string_view::npos) { - return; - } - } while (true); - } -} - -bool overlaps(std::string_view input1, const std::string& input2) noexcept { - ada_log("helpers::overlaps check if string_view '", input1, "' [", - input1.size(), " bytes] is part of string '", input2, "' [", - input2.size(), " bytes]"); - return !input1.empty() && !input2.empty() && input1.data() >= input2.data() && - input1.data() < input2.data() + input2.size(); -} - -template -ada_really_inline void strip_trailing_spaces_from_opaque_path(url_type& url) { - ada_log("helpers::strip_trailing_spaces_from_opaque_path"); - if (!url.has_opaque_path) return; - if (url.has_hash()) return; - if (url.has_search()) return; - - auto path = std::string(url.get_pathname()); - while (!path.empty() && path.back() == ' ') { - path.resize(path.size() - 1); - } - url.update_base_pathname(path); -} - -// @ / \\ ? -static constexpr std::array authority_delimiter_special = - []() consteval { - std::array result{}; - for (uint8_t i : {'@', '/', '\\', '?'}) { - result[i] = 1; - } - return result; - }(); -// credit: @the-moisrex recommended a table-based approach -ada_really_inline size_t -find_authority_delimiter_special(std::string_view view) noexcept { - // performance note: we might be able to gain further performance - // with SIMD intrinsics. - for (auto pos = view.begin(); pos != view.end(); ++pos) { - if (authority_delimiter_special[(uint8_t)*pos]) { - return pos - view.begin(); - } - } - return size_t(view.size()); -} - -// @ / ? -static constexpr std::array authority_delimiter = []() consteval { - std::array result{}; - for (uint8_t i : {'@', '/', '?'}) { - result[i] = 1; - } - return result; -}(); -// credit: @the-moisrex recommended a table-based approach -ada_really_inline size_t -find_authority_delimiter(std::string_view view) noexcept { - // performance note: we might be able to gain further performance - // with SIMD instrinsics. - for (auto pos = view.begin(); pos != view.end(); ++pos) { - if (authority_delimiter[(uint8_t)*pos]) { - return pos - view.begin(); - } - } - return size_t(view.size()); -} - -} // namespace ada::helpers - -namespace ada { -ada_warn_unused std::string to_string(ada::state state) { - return ada::helpers::get_state(state); -} -#undef ada_make_uint8x16_t -} // namespace ada -/* end file src/helpers.cpp */ -/* begin file src/url.cpp */ -/* begin file include/ada/url_ip-inl.h */ -/** - * @file url_ip-inl.h - * @brief Shared IPv4/IPv6 parsing helpers used by url and url_aggregator. - * - * Not part of the public API. Defined inline so the single-TU amalgamation - * (ada.cpp) can include them from multiple translation-unit sources once. - */ -#ifndef ADA_URL_IP_INL_H -#define ADA_URL_IP_INL_H - - -#include -#include - -#if defined(ADA_AVX512) -#include -#endif - -namespace ada::detail { - -// 256-entry: 0xff = not hex, else nibble value. -inline constexpr std::array make_hex_nibble_table() noexcept { - std::array t{}; - for (size_t i = 0; i < 256; ++i) { - t[i] = 0xff; - } - for (size_t d = 0; d < 10; ++d) { - t[size_t{'0'} + d] = static_cast(d); - } - for (size_t d = 0; d < 6; ++d) { - t[size_t{'a'} + d] = static_cast(10 + d); - t[size_t{'A'} + d] = static_cast(10 + d); - } - return t; -} - -inline constexpr auto hex_nibble = make_hex_nibble_table(); - -inline bool parse_ipv4_number(const char*& p, const char* end, uint64_t& value, - bool& is_pure_decimal) noexcept { - is_pure_decimal = false; - if (p >= end) [[unlikely]] { - return false; - } - if (end - p >= 2 && p[0] == '0' && ((p[1] | 0x20) == 'x')) { - p += 2; - if (p == end || *p == '.') { - value = 0; - return true; - } - uint64_t v = 0; - int digits = 0; - while (p < end && *p != '.') { - const uint8_t nib = hex_nibble[static_cast(*p)]; - if (nib == 0xff) [[unlikely]] { - return false; - } - if (v > (0xFFFFFFFFULL >> 4)) [[unlikely]] { - return false; - } - v = (v << 4) | nib; - ++p; - ++digits; - } - if (digits == 0) [[unlikely]] { - return false; - } - value = v; - return true; - } - if (end - p >= 2 && p[0] == '0' && p[1] >= '0' && p[1] <= '9') { - ++p; - uint64_t v = 0; - while (p < end && *p != '.') { - const char c = *p; - if (c < '0' || c > '7') [[unlikely]] { - return false; - } - if (v > (0xFFFFFFFFULL >> 3)) [[unlikely]] { - return false; - } - v = (v << 3) | static_cast(c - '0'); - ++p; - } - value = v; - return true; - } - if (*p < '0' || *p > '9') [[unlikely]] { - return false; - } - is_pure_decimal = true; - uint64_t v = static_cast(*p - '0'); - ++p; - while (p < end && *p != '.') { - const char c = *p; - if (c < '0' || c > '9') [[unlikely]] { - return false; - } - if (v > 429496729ULL) [[unlikely]] { - return false; - } - v = v * 10u + static_cast(c - '0'); - if (v > 0xFFFFFFFFULL) [[unlikely]] { - return false; - } - ++p; - } - value = v; - return true; -} - -// Parse up to 4 hex digits. Returns digit count (0 if none). -inline int parse_hex_piece(const char*& pointer, const char* end, - uint16_t& value) noexcept { - if (pointer == end) { - return 0; - } - const uint8_t n0 = hex_nibble[static_cast(*pointer)]; - if (n0 == 0xff) { - return 0; - } - uint32_t v = n0; - ++pointer; - int length = 1; - if (pointer != end) { - const uint8_t n1 = hex_nibble[static_cast(*pointer)]; - if (n1 != 0xff) { - v = (v << 4) | n1; - ++pointer; - ++length; - if (pointer != end) { - const uint8_t n2 = hex_nibble[static_cast(*pointer)]; - if (n2 != 0xff) { - v = (v << 4) | n2; - ++pointer; - ++length; - if (pointer != end) { - const uint8_t n3 = hex_nibble[static_cast(*pointer)]; - if (n3 != 0xff) { - v = (v << 4) | n3; - ++pointer; - ++length; - } - } - } - } - } - } - value = static_cast(v); - return length; -} - -#if defined(ADA_AVX512) -// Classify an IPv6 host (no brackets) with one masked 512-bit load. -// Returns false if the colon/dot shape is impossible (e.g. two "::", >8 -// colons, full form without 7 colons). Used as a cheap prefilter before the -// full WHATWG piece parser. -ada_really_inline bool ipv6_structure_plausible(const char* data, - size_t len) noexcept { - if (len < 2 || len > 45) { - return false; - } - const __mmask64 live = static_cast<__mmask64>((1ULL << len) - 1ULL); - const __m512i input = - _mm512_maskz_loadu_epi8(live, reinterpret_cast(data)); - const __mmask64 is_colon = - _mm512_mask_cmpeq_epi8_mask(live, input, _mm512_set1_epi8(':')); - const __mmask64 is_dot = - _mm512_mask_cmpeq_epi8_mask(live, input, _mm512_set1_epi8('.')); - const int colons = - static_cast(_mm_popcnt_u64(static_cast(is_colon))); - if (colons > 8) { - return false; - } - const uint64_t doubles = - static_cast(is_colon) & (static_cast(is_colon) << 1); - if (doubles != 0 && (doubles & (doubles - 1)) != 0) { - return false; // more than one "::" (or ":::...") - } - const bool has_double = doubles != 0; - const bool has_dot = is_dot != 0; - if (!has_double && !has_dot && colons != 7) { - return false; - } - return true; -} -#endif // ADA_AVX512 - -} // namespace ada::detail - -#endif // ADA_URL_IP_INL_H -/* end file include/ada/url_ip-inl.h */ - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ada { - -bool url::parse_opaque_host(std::string_view input) { - ada_log("parse_opaque_host ", input, " [", input.size(), " bytes]"); - if (std::ranges::any_of(input, ada::unicode::is_forbidden_host_code_point)) { - return is_valid = false; - } - - // Return the result of running UTF-8 percent-encode on input using the C0 - // control percent-encode set. - host = ada::unicode::percent_encode( - input, ada::character_sets::C0_CONTROL_PERCENT_ENCODE); - return true; -} - -bool url::parse_ipv4(std::string_view input) { - ada_log("parse_ipv4 ", input, " [", input.size(), " bytes]"); - if (input.empty()) { - return is_valid = false; - } - std::string_view original_input = input; - if (original_input.back() == '.') { - original_input.remove_suffix(1); - } - if (input.back() == '.') { - input.remove_suffix(1); - if (input.empty()) { - return is_valid = false; - } - } - - const uint64_t fast = checkers::try_parse_ipv4_fast(input); - if (fast < checkers::ipv4_fast_fail) [[likely]] { - host = original_input; - host_type = IPV4; - return true; - } - - const char* p = input.data(); - const char* end = p + input.size(); - uint64_t ipv4 = 0; - int digit_count = 0; - int pure_decimal_count = 0; - - for (; digit_count < 4 && p < end; ++digit_count) { - uint64_t segment = 0; - bool pure = false; - if (!detail::parse_ipv4_number(p, end, segment, pure)) { - return is_valid = false; - } - if (pure) { - ++pure_decimal_count; - } - if (p >= end) { - const unsigned shift = static_cast(32 - digit_count * 8); - if (segment >= (uint64_t{1} << shift)) { - return is_valid = false; - } - ipv4 = (ipv4 << shift) | segment; - host = (pure_decimal_count == 4) ? std::string(original_input) - : ada::serializers::ipv4(ipv4); - host_type = IPV4; - return true; - } - if (segment > 255 || *p != '.') { - return is_valid = false; - } - ipv4 = (ipv4 << 8) | segment; - ++p; - } - if (digit_count != 4 || p != end) { - return is_valid = false; - } - host = (pure_decimal_count == 4) ? std::string(original_input) - : ada::serializers::ipv4(ipv4); - host_type = IPV4; - return true; -} - -bool url::parse_ipv6(std::string_view input) { - ada_log("parse_ipv6 ", input, " [", input.size(), " bytes]"); - if (input.empty() || input.size() > 45) [[unlikely]] { - return is_valid = false; - } -#if defined(ADA_AVX512) - // One masked load classifies colon/dot shape; rejects impossible inputs - // before the piece parser (free on AVX-512BW+VL builds). - if (!detail::ipv6_structure_plausible(input.data(), input.size())) - [[unlikely]] { - return is_valid = false; - } -#endif - - std::array address{}; - const char* pointer = input.data(); - const char* const end = pointer + input.size(); - int piece_index = 0; - int compress = -1; - - if (*pointer == ':') { - if (input.size() == 1 || pointer[1] != ':') [[unlikely]] { - return is_valid = false; - } - pointer += 2; - compress = ++piece_index; - } - - while (pointer != end) { - if (piece_index == 8) [[unlikely]] { - return is_valid = false; - } - if (*pointer == ':') { - if (compress != -1) [[unlikely]] { - return is_valid = false; - } - ++pointer; - compress = ++piece_index; - continue; - } - - uint16_t value = 0; - const int length = detail::parse_hex_piece(pointer, end, value); - - if (pointer != end && *pointer == '.') { - if (length == 0) [[unlikely]] { - return is_valid = false; - } - pointer -= length; - if (piece_index > 6) [[unlikely]] { - return is_valid = false; - } - - int numbers_seen = 0; - while (pointer != end) { - int ipv4_piece = -1; - if (numbers_seen > 0) { - if (*pointer == '.' && numbers_seen < 4) { - ++pointer; - } else { - return is_valid = false; - } - } - if (pointer == end || *pointer < '0' || *pointer > '9') [[unlikely]] { - return is_valid = false; - } - ipv4_piece = *pointer - '0'; - ++pointer; - if (pointer != end && *pointer >= '0' && *pointer <= '9') { - if (ipv4_piece == 0) [[unlikely]] { - return is_valid = false; - } - ipv4_piece = ipv4_piece * 10 + (*pointer - '0'); - ++pointer; - if (pointer != end && *pointer >= '0' && *pointer <= '9') { - ipv4_piece = ipv4_piece * 10 + (*pointer - '0'); - ++pointer; - if (ipv4_piece > 255) [[unlikely]] { - return is_valid = false; - } - } - } - address[static_cast(piece_index)] = static_cast( - address[static_cast(piece_index)] * 0x100 + - static_cast(ipv4_piece)); - ++numbers_seen; - if (numbers_seen == 2 || numbers_seen == 4) { - ++piece_index; - } - } - if (numbers_seen != 4) [[unlikely]] { - return is_valid = false; - } - break; - } - - if (length == 0) [[unlikely]] { - return is_valid = false; - } - - if (pointer != end && *pointer == ':') { - ++pointer; - if (pointer == end) [[unlikely]] { - return is_valid = false; - } - } else if (pointer != end) [[unlikely]] { - return is_valid = false; - } - - address[static_cast(piece_index)] = value; - ++piece_index; - } - - if (compress != -1) { - const int right = piece_index - compress; - if (right > 0) { - const size_t dest = static_cast(8 - right); - const size_t src = static_cast(compress); - if (dest != src) { - for (size_t i = static_cast(right); i-- > 0;) { - address[dest + i] = address[src + i]; - address[src + i] = 0; - } - } - } - } else if (piece_index != 8) [[unlikely]] { - return is_valid = false; - } - - host = ada::serializers::ipv6(address); - ada_log("parse_ipv6 ", *host); - host_type = IPV6; - return true; -} - -template -ada_really_inline bool url::parse_scheme(const std::string_view input) { - auto parsed_type = ada::scheme::get_scheme_type(input); - bool is_input_special = (parsed_type != ada::scheme::NOT_SPECIAL); - /** - * In the common case, we will immediately recognize a special scheme (e.g., - *http, https), in which case, we can go really fast. - **/ - if (is_input_special) { // fast path!!! - if constexpr (has_state_override) { - // If url's scheme is not a special scheme and buffer is a special scheme, - // then return. - if (is_special() != is_input_special) { - return false; - } - - // If url includes credentials or has a non-null port, and buffer is - // "file", then return. - if ((has_credentials() || port.has_value()) && - parsed_type == ada::scheme::type::FILE) { - return false; - } - - // If url's scheme is "file" and its host is an empty host, then return. - // An empty host is the empty string. - if (type == ada::scheme::type::FILE && host.has_value() && - host->empty()) { - return false; - } - } - - type = parsed_type; - - if constexpr (has_state_override) { - // This is uncommon. - uint16_t urls_scheme_port = get_special_port(); - - if (urls_scheme_port) { - // If url's port is url's scheme's default port, then set url's port to - // null. - if (port.has_value() && *port == urls_scheme_port) { - port = std::nullopt; - } - } - } - } else { // slow path - std::string _buffer(input); - // Next function is only valid if the input is ASCII and returns false - // otherwise, but it seems that we always have ascii content so we do not - // need to check the return value. - // bool is_ascii = - unicode::to_lower_ascii(_buffer.data(), _buffer.size()); - - if constexpr (has_state_override) { - // The state-override validation errors below ("return" in the WHATWG URL - // parser) leave the URL unchanged. The setter contract is - // "true on success, false if the scheme is invalid" -- the fast path - // above already returns false here, so the slow path must agree. - - // If url's scheme is a special scheme and buffer is not a special scheme, - // then return. If url's scheme is not a special scheme and buffer is a - // special scheme, then return. - if (is_special() != ada::scheme::is_special(_buffer)) { - return false; - } - - // If url includes credentials or has a non-null port, and buffer is - // "file", then return. - if ((has_credentials() || port.has_value()) && _buffer == "file") { - return false; - } - - // If url's scheme is "file" and its host is an empty host, then return. - // An empty host is the empty string. - if (type == ada::scheme::type::FILE && host.has_value() && - host->empty()) { - return false; - } - } - - set_scheme(std::move(_buffer)); - - if constexpr (has_state_override) { - // This is uncommon. - uint16_t urls_scheme_port = get_special_port(); - - if (urls_scheme_port) { - // If url's port is url's scheme's default port, then set url's port to - // null. - if (port.has_value() && *port == urls_scheme_port) { - port = std::nullopt; - } - } - } - } - - return true; -} - -ada_really_inline bool url::parse_host(std::string_view input) { - ada_log("parse_host ", input, " [", input.size(), " bytes]"); - if (input.empty()) { - return is_valid = false; - } // technically unnecessary. - // If input starts with U+005B ([), then: - if (input[0] == '[') { - // If input does not end with U+005D (]), validation error, return failure. - if (input.back() != ']') { - return is_valid = false; - } - ada_log("parse_host ipv6"); - - // Return the result of IPv6 parsing input with its leading U+005B ([) and - // trailing U+005D (]) removed. - input.remove_prefix(1); - input.remove_suffix(1); - return parse_ipv6(input); - } - - // If isNotSpecial is true, then return the result of opaque-host parsing - // input. - if (!is_special()) { - return parse_opaque_host(input); - } - - // Fast path: try to parse as pure decimal IPv4(a.b.c.d) first. - const uint64_t fast_result = checkers::try_parse_ipv4_fast(input); - if (fast_result < checkers::ipv4_fast_fail) { - // Fast path succeeded - input is pure decimal IPv4 - if (!input.empty() && input.back() == '.') { - host = input.substr(0, input.size() - 1); - } else { - host = input; - } - host_type = IPV4; - is_valid = true; - ada_log("parse_host fast path decimal ipv4"); - return true; - } - // Let domain be the result of running UTF-8 decode without BOM on the - // percent-decoding of input. Let asciiDomain be the result of running domain - // to ASCII with domain and false. The most common case is an ASCII input, in - // which case we do not need to call the expensive 'to_ascii' if a few - // conditions are met: no '%' and no 'xn-' subsequence. - std::string buffer = std::string(input); - // This next function checks that the result is ascii, but we are going to - // to check anyhow with is_forbidden. - // bool is_ascii = - unicode::to_lower_ascii(buffer.data(), buffer.size()); - bool is_forbidden = unicode::contains_forbidden_domain_code_point( - buffer.data(), buffer.size()); - static constexpr std::string_view xn_dash{"xn-", 3}; - if (is_forbidden == 0 && buffer.find(xn_dash) == std::string_view::npos) { - // fast path - host = std::move(buffer); - - // Check for other IPv4 formats (hex, octal, etc.) - if (checkers::is_ipv4(host.value())) { - ada_log("parse_host fast path ipv4"); - return parse_ipv4(host.value()); - } - ada_log("parse_host fast path ", *host); - is_valid = true; - return true; - } - ada_log("parse_host calling to_ascii"); - is_valid = ada::unicode::to_ascii(host, input, input.find('%')); - if (!is_valid || !host.has_value()) { - ada_log("parse_host to_ascii returns false"); - return is_valid = false; - } - ada_log("parse_host to_ascii succeeded ", *host, " [", host->size(), - " bytes]"); - - if (std::any_of(host->begin(), host->end(), - ada::unicode::is_forbidden_domain_code_point)) { - host = std::nullopt; - return is_valid = false; - } - - // If asciiDomain ends in a number, then return the result of IPv4 parsing - // asciiDomain. - if (checkers::is_ipv4(*host)) { - ada_log("parse_host got ipv4 ", *host); - return parse_ipv4(*host); - } - - return true; -} - -ada_really_inline void url::parse_path(std::string_view input) { - ada_log("parse_path ", input); - std::string tmp_buffer; - std::string_view internal_input; - if (unicode::has_tabs_or_newline(input)) { - tmp_buffer = input; - // Optimization opportunity: Instead of copying and then pruning, we could - // just directly build the string from user_input. - helpers::remove_ascii_tab_or_newline(tmp_buffer); - internal_input = tmp_buffer; - } else { - internal_input = input; - } - - // If url is special, then: - if (is_special()) { - if (internal_input.empty()) { - path = "/"; - } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) { - helpers::parse_prepared_path(internal_input.substr(1), type, path); - } else { - helpers::parse_prepared_path(internal_input, type, path); - } - } else if (!internal_input.empty()) { - if (internal_input[0] == '/') { - helpers::parse_prepared_path(internal_input.substr(1), type, path); - } else { - helpers::parse_prepared_path(internal_input, type, path); - } - } else { - if (!host.has_value()) { - path = "/"; - } - } -} - -[[nodiscard]] std::string url::to_string() const { - if (!is_valid) { - return "null"; - } - std::string answer; - auto back = std::back_insert_iterator(answer); - answer.append("{\n"); - answer.append("\t\"protocol\":\""); - helpers::encode_json(get_protocol(), back); - answer.append("\",\n"); - if (has_credentials()) { - answer.append("\t\"username\":\""); - helpers::encode_json(username, back); - answer.append("\",\n"); - answer.append("\t\"password\":\""); - helpers::encode_json(password, back); - answer.append("\",\n"); - } - if (host.has_value()) { - answer.append("\t\"host\":\""); - helpers::encode_json(host.value(), back); - answer.append("\",\n"); - } - if (port.has_value()) { - answer.append("\t\"port\":\""); - answer.append(std::to_string(port.value())); - answer.append("\",\n"); - } - answer.append("\t\"path\":\""); - helpers::encode_json(path, back); - answer.append("\",\n"); - answer.append("\t\"opaque path\":"); - answer.append((has_opaque_path ? "true" : "false")); - if (has_search()) { - answer.append(",\n"); - answer.append("\t\"query\":\""); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - helpers::encode_json(query.value(), back); - answer.append("\""); - } - if (hash.has_value()) { - answer.append(",\n"); - answer.append("\t\"hash\":\""); - helpers::encode_json(hash.value(), back); - answer.append("\""); - } - answer.append("\n}"); - return answer; -} - -[[nodiscard]] bool url::has_valid_domain() const noexcept { - if (!host.has_value()) { - return false; - } - return checkers::verify_dns_length(*host); -} - -[[nodiscard]] std::string url::get_origin() const { - if (is_special()) { - // Return a new opaque origin. - if (type == scheme::FILE) { - return "null"; - } - return ada::helpers::concat(get_protocol(), "//", get_host()); - } - - if (non_special_scheme == "blob") { - if (!path.empty()) { - auto result = ada::parse(path); - if (result && - (result->type == scheme::HTTP || result->type == scheme::HTTPS)) { - // If pathURL's scheme is not "http" and not "https", then return a - // new opaque origin. - return ada::helpers::concat(result->get_protocol(), "//", - result->get_host()); - } - } - } - - // Return a new opaque origin. - return "null"; -} - -[[nodiscard]] std::string url::get_protocol() const { - if (is_special()) { - return helpers::concat(ada::scheme::details::is_special_list[type], ":"); - } - // We only move the 'scheme' if it is non-special. - return helpers::concat(non_special_scheme, ":"); -} - -[[nodiscard]] std::string url::get_host() const { - // If url's host is null, then return the empty string. - // If url's port is null, return url's host, serialized. - // Return url's host, serialized, followed by U+003A (:) and url's port, - // serialized. - if (!host.has_value()) { - return ""; - } - if (port.has_value()) { - return host.value() + ":" + get_port(); - } - return host.value(); -} - -[[nodiscard]] std::string url::get_hostname() const { - return host.value_or(""); -} - -[[nodiscard]] std::string url::get_search() const { - // If this's URL's query is either null or the empty string, then return the - // empty string. Return U+003F (?), followed by this's URL's query. - return (!query.has_value() || (query->empty())) ? "" : "?" + query.value(); -} - -[[nodiscard]] const std::string& url::get_username() const noexcept { - return username; -} - -[[nodiscard]] const std::string& url::get_password() const noexcept { - return password; -} - -[[nodiscard]] std::string url::get_port() const { - return port.has_value() ? std::to_string(port.value()) : ""; -} - -[[nodiscard]] std::string url::get_hash() const { - // If this's URL's fragment is either null or the empty string, then return - // the empty string. Return U+0023 (#), followed by this's URL's fragment. - return (!hash.has_value() || (hash->empty())) ? "" : "#" + hash.value(); -} - -template -bool url::set_host_or_hostname(const std::string_view input) { - if (has_opaque_path) { - return false; - } - - url saved_url(*this); - - size_t host_end_pos = input.find('#'); - std::string _host(input.data(), host_end_pos != std::string_view::npos - ? host_end_pos - : input.size()); - helpers::remove_ascii_tab_or_newline(_host); - std::string_view new_host(_host); - - auto check_url_size = [&]() -> bool { - if (get_href_size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - return true; - }; - - // If url's scheme is "file", then set state to file host state, instead of - // host state. - if (type != ada::scheme::type::FILE) { - std::string_view host_view(_host.data(), _host.length()); - auto [location, found_colon] = - helpers::get_host_delimiter_location(is_special(), host_view); - - // Otherwise, if c is U+003A (:) and insideBrackets is false, then: - // Note: the 'found_colon' value is true if and only if a colon was - // encountered while not inside brackets. - if (found_colon) { - // If buffer is the empty string, host-missing validation error, return - // failure. - std::string_view buffer = host_view.substr(0, location); - if (buffer.empty()) { - return false; - } - - // If state override is given and state override is hostname state, then - // return failure. - if constexpr (override_hostname) { - return false; - } - - // Let host be the result of host parsing buffer with url is not special. - bool succeeded = parse_host(buffer); - if (!succeeded) { - *this = std::move(saved_url); - return false; - } - - // Set url's host to host, buffer to the empty string, and state to port - // state. - std::string_view port_buffer = new_host.substr(location + 1); - if (!port_buffer.empty()) { - set_port(port_buffer); - } - return check_url_size(); - } - // Otherwise, if one of the following is true: - // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) - // - url is special and c is U+005C (\) - else { - // If url is special and host_view is the empty string, host-missing - // validation error, return failure. - if (host_view.empty() && is_special()) { - return false; - } - - // Otherwise, if state override is given, host_view is the empty string, - // and either url includes credentials or url's port is non-null, then - // return failure. - if (host_view.empty() && (has_credentials() || port.has_value())) { - return false; - } - - // Let host be the result of host parsing host_view with url is not - // special. - if (host_view.empty() && !is_special()) { - host = ""; - return check_url_size(); - } - - bool succeeded = parse_host(host_view); - if (!succeeded) { - *this = std::move(saved_url); - return false; - } - return check_url_size(); - } - } - - size_t location = new_host.find_first_of("/\\?"); - if (location != std::string_view::npos) { - new_host.remove_suffix(new_host.length() - location); - } - - if (new_host.empty()) { - // Set url's host to the empty string. - host = ""; - } else { - // Let host be the result of host parsing buffer with url is not special. - if (!parse_host(new_host)) { - *this = std::move(saved_url); - return false; - } - - // If host is "localhost", then set host to the empty string. - if (host == "localhost") { - host = ""; - } - } - return check_url_size(); -} - -bool url::set_host(const std::string_view input) { - return set_host_or_hostname(input); -} - -bool url::set_hostname(const std::string_view input) { - return set_host_or_hostname(input); -} - -bool url::set_username(const std::string_view input) { - if (cannot_have_credentials_or_port()) { - return false; - } - auto previous_username = std::move(username); - username = ada::unicode::percent_encode( - input, character_sets::USERINFO_PERCENT_ENCODE); - if (get_href_size() > ada::get_max_input_length()) { - username = std::move(previous_username); - return false; - } - return true; -} - -bool url::set_password(const std::string_view input) { - if (cannot_have_credentials_or_port()) { - return false; - } - auto previous_password = std::move(password); - password = ada::unicode::percent_encode( - input, character_sets::USERINFO_PERCENT_ENCODE); - if (get_href_size() > ada::get_max_input_length()) { - password = std::move(previous_password); - return false; - } - return true; -} - -bool url::set_port(const std::string_view input) { - if (cannot_have_credentials_or_port()) { - return false; - } - - if (input.empty()) { - port = std::nullopt; - return true; - } - - std::string trimmed(input); - helpers::remove_ascii_tab_or_newline(trimmed); - - if (trimmed.empty()) { - return true; - } - - // Input should not start with a non-digit character. - if (!ada::unicode::is_ascii_digit(trimmed.front())) { - return false; - } - - // Find the first non-digit character to determine the length of digits - auto first_non_digit = - std::ranges::find_if_not(trimmed, ada::unicode::is_ascii_digit); - std::string_view digits_to_parse = - std::string_view(trimmed.data(), first_non_digit - trimmed.begin()); - - // Revert changes if parse_port fails. - std::optional previous_port = port; - parse_port(digits_to_parse); - if (is_valid) { - if (get_href_size() > ada::get_max_input_length()) { - port = std::move(previous_port); - return false; - } - return true; - } - port = std::move(previous_port); - is_valid = true; - return false; -} - -void url::set_hash(const std::string_view input) { - if (input.empty()) { - hash = std::nullopt; - helpers::strip_trailing_spaces_from_opaque_path(*this); - return; - } - - std::string new_value; - new_value = input[0] == '#' ? input.substr(1) : input; - helpers::remove_ascii_tab_or_newline(new_value); - auto previous_hash = std::move(hash); - hash = unicode::percent_encode(new_value, - ada::character_sets::FRAGMENT_PERCENT_ENCODE); - if (get_href_size() > ada::get_max_input_length()) { - hash = std::move(previous_hash); - } -} - -void url::set_search(const std::string_view input) { - if (input.empty()) { - query = std::nullopt; - helpers::strip_trailing_spaces_from_opaque_path(*this); - return; - } - - std::string new_value; - new_value = input[0] == '?' ? input.substr(1) : input; - helpers::remove_ascii_tab_or_newline(new_value); - - auto query_percent_encode_set = - is_special() ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE - : ada::character_sets::QUERY_PERCENT_ENCODE; - - auto previous_query = std::move(query); - query = ada::unicode::percent_encode(new_value, query_percent_encode_set); - if (get_href_size() > ada::get_max_input_length()) { - query = std::move(previous_query); - } -} - -bool url::set_pathname(const std::string_view input) { - if (has_opaque_path) { - return false; - } - auto previous_path = std::move(path); - path.clear(); - parse_path(input); - if (get_href_size() > ada::get_max_input_length()) { - path = std::move(previous_path); - return false; - } - return true; -} - -bool url::set_protocol(const std::string_view input) { - std::string view(input); - helpers::remove_ascii_tab_or_newline(view); - if (view.empty()) { - return true; - } - - // Schemes should start with alpha values. - if (!checkers::is_alpha(view[0])) { - return false; - } - - view.append(":"); - - std::string::iterator pointer = - std::ranges::find_if_not(view, unicode::is_alnum_plus); - - if (pointer != view.end() && *pointer == ':') { - url saved_url(*this); - bool result = parse_scheme( - std::string_view(view.data(), pointer - view.begin())); - if (result && get_href_size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - return result; - } - return false; -} - -bool url::set_href(const std::string_view input) { - ada::result out = ada::parse(input); - - if (out) { - // The parser enforces get_max_input_length() on both the input and the - // normalized result. This is a defense-in-depth check. - if (out->get_href_size() > ada::get_max_input_length()) { - return false; - } - *this = *out; - } - - return out.has_value(); -} - -} // namespace ada -/* end file src/url.cpp */ -/* begin file src/parser.cpp */ - -#include -#include -#include -#include - - -namespace ada::parser { - -// 0 = host byte, 1 = host delimiter (/ ? #), 2 = reject -namespace { - -constexpr std::array k_host_class = []() consteval { - std::array t{}; - for (size_t i = 0; i < 256; ++i) { - t[i] = 2; - } - for (size_t i = 0x21; i <= 0x7E; ++i) { - t[i] = 0; - } - for (uint8_t c : - {'#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) { - t[c] = 2; - } - t[static_cast('/')] = 1; - t[static_cast('?')] = 1; - t[static_cast('#')] = 1; - return t; -}(); - -// 0 = ok, 1 = ?/#, 2 = reject. Path rejects '%' so "%2e" falls through. -constexpr std::array k_rest = []() consteval { - std::array t{}; - for (size_t i = 0; i < 256; ++i) { - t[i] = 2; - } - for (uint8_t c = 0x21; c <= 0x7E; ++c) { - t[c] = 0; - } - for (uint8_t c : {static_cast('"'), static_cast('<'), - static_cast('>'), static_cast('`'), - static_cast('{'), static_cast('}'), - static_cast('^'), static_cast('\\'), - static_cast('%'), static_cast('\'')}) { - t[c] = 2; - } - t[static_cast('?')] = 1; - t[static_cast('#')] = 1; - return t; -}(); - -} // namespace - -// Fast path for already-normalized absolute http(s) URLs. noinline keeps -// the fallthrough path small (IPv4 microbenches). -template -ada_never_inline bool try_parse_simple_absolute(std::string_view input, - result_type& out) { - constexpr bool is_ada_url = std::is_same_v; - constexpr bool is_aggregator = - std::is_same_v; - static_assert(is_ada_url || is_aggregator); - - const size_t len = input.size(); - if (len < 8) [[unlikely]] { - return false; - } - const auto* b = reinterpret_cast(input.data()); - - size_t pos; - ada::scheme::type scheme_type; - uint32_t protocol_end; - if (b[0] == 'h' && b[1] == 't' && b[2] == 't' && b[3] == 'p') { - if (b[4] == ':' && b[5] == '/' && b[6] == '/') { - pos = 7; - scheme_type = ada::scheme::type::HTTP; - protocol_end = 5; - } else if (len >= 8 && b[4] == 's' && b[5] == ':' && b[6] == '/' && - b[7] == '/') { - pos = 8; - scheme_type = ada::scheme::type::HTTPS; - protocol_end = 6; - } else { - return false; - } - } else { - return false; - } - if (pos < len && (b[pos] == '/' || b[pos] == '\\')) [[unlikely]] { - return false; - } - - // Digit-led hosts are IPv4/numeric; skip before scanning. - if (pos < len && b[pos] >= '0' && b[pos] <= '9') { - return false; - } - - const size_t host_start = pos; - bool has_upper = false; - size_t i = pos; - for (; i < len; ++i) { - const uint8_t c = b[i]; - const uint8_t cls = k_host_class[c]; - if (cls == 1) { - break; - } - if (cls == 2) [[unlikely]] { - return false; - } - if (c >= 'A' && c <= 'Z') { - has_upper = true; - } - } - const size_t host_end = i; - if (host_start == host_end) [[unlikely]] { - return false; - } - const size_t host_len = host_end - host_start; - if (host_len > 253) [[unlikely]] { - return false; - } - { - std::string_view hv(input.data() + host_start, host_len); - char host_buf[256]; - if (has_upper) [[unlikely]] { - std::memcpy(host_buf, input.data() + host_start, host_len); - unicode::to_lower_ascii(host_buf, host_len); - hv = std::string_view(host_buf, host_len); - } - if (checkers::is_ipv4(hv)) [[unlikely]] { - return false; - } - static constexpr std::string_view xn{"xn-", 3}; - if (hv.find(xn) != std::string_view::npos) [[unlikely]] { - return false; - } - } - - size_t path_start = host_end; - size_t path_end = host_end; - size_t query_start = std::string_view::npos; - size_t hash_start = std::string_view::npos; - bool has_path = false; - bool path_has_dot = false; - - if (i < len && b[i] == '/') { - has_path = true; - path_start = i; - ++i; - for (; i < len; ++i) { - const uint8_t c = b[i]; - const uint8_t cls = k_rest[c]; - if (cls == 0) { - path_has_dot |= (c == '.'); - continue; - } - if (cls == 1) { - path_end = i; - if (c == '?') { - query_start = i; - ++i; - goto scan_query; - } - hash_start = i; - ++i; - goto scan_hash; - } - return false; - } - path_end = i; - } else if (i < len && b[i] == '?') { - query_start = i; - ++i; - goto scan_query; - } else if (i < len && b[i] == '#') { - hash_start = i; - ++i; - goto scan_hash; - } - goto after_rest; - -scan_query: - for (; i < len; ++i) { - const uint8_t c = b[i]; - if (c == '#') { - hash_start = i; - ++i; - goto scan_hash; - } - if (c == '?' || c == '%') { - continue; - } - if (k_rest[c] == 2) [[unlikely]] { - return false; - } - } - goto after_rest; - -scan_hash: - for (; i < len; ++i) { - const uint8_t c = b[i]; - if (c == '?' || c == '#' || c == '%') { - continue; - } - if (k_rest[c] == 2) [[unlikely]] { - return false; - } - } - -after_rest: - if (path_has_dot) [[unlikely]] { - const std::string_view path_body(input.data() + path_start, - path_end - path_start); - if (path_body.size() >= 2 && path_body[1] == '.') { - if (path_body.size() == 2 || path_body[2] == '/' || - (path_body.size() >= 3 && path_body[2] == '.' && - (path_body.size() == 3 || path_body[3] == '/'))) { - return false; - } - } - static constexpr std::string_view slash_dot{"/.", 2}; - size_t p = 1; - while ((p = path_body.find(slash_dot, p)) != std::string_view::npos) { - const size_t after = p + 2; - if (after == path_body.size() || path_body[after] == '/' || - (after + 1 <= path_body.size() && path_body[after] == '.' && - (after + 1 == path_body.size() || path_body[after + 1] == '/'))) { - return false; - } - p = after; - } - } - - const bool need_slash = !has_path; - out.type = scheme_type; - out.is_valid = true; - out.has_opaque_path = false; - out.host_type = DEFAULT; - - if constexpr (is_aggregator) { - if (!need_slash) { - out.buffer.resize(len); - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - std::memcpy(out.buffer.data(), input.data(), len); - if (has_upper) { - unicode::to_lower_ascii(out.buffer.data() + host_start, - host_end - host_start); - } - out.components.protocol_end = protocol_end; - out.components.username_end = protocol_end + 2; - out.components.host_start = protocol_end + 2; - out.components.host_end = static_cast(host_end); - out.components.port = url_components::omitted; - out.components.pathname_start = static_cast(path_start); - out.components.search_start = (query_start != std::string_view::npos) - ? static_cast(query_start) - : url_components::omitted; - out.components.hash_start = (hash_start != std::string_view::npos) - ? static_cast(hash_start) - : url_components::omitted; - } else { - out.buffer.resize(len + 1); - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - std::memcpy(out.buffer.data(), input.data(), host_end); - out.buffer[host_end] = '/'; - if (host_end < len) { - std::memcpy(out.buffer.data() + host_end + 1, input.data() + host_end, - len - host_end); - } - if (has_upper) { - unicode::to_lower_ascii(out.buffer.data() + host_start, - host_end - host_start); - } - out.components.protocol_end = protocol_end; - out.components.username_end = protocol_end + 2; - out.components.host_start = protocol_end + 2; - out.components.host_end = static_cast(host_end); - out.components.port = url_components::omitted; - out.components.pathname_start = static_cast(host_end); - out.components.search_start = (query_start != std::string_view::npos) - ? static_cast(query_start + 1) - : url_components::omitted; - out.components.hash_start = (hash_start != std::string_view::npos) - ? static_cast(hash_start + 1) - : url_components::omitted; - } - } else { - std::string host_str(input.substr(host_start, host_end - host_start)); - if (has_upper) { - unicode::to_lower_ascii(host_str.data(), host_str.size()); - } - out.host = std::move(host_str); - if (need_slash) { - out.path = "/"; - } else { - out.path.assign(input.data() + path_start, path_end - path_start); - } - if (query_start != std::string_view::npos) { - const size_t q_end = - (hash_start != std::string_view::npos) ? hash_start : len; - out.query.emplace(input.data() + query_start + 1, - q_end - query_start - 1); - } - if (hash_start != std::string_view::npos) { - out.hash.emplace(input.data() + hash_start + 1, len - hash_start - 1); - } - } - return true; -} - -template -result_type parse_url_impl(std::string_view user_input, - const result_type* base_url) { - // We can specialize the implementation per type. - // Important: result_type_is_ada_url is evaluated at *compile time*. This - // means that doing if constexpr(result_type_is_ada_url) { something } else { - // something else } is free (at runtime). This means that ada::url_aggregator - // and ada::url **do not have to support the exact same API**. - constexpr bool result_type_is_ada_url = std::is_same_v; - constexpr bool result_type_is_ada_url_aggregator = - std::is_same_v; - static_assert(result_type_is_ada_url || - result_type_is_ada_url_aggregator); // We don't support - // anything else for now. - - ada_log("ada::parser::parse_url('", user_input, "' [", user_input.size(), - " bytes],", (base_url != nullptr ? base_url->to_string() : "null"), - ")"); - - state state = state::SCHEME_START; - result_type url{}; - - const uint32_t max_input_length = ada::get_max_input_length(); - - // We refuse to parse URL strings that exceed the maximum input length. - // By default, this is 4GB but can be configured via - // ada::set_max_input_length(). - if (user_input.size() > max_input_length) [[unlikely]] { - url.is_valid = false; - } - // Going forward, user_input.size() is in [0, - // std::numeric_limits::max). If we are provided with an invalid - // base, or the optional_url was invalid, we must return. - if (base_url != nullptr) { - url.is_valid &= base_url->is_valid; - } - if (!url.is_valid) { - return url; - } - - // Simple absolute http(s) fast path (before tabs/newline scan). - // Skip digit-led hosts (IPv4) with a cheap peek. - if constexpr (store_values) { - if (base_url == nullptr) { - const auto* p = reinterpret_cast(user_input.data()); - const size_t n = user_input.size(); - const bool digit_led_host = (n >= 8 && p[4] == ':' && p[5] == '/' && - p[6] == '/' && p[7] >= '0' && p[7] <= '9') || - (n >= 9 && p[5] == ':' && p[6] == '/' && - p[7] == '/' && p[8] >= '0' && p[8] <= '9'); - if (!digit_led_host && try_parse_simple_absolute(user_input, url)) { - if constexpr (result_type_is_ada_url_aggregator) { - if (url.buffer.size() > max_input_length) [[unlikely]] { - url.is_valid = false; - } - } else { - if (url.get_href_size() > max_input_length) [[unlikely]] { - url.is_valid = false; - } - } - return url; - } - } - } - - std::string tmp_buffer; - std::string_view url_data; - if (unicode::has_tabs_or_newline(user_input)) [[unlikely]] { - tmp_buffer = user_input; - // Optimization opportunity: Instead of copying and then pruning, we could - // just directly build the string from user_input. - helpers::remove_ascii_tab_or_newline(tmp_buffer); - url_data = tmp_buffer; - } else [[likely]] { - url_data = user_input; - } - - // Leading and trailing control characters are uncommon and easy to deal with - // (no performance concern). - helpers::trim_c0_whitespace(url_data); - - if constexpr (result_type_is_ada_url_aggregator && store_values) { - // Most of the time, we just need user_input.size(). - // In some instances, we may need a bit more. - /////////////////////////// - // This is *very* important. This line should *not* be removed - // hastily. There are principled reasons why reserve is important - // for performance. If you have a benchmark with small inputs, - // it may not matter, but in other instances, it could. - //// - // This rounds up to the next power of two. - // We know that user_input.size() is in [0, - // std::numeric_limits::max). - uint32_t reserve_capacity = - (0xFFFFFFFF >> - helpers::leading_zeroes(uint32_t(1 | user_input.size()))) + - 1; - url.reserve(reserve_capacity); - } - - // Optimization opportunity. Most websites do not have fragment. - std::optional fragment = helpers::prune_hash(url_data); - // We add it last so that an implementation like ada::url_aggregator - // can append it last to its internal buffer, thus improving performance. - - // Here url_data no longer has its fragment. - // We are going to access the data from url_data (it is immutable). - // At any given time, we are pointing at byte 'input_position' in url_data. - // The input_position variable should range from 0 to input_size. - // It is illegal to access url_data at input_size. - size_t input_position = 0; - const size_t input_size = url_data.size(); - // Keep running the following state machine by switching on state. - // If after a run pointer points to the EOF code point, go to the next step. - // Otherwise, increase pointer by 1 and continue with the state machine. - // We never decrement input_position. - while (input_position <= input_size) { - ada_log("In parsing at ", input_position, " out of ", input_size, - " in state ", ada::to_string(state)); - switch (state) { - case state::SCHEME_START: { - ada_log("SCHEME_START ", helpers::substring(url_data, input_position)); - // If c is an ASCII alpha, append c, lowercased, to buffer, and set - // state to scheme state. - if ((input_position != input_size) && - checkers::is_alpha(url_data[input_position])) { - state = state::SCHEME; - input_position++; - } else { - // Otherwise, if state override is not given, set state to no scheme - // state and decrease pointer by 1. - state = state::NO_SCHEME; - } - break; - } - case state::SCHEME: { - ada_log("SCHEME ", helpers::substring(url_data, input_position)); - // If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E (.), - // append c, lowercased, to buffer. - while ((input_position != input_size) && - (unicode::is_alnum_plus(url_data[input_position]))) { - input_position++; - } - // Otherwise, if c is U+003A (:), then: - if ((input_position != input_size) && - (url_data[input_position] == ':')) { - ada_log("SCHEME the scheme should be ", - url_data.substr(0, input_position)); - if constexpr (result_type_is_ada_url) { - if (!url.parse_scheme(url_data.substr(0, input_position))) { - return url; - } - } else { - // we pass the colon along instead of painfully adding it back. - if (!url.parse_scheme_with_colon( - url_data.substr(0, input_position + 1))) { - return url; - } - } - ada_log("SCHEME the scheme is ", url.get_protocol()); - - // If url's scheme is "file", then: - // NOLINTNEXTLINE(bugprone-branch-clone) - if (url.type == scheme::type::FILE) { - // Set state to file state. - state = state::FILE; - } - // Otherwise, if url is special, base is non-null, and base's scheme - // is url's scheme: Note: Doing base_url->scheme is unsafe if base_url - // != nullptr is false. - else if (url.is_special() && base_url != nullptr && - base_url->type == url.type) { - // Set state to special relative or authority state. - state = state::SPECIAL_RELATIVE_OR_AUTHORITY; - } - // Otherwise, if url is special, set state to special authority - // slashes state. - else if (url.is_special()) { - state = state::SPECIAL_AUTHORITY_SLASHES; - } - // Otherwise, if remaining starts with an U+002F (/), set state to - // path or authority state and increase pointer by 1. - else if (input_position + 1 < input_size && - url_data[input_position + 1] == '/') { - state = state::PATH_OR_AUTHORITY; - input_position++; - } - // Otherwise, set url's path to the empty string and set state to - // opaque path state. - else { - state = state::OPAQUE_PATH; - } - } - // Otherwise, if state override is not given, set buffer to the empty - // string, state to no scheme state, and start over (from the first code - // point in input). - else { - state = state::NO_SCHEME; - input_position = 0; - break; - } - input_position++; - break; - } - case state::NO_SCHEME: { - ada_log("NO_SCHEME ", helpers::substring(url_data, input_position)); - // The fragment was pruned from url_data before the state machine ran, - // so 'c is U+0023 (#)' holds exactly when a fragment was found and - // nothing else remains in front of it. - const bool c_is_hash = - fragment.has_value() && input_position == input_size; - // If base is null, or base has an opaque path and c is not U+0023 (#), - // validation error, return failure. - if (base_url == nullptr || (base_url->has_opaque_path && !c_is_hash)) { - ada_log("NO_SCHEME validation error"); - url.is_valid = false; - return url; - } - // Otherwise, if base has an opaque path and c is U+0023 (#), - // set url's scheme to base's scheme, url's path to base's path, url's - // query to base's query, and set state to fragment state. - else if (base_url->has_opaque_path && c_is_hash) { - ada_log("NO_SCHEME opaque base with fragment"); - url.copy_scheme(*base_url); - url.has_opaque_path = base_url->has_opaque_path; - - if constexpr (result_type_is_ada_url) { - url.path = base_url->path; - url.query = base_url->query; - } else { - url.update_base_pathname(base_url->get_pathname()); - if (base_url->has_search()) { - // get_search() returns "" for an empty query string (URL ends - // with '?'). update_base_search("") would incorrectly clear the - // query, so pass "?" to preserve the empty query distinction. - auto s = base_url->get_search(); - url.update_base_search(s.empty() ? std::string_view("?") : s); - } - } - url.update_unencoded_base_hash(*fragment); - return url; - } - // Otherwise, if base's scheme is not "file", set state to relative - // state and decrease pointer by 1. - // NOLINTNEXTLINE(bugprone-branch-clone) - else if (base_url->type != scheme::type::FILE) { - ada_log("NO_SCHEME non-file relative path"); - state = state::RELATIVE_SCHEME; - } - // Otherwise, set state to file state and decrease pointer by 1. - else { - ada_log("NO_SCHEME file base type"); - state = state::FILE; - } - break; - } - case state::AUTHORITY: { - ada_log("AUTHORITY ", helpers::substring(url_data, input_position)); - // most URLs have no @. Having no @ tells us that we don't have to worry - // about AUTHORITY. Of course, we could have @ and still not have to - // worry about AUTHORITY. - // TODO: Instead of just collecting a bool, collect the location of the - // '@' and do something useful with it. - // TODO: We could do various processing early on, using a single pass - // over the string to collect information about it, e.g., telling us - // whether there is a @ and if so, where (or how many). - - // Check if url data contains an @. - if (url_data.find('@', input_position) == std::string_view::npos) { - state = state::HOST; - break; - } - bool at_sign_seen{false}; - bool password_token_seen{false}; - /** - * We expect something of the sort... - * https://user:pass@example.com:1234/foo/bar?baz#quux - * --------^ - */ - do { - std::string_view view = url_data.substr(input_position); - // The delimiters are @, /, ? \\. - size_t location = - url.is_special() ? helpers::find_authority_delimiter_special(view) - : helpers::find_authority_delimiter(view); - std::string_view authority_view = view.substr(0, location); - size_t end_of_authority = input_position + authority_view.size(); - // If c is U+0040 (@), then: - if ((end_of_authority != input_size) && - (url_data[end_of_authority] == '@')) { - // If atSignSeen is true, then prepend "%40" to buffer. - if (at_sign_seen) { - if (password_token_seen) { - if constexpr (result_type_is_ada_url) { - url.password += "%40"; - } else { - url.append_base_password("%40"); - } - } else { - if constexpr (result_type_is_ada_url) { - url.username += "%40"; - } else { - url.append_base_username("%40"); - } - } - } - - at_sign_seen = true; - - if (!password_token_seen) { - size_t password_token_location = authority_view.find(':'); - password_token_seen = - password_token_location != std::string_view::npos; - - if constexpr (store_values) { - if (!password_token_seen) { - if constexpr (result_type_is_ada_url) { - url.username += unicode::percent_encode( - authority_view, - character_sets::USERINFO_PERCENT_ENCODE); - } else { - url.append_base_username(unicode::percent_encode( - authority_view, - character_sets::USERINFO_PERCENT_ENCODE)); - } - } else { - if constexpr (result_type_is_ada_url) { - url.username += unicode::percent_encode( - authority_view.substr(0, password_token_location), - character_sets::USERINFO_PERCENT_ENCODE); - url.password += unicode::percent_encode( - authority_view.substr(password_token_location + 1), - character_sets::USERINFO_PERCENT_ENCODE); - } else { - url.append_base_username(unicode::percent_encode( - authority_view.substr(0, password_token_location), - character_sets::USERINFO_PERCENT_ENCODE)); - url.append_base_password(unicode::percent_encode( - authority_view.substr(password_token_location + 1), - character_sets::USERINFO_PERCENT_ENCODE)); - } - } - } - } else if constexpr (store_values) { - if constexpr (result_type_is_ada_url) { - url.password += unicode::percent_encode( - authority_view, character_sets::USERINFO_PERCENT_ENCODE); - } else { - url.append_base_password(unicode::percent_encode( - authority_view, character_sets::USERINFO_PERCENT_ENCODE)); - } - } - } - // Otherwise, if one of the following is true: - // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) - // - url is special and c is U+005C (\) - else if (end_of_authority == input_size || - url_data[end_of_authority] == '/' || - url_data[end_of_authority] == '?' || - (url.is_special() && url_data[end_of_authority] == '\\')) { - // If atSignSeen is true and authority_view is the empty string, - // validation error, return failure. - if (at_sign_seen && authority_view.empty()) { - url.is_valid = false; - return url; - } - state = state::HOST; - break; - } - if (end_of_authority == input_size) { - if constexpr (store_values) { - if (fragment.has_value()) { - url.update_unencoded_base_hash(*fragment); - } - } - return url; - } - input_position = end_of_authority + 1; - } while (true); - - break; - } - case state::SPECIAL_RELATIVE_OR_AUTHORITY: { - ada_log("SPECIAL_RELATIVE_OR_AUTHORITY ", - helpers::substring(url_data, input_position)); - - // If c is U+002F (/) and remaining starts with U+002F (/), - // then set state to special authority ignore slashes state and increase - // pointer by 1. - if (url_data.substr(input_position, 2) == "//") { - state = state::SPECIAL_AUTHORITY_IGNORE_SLASHES; - input_position += 2; - } else { - // Otherwise, validation error, set state to relative state and - // decrease pointer by 1. - state = state::RELATIVE_SCHEME; - } - - break; - } - case state::PATH_OR_AUTHORITY: { - ada_log("PATH_OR_AUTHORITY ", - helpers::substring(url_data, input_position)); - - // If c is U+002F (/), then set state to authority state. - if ((input_position != input_size) && - (url_data[input_position] == '/')) { - state = state::AUTHORITY; - input_position++; - } else { - // Otherwise, set state to path state, and decrease pointer by 1. - state = state::PATH; - } - - break; - } - case state::RELATIVE_SCHEME: { - ada_log("RELATIVE_SCHEME ", - helpers::substring(url_data, input_position)); - - // Set url's scheme to base's scheme. - url.copy_scheme(*base_url); - - // If c is U+002F (/), then set state to relative slash state. - if ((input_position != input_size) && - // NOLINTNEXTLINE(bugprone-branch-clone) - (url_data[input_position] == '/')) { - ada_log( - "RELATIVE_SCHEME if c is U+002F (/), then set state to relative " - "slash state"); - state = state::RELATIVE_SLASH; - } else if (url.is_special() && (input_position != input_size) && - (url_data[input_position] == '\\')) { - // Otherwise, if url is special and c is U+005C (\), validation error, - // set state to relative slash state. - ada_log( - "RELATIVE_SCHEME if url is special and c is U+005C, validation " - "error, set state to relative slash state"); - state = state::RELATIVE_SLASH; - } else { - ada_log("RELATIVE_SCHEME otherwise"); - // Set url's username to base's username, url's password to base's - // password, url's host to base's host, url's port to base's port, - // url's path to a clone of base's path, and url's query to base's - // query. - if constexpr (result_type_is_ada_url) { - url.username = base_url->username; - url.password = base_url->password; - url.host = base_url->host; - url.port = base_url->port; - // cloning the base path includes cloning the has_opaque_path flag - url.has_opaque_path = base_url->has_opaque_path; - url.path = base_url->path; - url.query = base_url->query; - } else { - url.update_base_authority(base_url->get_href(), - base_url->get_components()); - url.update_host_to_base_host(base_url->get_hostname()); - url.update_base_port(base_url->retrieve_base_port()); - // cloning the base path includes cloning the has_opaque_path flag - url.has_opaque_path = base_url->has_opaque_path; - url.update_base_pathname(base_url->get_pathname()); - if (base_url->has_search()) { - // get_search() returns "" for an empty query string (URL ends - // with '?'). update_base_search("") would incorrectly clear the - // query, so pass "?" to preserve the empty query distinction. - auto s = base_url->get_search(); - url.update_base_search(s.empty() ? std::string_view("?") : s); - } - } - - url.has_opaque_path = base_url->has_opaque_path; - - // If c is U+003F (?), then set url's query to the empty string, and - // state to query state. - if ((input_position != input_size) && - (url_data[input_position] == '?')) { - state = state::QUERY; - } - // Otherwise, if c is not the EOF code point: - else if (input_position != input_size) { - // Set url's query to null. - url.clear_search(); - if constexpr (result_type_is_ada_url) { - // Shorten url's path. - helpers::shorten_path(url.path, url.type); - } else { - std::string_view path = url.get_pathname(); - if (helpers::shorten_path(path, url.type)) { - url.update_base_pathname(std::move(std::string(path))); - } - } - // Set state to path state and decrease pointer by 1. - state = state::PATH; - break; - } - } - input_position++; - break; - } - case state::RELATIVE_SLASH: { - ada_log("RELATIVE_SLASH ", - helpers::substring(url_data, input_position)); - - // If url is special and c is U+002F (/) or U+005C (\), then: - // NOLINTNEXTLINE(bugprone-branch-clone) - if (url.is_special() && (input_position != input_size) && - (url_data[input_position] == '/' || - url_data[input_position] == '\\')) { - // Set state to special authority ignore slashes state. - state = state::SPECIAL_AUTHORITY_IGNORE_SLASHES; - } - // Otherwise, if c is U+002F (/), then set state to authority state. - else if ((input_position != input_size) && - (url_data[input_position] == '/')) { - state = state::AUTHORITY; - } - // Otherwise, set - // - url's username to base's username, - // - url's password to base's password, - // - url's host to base's host, - // - url's port to base's port, - // - state to path state, and then, decrease pointer by 1. - else { - if constexpr (result_type_is_ada_url) { - url.username = base_url->username; - url.password = base_url->password; - url.host = base_url->host; - url.port = base_url->port; - } else { - url.update_base_authority(base_url->get_href(), - base_url->get_components()); - url.update_host_to_base_host(base_url->get_hostname()); - url.update_base_port(base_url->retrieve_base_port()); - } - state = state::PATH; - break; - } - - input_position++; - break; - } - case state::SPECIAL_AUTHORITY_SLASHES: { - ada_log("SPECIAL_AUTHORITY_SLASHES ", - helpers::substring(url_data, input_position)); - - // If c is U+002F (/) and remaining starts with U+002F (/), - // then set state to special authority ignore slashes state and increase - // pointer by 1. - if (url_data.substr(input_position, 2) == "//") { - input_position += 2; - } - - [[fallthrough]]; - } - case state::SPECIAL_AUTHORITY_IGNORE_SLASHES: { - ada_log("SPECIAL_AUTHORITY_IGNORE_SLASHES ", - helpers::substring(url_data, input_position)); - - // If c is neither U+002F (/) nor U+005C (\), then set state to - // authority state and decrease pointer by 1. - while ((input_position != input_size) && - ((url_data[input_position] == '/') || - (url_data[input_position] == '\\'))) { - input_position++; - } - state = state::AUTHORITY; - - break; - } - case state::QUERY: { - ada_log("QUERY ", helpers::substring(url_data, input_position)); - if constexpr (store_values) { - // Let queryPercentEncodeSet be the special-query percent-encode set - // if url is special; otherwise the query percent-encode set. - const uint8_t* query_percent_encode_set = - url.is_special() ? character_sets::SPECIAL_QUERY_PERCENT_ENCODE - : character_sets::QUERY_PERCENT_ENCODE; - - // Percent-encode after encoding, with encoding, buffer, and - // queryPercentEncodeSet, and append the result to url's query. - url.update_base_search(url_data.substr(input_position), - query_percent_encode_set); - ada_log("QUERY update_base_search completed "); - if (fragment.has_value()) { - url.update_unencoded_base_hash(*fragment); - } - } - return url; - } - case state::HOST: { - ada_log("HOST ", helpers::substring(url_data, input_position)); - - std::string_view host_view = url_data.substr(input_position); - auto [location, found_colon] = - helpers::get_host_delimiter_location(url.is_special(), host_view); - input_position = (location != std::string_view::npos) - ? input_position + location - : input_size; - // Otherwise, if c is U+003A (:) and insideBrackets is false, then: - // Note: the 'found_colon' value is true if and only if a colon was - // encountered while not inside brackets. - if (found_colon) { - // If buffer is the empty string, validation error, return failure. - // Let host be the result of host parsing buffer with url is not - // special. - ada_log("HOST parsing ", host_view); - if (!url.parse_host(host_view)) { - return url; - } - ada_log("HOST parsing results in ", url.get_hostname()); - // Set url's host to host, buffer to the empty string, and state to - // port state. - state = state::PORT; - input_position++; - } - // Otherwise, if one of the following is true: - // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) - // - url is special and c is U+005C (\) - // The get_host_delimiter_location function either brings us to - // the colon outside of the bracket, or to one of those characters. - else { - // If url is special and host_view is the empty string, validation - // error, return failure. - if (host_view.empty() && url.is_special()) { - url.is_valid = false; - return url; - } - ada_log("HOST parsing ", host_view, " href=", url.get_href()); - // Let host be the result of host parsing host_view with url is not - // special. - if (host_view.empty()) { - url.update_base_hostname(""); - } else if (!url.parse_host(host_view)) { - return url; - } - ada_log("HOST parsing results in ", url.get_hostname(), - " href=", url.get_href()); - - // Set url's host to host, and state to path start state. - state = state::PATH_START; - } - - break; - } - case state::OPAQUE_PATH: { - ada_log("OPAQUE_PATH ", helpers::substring(url_data, input_position)); - // Opaque path, query, and fragment are structurally always valid: - // the parser would just percent-encode whatever is there. When we - // are not storing values (can_parse), we can return immediately. - // We must set has_opaque_path = true before returning so that when - // this URL is used as an internal base inside can_parse, NO_SCHEME - // correctly rejects relative inputs against an opaque-path base - // (e.g. can_parse("", &"W:") must return false). - if constexpr (!store_values) { - url.has_opaque_path = true; - return url; - } - std::string_view view = url_data.substr(input_position); - // If c is U+003F (?), then set url's query to the empty string and - // state to query state. - size_t location = view.find('?'); - if (location != std::string_view::npos) { - view.remove_suffix(view.size() - location); - state = state::QUERY; - input_position += location + 1; - } else { - input_position = input_size + 1; - } - url.has_opaque_path = true; - - // This is a really unlikely scenario in real world. We should not seek - // to optimize it. - if (view.ends_with(' ')) { - std::string modified_view = - std::string(view.substr(0, view.size() - 1)) + "%20"; - url.update_base_pathname(unicode::percent_encode( - modified_view, character_sets::C0_CONTROL_PERCENT_ENCODE)); - } else { - url.update_base_pathname(unicode::percent_encode( - view, character_sets::C0_CONTROL_PERCENT_ENCODE)); - } - break; - } - case state::PORT: { - ada_log("PORT ", helpers::substring(url_data, input_position)); - std::string_view port_view = url_data.substr(input_position); - input_position += url.parse_port(port_view, true); - if (!url.is_valid) { - return url; - } - state = state::PATH_START; - [[fallthrough]]; - } - case state::PATH_START: { - ada_log("PATH_START ", helpers::substring(url_data, input_position)); - // Path, query, and fragment are structurally always valid: the - // parser would just percent-encode whatever is there. When we are - // not storing values (can_parse), we can return immediately since - // no subsequent state can invalidate the URL. - if constexpr (!store_values) { - return url; - } - - // If url is special, then: - if (url.is_special()) { - // Set state to path state. - state = state::PATH; - - // Optimization: Avoiding going into PATH state improves the - // performance of urls ending with /. - if (input_position == input_size) { - if constexpr (store_values) { - url.update_base_pathname("/"); - if (fragment.has_value()) { - url.update_unencoded_base_hash(*fragment); - } - } - return url; - } - // If c is neither U+002F (/) nor U+005C (\), then decrease pointer - // by 1. We know that (input_position == input_size) is impossible - // here, because of the previous if-check. - if ((url_data[input_position] != '/') && - (url_data[input_position] != '\\')) { - break; - } - } - // Otherwise, if state override is not given and c is U+003F (?), - // set url's query to the empty string and state to query state. - else if ((input_position != input_size) && - (url_data[input_position] == '?')) { - state = state::QUERY; - } - // Otherwise, if c is not the EOF code point: - else if (input_position != input_size) { - // Set state to path state. - state = state::PATH; - - // If c is not U+002F (/), then decrease pointer by 1. - if (url_data[input_position] != '/') { - break; - } - } - - input_position++; - break; - } - case state::PATH: { - ada_log("PATH ", helpers::substring(url_data, input_position)); - // Path, query, and fragment are structurally always valid: the - // parser would just percent-encode whatever is there. When we are - // not storing values (can_parse), we can return immediately since - // no subsequent state can invalidate the URL. - if constexpr (!store_values) { - return url; - } - std::string_view view = url_data.substr(input_position); - - // Most time, we do not need percent encoding. - // Furthermore, we can immediately locate the '?'. - size_t locofquestionmark = view.find('?'); - if (locofquestionmark != std::string_view::npos) { - state = state::QUERY; - view.remove_suffix(view.size() - locofquestionmark); - input_position += locofquestionmark + 1; - } else { - input_position = input_size + 1; - } - if constexpr (store_values) { - if constexpr (result_type_is_ada_url) { - helpers::parse_prepared_path(view, url.type, url.path); - } else { - url.consume_prepared_path(view); - ADA_ASSERT_TRUE(url.validate()); - } - } - break; - } - case state::FILE_SLASH: { - ada_log("FILE_SLASH ", helpers::substring(url_data, input_position)); - - // If c is U+002F (/) or U+005C (\), then: - if ((input_position != input_size) && - (url_data[input_position] == '/' || - url_data[input_position] == '\\')) { - ada_log("FILE_SLASH c is U+002F or U+005C"); - // Set state to file host state. - state = state::FILE_HOST; - input_position++; - } else { - ada_log("FILE_SLASH otherwise"); - // If base is non-null and base's scheme is "file", then: - // Note: it is unsafe to do base_url->scheme unless you know that - // base_url_has_value() is true. - if (base_url != nullptr && base_url->type == scheme::type::FILE) { - // Set url's host to base's host. - if constexpr (result_type_is_ada_url) { - url.host = base_url->host; - } else { - url.update_host_to_base_host(base_url->get_host()); - } - // If the code point substring from pointer to the end of input does - // not start with a Windows drive letter and base's path[0] is a - // normalized Windows drive letter, then append base's path[0] to - // url's path. - if (!base_url->get_pathname().empty()) { - if (!checkers::is_windows_drive_letter( - url_data.substr(input_position))) { - std::string_view first_base_url_path = - base_url->get_pathname().substr(1); - size_t loc = first_base_url_path.find('/'); - if (loc != std::string_view::npos) { - helpers::resize(first_base_url_path, loc); - } - if (checkers::is_normalized_windows_drive_letter( - first_base_url_path)) { - if constexpr (result_type_is_ada_url) { - url.path += '/'; - url.path += first_base_url_path; - } else { - url.append_base_pathname( - helpers::concat("/", first_base_url_path)); - } - } - } - } - } - - // Set state to path state, and decrease pointer by 1. - state = state::PATH; - } - - break; - } - case state::FILE_HOST: { - ada_log("FILE_HOST ", helpers::substring(url_data, input_position)); - std::string_view view = url_data.substr(input_position); - - size_t location = view.find_first_of("/\\?"); - std::string_view file_host_buffer = view.substr( - 0, (location != std::string_view::npos) ? location : view.size()); - - if (checkers::is_windows_drive_letter(file_host_buffer)) { - state = state::PATH; - } else if (file_host_buffer.empty()) { - // Set url's host to the empty string. - if constexpr (result_type_is_ada_url) { - url.host = ""; - } else { - url.update_base_hostname(""); - } - // Set state to path start state. - state = state::PATH_START; - } else { - size_t consumed_bytes = file_host_buffer.size(); - input_position += consumed_bytes; - // Let host be the result of host parsing buffer with url is not - // special. - if (!url.parse_host(file_host_buffer)) { - return url; - } - - if constexpr (result_type_is_ada_url) { - // If host is "localhost", then set host to the empty string. - if (url.host.has_value() && url.host.value() == "localhost") { - url.host = ""; - } - } else { - if (url.get_hostname() == "localhost") { - url.update_base_hostname(""); - } - } - - // Set buffer to the empty string and state to path start state. - state = state::PATH_START; - } - - break; - } - case state::FILE: { - ada_log("FILE ", helpers::substring(url_data, input_position)); - std::string_view file_view = url_data.substr(input_position); - - url.set_protocol_as_file(); - if constexpr (result_type_is_ada_url) { - // Set url's host to the empty string. - url.host = ""; - } else { - url.update_base_hostname(""); - } - // If c is U+002F (/) or U+005C (\), then: - if (input_position != input_size && - (url_data[input_position] == '/' || - url_data[input_position] == '\\')) { - ada_log("FILE c is U+002F or U+005C"); - // Set state to file slash state. - state = state::FILE_SLASH; - } - // Otherwise, if base is non-null and base's scheme is "file": - else if (base_url != nullptr && base_url->type == scheme::type::FILE) { - // Set url's host to base's host, url's path to a clone of base's - // path, and url's query to base's query. - ada_log("FILE base non-null"); - if constexpr (result_type_is_ada_url) { - url.host = base_url->host; - url.path = base_url->path; - url.query = base_url->query; - } else { - url.update_host_to_base_host(base_url->get_hostname()); - url.update_base_pathname(base_url->get_pathname()); - if (base_url->has_search()) { - // get_search() returns "" for an empty query string (URL ends - // with '?'). update_base_search("") would incorrectly clear the - // query, so pass "?" to preserve the empty query distinction. - auto s = base_url->get_search(); - url.update_base_search(s.empty() ? std::string_view("?") : s); - } - } - url.has_opaque_path = base_url->has_opaque_path; - - // If c is U+003F (?), then set url's query to the empty string and - // state to query state. - if (input_position != input_size && url_data[input_position] == '?') { - state = state::QUERY; - } - // Otherwise, if c is not the EOF code point: - else if (input_position != input_size) { - // Set url's query to null. - url.clear_search(); - // If the code point substring from pointer to the end of input does - // not start with a Windows drive letter, then shorten url's path. - if (!checkers::is_windows_drive_letter(file_view)) { - if constexpr (result_type_is_ada_url) { - helpers::shorten_path(url.path, url.type); - } else { - std::string_view path = url.get_pathname(); - if (helpers::shorten_path(path, url.type)) { - url.update_base_pathname(std::move(std::string(path))); - } - } - } - // Otherwise: - else { - // Set url's path to an empty list. - url.clear_pathname(); - url.has_opaque_path = true; - } - - // Set state to path state and decrease pointer by 1. - state = state::PATH; - break; - } - } - // Otherwise, set state to path state, and decrease pointer by 1. - else { - ada_log("FILE go to path"); - state = state::PATH; - break; - } - - input_position++; - break; - } - default: - unreachable(); - } - } - if constexpr (store_values) { - if (fragment.has_value()) { - url.update_unencoded_base_hash(*fragment); - } - } - // Check the resulting (normalized) URL size against the maximum input length. - // Normalization (percent-encoding, IDNA, etc.) can expand the URL beyond the - // original input size. - if constexpr (store_values) { - if (url.is_valid) { - if constexpr (result_type_is_ada_url_aggregator) { - if (url.buffer.size() > max_input_length) { - url.is_valid = false; - } - } else { - if (url.get_href_size() > max_input_length) { - url.is_valid = false; - } - } - } - } - return url; -} - -template url parse_url_impl(std::string_view user_input, - const url* base_url = nullptr); -template url_aggregator parse_url_impl( - std::string_view user_input, const url_aggregator* base_url = nullptr); -template url_aggregator parse_url_impl( - std::string_view user_input, const url_aggregator* base_url = nullptr); - -template -result_type parse_url(std::string_view user_input, - const result_type* base_url) { - return parse_url_impl(user_input, base_url); -} - -template url parse_url(std::string_view user_input, - const url* base_url = nullptr); -template url_aggregator parse_url( - std::string_view user_input, const url_aggregator* base_url = nullptr); -} // namespace ada::parser -/* end file src/parser.cpp */ -/* begin file src/url_components.cpp */ - -#include -#include - -namespace ada { - -[[nodiscard]] std::string url_components::to_string() const { - std::string answer; - auto back = std::back_insert_iterator(answer); - answer.append("{\n"); - - answer.append("\t\"protocol_end\":\""); - helpers::encode_json(std::to_string(protocol_end), back); - answer.append("\",\n"); - - answer.append("\t\"username_end\":\""); - helpers::encode_json(std::to_string(username_end), back); - answer.append("\",\n"); - - answer.append("\t\"host_start\":\""); - helpers::encode_json(std::to_string(host_start), back); - answer.append("\",\n"); - - answer.append("\t\"host_end\":\""); - helpers::encode_json(std::to_string(host_end), back); - answer.append("\",\n"); - - answer.append("\t\"port\":\""); - helpers::encode_json(std::to_string(port), back); - answer.append("\",\n"); - - answer.append("\t\"pathname_start\":\""); - helpers::encode_json(std::to_string(pathname_start), back); - answer.append("\",\n"); - - answer.append("\t\"search_start\":\""); - helpers::encode_json(std::to_string(search_start), back); - answer.append("\",\n"); - - answer.append("\t\"hash_start\":\""); - helpers::encode_json(std::to_string(hash_start), back); - answer.append("\",\n"); - - answer.append("\n}"); - return answer; -} - -} // namespace ada -/* end file src/url_components.cpp */ -/* begin file src/url_aggregator.cpp */ - -#include -#include -#include -#include -#include -#include -#include - -namespace { - -ada_really_inline void apply_shifted_non_scheme_offsets( - ada::url_components& components, uint32_t new_difference) { - components.username_end += new_difference; - components.host_start += new_difference; - components.host_end += new_difference; - components.pathname_start += new_difference; - if (components.search_start != ada::url_components::omitted) { - components.search_start += new_difference; - } - if (components.hash_start != ada::url_components::omitted) { - components.hash_start += new_difference; - } -} - -} // namespace - -namespace ada { -template -[[nodiscard]] ada_really_inline bool url_aggregator::parse_scheme_with_colon( - const std::string_view input_with_colon) { - ada_log("url_aggregator::parse_scheme_with_colon ", input_with_colon); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input_with_colon, buffer)); - std::string_view input{input_with_colon}; - input.remove_suffix(1); - auto parsed_type = ada::scheme::get_scheme_type(input); - const bool is_input_special = (parsed_type != ada::scheme::NOT_SPECIAL); - /** - * In the common case, we will immediately recognize a special scheme (e.g., - *http, https), in which case, we can go really fast. - **/ - if (is_input_special) { // fast path!!! - if constexpr (has_state_override) { - // If url's scheme is not a special scheme and buffer is a special scheme, - // then return. - if (is_special() != is_input_special) { - return false; - } - - // If url includes credentials or has a non-null port, and buffer is - // "file", then return. - if ((has_credentials() || components.port != url_components::omitted) && - parsed_type == ada::scheme::type::FILE) { - return false; - } - - // If url's scheme is "file" and its host is an empty host, then return. - // An empty host is the empty string. - if (type == ada::scheme::type::FILE && - components.host_start == components.host_end) { - return false; - } - } - - type = parsed_type; - set_scheme_from_view_with_colon(input_with_colon); - - if constexpr (has_state_override) { - // This is uncommon. - uint16_t urls_scheme_port = get_special_port(); - - if (urls_scheme_port) { - // If url's port is url's scheme's default port, then set url's port to - // null. - if (components.port == urls_scheme_port) { - clear_port(); - } - } - } - } else { // slow path - std::string _buffer(input); - // Next function is only valid if the input is ASCII and returns false - // otherwise, but it seems that we always have ascii content so we do not - // need to check the return value. - unicode::to_lower_ascii(_buffer.data(), _buffer.size()); - - if constexpr (has_state_override) { - // The state-override validation errors below ("return" in the WHATWG URL - // parser) leave the URL unchanged. The setter contract is - // "true on success, false if the scheme is invalid" -- the fast path - // above already returns false here, so the slow path must agree. - - // If url's scheme is a special scheme and buffer is not a special scheme, - // then return. If url's scheme is not a special scheme and buffer is a - // special scheme, then return. - if (is_special() != ada::scheme::is_special(_buffer)) { - return false; - } - - // If url includes credentials or has a non-null port, and buffer is - // "file", then return. - if ((has_credentials() || components.port != url_components::omitted) && - _buffer == "file") { - return false; - } - - // If url's scheme is "file" and its host is an empty host, then return. - // An empty host is the empty string. - if (type == ada::scheme::type::FILE && - components.host_start == components.host_end) { - return false; - } - } - - set_scheme(_buffer); - - if constexpr (has_state_override) { - // This is uncommon. - uint16_t urls_scheme_port = get_special_port(); - - if (urls_scheme_port) { - // If url's port is url's scheme's default port, then set url's port to - // null. - if (components.port == urls_scheme_port) { - clear_port(); - } - } - } - } - ADA_ASSERT_TRUE(validate()); - return true; -} - -inline void url_aggregator::copy_scheme(const url_aggregator& u) { - ada_log("url_aggregator::copy_scheme ", u.buffer); - ADA_ASSERT_TRUE(validate()); - // next line could overflow but unsigned arithmetic has well-defined - // overflows. - uint32_t new_difference = u.components.protocol_end - components.protocol_end; - - type = u.type; - buffer.erase(0, components.protocol_end); - buffer.insert(0, u.get_protocol()); - components.protocol_end = u.components.protocol_end; - - // No need to update the components - if (new_difference == 0) { - return; - } - - apply_shifted_non_scheme_offsets(components, new_difference); - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::set_scheme_from_view_with_colon( - std::string_view new_scheme_with_colon) { - ada_log("url_aggregator::set_scheme_from_view_with_colon ", - new_scheme_with_colon); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!new_scheme_with_colon.empty() && - new_scheme_with_colon.back() == ':'); - // next line could overflow but unsigned arithmetic has well-defined - // overflows. - uint32_t new_difference = - uint32_t(new_scheme_with_colon.size()) - components.protocol_end; - - if (buffer.empty()) { - buffer.append(new_scheme_with_colon); - } else { - buffer.erase(0, components.protocol_end); - buffer.insert(0, new_scheme_with_colon); - } - components.protocol_end += new_difference; - - apply_shifted_non_scheme_offsets(components, new_difference); - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::set_scheme(std::string_view new_scheme) { - ada_log("url_aggregator::set_scheme ", new_scheme); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(new_scheme.empty() || new_scheme.back() != ':'); - // next line could overflow but unsigned arithmetic has well-defined - // overflows. - uint32_t new_difference = - uint32_t(new_scheme.size()) - components.protocol_end + 1; - - type = ada::scheme::get_scheme_type(new_scheme); - if (buffer.empty()) { - buffer.append(helpers::concat(new_scheme, ":")); - } else { - buffer.erase(0, components.protocol_end); - buffer.insert(0, helpers::concat(new_scheme, ":")); - } - components.protocol_end = uint32_t(new_scheme.size() + 1); - - apply_shifted_non_scheme_offsets(components, new_difference); - ADA_ASSERT_TRUE(validate()); -} - -bool url_aggregator::set_protocol(const std::string_view input) { - ada_log("url_aggregator::set_protocol ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - std::string view(input); - helpers::remove_ascii_tab_or_newline(view); - if (view.empty()) { - return true; - } - - // Schemes should start with alpha values. - if (!checkers::is_alpha(view[0])) { - return false; - } - - view.append(":"); - - std::string::iterator pointer = - std::ranges::find_if_not(view, unicode::is_alnum_plus); - - if (pointer != view.end() && *pointer == ':') { - url_aggregator saved_url(*this); - bool result = parse_scheme_with_colon( - view.substr(0, pointer - view.begin() + 1)); - if (result && buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - return result; - } - return false; -} - -bool url_aggregator::set_username(const std::string_view input) { - ada_log("url_aggregator::set_username '", input, "' "); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (cannot_have_credentials_or_port()) { - return false; - } - url_aggregator saved_url(*this); - size_t idx = ada::unicode::percent_encode_index( - input, character_sets::USERINFO_PERCENT_ENCODE); - if (idx == input.size()) { - update_base_username(input); - } else { - // We only create a temporary string if we have to! - update_base_username(ada::unicode::percent_encode( - input, character_sets::USERINFO_PERCENT_ENCODE, idx)); - } - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - ADA_ASSERT_TRUE(validate()); - return true; -} - -bool url_aggregator::set_password(const std::string_view input) { - ada_log("url_aggregator::set_password '", input, "'"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (cannot_have_credentials_or_port()) { - return false; - } - url_aggregator saved_url(*this); - size_t idx = ada::unicode::percent_encode_index( - input, character_sets::USERINFO_PERCENT_ENCODE); - if (idx == input.size()) { - update_base_password(input); - } else { - // We only create a temporary string if we have to! - update_base_password(ada::unicode::percent_encode( - input, character_sets::USERINFO_PERCENT_ENCODE, idx)); - } - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - ADA_ASSERT_TRUE(validate()); - return true; -} - -bool url_aggregator::set_port(const std::string_view input) { - ada_log("url_aggregator::set_port ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (cannot_have_credentials_or_port()) { - return false; - } - - if (input.empty()) { - clear_port(); - return true; - } - - std::string trimmed(input); - helpers::remove_ascii_tab_or_newline(trimmed); - - if (trimmed.empty()) { - return true; - } - - // Input should not start with a non-digit character. - if (!ada::unicode::is_ascii_digit(trimmed.front())) { - return false; - } - - // Find the first non-digit character to determine the length of digits - auto first_non_digit = - std::ranges::find_if_not(trimmed, ada::unicode::is_ascii_digit); - std::string_view digits_to_parse = - std::string_view(trimmed.data(), first_non_digit - trimmed.begin()); - - // Revert changes if parse_port fails. - url_aggregator saved_url(*this); - parse_port(digits_to_parse); - if (is_valid) { - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - return true; - } - *this = std::move(saved_url); - is_valid = true; - ADA_ASSERT_TRUE(validate()); - return false; -} - -bool url_aggregator::set_pathname(const std::string_view input) { - ada_log("url_aggregator::set_pathname ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (has_opaque_path) { - return false; - } - url_aggregator saved_url(*this); - clear_pathname(); - parse_path(input); - if (get_pathname().starts_with("//") && !has_authority() && !has_dash_dot()) { - buffer.insert(components.pathname_start, "/."); - components.pathname_start += 2; - if (components.search_start != url_components::omitted) { - components.search_start += 2; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += 2; - } - } - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - ADA_ASSERT_TRUE(validate()); - return true; -} - -ada_really_inline void url_aggregator::parse_path(std::string_view input) { - ada_log("url_aggregator::parse_path ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - std::string tmp_buffer; - std::string_view internal_input; - if (unicode::has_tabs_or_newline(input)) { - tmp_buffer = input; - // Optimization opportunity: Instead of copying and then pruning, we could - // just directly build the string from user_input. - helpers::remove_ascii_tab_or_newline(tmp_buffer); - internal_input = tmp_buffer; - } else { - internal_input = input; - } - - // If url is special, then: - if (is_special()) { - if (internal_input.empty()) { - update_base_pathname("/"); - } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) { - consume_prepared_path(internal_input.substr(1)); - } else { - consume_prepared_path(internal_input); - } - } else if (!internal_input.empty()) { - if (internal_input[0] == '/') { - consume_prepared_path(internal_input.substr(1)); - } else { - consume_prepared_path(internal_input); - } - } else { - // Non-special URLs with an empty host can have their paths erased - // Path-only URLs cannot have their paths erased - if (components.host_start == components.host_end && !has_authority()) { - update_base_pathname("/"); - } - } - ADA_ASSERT_TRUE(validate()); -} - -void url_aggregator::set_search(const std::string_view input) { - ada_log("url_aggregator::set_search ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (input.empty()) { - clear_search(); - helpers::strip_trailing_spaces_from_opaque_path(*this); - return; - } - - std::string new_value; - new_value = input[0] == '?' ? input.substr(1) : input; - helpers::remove_ascii_tab_or_newline(new_value); - - auto query_percent_encode_set = - is_special() ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE - : ada::character_sets::QUERY_PERCENT_ENCODE; - - url_aggregator saved_url(*this); - update_base_search(new_value, query_percent_encode_set); - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return; - } - ADA_ASSERT_TRUE(validate()); -} - -void url_aggregator::set_hash(const std::string_view input) { - ada_log("url_aggregator::set_hash ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (input.empty()) { - if (components.hash_start != url_components::omitted) { - buffer.resize(components.hash_start); - components.hash_start = url_components::omitted; - } - helpers::strip_trailing_spaces_from_opaque_path(*this); - return; - } - - std::string new_value; - new_value = input[0] == '#' ? input.substr(1) : input; - helpers::remove_ascii_tab_or_newline(new_value); - url_aggregator saved_url(*this); - update_unencoded_base_hash(new_value); - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return; - } - ADA_ASSERT_TRUE(validate()); -} - -bool url_aggregator::set_href(const std::string_view input) { - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - ada_log("url_aggregator::set_href ", input, " [", input.size(), " bytes]"); - ada::result out = ada::parse(input); - ada_log("url_aggregator::set_href, success :", out.has_value()); - - if (out) { - // The parser enforces get_max_input_length() on both the input and the - // normalized result. This is a defense-in-depth check. - if (out->buffer.size() > ada::get_max_input_length()) { - return false; - } - ada_log("url_aggregator::set_href, parsed ", out->to_string()); - // TODO: Figure out why the following line puts test to never finish. - *this = *out; - } - - return out.has_value(); -} - -ada_really_inline bool url_aggregator::parse_host(std::string_view input) { - ada_log("url_aggregator:parse_host \"", input, "\" [", input.size(), - " bytes]"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (input.empty()) { - return is_valid = false; - } // technically unnecessary. - // If input starts with U+005B ([), then: - if (input[0] == '[') { - // If input does not end with U+005D (]), validation error, return failure. - if (input.back() != ']') { - return is_valid = false; - } - ada_log("parse_host ipv6"); - - // Return the result of IPv6 parsing input with its leading U+005B ([) and - // trailing U+005D (]) removed. - input.remove_prefix(1); - input.remove_suffix(1); - return parse_ipv6(input); - } - - // If isNotSpecial is true, then return the result of opaque-host parsing - // input. - if (!is_special()) { - return parse_opaque_host(input); - } - // Let domain be the result of running UTF-8 decode without BOM on the - // percent-decoding of input. Let asciiDomain be the result of running domain - // to ASCII with domain and false. The most common case is an ASCII input, in - // which case we do not need to call the expensive 'to_ascii' if a few - // conditions are met: no '%' and no 'xn-' subsequence. - - // Often, the input does not contain any forbidden code points, and no upper - // case ASCII letter, then we can just copy it to the buffer. We want to - // optimize for such a common case. - - // Fast path: try to parse as pure decimal IPv4(a.b.c.d) first. - const uint64_t fast_result = checkers::try_parse_ipv4_fast(input); - if (fast_result < checkers::ipv4_fast_fail) { - // Fast path succeeded - input is pure decimal IPv4 - if (!input.empty() && input.back() == '.') { - update_base_hostname(input.substr(0, input.size() - 1)); - } else { - update_base_hostname(input); - } - host_type = IPV4; - is_valid = true; - ada_log("parse_host fast path decimal ipv4"); - ADA_ASSERT_TRUE(validate()); - return true; - } - uint8_t is_forbidden_or_upper = - unicode::contains_forbidden_domain_code_point_or_upper(input.data(), - input.size()); - // Minor optimization opportunity: - // contains_forbidden_domain_code_point_or_upper could be extend to check for - // the presence of characters that cannot appear in the ipv4 address and we - // could also check whether x and n and - are present, and so we could skip - // some of the checks below. However, the gains are likely to be small, and - // the code would be more complex. - static constexpr std::string_view xn_dash{"xn-", 3}; - if (is_forbidden_or_upper == 0 && - input.find(xn_dash) == std::string_view::npos) { - // fast path - update_base_hostname(input); - - // Check for other IPv4 formats (hex, octal, etc.) - if (checkers::is_ipv4(get_hostname())) { - ada_log("parse_host fast path ipv4"); - return parse_ipv4(get_hostname(), true); - } - ada_log("parse_host fast path ", get_hostname()); - is_valid = true; - return true; - } - // We have encountered at least one forbidden code point or the input contains - // 'xn-' (case insensitive), so we need to call 'to_ascii' to perform the full - // conversion. - - ada_log("parse_host calling to_ascii"); - std::optional host = std::string(get_hostname()); - is_valid = ada::unicode::to_ascii(host, input, input.find('%')); - if (!is_valid) { - ada_log("parse_host to_ascii returns false"); - return is_valid = false; - } - ada_log("parse_host to_ascii succeeded ", *host, " [", host->size(), - " bytes]"); - - if (std::ranges::any_of(host.value(), - ada::unicode::is_forbidden_domain_code_point)) { - return is_valid = false; - } - - // If asciiDomain ends in a number, then return the result of IPv4 parsing - // asciiDomain. - if (checkers::is_ipv4(host.value())) { - ada_log("parse_host got ipv4 ", *host); - return parse_ipv4(host.value(), false); - } - - update_base_hostname(host.value()); - ADA_ASSERT_TRUE(validate()); - return true; -} - -template -bool url_aggregator::set_host_or_hostname(const std::string_view input) { - ada_log("url_aggregator::set_host_or_hostname ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (has_opaque_path) { - return false; - } - - url_aggregator saved_url(*this); - - size_t host_end_pos = input.find('#'); - std::string _host(input.data(), host_end_pos != std::string_view::npos - ? host_end_pos - : input.size()); - helpers::remove_ascii_tab_or_newline(_host); - std::string_view new_host(_host); - - auto check_url_size = [&]() -> bool { - if (buffer.size() > ada::get_max_input_length()) { - *this = std::move(saved_url); - return false; - } - return true; - }; - - // If url's scheme is "file", then set state to file host state, instead of - // host state. - if (type != ada::scheme::type::FILE) { - std::string_view host_view(_host.data(), _host.length()); - auto [location, found_colon] = - helpers::get_host_delimiter_location(is_special(), host_view); - - // Otherwise, if c is U+003A (:) and insideBrackets is false, then: - // Note: the 'found_colon' value is true if and only if a colon was - // encountered while not inside brackets. - if (found_colon) { - // If buffer is the empty string, host-missing validation error, return - // failure. - std::string_view host_buffer = host_view.substr(0, location); - if (host_buffer.empty()) { - return false; - } - - // If state override is given and state override is hostname state, then - // return failure. - if constexpr (override_hostname) { - return false; - } - - // Let host be the result of host parsing buffer with url is not special. - bool succeeded = parse_host(host_buffer); - if (!succeeded) { - *this = std::move(saved_url); - return false; - } else if (has_dash_dot()) { - // The url now has a non-null host, so the "/." that guarded a - // leading "//" path is no longer needed. Drop it before the port is - // inserted at host_end, otherwise it stays wedged in the path. - delete_dash_dot(); - } - - // Set url's host to host, buffer to the empty string, and state to port - // state. - std::string_view port_buffer = new_host.substr(location + 1); - if (!port_buffer.empty()) { - set_port(port_buffer); - } - return check_url_size(); - } - // Otherwise, if one of the following is true: - // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) - // - url is special and c is U+005C (\) - else { - // If url is special and host_view is the empty string, host-missing - // validation error, return failure. - if (host_view.empty() && is_special()) { - return false; - } - - // Otherwise, if state override is given, host_view is the empty string, - // and either url includes credentials or url's port is non-null, then - // return failure. - if (host_view.empty() && (has_credentials() || has_port())) { - return false; - } - - // Let host be the result of host parsing host_view with url is not - // special. - if (host_view.empty() && !is_special()) { - if (has_hostname()) { - clear_hostname(); // easy! - } else if (has_dash_dot()) { - add_authority_slashes_if_needed(); - delete_dash_dot(); - } else { - // The url has no authority yet (e.g. "foo:/bar"); setting an empty - // host must still give it an (empty) authority, matching ada::url. - add_authority_slashes_if_needed(); - } - return check_url_size(); - } - - bool succeeded = parse_host(host_view); - if (!succeeded) { - *this = std::move(saved_url); - return false; - } else if (has_dash_dot()) { - // Should remove dash_dot from pathname - delete_dash_dot(); - } - return check_url_size(); - } - } - - size_t location = new_host.find_first_of("/\\?"); - if (location != std::string_view::npos) { - new_host.remove_suffix(new_host.length() - location); - } - - if (new_host.empty()) { - // Set url's host to the empty string. - clear_hostname(); - } else { - // Let host be the result of host parsing buffer with url is not special. - if (!parse_host(new_host)) { - *this = std::move(saved_url); - return false; - } - - // If host is "localhost", then set host to the empty string. - if (helpers::substring(buffer, components.host_start, - components.host_end) == "localhost") { - clear_hostname(); - } - } - ADA_ASSERT_TRUE(validate()); - return check_url_size(); -} - -bool url_aggregator::set_host(const std::string_view input) { - ada_log("url_aggregator::set_host '", input, "'"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - return set_host_or_hostname(input); -} - -bool url_aggregator::set_hostname(const std::string_view input) { - ada_log("url_aggregator::set_hostname '", input, "'"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - return set_host_or_hostname(input); -} - -[[nodiscard]] std::string url_aggregator::get_origin() const { - ada_log("url_aggregator::get_origin"); - if (is_special()) { - // Return a new opaque origin. - if (type == scheme::FILE) { - return "null"; - } - - return helpers::concat(get_protocol(), "//", get_host()); - } - - if (get_protocol() == "blob:") { - std::string_view path = get_pathname(); - if (!path.empty()) { - auto out = ada::parse(path); - if (out && (out->type == scheme::HTTP || out->type == scheme::HTTPS)) { - // If pathURL's scheme is not "http" and not "https", then return a - // new opaque origin. - return helpers::concat(out->get_protocol(), "//", out->get_host()); - } - } - } - - // Return a new opaque origin. - return "null"; -} - -[[nodiscard]] std::string_view url_aggregator::get_username() const - ada_lifetime_bound { - ada_log("url_aggregator::get_username"); - if (has_non_empty_username()) { - return helpers::substring(buffer, components.protocol_end + 2, - components.username_end); - } - return ""; -} - -[[nodiscard]] std::string_view url_aggregator::get_password() const - ada_lifetime_bound { - ada_log("url_aggregator::get_password"); - if (has_non_empty_password()) { - return helpers::substring(buffer, components.username_end + 1, - components.host_start); - } - return ""; -} - -[[nodiscard]] std::string_view url_aggregator::get_port() const - ada_lifetime_bound { - ada_log("url_aggregator::get_port"); - if (components.port == url_components::omitted) { - return ""; - } - return helpers::substring(buffer, components.host_end + 1, - components.pathname_start); -} - -[[nodiscard]] std::string_view url_aggregator::get_hash() const - ada_lifetime_bound { - ada_log("url_aggregator::get_hash"); - // If this's URL's fragment is either null or the empty string, then return - // the empty string. Return U+0023 (#), followed by this's URL's fragment. - if (components.hash_start == url_components::omitted) { - return ""; - } - if (buffer.size() - components.hash_start <= 1) { - return ""; - } - return helpers::substring(buffer, components.hash_start); -} - -[[nodiscard]] std::string_view url_aggregator::get_host() const - ada_lifetime_bound { - ada_log("url_aggregator::get_host"); - // Technically, we should check if there is a hostname, but - // the code below works even if there isn't. - // if(!has_hostname()) { return ""; } - size_t start = components.host_start; - if (components.host_end > components.host_start && - buffer[components.host_start] == '@') { - start++; - } - // if we have an empty host, then the space between components.host_end and - // components.pathname_start may be occupied by /. - if (start == components.host_end) { - return {}; - } - return helpers::substring(buffer, start, components.pathname_start); -} - -[[nodiscard]] std::string_view url_aggregator::get_hostname() const - ada_lifetime_bound { - ada_log("url_aggregator::get_hostname"); - // Technically, we should check if there is a hostname, but - // the code below works even if there isn't. - // if(!has_hostname()) { return ""; } - size_t start = components.host_start; - // So host_start is not where the host begins. - if (components.host_end > components.host_start && - buffer[components.host_start] == '@') { - start++; - } - return helpers::substring(buffer, start, components.host_end); -} - -[[nodiscard]] std::string_view url_aggregator::get_search() const - ada_lifetime_bound { - ada_log("url_aggregator::get_search"); - // If this's URL's query is either null or the empty string, then return the - // empty string. Return U+003F (?), followed by this's URL's query. - if (components.search_start == url_components::omitted) { - return ""; - } - auto ending_index = uint32_t(buffer.size()); - if (components.hash_start != url_components::omitted) { - ending_index = components.hash_start; - } - if (ending_index - components.search_start <= 1) { - return ""; - } - return helpers::substring(buffer, components.search_start, ending_index); -} - -[[nodiscard]] std::string_view url_aggregator::get_protocol() const - ada_lifetime_bound { - ada_log("url_aggregator::get_protocol"); - return helpers::substring(buffer, 0, components.protocol_end); -} - -[[nodiscard]] std::string ada::url_aggregator::to_string() const { - ada_log("url_aggregator::to_string buffer:", buffer, " [", buffer.size(), - " bytes]"); - if (!is_valid) { - return "null"; - } - - std::string answer; - auto back = std::back_insert_iterator(answer); - answer.append("{\n"); - - answer.append("\t\"buffer\":\""); - helpers::encode_json(buffer, back); - answer.append("\",\n"); - - answer.append("\t\"protocol\":\""); - helpers::encode_json(get_protocol(), back); - answer.append("\",\n"); - - if (has_credentials()) { - answer.append("\t\"username\":\""); - helpers::encode_json(get_username(), back); - answer.append("\",\n"); - answer.append("\t\"password\":\""); - helpers::encode_json(get_password(), back); - answer.append("\",\n"); - } - - answer.append("\t\"host\":\""); - helpers::encode_json(get_host(), back); - answer.append("\",\n"); - - answer.append("\t\"path\":\""); - helpers::encode_json(get_pathname(), back); - answer.append("\",\n"); - answer.append("\t\"opaque path\":"); - answer.append((has_opaque_path ? "true" : "false")); - answer.append(",\n"); - - if (components.search_start != url_components::omitted) { - answer.append("\t\"query\":\""); - helpers::encode_json(get_search(), back); - answer.append("\",\n"); - } - if (components.hash_start != url_components::omitted) { - answer.append("\t\"fragment\":\""); - helpers::encode_json(get_hash(), back); - answer.append("\",\n"); - } - - auto convert_offset_to_string = [](uint32_t offset) -> std::string { - if (offset == url_components::omitted) { - return "null"; - } else { - return std::to_string(offset); - } - }; - - answer.append("\t\"protocol_end\":"); - answer.append(convert_offset_to_string(components.protocol_end)); - answer.append(",\n"); - - answer.append("\t\"username_end\":"); - answer.append(convert_offset_to_string(components.username_end)); - answer.append(",\n"); - - answer.append("\t\"host_start\":"); - answer.append(convert_offset_to_string(components.host_start)); - answer.append(",\n"); - - answer.append("\t\"host_end\":"); - answer.append(convert_offset_to_string(components.host_end)); - answer.append(",\n"); - - answer.append("\t\"port\":"); - answer.append(convert_offset_to_string(components.port)); - answer.append(",\n"); - - answer.append("\t\"pathname_start\":"); - answer.append(convert_offset_to_string(components.pathname_start)); - answer.append(",\n"); - - answer.append("\t\"search_start\":"); - answer.append(convert_offset_to_string(components.search_start)); - answer.append(",\n"); - - answer.append("\t\"hash_start\":"); - answer.append(convert_offset_to_string(components.hash_start)); - answer.append("\n}"); - - return answer; -} - -[[nodiscard]] bool url_aggregator::has_valid_domain() const noexcept { - if (components.host_start == components.host_end) { - return false; - } - // Avoid allocation: construct a string_view directly into the buffer. - size_t start = components.host_start; - if (components.host_end > components.host_start && - buffer[components.host_start] == '@') { - start++; - } - return checkers::verify_dns_length( - std::string_view(buffer.data() + start, components.host_end - start)); -} - -bool url_aggregator::parse_ipv4(std::string_view input, bool in_place) { - ada_log("parse_ipv4 ", input, " [", input.size(), - " bytes], overlaps with buffer: ", - helpers::overlaps(input, buffer) ? "yes" : "no"); - ADA_ASSERT_TRUE(validate()); - if (input.empty()) { - return is_valid = false; - } - const bool trailing_dot = (input.back() == '.'); - if (trailing_dot) { - input.remove_suffix(1); - if (input.empty()) { - return is_valid = false; - } - } - - const uint64_t fast = checkers::try_parse_ipv4_fast(input); - if (fast < checkers::ipv4_fast_fail) [[likely]] { - // Pure decimal: keep the buffer when it already holds the address. - // Otherwise write the (trailing-dot-stripped) input. - if (!(in_place && !trailing_dot)) { - update_base_hostname(input); - } - host_type = IPV4; - ADA_ASSERT_TRUE(validate()); - return true; - } - - const char* p = input.data(); - const char* end = p + input.size(); - uint64_t ipv4 = 0; - int digit_count = 0; - int pure_decimal_count = 0; - - for (; digit_count < 4 && p < end; ++digit_count) { - uint64_t segment = 0; - bool pure = false; - if (!detail::parse_ipv4_number(p, end, segment, pure)) { - return is_valid = false; - } - if (pure) { - ++pure_decimal_count; - } - if (p >= end) { - const unsigned shift = static_cast(32 - digit_count * 8); - if (segment >= (uint64_t{1} << shift)) { - return is_valid = false; - } - ipv4 = (ipv4 << shift) | segment; - goto ipv4_done; - } - if (segment > 255 || *p != '.') { - return is_valid = false; - } - ipv4 = (ipv4 << 8) | segment; - ++p; - } - if (digit_count != 4 || p != end) { - return is_valid = false; - } -ipv4_done: - ada_log("url_aggregator::parse_ipv4 completed ", get_href(), - " host: ", get_host()); - if (in_place && pure_decimal_count == 4 && !trailing_dot) { - ada_log( - "url_aggregator::parse_ipv4 completed and was already correct in the " - "buffer"); - } else { - update_base_hostname(ada::serializers::ipv4(ipv4)); - } - host_type = IPV4; - ADA_ASSERT_TRUE(validate()); - return true; -} - -bool url_aggregator::parse_ipv6(std::string_view input) { - ada_log("parse_ipv6 ", input, " [", input.size(), " bytes]"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (input.empty() || input.size() > 45) [[unlikely]] { - return is_valid = false; - } -#if defined(ADA_AVX512) - if (!detail::ipv6_structure_plausible(input.data(), input.size())) - [[unlikely]] { - return is_valid = false; - } -#endif - - std::array address{}; - const char* pointer = input.data(); - const char* const end = pointer + input.size(); - int piece_index = 0; - int compress = -1; - - if (*pointer == ':') { - if (input.size() == 1 || pointer[1] != ':') [[unlikely]] { - return is_valid = false; - } - pointer += 2; - compress = ++piece_index; - } - - while (pointer != end) { - if (piece_index == 8) [[unlikely]] { - return is_valid = false; - } - if (*pointer == ':') { - if (compress != -1) [[unlikely]] { - return is_valid = false; - } - ++pointer; - compress = ++piece_index; - continue; - } - - uint16_t value = 0; - const int length = detail::parse_hex_piece(pointer, end, value); - - if (pointer != end && *pointer == '.') { - if (length == 0) [[unlikely]] { - return is_valid = false; - } - pointer -= length; - if (piece_index > 6) [[unlikely]] { - return is_valid = false; - } - - int numbers_seen = 0; - while (pointer != end) { - int ipv4_piece = -1; - if (numbers_seen > 0) { - if (*pointer == '.' && numbers_seen < 4) { - ++pointer; - } else { - return is_valid = false; - } - } - if (pointer == end || *pointer < '0' || *pointer > '9') [[unlikely]] { - return is_valid = false; - } - ipv4_piece = *pointer - '0'; - ++pointer; - if (pointer != end && *pointer >= '0' && *pointer <= '9') { - if (ipv4_piece == 0) [[unlikely]] { - return is_valid = false; - } - ipv4_piece = ipv4_piece * 10 + (*pointer - '0'); - ++pointer; - if (pointer != end && *pointer >= '0' && *pointer <= '9') { - ipv4_piece = ipv4_piece * 10 + (*pointer - '0'); - ++pointer; - if (ipv4_piece > 255) [[unlikely]] { - return is_valid = false; - } - } - } - address[static_cast(piece_index)] = static_cast( - address[static_cast(piece_index)] * 0x100 + - static_cast(ipv4_piece)); - ++numbers_seen; - if (numbers_seen == 2 || numbers_seen == 4) { - ++piece_index; - } - } - if (numbers_seen != 4) [[unlikely]] { - return is_valid = false; - } - break; - } - - if (length == 0) [[unlikely]] { - return is_valid = false; - } - - if (pointer != end && *pointer == ':') { - ++pointer; - if (pointer == end) [[unlikely]] { - return is_valid = false; - } - } else if (pointer != end) [[unlikely]] { - return is_valid = false; - } - - address[static_cast(piece_index)] = value; - ++piece_index; - } - - if (compress != -1) { - const int right = piece_index - compress; - if (right > 0) { - const size_t dest = static_cast(8 - right); - const size_t src = static_cast(compress); - if (dest != src) { - for (size_t i = static_cast(right); i-- > 0;) { - address[dest + i] = address[src + i]; - address[src + i] = 0; - } - } - } - } else if (piece_index != 8) [[unlikely]] { - return is_valid = false; - } - - // Serialize once; skip rewrite when hostname is already canonical. - const std::string serialized = ada::serializers::ipv6(address); - const std::string_view current = get_hostname(); - if (current.size() == serialized.size() && - std::memcmp(current.data(), serialized.data(), serialized.size()) == 0) { - ada_log("parse_ipv6 in-place canonical match"); - } else { - update_base_hostname(serialized); - } - ada_log("parse_ipv6 ", get_hostname()); - ADA_ASSERT_TRUE(validate()); - host_type = IPV6; - return true; -} - -bool url_aggregator::parse_opaque_host(std::string_view input) { - ada_log("parse_opaque_host ", input, " [", input.size(), " bytes]"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (std::ranges::any_of(input, ada::unicode::is_forbidden_host_code_point)) { - return is_valid = false; - } - - // Return the result of running UTF-8 percent-encode on input using the C0 - // control percent-encode set. - size_t idx = ada::unicode::percent_encode_index( - input, character_sets::C0_CONTROL_PERCENT_ENCODE); - if (idx == input.size()) { - update_base_hostname(input); - } else { - // We only create a temporary string if we need to. - update_base_hostname(ada::unicode::percent_encode( - input, character_sets::C0_CONTROL_PERCENT_ENCODE, idx)); - } - ADA_ASSERT_TRUE(validate()); - return true; -} - -[[nodiscard]] std::string url_aggregator::to_diagram() const { - if (!is_valid) { - return "invalid"; - } - std::string answer; - answer.append(buffer); - answer.append(" ["); - answer.append(std::to_string(buffer.size())); - answer.append(" bytes]"); - answer.append("\n"); - // first line - std::string line1; - line1.resize(buffer.size(), ' '); - if (components.hash_start != url_components::omitted) { - line1[components.hash_start] = '|'; - } - if (components.search_start != url_components::omitted) { - line1[components.search_start] = '|'; - } - if (components.pathname_start != buffer.size()) { - line1[components.pathname_start] = '|'; - } - if (components.host_end != buffer.size()) { - line1[components.host_end] = '|'; - } - if (components.host_start != buffer.size()) { - line1[components.host_start] = '|'; - } - if (components.username_end != buffer.size()) { - line1[components.username_end] = '|'; - } - if (components.protocol_end != buffer.size()) { - line1[components.protocol_end] = '|'; - } - answer.append(line1); - answer.append("\n"); - - std::string line2 = line1; - if (components.hash_start != url_components::omitted) { - line2[components.hash_start] = '`'; - line1[components.hash_start] = ' '; - - for (size_t i = components.hash_start + 1; i < line2.size(); i++) { - line2[i] = '-'; - } - line2.append(" hash_start"); - answer.append(line2); - answer.append("\n"); - } - - std::string line3 = line1; - if (components.search_start != url_components::omitted) { - line3[components.search_start] = '`'; - line1[components.search_start] = ' '; - - for (size_t i = components.search_start + 1; i < line3.size(); i++) { - line3[i] = '-'; - } - line3.append(" search_start "); - line3.append(std::to_string(components.search_start)); - answer.append(line3); - answer.append("\n"); - } - - std::string line4 = line1; - if (components.pathname_start != buffer.size()) { - line4[components.pathname_start] = '`'; - line1[components.pathname_start] = ' '; - for (size_t i = components.pathname_start + 1; i < line4.size(); i++) { - line4[i] = '-'; - } - line4.append(" pathname_start "); - line4.append(std::to_string(components.pathname_start)); - answer.append(line4); - answer.append("\n"); - } - - std::string line5 = line1; - if (components.host_end != buffer.size()) { - line5[components.host_end] = '`'; - line1[components.host_end] = ' '; - - for (size_t i = components.host_end + 1; i < line5.size(); i++) { - line5[i] = '-'; - } - line5.append(" host_end "); - line5.append(std::to_string(components.host_end)); - answer.append(line5); - answer.append("\n"); - } - - std::string line6 = line1; - if (components.host_start != buffer.size()) { - line6[components.host_start] = '`'; - line1[components.host_start] = ' '; - - for (size_t i = components.host_start + 1; i < line6.size(); i++) { - line6[i] = '-'; - } - line6.append(" host_start "); - line6.append(std::to_string(components.host_start)); - answer.append(line6); - answer.append("\n"); - } - - std::string line7 = line1; - if (components.username_end != buffer.size()) { - line7[components.username_end] = '`'; - line1[components.username_end] = ' '; - - for (size_t i = components.username_end + 1; i < line7.size(); i++) { - line7[i] = '-'; - } - line7.append(" username_end "); - line7.append(std::to_string(components.username_end)); - answer.append(line7); - answer.append("\n"); - } - - std::string line8 = line1; - if (components.protocol_end != buffer.size()) { - line8[components.protocol_end] = '`'; - line1[components.protocol_end] = ' '; - - for (size_t i = components.protocol_end + 1; i < line8.size(); i++) { - line8[i] = '-'; - } - line8.append(" protocol_end "); - line8.append(std::to_string(components.protocol_end)); - answer.append(line8); - answer.append("\n"); - } - - if (components.hash_start == url_components::omitted) { - answer.append("note: hash omitted\n"); - } - if (components.search_start == url_components::omitted) { - answer.append("note: search omitted\n"); - } - if (components.protocol_end > buffer.size()) { - answer.append("warning: protocol_end overflows\n"); - } - if (components.username_end > buffer.size()) { - answer.append("warning: username_end overflows\n"); - } - if (components.host_start > buffer.size()) { - answer.append("warning: host_start overflows\n"); - } - if (components.host_end > buffer.size()) { - answer.append("warning: host_end overflows\n"); - } - if (components.pathname_start > buffer.size()) { - answer.append("warning: pathname_start overflows\n"); - } - return answer; -} - -void url_aggregator::delete_dash_dot() { - ada_log("url_aggregator::delete_dash_dot"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(has_dash_dot()); - buffer.erase(components.host_end, 2); - components.pathname_start -= 2; - if (components.search_start != url_components::omitted) { - components.search_start -= 2; - } - if (components.hash_start != url_components::omitted) { - components.hash_start -= 2; - } - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!has_dash_dot()); -} - -inline void url_aggregator::consume_prepared_path(std::string_view input) { - ada_log("url_aggregator::consume_prepared_path ", input); - /*** - * This is largely duplicated code from helpers::parse_prepared_path, which is - * unfortunate. This particular function is nearly identical, except that it - * is a method on url_aggregator. The idea is that the trivial path (which is - * very common) merely appends to the buffer. This is the same trivial path as - * with helpers::parse_prepared_path, except that we have the additional check - * for is_at_path(). Otherwise, we grab a copy of the current path and we - * modify it, and then insert it back into the buffer. - */ - uint8_t accumulator = checkers::path_signature(input); - // Let us first detect a trivial case. - // If it is special, we check that we have no dot, no %, no \ and no - // character needing percent encoding. Otherwise, we check that we have no %, - // no dot, and no character needing percent encoding. - constexpr uint8_t need_encoding = 1; - constexpr uint8_t backslash_char = 2; - constexpr uint8_t dot_char = 4; - constexpr uint8_t percent_char = 8; - bool special = type != ada::scheme::NOT_SPECIAL; - bool may_need_slow_file_handling = (type == ada::scheme::type::FILE && - checkers::is_windows_drive_letter(input)); - bool trivial_path = - (special ? (accumulator == 0) - : ((accumulator & (need_encoding | dot_char | percent_char)) == - 0)) && - (!may_need_slow_file_handling); - if (accumulator == dot_char && !may_need_slow_file_handling) { - // '4' means that we have at least one dot, but nothing that requires - // percent encoding or decoding. The only part that is not trivial is - // that we may have single dots and double dots path segments. - // If we have such segments, then we either have a path that begins - // with '.' (easy to check), or we have the sequence './'. - // Note: input cannot be empty, it must at least contain one character ('.') - // Note: we know that '\' is not present. - if (input[0] != '.') { - size_t slashdot = 0; - bool dot_is_file = true; - for (;;) { - slashdot = input.find("/.", slashdot); - if (slashdot == std::string_view::npos) { // common case - break; - } else { // uncommon - // only three cases matter: /./, /.. or a final / - slashdot += 2; - dot_is_file &= !(slashdot == input.size() || input[slashdot] == '.' || - input[slashdot] == '/'); - } - } - trivial_path = dot_is_file; - } - } - if (trivial_path && is_at_path()) { - ada_log("parse_path trivial"); - buffer += '/'; - buffer += input; - return; - } - std::string path = std::string(get_pathname()); - // We are going to need to look a bit at the path, but let us see if we can - // ignore percent encoding *and* backslashes *and* percent characters. - // Except for the trivial case, this is likely to capture 99% of paths out - // there. - bool fast_path = - (special && - (accumulator & (need_encoding | backslash_char | percent_char)) == 0) && - (type != ada::scheme::type::FILE); - if (fast_path) { - ada_log("parse_prepared_path fast"); - // Here we don't need to worry about \ or percent encoding. - // We also do not have a file protocol. We might have dots, however, - // but dots must as appear as '.', and they cannot be encoded because - // the symbol '%' is not present. - size_t previous_location = 0; // We start at 0. - do { - size_t new_location = input.find('/', previous_location); - // std::string_view path_view = input; - // We process the last segment separately: - if (new_location == std::string_view::npos) { - std::string_view path_view = input.substr(previous_location); - if (path_view == "..") { // The path ends with .. - // e.g., if you receive ".." with an empty path, you go to "/". - if (path.empty()) { - path = '/'; - update_base_pathname(path); - return; - } - // Fast case where we have nothing to do: - if (path.back() == '/') { - update_base_pathname(path); - return; - } - // If you have the path "/joe/myfriend", - // then you delete 'myfriend'. - path.resize(path.rfind('/') + 1); - update_base_pathname(path); - return; - } - path += '/'; - if (path_view != ".") { - path.append(path_view); - } - update_base_pathname(path); - return; - } else { - // This is a non-final segment. - std::string_view path_view = - input.substr(previous_location, new_location - previous_location); - previous_location = new_location + 1; - if (path_view == "..") { - size_t last_delimiter = path.rfind('/'); - if (last_delimiter != std::string::npos) { - path.erase(last_delimiter); - } - } else if (path_view != ".") { - path += '/'; - path.append(path_view); - } - } - } while (true); - } else { - ada_log("parse_path slow"); - // we have reached the general case - bool needs_percent_encoding = (accumulator & 1); - std::string path_buffer_tmp; - do { - size_t location = (special && (accumulator & 2)) - ? input.find_first_of("/\\") - : input.find('/'); - std::string_view path_view = input; - if (location != std::string_view::npos) { - path_view.remove_suffix(path_view.size() - location); - input.remove_prefix(location + 1); - } - // path_buffer is either path_view or it might point at a percent encoded - // temporary string. - std::string_view path_buffer = - (needs_percent_encoding && - ada::unicode::percent_encode( - path_view, character_sets::PATH_PERCENT_ENCODE, path_buffer_tmp)) - ? path_buffer_tmp - : path_view; - if (unicode::is_double_dot_path_segment(path_buffer)) { - helpers::shorten_path(path, type); - if (location == std::string_view::npos) { - path += '/'; - } - } else if (unicode::is_single_dot_path_segment(path_buffer) && - (location == std::string_view::npos)) { - path += '/'; - } - // Otherwise, if path_buffer is not a single-dot path segment, then: - else if (!unicode::is_single_dot_path_segment(path_buffer)) { - // If url's scheme is "file", url's path is empty, and path_buffer is a - // Windows drive letter, then replace the second code point in - // path_buffer with U+003A (:). - if (type == ada::scheme::type::FILE && path.empty() && - checkers::is_windows_drive_letter(path_buffer)) { - path += '/'; - path += path_buffer[0]; - path += ':'; - path_buffer.remove_prefix(2); - path.append(path_buffer); - } else { - // Append path_buffer to url's path. - path += '/'; - path.append(path_buffer); - } - } - if (location == std::string_view::npos) { - update_base_pathname(path); - return; - } - } while (true); - } -} -} // namespace ada -/* end file src/url_aggregator.cpp */ - -#if ADA_INCLUDE_URL_PATTERN -/* begin file src/url_pattern.cpp */ -#if ADA_INCLUDE_URL_PATTERN - - -#include -#include -#include - -namespace ada { - -tl::expected url_pattern_init::process( - const url_pattern_init& init, url_pattern_init::process_type type, - std::optional protocol, - std::optional username, - std::optional password, - std::optional hostname, - std::optional port, - std::optional pathname, - std::optional search, - std::optional hash) { - // Let result be the result of creating a new URLPatternInit. - auto result = url_pattern_init{}; - - // If protocol is not null, set result["protocol"] to protocol. - if (protocol.has_value()) result.protocol = *protocol; - - // If username is not null, set result["username"] to username. - if (username.has_value()) result.username = *username; - - // If password is not null, set result["password"] to password. - if (password.has_value()) result.password = *password; - - // If hostname is not null, set result["hostname"] to hostname. - if (hostname.has_value()) result.hostname = *hostname; - - // If port is not null, set result["port"] to port. - if (port.has_value()) result.port = *port; - - // If pathname is not null, set result["pathname"] to pathname. - if (pathname.has_value()) result.pathname = *pathname; - - // If search is not null, set result["search"] to search. - if (search.has_value()) result.search = *search; - - // If hash is not null, set result["hash"] to hash. - if (hash.has_value()) result.hash = *hash; - - // Let baseURL be null. - std::optional base_url{}; - - // If init["baseURL"] exists: - if (init.base_url.has_value()) { - // Set baseURL to the result of parsing init["baseURL"]. - auto parsing_result = ada::parse(*init.base_url); - // If baseURL is failure, then throw a TypeError. - if (!parsing_result) { - return tl::unexpected(errors::type_error); - } - base_url = std::move(*parsing_result); - - // If init["protocol"] does not exist, then set result["protocol"] to the - // result of processing a base URL string given baseURL's scheme and type. - if (!init.protocol.has_value()) { - ADA_ASSERT_TRUE(base_url.has_value()); - std::string_view base_url_protocol = base_url->get_protocol(); - if (base_url_protocol.ends_with(":")) base_url_protocol.remove_suffix(1); - result.protocol = - url_pattern_helpers::process_base_url_string(base_url_protocol, type); - } - - // If type is not "pattern" and init contains none of "protocol", - // "hostname", "port" and "username", then set result["username"] to the - // result of processing a base URL string given baseURL's username and type. - if (type != process_type::pattern && !init.protocol && !init.hostname && - !init.port && !init.username) { - result.username = url_pattern_helpers::process_base_url_string( - base_url->get_username(), type); - } - - // TODO: Optimization opportunity: Merge this with the previous check. - // If type is not "pattern" and init contains none of "protocol", - // "hostname", "port", "username" and "password", then set - // result["password"] to the result of processing a base URL string given - // baseURL's password and type. - if (type != process_type::pattern && !init.protocol && !init.hostname && - !init.port && !init.username && !init.password) { - result.password = url_pattern_helpers::process_base_url_string( - base_url->get_password(), type); - } - - // If init contains neither "protocol" nor "hostname", then: - if (!init.protocol && !init.hostname) { - // Let baseHost be baseURL's host. - // If baseHost is null, then set baseHost to the empty string. - auto base_host = base_url->get_hostname(); - // Set result["hostname"] to the result of processing a base URL string - // given baseHost and type. - result.hostname = - url_pattern_helpers::process_base_url_string(base_host, type); - } - - // If init contains none of "protocol", "hostname", and "port", then: - if (!init.protocol && !init.hostname && !init.port) { - // If baseURL's port is null, then set result["port"] to the empty string. - // Otherwise, set result["port"] to baseURL's port, serialized. - result.port = base_url->get_port(); - } - - // If init contains none of "protocol", "hostname", "port", and "pathname", - // then set result["pathname"] to the result of processing a base URL string - // given the result of URL path serializing baseURL and type. - if (!init.protocol && !init.hostname && !init.port && !init.pathname) { - result.pathname = url_pattern_helpers::process_base_url_string( - base_url->get_pathname(), type); - } - - // If init contains none of "protocol", "hostname", "port", "pathname", and - // "search", then: - if (!init.protocol && !init.hostname && !init.port && !init.pathname && - !init.search) { - // Let baseQuery be baseURL's query. get_search() carries the leading "?" - // delimiter, but the spec inherits the query itself, so drop it. - std::string_view base_query = base_url->get_search(); - if (base_query.starts_with("?")) base_query.remove_prefix(1); - // Set result["search"] to the result of processing a base URL string - // given baseQuery and type. - result.search = - url_pattern_helpers::process_base_url_string(base_query, type); - } - - // If init contains none of "protocol", "hostname", "port", "pathname", - // "search", and "hash", then: - if (!init.protocol && !init.hostname && !init.port && !init.pathname && - !init.search && !init.hash) { - // Let baseFragment be baseURL's fragment. get_hash() carries the leading - // "#" delimiter, but the spec inherits the fragment itself, so drop it. - std::string_view base_fragment = base_url->get_hash(); - if (base_fragment.starts_with("#")) base_fragment.remove_prefix(1); - // Set result["hash"] to the result of processing a base URL string given - // baseFragment and type. - result.hash = - url_pattern_helpers::process_base_url_string(base_fragment, type); - } - } - - // If init["protocol"] exists, then set result["protocol"] to the result of - // process protocol for init given init["protocol"] and type. - if (init.protocol) { - auto process_result = process_protocol(*init.protocol, type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.protocol = std::move(*process_result); - } - - // If init["username"] exists, then set result["username"] to the result of - // process username for init given init["username"] and type. - if (init.username.has_value()) { - auto process_result = process_username(*init.username, type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.username = std::move(*process_result); - } - - // If init["password"] exists, then set result["password"] to the result of - // process password for init given init["password"] and type. - if (init.password.has_value()) { - auto process_result = process_password(*init.password, type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.password = std::move(*process_result); - } - - // If init["hostname"] exists, then set result["hostname"] to the result of - // process hostname for init given init["hostname"] and type. - if (init.hostname.has_value()) { - auto process_result = process_hostname(*init.hostname, type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.hostname = std::move(*process_result); - } - - // If init["port"] exists, then set result["port"] to the result of process - // port for init given init["port"], result["protocol"], and type. - if (init.port) { - auto process_result = - process_port(*init.port, result.protocol.value_or("fake"), type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.port = std::move(*process_result); - } - - // If init["pathname"] exists: - if (init.pathname.has_value()) { - // Set result["pathname"] to init["pathname"]. - result.pathname = init.pathname; - - // If the following are all true: - // - baseURL is not null; - // - baseURL has an opaque path; and - // - the result of running is an absolute pathname given result["pathname"] - // and type is false, - if (base_url && !base_url->has_opaque_path && - !url_pattern_helpers::is_absolute_pathname(*result.pathname, type)) { - // Let baseURLPath be the result of running process a base URL string - // given the result of URL path serializing baseURL and type. - // TODO: Optimization opportunity: Avoid returning a string if no slash - // exist. - std::string base_url_path = url_pattern_helpers::process_base_url_string( - base_url->get_pathname(), type); - - // Let slash index be the index of the last U+002F (/) code point found in - // baseURLPath, interpreted as a sequence of code points, or null if there - // are no instances of the code point. - auto slash_index = base_url_path.find_last_of('/'); - - // If slash index is not null: - if (slash_index != std::string::npos) { - // Let new pathname be the code point substring from 0 to slash index + - // 1 within baseURLPath. - base_url_path.resize(slash_index + 1); - // Append result["pathname"] to the end of new pathname. - ADA_ASSERT_TRUE(result.pathname.has_value()); - base_url_path.append(std::move(*result.pathname)); - // Set result["pathname"] to new pathname. - result.pathname = std::move(base_url_path); - } - } - - // Set result["pathname"] to the result of process pathname for init given - // result["pathname"], result["protocol"], and type. - auto pathname_processing_result = - process_pathname(*result.pathname, result.protocol.value_or(""), type); - if (!pathname_processing_result) { - return tl::unexpected(pathname_processing_result.error()); - } - result.pathname = std::move(*pathname_processing_result); - } - - // If init["search"] exists then set result["search"] to the result of process - // search for init given init["search"] and type. - if (init.search) { - auto process_result = process_search(*init.search, type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.search = std::move(*process_result); - } - - // If init["hash"] exists then set result["hash"] to the result of process - // hash for init given init["hash"] and type. - if (init.hash) { - auto process_result = process_hash(*init.hash, type); - if (!process_result) { - return tl::unexpected(process_result.error()); - } - result.hash = std::move(*process_result); - } - // Return result. - return result; -} - -tl::expected url_pattern_init::process_protocol( - std::string_view value, process_type type) { - ada_log("process_protocol=", value, " [", type, "]"); - // Let strippedValue be the given value with a single trailing U+003A (:) - // removed, if any. - if (value.ends_with(":")) { - value.remove_suffix(1); - } - // If type is "pattern" then return strippedValue. - if (type == process_type::pattern) { - return std::string(value); - } - // Return the result of running canonicalize a protocol given strippedValue. - return url_pattern_helpers::canonicalize_protocol(value); -} - -tl::expected url_pattern_init::process_username( - std::string_view value, process_type type) { - // If type is "pattern" then return value. - if (type == process_type::pattern) { - return std::string(value); - } - // Return the result of running canonicalize a username given value. - return url_pattern_helpers::canonicalize_username(value); -} - -tl::expected url_pattern_init::process_password( - std::string_view value, process_type type) { - // If type is "pattern" then return value. - if (type == process_type::pattern) { - return std::string(value); - } - // Return the result of running canonicalize a password given value. - return url_pattern_helpers::canonicalize_password(value); -} - -tl::expected url_pattern_init::process_hostname( - std::string_view value, process_type type) { - ada_log("process_hostname value=", value, " type=", type); - // If type is "pattern" then return value. - if (type == process_type::pattern) { - return std::string(value); - } - // Return the result of running canonicalize a hostname given value. - return url_pattern_helpers::canonicalize_hostname(value); -} - -tl::expected url_pattern_init::process_port( - std::string_view port, std::string_view protocol, process_type type) { - // If type is "pattern" then return portValue. - if (type == process_type::pattern) { - return std::string(port); - } - // Return the result of running canonicalize a port given portValue and - // protocolValue. - return url_pattern_helpers::canonicalize_port_with_protocol(port, protocol); -} - -tl::expected url_pattern_init::process_pathname( - std::string_view value, std::string_view protocol, process_type type) { - // If type is "pattern" then return pathnameValue. - if (type == process_type::pattern) { - return std::string(value); - } - - // If protocolValue is a special scheme or the empty string, then return the - // result of running canonicalize a pathname given pathnameValue. - if (protocol.empty() || scheme::is_special(protocol)) { - return url_pattern_helpers::canonicalize_pathname(value); - } - - // Return the result of running canonicalize an opaque pathname given - // pathnameValue. - return url_pattern_helpers::canonicalize_opaque_pathname(value); -} - -tl::expected url_pattern_init::process_search( - std::string_view value, process_type type) { - // Let strippedValue be the given value with a single leading U+003F (?) - // removed, if any. - if (value.starts_with("?")) { - value.remove_prefix(1); - } - // We cannot assert that the value is no longer starting with a single - // question mark because technically it can start. The question is whether or - // not we should remove the first question mark. Ref: - // https://github.com/ada-url/ada/pull/992 The spec is not clear on this. - - // If type is "pattern" then return strippedValue. - if (type == process_type::pattern) { - return std::string(value); - } - // Return the result of running canonicalize a search given strippedValue. - return url_pattern_helpers::canonicalize_search(value); -} - -tl::expected url_pattern_init::process_hash( - std::string_view value, process_type type) { - // Let strippedValue be the given value with a single leading U+0023 (#) - // removed, if any. - if (value.starts_with("#")) { - value.remove_prefix(1); - } - ADA_ASSERT_TRUE(!value.starts_with("#")); - // If type is "pattern" then return strippedValue. - if (type == process_type::pattern) { - return std::string(value); - } - // Return the result of running canonicalize a hash given strippedValue. - return url_pattern_helpers::canonicalize_hash(value); -} - -} // namespace ada - -#endif // ADA_INCLUDE_URL_PATTERN -/* end file src/url_pattern.cpp */ -/* begin file src/url_pattern_helpers.cpp */ -#if ADA_INCLUDE_URL_PATTERN - -#include -#include -#include -#include -#include -#include - - -namespace ada::url_pattern_helpers { - -std::tuple> -generate_regular_expression_and_name_list( - const std::vector& part_list, - url_pattern_compile_component_options options) { - // Let result be "^" - std::string result = "^"; - // Reserve capacity to reduce reallocations - result.reserve(part_list.size() * 16); - - // Let name list be a new list - std::vector name_list{}; - name_list.reserve(part_list.size()); - - // Pre-generate segment wildcard regexp if needed (avoids repeated generation) - std::string segment_wildcard_regexp; - - // For each part of part list: - for (const url_pattern_part& part : part_list) { - // If part's type is "fixed-text": - if (part.type == url_pattern_part_type::FIXED_TEXT) { - // If part's modifier is "none" - if (part.modifier == url_pattern_part_modifier::none) { - result.append(escape_regexp_string(part.value)); - } else { - // (?:) - result.append("(?:"); - result.append(escape_regexp_string(part.value)); - result.push_back(')'); - result.append(convert_modifier_to_string(part.modifier)); - } - continue; - } - - // Assert: part's name is not the empty string - ADA_ASSERT_TRUE(!part.name.empty()); - name_list.push_back(part.name); - - // Use string_view to avoid copies where possible - std::string_view regexp_value = part.value; - - if (part.type == url_pattern_part_type::SEGMENT_WILDCARD) { - // Lazy generate segment wildcard regexp - if (segment_wildcard_regexp.empty()) { - segment_wildcard_regexp = generate_segment_wildcard_regexp(options); - } - regexp_value = segment_wildcard_regexp; - } else if (part.type == url_pattern_part_type::FULL_WILDCARD) { - regexp_value = ".*"; - } - - // If part's prefix is the empty string and part's suffix is the empty - // string - if (part.prefix.empty() && part.suffix.empty()) { - // If part's modifier is "none" or "optional" - if (part.modifier == url_pattern_part_modifier::none || - part.modifier == url_pattern_part_modifier::optional) { - // () - result.push_back('('); - result.append(regexp_value); - result.push_back(')'); - result.append(convert_modifier_to_string(part.modifier)); - } else { - // ((?:)) - result.append("((?:"); - result.append(regexp_value); - result.push_back(')'); - result.append(convert_modifier_to_string(part.modifier)); - result.push_back(')'); - } - continue; - } - - // If part's modifier is "none" or "optional" - if (part.modifier == url_pattern_part_modifier::none || - part.modifier == url_pattern_part_modifier::optional) { - // (?:()) - result.append("(?:"); - result.append(escape_regexp_string(part.prefix)); - result.push_back('('); - result.append(regexp_value); - result.push_back(')'); - result.append(escape_regexp_string(part.suffix)); - result.push_back(')'); - result.append(convert_modifier_to_string(part.modifier)); - continue; - } - - // Assert: part's modifier is "zero-or-more" or "one-or-more" - ADA_ASSERT_TRUE(part.modifier == url_pattern_part_modifier::zero_or_more || - part.modifier == url_pattern_part_modifier::one_or_more); - - // Assert: part's prefix is not the empty string or part's suffix is not the - // empty string - ADA_ASSERT_TRUE(!part.prefix.empty() || !part.suffix.empty()); - - // (?:((?:)(?:(?:))*))? - // Append "(?:" to the end of result. - result.append("(?:"); - // Append the result of running escape a regexp string given part's prefix - // to the end of result. - result.append(escape_regexp_string(part.prefix)); - // Append "((?:" to the end of result. - result.append("((?:"); - // Append regexp value to the end of result. - result.append(regexp_value); - // Append ")(?:" to the end of result. - result.append(")(?:"); - // Append the result of running escape a regexp string given part's suffix - // to the end of result. - result.append(escape_regexp_string(part.suffix)); - // Append the result of running escape a regexp string given part's prefix - // to the end of result. - result.append(escape_regexp_string(part.prefix)); - // Append "(?:" to the end of result. - result.append("(?:"); - // Append regexp value to the end of result. - result.append(regexp_value); - // Append "))*)" to the end of result. - result.append("))*)"); - // Append the result of running escape a regexp string given part's suffix - // to the end of result. - result.append(escape_regexp_string(part.suffix)); - // Append ")" to the end of result. - result.append(")"); - - // If part's modifier is "zero-or-more" then append "?" to the end of result - if (part.modifier == url_pattern_part_modifier::zero_or_more) { - result += "?"; - } - } - - // Append "$" to the end of result - result += "$"; - - // Return (result, name list) - return {std::move(result), std::move(name_list)}; -} - -bool is_ipv6_address(std::string_view input) noexcept { - // If input's code point length is less than 2, then return false. - if (input.size() < 2) return false; - - // Let input code points be input interpreted as a list of code points. - // If input code points[0] is U+005B ([), then return true. - if (input.front() == '[') return true; - // If input code points[0] is U+007B ({) and input code points[1] is U+005B - // ([), then return true. - if (input.starts_with("{[")) return true; - // If input code points[0] is U+005C (\) and input code points[1] is U+005B - // ([), then return true. - return input.starts_with("\\["); -} - -std::string_view convert_modifier_to_string( - url_pattern_part_modifier modifier) { - switch (modifier) { - // If modifier is "zero-or-more", then return "*". - case url_pattern_part_modifier::zero_or_more: - return "*"; - // If modifier is "optional", then return "?". - case url_pattern_part_modifier::optional: - return "?"; - // If modifier is "one-or-more", then return "+". - case url_pattern_part_modifier::one_or_more: - return "+"; - // Return the empty string. - default: - return ""; - } -} - -std::string generate_segment_wildcard_regexp( - url_pattern_compile_component_options options) { - // Let result be "[^". - std::string result = "[^"; - // Append the result of running escape a regexp string given options's - // delimiter code point to the end of result. - result.append(escape_regexp_string(options.get_delimiter())); - // Append "]+?" to the end of result. - result.append("]+?"); - // Return result. - ada_log("generate_segment_wildcard_regexp result: ", result); - return result; -} - -namespace { -// Unified lookup table for URL pattern character classification -// Bit flags for different character types -constexpr uint8_t CHAR_SCHEME = 1; // valid in scheme (a-z, A-Z, 0-9, +, -, .) -constexpr uint8_t CHAR_UPPER = 2; // uppercase letter (needs lowercasing) -constexpr uint8_t CHAR_SIMPLE_HOSTNAME = 4; // simple hostname (a-z, 0-9, -, .) -constexpr uint8_t CHAR_SIMPLE_PATHNAME = - 8; // simple pathname (a-z, A-Z, 0-9, /, -, _, ~) - -constexpr std::array char_class_table = []() consteval { - std::array table{}; - for (int c = 'a'; c <= 'z'; c++) - table[c] = CHAR_SCHEME | CHAR_SIMPLE_HOSTNAME | CHAR_SIMPLE_PATHNAME; - for (int c = 'A'; c <= 'Z'; c++) - table[c] = CHAR_SCHEME | CHAR_UPPER | CHAR_SIMPLE_PATHNAME; - for (int c = '0'; c <= '9'; c++) - table[c] = CHAR_SCHEME | CHAR_SIMPLE_HOSTNAME | CHAR_SIMPLE_PATHNAME; - table['+'] = CHAR_SCHEME; - table['-'] = CHAR_SCHEME | CHAR_SIMPLE_HOSTNAME | CHAR_SIMPLE_PATHNAME; - table['.'] = - CHAR_SCHEME | CHAR_SIMPLE_HOSTNAME; // not pathname (needs normalization) - table['/'] = CHAR_SIMPLE_PATHNAME; - table['_'] = CHAR_SIMPLE_PATHNAME; - table['~'] = CHAR_SIMPLE_PATHNAME; - return table; -}(); -} // namespace - -tl::expected canonicalize_protocol( - std::string_view input) { - ada_log("canonicalize_protocol called with input=", input); - if (input.empty()) [[unlikely]] { - return ""; - } - - if (input.ends_with(":")) { - input.remove_suffix(1); - } - - // Fast path: special schemes are already canonical - if (scheme::is_special(input)) { - return std::string(input); - } - - // Fast path: validate scheme chars and check for uppercase - // First char must be alpha (not +, -, ., or digit) - uint8_t first_flags = char_class_table[static_cast(input[0])]; - if (!(first_flags & CHAR_SCHEME) || input[0] == '+' || input[0] == '-' || - input[0] == '.' || unicode::is_ascii_digit(input[0])) { - return tl::unexpected(errors::type_error); - } - - uint8_t needs_lowercase = first_flags & CHAR_UPPER; - for (size_t i = 1; i < input.size(); i++) { - uint8_t flags = char_class_table[static_cast(input[i])]; - if (!(flags & CHAR_SCHEME)) { - return tl::unexpected(errors::type_error); - } - needs_lowercase |= flags & CHAR_UPPER; - } - - if (needs_lowercase == 0) { - return std::string(input); - } - - std::string result(input); - unicode::to_lower_ascii(result.data(), result.size()); - return result; -} - -tl::expected canonicalize_username( - std::string_view input) { - // If value is the empty string, return value. - if (input.empty()) [[unlikely]] { - return ""; - } - // Percent-encode the input using the userinfo percent-encode set. - size_t idx = ada::unicode::percent_encode_index( - input, character_sets::USERINFO_PERCENT_ENCODE); - if (idx == input.size()) { - // No encoding needed, return input as-is - return std::string(input); - } - // Percent-encode from the first character that needs encoding - return ada::unicode::percent_encode( - input, character_sets::USERINFO_PERCENT_ENCODE, idx); -} - -tl::expected canonicalize_password( - std::string_view input) { - // If value is the empty string, return value. - if (input.empty()) [[unlikely]] { - return ""; - } - // Percent-encode the input using the userinfo percent-encode set. - size_t idx = ada::unicode::percent_encode_index( - input, character_sets::USERINFO_PERCENT_ENCODE); - if (idx == input.size()) { - // No encoding needed, return input as-is - return std::string(input); - } - // Percent-encode from the first character that needs encoding - return ada::unicode::percent_encode( - input, character_sets::USERINFO_PERCENT_ENCODE, idx); -} - -tl::expected canonicalize_hostname( - std::string_view input) { - ada_log("canonicalize_hostname input=", input); - if (input.empty()) [[unlikely]] { - return ""; - } - - // Fast path: simple hostnames (lowercase ASCII, digits, -, .) need no IDNA. - // The simple character set also covers IPv4-shaped hosts, which the slow - // path rewrites ("0" -> "0.0.0.0", "0x7f.1" -> "127.0.0.1"). Exclude those - // so the fast path can't return a non-canonical hostname. - // - // Pure-ASCII "xn--" labels are intentionally left on the fast path: with - // beStrict=false, domain-to-ASCII returns the lowercased ASCII input even - // when Unicode ToASCII would fail (e.g. invalid ACE "xn--a"). See - // https://url.spec.whatwg.org/#concept-domain-to-ascii - bool needs_processing = false; - for (char c : input) { - needs_processing |= - !(char_class_table[static_cast(c)] & CHAR_SIMPLE_HOSTNAME); - } - if (!needs_processing && !checkers::is_ipv4(input)) { - return std::string(input); - } - - // Let dummyURL be a new URL record. - // Let parseResult be the result of running the basic URL parser given value - // with dummyURL as url and hostname state as state override. - - // IMPORTANT: The protocol needs to be a special protocol, otherwise the - // hostname will not be converted using IDNA. - auto url = ada::parse("https://dummy.test", nullptr); - ADA_ASSERT_TRUE(url); - // if (!isValidHostnameInput(hostname)) return kj::none; - if (!url->set_hostname(input)) { - // If parseResult is failure, then throw a TypeError. - return tl::unexpected(errors::type_error); - } - // Return dummyURL's host, serialized, or empty string if it is null. - return std::string(url->get_hostname()); -} - -tl::expected canonicalize_ipv6_hostname( - std::string_view input) { - ada_log("canonicalize_ipv6_hostname input=", input); - // TODO: Optimization opportunity: Use lookup table to speed up checking - if (std::ranges::any_of(input, [](char c) { - return c != '[' && c != ']' && c != ':' && - !unicode::is_ascii_hex_digit(c); - })) { - return tl::unexpected(errors::type_error); - } - // Append the result of running ASCII lowercase given code point to the end of - // result. - auto hostname = std::string(input); - unicode::to_lower_ascii(hostname.data(), hostname.size()); - return hostname; -} - -tl::expected canonicalize_port( - std::string_view port_value) { - // If portValue is the empty string, return portValue. - if (port_value.empty()) [[unlikely]] { - return ""; - } - - // Remove ASCII tab or newline characters - std::string trimmed(port_value); - helpers::remove_ascii_tab_or_newline(trimmed); - - if (trimmed.empty()) { - return ""; - } - - // Input should start with a digit character - if (!unicode::is_ascii_digit(trimmed.front())) { - return tl::unexpected(errors::type_error); - } - - // Find the first non-digit character - auto first_non_digit = - std::ranges::find_if_not(trimmed, unicode::is_ascii_digit); - std::string_view digits_to_parse = - std::string_view(trimmed.data(), first_non_digit - trimmed.begin()); - - // Here we have a range of ASCII digit characters identified by - // digits_to_parse. It is non-empty. The port state of the URL parser reads - // it as an integer, so leading zeros are part of the number: "0080" is 80 - // and "065535" is 65535, not a six-digit overflow. Only the significant - // digits count toward the 5-digit maximum, matching the fast path in - // implementation.cpp. - size_t first_significant = digits_to_parse.find_first_not_of('0'); - if (first_significant == std::string_view::npos) { - // The value is all zeros, i.e. port 0. - return "0"; - } - digits_to_parse.remove_prefix(first_significant); - // We want to determine whether it is a valid port number (0-65535). - // Clearly, if the length is greater than 5, it is invalid. - // If the length is 5, we need to compare lexicographically to "65535". - // Otherwise it is valid. - if (digits_to_parse.size() == 5) { - if (digits_to_parse > "65535") { - return tl::unexpected(errors::type_error); - } - } else if (digits_to_parse.size() > 5) { - return tl::unexpected(errors::type_error); - } - // It is valid! Most times, we do not need to parse it into an integer. - return std::string(digits_to_parse); -} - -tl::expected canonicalize_port_with_protocol( - std::string_view port_value, std::string_view protocol) { - // If portValue is the empty string, return portValue. - if (port_value.empty()) [[unlikely]] { - return ""; - } - - // Handle empty or trailing colon in protocol - if (protocol.empty()) { - protocol = "fake"; - } else if (protocol.ends_with(":")) { - protocol.remove_suffix(1); - } - - // Remove ASCII tab or newline characters - std::string trimmed(port_value); - helpers::remove_ascii_tab_or_newline(trimmed); - - if (trimmed.empty()) { - return ""; - } - - // Input should start with a digit character - if (!unicode::is_ascii_digit(trimmed.front())) { - return tl::unexpected(errors::type_error); - } - - // Find the first non-digit character - auto first_non_digit = - std::ranges::find_if_not(trimmed, unicode::is_ascii_digit); - std::string_view digits_to_parse = - std::string_view(trimmed.data(), first_non_digit - trimmed.begin()); - - // Parse the port number - uint16_t parsed_port{}; - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - auto result = std::from_chars(digits_to_parse.data(), - digits_to_parse.data() + digits_to_parse.size(), - parsed_port); - - if (result.ec == std::errc::result_out_of_range) { - return tl::unexpected(errors::type_error); - } - - if (result.ec == std::errc()) { - // Check if this is the default port for the scheme - uint16_t default_port = scheme::get_special_port(protocol); - - // If it's the default port for a special scheme, return empty string - if (default_port != 0 && default_port == parsed_port) { - return ""; - } - - // Successfully parsed, return as string - return std::to_string(parsed_port); - } - - return tl::unexpected(errors::type_error); -} - -tl::expected canonicalize_pathname( - std::string_view input) { - if (input.empty()) [[unlikely]] { - return ""; - } - - // Fast path: simple pathnames (no . which needs normalization) can be - // returned as-is - bool needs_processing = false; - for (char c : input) { - needs_processing |= - !(char_class_table[static_cast(c)] & CHAR_SIMPLE_PATHNAME); - } - if (!needs_processing) { - return std::string(input); - } - - // Let leading slash be true if the first code point in value is U+002F (/) - // and otherwise false. - const bool leading_slash = input.starts_with("/"); - // Let modified value be "/-" if leading slash is false and otherwise the - // empty string. - const auto modified_value = - (leading_slash ? std::string() : std::string("/-")) + std::string(input); - // The spec runs the basic URL parser with the path in state override, where - // '?' and '#' are ordinary path code points (percent-encoded) and leading or - // trailing C0 control or space is kept. Building a full URL string and - // parsing it (no state override) instead let '?' and '#' start the - // query/fragment, so the pathname was silently truncated at the first literal - // '?' or '#'. set_pathname runs the parser in path state override, matching - // how canonicalize_hostname uses set_hostname. - auto url = ada::parse("fake://fake-url", nullptr); - ADA_ASSERT_TRUE(url); - if (!url->set_pathname(modified_value)) { - // If parseResult is failure, then throw a TypeError. - return tl::unexpected(errors::type_error); - } - const auto pathname = url->get_pathname(); - // If leading slash is false, then set result to the code point substring - // from 2 to the end of the string within result. - if (!leading_slash) { - // pathname should start with "/-" but path traversal (e.g. "../../") - // can reduce it to just "/" which is shorter than 2 characters. - if (pathname.size() < 2) { - return tl::unexpected(errors::type_error); - } - return std::string(pathname.substr(2)); - } - return std::string(pathname); -} - -tl::expected canonicalize_opaque_pathname( - std::string_view input) { - // If value is the empty string, return value. - if (input.empty()) [[unlikely]] { - return ""; - } - // Let dummyURL be a new URL record. - // Set dummyURL's path to the empty string. - // Let parseResult be the result of running URL parsing given value with - // dummyURL as url and opaque path state as state override. - if (auto url = - ada::parse("fake:" + std::string(input), nullptr)) { - // Return the result of URL path serializing dummyURL. - return std::string(url->get_pathname()); - } - // If parseResult is failure, then throw a TypeError. - return tl::unexpected(errors::type_error); -} - -tl::expected canonicalize_search(std::string_view input) { - // If value is the empty string, return value. - if (input.empty()) [[unlikely]] { - return ""; - } - // A leading '?' is a valid query code point here: it is the caller (process a - // search) that removes a single delimiter, not this step, which mirrors the - // basic URL parser's query state. - std::string new_value(input); - // Remove ASCII tab or newline characters - helpers::remove_ascii_tab_or_newline(new_value); - - if (new_value.empty()) { - return ""; - } - - // Percent-encode using QUERY_PERCENT_ENCODE (for non-special URLs) - // Note: "fake://dummy.test" is not a special URL, so we use - // QUERY_PERCENT_ENCODE - size_t idx = ada::unicode::percent_encode_index( - new_value, character_sets::QUERY_PERCENT_ENCODE); - if (idx == new_value.size()) { - // No encoding needed - return new_value; - } - // Percent-encode from the first character that needs encoding - return ada::unicode::percent_encode( - new_value, character_sets::QUERY_PERCENT_ENCODE, idx); -} - -tl::expected canonicalize_hash(std::string_view input) { - // If value is the empty string, return value. - if (input.empty()) [[unlikely]] { - return ""; - } - // A leading '#' is a valid fragment code point here: it is the caller - // (process a hash) that removes a single delimiter, not this step, which - // mirrors the basic URL parser's fragment state. - std::string new_value(input); - // Remove ASCII tab or newline characters - helpers::remove_ascii_tab_or_newline(new_value); - - if (new_value.empty()) { - return ""; - } - - // Percent-encode using FRAGMENT_PERCENT_ENCODE - size_t idx = ada::unicode::percent_encode_index( - new_value, character_sets::FRAGMENT_PERCENT_ENCODE); - if (idx == new_value.size()) { - // No encoding needed - return new_value; - } - // Percent-encode from the first character that needs encoding - return ada::unicode::percent_encode( - new_value, character_sets::FRAGMENT_PERCENT_ENCODE, idx); -} - -tl::expected, errors> tokenize(std::string_view input, - token_policy policy) { - ada_log("tokenize input: ", input); - // Let tokenizer be a new tokenizer. - // Set tokenizer's input to input. - // Set tokenizer's policy to policy. - auto tokenizer = Tokenizer(input, policy); - // While tokenizer's index is less than tokenizer's input's code point length: - while (tokenizer.index < tokenizer.input.size()) { - // Run seek and get the next code point given tokenizer and tokenizer's - // index. - tokenizer.seek_and_get_next_code_point(tokenizer.index); - - // Malformed UTF-8 must not stall tokenization: report an invalid-char - // token (lenient) or fail fast (strict), while always making progress. - if (tokenizer.had_invalid_code_point()) { - if (auto error = tokenizer.process_tokenizing_error(tokenizer.next_index, - tokenizer.index)) { - return tl::unexpected(*error); - } - continue; - } - - // If tokenizer's code point is U+002A (*): - if (tokenizer.code_point == '*') { - // Run add a token with default position and length given tokenizer and - // "asterisk". - tokenizer.add_token_with_defaults(token_type::ASTERISK); - ada_log("add ASTERISK token"); - // Continue. - continue; - } - - // If tokenizer's code point is U+002B (+) or U+003F (?): - if (tokenizer.code_point == '+' || tokenizer.code_point == '?') { - // Run add a token with default position and length given tokenizer and - // "other-modifier". - tokenizer.add_token_with_defaults(token_type::OTHER_MODIFIER); - // Continue. - continue; - } - - // If tokenizer's code point is U+005C (\): - if (tokenizer.code_point == '\\') { - // If tokenizer's index is equal to tokenizer's input's code point length - // - 1: - if (tokenizer.index == tokenizer.input.size() - 1) { - // Run process a tokenizing error given tokenizer, tokenizer's next - // index, and tokenizer's index. - if (auto error = tokenizer.process_tokenizing_error( - tokenizer.next_index, tokenizer.index)) { - ada_log("process_tokenizing_error failed"); - return tl::unexpected(*error); - } - continue; - } - - // Let escaped index be tokenizer's next index. - auto escaped_index = tokenizer.next_index; - // Run get the next code point given tokenizer. - tokenizer.get_next_code_point(); - if (tokenizer.had_invalid_code_point()) { - if (auto error = tokenizer.process_tokenizing_error( - tokenizer.next_index, escaped_index)) { - return tl::unexpected(*error); - } - continue; - } - // Run add a token with default length given tokenizer, "escaped-char", - // tokenizer's next index, and escaped index. - tokenizer.add_token_with_default_length( - token_type::ESCAPED_CHAR, tokenizer.next_index, escaped_index); - ada_log("add ESCAPED_CHAR token on next_index ", tokenizer.next_index, - " with escaped index ", escaped_index); - // Continue. - continue; - } - - // If tokenizer's code point is U+007B ({): - if (tokenizer.code_point == '{') { - // Run add a token with default position and length given tokenizer and - // "open". - tokenizer.add_token_with_defaults(token_type::OPEN); - ada_log("add OPEN token"); - continue; - } - - // If tokenizer's code point is U+007D (}): - if (tokenizer.code_point == '}') { - // Run add a token with default position and length given tokenizer and - // "close". - tokenizer.add_token_with_defaults(token_type::CLOSE); - ada_log("add CLOSE token"); - continue; - } - - // If tokenizer's code point is U+003A (:): - if (tokenizer.code_point == ':') { - // Let name position be tokenizer's next index. - auto name_position = tokenizer.next_index; - // Let name start be name position. - auto name_start = name_position; - bool invalid_name = false; - // While name position is less than tokenizer's input's code point length: - while (name_position < tokenizer.input.size()) { - // Run seek and get the next code point given tokenizer and name - // position. - tokenizer.seek_and_get_next_code_point(name_position); - if (tokenizer.had_invalid_code_point()) { - if (auto error = tokenizer.process_tokenizing_error( - tokenizer.next_index, name_position)) { - return tl::unexpected(*error); - } - invalid_name = true; - break; - } - // Let first code point be true if name position equals name start and - // false otherwise. - bool first_code_point = name_position == name_start; - // Let valid code point be the result of running is a valid name code - // point given tokenizer's code point and first code point. - auto valid_code_point = - idna::valid_name_code_point(tokenizer.code_point, first_code_point); - ada_log("tokenizer.code_point=", uint32_t(tokenizer.code_point), - " first_code_point=", first_code_point, - " valid_code_point=", valid_code_point); - // If valid code point is false break. - if (!valid_code_point) break; - // Set name position to tokenizer's next index. - name_position = tokenizer.next_index; - } - - if (invalid_name) { - continue; - } - - // If name position is less than or equal to name start: - if (name_position <= name_start) { - // Run process a tokenizing error given tokenizer, name start, and - // tokenizer's index. - if (auto error = tokenizer.process_tokenizing_error(name_start, - tokenizer.index)) { - ada_log("process_tokenizing_error failed"); - return tl::unexpected(*error); - } - // Continue - continue; - } - - // Run add a token with default length given tokenizer, "name", name - // position, and name start. - tokenizer.add_token_with_default_length(token_type::NAME, name_position, - name_start); - continue; - } - - // If tokenizer's code point is U+0028 ((): - if (tokenizer.code_point == '(') { - // Let depth be 1. - size_t depth = 1; - // Let regexp position be tokenizer's next index. - auto regexp_position = tokenizer.next_index; - // Let regexp start be regexp position. - auto regexp_start = regexp_position; - // Let error be false. - bool error = false; - - // While regexp position is less than tokenizer's input's code point - // length: - while (regexp_position < tokenizer.input.size()) { - // Run seek and get the next code point given tokenizer and regexp - // position. - tokenizer.seek_and_get_next_code_point(regexp_position); - if (tokenizer.had_invalid_code_point()) { - if (auto process_error = tokenizer.process_tokenizing_error( - tokenizer.next_index, regexp_position)) { - return tl::unexpected(*process_error); - } - error = true; - break; - } - - // TODO: Optimization opportunity: The next 2 if statements can be - // merged. If the result of running is ASCII given tokenizer's code - // point is false: - if (!unicode::is_ascii(tokenizer.code_point)) { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - return tl::unexpected(*process_error); - } - // Set error to true. - error = true; - break; - } - - // If regexp position equals regexp start and tokenizer's code point is - // U+003F (?): - if (regexp_position == regexp_start && tokenizer.code_point == '?') { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - return tl::unexpected(*process_error); - } - // Set error to true; - error = true; - break; - } - - // If tokenizer's code point is U+005C (\): - if (tokenizer.code_point == '\\') { - // If regexp position equals tokenizer's input's code point length - 1 - if (regexp_position == tokenizer.input.size() - 1) { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - return tl::unexpected(*process_error); - } - // Set error to true. - error = true; - break; - } - // Run get the next code point given tokenizer. - auto escaped_index = tokenizer.next_index; - tokenizer.get_next_code_point(); - if (tokenizer.had_invalid_code_point()) { - if (auto process_error = tokenizer.process_tokenizing_error( - tokenizer.next_index, escaped_index)) { - return tl::unexpected(*process_error); - } - error = true; - break; - } - // If the result of running is ASCII given tokenizer's code point is - // false: - if (!unicode::is_ascii(tokenizer.code_point)) { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index); - process_error.has_value()) { - return tl::unexpected(*process_error); - } - // Set error to true. - error = true; - break; - } - // Set regexp position to tokenizer's next index. - regexp_position = tokenizer.next_index; - continue; - } - - // If tokenizer's code point is U+0029 ()): - if (tokenizer.code_point == ')') { - // Decrement depth by 1. - depth--; - // If depth is 0: - if (depth == 0) { - // Set regexp position to tokenizer's next index. - regexp_position = tokenizer.next_index; - // Break. - break; - } - } else if (tokenizer.code_point == '(') { - // Otherwise if tokenizer's code point is U+0028 ((): - // Increment depth by 1. - depth++; - // If regexp position equals tokenizer's input's code point length - - // 1: - if (regexp_position == tokenizer.input.size() - 1) { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - return tl::unexpected(*process_error); - } - // Set error to true. - error = true; - break; - } - // Let temporary position be tokenizer's next index. - auto temporary_position = tokenizer.next_index; - // Run get the next code point given tokenizer. - tokenizer.get_next_code_point(); - if (tokenizer.had_invalid_code_point()) { - if (auto process_error = tokenizer.process_tokenizing_error( - tokenizer.next_index, temporary_position)) { - return tl::unexpected(*process_error); - } - error = true; - break; - } - // If tokenizer's code point is not U+003F (?): - if (tokenizer.code_point != '?') { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - return tl::unexpected(*process_error); - } - // Set error to true. - error = true; - break; - } - // Set tokenizer's next index to temporary position. - tokenizer.next_index = temporary_position; - } - // Set regexp position to tokenizer's next index. - regexp_position = tokenizer.next_index; - } - - // If error is true continue. - if (error) continue; - // If depth is not zero: - if (depth != 0) { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - return tl::unexpected(*process_error); - } - continue; - } - // Let regexp length be regexp position - regexp start - 1. - auto regexp_length = regexp_position - regexp_start - 1; - // If regexp length is zero: - if (regexp_length == 0) { - // Run process a tokenizing error given tokenizer, regexp start, and - // tokenizer's index. - if (auto process_error = tokenizer.process_tokenizing_error( - regexp_start, tokenizer.index)) { - ada_log("process_tokenizing_error failed"); - return tl::unexpected(*process_error); - } - continue; - } - // Run add a token given tokenizer, "regexp", regexp position, regexp - // start, and regexp length. - tokenizer.add_token(token_type::REGEXP, regexp_position, regexp_start, - regexp_length); - continue; - } - // Run add a token with default position and length given tokenizer and - // "char". - tokenizer.add_token_with_defaults(token_type::CHAR); - } - // Run add a token with default length given tokenizer, "end", tokenizer's - // index, and tokenizer's index. - tokenizer.add_token_with_default_length(token_type::END, tokenizer.index, - tokenizer.index); - - ada_log("tokenizer.token_list size is: ", tokenizer.token_list.size()); - // Return tokenizer's token list. - return tokenizer.token_list; -} - -namespace { -constexpr std::array escape_pattern_table = []() consteval { - std::array out{}; - for (auto& c : {'+', '*', '?', ':', '{', '}', '(', ')', '\\'}) { - out[c] = 1; - } - return out; -}(); - -constexpr bool should_escape_pattern_char(char c) { - return escape_pattern_table[static_cast(c)]; -} -} // namespace - -std::string escape_pattern_string(std::string_view input) { - ada_log("escape_pattern_string called with input=", input); - if (input.empty()) [[unlikely]] { - return ""; - } - // Assert: input is an ASCII string. - ADA_ASSERT_TRUE(ada::idna::is_ascii(input)); - // Let result be the empty string. - std::string result{}; - // Reserve extra space for potential escapes - result.reserve(input.size() * 2); - - // While index is less than input's length: - for (const char c : input) { - if (should_escape_pattern_char(c)) { - // Append U+005C (\) to the end of result. - result.push_back('\\'); - } - // Append c to the end of result. - result.push_back(c); - } - // Return result. - return result; -} - -namespace { -constexpr std::array escape_regexp_table = []() consteval { - std::array out{}; - for (auto& c : {'.', '+', '*', '?', '^', '$', '{', '}', '(', ')', '[', ']', - '|', '/', '\\'}) { - out[c] = 1; - } - return out; -}(); - -constexpr bool should_escape_regexp_char(char c) { - return escape_regexp_table[(uint8_t)c]; -} -} // namespace - -std::string escape_regexp_string(std::string_view input) { - // Assert: input is an ASCII string. - ADA_ASSERT_TRUE(idna::is_ascii(input)); - // Let result be the empty string. - std::string result{}; - // Reserve extra space for potential escapes (worst case: all chars escaped) - result.reserve(input.size() * 2); - for (const char c : input) { - if (should_escape_regexp_char(c)) { - // Avoid temporary string allocation - directly append characters - result.push_back('\\'); - result.push_back(c); - } else { - result.push_back(c); - } - } - return result; -} - -std::string process_base_url_string(std::string_view input, - url_pattern_init::process_type type) { - // If type is not "pattern" return input. - if (type != url_pattern_init::process_type::pattern) { - return std::string(input); - } - // Return the result of escaping a pattern string given input. - return escape_pattern_string(input); -} - -constexpr bool is_absolute_pathname( - std::string_view input, url_pattern_init::process_type type) noexcept { - // If input is the empty string, then return false. - if (input.empty()) [[unlikely]] { - return false; - } - // If input[0] is U+002F (/), then return true. - if (input.starts_with("/")) return true; - // If type is "url", then return false. - if (type == url_pattern_init::process_type::url) return false; - // If input's code point length is less than 2, then return false. - if (input.size() < 2) return false; - // If input[0] is U+005C (\) and input[1] is U+002F (/), then return true. - // If input[0] is U+007B ({) and input[1] is U+002F (/), then return true. - // Return false. - return input[1] == '/' && (input[0] == '\\' || input[0] == '{'); -} - -std::string generate_pattern_string( - std::vector& part_list, - url_pattern_compile_component_options& options) { - // Let result be the empty string. - std::string result{}; - // Let index list be the result of getting the indices for part list. - // For each index of index list: - for (size_t index = 0; index < part_list.size(); index++) { - // Let part be part list[index]. - // Use reference to avoid copy - const auto& part = part_list[index]; - // Let previous part be part list[index - 1] if index is greater than 0, - // otherwise let it be null. - // Use pointer to avoid copy - const url_pattern_part* previous_part = - index == 0 ? nullptr : &part_list[index - 1]; - // Let next part be part list[index + 1] if index is less than index list's - // size - 1, otherwise let it be null. - const url_pattern_part* next_part = - index < part_list.size() - 1 ? &part_list[index + 1] : nullptr; - // If part's type is "fixed-text" then: - if (part.type == url_pattern_part_type::FIXED_TEXT) { - // If part's modifier is "none" then: - if (part.modifier == url_pattern_part_modifier::none) { - // Append the result of running escape a pattern string given part's - // value to the end of result. - result.append(escape_pattern_string(part.value)); - continue; - } - // Append "{" to the end of result. - result += "{"; - // Append the result of running escape a pattern string given part's value - // to the end of result. - result.append(escape_pattern_string(part.value)); - // Append "}" to the end of result. - result += "}"; - // Append the result of running convert a modifier to a string given - // part's modifier to the end of result. - result.append(convert_modifier_to_string(part.modifier)); - continue; - } - // Let custom name be true if part's name[0] is not an ASCII digit; - // otherwise false. - bool custom_name = !unicode::is_ascii_digit(part.name[0]); - // Let needs grouping be true if at least one of the following are true, - // otherwise let it be false: - // - part's suffix is not the empty string. - // - part's prefix is not the empty string and is not options's prefix code - // point. - bool needs_grouping = - !part.suffix.empty() || - (!part.prefix.empty() && (options.get_prefix().empty() || - part.prefix[0] != options.get_prefix()[0])); - - // If all of the following are true: - // - needs grouping is false; and - // - custom name is true; and - // - part's type is "segment-wildcard"; and - // - part's modifier is "none"; and - // - next part is not null; and - // - next part's prefix is the empty string; and - // - next part's suffix is the empty string - if (!needs_grouping && custom_name && - part.type == url_pattern_part_type::SEGMENT_WILDCARD && - part.modifier == url_pattern_part_modifier::none && next_part && - next_part->prefix.empty() && next_part->suffix.empty()) { - // If next part's type is "fixed-text": - if (next_part->type == url_pattern_part_type::FIXED_TEXT) { - // Set needs grouping to true if the result of running is a valid name - // code point given next part's value's first code point and the boolean - // false is true. - if (idna::valid_name_code_point(next_part->value[0], false)) { - needs_grouping = true; - } - } else { - // Set needs grouping to true if next part's name[0] is an ASCII digit. - needs_grouping = !next_part->name.empty() && - unicode::is_ascii_digit(next_part->name[0]); - } - } - - // If all of the following are true: - // - needs grouping is false; and - // - part's prefix is the empty string; and - // - previous part is not null; and - // - previous part's type is "fixed-text"; and - // - previous part's value's last code point is options's prefix code point. - // then set needs grouping to true. - if (!needs_grouping && part.prefix.empty() && previous_part && - previous_part->type == url_pattern_part_type::FIXED_TEXT && - !previous_part->value.empty() && !options.get_prefix().empty() && - previous_part->value.back() == options.get_prefix()[0]) { - needs_grouping = true; - } - - // Assert: part's name is not the empty string or null. - ADA_ASSERT_TRUE(!part.name.empty()); - - // If needs grouping is true, then append "{" to the end of result. - if (needs_grouping) { - result.append("{"); - } - - // Append the result of running escape a pattern string given part's prefix - // to the end of result. - result.append(escape_pattern_string(part.prefix)); - - // If custom name is true: - if (custom_name) { - // Append ":" to the end of result. - result.append(":"); - // Append part's name to the end of result. - result.append(part.name); - } - - // If part's type is "regexp" then: - if (part.type == url_pattern_part_type::REGEXP) { - // Append "(" to the end of result. - result.append("("); - // Append part's value to the end of result. - result.append(part.value); - // Append ")" to the end of result. - result.append(")"); - } else if (part.type == url_pattern_part_type::SEGMENT_WILDCARD && - !custom_name) { - // Otherwise if part's type is "segment-wildcard" and custom name is - // false: Append "(" to the end of result. - result.append("("); - // Append the result of running generate a segment wildcard regexp given - // options to the end of result. - result.append(generate_segment_wildcard_regexp(options)); - // Append ")" to the end of result. - result.append(")"); - } else if (part.type == url_pattern_part_type::FULL_WILDCARD) { - // Otherwise if part's type is "full-wildcard": - // If custom name is false and one of the following is true: - // - previous part is null; or - // - previous part's type is "fixed-text"; or - // - previous part's modifier is not "none"; or - // - needs grouping is true; or - // - part's prefix is not the empty string - // - then append "*" to the end of result. - if (!custom_name && - (!previous_part || - previous_part->type == url_pattern_part_type::FIXED_TEXT || - previous_part->modifier != url_pattern_part_modifier::none || - needs_grouping || !part.prefix.empty())) { - result.append("*"); - } else { - // Append "(" to the end of result. - // Append full wildcard regexp value to the end of result. - // Append ")" to the end of result. - result.append("(.*)"); - } - } - - // If all of the following are true: - // - part's type is "segment-wildcard"; and - // - custom name is true; and - // - part's suffix is not the empty string; and - // - The result of running is a valid name code point given part's suffix's - // first code point and the boolean false is true then append U+005C (\) to - // the end of result. - if (part.type == url_pattern_part_type::SEGMENT_WILDCARD && custom_name && - !part.suffix.empty() && - idna::valid_name_code_point(part.suffix[0], false)) { - result.append("\\"); - } - - // Append the result of running escape a pattern string given part's suffix - // to the end of result. - result.append(escape_pattern_string(part.suffix)); - // If needs grouping is true, then append "}" to the end of result. - if (needs_grouping) result.append("}"); - // Append the result of running convert a modifier to a string given part's - // modifier to the end of result. - result.append(convert_modifier_to_string(part.modifier)); - } - // Return result. - return result; -} -} // namespace ada::url_pattern_helpers - -#endif // ADA_INCLUDE_URL_PATTERN -/* end file src/url_pattern_helpers.cpp */ -/* begin file src/url_pattern_regex.cpp */ -#if ADA_INCLUDE_URL_PATTERN - - -namespace ada::url_pattern_regex { - -#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER -std::optional std_regex_provider::create_instance( - std::string_view pattern, bool ignore_case) { - // Let flags be an empty string. - // If options's ignore case is true then set flags to "vi". - // Otherwise set flags to "v" - auto flags = ignore_case - ? std::regex::icase | std::regex_constants::ECMAScript - : std::regex_constants::ECMAScript; - try { - return std::regex(pattern.data(), pattern.size(), flags); - } catch (const std::regex_error& e) { - (void)e; - ada_log("std_regex_provider::create_instance failed:", e.what()); - return std::nullopt; - } -} - -std::optional>> -std_regex_provider::regex_search(std::string_view input, - const std::regex& pattern) { - // Use iterator-based regex_search to avoid string allocation - std::match_results match_result; - try { - if (!std::regex_search(input.begin(), input.end(), match_result, pattern, - std::regex_constants::match_any)) { - return std::nullopt; - } - } catch (const std::regex_error& e) { - (void)e; - ada_log("std_regex_provider::regex_search failed:", e.what()); - return std::nullopt; - } - std::vector> matches; - // An empty input can still match with capture groups (e.g. "(x*)" or an - // optional ":a?" against ""), so the group values must be extracted here - // just like for a non-empty input. A successful regex_search always leaves - // the full match at index 0, so match_result is never empty at this point. - if (match_result.empty()) { - return matches; - } - matches.reserve(match_result.size()); - for (size_t i = 1; i < match_result.size(); ++i) { - if (auto entry = match_result[i]; entry.matched) { - matches.emplace_back(entry.str()); - } else { - // A capture group that did not participate in the match is undefined, - // not absent. Emitting nullopt keeps the result aligned by position with - // the component's group name list; dropping it shifts every later group - // onto the wrong name. - matches.emplace_back(std::nullopt); - } - } - return matches; -} - -bool std_regex_provider::regex_match(std::string_view input, - const std::regex& pattern) { - try { - return std::regex_match(input.begin(), input.end(), pattern); - } catch (const std::regex_error& e) { - (void)e; - ada_log("std_regex_provider::regex_match failed:", e.what()); - return false; - } -} - -#endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER - -} // namespace ada::url_pattern_regex - -#endif // ADA_INCLUDE_URL_PATTERN -/* end file src/url_pattern_regex.cpp */ -#endif // ADA_INCLUDE_URL_PATTERN - -/* begin file src/ada_c.cpp */ -// NOLINTBEGIN(bugprone-exception-escape, -// bugprone-suspicious-stringview-data-usage) - -ada::result& get_instance(void* result) noexcept { - return *(ada::result*)result; -} - -extern "C" { -typedef void* ada_url; -typedef void* ada_url_search_params; -typedef void* ada_strings; -typedef void* ada_url_search_params_keys_iter; -typedef void* ada_url_search_params_values_iter; -typedef void* ada_url_search_params_entries_iter; - -struct ada_string { - const char* data; - size_t length; -}; - -struct ada_owned_string { - const char* data; - size_t length; -}; - -struct ada_string_pair { - ada_string key; - ada_string value; -}; - -ada_string ada_string_create(const char* data, size_t length) { - ada_string out{}; - out.data = data; - out.length = length; - return out; -} - -struct ada_url_components { - /* - * By using 32-bit integers, we implicitly assume that the URL string - * cannot exceed 4 GB. - * - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - */ - uint32_t protocol_end; - /** - * Username end is not `omitted` by default (-1) to make username and password - * getters less costly to implement. - */ - uint32_t username_end; - uint32_t host_start; - uint32_t host_end; - uint32_t port; - uint32_t pathname_start; - uint32_t search_start; - uint32_t hash_start; -}; - -ada_url ada_parse(const char* input, size_t length) noexcept { - return new ada::result( - ada::parse(std::string_view(input, length))); -} - -ada_url ada_parse_with_base(const char* input, size_t input_length, - const char* base, size_t base_length) noexcept { - auto base_out = - ada::parse(std::string_view(base, base_length)); - - if (!base_out) { - return new ada::result(base_out); - } - - return new ada::result(ada::parse( - std::string_view(input, input_length), &base_out.value())); -} - -bool ada_can_parse(const char* input, size_t length) noexcept { - return ada::can_parse(std::string_view(input, length)); -} - -bool ada_can_parse_with_base(const char* input, size_t input_length, - const char* base, size_t base_length) noexcept { - std::string_view base_view(base, base_length); - return ada::can_parse(std::string_view(input, input_length), &base_view); -} - -void ada_free(ada_url result) noexcept { - auto* r = (ada::result*)result; - delete r; -} - -ada_url ada_copy(ada_url input) noexcept { - ada::result& r = get_instance(input); - return new ada::result(r); -} - -bool ada_is_valid(ada_url result) noexcept { - ada::result& r = get_instance(result); - return r.has_value(); -} - -// caller must free the result with ada_free_owned_string -ada_owned_string ada_get_origin(ada_url result) noexcept { - ada::result& r = get_instance(result); - ada_owned_string owned{}; - if (!r) { - owned.data = nullptr; - owned.length = 0; - return owned; - } - std::string out = r->get_origin(); - owned.length = out.size(); - owned.data = new char[owned.length]; - memcpy((void*)owned.data, out.data(), owned.length); - return owned; -} - -void ada_free_owned_string(ada_owned_string owned) noexcept { - delete[] owned.data; -} - -ada_string ada_get_href(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_href(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_username(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_username(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_password(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_password(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_port(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_port(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_hash(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_hash(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_host(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_host(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_hostname(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_hostname(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_pathname(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_pathname(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_search(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_search(); - return ada_string_create(out.data(), out.length()); -} - -ada_string ada_get_protocol(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view out = r->get_protocol(); - return ada_string_create(out.data(), out.length()); -} - -uint8_t ada_get_host_type(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return 0; - } - return r->host_type; -} - -uint8_t ada_get_scheme_type(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return 0; - } - return r->type; -} - -bool ada_set_href(ada_url result, const char* input, size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_href(std::string_view(input, length)); -} - -bool ada_set_host(ada_url result, const char* input, size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_host(std::string_view(input, length)); -} - -bool ada_set_hostname(ada_url result, const char* input, - size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_hostname(std::string_view(input, length)); -} - -bool ada_set_protocol(ada_url result, const char* input, - size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_protocol(std::string_view(input, length)); -} - -bool ada_set_username(ada_url result, const char* input, - size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_username(std::string_view(input, length)); -} - -bool ada_set_password(ada_url result, const char* input, - size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_password(std::string_view(input, length)); -} - -bool ada_set_port(ada_url result, const char* input, size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_port(std::string_view(input, length)); -} - -bool ada_set_pathname(ada_url result, const char* input, - size_t length) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->set_pathname(std::string_view(input, length)); -} - -/** - * Update the search/query of the URL. - * - * If a URL has `?` as the search value, passing empty string to this function - * does not remove the attribute. If you need to remove it, please use - * `ada_clear_search` method. - */ -void ada_set_search(ada_url result, const char* input, size_t length) noexcept { - ada::result& r = get_instance(result); - if (r) { - r->set_search(std::string_view(input, length)); - } -} - -/** - * Update the hash/fragment of the URL. - * - * If a URL has `#` as the hash value, passing empty string to this function - * does not remove the attribute. If you need to remove it, please use - * `ada_clear_hash` method. - */ -void ada_set_hash(ada_url result, const char* input, size_t length) noexcept { - ada::result& r = get_instance(result); - if (r) { - r->set_hash(std::string_view(input, length)); - } -} - -void ada_clear_port(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (r) { - r->clear_port(); - } -} - -/** - * Removes the hash of the URL. - * - * Despite `ada_set_hash` method, this function allows the complete - * removal of the hash attribute, even if it has a value of `#`. - */ -void ada_clear_hash(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (r) { - r->clear_hash(); - } -} - -/** - * Removes the search of the URL. - * - * Despite `ada_set_search` method, this function allows the complete - * removal of the search attribute, even if it has a value of `?`. - */ -void ada_clear_search(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (r) { - r->clear_search(); - } -} - -bool ada_has_credentials(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_credentials(); -} - -bool ada_has_empty_hostname(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_empty_hostname(); -} - -bool ada_has_hostname(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_hostname(); -} - -bool ada_has_non_empty_username(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_non_empty_username(); -} - -bool ada_has_non_empty_password(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_non_empty_password(); -} - -bool ada_has_port(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_port(); -} - -bool ada_has_password(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_password(); -} - -bool ada_has_hash(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_hash(); -} - -bool ada_has_search(ada_url result) noexcept { - ada::result& r = get_instance(result); - if (!r) { - return false; - } - return r->has_search(); -} - -// returns a pointer to the internal url_aggregator::url_components -const ada_url_components* ada_get_components(ada_url result) noexcept { - static_assert(sizeof(ada_url_components) == sizeof(ada::url_components)); - ada::result& r = get_instance(result); - if (!r) { - return nullptr; - } - return reinterpret_cast(&r->get_components()); -} - -ada_owned_string ada_idna_to_unicode(const char* input, size_t length) { - std::string out = ada::idna::to_unicode(std::string_view(input, length)); - ada_owned_string owned{}; - owned.length = out.length(); - owned.data = new char[owned.length]; - memcpy((void*)owned.data, out.data(), owned.length); - return owned; -} - -ada_owned_string ada_idna_to_ascii(const char* input, size_t length) { - std::string out = ada::idna::to_ascii(std::string_view(input, length)); - ada_owned_string owned{}; - owned.length = out.size(); - owned.data = new char[owned.length]; - memcpy((void*)owned.data, out.data(), owned.length); - return owned; -} - -ada_url_search_params ada_parse_search_params(const char* input, - size_t length) { - return new ada::result( - ada::url_search_params(std::string_view(input, length))); -} - -void ada_free_search_params(ada_url_search_params result) { - auto* r = (ada::result*)result; - delete r; -} - -ada_owned_string ada_search_params_to_string(ada_url_search_params result) { - ada::result& r = - *(ada::result*)result; - if (!r) return ada_owned_string{nullptr, 0}; - std::string out = r->to_string(); - ada_owned_string owned{}; - owned.length = out.size(); - owned.data = new char[owned.length]; - memcpy((void*)owned.data, out.data(), owned.length); - return owned; -} - -size_t ada_search_params_size(ada_url_search_params result) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return 0; - } - return r->size(); -} - -void ada_search_params_sort(ada_url_search_params result) { - ada::result& r = - *(ada::result*)result; - if (r) { - r->sort(); - } -} - -void ada_search_params_reset(ada_url_search_params result, const char* input, - size_t length) { - ada::result& r = - *(ada::result*)result; - if (r) { - r->reset(std::string_view(input, length)); - } -} - -void ada_search_params_append(ada_url_search_params result, const char* key, - size_t key_length, const char* value, - size_t value_length) { - ada::result& r = - *(ada::result*)result; - if (r) { - r->append(std::string_view(key, key_length), - std::string_view(value, value_length)); - } -} - -void ada_search_params_set(ada_url_search_params result, const char* key, - size_t key_length, const char* value, - size_t value_length) { - ada::result& r = - *(ada::result*)result; - if (r) { - r->set(std::string_view(key, key_length), - std::string_view(value, value_length)); - } -} - -void ada_search_params_remove(ada_url_search_params result, const char* key, - size_t key_length) { - ada::result& r = - *(ada::result*)result; - if (r) { - r->remove(std::string_view(key, key_length)); - } -} - -void ada_search_params_remove_value(ada_url_search_params result, - const char* key, size_t key_length, - const char* value, size_t value_length) { - ada::result& r = - *(ada::result*)result; - if (r) { - r->remove(std::string_view(key, key_length), - std::string_view(value, value_length)); - } -} - -bool ada_search_params_has(ada_url_search_params result, const char* key, - size_t key_length) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return false; - } - return r->has(std::string_view(key, key_length)); -} - -bool ada_search_params_has_value(ada_url_search_params result, const char* key, - size_t key_length, const char* value, - size_t value_length) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return false; - } - return r->has(std::string_view(key, key_length), - std::string_view(value, value_length)); -} - -ada_string ada_search_params_get(ada_url_search_params result, const char* key, - size_t key_length) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return ada_string_create(nullptr, 0); - } - auto found = r->get(std::string_view(key, key_length)); - if (!found.has_value()) { - return ada_string_create(nullptr, 0); - } - return ada_string_create(found->data(), found->length()); -} - -ada_strings ada_search_params_get_all(ada_url_search_params result, - const char* key, size_t key_length) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return new ada::result>( - std::vector()); - } - return new ada::result>( - r->get_all(std::string_view(key, key_length))); -} - -ada_url_search_params_keys_iter ada_search_params_get_keys( - ada_url_search_params result) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return new ada::result( - ada::url_search_params_keys_iter()); - } - return new ada::result(r->get_keys()); -} - -ada_url_search_params_values_iter ada_search_params_get_values( - ada_url_search_params result) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return new ada::result( - ada::url_search_params_values_iter()); - } - return new ada::result(r->get_values()); -} - -ada_url_search_params_entries_iter ada_search_params_get_entries( - ada_url_search_params result) { - ada::result& r = - *(ada::result*)result; - if (!r) { - return new ada::result( - ada::url_search_params_entries_iter()); - } - return new ada::result(r->get_entries()); -} - -void ada_free_strings(ada_strings result) { - auto* r = (ada::result>*)result; - delete r; -} - -size_t ada_strings_size(ada_strings result) { - auto* r = (ada::result>*)result; - if (!r) { - return 0; - } - return (*r)->size(); -} - -ada_string ada_strings_get(ada_strings result, size_t index) { - auto* r = (ada::result>*)result; - if (!r) { - return ada_string_create(nullptr, 0); - } - std::string_view view = (*r)->at(index); - return ada_string_create(view.data(), view.length()); -} - -void ada_free_search_params_keys_iter(ada_url_search_params_keys_iter result) { - auto* r = (ada::result*)result; - delete r; -} - -ada_string ada_search_params_keys_iter_next( - ada_url_search_params_keys_iter result) { - auto* r = (ada::result*)result; - if (!r) { - return ada_string_create(nullptr, 0); - } - auto next = (*r)->next(); - if (!next.has_value()) { - return ada_string_create(nullptr, 0); - } - return ada_string_create(next->data(), next->length()); -} - -bool ada_search_params_keys_iter_has_next( - ada_url_search_params_keys_iter result) { - auto* r = (ada::result*)result; - if (!r) { - return false; - } - return (*r)->has_next(); -} - -void ada_free_search_params_values_iter( - ada_url_search_params_values_iter result) { - auto* r = (ada::result*)result; - delete r; -} - -ada_string ada_search_params_values_iter_next( - ada_url_search_params_values_iter result) { - auto* r = (ada::result*)result; - if (!r) { - return ada_string_create(nullptr, 0); - } - auto next = (*r)->next(); - if (!next.has_value()) { - return ada_string_create(nullptr, 0); - } - return ada_string_create(next->data(), next->length()); -} - -bool ada_search_params_values_iter_has_next( - ada_url_search_params_values_iter result) { - auto* r = (ada::result*)result; - if (!r) { - return false; - } - return (*r)->has_next(); -} - -void ada_free_search_params_entries_iter( - ada_url_search_params_entries_iter result) { - auto* r = (ada::result*)result; - delete r; -} - -ada_string_pair ada_search_params_entries_iter_next( - ada_url_search_params_entries_iter result) { - auto* r = (ada::result*)result; - if (!r) return {ada_string_create(nullptr, 0), ada_string_create(nullptr, 0)}; - auto next = (*r)->next(); - if (!next.has_value()) { - return {ada_string_create(nullptr, 0), ada_string_create(nullptr, 0)}; - } - return ada_string_pair{ - ada_string_create(next->first.data(), next->first.length()), - ada_string_create(next->second.data(), next->second.length())}; -} - -bool ada_search_params_entries_iter_has_next( - ada_url_search_params_entries_iter result) { - auto* r = (ada::result*)result; - if (!r) { - return false; - } - return (*r)->has_next(); -} - -void ada_set_max_input_length(uint32_t length) noexcept { - ada::set_max_input_length(length); -} - -uint32_t ada_get_max_input_length() noexcept { - return ada::get_max_input_length(); -} - -typedef struct { - int major; - int minor; - int revision; -} ada_version_components; - -const char* ada_get_version() { return ADA_VERSION; } - -ada_version_components ada_get_version_components() { - return ada_version_components{ - .major = ada::ADA_VERSION_MAJOR, - .minor = ada::ADA_VERSION_MINOR, - .revision = ada::ADA_VERSION_REVISION, - }; -} - -} // extern "C" -// NOLINTEND(bugprone-exception-escape, -// bugprone-suspicious-stringview-data-usage) -/* end file src/ada_c.cpp */ -/* end file src/ada.cpp */ diff --git a/deps/ada.h b/deps/ada.h deleted file mode 100644 index dc96fe8..0000000 --- a/deps/ada.h +++ /dev/null @@ -1,11654 +0,0 @@ -/* auto-generated on 2026-07-27 16:10:09 -0400. Do not edit! */ -/* begin file include/ada.h */ -/** - * @file ada.h - * @brief Main header for the Ada URL parser library. - * - * This is the primary entry point for the Ada URL parser library. Including - * this single header provides access to the complete Ada API, including: - * - * - URL parsing via `ada::parse()` function - * - Two URL representations: `ada::url` and `ada::url_aggregator` - * - URL search parameters via `ada::url_search_params` - * - URL pattern matching via `ada::url_pattern` (URLPattern API) - * - IDNA (Internationalized Domain Names) support - * - * @example - * ```cpp - * - * // Parse a URL - * auto url = ada::parse("https://example.com/path?query=1"); - * if (url) { - * std::cout << url->get_hostname(); // "example.com" - * } - * ``` - * - * @see https://url.spec.whatwg.org/ - WHATWG URL Standard - * @see https://github.com/ada-url/ada - Ada URL Parser GitHub Repository - */ -#ifndef ADA_H -#define ADA_H - -/* begin file include/ada/ada_idna.h */ -/* auto-generated on 2026-07-12 20:34:08 -0400. Do not edit! */ -/* begin file include/idna.h */ -#ifndef ADA_IDNA_H -#define ADA_IDNA_H - -/* begin file include/ada/idna/unicode_transcoding.h */ -#ifndef ADA_IDNA_UNICODE_TRANSCODING_H -#define ADA_IDNA_UNICODE_TRANSCODING_H - -#include -#include - -namespace ada::idna { - -size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output); - -size_t utf8_length_from_utf32(const char32_t* buf, size_t len); - -size_t utf32_length_from_utf8(const char* buf, size_t len); - -size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output); - -} // namespace ada::idna - -#endif // ADA_IDNA_UNICODE_TRANSCODING_H -/* end file include/ada/idna/unicode_transcoding.h */ -/* begin file include/ada/idna/mapping.h */ -#ifndef ADA_IDNA_MAPPING_H -#define ADA_IDNA_MAPPING_H - -#include -#include - -namespace ada::idna { - -// If the input is ascii, then the mapping is just -> lower case. -void ascii_map(char* input, size_t length); -// Map the characters according to IDNA, returning the empty string on error. -std::u32string map(std::u32string_view input); -// Map into an existing buffer (cleared on entry). Returns false if any code -// point is disallowed. Reusing the buffer avoids repeated heap allocations -// when called in a loop over multiple labels. -bool map(std::u32string_view input, std::u32string& out); - -} // namespace ada::idna - -#endif -/* end file include/ada/idna/mapping.h */ -/* begin file include/ada/idna/normalization.h */ -#ifndef ADA_IDNA_NORMALIZATION_H -#define ADA_IDNA_NORMALIZATION_H - -#include -#include - -namespace ada::idna { - -// Returns true if `input` is already in Unicode Normalization Form C. -// Requires that internal tables have been loaded (call ensure via normalize -// or map first, or this returns false if tables are unavailable). -[[nodiscard]] bool is_already_nfc(std::u32string_view input) noexcept; - -// Normalize the characters according to IDNA (Unicode Normalization Form C). -// Returns false if the internal Unicode tables could not be loaded; in that -// case `input` is left unchanged. Skips work when the string is already NFC. -[[nodiscard]] bool normalize(std::u32string& input); - -} // namespace ada::idna -#endif -/* end file include/ada/idna/normalization.h */ -/* begin file include/ada/idna/punycode.h */ -#ifndef ADA_IDNA_PUNYCODE_H -#define ADA_IDNA_PUNYCODE_H - -#include -#include - -namespace ada::idna { - -bool punycode_to_utf32(std::string_view input, std::u32string& out); -bool verify_punycode(std::string_view input); -bool utf32_to_punycode(std::u32string_view input, std::string& out); - -} // namespace ada::idna - -#endif // ADA_IDNA_PUNYCODE_H -/* end file include/ada/idna/punycode.h */ -/* begin file include/ada/idna/validity.h */ -#ifndef ADA_IDNA_VALIDITY_H -#define ADA_IDNA_VALIDITY_H - -#include -#include - -namespace ada::idna { - -/** - * @see https://www.unicode.org/reports/tr46/#Validity_Criteria - */ -bool is_label_valid(std::u32string_view label); - -} // namespace ada::idna - -#endif // ADA_IDNA_VALIDITY_H -/* end file include/ada/idna/validity.h */ -/* begin file include/ada/idna/to_ascii.h */ -#ifndef ADA_IDNA_TO_ASCII_H -#define ADA_IDNA_TO_ASCII_H - -#include -#include - -/* begin file include/ada/idna/limits.h */ -#ifndef ADA_IDNA_LIMITS_H -#define ADA_IDNA_LIMITS_H - -#include - -namespace ada::idna { - -// Maximum accepted UTF-8 domain length for to_ascii / to_unicode. -// Bounds heap growth under untrusted input (DoS resistance). DNS wire limits -// are smaller; this allows long Unicode labels used in URL tests/fixtures. -inline constexpr size_t max_domain_input_bytes = 16384; - -} // namespace ada::idna - -#endif // ADA_IDNA_LIMITS_H -/* end file include/ada/idna/limits.h */ - -namespace ada::idna { - -// Converts a domain (e.g., www.google.com) possibly containing international -// characters to an ascii domain (with punycode). It will not do percent -// decoding: percent decoding should be done prior to calling this function. We -// do not remove tabs and spaces, they should have been removed prior to calling -// this function. We also do not trim control characters. We also assume that -// the input is not empty. We return "" on error. Inputs longer than -// max_domain_input_bytes are rejected. -// -// This function may accept or even produce invalid domains (WHATWG carve-outs). -std::string to_ascii(std::string_view ut8_string); - -// Same as to_ascii, but writes into `out` and returns false on error without -// relying on empty-string ambiguity. -[[nodiscard]] bool to_ascii(std::string_view ut8_string, std::string& out); - -// Returns true if the string contains a forbidden code point according to the -// WHATGL URL specification: -// https://url.spec.whatwg.org/#forbidden-domain-code-point -bool contains_forbidden_domain_code_point(std::string_view ascii_string); - -bool constexpr is_ascii(std::u32string_view view); -bool constexpr is_ascii(std::string_view view); - -} // namespace ada::idna - -#endif // ADA_IDNA_TO_ASCII_H -/* end file include/ada/idna/to_ascii.h */ -/* begin file include/ada/idna/to_unicode.h */ -#ifndef ADA_IDNA_TO_UNICODE_H -#define ADA_IDNA_TO_UNICODE_H - -#include -#include - -namespace ada::idna { - -// UTS #46 ToUnicode. Never fails per the standard: on step failure the original -// label is kept. Inputs longer than max_domain_input_bytes are returned -// unchanged as a safety measure under untrusted input. -std::string to_unicode(std::string_view input); - -// Writes into `out`. Returns false only if the input exceeds -// max_domain_input_bytes (out is left empty). Otherwise always returns true -// (ToUnicode does not fail). -[[nodiscard]] bool to_unicode(std::string_view input, std::string& out); - -} // namespace ada::idna - -#endif // ADA_IDNA_TO_UNICODE_H -/* end file include/ada/idna/to_unicode.h */ -/* begin file include/ada/idna/identifier.h */ -#ifndef ADA_IDNA_IDENTIFIER_H -#define ADA_IDNA_IDENTIFIER_H - -#include -#include - -namespace ada::idna { - -// Verify if it is valid name code point given a Unicode code point and a -// boolean first: If first is true return the result of checking if code point -// is contained in the IdentifierStart set of code points. Otherwise return the -// result of checking if code point is contained in the IdentifierPart set of -// code points. Returns false if the input is empty or the code point is not -// valid. There is minimal Unicode error handling: the input should be valid -// UTF-8. https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point -bool valid_name_code_point(char32_t code_point, bool first); - -} // namespace ada::idna - -#endif -/* end file include/ada/idna/identifier.h */ - -#endif -/* end file include/idna.h */ -/* end file include/ada/ada_idna.h */ -/* begin file include/ada/character_sets.h */ -/** - * @file character_sets.h - * @brief Declaration of the character sets used by unicode functions. - * @author Node.js - * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc - */ -#ifndef ADA_CHARACTER_SETS_H -#define ADA_CHARACTER_SETS_H - -/* begin file include/ada/common_defs.h */ -/** - * @file common_defs.h - * @brief Cross-platform compiler macros and common definitions. - * - * This header provides compiler-specific macros for optimization hints, - * platform detection, SIMD support detection, and development/debug utilities. - * It ensures consistent behavior across different compilers (GCC, Clang, MSVC). - */ -#ifndef ADA_COMMON_DEFS_H -#define ADA_COMMON_DEFS_H - -// https://en.cppreference.com/w/cpp/feature_test#Library_features -// detect C++20 features -#include - -#ifdef _MSC_VER -#define ADA_VISUAL_STUDIO 1 -/** - * We want to differentiate carefully between - * clang under visual studio and regular visual - * studio. - */ -#ifdef __clang__ -// clang under visual studio -#define ADA_CLANG_VISUAL_STUDIO 1 -#else -// just regular visual studio (best guess) -#define ADA_REGULAR_VISUAL_STUDIO 1 -#endif // __clang__ -#endif // _MSC_VER - -#if defined(__GNUC__) -// Marks a block with a name so that MCA analysis can see it. -#define ADA_BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name); -#define ADA_END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name); -#define ADA_DEBUG_BLOCK(name, block) \ - BEGIN_DEBUG_BLOCK(name); \ - block; \ - END_DEBUG_BLOCK(name); -#else -#define ADA_BEGIN_DEBUG_BLOCK(name) -#define ADA_END_DEBUG_BLOCK(name) -#define ADA_DEBUG_BLOCK(name, block) -#endif - -// Align to N-byte boundary -#define ADA_ROUNDUP_N(a, n) (((a) + ((n) - 1)) & ~((n) - 1)) -#define ADA_ROUNDDOWN_N(a, n) ((a) & ~((n) - 1)) - -#define ADA_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n) - 1)) == 0) - -#if defined(ADA_REGULAR_VISUAL_STUDIO) - -#define ada_really_inline __forceinline -#define ada_never_inline __declspec(noinline) - -#define ada_unused -#define ada_warn_unused - -#define ADA_PUSH_DISABLE_WARNINGS __pragma(warning(push)) -#define ADA_PUSH_DISABLE_ALL_WARNINGS __pragma(warning(push, 0)) -#define ADA_DISABLE_VS_WARNING(WARNING_NUMBER) \ - __pragma(warning(disable : WARNING_NUMBER)) -// Get rid of Intellisense-only warnings (Code Analysis) -// Though __has_include is C++17, it is supported in Visual Studio 2017 or -// better (_MSC_VER>=1910). -#ifdef __has_include -#if __has_include() -#include -#define ADA_DISABLE_UNDESIRED_WARNINGS \ - ADA_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS) -#endif -#endif - -#ifndef ADA_DISABLE_UNDESIRED_WARNINGS -#define ADA_DISABLE_UNDESIRED_WARNINGS -#endif - -#define ADA_DISABLE_DEPRECATED_WARNING ADA_DISABLE_VS_WARNING(4996) -#define ADA_DISABLE_STRICT_OVERFLOW_WARNING -#define ADA_POP_DISABLE_WARNINGS __pragma(warning(pop)) - -#else // ADA_REGULAR_VISUAL_STUDIO - -#define ada_really_inline inline __attribute__((always_inline)) -#define ada_never_inline inline __attribute__((noinline)) - -#define ada_unused __attribute__((unused)) -#define ada_warn_unused __attribute__((warn_unused_result)) - -#define ADA_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push") -// gcc doesn't seem to disable all warnings with all and extra, add warnings -// here as necessary -#define ADA_PUSH_DISABLE_ALL_WARNINGS \ - ADA_PUSH_DISABLE_WARNINGS \ - ADA_DISABLE_GCC_WARNING("-Weffc++") \ - ADA_DISABLE_GCC_WARNING("-Wall") \ - ADA_DISABLE_GCC_WARNING("-Wconversion") \ - ADA_DISABLE_GCC_WARNING("-Wextra") \ - ADA_DISABLE_GCC_WARNING("-Wattributes") \ - ADA_DISABLE_GCC_WARNING("-Wimplicit-fallthrough") \ - ADA_DISABLE_GCC_WARNING("-Wnon-virtual-dtor") \ - ADA_DISABLE_GCC_WARNING("-Wreturn-type") \ - ADA_DISABLE_GCC_WARNING("-Wshadow") \ - ADA_DISABLE_GCC_WARNING("-Wunused-parameter") \ - ADA_DISABLE_GCC_WARNING("-Wunused-variable") \ - ADA_DISABLE_GCC_WARNING("-Wsign-compare") -#define ADA_PRAGMA(P) _Pragma(#P) -#define ADA_DISABLE_GCC_WARNING(WARNING) \ - ADA_PRAGMA(GCC diagnostic ignored WARNING) -#if defined(ADA_CLANG_VISUAL_STUDIO) -#define ADA_DISABLE_UNDESIRED_WARNINGS \ - ADA_DISABLE_GCC_WARNING("-Wmicrosoft-include") -#else -#define ADA_DISABLE_UNDESIRED_WARNINGS -#endif -#define ADA_DISABLE_DEPRECATED_WARNING \ - ADA_DISABLE_GCC_WARNING("-Wdeprecated-declarations") -#define ADA_DISABLE_STRICT_OVERFLOW_WARNING \ - ADA_DISABLE_GCC_WARNING("-Wstrict-overflow") -#define ADA_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop") - -#endif // MSC_VER - -#if defined(ADA_VISUAL_STUDIO) -/** - * It does not matter here whether you are using - * the regular visual studio or clang under visual - * studio. - */ -#if ADA_USING_LIBRARY -#define ADA_DLLIMPORTEXPORT __declspec(dllimport) -#else -#define ADA_DLLIMPORTEXPORT __declspec(dllexport) -#endif -#else -#define ADA_DLLIMPORTEXPORT -#endif - -/// If EXPR is an error, returns it. -#define ADA_TRY(EXPR) \ - { \ - auto _err = (EXPR); \ - if (_err) { \ - return _err; \ - } \ - } - -// __has_cpp_attribute is part of C++20 -#if !defined(__has_cpp_attribute) -#define __has_cpp_attribute(x) 0 -#endif - -#if __has_cpp_attribute(gnu::noinline) -#define ADA_ATTRIBUTE_NOINLINE [[gnu::noinline]] -#else -#define ADA_ATTRIBUTE_NOINLINE -#endif - -namespace ada { -[[noreturn]] inline void unreachable() { -#ifdef __GNUC__ - __builtin_unreachable(); -#elif defined(_MSC_VER) - __assume(false); -#else -#endif -} -} // namespace ada - -// Unless the programmer has already set ADA_DEVELOPMENT_CHECKS, -// we want to set it under debug builds. We detect a debug build -// under Visual Studio when the _DEBUG macro is set. Under the other -// compilers, we use the fact that they define __OPTIMIZE__ whenever -// they allow optimizations. -// It is possible that this could miss some cases where ADA_DEVELOPMENT_CHECKS -// is helpful, but the programmer can set the macro ADA_DEVELOPMENT_CHECKS. -// It could also wrongly set ADA_DEVELOPMENT_CHECKS (e.g., if the programmer -// sets _DEBUG in a release build under Visual Studio, or if some compiler fails -// to set the __OPTIMIZE__ macro). -#if !defined(ADA_DEVELOPMENT_CHECKS) && !defined(NDEBUG) -#ifdef _MSC_VER -// Visual Studio seems to set _DEBUG for debug builds. -#ifdef _DEBUG -#define ADA_DEVELOPMENT_CHECKS 1 -#endif // _DEBUG -#else // _MSC_VER -// All other compilers appear to set __OPTIMIZE__ to a positive integer -// when the compiler is optimizing. -#ifndef __OPTIMIZE__ -#define ADA_DEVELOPMENT_CHECKS 1 -#endif // __OPTIMIZE__ -#endif // _MSC_VER -#endif // ADA_DEVELOPMENT_CHECKS - -#define ADA_STR(x) #x - -#if ADA_DEVELOPMENT_CHECKS -#define ADA_REQUIRE(EXPR) \ - { \ - if (!(EXPR) { abort(); }) } - -#define ADA_FAIL(MESSAGE) \ - do { \ - std::cerr << "FAIL: " << (MESSAGE) << std::endl; \ - abort(); \ - } while (0); -#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE) \ - do { \ - if (LHS != RHS) { \ - std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \ - ADA_FAIL(MESSAGE); \ - } \ - } while (0); -#define ADA_ASSERT_TRUE(COND) \ - do { \ - if (!(COND)) { \ - std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \ - << std::endl; \ - ADA_FAIL(ADA_STR(COND)); \ - } \ - } while (0); -#else -#define ADA_FAIL(MESSAGE) -#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE) -#define ADA_ASSERT_TRUE(COND) -#endif - -#ifdef ADA_VISUAL_STUDIO -#define ADA_ASSUME(COND) __assume(COND) -#else -#define ADA_ASSUME(COND) \ - do { \ - if (!(COND)) { \ - __builtin_unreachable(); \ - } \ - } while (0) -#endif - -#if defined(__SSSE3__) -#define ADA_SSSE3 1 -#endif - -#if defined(__SSE2__) || defined(__x86_64__) || defined(__x86_64) || \ - (defined(_M_AMD64) || defined(_M_X64) || \ - (defined(_M_IX86_FP) && _M_IX86_FP == 2)) -#define ADA_SSE2 1 -#endif - -// AVX-512 byte/word ops + 128/256-bit vectors of AVX-512 instructions. -// Used for optional high-performance IP address parsing kernels. -#if defined(__AVX512BW__) && defined(__AVX512VL__) -#define ADA_AVX512 1 -#endif - -#if defined(__aarch64__) || defined(_M_ARM64) -#define ADA_NEON 1 -#endif - -#if defined(__loongarch_sx) -#define ADA_LSX 1 -#endif - -#if defined(__riscv_v) && __riscv_v_intrinsic >= 11000 -// Support RVV intrinsics v0.11 and above -#define ADA_RVV 1 -#endif - -#ifndef __has_cpp_attribute -#define ada_lifetime_bound -#elif __has_cpp_attribute(msvc::lifetimebound) -#define ada_lifetime_bound [[msvc::lifetimebound]] -#elif __has_cpp_attribute(clang::lifetimebound) -#define ada_lifetime_bound [[clang::lifetimebound]] -#elif __has_cpp_attribute(lifetimebound) -#define ada_lifetime_bound [[lifetimebound]] -#else -#define ada_lifetime_bound -#endif - -#ifdef __cpp_lib_format -#if __cpp_lib_format >= 202110L -#include -#define ADA_HAS_FORMAT 1 -#endif -#endif - -#ifndef ADA_INCLUDE_URL_PATTERN -#define ADA_INCLUDE_URL_PATTERN 1 -#endif // ADA_INCLUDE_URL_PATTERN - -#endif // ADA_COMMON_DEFS_H -/* end file include/ada/common_defs.h */ -#include - -/** - * These functions are not part of our public API and may - * change at any time. - * @private - * @namespace ada::character_sets - * @brief Includes the definitions for unicode character sets. - */ -namespace ada::character_sets { -ada_really_inline constexpr bool bit_at(const uint8_t a[], uint8_t i); -} // namespace ada::character_sets - -#endif // ADA_CHARACTER_SETS_H -/* end file include/ada/character_sets.h */ -/* begin file include/ada/character_sets-inl.h */ -/** - * @file character_sets-inl.h - * @brief Definitions of the character sets used by unicode functions. - * @author Node.js - * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc - */ -#ifndef ADA_CHARACTER_SETS_INL_H -#define ADA_CHARACTER_SETS_INL_H - - -/** - * These functions are not part of our public API and may - * change at any time. - * @private - */ -namespace ada::character_sets { - -constexpr char hex[1024] = - "%00\0%01\0%02\0%03\0%04\0%05\0%06\0%07\0" - "%08\0%09\0%0A\0%0B\0%0C\0%0D\0%0E\0%0F\0" - "%10\0%11\0%12\0%13\0%14\0%15\0%16\0%17\0" - "%18\0%19\0%1A\0%1B\0%1C\0%1D\0%1E\0%1F\0" - "%20\0%21\0%22\0%23\0%24\0%25\0%26\0%27\0" - "%28\0%29\0%2A\0%2B\0%2C\0%2D\0%2E\0%2F\0" - "%30\0%31\0%32\0%33\0%34\0%35\0%36\0%37\0" - "%38\0%39\0%3A\0%3B\0%3C\0%3D\0%3E\0%3F\0" - "%40\0%41\0%42\0%43\0%44\0%45\0%46\0%47\0" - "%48\0%49\0%4A\0%4B\0%4C\0%4D\0%4E\0%4F\0" - "%50\0%51\0%52\0%53\0%54\0%55\0%56\0%57\0" - "%58\0%59\0%5A\0%5B\0%5C\0%5D\0%5E\0%5F\0" - "%60\0%61\0%62\0%63\0%64\0%65\0%66\0%67\0" - "%68\0%69\0%6A\0%6B\0%6C\0%6D\0%6E\0%6F\0" - "%70\0%71\0%72\0%73\0%74\0%75\0%76\0%77\0" - "%78\0%79\0%7A\0%7B\0%7C\0%7D\0%7E\0%7F\0" - "%80\0%81\0%82\0%83\0%84\0%85\0%86\0%87\0" - "%88\0%89\0%8A\0%8B\0%8C\0%8D\0%8E\0%8F\0" - "%90\0%91\0%92\0%93\0%94\0%95\0%96\0%97\0" - "%98\0%99\0%9A\0%9B\0%9C\0%9D\0%9E\0%9F\0" - "%A0\0%A1\0%A2\0%A3\0%A4\0%A5\0%A6\0%A7\0" - "%A8\0%A9\0%AA\0%AB\0%AC\0%AD\0%AE\0%AF\0" - "%B0\0%B1\0%B2\0%B3\0%B4\0%B5\0%B6\0%B7\0" - "%B8\0%B9\0%BA\0%BB\0%BC\0%BD\0%BE\0%BF\0" - "%C0\0%C1\0%C2\0%C3\0%C4\0%C5\0%C6\0%C7\0" - "%C8\0%C9\0%CA\0%CB\0%CC\0%CD\0%CE\0%CF\0" - "%D0\0%D1\0%D2\0%D3\0%D4\0%D5\0%D6\0%D7\0" - "%D8\0%D9\0%DA\0%DB\0%DC\0%DD\0%DE\0%DF\0" - "%E0\0%E1\0%E2\0%E3\0%E4\0%E5\0%E6\0%E7\0" - "%E8\0%E9\0%EA\0%EB\0%EC\0%ED\0%EE\0%EF\0" - "%F0\0%F1\0%F2\0%F3\0%F4\0%F5\0%F6\0%F7\0" - "%F8\0%F9\0%FA\0%FB\0%FC\0%FD\0%FE\0%FF"; - -constexpr uint8_t C0_CONTROL_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 28 29 2A 2B 2C 2D 2E 2F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 40 41 42 43 44 45 46 47 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -constexpr uint8_t SPECIAL_QUERY_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x80, - // 28 29 2A 2B 2C 2D 2E 2F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00, - // 40 41 42 43 44 45 46 47 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -constexpr uint8_t QUERY_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00, - // 28 29 2A 2B 2C 2D 2E 2F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00, - // 40 41 42 43 44 45 46 47 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -constexpr uint8_t FRAGMENT_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x01 | 0x00 | 0x04 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 28 29 2A 2B 2C 2D 2E 2F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00, - // 40 41 42 43 44 45 46 47 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -constexpr uint8_t USERINFO_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00, - // 28 29 2A 2B 2C 2D 2E 2F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 40 41 42 43 44 45 46 47 - 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x00 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -constexpr uint8_t PATH_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00, - // 28 29 2A 2B 2C 2D 2E 2F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x80, - // 40 41 42 43 44 45 46 47 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x40 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x08 | 0x00 | 0x20 | 0x00 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -constexpr uint8_t WWW_FORM_URLENCODED_PERCENT_ENCODE[32] = { - // 00 01 02 03 04 05 06 07 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 08 09 0A 0B 0C 0D 0E 0F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 10 11 12 13 14 15 16 17 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 18 19 1A 1B 1C 1D 1E 1F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 20 21 22 23 24 25 26 27 - 0x00 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 28 29 2A 2B 2C 2D 2E 2F - 0x01 | 0x02 | 0x00 | 0x08 | 0x10 | 0x00 | 0x00 | 0x80, - // 30 31 32 33 34 35 36 37 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 38 39 3A 3B 3C 3D 3E 3F - 0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 40 41 42 43 44 45 46 47 - 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 48 49 4A 4B 4C 4D 4E 4F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 50 51 52 53 54 55 56 57 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00, - // 60 61 62 63 64 65 66 67 - 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 68 69 6A 6B 6C 6D 6E 6F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 70 71 72 73 74 75 76 77 - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, - // 78 79 7A 7B 7C 7D 7E 7F - 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 80 81 82 83 84 85 86 87 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 88 89 8A 8B 8C 8D 8E 8F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 90 91 92 93 94 95 96 97 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // 98 99 9A 9B 9C 9D 9E 9F - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A0 A1 A2 A3 A4 A5 A6 A7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // A8 A9 AA AB AC AD AE AF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B0 B1 B2 B3 B4 B5 B6 B7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // B8 B9 BA BB BC BD BE BF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C0 C1 C2 C3 C4 C5 C6 C7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // C8 C9 CA CB CC CD CE CF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D0 D1 D2 D3 D4 D5 D6 D7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // D8 D9 DA DB DC DD DE DF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E0 E1 E2 E3 E4 E5 E6 E7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // E8 E9 EA EB EC ED EE EF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F0 F1 F2 F3 F4 F5 F6 F7 - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, - // F8 F9 FA FB FC FD FE FF - 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; - -ada_really_inline constexpr bool bit_at(const uint8_t a[], const uint8_t i) { - return !!(a[i >> 3] & (1 << (i & 7))); -} - -} // namespace ada::character_sets - -#endif // ADA_CHARACTER_SETS_INL_H -/* end file include/ada/character_sets-inl.h */ -/* begin file include/ada/checkers-inl.h */ -/** - * @file checkers-inl.h - * @brief Definitions for URL specific checkers used within Ada. - */ -#ifndef ADA_CHECKERS_INL_H -#define ADA_CHECKERS_INL_H - -#include -#include -#include -#include -/* begin file include/ada/checkers.h */ -/** - * @file checkers.h - * @brief Declarations for URL specific checkers used within Ada. - */ -#ifndef ADA_CHECKERS_H -#define ADA_CHECKERS_H - - -#include -#include - -/** - * These functions are not part of our public API and may - * change at any time. - * @private - * @namespace ada::checkers - * @brief Includes the definitions for validation functions - */ -namespace ada::checkers { - -/** - * @private - * Assuming that x is an ASCII letter, this function returns the lower case - * equivalent. - * @details More likely to be inlined by the compiler and constexpr. - */ -constexpr char to_lower(char x) noexcept; - -/** - * @private - * Returns true if the character is an ASCII letter. Equivalent to std::isalpha - * but more likely to be inlined by the compiler. - * - * @attention std::isalpha is not constexpr generally. - */ -constexpr bool is_alpha(char x) noexcept; - -/** - * @private - * Check whether a string starts with 0x or 0X. The function is only - * safe if input.size() >=2. - * - * @see has_hex_prefix - */ -constexpr bool has_hex_prefix_unsafe(std::string_view input); -/** - * @private - * Check whether a string starts with 0x or 0X. - */ -constexpr bool has_hex_prefix(std::string_view input); - -/** - * @private - * Check whether x is an ASCII digit. More likely to be inlined than - * std::isdigit. - */ -constexpr bool is_digit(char x) noexcept; - -/** - * @private - * @details A string starts with a Windows drive letter if all of the following - * are true: - * - * - its length is greater than or equal to 2 - * - its first two code points are a Windows drive letter - * - its length is 2 or its third code point is U+002F (/), U+005C (\), U+003F - * (?), or U+0023 (#). - * - * https://url.spec.whatwg.org/#start-with-a-windows-drive-letter - */ -inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept; - -/** - * @private - * @details A normalized Windows drive letter is a Windows drive letter of which - * the second code point is U+003A (:). - */ -inline constexpr bool is_normalized_windows_drive_letter( - std::string_view input) noexcept; - -/** - * @private - * Returns true if an input is an ipv4 address. It is assumed that the string - * does not contain uppercase ASCII characters (the input should have been - * lowered cased before calling this function) and is not empty. - */ -ada_really_inline constexpr bool is_ipv4(std::string_view view) noexcept; - -/** - * @private - * Returns a bitset. If the first bit is set, then at least one character needs - * percent encoding. If the second bit is set, a \\ is found. If the third bit - * is set then we have a dot. If the fourth bit is set, then we have a percent - * character. - */ -ada_really_inline constexpr uint8_t path_signature( - std::string_view input) noexcept; - -/** - * @private - * Returns true if the length of the domain name and its labels are according to - * the specifications. The length of the domain must be 255 octets (253 - * characters not including the last 2 which are the empty label reserved at the - * end). When the empty label is included (a dot at the end), the domain name - * can have 254 characters. The length of a label must be at least 1 and at most - * 63 characters. - * @see section 3.1. of https://www.rfc-editor.org/rfc/rfc1034 - * @see https://www.unicode.org/reports/tr46/#ToASCII - */ -ada_really_inline constexpr bool verify_dns_length( - std::string_view input) noexcept; - -/** - * @private - * Fast-path parser for pure decimal IPv4 addresses (e.g., "192.168.1.1"). - * Returns the packed 32-bit IPv4 address on success, or a value > 0xFFFFFFFF - * to indicate failure (caller should fall back to general parser). - * This is optimized for the common case where the input is a well-formed - * decimal IPv4 address with exactly 4 octets. - */ -ada_really_inline uint64_t try_parse_ipv4_fast(std::string_view input) noexcept; - -/** - * Sentinel value indicating try_parse_ipv4_fast() did not succeed. - * Any value > 0xFFFFFFFF indicates the fast path should not be used. - */ -constexpr uint64_t ipv4_fast_fail = uint64_t(1) << 32; - -} // namespace ada::checkers - -#endif // ADA_CHECKERS_H -/* end file include/ada/checkers.h */ - -#if defined(ADA_AVX512) -#include -#endif - -namespace ada::checkers { - -constexpr bool has_hex_prefix_unsafe(std::string_view input) { - // This is actually efficient code, see has_hex_prefix for the assembly. - constexpr bool is_little_endian = std::endian::native == std::endian::little; - constexpr uint16_t word0x = 0x7830; - uint16_t two_first_bytes = - static_cast(input[0]) | - static_cast((static_cast(input[1]) << 8)); - if constexpr (is_little_endian) { - two_first_bytes |= 0x2000; - } else { - two_first_bytes |= 0x020; - } - return two_first_bytes == word0x; -} - -constexpr bool has_hex_prefix(std::string_view input) { - return input.size() >= 2 && has_hex_prefix_unsafe(input); -} - -constexpr bool is_digit(char x) noexcept { return (x >= '0') & (x <= '9'); } - -constexpr char to_lower(char x) noexcept { return (x | 0x20); } - -constexpr bool is_alpha(char x) noexcept { - return (to_lower(x) >= 'a') && (to_lower(x) <= 'z'); -} - -constexpr bool is_windows_drive_letter(std::string_view input) noexcept { - return input.size() >= 2 && - (is_alpha(input[0]) && ((input[1] == ':') || (input[1] == '|'))) && - ((input.size() == 2) || (input[2] == '/' || input[2] == '\\' || - input[2] == '?' || input[2] == '#')); -} - -constexpr bool is_normalized_windows_drive_letter( - std::string_view input) noexcept { - return input.size() == 2 && (is_alpha(input[0]) && (input[1] == ':')); -} - -namespace detail { - -// Unrolled pure-decimal IPv4. The common portable path for 7-16 byte hosts. -ada_really_inline uint64_t -parse_ipv4_decimal_scalar(const char* p, const char* pend) noexcept { - uint32_t ipv4 = 0; - for (int i = 0; i < 4; ++i) { - if (p == pend) [[unlikely]] { - return ipv4_fast_fail; - } - uint32_t val; - char c = *p; - if (c >= '0' && c <= '9') [[likely]] { - val = static_cast(c - '0'); - ++p; - } else { - return ipv4_fast_fail; - } - if (p < pend) { - c = *p; - if (c >= '0' && c <= '9') { - if (val == 0) [[unlikely]] { - return ipv4_fast_fail; - } - val = val * 10u + static_cast(c - '0'); - ++p; - if (p < pend) { - c = *p; - if (c >= '0' && c <= '9') { - val = val * 10u + static_cast(c - '0'); - ++p; - if (val > 255u) [[unlikely]] { - return ipv4_fast_fail; - } - } - } - } - } - ipv4 = (ipv4 << 8) | val; - if (i < 3) { - if (p == pend || *p != '.') [[unlikely]] { - return ipv4_fast_fail; - } - ++p; - } - } - if (p != pend) { - if (p == pend - 1 && *p == '.') { - return ipv4; - } - return ipv4_fast_fail; - } - return ipv4; -} - -#if defined(ADA_AVX512) -// After SIMD validation: fewer rejection branches on convert. -ada_really_inline uint64_t -parse_ipv4_decimal_trusted(const char* p, const char* pend) noexcept { - uint32_t ipv4 = 0; - for (int i = 0; i < 4; ++i) { - uint32_t val = static_cast(*p - '0'); - ++p; - if (p < pend && static_cast(*p - '0') <= 9) { - if (val == 0) [[unlikely]] { - return ipv4_fast_fail; - } - val = val * 10u + static_cast(*p - '0'); - ++p; - if (p < pend && static_cast(*p - '0') <= 9) { - val = val * 10u + static_cast(*p - '0'); - ++p; - if (val > 255u) [[unlikely]] { - return ipv4_fast_fail; - } - } - } - ipv4 = (ipv4 << 8) | val; - if (i < 3) { - ++p; // trusted '.' - } - } - return ipv4; // trailing-dot already accounted for by caller via pend -} - -// AVX-512 pure-decimal IPv4 (Lemire/Mula-style masked load + parallel checks). -// No over-read of the source string. Wins when the binary is built with -// -mavx512bw -mavx512vl (or -march that enables them). -ada_really_inline uint64_t try_parse_ipv4_avx512(const char* data, - size_t len) noexcept { - const __mmask16 live = static_cast<__mmask16>((1u << len) - 1u); - const __m128i input = - _mm_maskz_loadu_epi8(live, reinterpret_cast(data)); - const __mmask16 is_dot = - _mm_mask_cmpeq_epi8_mask(live, input, _mm_set1_epi8('.')); - const __m128i shifted = _mm_sub_epi8(input, _mm_set1_epi8('0')); - const __mmask16 is_digit = - _mm_mask_cmplt_epu8_mask(live, shifted, _mm_set1_epi8(10)); - if ((is_digit | is_dot) != live) { - return ipv4_fast_fail; - } - const unsigned dot_count = - static_cast(_mm_popcnt_u32(static_cast(is_dot))); - size_t effective_len = len; - if (dot_count == 3) { - // ok - } else if (dot_count == 4 && data[len - 1] == '.') { - effective_len = len - 1; // strip trailing dot for convert - } else { - return ipv4_fast_fail; - } - // Convert from a tiny stack copy so trusted peeks stay in-bounds. - alignas(16) char buf[16]{}; - std::memcpy(buf, data, effective_len); - return parse_ipv4_decimal_trusted(buf, buf + effective_len); -} -#endif // ADA_AVX512 - -} // namespace detail - -/** - * Fast pure-decimal IPv4 parse. Returns packed address or ipv4_fast_fail. - * Accepts an optional single trailing dot. - * - * On AVX-512BW+VL targets, uses a masked-load SIMD kernel (no source - * over-read) inspired by Lemire/Mula. Otherwise uses an unrolled scalar path - * (typically faster than SSE2/NEON pre-validation for these 7-16 byte hosts). - */ -ada_really_inline uint64_t -try_parse_ipv4_fast(std::string_view input) noexcept { - const size_t len = input.size(); - // Shortest pure decimal: "0.0.0.0" (7). Longest + trailing dot: 16. - if (len < 7 || len > 16) [[unlikely]] { - return ipv4_fast_fail; - } - const char* data = input.data(); - -#if defined(ADA_AVX512) - return detail::try_parse_ipv4_avx512(data, len); -#else - return detail::parse_ipv4_decimal_scalar(data, data + len); -#endif -} - -} // namespace ada::checkers - -#endif // ADA_CHECKERS_INL_H -/* end file include/ada/checkers-inl.h */ -/* begin file include/ada/log.h */ -/** - * @file log.h - * @brief Includes the definitions for logging. - * @private Excluded from docs through the doxygen file. - */ -#ifndef ADA_LOG_H -#define ADA_LOG_H - -// To enable logging, set ADA_LOGGING to 1: -#ifndef ADA_LOGGING -#define ADA_LOGGING 0 -#endif - -#if ADA_LOGGING -#include -#endif // ADA_LOGGING - -namespace ada { - -/** - * Log a message. If you want to have no overhead when logging is disabled, use - * the ada_log macro. - * @private - */ -template -constexpr ada_really_inline void log([[maybe_unused]] Args... args) { -#if ADA_LOGGING - ((std::cout << "ADA_LOG: ") << ... << args) << std::endl; -#endif // ADA_LOGGING -} -} // namespace ada - -#if ADA_LOGGING -#ifndef ada_log -#define ada_log(...) \ - do { \ - ada::log(__VA_ARGS__); \ - } while (0) -#endif // ada_log -#else -#define ada_log(...) -#endif // ADA_LOGGING - -#endif // ADA_LOG_H -/* end file include/ada/log.h */ -/* begin file include/ada/encoding_type.h */ -/** - * @file encoding_type.h - * @brief Character encoding type definitions. - * - * Defines the encoding types supported for URL processing. - * - * @see https://encoding.spec.whatwg.org/ - */ -#ifndef ADA_ENCODING_TYPE_H -#define ADA_ENCODING_TYPE_H - -#include - -namespace ada { - -/** - * @brief Character encoding types for URL processing. - * - * Specifies the character encoding used for percent-decoding and other - * string operations. UTF-8 is the most commonly used encoding for URLs. - * - * @see https://encoding.spec.whatwg.org/#encodings - */ -enum class encoding_type { - UTF8, /**< UTF-8 encoding (default for URLs) */ - UTF_16LE, /**< UTF-16 Little Endian encoding */ - UTF_16BE, /**< UTF-16 Big Endian encoding */ -}; - -/** - * Converts an encoding_type to its string representation. - * @param type The encoding type to convert. - * @return A string view of the encoding name. - */ -ada_warn_unused std::string_view to_string(encoding_type type); - -} // namespace ada - -#endif // ADA_ENCODING_TYPE_H -/* end file include/ada/encoding_type.h */ -/* begin file include/ada/helpers.h */ -/** - * @file helpers.h - * @brief Definitions for helper functions used within Ada. - */ -#ifndef ADA_HELPERS_H -#define ADA_HELPERS_H - -/* begin file include/ada/url_base.h */ -/** - * @file url_base.h - * @brief Base class and common definitions for URL types. - * - * This file defines the `url_base` abstract base class from which both - * `ada::url` and `ada::url_aggregator` inherit. It also defines common - * enumerations like `url_host_type`. - */ -#ifndef ADA_URL_BASE_H -#define ADA_URL_BASE_H - -/* begin file include/ada/scheme.h */ -/** - * @file scheme.h - * @brief URL scheme type definitions and utilities. - * - * This header defines the URL scheme types (http, https, etc.) and provides - * functions to identify special schemes and their default ports according - * to the WHATWG URL Standard. - * - * @see https://url.spec.whatwg.org/#special-scheme - */ -#ifndef ADA_SCHEME_H -#define ADA_SCHEME_H - - -#include - -/** - * @namespace ada::scheme - * @brief URL scheme utilities and constants. - * - * Provides functions for working with URL schemes, including identification - * of special schemes and retrieval of default port numbers. - */ -namespace ada::scheme { - -/** - * @brief Enumeration of URL scheme types. - * - * Special schemes have specific parsing rules and default ports. - * Using an enum allows efficient scheme comparisons without string operations. - * - * Default ports: - * - HTTP: 80 - * - HTTPS: 443 - * - WS: 80 - * - WSS: 443 - * - FTP: 21 - * - FILE: (none) - */ -enum type : uint8_t { - HTTP = 0, /**< http:// scheme (port 80) */ - NOT_SPECIAL = 1, /**< Non-special scheme (no default port) */ - HTTPS = 2, /**< https:// scheme (port 443) */ - WS = 3, /**< ws:// WebSocket scheme (port 80) */ - FTP = 4, /**< ftp:// scheme (port 21) */ - WSS = 5, /**< wss:// secure WebSocket scheme (port 443) */ - FILE = 6 /**< file:// scheme (no default port) */ -}; - -/** - * Checks if a scheme string is a special scheme. - * @param scheme The scheme string to check (e.g., "http", "https"). - * @return `true` if the scheme is special, `false` otherwise. - * @see https://url.spec.whatwg.org/#special-scheme - */ -ada_really_inline constexpr bool is_special(std::string_view scheme); - -/** - * Returns the default port for a special scheme string. - * @param scheme The scheme string (e.g., "http", "https"). - * @return The default port number, or 0 if not a special scheme. - * @see https://url.spec.whatwg.org/#special-scheme - */ -constexpr uint16_t get_special_port(std::string_view scheme) noexcept; - -/** - * Returns the default port for a scheme type. - * @param type The scheme type enum value. - * @return The default port number, or 0 if not applicable. - * @see https://url.spec.whatwg.org/#special-scheme - */ -constexpr uint16_t get_special_port(ada::scheme::type type) noexcept; - -/** - * Converts a scheme string to its type enum. - * @param scheme The scheme string to convert. - * @return The corresponding scheme type, or NOT_SPECIAL if not recognized. - */ -constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept; - -} // namespace ada::scheme - -#endif // ADA_SCHEME_H -/* end file include/ada/scheme.h */ - -#include -#include - -namespace ada { - -/** - * @brief Enum representing the type of host in a URL. - * - * Used to distinguish between regular domain names, IPv4 addresses, - * and IPv6 addresses for proper parsing and serialization. - */ -enum url_host_type : uint8_t { - /** Regular domain name (e.g., "www.example.com") */ - DEFAULT = 0, - /** IPv4 address (e.g., "127.0.0.1") */ - IPV4 = 1, - /** IPv6 address (e.g., "[::1]" or "[2001:db8::1]") */ - IPV6 = 2, -}; - -/** - * @brief Abstract base class for URL representations. - * - * The `url_base` class provides the common interface and state shared by - * both `ada::url` and `ada::url_aggregator`. It contains basic URL attributes - * like validity status and scheme type, but delegates component storage and - * access to derived classes. - * - * @note This is an abstract class and cannot be instantiated directly. - * Use `ada::url` or `ada::url_aggregator` instead. - * - * @see url - * @see url_aggregator - */ -struct url_base { - virtual ~url_base() = default; - - /** - * Indicates whether the URL was successfully parsed. - * Set to `false` if parsing failed (e.g., invalid URL syntax). - */ - bool is_valid{true}; - - /** - * Indicates whether the URL has an opaque path (non-hierarchical). - * Opaque paths occur in non-special URLs like `mailto:` or `javascript:`. - */ - bool has_opaque_path{false}; - - /** - * The type of the URL's host (domain, IPv4, or IPv6). - */ - url_host_type host_type = url_host_type::DEFAULT; - - /** - * @private - * Internal representation of the URL's scheme type. - */ - ada::scheme::type type{ada::scheme::type::NOT_SPECIAL}; - - /** - * Checks if the URL has a special scheme (http, https, ws, wss, ftp, file). - * Special schemes have specific parsing rules and default ports. - * @return `true` if the scheme is special, `false` otherwise. - */ - [[nodiscard]] ada_really_inline constexpr bool is_special() const noexcept; - - /** - * Returns the URL's origin (scheme + host + port for special URLs). - * @return A newly allocated string containing the serialized origin. - * @see https://url.spec.whatwg.org/#concept-url-origin - */ - [[nodiscard]] virtual std::string get_origin() const = 0; - - /** - * Validates whether the hostname is a valid domain according to RFC 1034. - * Checks that the domain and its labels have valid lengths. - * @return `true` if the domain is valid, `false` otherwise. - */ - [[nodiscard]] virtual bool has_valid_domain() const noexcept = 0; - - /** - * @private - * Returns the default port for special schemes (e.g., 443 for https). - * Returns 0 for file:// URLs or non-special schemes. - */ - [[nodiscard]] inline uint16_t get_special_port() const noexcept; - - /** - * @private - * Returns the default port for the URL's scheme, or 0 if none. - */ - [[nodiscard]] ada_really_inline uint16_t scheme_default_port() const noexcept; - - /** - * @private - * Parses a port number from the input string. - * @param view The string containing the port to parse. - * @param check_trailing_content Whether to validate no trailing characters. - * @return Number of bytes consumed on success, 0 on failure. - */ - virtual size_t parse_port(std::string_view view, - bool check_trailing_content) = 0; - - /** @private */ - virtual ada_really_inline size_t parse_port(std::string_view view) { - return this->parse_port(view, false); - } - - /** - * Returns a JSON string representation of this URL for debugging. - * @return A JSON-formatted string with URL information. - */ - [[nodiscard]] virtual std::string to_string() const = 0; - - /** @private */ - virtual inline void clear_pathname() = 0; - - /** @private */ - virtual inline void clear_search() = 0; - - /** @private */ - [[nodiscard]] virtual inline bool has_hash() const noexcept = 0; - - /** @private */ - [[nodiscard]] virtual inline bool has_search() const noexcept = 0; - -}; // url_base - -} // namespace ada - -#endif -/* end file include/ada/url_base.h */ - -#include -#include -#include - -#if ADA_DEVELOPMENT_CHECKS -#include -#endif // ADA_DEVELOPMENT_CHECKS - -/** - * These functions are not part of our public API and may - * change at any time. - * - * @private - * @namespace ada::helpers - * @brief Includes the definitions for helper functions - */ -namespace ada::helpers { - -/** - * @private - */ -template -void encode_json(std::string_view view, out_iter out); - -/** - * @private - * This function is used to prune a fragment from a url, and returning the - * removed string if input has fragment. - * - * @details prune_hash seeks the first '#' and returns everything after it - * as a string_view, and modifies (in place) the input so that it points at - * everything before the '#'. If no '#' is found, the input is left unchanged - * and std::nullopt is returned. - * - * @attention The function is non-allocating and it does not throw. - * @returns Note that the returned string_view might be empty! - */ -ada_really_inline std::optional prune_hash( - std::string_view& input) noexcept; - -/** - * @private - * Defined by the URL specification, shorten a URLs paths. - * @see https://url.spec.whatwg.org/#shorten-a-urls-path - * @returns Returns true if path is shortened. - */ -ada_really_inline bool shorten_path(std::string& path, ada::scheme::type type); - -/** - * @private - * Defined by the URL specification, shorten a URLs paths. - * @see https://url.spec.whatwg.org/#shorten-a-urls-path - * @returns Returns true if path is shortened. - */ -ada_really_inline bool shorten_path(std::string_view& path, - ada::scheme::type type); - -/** - * @private - * - * Parse the path from the provided input and append to the existing - * (possibly empty) path. The input cannot contain tabs and spaces: it - * is the user's responsibility to check. - * - * The input is expected to be UTF-8. - * - * @see https://url.spec.whatwg.org/ - */ -ada_really_inline void parse_prepared_path(std::string_view input, - ada::scheme::type type, - std::string& path); - -/** - * @private - * Remove and mutate all ASCII tab or newline characters from an input. - */ -ada_really_inline void remove_ascii_tab_or_newline(std::string& input); - -/** - * @private - * Return the substring from input going from index pos to the end. - */ -ada_really_inline constexpr std::string_view substring(std::string_view input, - size_t pos); - -/** - * @private - * Returns true if the string_view points within the string. - */ -bool overlaps(std::string_view input1, const std::string& input2) noexcept; - -/** - * @private - * Return the substring from input going from index pos1 to the pos2 (non - * included). The length of the substring is pos2 - pos1. - */ -ada_really_inline constexpr std::string_view substring(std::string_view input, - size_t pos1, - size_t pos2) { -#if ADA_DEVELOPMENT_CHECKS - if (pos2 < pos1) { - std::cerr << "Negative-length substring: [" << pos1 << " to " << pos2 << ")" - << std::endl; - abort(); - } -#endif - return input.substr(pos1, pos2 - pos1); -} - -/** - * @private - * Modify the string_view so that it has the new size pos, assuming that pos <= - * input.size(). This function cannot throw. - */ -ada_really_inline void resize(std::string_view& input, size_t pos) noexcept; - -/** - * @private - * Returns a host's delimiter location depending on the state of the instance, - * and whether a colon was found outside brackets. Used by the host parser. - */ -ada_really_inline std::pair get_host_delimiter_location( - bool is_special, std::string_view& view) noexcept; - -/** - * @private - * Removes leading and trailing C0 control and whitespace characters from - * string. - */ -void trim_c0_whitespace(std::string_view& input) noexcept; - -/** - * @private - * @see - * https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path - */ -template -ada_really_inline void strip_trailing_spaces_from_opaque_path(url_type& url); - -/** - * @private - * Finds the delimiter of a view in authority state. - */ -ada_really_inline size_t -find_authority_delimiter_special(std::string_view view) noexcept; - -/** - * @private - * Finds the delimiter of a view in authority state. - */ -ada_really_inline size_t -find_authority_delimiter(std::string_view view) noexcept; - -/** - * @private - */ -template -inline void inner_concat(std::string& buffer, T t) { - buffer.append(t); -} - -/** - * @private - */ -template -inline void inner_concat(std::string& buffer, T t, Args... args) { - buffer.append(t); - return inner_concat(buffer, args...); -} - -/** - * @private - * Concatenate the arguments and return a string. - * @returns a string - */ -template -std::string concat(Args... args) { - std::string answer; - inner_concat(answer, args...); - return answer; -} - -/** - * @private - * @return Number of leading zeroes. - */ -inline int leading_zeroes(uint32_t input_num) noexcept { -#if ADA_REGULAR_VISUAL_STUDIO - unsigned long leading_zero(0); - unsigned long in(input_num); - return _BitScanReverse(&leading_zero, in) ? int(31 - leading_zero) : 32; -#else - return __builtin_clz(input_num); -#endif // ADA_REGULAR_VISUAL_STUDIO -} - -/** - * @private - * Counts the number of decimal digits necessary to represent x. - * faster than std::to_string(x).size(). - * @return digit count - */ -inline int fast_digit_count(uint32_t x) noexcept { - auto int_log2 = [](uint32_t z) -> int { - return 31 - ada::helpers::leading_zeroes(z | 1); - }; - // Compiles to very few instructions. Note that the - // table is static and thus effectively a constant. - // We leave it inside the function because it is meaningless - // outside of it (this comes at no performance cost). - const static uint64_t table[] = { - 4294967296, 8589934582, 8589934582, 8589934582, 12884901788, - 12884901788, 12884901788, 17179868184, 17179868184, 17179868184, - 21474826480, 21474826480, 21474826480, 21474826480, 25769703776, - 25769703776, 25769703776, 30063771072, 30063771072, 30063771072, - 34349738368, 34349738368, 34349738368, 34349738368, 38554705664, - 38554705664, 38554705664, 41949672960, 41949672960, 41949672960, - 42949672960, 42949672960}; - return int((x + table[int_log2(x)]) >> 32); -} -} // namespace ada::helpers - -#endif // ADA_HELPERS_H -/* end file include/ada/helpers.h */ -/* begin file include/ada/parser.h */ -/** - * @file parser.h - * @brief Low-level URL parsing functions. - * - * This header provides the internal URL parsing implementation. Most users - * should use `ada::parse()` from implementation.h instead of these functions - * directly. - * - * @see implementation.h for the recommended public API - */ -#ifndef ADA_PARSER_H -#define ADA_PARSER_H - -#include -#include - -/* begin file include/ada/expected.h */ -/** - * @file expected.h - * @brief Definitions for std::expected - * @private Excluded from docs through the doxygen file. - */ -/// -// expected - An implementation of std::expected with extensions -// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) -// -// Documentation available at http://tl.tartanllama.xyz/ -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to the -// public domain worldwide. This software is distributed without any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. If not, see -// . -/// - -#ifndef TL_EXPECTED_HPP -#define TL_EXPECTED_HPP - -#define TL_EXPECTED_VERSION_MAJOR 1 -#define TL_EXPECTED_VERSION_MINOR 1 -#define TL_EXPECTED_VERSION_PATCH 0 - -#include -#include -#include -#include - -#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) -#define TL_EXPECTED_EXCEPTIONS_ENABLED -#endif - -#if (defined(_MSC_VER) && _MSC_VER == 1900) -#define TL_EXPECTED_MSVC2015 -#define TL_EXPECTED_MSVC2015_CONSTEXPR -#else -#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr -#endif - -#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ - !defined(__clang__)) -#define TL_EXPECTED_GCC49 -#endif - -#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ - !defined(__clang__)) -#define TL_EXPECTED_GCC54 -#endif - -#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ - !defined(__clang__)) -#define TL_EXPECTED_GCC55 -#endif - -#if !defined(TL_ASSERT) -// can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug -#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49) -#include -#define TL_ASSERT(x) assert(x) -#else -#define TL_ASSERT(x) -#endif -#endif - -#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ - !defined(__clang__)) -// GCC < 5 doesn't support overloading on const&& for member functions - -#define TL_EXPECTED_NO_CONSTRR -// GCC < 5 doesn't support some standard C++11 type traits -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - std::has_trivial_copy_constructor -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::has_trivial_copy_assign - -// This one will be different for GCC 5.7 if it's ever supported -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ - std::is_trivially_destructible - -// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks -// std::vector for non-copyable types -#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) -#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX -#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX -namespace tl { -namespace detail { -template -struct is_trivially_copy_constructible - : std::is_trivially_copy_constructible {}; -#ifdef _GLIBCXX_VECTOR -template -struct is_trivially_copy_constructible> : std::false_type {}; -#endif -} // namespace detail -} // namespace tl -#endif - -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - tl::detail::is_trivially_copy_constructible -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::is_trivially_copy_assignable -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ - std::is_trivially_destructible -#else -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - std::is_trivially_copy_constructible -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::is_trivially_copy_assignable -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ - std::is_trivially_destructible -#endif - -#if __cplusplus > 201103L -#define TL_EXPECTED_CXX14 -#endif - -#ifdef TL_EXPECTED_GCC49 -#define TL_EXPECTED_GCC49_CONSTEXPR -#else -#define TL_EXPECTED_GCC49_CONSTEXPR constexpr -#endif - -#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ - defined(TL_EXPECTED_GCC49)) -#define TL_EXPECTED_11_CONSTEXPR -#else -#define TL_EXPECTED_11_CONSTEXPR constexpr -#endif - -namespace tl { -template -class expected; - -#ifndef TL_MONOSTATE_INPLACE_MUTEX -#define TL_MONOSTATE_INPLACE_MUTEX -class monostate {}; - -struct in_place_t { - explicit in_place_t() = default; -}; -static constexpr in_place_t in_place{}; -#endif - -template -class unexpected { - public: - static_assert(!std::is_same::value, "E must not be void"); - - unexpected() = delete; - constexpr explicit unexpected(const E& e) : m_val(e) {} - - constexpr explicit unexpected(E&& e) : m_val(std::move(e)) {} - - template ::value>::type* = nullptr> - constexpr explicit unexpected(Args&&... args) - : m_val(std::forward(args)...) {} - template < - class U, class... Args, - typename std::enable_if&, Args&&...>::value>::type* = nullptr> - constexpr explicit unexpected(std::initializer_list l, Args&&... args) - : m_val(l, std::forward(args)...) {} - - constexpr const E& value() const& { return m_val; } - TL_EXPECTED_11_CONSTEXPR E& value() & { return m_val; } - TL_EXPECTED_11_CONSTEXPR E&& value() && { return std::move(m_val); } - constexpr const E&& value() const&& { return std::move(m_val); } - - private: - E m_val; -}; - -#ifdef __cpp_deduction_guides -template -unexpected(E) -> unexpected; -#endif - -template -constexpr bool operator==(const unexpected& lhs, const unexpected& rhs) { - return lhs.value() == rhs.value(); -} -template -constexpr bool operator!=(const unexpected& lhs, const unexpected& rhs) { - return lhs.value() != rhs.value(); -} -template -constexpr bool operator<(const unexpected& lhs, const unexpected& rhs) { - return lhs.value() < rhs.value(); -} -template -constexpr bool operator<=(const unexpected& lhs, const unexpected& rhs) { - return lhs.value() <= rhs.value(); -} -template -constexpr bool operator>(const unexpected& lhs, const unexpected& rhs) { - return lhs.value() > rhs.value(); -} -template -constexpr bool operator>=(const unexpected& lhs, const unexpected& rhs) { - return lhs.value() >= rhs.value(); -} - -template -unexpected::type> make_unexpected(E&& e) { - return unexpected::type>(std::forward(e)); -} - -struct unexpect_t { - unexpect_t() = default; -}; -static constexpr unexpect_t unexpect{}; - -namespace detail { -template -[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E&& e) { -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - throw std::forward(e); -#else - (void)e; -#ifdef _MSC_VER - __assume(0); -#else - __builtin_unreachable(); -#endif -#endif -} - -#ifndef TL_TRAITS_MUTEX -#define TL_TRAITS_MUTEX -// C++14-style aliases for brevity -template -using remove_const_t = typename std::remove_const::type; -template -using remove_reference_t = typename std::remove_reference::type; -template -using decay_t = typename std::decay::type; -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; - -// std::conjunction from C++17 -template -struct conjunction : std::true_type {}; -template -struct conjunction : B {}; -template -struct conjunction - : std::conditional, B>::type {}; - -#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L -#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND -#endif - -// In C++11 mode, there's an issue in libc++'s std::mem_fn -// which results in a hard-error when using it in a noexcept expression -// in some cases. This is a check to workaround the common failing case. -#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND -template -struct is_pointer_to_non_const_member_func : std::false_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; - -template -struct is_const_or_const_ref : std::false_type {}; -template -struct is_const_or_const_ref : std::true_type {}; -template -struct is_const_or_const_ref : std::true_type {}; -#endif - -// std::invoke from C++17 -// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround -template < - typename Fn, typename... Args, -#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND - typename = enable_if_t::value && - is_const_or_const_ref::value)>, -#endif - typename = enable_if_t>::value>, int = 0> -constexpr auto invoke(Fn&& f, Args&&... args) noexcept( - noexcept(std::mem_fn(f)(std::forward(args)...))) - -> decltype(std::mem_fn(f)(std::forward(args)...)) { - return std::mem_fn(f)(std::forward(args)...); -} - -template >::value>> -constexpr auto invoke(Fn&& f, Args&&... args) noexcept( - noexcept(std::forward(f)(std::forward(args)...))) - -> decltype(std::forward(f)(std::forward(args)...)) { - return std::forward(f)(std::forward(args)...); -} - -// std::invoke_result from C++17 -template -struct invoke_result_impl; - -template -struct invoke_result_impl< - F, - decltype(detail::invoke(std::declval(), std::declval()...), void()), - Us...> { - using type = - decltype(detail::invoke(std::declval(), std::declval()...)); -}; - -template -using invoke_result = invoke_result_impl; - -template -using invoke_result_t = typename invoke_result::type; - -#if defined(_MSC_VER) && _MSC_VER <= 1900 -// TODO make a version which works with MSVC 2015 -template -struct is_swappable : std::true_type {}; - -template -struct is_nothrow_swappable : std::true_type {}; -#else -// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept -namespace swap_adl_tests { -// if swap ADL finds this then it would call std::swap otherwise (same -// signature) -struct tag {}; - -template -tag swap(T&, T&); -template -tag swap(T (&a)[N], T (&b)[N]); - -// helper functions to test if an unqualified swap is possible, and if it -// becomes std::swap -template -std::false_type can_swap(...) noexcept(false); -template (), std::declval()))> -std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), - std::declval()))); - -template -std::false_type uses_std(...); -template -std::is_same(), std::declval())), tag> -uses_std(int); - -template -struct is_std_swap_noexcept - : std::integral_constant::value && - std::is_nothrow_move_assignable::value> {}; - -template -struct is_std_swap_noexcept : is_std_swap_noexcept {}; - -template -struct is_adl_swap_noexcept - : std::integral_constant(0))> {}; -} // namespace swap_adl_tests - -template -struct is_swappable - : std::integral_constant< - bool, - decltype(detail::swap_adl_tests::can_swap(0))::value && - (!decltype(detail::swap_adl_tests::uses_std(0))::value || - (std::is_move_assignable::value && - std::is_move_constructible::value))> {}; - -template -struct is_swappable - : std::integral_constant< - bool, - decltype(detail::swap_adl_tests::can_swap(0))::value && - (!decltype(detail::swap_adl_tests::uses_std( - 0))::value || - is_swappable::value)> {}; - -template -struct is_nothrow_swappable - : std::integral_constant< - bool, - is_swappable::value && - ((decltype(detail::swap_adl_tests::uses_std(0))::value && - detail::swap_adl_tests::is_std_swap_noexcept::value) || - (!decltype(detail::swap_adl_tests::uses_std(0))::value && - detail::swap_adl_tests::is_adl_swap_noexcept::value))> {}; -#endif -#endif - -// Trait for checking if a type is a tl::expected -template -struct is_expected_impl : std::false_type {}; -template -struct is_expected_impl> : std::true_type {}; -template -using is_expected = is_expected_impl>; - -template -using expected_enable_forward_value = detail::enable_if_t< - std::is_constructible::value && - !std::is_same, in_place_t>::value && - !std::is_same, detail::decay_t>::value && - !std::is_same, detail::decay_t>::value>; - -template -using expected_enable_from_other = detail::enable_if_t< - std::is_constructible::value && - std::is_constructible::value && - !std::is_constructible&>::value && - !std::is_constructible&&>::value && - !std::is_constructible&>::value && - !std::is_constructible&&>::value && - !std::is_convertible&, T>::value && - !std::is_convertible&&, T>::value && - !std::is_convertible&, T>::value && - !std::is_convertible&&, T>::value>; - -template -using is_void_or = conditional_t::value, std::true_type, U>; - -template -using is_copy_constructible_or_void = - is_void_or>; - -template -using is_move_constructible_or_void = - is_void_or>; - -template -using is_copy_assignable_or_void = is_void_or>; - -template -using is_move_assignable_or_void = is_void_or>; - -} // namespace detail - -namespace detail { -struct no_init_t {}; -static constexpr no_init_t no_init{}; - -// Implements the storage of the values, and ensures that the destructor is -// trivial if it can be. -// -// This specialization is for where neither `T` or `E` is trivially -// destructible, so the destructors must be called on destruction of the -// `expected` -template ::value, - bool = std::is_trivially_destructible::value> -struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} - - template ::value>* = - nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args&&...>::value>* = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args&&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (m_has_val) { - m_val.~T(); - } else { - m_unexpect.~unexpected(); - } - } - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// This specialization is for when both `T` and `E` are trivially-destructible, -// so the destructor of the `expected` can be trivial. -template -struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} - - template ::value>* = - nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args&&...>::value>* = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args&&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() = default; - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// T is trivial, E is not. -template -struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) - : m_no_init(), m_has_val(false) {} - - template ::value>* = - nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args&&...>::value>* = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args&&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (!m_has_val) { - m_unexpect.~unexpected(); - } - } - - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// E is trivial, T is not. -template -struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} - - template ::value>* = - nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args&&...>::value>* = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args&&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (m_has_val) { - m_val.~T(); - } - } - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// `T` is `void`, `E` is trivially-destructible -template -struct expected_storage_base { -#if __GNUC__ <= 5 -// no constexpr for GCC 4/5 bug -#else - TL_EXPECTED_MSVC2015_CONSTEXPR -#endif - expected_storage_base() : m_has_val(true) {} - - constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} - - constexpr expected_storage_base(in_place_t) : m_has_val(true) {} - - template ::value>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args&&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() = default; - struct dummy {}; - union { - unexpected m_unexpect; - dummy m_val; - }; - bool m_has_val; -}; - -// `T` is `void`, `E` is not trivially-destructible -template -struct expected_storage_base { - constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} - - constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} - - template ::value>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args&&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (!m_has_val) { - m_unexpect.~unexpected(); - } - } - - union { - unexpected m_unexpect; - char m_dummy; - }; - bool m_has_val; -}; - -// This base class provides some handy member functions which can be used in -// further derived classes -template -struct expected_operations_base : expected_storage_base { - using expected_storage_base::expected_storage_base; - - template - void construct(Args&&... args) noexcept { - new (std::addressof(this->m_val)) T(std::forward(args)...); - this->m_has_val = true; - } - - template - // NOLINTNEXTLINE(bugprone-exception-escape) - void construct_with(Rhs&& rhs) noexcept { - new (std::addressof(this->m_val)) T(std::forward(rhs).get()); - this->m_has_val = true; - } - - template - void construct_error(Args&&... args) noexcept { - new (std::addressof(this->m_unexpect)) - unexpected(std::forward(args)...); - this->m_has_val = false; - } - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - - // These assign overloads ensure that the most efficient assignment - // implementation is used while maintaining the strong exception guarantee. - // The problematic case is where rhs has a value, but *this does not. - // - // This overload handles the case where we can just copy-construct `T` - // directly into place without throwing. - template ::value>* = - nullptr> - void assign(const expected_operations_base& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(rhs.get()); - } else { - assign_common(rhs); - } - } - - // This overload handles the case where we can attempt to create a copy of - // `T`, then no-throw move it into place if the copy was successful. - template ::value && - std::is_nothrow_move_constructible::value>* = - nullptr> - void assign(const expected_operations_base& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - T tmp = rhs.get(); - geterr().~unexpected(); - construct(std::move(tmp)); - } else { - assign_common(rhs); - } - } - - // This overload is the worst-case, where we have to move-construct the - // unexpected value into temporary storage, then try to copy the T into place. - // If the construction succeeds, then everything is fine, but if it throws, - // then we move the old unexpected value back into place before rethrowing the - // exception. - template ::value && - !std::is_nothrow_move_constructible::value>* = nullptr> - void assign(const expected_operations_base& rhs) { - if (!this->m_has_val && rhs.m_has_val) { - auto tmp = std::move(geterr()); - geterr().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - construct(rhs.get()); - } catch (...) { - geterr() = std::move(tmp); - throw; - } -#else - construct(rhs.get()); -#endif - } else { - assign_common(rhs); - } - } - - // These overloads do the same as above, but for rvalues - template ::value>* = - nullptr> - void assign(expected_operations_base&& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(std::move(rhs).get()); - } else { - assign_common(std::move(rhs)); - } - } - - template ::value>* = nullptr> - void assign(expected_operations_base&& rhs) { - if (!this->m_has_val && rhs.m_has_val) { - auto tmp = std::move(geterr()); - geterr().~unexpected(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - construct(std::move(rhs).get()); - } catch (...) { - geterr() = std::move(tmp); - throw; - } -#else - construct(std::move(rhs).get()); -#endif - } else { - assign_common(std::move(rhs)); - } - } - -#else - - // If exceptions are disabled then we can just copy-construct - void assign(const expected_operations_base& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(rhs.get()); - } else { - assign_common(rhs); - } - } - - void assign(expected_operations_base&& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(std::move(rhs).get()); - } else { - assign_common(std::move(rhs)); - } - } - -#endif - - // The common part of move/copy assigning - template - void assign_common(Rhs&& rhs) { - if (this->m_has_val) { - if (rhs.m_has_val) { - get() = std::forward(rhs).get(); - } else { - destroy_val(); - construct_error(std::forward(rhs).geterr()); - } - } else { - if (!rhs.m_has_val) { - geterr() = std::forward(rhs).geterr(); - } - } - } - - bool has_value() const { return this->m_has_val; } - - TL_EXPECTED_11_CONSTEXPR T& get() & { return this->m_val; } - constexpr const T& get() const& { return this->m_val; } - TL_EXPECTED_11_CONSTEXPR T&& get() && { return std::move(this->m_val); } -#ifndef TL_EXPECTED_NO_CONSTRR - constexpr const T&& get() const&& { return std::move(this->m_val); } -#endif - - TL_EXPECTED_11_CONSTEXPR unexpected& geterr() & { - return this->m_unexpect; - } - constexpr const unexpected& geterr() const& { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected&& geterr() && { - return std::move(this->m_unexpect); - } -#ifndef TL_EXPECTED_NO_CONSTRR - constexpr const unexpected&& geterr() const&& { - return std::move(this->m_unexpect); - } -#endif - - TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } -}; - -// This base class provides some handy member functions which can be used in -// further derived classes -template -struct expected_operations_base : expected_storage_base { - using expected_storage_base::expected_storage_base; - - template - void construct() noexcept { - this->m_has_val = true; - } - - // This function doesn't use its argument, but needs it so that code in - // levels above this can work independently of whether T is void - template - void construct_with(Rhs&&) noexcept { - this->m_has_val = true; - } - - template - void construct_error(Args&&... args) noexcept { - new (std::addressof(this->m_unexpect)) - unexpected(std::forward(args)...); - this->m_has_val = false; - } - - template - void assign(Rhs&& rhs) noexcept { - if (!this->m_has_val) { - if (rhs.m_has_val) { - geterr().~unexpected(); - construct(); - } else { - geterr() = std::forward(rhs).geterr(); - } - } else { - if (!rhs.m_has_val) { - construct_error(std::forward(rhs).geterr()); - } - } - } - - bool has_value() const { return this->m_has_val; } - - TL_EXPECTED_11_CONSTEXPR unexpected& geterr() & { - return this->m_unexpect; - } - constexpr const unexpected& geterr() const& { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected&& geterr() && { - return std::move(this->m_unexpect); - } -#ifndef TL_EXPECTED_NO_CONSTRR - constexpr const unexpected&& geterr() const&& { - return std::move(this->m_unexpect); - } -#endif - - TL_EXPECTED_11_CONSTEXPR void destroy_val() { - // no-op - } -}; - -// This class manages conditionally having a trivial copy constructor -// This specialization is for when T and E are trivially copy constructible -template ::value && - TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> -struct expected_copy_base : expected_operations_base { - using expected_operations_base::expected_operations_base; -}; - -// This specialization is for when T or E are not trivially copy constructible -template -struct expected_copy_base : expected_operations_base { - using expected_operations_base::expected_operations_base; - - expected_copy_base() = default; - expected_copy_base(const expected_copy_base& rhs) - : expected_operations_base(no_init) { - if (rhs.has_value()) { - this->construct_with(rhs); - } else { - this->construct_error(rhs.geterr()); - } - } - - expected_copy_base(expected_copy_base&& rhs) = default; - expected_copy_base& operator=(const expected_copy_base& rhs) = default; - expected_copy_base& operator=(expected_copy_base&& rhs) = default; -}; - -// This class manages conditionally having a trivial move constructor -// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it -// doesn't implement an analogue to std::is_trivially_move_constructible. We -// have to make do with a non-trivial move constructor even if T is trivially -// move constructible -#ifndef TL_EXPECTED_GCC49 -template >::value && - std::is_trivially_move_constructible::value> -struct expected_move_base : expected_copy_base { - using expected_copy_base::expected_copy_base; -}; -#else -template -struct expected_move_base; -#endif -template -struct expected_move_base : expected_copy_base { - using expected_copy_base::expected_copy_base; - - expected_move_base() = default; - expected_move_base(const expected_move_base& rhs) = default; - - expected_move_base(expected_move_base&& rhs) noexcept( - std::is_nothrow_move_constructible::value) - : expected_copy_base(no_init) { - if (rhs.has_value()) { - this->construct_with(std::move(rhs)); - } else { - this->construct_error(std::move(rhs.geterr())); - } - } - expected_move_base& operator=(const expected_move_base& rhs) = default; - expected_move_base& operator=(expected_move_base&& rhs) = default; -}; - -// This class manages conditionally having a trivial copy assignment operator -template < - class T, class E, - bool = - is_void_or< - T, conjunction>::value && - TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value && - TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value && - TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> -struct expected_copy_assign_base : expected_move_base { - using expected_move_base::expected_move_base; -}; - -template -struct expected_copy_assign_base : expected_move_base { - using expected_move_base::expected_move_base; - - expected_copy_assign_base() = default; - expected_copy_assign_base(const expected_copy_assign_base& rhs) = default; - - expected_copy_assign_base(expected_copy_assign_base&& rhs) = default; - expected_copy_assign_base& operator=(const expected_copy_assign_base& rhs) { - this->assign(rhs); - return *this; - } - expected_copy_assign_base& operator=(expected_copy_assign_base&& rhs) = - default; -}; - -// This class manages conditionally having a trivial move assignment operator -// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it -// doesn't implement an analogue to std::is_trivially_move_assignable. We have -// to make do with a non-trivial move assignment operator even if T is trivially -// move assignable -#ifndef TL_EXPECTED_GCC49 -template < - class T, class E, - bool = is_void_or< - T, conjunction, - std::is_trivially_move_constructible, - std::is_trivially_move_assignable>>::value && - std::is_trivially_destructible::value && - std::is_trivially_move_constructible::value && - std::is_trivially_move_assignable::value> -struct expected_move_assign_base : expected_copy_assign_base { - using expected_copy_assign_base::expected_copy_assign_base; -}; -#else -template -struct expected_move_assign_base; -#endif - -template -struct expected_move_assign_base - : expected_copy_assign_base { - using expected_copy_assign_base::expected_copy_assign_base; - - expected_move_assign_base() = default; - expected_move_assign_base(const expected_move_assign_base& rhs) = default; - - expected_move_assign_base(expected_move_assign_base&& rhs) = default; - - expected_move_assign_base& operator=(const expected_move_assign_base& rhs) = - default; - - expected_move_assign_base& - operator=(expected_move_assign_base&& rhs) noexcept( - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value) { - this->assign(std::move(rhs)); - return *this; - } -}; - -// expected_delete_ctor_base will conditionally delete copy and move -// constructors depending on whether T is copy/move constructible -template ::value && - std::is_copy_constructible::value), - bool EnableMove = (is_move_constructible_or_void::value && - std::is_move_constructible::value)> -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = - default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = - default; -}; - -template -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = - default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = - default; -}; - -template -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = - default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = - default; -}; - -template -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = - default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = - default; -}; - -// expected_delete_assign_base will conditionally delete copy and move -// constructors depending on whether T and E are copy/move constructible + -// assignable -template ::value && - std::is_copy_constructible::value && - is_copy_assignable_or_void::value && - std::is_copy_assignable::value), - bool EnableMove = (is_move_constructible_or_void::value && - std::is_move_constructible::value && - is_move_assignable_or_void::value && - std::is_move_assignable::value)> -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = - default; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = default; -}; - -template -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = - default; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = delete; -}; - -template -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = - delete; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = default; -}; - -template -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = - delete; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = delete; -}; - -// This is needed to be able to construct the expected_default_ctor_base which -// follows, while still conditionally deleting the default constructor. -struct default_constructor_tag { - explicit constexpr default_constructor_tag() = default; -}; - -// expected_default_ctor_base will ensure that expected has a deleted default -// consturctor if T is not default constructible. -// This specialization is for when T is default constructible -template ::value || std::is_void::value> -struct expected_default_ctor_base { - constexpr expected_default_ctor_base() noexcept = default; - constexpr expected_default_ctor_base( - expected_default_ctor_base const&) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = - default; - expected_default_ctor_base& operator=( - expected_default_ctor_base const&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = - default; - - constexpr explicit expected_default_ctor_base(default_constructor_tag) {} -}; - -// This specialization is for when T is not default constructible -template -struct expected_default_ctor_base { - constexpr expected_default_ctor_base() noexcept = delete; - constexpr expected_default_ctor_base( - expected_default_ctor_base const&) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = - default; - expected_default_ctor_base& operator=( - expected_default_ctor_base const&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = - default; - - constexpr explicit expected_default_ctor_base(default_constructor_tag) {} -}; -} // namespace detail - -template -class bad_expected_access : public std::exception { - public: - explicit bad_expected_access(E e) : m_val(std::move(e)) {} - - virtual const char* what() const noexcept override { - return "Bad expected access"; - } - - const E& error() const& { return m_val; } - E& error() & { return m_val; } - const E&& error() const&& { return std::move(m_val); } - E&& error() && { return std::move(m_val); } - - private: - E m_val; -}; - -/// An `expected` object is an object that contains the storage for -/// another object and manages the lifetime of this contained object `T`. -/// Alternatively it could contain the storage for another unexpected object -/// `E`. The contained object may not be initialized after the expected object -/// has been initialized, and may not be destroyed before the expected object -/// has been destroyed. The initialization state of the contained object is -/// tracked by the expected object. -template -class expected : private detail::expected_move_assign_base, - private detail::expected_delete_ctor_base, - private detail::expected_delete_assign_base, - private detail::expected_default_ctor_base { - static_assert(!std::is_reference::value, "T must not be a reference"); - static_assert(!std::is_same::type>::value, - "T must not be in_place_t"); - static_assert(!std::is_same::type>::value, - "T must not be unexpect_t"); - static_assert( - !std::is_same>::type>::value, - "T must not be unexpected"); - static_assert(!std::is_reference::value, "E must not be a reference"); - - T* valptr() { return std::addressof(this->m_val); } - const T* valptr() const { return std::addressof(this->m_val); } - unexpected* errptr() { return std::addressof(this->m_unexpect); } - const unexpected* errptr() const { - return std::addressof(this->m_unexpect); - } - - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U& val() { - return this->m_val; - } - TL_EXPECTED_11_CONSTEXPR unexpected& err() { return this->m_unexpect; } - - template ::value>* = nullptr> - constexpr const U& val() const { - return this->m_val; - } - constexpr const unexpected& err() const { return this->m_unexpect; } - - using impl_base = detail::expected_move_assign_base; - using ctor_base = detail::expected_default_ctor_base; - - public: - typedef T value_type; - typedef E error_type; - typedef unexpected unexpected_type; - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & { - return and_then_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && { - return and_then_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto and_then(F&& f) const& { - return and_then_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto and_then(F&& f) const&& { - return and_then_impl(std::move(*this), std::forward(f)); - } -#endif - -#else - template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & -> decltype(and_then_impl( - std::declval(), std::forward(f))) { - return and_then_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && -> decltype(and_then_impl( - std::declval(), std::forward(f))) { - return and_then_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto and_then(F&& f) const& -> decltype(and_then_impl( - std::declval(), std::forward(f))) { - return and_then_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto and_then(F&& f) const&& -> decltype(and_then_impl( - std::declval(), std::forward(f))) { - return and_then_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template - TL_EXPECTED_11_CONSTEXPR auto map(F&& f) & { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto map(F&& f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto map(F&& f) const& { - return expected_map_impl(*this, std::forward(f)); - } - template - constexpr auto map(F&& f) const&& { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( - std::declval(), std::declval())) map(F&& f) & { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - map(F&& f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F&& f) const& { - return expected_map_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) map(F&& f) - const&& { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template - TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) & { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto transform(F&& f) const& { - return expected_map_impl(*this, std::forward(f)); - } - template - constexpr auto transform(F&& f) const&& { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( - std::declval(), std::declval())) transform(F&& f) & { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F&& f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F&& f) const& { - return expected_map_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) transform(F&& f) - const&& { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template - TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) & { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto map_error(F&& f) const& { - return map_error_impl(*this, std::forward(f)); - } - template - constexpr auto map_error(F&& f) const&& { - return map_error_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl( - std::declval(), std::declval())) map_error(F&& f) & { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F&& f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F&& f) const& { - return map_error_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) map_error(F&& f) - const&& { - return map_error_impl(std::move(*this), std::forward(f)); - } -#endif -#endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template - TL_EXPECTED_11_CONSTEXPR auto transform_error(F&& f) & { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto transform_error(F&& f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto transform_error(F&& f) const& { - return map_error_impl(*this, std::forward(f)); - } - template - constexpr auto transform_error(F&& f) const&& { - return map_error_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl( - std::declval(), - std::declval())) transform_error(F&& f) & { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F&& f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F&& f) const& { - return map_error_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) transform_error(F&& f) - const&& { - return map_error_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - template - expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) & { - return or_else_impl(*this, std::forward(f)); - } - - template - expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) && { - return or_else_impl(std::move(*this), std::forward(f)); - } - - template - expected constexpr or_else(F&& f) const& { - return or_else_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - expected constexpr or_else(F&& f) const&& { - return or_else_impl(std::move(*this), std::forward(f)); - } -#endif - constexpr expected() = default; - constexpr expected(const expected& rhs) = default; - constexpr expected(expected&& rhs) = default; - expected& operator=(const expected& rhs) = default; - expected& operator=(expected&& rhs) = default; - - template ::value>* = - nullptr> - constexpr expected(in_place_t, Args&&... args) - : impl_base(in_place, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template &, Args&&...>::value>* = nullptr> - constexpr expected(in_place_t, std::initializer_list il, Args&&... args) - : impl_base(in_place, il, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - explicit constexpr expected(const unexpected& e) - : impl_base(unexpect, e.value()), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - constexpr expected(unexpected const& e) - : impl_base(unexpect, e.value()), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - explicit constexpr expected(unexpected&& e) noexcept( - std::is_nothrow_constructible::value) - : impl_base(unexpect, std::move(e.value())), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - constexpr expected(unexpected&& e) noexcept( - std::is_nothrow_constructible::value) - : impl_base(unexpect, std::move(e.value())), - ctor_base(detail::default_constructor_tag{}) {} - - template ::value>* = - nullptr> - constexpr explicit expected(unexpect_t, Args&&... args) - : impl_base(unexpect, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template &, Args&&...>::value>* = nullptr> - constexpr explicit expected(unexpect_t, std::initializer_list il, - Args&&... args) - : impl_base(unexpect, il, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template ::value && - std::is_convertible::value)>* = - nullptr, - detail::expected_enable_from_other* = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(const expected& rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } - - template < - class U, class G, - detail::enable_if_t<(std::is_convertible::value && - std::is_convertible::value)>* = nullptr, - detail::expected_enable_from_other* = - nullptr> - TL_EXPECTED_11_CONSTEXPR expected(const expected& rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } - - template < - class U, class G, - detail::enable_if_t::value && - std::is_convertible::value)>* = nullptr, - detail::expected_enable_from_other* = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(expected&& rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } - - template < - class U, class G, - detail::enable_if_t<(std::is_convertible::value && - std::is_convertible::value)>* = nullptr, - detail::expected_enable_from_other* = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(expected&& rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } - - template ::value>* = nullptr, - detail::expected_enable_forward_value* = nullptr> - explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v) - : expected(in_place, std::forward(v)) {} - - template ::value>* = nullptr, - detail::expected_enable_forward_value* = nullptr> - TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v) - : expected(in_place, std::forward(v)) {} - - template < - class U = T, class G = T, - detail::enable_if_t::value>* = - nullptr, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t< - (!std::is_same, detail::decay_t>::value && - !detail::conjunction, - std::is_same>>::value && - std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)>* = nullptr> - expected& operator=(U&& v) { - if (has_value()) { - val() = std::forward(v); - } else { - err().~unexpected(); - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - } - - return *this; - } - - template < - class U = T, class G = T, - detail::enable_if_t::value>* = - nullptr, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t< - (!std::is_same, detail::decay_t>::value && - !detail::conjunction, - std::is_same>>::value && - std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)>* = nullptr> - expected& operator=(U&& v) { - if (has_value()) { - val() = std::forward(v); - } else { - auto tmp = std::move(err()); - err().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } -#else - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; -#endif - } - - return *this; - } - - template ::value && - std::is_assignable::value>* = nullptr> - expected& operator=(const unexpected& rhs) { - if (!has_value()) { - err() = rhs; - } else { - this->destroy_val(); - ::new (errptr()) unexpected(rhs); - this->m_has_val = false; - } - - return *this; - } - - template ::value && - std::is_move_assignable::value>* = nullptr> - expected& operator=(unexpected&& rhs) noexcept { - if (!has_value()) { - err() = std::move(rhs); - } else { - this->destroy_val(); - ::new (errptr()) unexpected(std::move(rhs)); - this->m_has_val = false; - } - - return *this; - } - - template ::value>* = nullptr> - void emplace(Args&&... args) { - if (has_value()) { - val().~T(); - } else { - err().~unexpected(); - this->m_has_val = true; - } - ::new (valptr()) T(std::forward(args)...); - } - - template ::value>* = nullptr> - void emplace(Args&&... args) { - if (has_value()) { - val().~T(); - ::new (valptr()) T(std::forward(args)...); - } else { - auto tmp = std::move(err()); - err().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } -#else - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; -#endif - } - } - - template &, Args&&...>::value>* = nullptr> - void emplace(std::initializer_list il, Args&&... args) { - if (has_value()) { - T t(il, std::forward(args)...); - val() = std::move(t); - } else { - err().~unexpected(); - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - } - } - - template &, Args&&...>::value>* = nullptr> - void emplace(std::initializer_list il, Args&&... args) { - if (has_value()) { - T t(il, std::forward(args)...); - val() = std::move(t); - } else { - auto tmp = std::move(err()); - err().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } -#else - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; -#endif - } - } - - private: - using t_is_void = std::true_type; - using t_is_not_void = std::false_type; - using t_is_nothrow_move_constructible = std::true_type; - using move_constructing_t_can_throw = std::false_type; - using e_is_nothrow_move_constructible = std::true_type; - using move_constructing_e_can_throw = std::false_type; - - void swap_where_both_have_value(expected& /*rhs*/, t_is_void) noexcept { - // swapping void is a no-op - } - - void swap_where_both_have_value(expected& rhs, t_is_not_void) { - using std::swap; - swap(val(), rhs.val()); - } - - void swap_where_only_one_has_value(expected& rhs, t_is_void) noexcept( - std::is_nothrow_move_constructible::value) { - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - std::swap(this->m_has_val, rhs.m_has_val); - } - - void swap_where_only_one_has_value(expected& rhs, t_is_not_void) { - swap_where_only_one_has_value_and_t_is_not_void( - rhs, typename std::is_nothrow_move_constructible::type{}, - typename std::is_nothrow_move_constructible::type{}); - } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected& rhs, t_is_nothrow_move_constructible, - e_is_nothrow_move_constructible) noexcept { - auto temp = std::move(val()); - val().~T(); - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected& rhs, t_is_nothrow_move_constructible, - move_constructing_e_can_throw) { - auto temp = std::move(val()); - val().~T(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } catch (...) { - val() = std::move(temp); - throw; - } -#else - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); -#endif - } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected& rhs, move_constructing_t_can_throw, - e_is_nothrow_move_constructible) { - auto temp = std::move(rhs.err()); - rhs.err().~unexpected_type(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (rhs.valptr()) T(std::move(val())); - val().~T(); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } catch (...) { - rhs.err() = std::move(temp); - throw; - } -#else - ::new (rhs.valptr()) T(std::move(val())); - val().~T(); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); -#endif - } - - public: - template - detail::enable_if_t::value && - detail::is_swappable::value && - (std::is_nothrow_move_constructible::value || - std::is_nothrow_move_constructible::value)> - swap(expected& rhs) noexcept(std::is_nothrow_move_constructible::value && - detail::is_nothrow_swappable::value && - std::is_nothrow_move_constructible::value && - detail::is_nothrow_swappable::value) { - if (has_value() && rhs.has_value()) { - swap_where_both_have_value(rhs, typename std::is_void::type{}); - } else if (!has_value() && rhs.has_value()) { - rhs.swap(*this); - } else if (has_value()) { - swap_where_only_one_has_value(rhs, typename std::is_void::type{}); - } else { - using std::swap; - swap(err(), rhs.err()); - } - } - - constexpr const T* operator->() const { - TL_ASSERT(has_value()); - return valptr(); - } - TL_EXPECTED_11_CONSTEXPR T* operator->() { - TL_ASSERT(has_value()); - return valptr(); - } - - template ::value>* = nullptr> - constexpr const U& operator*() const& { - TL_ASSERT(has_value()); - return val(); - } - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U& operator*() & { - TL_ASSERT(has_value()); - return val(); - } - template ::value>* = nullptr> - constexpr const U&& operator*() const&& { - TL_ASSERT(has_value()); - return std::move(val()); - } - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U&& operator*() && { - TL_ASSERT(has_value()); - return std::move(val()); - } - - constexpr bool has_value() const noexcept { return this->m_has_val; } - constexpr explicit operator bool() const noexcept { return this->m_has_val; } - - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR const U& value() const& { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); - } - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U& value() & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); - } - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR const U&& value() const&& { - if (!has_value()) - detail::throw_exception(bad_expected_access(std::move(err()).value())); - return std::move(val()); - } - template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U&& value() && { - if (!has_value()) - detail::throw_exception(bad_expected_access(std::move(err()).value())); - return std::move(val()); - } - - constexpr const E& error() const& { - TL_ASSERT(!has_value()); - return err().value(); - } - TL_EXPECTED_11_CONSTEXPR E& error() & { - TL_ASSERT(!has_value()); - return err().value(); - } - constexpr const E&& error() const&& { - TL_ASSERT(!has_value()); - return std::move(err().value()); - } - TL_EXPECTED_11_CONSTEXPR E&& error() && { - TL_ASSERT(!has_value()); - return std::move(err().value()); - } - - template - constexpr T value_or(U&& v) const& { - static_assert(std::is_copy_constructible::value && - std::is_convertible::value, - "T must be copy-constructible and convertible to from U&&"); - return bool(*this) ? **this : static_cast(std::forward(v)); - } - template - TL_EXPECTED_11_CONSTEXPR T value_or(U&& v) && { - static_assert(std::is_move_constructible::value && - std::is_convertible::value, - "T must be move-constructible and convertible to from U&&"); - return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); - } -}; - -namespace detail { -template -using exp_t = typename detail::decay_t::value_type; -template -using err_t = typename detail::decay_t::error_type; -template -using ret_t = expected>; - -#ifdef TL_EXPECTED_CXX14 -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval()))> -constexpr auto and_then_impl(Exp&& exp, F&& f) { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval()))> -constexpr auto and_then_impl(Exp&& exp, F&& f) { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#else -template -struct TC; -template (), - *std::declval())), - detail::enable_if_t>::value>* = nullptr> -auto and_then_impl(Exp&& exp, F&& f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} - -template ())), - detail::enable_if_t>::value>* = nullptr> -constexpr auto and_then_impl(Exp&& exp, F&& f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#endif - -#ifdef TL_EXPECTED_CXX14 -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value>* = nullptr> -constexpr auto expected_map_impl(Exp&& exp, F&& f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value>* = nullptr> -auto expected_map_impl(Exp&& exp, F&& f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return result(); - } - - return result(unexpect, std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> -constexpr auto expected_map_impl(Exp&& exp, F&& f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> -auto expected_map_impl(Exp&& exp, F&& f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return result(); - } - - return result(unexpect, std::forward(exp).error()); -} -#else -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value>* = nullptr> - -constexpr auto expected_map_impl(Exp&& exp, F&& f) - -> ret_t> { - using result = ret_t>; - - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value>* = nullptr> - -auto expected_map_impl(Exp&& exp, F&& f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return {}; - } - - return unexpected>(std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> - -constexpr auto expected_map_impl(Exp&& exp, F&& f) - -> ret_t> { - using result = ret_t>; - - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> - -auto expected_map_impl(Exp&& exp, F&& f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return {}; - } - - return unexpected>(std::forward(exp).error()); -} -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) { - using result = expected, detail::decay_t>; - return exp.has_value() - ? result(*std::forward(exp)) - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) { - using result = expected, monostate>; - if (exp.has_value()) { - return result(*std::forward(exp)); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) { - using result = expected, detail::decay_t>; - return exp.has_value() - ? result() - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) { - using result = expected, monostate>; - if (exp.has_value()) { - return result(); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -#else -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) - -> expected, detail::decay_t> { - using result = expected, detail::decay_t>; - - return exp.has_value() - ? result(*std::forward(exp)) - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> { - using result = expected, monostate>; - if (exp.has_value()) { - return result(*std::forward(exp)); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) - -> expected, detail::decay_t> { - using result = expected, detail::decay_t>; - - return exp.has_value() - ? result() - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} - -template >::value>* = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> { - using result = expected, monostate>; - if (exp.has_value()) { - return result(); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -#endif - -#ifdef TL_EXPECTED_CXX14 -template (), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto or_else_impl(Exp&& exp, F&& f) { - static_assert(detail::is_expected::value, "F must return an expected"); - return exp.has_value() ? std::forward(exp) - : detail::invoke(std::forward(f), - std::forward(exp).error()); -} - -template (), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -detail::decay_t or_else_impl(Exp&& exp, F&& f) { - return exp.has_value() ? std::forward(exp) - : (detail::invoke(std::forward(f), - std::forward(exp).error()), - std::forward(exp)); -} -#else -template (), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto or_else_impl(Exp&& exp, F&& f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - return exp.has_value() ? std::forward(exp) - : detail::invoke(std::forward(f), - std::forward(exp).error()); -} - -template (), - std::declval().error())), - detail::enable_if_t::value>* = nullptr> -detail::decay_t or_else_impl(Exp&& exp, F&& f) { - return exp.has_value() ? std::forward(exp) - : (detail::invoke(std::forward(f), - std::forward(exp).error()), - std::forward(exp)); -} -#endif -} // namespace detail - -template -constexpr bool operator==(const expected& lhs, - const expected& rhs) { - return (lhs.has_value() != rhs.has_value()) - ? false - : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); -} -template -constexpr bool operator!=(const expected& lhs, - const expected& rhs) { - return (lhs.has_value() != rhs.has_value()) - ? true - : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); -} -template -constexpr bool operator==(const expected& lhs, - const expected& rhs) { - return (lhs.has_value() != rhs.has_value()) - ? false - : (!lhs.has_value() ? lhs.error() == rhs.error() : true); -} -template -constexpr bool operator!=(const expected& lhs, - const expected& rhs) { - return (lhs.has_value() != rhs.has_value()) - ? true - : (!lhs.has_value() ? lhs.error() == rhs.error() : false); -} - -template -constexpr bool operator==(const expected& x, const U& v) { - return x.has_value() ? *x == v : false; -} -template -constexpr bool operator==(const U& v, const expected& x) { - return x.has_value() ? *x == v : false; -} -template -constexpr bool operator!=(const expected& x, const U& v) { - return x.has_value() ? *x != v : true; -} -template -constexpr bool operator!=(const U& v, const expected& x) { - return x.has_value() ? *x != v : true; -} - -template -constexpr bool operator==(const expected& x, const unexpected& e) { - return x.has_value() ? false : x.error() == e.value(); -} -template -constexpr bool operator==(const unexpected& e, const expected& x) { - return x.has_value() ? false : x.error() == e.value(); -} -template -constexpr bool operator!=(const expected& x, const unexpected& e) { - return x.has_value() ? true : x.error() != e.value(); -} -template -constexpr bool operator!=(const unexpected& e, const expected& x) { - return x.has_value() ? true : x.error() != e.value(); -} - -template ::value || - std::is_move_constructible::value) && - detail::is_swappable::value && - std::is_move_constructible::value && - detail::is_swappable::value>* = nullptr> -void swap(expected& lhs, - expected& rhs) noexcept(noexcept(lhs.swap(rhs))) { - lhs.swap(rhs); -} -} // namespace tl - -#endif -/* end file include/ada/expected.h */ - -/* begin file include/ada/url_pattern_regex.h */ -/** - * @file url_search_params.h - * @brief Declaration for the URL Search Params - */ -#ifndef ADA_URL_PATTERN_REGEX_H -#define ADA_URL_PATTERN_REGEX_H - -#include -#include - -#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER -#include -#endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER - -#if ADA_INCLUDE_URL_PATTERN -namespace ada::url_pattern_regex { - -template -concept regex_concept = requires(T t, std::string_view pattern, - bool ignore_case, std::string_view input) { - // Ensure the class has a type alias 'regex_type' - typename T::regex_type; - - // Function to create a regex instance - { - T::create_instance(pattern, ignore_case) - } -> std::same_as>; - - // Function to perform regex search - { - T::regex_search(input, std::declval()) - } -> std::same_as>>>; - - // Function to match regex pattern - { - T::regex_match(input, std::declval()) - } -> std::same_as; - - // Copy constructor - { T(std::declval()) } -> std::same_as; - - // Move constructor - { T(std::declval()) } -> std::same_as; -}; - -#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER -class std_regex_provider final { - public: - std_regex_provider() = default; - using regex_type = std::regex; - static std::optional create_instance(std::string_view pattern, - bool ignore_case); - static std::optional>> regex_search( - std::string_view input, const regex_type& pattern); - static bool regex_match(std::string_view input, const regex_type& pattern); -}; -#endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER - -} // namespace ada::url_pattern_regex -#endif // ADA_INCLUDE_URL_PATTERN -#endif // ADA_URL_PATTERN_REGEX_H -/* end file include/ada/url_pattern_regex.h */ -/* begin file include/ada/url_pattern_init.h */ -/** - * @file url_pattern_init.h - * @brief Declaration for the url_pattern_init implementation. - */ -#ifndef ADA_URL_PATTERN_INIT_H -#define ADA_URL_PATTERN_INIT_H - -/* begin file include/ada/errors.h */ -/** - * @file errors.h - * @brief Error type definitions for URL parsing. - * - * Defines the error codes that can be returned when URL parsing fails. - */ -#ifndef ADA_ERRORS_H -#define ADA_ERRORS_H - -#include -namespace ada { -/** - * @brief Error codes for URL parsing operations. - * - * Used with `tl::expected` to indicate why a URL parsing operation failed. - */ -enum class errors : uint8_t { - type_error /**< A type error occurred (e.g., invalid URL syntax). */ -}; -} // namespace ada -#endif // ADA_ERRORS_H -/* end file include/ada/errors.h */ - -#include -#include -#include -#include - -#if ADA_TESTING -#include -#endif // ADA_TESTING - -#if ADA_INCLUDE_URL_PATTERN -namespace ada { - -// Important: C++20 allows us to use concept rather than `using` or `typedef -// and allows functions with second argument, which is optional (using either -// std::nullopt or a parameter with default value) -template -concept url_pattern_encoding_callback = requires(F f, std::string_view sv) { - { f(sv) } -> std::same_as>; -}; - -// A structure providing matching patterns for individual components -// of a URL. When a URLPattern is created, or when a URLPattern is -// used to match or test against a URL, the input can be given as -// either a string or a URLPatternInit struct. If a string is given, -// it will be parsed to create a URLPatternInit. The URLPatternInit -// API is defined as part of the URLPattern specification. -// All provided strings must be valid UTF-8. -struct url_pattern_init { - enum class process_type : uint8_t { - url, - pattern, - }; - - friend std::ostream& operator<<(std::ostream& os, process_type type) { - switch (type) { - case process_type::url: - return os << "url"; - case process_type::pattern: - return os << "pattern"; - default: - return os << "unknown"; - } - } - - // All strings must be valid UTF-8. - // @see https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit - static tl::expected process( - const url_pattern_init& init, process_type type, - std::optional protocol = std::nullopt, - std::optional username = std::nullopt, - std::optional password = std::nullopt, - std::optional hostname = std::nullopt, - std::optional port = std::nullopt, - std::optional pathname = std::nullopt, - std::optional search = std::nullopt, - std::optional hash = std::nullopt); - - // @see https://urlpattern.spec.whatwg.org/#process-protocol-for-init - static tl::expected process_protocol( - std::string_view value, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-username-for-init - static tl::expected process_username( - std::string_view value, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-password-for-init - static tl::expected process_password( - std::string_view value, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-hostname-for-init - static tl::expected process_hostname( - std::string_view value, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-port-for-init - static tl::expected process_port( - std::string_view port, std::string_view protocol, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-pathname-for-init - static tl::expected process_pathname( - std::string_view value, std::string_view protocol, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-search-for-init - static tl::expected process_search( - std::string_view value, process_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-hash-for-init - static tl::expected process_hash(std::string_view value, - process_type type); - -#if ADA_TESTING - friend void PrintTo(const url_pattern_init& init, std::ostream* os) { - *os << "protocol: '" << init.protocol.value_or("undefined") << "', "; - *os << "username: '" << init.username.value_or("undefined") << "', "; - *os << "password: '" << init.password.value_or("undefined") << "', "; - *os << "hostname: '" << init.hostname.value_or("undefined") << "', "; - *os << "port: '" << init.port.value_or("undefined") << "', "; - *os << "pathname: '" << init.pathname.value_or("undefined") << "', "; - *os << "search: '" << init.search.value_or("undefined") << "', "; - *os << "hash: '" << init.hash.value_or("undefined") << "', "; - *os << "base_url: '" << init.base_url.value_or("undefined") << "', "; - } -#endif // ADA_TESTING - - bool operator==(const url_pattern_init&) const; - // If present, must be valid UTF-8. - std::optional protocol{}; - // If present, must be valid UTF-8. - std::optional username{}; - // If present, must be valid UTF-8. - std::optional password{}; - // If present, must be valid UTF-8. - std::optional hostname{}; - // If present, must be valid UTF-8. - std::optional port{}; - // If present, must be valid UTF-8. - std::optional pathname{}; - // If present, must be valid UTF-8. - std::optional search{}; - // If present, must be valid UTF-8. - std::optional hash{}; - // If present, must be valid UTF-8. - std::optional base_url{}; -}; -} // namespace ada -#endif // ADA_INCLUDE_URL_PATTERN -#endif // ADA_URL_PATTERN_INIT_H -/* end file include/ada/url_pattern_init.h */ - -/** @private Forward declarations */ -namespace ada { -struct url_aggregator; -struct url; -#if ADA_INCLUDE_URL_PATTERN -template -class url_pattern; -struct url_pattern_options; -#endif // ADA_INCLUDE_URL_PATTERN -enum class errors : uint8_t; -} // namespace ada - -/** - * @namespace ada::parser - * @brief Internal URL parsing implementation. - * - * Contains the core URL parsing algorithm as specified by the WHATWG URL - * Standard. These functions are used internally by `ada::parse()`. - */ -namespace ada::parser { -/** - * Parses a URL string into a URL object. - * - * @tparam result_type The type of URL object to create (url or url_aggregator). - * - * @param user_input The URL string to parse (must be valid UTF-8). - * @param base_url Optional base URL for resolving relative URLs. - * - * @return The parsed URL object. Check `is_valid` to determine if parsing - * succeeded. - * - * @see https://url.spec.whatwg.org/#concept-basic-url-parser - */ -template -result_type parse_url(std::string_view user_input, - const result_type* base_url = nullptr); - -extern template url_aggregator parse_url( - std::string_view user_input, const url_aggregator* base_url); -extern template url parse_url(std::string_view user_input, - const url* base_url); - -template -result_type parse_url_impl(std::string_view user_input, - const result_type* base_url = nullptr); - -extern template url_aggregator parse_url_impl( - std::string_view user_input, const url_aggregator* base_url); -extern template url_aggregator parse_url_impl( - std::string_view user_input, const url_aggregator* base_url); -extern template url parse_url_impl(std::string_view user_input, - const url* base_url); - -/** @private */ -template -bool try_parse_simple_absolute(std::string_view input, result_type& out); - -#if ADA_INCLUDE_URL_PATTERN -template -tl::expected, errors> parse_url_pattern_impl( - std::variant&& input, - const std::string_view* base_url, const url_pattern_options* options); -#endif // ADA_INCLUDE_URL_PATTERN - -} // namespace ada::parser - -#endif // ADA_PARSER_H -/* end file include/ada/parser.h */ -/* begin file include/ada/parser-inl.h */ -/** - * @file parser-inl.h - */ -#ifndef ADA_PARSER_INL_H -#define ADA_PARSER_INL_H - -/* begin file include/ada/url_pattern.h */ -/** - * @file url_pattern.h - * @brief URLPattern API implementation. - * - * This header provides the URLPattern API as specified by the WHATWG URL - * Pattern Standard. URLPattern allows matching URLs against patterns with - * wildcards and named groups, similar to how regular expressions match strings. - * - * @see https://urlpattern.spec.whatwg.org/ - * @see https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API - */ -#ifndef ADA_URL_PATTERN_H -#define ADA_URL_PATTERN_H - -/* begin file include/ada/implementation.h */ -/** - * @file implementation.h - * @brief User-facing functions for URL parsing and manipulation. - * - * This header provides the primary public API for parsing URLs in Ada. - * It includes the main `ada::parse()` function which is the recommended - * entry point for most users. - * - * @see https://url.spec.whatwg.org/#api - */ -#ifndef ADA_IMPLEMENTATION_H -#define ADA_IMPLEMENTATION_H - -#include -#include -#include - -/* begin file include/ada/url.h */ -/** - * @file url.h - * @brief Declaration for the `ada::url` class. - * - * This file contains the `ada::url` struct which represents a parsed URL - * using separate `std::string` instances for each component. This - * representation is more flexible but uses more memory than `url_aggregator`. - * - * @see url_aggregator.h for a more memory-efficient alternative - */ -#ifndef ADA_URL_H -#define ADA_URL_H - -#include -#include -#include -#include -#include - -/* begin file include/ada/url_components.h */ -/** - * @file url_components.h - * @brief URL component offset representation for url_aggregator. - * - * This file defines the `url_components` struct which stores byte offsets - * into a URL string buffer. It is used internally by `url_aggregator` to - * efficiently locate URL components without storing separate strings. - */ -#ifndef ADA_URL_COMPONENTS_H -#define ADA_URL_COMPONENTS_H - -namespace ada { - -/** - * @brief Stores byte offsets for URL components within a buffer. - * - * The `url_components` struct uses 32-bit offsets to track the boundaries - * of each URL component within a single string buffer. This enables efficient - * component extraction without additional memory allocations. - * - * Component layout in a URL: - * ``` - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - * ``` - * - * @note The 32-bit offsets limit URLs to 4GB in length. - * @note A value of `omitted` (UINT32_MAX) indicates the component is not - * present. - */ -struct url_components { - /** Sentinel value indicating a component is not present. */ - constexpr static uint32_t omitted = uint32_t(-1); - - url_components() = default; - url_components(const url_components& u) = default; - url_components(url_components&& u) noexcept = default; - url_components& operator=(url_components&& u) noexcept = default; - url_components& operator=(const url_components& u) = default; - ~url_components() = default; - - /** Offset of the end of the protocol/scheme (position of ':'). */ - uint32_t protocol_end{0}; - - /** - * Offset of the end of the username. - * Initialized to 0 (not `omitted`) to simplify username/password getters. - */ - uint32_t username_end{0}; - - /** Offset of the start of the host. */ - uint32_t host_start{0}; - - /** Offset of the end of the host. */ - uint32_t host_end{0}; - - /** Port number, or `omitted` if no port is specified. */ - uint32_t port{omitted}; - - /** Offset of the start of the pathname. */ - uint32_t pathname_start{0}; - - /** Offset of the '?' starting the query, or `omitted` if no query. */ - uint32_t search_start{omitted}; - - /** Offset of the '#' starting the fragment, or `omitted` if no fragment. */ - uint32_t hash_start{omitted}; - - /** - * Validates that offsets are in ascending order and consistent. - * Useful for debugging to detect internal corruption. - * @return `true` if offsets are consistent, `false` otherwise. - */ - [[nodiscard]] constexpr bool check_offset_consistency() const noexcept; - - /** - * Returns a JSON string representation of the offsets for debugging. - * @return A JSON-formatted string with all offset values. - */ - [[nodiscard]] std::string to_string() const; - -}; // struct url_components -} // namespace ada -#endif -/* end file include/ada/url_components.h */ - -namespace ada { - -struct url_aggregator; - -// namespace parser { -// template -// result_type parse_url(std::string_view user_input, -// const result_type* base_url = nullptr); -// template -// result_type parse_url_impl(std::string_view user_input, -// const result_type* base_url = nullptr); -// } - -/** - * @brief Represents a parsed URL with individual string components. - * - * The `url` struct stores each URL component (scheme, username, password, - * host, port, path, query, fragment) as a separate `std::string`. This - * provides flexibility but incurs more memory allocations compared to - * `url_aggregator`. - * - * **When to use `ada::url`:** - * - When you need to frequently modify individual URL components - * - When you want independent ownership of component strings - * - * **When to use `ada::url_aggregator` instead:** - * - For read-mostly operations on parsed URLs - * - When memory efficiency is important - * - When you only need string_view access to components - * - * @note This type is returned when parsing with `ada::parse()`. - * By default, `ada::parse()` returns `ada::url_aggregator`. - * - * @see url_aggregator For a more memory-efficient URL representation - * @see https://url.spec.whatwg.org/#url-representation - */ -struct url : url_base { - url() = default; - url(const url& u) = default; - url(url&& u) noexcept = default; - url& operator=(url&& u) noexcept = default; - url& operator=(const url& u) = default; - ~url() override = default; - - // Fields are ordered so that the most frequently accessed components - // tend to occupy earlier cache lines and remain close together in memory. - // - // Note: The exact object layout (including cache-line boundaries, byte - // offsets, and member sizes) is implementation- and platform-dependent. - // This ordering expresses an intent for better cache locality but does not - // guarantee any specific in-memory layout. - - /** - * @private - * A URL's host is null or a host. It is initially null. - */ - std::optional host{}; - - /** - * @private - * A URL's path is either an ASCII string or a list of zero or more ASCII - * strings, usually identifying a location. - */ - std::string path{}; - - /** - * @private - * A URL's query is either null or an ASCII string. It is initially null. - */ - std::optional query{}; - - /** - * @private - * A URL's fragment is either null or an ASCII string that can be used for - * further processing on the resource the URL's other components identify. It - * is initially null. - */ - std::optional hash{}; - - /** - * @private - * A URL's port is either null or a 16-bit unsigned integer that identifies a - * networking port. It is initially null. - */ - std::optional port{}; - - /** - * @private - * A URL's username is an ASCII string identifying a username. It is initially - * the empty string. - */ - std::string username{}; - - /** - * @private - * A URL's password is an ASCII string identifying a password. It is initially - * the empty string. - */ - std::string password{}; - - /** - * Checks if the URL has an empty hostname (host is set but empty string). - * @return `true` if host exists but is empty, `false` otherwise. - */ - [[nodiscard]] inline bool has_empty_hostname() const noexcept; - - /** - * Checks if the URL has a non-default port explicitly specified. - * @return `true` if a port is present, `false` otherwise. - */ - [[nodiscard]] inline bool has_port() const noexcept; - - /** - * Checks if the URL has a hostname (including empty hostnames). - * @return `true` if host is present, `false` otherwise. - */ - [[nodiscard]] inline bool has_hostname() const noexcept; - - /** - * Validates whether the hostname is a valid domain according to RFC 1034. - * Checks that the domain and its labels have valid lengths (max 255 octets - * total, max 63 octets per label). - * @return `true` if the domain is valid, `false` otherwise. - */ - [[nodiscard]] bool has_valid_domain() const noexcept override; - - /** - * Returns a JSON string representation of this URL for debugging. - * @return A JSON-formatted string with all URL components. - */ - [[nodiscard]] std::string to_string() const override; - - /** - * Returns the full serialized URL (the href). - * @return The complete URL string (allocates a new string). - * @see https://url.spec.whatwg.org/#dom-url-href - */ - [[nodiscard]] ada_really_inline std::string get_href() const; - - /** - * Returns the byte length of the serialized URL without allocating a string. - * @return Size of the href in bytes. - */ - [[nodiscard]] size_t get_href_size() const noexcept; - - /** - * Returns the URL's origin as a string (scheme + host + port for special - * URLs). - * @return A newly allocated string containing the serialized origin. - * @see https://url.spec.whatwg.org/#concept-url-origin - */ - [[nodiscard]] std::string get_origin() const override; - - /** - * Returns the URL's scheme followed by a colon (e.g., "https:"). - * @return A newly allocated string with the protocol. - * @see https://url.spec.whatwg.org/#dom-url-protocol - */ - [[nodiscard]] std::string get_protocol() const; - - /** - * Returns the URL's host and port (e.g., "example.com:8080"). - * If no port is set, returns just the host. Returns empty string if no host. - * @return A newly allocated string with host:port. - * @see https://url.spec.whatwg.org/#dom-url-host - */ - [[nodiscard]] std::string get_host() const; - - /** - * Returns the URL's hostname (without port). - * Returns empty string if no host is set. - * @return A newly allocated string with the hostname. - * @see https://url.spec.whatwg.org/#dom-url-hostname - */ - [[nodiscard]] std::string get_hostname() const; - - /** - * Returns the URL's path component. - * @return A string_view pointing to the path. - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - [[nodiscard]] constexpr std::string_view get_pathname() const noexcept; - - /** - * Returns the byte length of the pathname without creating a string. - * @return Size of the pathname in bytes. - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - [[nodiscard]] ada_really_inline size_t get_pathname_length() const noexcept; - - /** - * Returns the URL's query string prefixed with '?' (e.g., "?foo=bar"). - * Returns empty string if no query is set. - * @return A newly allocated string with the search/query. - * @see https://url.spec.whatwg.org/#dom-url-search - */ - [[nodiscard]] std::string get_search() const; - - /** - * Returns the URL's username component. - * @return A constant reference to the username string. - * @see https://url.spec.whatwg.org/#dom-url-username - */ - [[nodiscard]] const std::string& get_username() const noexcept; - - /** - * Sets the URL's username, percent-encoding special characters. - * @param input The new username value. - * @return `true` on success, `false` if the URL cannot have credentials. - * @see https://url.spec.whatwg.org/#dom-url-username - */ - bool set_username(std::string_view input); - - /** - * Sets the URL's password, percent-encoding special characters. - * @param input The new password value. - * @return `true` on success, `false` if the URL cannot have credentials. - * @see https://url.spec.whatwg.org/#dom-url-password - */ - bool set_password(std::string_view input); - - /** - * Sets the URL's port from a string (e.g., "8080"). - * @param input The port string. Empty string removes the port. - * @return `true` on success, `false` if the URL cannot have a port. - * @see https://url.spec.whatwg.org/#dom-url-port - */ - bool set_port(std::string_view input); - - /** - * Sets the URL's fragment/hash (the part after '#'). - * @param input The new hash value (with or without leading '#'). - * @see https://url.spec.whatwg.org/#dom-url-hash - */ - void set_hash(std::string_view input); - - /** - * Sets the URL's query string (the part after '?'). - * @param input The new query value (with or without leading '?'). - * @see https://url.spec.whatwg.org/#dom-url-search - */ - void set_search(std::string_view input); - - /** - * Sets the URL's pathname. - * @param input The new path value. - * @return `true` on success, `false` if the URL has an opaque path. - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - bool set_pathname(std::string_view input); - - /** - * Sets the URL's host (hostname and optionally port). - * @param input The new host value (e.g., "example.com:8080"). - * @return `true` on success, `false` if parsing fails. - * @see https://url.spec.whatwg.org/#dom-url-host - */ - bool set_host(std::string_view input); - - /** - * Sets the URL's hostname (without port). - * @param input The new hostname value. - * @return `true` on success, `false` if parsing fails. - * @see https://url.spec.whatwg.org/#dom-url-hostname - */ - bool set_hostname(std::string_view input); - - /** - * Sets the URL's protocol/scheme. - * @param input The new protocol (with or without trailing ':'). - * @return `true` on success, `false` if the scheme is invalid. - * @see https://url.spec.whatwg.org/#dom-url-protocol - */ - bool set_protocol(std::string_view input); - - /** - * Replaces the entire URL by parsing a new href string. - * @param input The new URL string to parse. - * @return `true` on success, `false` if parsing fails. - * @see https://url.spec.whatwg.org/#dom-url-href - */ - bool set_href(std::string_view input); - - /** - * Returns the URL's password component. - * @return A constant reference to the password string. - * @see https://url.spec.whatwg.org/#dom-url-password - */ - [[nodiscard]] const std::string& get_password() const noexcept; - - /** - * Returns the URL's port as a string (e.g., "8080"). - * Returns empty string if no port is set. - * @return A newly allocated string with the port. - * @see https://url.spec.whatwg.org/#dom-url-port - */ - [[nodiscard]] std::string get_port() const; - - /** - * Returns the URL's fragment prefixed with '#' (e.g., "#section"). - * Returns empty string if no fragment is set. - * @return A newly allocated string with the hash. - * @see https://url.spec.whatwg.org/#dom-url-hash - */ - [[nodiscard]] std::string get_hash() const; - - /** - * Checks if the URL has credentials (non-empty username or password). - * @return `true` if username or password is non-empty, `false` otherwise. - */ - [[nodiscard]] ada_really_inline bool has_credentials() const noexcept; - - /** - * Returns the URL component offsets for efficient serialization. - * - * The components represent byte offsets into the serialized URL: - * ``` - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - * ``` - * @return A newly constructed url_components struct. - * @see https://github.com/servo/rust-url - */ - [[nodiscard]] ada_really_inline ada::url_components get_components() const; - - /** - * Checks if the URL has a fragment/hash component. - * @return `true` if hash is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_hash() const noexcept override; - - /** - * Checks if the URL has a query/search component. - * @return `true` if query is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_search() const noexcept override; - - private: - friend ada::url ada::parser::parse_url(std::string_view, - const ada::url*); - friend ada::url_aggregator ada::parser::parse_url( - std::string_view, const ada::url_aggregator*); - friend void ada::helpers::strip_trailing_spaces_from_opaque_path( - ada::url& url); - - friend ada::url ada::parser::parse_url_impl(std::string_view, - const ada::url*); - friend ada::url_aggregator ada::parser::parse_url_impl< - ada::url_aggregator, true>(std::string_view, const ada::url_aggregator*); - friend ada::url_aggregator ada::parser::parse_url_impl< - ada::url_aggregator, false>(std::string_view, const ada::url_aggregator*); - template - friend bool ada::parser::try_parse_simple_absolute(std::string_view, - result_type&); - - inline void update_unencoded_base_hash(std::string_view input); - inline void update_base_hostname(std::string_view input); - inline void update_base_search(std::string_view input, - const uint8_t query_percent_encode_set[]); - inline void update_base_search(std::optional&& input); - inline void update_base_pathname(std::string_view input); - inline void update_base_username(std::string_view input); - inline void update_base_password(std::string_view input); - inline void update_base_port(std::optional input); - - /** - * Sets the host or hostname according to override condition. - * Return true on success. - * @see https://url.spec.whatwg.org/#hostname-state - */ - template - bool set_host_or_hostname(std::string_view input); - - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-ipv4-parser - */ - [[nodiscard]] bool parse_ipv4(std::string_view input); - - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-ipv6-parser - */ - [[nodiscard]] bool parse_ipv6(std::string_view input); - - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-opaque-host-parser - */ - [[nodiscard]] bool parse_opaque_host(std::string_view input); - - /** - * A URL's scheme is an ASCII string that identifies the type of URL and can - * be used to dispatch a URL for further processing after parsing. It is - * initially the empty string. We only set non_special_scheme when the scheme - * is non-special, otherwise we avoid constructing string. - * - * Special schemes are stored in ada::scheme::details::is_special_list so we - * typically do not need to store them in each url instance. - */ - std::string non_special_scheme{}; - - /** - * A URL cannot have a username/password/port if its host is null or the empty - * string, or its scheme is "file". - */ - [[nodiscard]] inline bool cannot_have_credentials_or_port() const; - - ada_really_inline size_t parse_port( - std::string_view view, bool check_trailing_content) noexcept override; - - ada_really_inline size_t parse_port(std::string_view view) noexcept override { - return this->parse_port(view, false); - } - - /** - * Parse the host from the provided input. We assume that - * the input does not contain spaces or tabs. Control - * characters and spaces are not trimmed (they should have - * been removed if needed). - * Return true on success. - * @see https://url.spec.whatwg.org/#host-parsing - */ - [[nodiscard]] ada_really_inline bool parse_host(std::string_view input); - - template - [[nodiscard]] ada_really_inline bool parse_scheme(std::string_view input); - - constexpr void clear_pathname() override; - constexpr void clear_search() override; - constexpr void set_protocol_as_file(); - - /** - * Parse the path from the provided input. - * Return true on success. Control characters not - * trimmed from the ends (they should have - * been removed if needed). - * - * The input is expected to be UTF-8. - * - * @see https://url.spec.whatwg.org/ - */ - ada_really_inline void parse_path(std::string_view input); - - /** - * Set the scheme for this URL. The provided scheme should be a valid - * scheme string, be lower-cased, not contain spaces or tabs. It should - * have no spurious trailing or leading content. - */ - inline void set_scheme(std::string&& new_scheme) noexcept; - - /** - * Take the scheme from another URL. The scheme string is moved from the - * provided url. - */ - constexpr void copy_scheme(ada::url&& u); - - /** - * Take the scheme from another URL. The scheme string is copied from the - * provided url. - */ - constexpr void copy_scheme(const ada::url& u); - -}; // struct url - -inline std::ostream& operator<<(std::ostream& out, const ada::url& u); -} // namespace ada - -#endif // ADA_URL_H -/* end file include/ada/url.h */ - -namespace ada { - -/** - * Result type for URL parsing operations. - * - * Uses `tl::expected` to represent either a successfully parsed URL or an - * error. This allows for exception-free error handling. - * - * @tparam result_type The URL type to return (default: `ada::url_aggregator`) - * - * @example - * ```cpp - * ada::result result = ada::parse("https://example.com"); - * if (result) { - * // Success: use result.value() or *result - * } else { - * // Error: handle result.error() - * } - * ``` - */ -template -using result = tl::expected; - -/** - * Parses a URL string according to the WHATWG URL Standard. - * - * This is the main entry point for URL parsing in Ada. The function takes - * a string input and optionally a base URL for resolving relative URLs. - * - * @tparam result_type The URL type to return. Can be either `ada::url` or - * `ada::url_aggregator` (default). The `url_aggregator` type is more - * memory-efficient as it stores components as offsets into a single - * buffer. - * - * @param input The URL string to parse. Must be valid ASCII or UTF-8 encoded. - * Leading and trailing whitespace is automatically trimmed. - * @param base_url Optional pointer to a base URL for resolving relative URLs. - * If nullptr (default), only absolute URLs can be parsed successfully. - * - * @return A `result` containing either the parsed URL on success, - * or an error code on failure. Use the boolean conversion or - * `has_value()` to check for success. - * - * @note The parser is fully compliant with the WHATWG URL Standard. - * - * Parsing fails if the input or the resulting normalized URL exceeds - * `get_max_input_length()` bytes (default ~4 GB, configurable via - * `set_max_input_length()`). This accounts for percent-encoding expansion: - * a short input that normalizes into a long URL is still rejected. - * - * @example - * ```cpp - * // Parse an absolute URL - * auto url = ada::parse("https://user:pass@example.com:8080/path?query#hash"); - * if (url) { - * std::cout << url->get_hostname(); // "example.com" - * std::cout << url->get_pathname(); // "/path" - * } - * - * // Parse a relative URL with a base - * auto base = ada::parse("https://example.com/dir/"); - * if (base) { - * auto relative = ada::parse("../other/page", &*base); - * if (relative) { - * std::cout << relative->get_href(); // - * "https://example.com/other/page" - * } - * } - * ``` - * - * @see https://url.spec.whatwg.org/#url-parsing - */ -template -ada_warn_unused ada::result parse( - std::string_view input, const result_type* base_url = nullptr); - -extern template ada::result parse(std::string_view input, - const url* base_url); -extern template ada::result parse( - std::string_view input, const url_aggregator* base_url); - -/** - * Checks whether a URL string can be successfully parsed. - * - * Equivalent to `parse(input, base).has_value()` for every input, including - * when `set_max_input_length` rejects a normalized href that exceeds the limit. - * - * @param input The URL string to validate. Must be valid ASCII or UTF-8. - * @param base_input Optional base URL string for relative inputs. - * @return `true` if parsing would succeed, `false` otherwise. - * @see https://url.spec.whatwg.org/#dom-url-canparse - */ -bool can_parse(std::string_view input, - const std::string_view* base_input = nullptr); - -#if ADA_INCLUDE_URL_PATTERN -/** - * Parses a URL pattern according to the URLPattern specification. - * - * URL patterns provide a syntax for matching URLs against patterns, similar - * to how regular expressions match strings. This is useful for routing and - * URL-based dispatching. - * - * @tparam regex_provider The regex implementation to use for pattern matching. - * - * @param input Either a URL pattern string (valid UTF-8) or a URLPatternInit - * struct specifying individual component patterns. - * @param base_url Optional pointer to a base URL string (valid UTF-8) for - * resolving relative patterns. - * @param options Optional pointer to configuration options (e.g., ignore_case). - * - * @return A `tl::expected` containing either the parsed url_pattern on success, - * or an error code on failure. - * - * @see https://urlpattern.spec.whatwg.org - */ -template -ada_warn_unused tl::expected, errors> -parse_url_pattern(std::variant&& input, - const std::string_view* base_url = nullptr, - const url_pattern_options* options = nullptr); -#endif // ADA_INCLUDE_URL_PATTERN - -/** - * Converts a file system path to a file:// URL. - * - * Creates a properly formatted file URL from a local file system path. - * Handles platform-specific path separators and percent-encoding. - * - * Respects `get_max_input_length()`: if the input path or the resulting - * `file://` href would exceed the limit, returns an empty string. - * - * @param path The file system path to convert. Must be valid ASCII or UTF-8. - * - * @return A file:// URL string representing the given path, or empty on - * length-limit rejection. - */ -std::string href_from_file(std::string_view path); - -/** - * Sets the maximum allowed length for URLs. - * - * Both the raw input and the resulting normalized URL (the href) are checked - * against this limit. Parsing or setter calls that would produce a URL - * exceeding this length are rejected. The same limit also applies to - * `href_from_file` and to query strings passed to `url_search_params` - * construction / `reset`. The value must fit in a uint32_t. - * The default is std::numeric_limits::max() (approximately 4 GB). - * - * @param length The new maximum URL length in bytes. - */ -void set_max_input_length(uint32_t length); - -/** - * Returns the current maximum allowed length for URLs. - * - * @return The current maximum URL length in bytes. - */ -uint32_t get_max_input_length(); - -} // namespace ada - -#endif // ADA_IMPLEMENTATION_H -/* end file include/ada/implementation.h */ - -#include -#include -#include -#include -#include -#include - -#if ADA_TESTING -#include -#endif // ADA_TESTING - -#if ADA_INCLUDE_URL_PATTERN -namespace ada { - -enum class url_pattern_part_type : uint8_t { - // The part represents a simple fixed text string. - FIXED_TEXT, - // The part represents a matching group with a custom regular expression. - REGEXP, - // The part represents a matching group that matches code points up to the - // next separator code point. This is typically used for a named group like - // ":foo" that does not have a custom regular expression. - SEGMENT_WILDCARD, - // The part represents a matching group that greedily matches all code points. - // This is typically used for the "*" wildcard matching group. - FULL_WILDCARD, -}; - -// Pattern type for fast-path matching optimization. -// This allows skipping expensive regex evaluation for common simple patterns. -enum class url_pattern_component_type : uint8_t { - // Pattern is "^$" - only matches empty string - EMPTY, - // Pattern is "^$" - exact string match (no regex needed) - EXACT_MATCH, - // Pattern is "^(.*)$" - matches anything (full wildcard) - FULL_WILDCARD, - // Pattern requires actual regex evaluation - REGEXP, -}; - -enum class url_pattern_part_modifier : uint8_t { - // The part does not have a modifier. - none, - // The part has an optional modifier indicated by the U+003F (?) code point. - optional, - // The part has a "zero or more" modifier indicated by the U+002A (*) code - // point. - zero_or_more, - // The part has a "one or more" modifier indicated by the U+002B (+) code - // point. - one_or_more, -}; - -// @see https://urlpattern.spec.whatwg.org/#part -class url_pattern_part { - public: - url_pattern_part(url_pattern_part_type _type, std::string&& _value, - url_pattern_part_modifier _modifier) - : type(_type), value(std::move(_value)), modifier(_modifier) {} - - url_pattern_part(url_pattern_part_type _type, std::string&& _value, - url_pattern_part_modifier _modifier, std::string&& _name, - std::string&& _prefix, std::string&& _suffix) - : type(_type), - value(std::move(_value)), - modifier(_modifier), - name(std::move(_name)), - prefix(std::move(_prefix)), - suffix(std::move(_suffix)) {} - // A part has an associated type, a string, which must be set upon creation. - url_pattern_part_type type; - // A part has an associated value, a string, which must be set upon creation. - std::string value; - // A part has an associated modifier a string, which must be set upon - // creation. - url_pattern_part_modifier modifier; - // A part has an associated name, a string, initially the empty string. - std::string name{}; - // A part has an associated prefix, a string, initially the empty string. - std::string prefix{}; - // A part has an associated suffix, a string, initially the empty string. - std::string suffix{}; - - inline bool is_regexp() const noexcept; -}; - -// @see https://urlpattern.spec.whatwg.org/#options-header -struct url_pattern_compile_component_options { - url_pattern_compile_component_options() = default; - explicit url_pattern_compile_component_options( - std::optional new_delimiter = std::nullopt, - std::optional new_prefix = std::nullopt) noexcept - : delimiter(new_delimiter), prefix(new_prefix) {} - - inline std::string_view get_delimiter() const ada_warn_unused; - inline std::string_view get_prefix() const ada_warn_unused; - - // @see https://urlpattern.spec.whatwg.org/#options-ignore-case - bool ignore_case = false; - - static url_pattern_compile_component_options DEFAULT; - static url_pattern_compile_component_options HOSTNAME; - static url_pattern_compile_component_options PATHNAME; - - private: - // @see https://urlpattern.spec.whatwg.org/#options-delimiter-code-point - std::optional delimiter{}; - // @see https://urlpattern.spec.whatwg.org/#options-prefix-code-point - std::optional prefix{}; -}; - -// The default options is an options struct with delimiter code point set to -// the empty string and prefix code point set to the empty string. -inline url_pattern_compile_component_options - url_pattern_compile_component_options::DEFAULT(std::nullopt, std::nullopt); - -// The hostname options is an options struct with delimiter code point set -// "." and prefix code point set to the empty string. -inline url_pattern_compile_component_options - url_pattern_compile_component_options::HOSTNAME('.', std::nullopt); - -// The pathname options is an options struct with delimiter code point set -// "/" and prefix code point set to "/". -inline url_pattern_compile_component_options - url_pattern_compile_component_options::PATHNAME('/', '/'); - -// A struct providing the URLPattern matching results for a single -// URL component. The URLPatternComponentResult is only ever used -// as a member attribute of a URLPatternResult struct. The -// URLPatternComponentResult API is defined as part of the URLPattern -// specification. -struct url_pattern_component_result { - std::string input; - std::unordered_map> groups; - - bool operator==(const url_pattern_component_result&) const; - -#if ADA_TESTING - friend void PrintTo(const url_pattern_component_result& result, - std::ostream* os) { - *os << "input: '" << result.input << "', group: "; - for (const auto& group : result.groups) { - *os << "(" << group.first << ", " << group.second.value_or("undefined") - << ") "; - } - } -#endif // ADA_TESTING -}; - -template -class url_pattern_component { - public: - url_pattern_component() = default; - - // This function explicitly takes a std::string because it is moved. - // To avoid unnecessary copy, move each value while calling the constructor. - url_pattern_component(std::string&& new_pattern, - typename regex_provider::regex_type&& new_regexp, - std::vector&& new_group_name_list, - bool new_has_regexp_groups, - url_pattern_component_type new_type, - std::string&& new_exact_match_value = {}) - : regexp(std::move(new_regexp)), - pattern(std::move(new_pattern)), - group_name_list(std::move(new_group_name_list)), - exact_match_value(std::move(new_exact_match_value)), - has_regexp_groups(new_has_regexp_groups), - type(new_type) {} - - // @see https://urlpattern.spec.whatwg.org/#compile-a-component - template - static tl::expected compile( - std::string_view input, F& encoding_callback, - url_pattern_compile_component_options& options); - - // @see https://urlpattern.spec.whatwg.org/#create-a-component-match-result - url_pattern_component_result create_component_match_result( - std::string&& input, - std::vector>&& exec_result); - - // Fast path test that returns true/false without constructing result groups. - // Uses cached pattern type to skip regex evaluation for simple patterns. - bool fast_test(std::string_view input) const noexcept; - - // Fast path match that returns capture groups without regex for simple - // patterns. Returns nullopt if pattern doesn't match, otherwise returns - // capture groups. - std::optional>> fast_match( - std::string_view input) const; - -#if ADA_TESTING - friend void PrintTo(const url_pattern_component& component, - std::ostream* os) { - *os << "pattern: '" << component.pattern - << "', has_regexp_groups: " << component.has_regexp_groups - << "group_name_list: "; - for (const auto& name : component.group_name_list) { - *os << name << ", "; - } - } -#endif // ADA_TESTING - - typename regex_provider::regex_type regexp{}; - std::string pattern{}; - std::vector group_name_list{}; - // For EXACT_MATCH type: the literal string to compare against - std::string exact_match_value{}; - bool has_regexp_groups = false; - // Cached pattern type for fast-path optimization - url_pattern_component_type type = url_pattern_component_type::REGEXP; -}; - -// A URLPattern input can be either a string or a URLPatternInit object. -// If it is a string, it must be a valid UTF-8 string. -using url_pattern_input = std::variant; - -// A struct providing the URLPattern matching results for all -// components of a URL. The URLPatternResult API is defined as -// part of the URLPattern specification. -struct url_pattern_result { - std::vector inputs; - url_pattern_component_result protocol; - url_pattern_component_result username; - url_pattern_component_result password; - url_pattern_component_result hostname; - url_pattern_component_result port; - url_pattern_component_result pathname; - url_pattern_component_result search; - url_pattern_component_result hash; -}; - -struct url_pattern_options { - bool ignore_case = false; - -#if ADA_TESTING - friend void PrintTo(const url_pattern_options& options, std::ostream* os) { - *os << "ignore_case: '" << options.ignore_case; - } -#endif // ADA_TESTING -}; - -/** - * @brief URL pattern matching class implementing the URLPattern API. - * - * URLPattern provides a way to match URLs against patterns with wildcards - * and named capture groups. It's useful for routing, URL-based dispatching, - * and URL validation. - * - * Pattern syntax supports: - * - Literal text matching - * - Named groups: `:name` (matches up to the next separator) - * - Wildcards: `*` (matches everything) - * - Custom regex: `(pattern)` - * - Optional segments: `:name?` - * - Repeated segments: `:name+`, `:name*` - * - * @tparam regex_provider The regex implementation to use for pattern matching. - * Must satisfy the url_pattern_regex::regex_concept. - * - * @note All string inputs must be valid UTF-8. - * - * @see https://urlpattern.spec.whatwg.org/ - */ -template -class url_pattern { - public: - url_pattern() = default; - - /** - * If non-null, base_url must pointer at a valid UTF-8 string. - * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec - */ - result> exec( - const url_pattern_input& input, - const std::string_view* base_url = nullptr); - - /** - * If non-null, base_url must pointer at a valid UTF-8 string. - * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-test - */ - result test(const url_pattern_input& input, - const std::string_view* base_url = nullptr); - - /** - * @see https://urlpattern.spec.whatwg.org/#url-pattern-match - * This function expects a valid UTF-8 string if input is a string. - */ - result> match( - const url_pattern_input& input, - const std::string_view* base_url_string = nullptr); - - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol - [[nodiscard]] std::string_view get_protocol() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-username - [[nodiscard]] std::string_view get_username() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-password - [[nodiscard]] std::string_view get_password() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-hostname - [[nodiscard]] std::string_view get_hostname() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-port - [[nodiscard]] std::string_view get_port() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-pathname - [[nodiscard]] std::string_view get_pathname() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-search - [[nodiscard]] std::string_view get_search() const ada_lifetime_bound; - // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-hash - [[nodiscard]] std::string_view get_hash() const ada_lifetime_bound; - - // If ignoreCase is true, the JavaScript regular expression created for each - // pattern must use the `vi` flag. Otherwise, they must use the `v` flag. - [[nodiscard]] bool ignore_case() const; - - // @see https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups - [[nodiscard]] bool has_regexp_groups() const; - - // Helper to test all components at once. Returns true if all match. - [[nodiscard]] bool test_components( - std::string_view protocol, std::string_view username, - std::string_view password, std::string_view hostname, - std::string_view port, std::string_view pathname, std::string_view search, - std::string_view hash) const; - -#if ADA_TESTING - friend void PrintTo(const url_pattern& c, std::ostream* os) { - *os << "protocol_component: '" << c.get_protocol() << ", "; - *os << "username_component: '" << c.get_username() << ", "; - *os << "password_component: '" << c.get_password() << ", "; - *os << "hostname_component: '" << c.get_hostname() << ", "; - *os << "port_component: '" << c.get_port() << ", "; - *os << "pathname_component: '" << c.get_pathname() << ", "; - *os << "search_component: '" << c.get_search() << ", "; - *os << "hash_component: '" << c.get_hash(); - } -#endif // ADA_TESTING - - template - friend tl::expected, errors> parser::parse_url_pattern_impl( - std::variant&& input, - const std::string_view* base_url, const url_pattern_options* options); - - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component protocol_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component username_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component password_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component hostname_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component port_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component pathname_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component search_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - url_pattern_component hash_component{}; - /** - * @private - * We can not make this private due to a LLVM bug. - * Ref: https://github.com/ada-url/ada/pull/859 - */ - bool ignore_case_ = false; -}; -} // namespace ada -#endif // ADA_INCLUDE_URL_PATTERN -#endif -/* end file include/ada/url_pattern.h */ -/* begin file include/ada/url_pattern_helpers.h */ -/** - * @file url_pattern_helpers.h - * @brief Declaration for the URLPattern helpers. - */ -#ifndef ADA_URL_PATTERN_HELPERS_H -#define ADA_URL_PATTERN_HELPERS_H - - -#include -#include -#include - -#if ADA_INCLUDE_URL_PATTERN -namespace ada { -enum class errors : uint8_t; -} - -namespace ada::url_pattern_helpers { - -// @see https://urlpattern.spec.whatwg.org/#token -enum class token_type : uint8_t { - INVALID_CHAR, // 0 - OPEN, // 1 - CLOSE, // 2 - REGEXP, // 3 - NAME, // 4 - CHAR, // 5 - ESCAPED_CHAR, // 6 - OTHER_MODIFIER, // 7 - ASTERISK, // 8 - END, // 9 -}; - -#ifdef ADA_TESTING -std::string to_string(token_type type); -#endif // ADA_TESTING - -// @see https://urlpattern.spec.whatwg.org/#tokenize-policy -enum class token_policy { - strict, - lenient, -}; - -// @see https://urlpattern.spec.whatwg.org/#tokens -class token { - public: - token(token_type _type, size_t _index, std::string_view _value) - : type(_type), index(_index), value(_value) {} - - // A token has an associated type, a string, initially "invalid-char". - token_type type = token_type::INVALID_CHAR; - - // A token has an associated index, a number, initially 0. It is the position - // of the first code point in the pattern string represented by the token. - size_t index = 0; - - // A token has an associated value, a string, initially the empty string. It - // contains the code points from the pattern string represented by the token. - std::string_view value{}; -}; - -// @see https://urlpattern.spec.whatwg.org/#pattern-parser -template -class url_pattern_parser { - public: - url_pattern_parser(F& encoding_callback_, - std::string_view segment_wildcard_regexp_) - : encoding_callback(encoding_callback_), - segment_wildcard_regexp(segment_wildcard_regexp_) {} - - bool can_continue() const { return index < tokens.size(); } - - // @see https://urlpattern.spec.whatwg.org/#try-to-consume-a-token - token* try_consume_token(token_type type); - // @see https://urlpattern.spec.whatwg.org/#try-to-consume-a-modifier-token - token* try_consume_modifier_token(); - // @see - // https://urlpattern.spec.whatwg.org/#try-to-consume-a-regexp-or-wildcard-token - token* try_consume_regexp_or_wildcard_token(const token* name_token); - // @see https://urlpattern.spec.whatwg.org/#consume-text - std::string consume_text(); - // @see https://urlpattern.spec.whatwg.org/#consume-a-required-token - bool consume_required_token(token_type type); - // @see - // https://urlpattern.spec.whatwg.org/#maybe-add-a-part-from-the-pending-fixed-value - std::optional maybe_add_part_from_the_pending_fixed_value() - ada_warn_unused; - // @see https://urlpattern.spec.whatwg.org/#add-a-part - std::optional add_part(std::string_view prefix, token* name_token, - token* regexp_or_wildcard_token, - std::string_view suyffix, - token* modifier_token) ada_warn_unused; - - std::vector tokens{}; - F& encoding_callback; - std::string segment_wildcard_regexp; - std::vector parts{}; - std::string pending_fixed_value{}; - size_t index = 0; - size_t next_numeric_name = 0; -}; - -// @see https://urlpattern.spec.whatwg.org/#tokenizer -class Tokenizer { - public: - explicit Tokenizer(std::string_view new_input, token_policy new_policy) - : input(new_input), policy(new_policy) {} - - // @see https://urlpattern.spec.whatwg.org/#get-the-next-code-point - constexpr void get_next_code_point(); - - // True when the most recent decoded unit was malformed UTF-8. - bool had_invalid_code_point() const { return invalid_code_point; } - - // @see https://urlpattern.spec.whatwg.org/#seek-and-get-the-next-code-point - constexpr void seek_and_get_next_code_point(size_t index); - - // @see https://urlpattern.spec.whatwg.org/#add-a-token - - void add_token(token_type type, size_t next_position, size_t value_position, - size_t value_length); - - // @see https://urlpattern.spec.whatwg.org/#add-a-token-with-default-length - void add_token_with_default_length(token_type type, size_t next_position, - size_t value_position); - - // @see - // https://urlpattern.spec.whatwg.org/#add-a-token-with-default-position-and-length - void add_token_with_defaults(token_type type); - - // @see https://urlpattern.spec.whatwg.org/#process-a-tokenizing-error - std::optional process_tokenizing_error( - size_t next_position, size_t value_position) ada_warn_unused; - - friend tl::expected, errors> tokenize( - std::string_view input, token_policy policy); - - private: - // has an associated input, a pattern string, initially the empty string. - std::string_view input; - // has an associated policy, a tokenize policy, initially "strict". - token_policy policy; - // has an associated token list, a token list, initially an empty list. - std::vector token_list{}; - // has an associated index, a number, initially 0. - size_t index = 0; - // has an associated next index, a number, initially 0. - size_t next_index = 0; - // has an associated code point, a Unicode code point, initially null. - char32_t code_point{}; - // Tracks whether the last decoded code point was malformed UTF-8. - bool invalid_code_point = false; -}; - -// @see https://urlpattern.spec.whatwg.org/#constructor-string-parser -template -struct constructor_string_parser { - explicit constructor_string_parser(std::string_view new_input, - std::vector&& new_token_list) - : input(new_input), token_list(std::move(new_token_list)) {} - // @see https://urlpattern.spec.whatwg.org/#parse-a-constructor-string - static tl::expected parse(std::string_view input); - - // @see https://urlpattern.spec.whatwg.org/#constructor-string-parser-state - enum class State { - INIT, - PROTOCOL, - AUTHORITY, - USERNAME, - PASSWORD, - HOSTNAME, - PORT, - PATHNAME, - SEARCH, - HASH, - DONE, - }; - - // @see - // https://urlpattern.spec.whatwg.org/#compute-protocol-matches-a-special-scheme-flag - std::optional compute_protocol_matches_special_scheme_flag(); - - private: - // @see https://urlpattern.spec.whatwg.org/#rewind - constexpr void rewind(); - - // @see https://urlpattern.spec.whatwg.org/#is-a-hash-prefix - constexpr bool is_hash_prefix(); - - // @see https://urlpattern.spec.whatwg.org/#is-a-search-prefix - constexpr bool is_search_prefix(); - - // @see https://urlpattern.spec.whatwg.org/#change-state - void change_state(State state, size_t skip); - - // @see https://urlpattern.spec.whatwg.org/#is-a-group-open - constexpr bool is_group_open() const; - - // @see https://urlpattern.spec.whatwg.org/#is-a-group-close - constexpr bool is_group_close() const; - - // @see https://urlpattern.spec.whatwg.org/#is-a-protocol-suffix - constexpr bool is_protocol_suffix() const; - - // @see https://urlpattern.spec.whatwg.org/#next-is-authority-slashes - constexpr bool next_is_authority_slashes() const; - - // @see https://urlpattern.spec.whatwg.org/#is-an-identity-terminator - constexpr bool is_an_identity_terminator() const; - - // @see https://urlpattern.spec.whatwg.org/#is-a-pathname-start - constexpr bool is_pathname_start() const; - - // @see https://urlpattern.spec.whatwg.org/#is-a-password-prefix - constexpr bool is_password_prefix() const; - - // @see https://urlpattern.spec.whatwg.org/#is-an-ipv6-open - constexpr bool is_an_ipv6_open() const; - - // @see https://urlpattern.spec.whatwg.org/#is-an-ipv6-close - constexpr bool is_an_ipv6_close() const; - - // @see https://urlpattern.spec.whatwg.org/#is-a-port-prefix - constexpr bool is_port_prefix() const; - - // @see https://urlpattern.spec.whatwg.org/#is-a-non-special-pattern-char - constexpr bool is_non_special_pattern_char(size_t index, - uint32_t value) const; - - // @see https://urlpattern.spec.whatwg.org/#get-a-safe-token - constexpr const token* get_safe_token(size_t index) const; - - // @see https://urlpattern.spec.whatwg.org/#make-a-component-string - std::string make_component_string(); - // has an associated input, a string, which must be set upon creation. - std::string_view input; - // has an associated token list, a token list, which must be set upon - // creation. - std::vector token_list; - // has an associated result, a URLPatternInit, initially set to a new - // URLPatternInit. - url_pattern_init result{}; - // has an associated component start, a number, initially set to 0. - size_t component_start = 0; - // has an associated token index, a number, initially set to 0. - size_t token_index = 0; - // has an associated token increment, a number, initially set to 1. - size_t token_increment = 1; - // has an associated group depth, a number, initially set to 0. - size_t group_depth = 0; - // has an associated hostname IPv6 bracket depth, a number, initially set to - // 0. - size_t hostname_ipv6_bracket_depth = 0; - // has an associated protocol matches a special scheme flag, a boolean, - // initially set to false. - bool protocol_matches_a_special_scheme_flag = false; - // has an associated state, a string, initially set to "init". - State state = State::INIT; -}; - -// @see https://urlpattern.spec.whatwg.org/#canonicalize-a-protocol -tl::expected canonicalize_protocol(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-username -tl::expected canonicalize_username(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-password -tl::expected canonicalize_password(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-password -tl::expected canonicalize_hostname(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-an-ipv6-hostname -tl::expected canonicalize_ipv6_hostname( - std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-port -tl::expected canonicalize_port(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-port -tl::expected canonicalize_port_with_protocol( - std::string_view input, std::string_view protocol); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-pathname -tl::expected canonicalize_pathname(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-an-opaque-pathname -tl::expected canonicalize_opaque_pathname( - std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-search -tl::expected canonicalize_search(std::string_view input); - -// @see https://wicg.github.io/urlpattern/#canonicalize-a-hash -tl::expected canonicalize_hash(std::string_view input); - -// @see https://urlpattern.spec.whatwg.org/#tokenize -tl::expected, errors> tokenize(std::string_view input, - token_policy policy); - -// @see https://urlpattern.spec.whatwg.org/#process-a-base-url-string -std::string process_base_url_string(std::string_view input, - url_pattern_init::process_type type); - -// @see https://urlpattern.spec.whatwg.org/#escape-a-pattern-string -std::string escape_pattern_string(std::string_view input); - -// @see https://urlpattern.spec.whatwg.org/#escape-a-regexp-string -std::string escape_regexp_string(std::string_view input); - -// @see https://urlpattern.spec.whatwg.org/#is-an-absolute-pathname -constexpr bool is_absolute_pathname( - std::string_view input, url_pattern_init::process_type type) noexcept; - -// @see https://urlpattern.spec.whatwg.org/#parse-a-pattern-string -template -tl::expected, errors> parse_pattern_string( - std::string_view input, url_pattern_compile_component_options& options, - F& encoding_callback); - -// @see https://urlpattern.spec.whatwg.org/#generate-a-pattern-string -std::string generate_pattern_string( - std::vector& part_list, - url_pattern_compile_component_options& options); - -// @see -// https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list -std::tuple> -generate_regular_expression_and_name_list( - const std::vector& part_list, - url_pattern_compile_component_options options); - -// @see https://urlpattern.spec.whatwg.org/#hostname-pattern-is-an-ipv6-address -bool is_ipv6_address(std::string_view input) noexcept; - -// @see -// https://urlpattern.spec.whatwg.org/#protocol-component-matches-a-special-scheme -template -bool protocol_component_matches_special_scheme( - ada::url_pattern_component& input); - -// @see https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string -std::string_view convert_modifier_to_string(url_pattern_part_modifier modifier); - -// @see https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp -std::string generate_segment_wildcard_regexp( - url_pattern_compile_component_options options); - -} // namespace ada::url_pattern_helpers -#endif // ADA_INCLUDE_URL_PATTERN -#endif -/* end file include/ada/url_pattern_helpers.h */ - -#include -#include -#include - -namespace ada::parser { -#if ADA_INCLUDE_URL_PATTERN -template -tl::expected, errors> parse_url_pattern_impl( - std::variant&& input, - const std::string_view* base_url, const url_pattern_options* options) { - // Let init be null. - url_pattern_init init; - - // If input is a scalar value string then: - if (std::holds_alternative(input)) { - // Set init to the result of running parse a constructor string given input. - auto parse_result = - url_pattern_helpers::constructor_string_parser::parse( - std::get(input)); - if (!parse_result) { - ada_log("constructor_string_parser::parse failed"); - return tl::unexpected(parse_result.error()); - } - init = std::move(*parse_result); - // If baseURL is null and init["protocol"] does not exist, then throw a - // TypeError. - if (!base_url && !init.protocol) { - ada_log("base url is null and protocol is not set"); - return tl::unexpected(errors::type_error); - } - - // If baseURL is not null, set init["baseURL"] to baseURL. - if (base_url) { - init.base_url = std::string(*base_url); - } - } else { - // Assert: input is a URLPatternInit. - ADA_ASSERT_TRUE(std::holds_alternative(input)); - // If baseURL is not null, then throw a TypeError. - if (base_url) { - ada_log("base url is not null"); - return tl::unexpected(errors::type_error); - } - // Optimization: Avoid copy by moving the input value. - // Set init to input. - init = std::move(std::get(input)); - } - - // Let processedInit be the result of process a URLPatternInit given init, - // "pattern", null, null, null, null, null, null, null, and null. - auto processed_init = - url_pattern_init::process(init, url_pattern_init::process_type::pattern); - if (!processed_init) { - ada_log("url_pattern_init::process failed for init and 'pattern'"); - return tl::unexpected(processed_init.error()); - } - - // For each componentName of "protocol", "username", "password", "hostname", - // "port", "pathname", "search", "hash" If processedInit[componentName] does - // not exist, then set processedInit[componentName] to "*". - ADA_ASSERT_TRUE(processed_init.has_value()); - if (!processed_init->protocol) processed_init->protocol = "*"; - if (!processed_init->username) processed_init->username = "*"; - if (!processed_init->password) processed_init->password = "*"; - if (!processed_init->hostname) processed_init->hostname = "*"; - if (!processed_init->port) processed_init->port = "*"; - if (!processed_init->pathname) processed_init->pathname = "*"; - if (!processed_init->search) processed_init->search = "*"; - if (!processed_init->hash) processed_init->hash = "*"; - - ada_log("-- processed_init->protocol: ", processed_init->protocol.value()); - ada_log("-- processed_init->username: ", processed_init->username.value()); - ada_log("-- processed_init->password: ", processed_init->password.value()); - ada_log("-- processed_init->hostname: ", processed_init->hostname.value()); - ada_log("-- processed_init->port: ", processed_init->port.value()); - ada_log("-- processed_init->pathname: ", processed_init->pathname.value()); - ada_log("-- processed_init->search: ", processed_init->search.value()); - ada_log("-- processed_init->hash: ", processed_init->hash.value()); - - // If processedInit["protocol"] is a special scheme and processedInit["port"] - // is a string which represents its corresponding default port in radix-10 - // using ASCII digits then set processedInit["port"] to the empty string. - // TODO: Optimization opportunity. - if (scheme::is_special(*processed_init->protocol)) { - std::string_view port = processed_init->port.value(); - if (std::to_string(scheme::get_special_port(*processed_init->protocol)) == - port) { - processed_init->port->clear(); - } - } - - // Let urlPattern be a new URL pattern. - url_pattern url_pattern_{}; - - // Set urlPattern's protocol component to the result of compiling a component - // given processedInit["protocol"], canonicalize a protocol, and default - // options. - auto protocol_component = url_pattern_component::compile( - processed_init->protocol.value(), - url_pattern_helpers::canonicalize_protocol, - url_pattern_compile_component_options::DEFAULT); - if (!protocol_component) { - ada_log("url_pattern_component::compile failed for protocol ", - processed_init->protocol.value()); - return tl::unexpected(protocol_component.error()); - } - url_pattern_.protocol_component = std::move(*protocol_component); - - // Set urlPattern's username component to the result of compiling a component - // given processedInit["username"], canonicalize a username, and default - // options. - auto username_component = url_pattern_component::compile( - processed_init->username.value(), - url_pattern_helpers::canonicalize_username, - url_pattern_compile_component_options::DEFAULT); - if (!username_component) { - ada_log("url_pattern_component::compile failed for username ", - processed_init->username.value()); - return tl::unexpected(username_component.error()); - } - url_pattern_.username_component = std::move(*username_component); - - // Set urlPattern's password component to the result of compiling a component - // given processedInit["password"], canonicalize a password, and default - // options. - auto password_component = url_pattern_component::compile( - processed_init->password.value(), - url_pattern_helpers::canonicalize_password, - url_pattern_compile_component_options::DEFAULT); - if (!password_component) { - ada_log("url_pattern_component::compile failed for password ", - processed_init->password.value()); - return tl::unexpected(password_component.error()); - } - url_pattern_.password_component = std::move(*password_component); - - // TODO: Optimization opportunity. The following if statement can be - // simplified. - // If the result running hostname pattern is an IPv6 address given - // processedInit["hostname"] is true, then set urlPattern's hostname component - // to the result of compiling a component given processedInit["hostname"], - // canonicalize an IPv6 hostname, and hostname options. - if (url_pattern_helpers::is_ipv6_address(processed_init->hostname.value())) { - ada_log("processed_init->hostname is ipv6 address"); - // then set urlPattern's hostname component to the result of compiling a - // component given processedInit["hostname"], canonicalize an IPv6 hostname, - // and hostname options. - auto hostname_component = url_pattern_component::compile( - processed_init->hostname.value(), - url_pattern_helpers::canonicalize_ipv6_hostname, - url_pattern_compile_component_options::DEFAULT); - if (!hostname_component) { - ada_log("url_pattern_component::compile failed for ipv6 hostname ", - processed_init->hostname.value()); - return tl::unexpected(hostname_component.error()); - } - url_pattern_.hostname_component = std::move(*hostname_component); - } else { - // Otherwise, set urlPattern's hostname component to the result of compiling - // a component given processedInit["hostname"], canonicalize a hostname, and - // hostname options. - auto hostname_component = url_pattern_component::compile( - processed_init->hostname.value(), - url_pattern_helpers::canonicalize_hostname, - url_pattern_compile_component_options::HOSTNAME); - if (!hostname_component) { - ada_log("url_pattern_component::compile failed for hostname ", - processed_init->hostname.value()); - return tl::unexpected(hostname_component.error()); - } - url_pattern_.hostname_component = std::move(*hostname_component); - } - - // Set urlPattern's port component to the result of compiling a component - // given processedInit["port"], canonicalize a port, and default options. - auto port_component = url_pattern_component::compile( - processed_init->port.value(), url_pattern_helpers::canonicalize_port, - url_pattern_compile_component_options::DEFAULT); - if (!port_component) { - ada_log("url_pattern_component::compile failed for port ", - processed_init->port.value()); - return tl::unexpected(port_component.error()); - } - url_pattern_.port_component = std::move(*port_component); - - // Let compileOptions be a copy of the default options with the ignore case - // property set to options["ignoreCase"]. - auto compile_options = url_pattern_compile_component_options::DEFAULT; - if (options) { - compile_options.ignore_case = options->ignore_case; - } - - // TODO: Optimization opportunity: Simplify this if statement. - // If the result of running protocol component matches a special scheme given - // urlPattern's protocol component is true, then: - if (url_pattern_helpers::protocol_component_matches_special_scheme< - regex_provider>(url_pattern_.protocol_component)) { - // Let pathCompileOptions be copy of the pathname options with the ignore - // case property set to options["ignoreCase"]. - auto path_compile_options = url_pattern_compile_component_options::PATHNAME; - if (options) { - path_compile_options.ignore_case = options->ignore_case; - } - - // Set urlPattern's pathname component to the result of compiling a - // component given processedInit["pathname"], canonicalize a pathname, and - // pathCompileOptions. - auto pathname_component = url_pattern_component::compile( - processed_init->pathname.value(), - url_pattern_helpers::canonicalize_pathname, path_compile_options); - if (!pathname_component) { - ada_log("url_pattern_component::compile failed for pathname ", - processed_init->pathname.value()); - return tl::unexpected(pathname_component.error()); - } - url_pattern_.pathname_component = std::move(*pathname_component); - } else { - // Otherwise set urlPattern's pathname component to the result of compiling - // a component given processedInit["pathname"], canonicalize an opaque - // pathname, and compileOptions. - auto pathname_component = url_pattern_component::compile( - processed_init->pathname.value(), - url_pattern_helpers::canonicalize_opaque_pathname, compile_options); - if (!pathname_component) { - ada_log("url_pattern_component::compile failed for opaque pathname ", - processed_init->pathname.value()); - return tl::unexpected(pathname_component.error()); - } - url_pattern_.pathname_component = std::move(*pathname_component); - } - - // Set urlPattern's search component to the result of compiling a component - // given processedInit["search"], canonicalize a search, and compileOptions. - auto search_component = url_pattern_component::compile( - processed_init->search.value(), url_pattern_helpers::canonicalize_search, - compile_options); - if (!search_component) { - ada_log("url_pattern_component::compile failed for search ", - processed_init->search.value()); - return tl::unexpected(search_component.error()); - } - url_pattern_.search_component = std::move(*search_component); - - // Set urlPattern's hash component to the result of compiling a component - // given processedInit["hash"], canonicalize a hash, and compileOptions. - auto hash_component = url_pattern_component::compile( - processed_init->hash.value(), url_pattern_helpers::canonicalize_hash, - compile_options); - if (!hash_component) { - ada_log("url_pattern_component::compile failed for hash ", - processed_init->hash.value()); - return tl::unexpected(hash_component.error()); - } - url_pattern_.hash_component = std::move(*hash_component); - - // Return urlPattern. - return url_pattern_; -} -#endif // ADA_INCLUDE_URL_PATTERN - -} // namespace ada::parser - -#endif // ADA_PARSER_INL_H -/* end file include/ada/parser-inl.h */ -/* begin file include/ada/scheme-inl.h */ -/** - * @file scheme-inl.h - * @brief Definitions for the URL scheme. - */ -#ifndef ADA_SCHEME_INL_H -#define ADA_SCHEME_INL_H - - -namespace ada::scheme { - -/** - * @namespace ada::scheme::details - * @brief Includes the definitions for scheme specific entities - */ -namespace details { -// for use with is_special and get_special_port -// Spaces, if present, are removed from URL. -constexpr std::string_view is_special_list[] = {"http", " ", "https", "ws", - "ftp", "wss", "file", " "}; -// for use with get_special_port -constexpr uint16_t special_ports[] = {80, 0, 443, 80, 21, 443, 0, 0}; - -// @private -// convert a string_view to a 64-bit integer key for fast comparison -constexpr uint64_t make_key(std::string_view sv) { - uint64_t val = 0; - for (size_t i = 0; i < sv.size(); i++) - val |= (uint64_t)(uint8_t)sv[i] << (i * 8); - return val; -} -// precomputed keys for the special schemes, indexed by a hash of the input -// string -constexpr uint64_t scheme_keys[] = { - make_key("http"), // 0: HTTP - 0, // 1: sentinel - make_key("https"), // 2: HTTPS - make_key("ws"), // 3: WS - make_key("ftp"), // 4: FTP - make_key("wss"), // 5: WSS - make_key("file"), // 6: FILE - 0, // 7: sentinel -}; - -// @private -// branchless load of up to 5 characters into a uint64_t, padding with zeros if -// n < 5 -inline uint64_t branchless_load5(const char* p, size_t n) { - uint64_t input = (uint8_t)p[0]; - input |= ((uint64_t)(uint8_t)p[n > 1] << 8) & (0 - (uint64_t)(n > 1)); - input |= ((uint64_t)(uint8_t)p[(n > 2) * 2] << 16) & (0 - (uint64_t)(n > 2)); - input |= ((uint64_t)(uint8_t)p[(n > 3) * 3] << 24) & (0 - (uint64_t)(n > 3)); - input |= ((uint64_t)(uint8_t)p[(n > 4) * 4] << 32) & (0 - (uint64_t)(n > 4)); - return input; -} -} // namespace details - -/**** - * @private - * In is_special, get_scheme_type, and get_special_port, we - * use a standard hashing technique to find the index of the scheme in - * the is_special_list. The hashing technique is based on the size of - * the scheme and the first character of the scheme. It ensures that we - * do at most one string comparison per call. If the protocol is - * predictible (e.g., it is always "http"), we can get a better average - * performance by using a simpler approach where we loop and compare - * scheme with all possible protocols starting with the most likely - * protocol. Doing multiple comparisons may have a poor worst case - * performance, however. In this instance, we choose a potentially - * slightly lower best-case performance for a better worst-case - * performance. We can revisit this choice at any time. - * - * Reference: - * Schmidt, Douglas C. "Gperf: A perfect hash function generator." - * More C++ gems 17 (2000). - * - * Reference: https://en.wikipedia.org/wiki/Perfect_hash_function - * - * Reference: https://github.com/ada-url/ada/issues/617 - ****/ - -ada_really_inline constexpr bool is_special(std::string_view scheme) { - if (scheme.empty()) { - return false; - } - int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; - const std::string_view target = details::is_special_list[hash_value]; - return (target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1)); -} -constexpr uint16_t get_special_port(std::string_view scheme) noexcept { - if (scheme.empty()) { - return 0; - } - int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; - const std::string_view target = details::is_special_list[hash_value]; - if (scheme.size() == target.size() && - details::branchless_load5(scheme.data(), scheme.size()) == - details::scheme_keys[hash_value]) { - return details::special_ports[hash_value]; - } else { - return 0; - } -} -constexpr uint16_t get_special_port(ada::scheme::type type) noexcept { - return details::special_ports[int(type)]; -} -constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept { - if (scheme.empty()) { - return ada::scheme::NOT_SPECIAL; - } - int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; - const std::string_view target = details::is_special_list[hash_value]; - if (scheme.size() == target.size() && - details::branchless_load5(scheme.data(), scheme.size()) == - details::scheme_keys[hash_value]) { - return ada::scheme::type(hash_value); - } else { - return ada::scheme::NOT_SPECIAL; - } -} - -} // namespace ada::scheme - -#endif // ADA_SCHEME_INL_H -/* end file include/ada/scheme-inl.h */ -/* begin file include/ada/serializers.h */ -/** - * @file serializers.h - * @brief IP address serialization utilities. - * - * This header provides functions for converting IP addresses to their - * string representations according to the WHATWG URL Standard. - */ -#ifndef ADA_SERIALIZERS_H -#define ADA_SERIALIZERS_H - - -#include -#include - -/** - * @namespace ada::serializers - * @brief IP address serialization functions. - * - * Contains utilities for serializing IPv4 and IPv6 addresses to strings. - */ -namespace ada::serializers { - -/** - * Finds the longest consecutive sequence of zero pieces in an IPv6 address. - * Used for :: compression in IPv6 serialization. - * - * @param address The 8 16-bit pieces of the IPv6 address. - * @param[out] compress Index of the start of the longest zero sequence. - * @param[out] compress_length Length of the longest zero sequence. - */ -void find_longest_sequence_of_ipv6_pieces( - const std::array& address, size_t& compress, - size_t& compress_length) noexcept; - -/** - * Serializes an IPv6 address to its string representation. - * - * @param address The 8 16-bit pieces of the IPv6 address. - * @return The serialized IPv6 string (e.g., "2001:db8::1"). - * @see https://url.spec.whatwg.org/#concept-ipv6-serializer - */ -std::string ipv6(const std::array& address); - -/** - * Serializes an IPv4 address to its dotted-decimal string representation. - * - * @param address The 32-bit IPv4 address as an integer. - * @return The serialized IPv4 string (e.g., "192.168.1.1"). - * @see https://url.spec.whatwg.org/#concept-ipv4-serializer - */ -std::string ipv4(uint64_t address); - -} // namespace ada::serializers - -#endif // ADA_SERIALIZERS_H -/* end file include/ada/serializers.h */ -/* begin file include/ada/state.h */ -/** - * @file state.h - * @brief URL parser state machine states. - * - * Defines the states used by the URL parsing state machine as specified - * in the WHATWG URL Standard. - * - * @see https://url.spec.whatwg.org/#url-parsing - */ -#ifndef ADA_STATE_H -#define ADA_STATE_H - - -#include - -namespace ada { - -/** - * @brief States in the URL parsing state machine. - * - * The URL parser processes input through a sequence of states, each handling - * a specific part of the URL syntax. - * - * @see https://url.spec.whatwg.org/#url-parsing - */ -enum class state { - /** - * @see https://url.spec.whatwg.org/#authority-state - */ - AUTHORITY, - - /** - * @see https://url.spec.whatwg.org/#scheme-start-state - */ - SCHEME_START, - - /** - * @see https://url.spec.whatwg.org/#scheme-state - */ - SCHEME, - - /** - * @see https://url.spec.whatwg.org/#host-state - */ - HOST, - - /** - * @see https://url.spec.whatwg.org/#no-scheme-state - */ - NO_SCHEME, - - /** - * @see https://url.spec.whatwg.org/#fragment-state - */ - FRAGMENT, - - /** - * @see https://url.spec.whatwg.org/#relative-state - */ - RELATIVE_SCHEME, - - /** - * @see https://url.spec.whatwg.org/#relative-slash-state - */ - RELATIVE_SLASH, - - /** - * @see https://url.spec.whatwg.org/#file-state - */ - FILE, - - /** - * @see https://url.spec.whatwg.org/#file-host-state - */ - FILE_HOST, - - /** - * @see https://url.spec.whatwg.org/#file-slash-state - */ - FILE_SLASH, - - /** - * @see https://url.spec.whatwg.org/#path-or-authority-state - */ - PATH_OR_AUTHORITY, - - /** - * @see https://url.spec.whatwg.org/#special-authority-ignore-slashes-state - */ - SPECIAL_AUTHORITY_IGNORE_SLASHES, - - /** - * @see https://url.spec.whatwg.org/#special-authority-slashes-state - */ - SPECIAL_AUTHORITY_SLASHES, - - /** - * @see https://url.spec.whatwg.org/#special-relative-or-authority-state - */ - SPECIAL_RELATIVE_OR_AUTHORITY, - - /** - * @see https://url.spec.whatwg.org/#query-state - */ - QUERY, - - /** - * @see https://url.spec.whatwg.org/#path-state - */ - PATH, - - /** - * @see https://url.spec.whatwg.org/#path-start-state - */ - PATH_START, - - /** - * @see https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state - */ - OPAQUE_PATH, - - /** - * @see https://url.spec.whatwg.org/#port-state - */ - PORT, -}; - -/** - * Converts a parser state to its string name for debugging. - * @param s The state to convert. - * @return A string representation of the state. - */ -ada_warn_unused std::string to_string(ada::state s); - -} // namespace ada - -#endif // ADA_STATE_H -/* end file include/ada/state.h */ -/* begin file include/ada/unicode.h */ -/** - * @file unicode.h - * @brief Definitions for all unicode specific functions. - */ -#ifndef ADA_UNICODE_H -#define ADA_UNICODE_H - - -#include -#include -#include - -/** - * Unicode operations. These functions are not part of our public API and may - * change at any time. - * - * @private - * @namespace ada::unicode - * @brief Includes the definitions for unicode operations - */ -namespace ada::unicode { - -/** - * @private - * We receive a UTF-8 string representing a domain name. - * If the string is percent encoded, we apply percent decoding. - * - * Given a domain, we need to identify its labels. - * They are separated by label-separators: - * - * U+002E (.) FULL STOP - * U+FF0E FULLWIDTH FULL STOP - * U+3002 IDEOGRAPHIC FULL STOP - * U+FF61 HALFWIDTH IDEOGRAPHIC FULL STOP - * - * They are all mapped to U+002E. - * - * We process each label into a string that should not exceed 63 octets. - * If the string is already punycode (starts with "xn--"), then we must - * scan it to look for unallowed code points. - * Otherwise, if the string is not pure ASCII, we need to transcode it - * to punycode by following RFC 3454 which requires us to - * - Map characters (see section 3), - * - Normalize (see section 4), - * - Reject forbidden characters, - * - Check for right-to-left characters and if so, check all requirements (see - * section 6), - * - Optionally reject based on unassigned code points (section 7). - * - * The Unicode standard provides a table of code points with a mapping, a list - * of forbidden code points and so forth. This table is subject to change and - * will vary based on the implementation. For Unicode 15, the table is at - * https://www.unicode.org/Public/idna/15.0.0/IdnaMappingTable.txt - * If you use ICU, they parse this table and map it to code using a Python - * script. - * - * The resulting strings should not exceed 255 octets according to RFC 1035 - * section 2.3.4. ICU checks for label size and domain size, but these errors - * are ignored. - * - * @see https://url.spec.whatwg.org/#concept-domain-to-ascii - * - */ -bool to_ascii(std::optional& out, std::string_view plain, - size_t first_percent); - -/** - * @private - * Checks if the input has tab or newline characters. - * - * @attention The has_tabs_or_newline function is a bottleneck and it is simple - * enough that compilers like GCC can 'autovectorize it'. - */ -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept; - -/** - * @private - * Checks if the input is a forbidden host code point. - * @see https://url.spec.whatwg.org/#forbidden-host-code-point - */ -ada_really_inline constexpr bool is_forbidden_host_code_point(char c) noexcept; - -/** - * @private - * Checks if the input contains a forbidden domain code point. - * @see https://url.spec.whatwg.org/#forbidden-domain-code-point - */ -ada_really_inline constexpr bool contains_forbidden_domain_code_point( - const char* input, size_t length) noexcept; - -/** - * @private - * Checks if the input contains a forbidden domain code point in which case - * the first bit is set to 1. If the input contains an upper case ASCII letter, - * then the second bit is set to 1. - * @see https://url.spec.whatwg.org/#forbidden-domain-code-point - */ -ada_really_inline constexpr uint8_t -contains_forbidden_domain_code_point_or_upper(const char* input, - size_t length) noexcept; - -/** - * @private - * Checks if the input is a forbidden domain code point. - * @see https://url.spec.whatwg.org/#forbidden-domain-code-point - */ -ada_really_inline constexpr bool is_forbidden_domain_code_point( - char c) noexcept; - -/** - * @private - * Checks if the input is alphanumeric, '+', '-' or '.' - */ -ada_really_inline constexpr bool is_alnum_plus(char c) noexcept; - -/** - * @private - * @details An ASCII hex digit is an ASCII upper hex digit or ASCII lower hex - * digit. An ASCII upper hex digit is an ASCII digit or a code point in the - * range U+0041 (A) to U+0046 (F), inclusive. An ASCII lower hex digit is an - * ASCII digit or a code point in the range U+0061 (a) to U+0066 (f), inclusive. - */ -ada_really_inline constexpr bool is_ascii_hex_digit(char c) noexcept; - -/** - * @private - * An ASCII digit is a code point in the range U+0030 (0) to U+0039 (9), - * inclusive. - */ -ada_really_inline constexpr bool is_ascii_digit(char c) noexcept; - -/** - * @private - * @details If a char is between U+0000 and U+007F inclusive, then it's an ASCII - * character. - */ -ada_really_inline constexpr bool is_ascii(char32_t c) noexcept; - -/** - * @private - * Checks if the input is a C0 control or space character. - * - * @details A C0 control or space is a C0 control or U+0020 SPACE. - * A C0 control is a code point in the range U+0000 NULL to U+001F INFORMATION - * SEPARATOR ONE, inclusive. - */ -ada_really_inline constexpr bool is_c0_control_or_space(char c) noexcept; - -/** - * @private - * Checks if the input is a ASCII tab or newline character. - * - * @details An ASCII tab or newline is U+0009 TAB, U+000A LF, or U+000D CR. - */ -ada_really_inline constexpr bool is_ascii_tab_or_newline(char c) noexcept; - -/** - * @private - * @details A double-dot path segment must be ".." or an ASCII case-insensitive - * match for ".%2e", "%2e.", or "%2e%2e". - */ -ada_really_inline constexpr bool is_double_dot_path_segment( - std::string_view input) noexcept; - -/** - * @private - * @details A single-dot path segment must be "." or an ASCII case-insensitive - * match for "%2e". - */ -ada_really_inline constexpr bool is_single_dot_path_segment( - std::string_view input) noexcept; - -/** - * @private - * @details ipv4 character might contain 0-9 or a-f character ranges. - */ -ada_really_inline constexpr bool is_lowercase_hex(char c) noexcept; - -/** - * @private - * @details Convert hex to binary. Caller is responsible to ensure that - * the parameter is an hexadecimal digit (0-9, A-F, a-f). - */ -ada_really_inline unsigned constexpr convert_hex_to_binary(char c) noexcept; - -/** - * @private - * first_percent should be = input.find('%') - * - * @todo It would be faster as noexcept maybe, but it could be unsafe since. - * @author Node.js - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L245 - * @see https://encoding.spec.whatwg.org/#utf-8-decode-without-bom - */ -std::string percent_decode(std::string_view input, size_t first_percent); - -/** - * Decode an application/x-www-form-urlencoded component: map '+' to space, - * then percent-decode. Single allocation; no intermediate string. - * - * @param input A form-urlencoded component (key or value). - * @return The decoded string. - * @see https://url.spec.whatwg.org/#concept-urlencoded-parser - */ -std::string form_urlencoded_decode(std::string_view input); - -/** - * @private - * Returns a percent-encoding string whether percent encoding was needed or not. - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 - */ -std::string percent_encode(std::string_view input, - const uint8_t character_set[]); -/** - * @private - * Returns a percent-encoded string version of input, while starting the percent - * encoding at the provided index. - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 - */ -std::string percent_encode(std::string_view input, - const uint8_t character_set[], size_t index); -/** - * @private - * Returns true if percent encoding was needed, in which case, we store - * the percent-encoded content in 'out'. If the boolean 'append' is set to - * true, the content is appended to 'out'. - * If percent encoding is not needed, out is left unchanged. - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 - */ -template -bool percent_encode(std::string_view input, const uint8_t character_set[], - std::string& out); -/** - * @private - * Returns the index at which percent encoding should start, or (equivalently), - * the length of the prefix that does not require percent encoding. - */ -ada_really_inline size_t percent_encode_index(std::string_view input, - const uint8_t character_set[]); -/** - * @private - * Lowers the string in-place, assuming that the content is ASCII. - * Return true if the content was ASCII. - */ -constexpr bool to_lower_ascii(char* input, size_t length) noexcept; -} // namespace ada::unicode - -#endif // ADA_UNICODE_H -/* end file include/ada/unicode.h */ -/* begin file include/ada/url_base-inl.h */ -/** - * @file url_base-inl.h - * @brief Inline functions for url base - */ -#ifndef ADA_URL_BASE_INL_H -#define ADA_URL_BASE_INL_H - - -#include -#if ADA_REGULAR_VISUAL_STUDIO -#include -#endif // ADA_REGULAR_VISUAL_STUDIO - -namespace ada { - -[[nodiscard]] ada_really_inline constexpr bool url_base::is_special() - const noexcept { - return type != ada::scheme::NOT_SPECIAL; -} - -[[nodiscard]] inline uint16_t url_base::get_special_port() const noexcept { - return ada::scheme::get_special_port(type); -} - -[[nodiscard]] ada_really_inline uint16_t -url_base::scheme_default_port() const noexcept { - return scheme::get_special_port(type); -} - -} // namespace ada - -#endif // ADA_URL_BASE_INL_H -/* end file include/ada/url_base-inl.h */ -/* begin file include/ada/url-inl.h */ -/** - * @file url-inl.h - * @brief Definitions for the URL - */ -#ifndef ADA_URL_INL_H -#define ADA_URL_INL_H - - -#include -#include -#include -#include -#if ADA_REGULAR_VISUAL_STUDIO -#include -#endif // ADA_REGULAR_VISUAL_STUDIO - -namespace ada { -[[nodiscard]] ada_really_inline bool url::has_credentials() const noexcept { - return !username.empty() || !password.empty(); -} -[[nodiscard]] ada_really_inline bool url::has_port() const noexcept { - return port.has_value(); -} -[[nodiscard]] inline bool url::cannot_have_credentials_or_port() const { - return !host.has_value() || host->empty() || type == ada::scheme::type::FILE; -} -[[nodiscard]] inline bool url::has_empty_hostname() const noexcept { - if (!host.has_value()) { - return false; - } - return host->empty(); -} -[[nodiscard]] inline bool url::has_hostname() const noexcept { - return host.has_value(); -} -inline std::ostream& operator<<(std::ostream& out, const ada::url& u) { - return out << u.to_string(); -} - -[[nodiscard]] size_t url::get_pathname_length() const noexcept { - return path.size(); -} - -[[nodiscard]] constexpr std::string_view url::get_pathname() const noexcept { - return path; -} - -[[nodiscard]] ada_really_inline ada::url_components url::get_components() - const { - url_components out{}; - - // protocol ends with ':'. for example: "https:" - out.protocol_end = uint32_t(get_protocol().size()); - - // Trailing index is always the next character of the current one. - // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores) - size_t running_index = out.protocol_end; - - if (host.has_value()) { - // 2 characters for "//" and 1 character for starting index - out.host_start = out.protocol_end + 2; - - if (has_credentials()) { - out.username_end = uint32_t(out.host_start + username.size()); - - out.host_start += uint32_t(username.size()); - - if (!password.empty()) { - out.host_start += uint32_t(password.size() + 1); - } - - out.host_end = uint32_t(out.host_start + host->size()); - } else { - out.username_end = out.host_start; - - // Host does not start with "@" if it does not include credentials. - out.host_end = uint32_t(out.host_start + host->size()) - 1; - } - - running_index = out.host_end + 1; - } else { - // Update host start and end date to the same index, since it does not - // exist. - out.host_start = out.protocol_end; - out.host_end = out.host_start; - - if (!has_opaque_path && path.starts_with("//")) { - // If url's host is null, url does not have an opaque path, url's path's - // size is greater than 1, and url's path[0] is the empty string, then - // append U+002F (/) followed by U+002E (.) to output. - running_index = out.protocol_end + 2; - } else { - running_index = out.protocol_end; - } - } - - if (port.has_value()) { - out.port = *port; - running_index += helpers::fast_digit_count(*port) + 1; // Port omits ':' - } - - out.pathname_start = uint32_t(running_index); - - running_index += path.size(); - - if (query.has_value()) { - out.search_start = uint32_t(running_index); - running_index += get_search().size(); - if (get_search().empty()) { - running_index++; - } - } - - if (hash.has_value()) { - out.hash_start = uint32_t(running_index); - } - - return out; -} - -inline void url::update_base_hostname(std::string_view input) { host = input; } - -inline void url::update_unencoded_base_hash(std::string_view input) { - // We do the percent encoding - hash = unicode::percent_encode(input, - ada::character_sets::FRAGMENT_PERCENT_ENCODE); -} - -inline void url::update_base_search(std::string_view input, - const uint8_t query_percent_encode_set[]) { - query = ada::unicode::percent_encode(input, query_percent_encode_set); -} - -inline void url::update_base_search(std::optional&& input) { - query = std::move(input); -} - -inline void url::update_base_pathname(const std::string_view input) { - path = input; -} - -inline void url::update_base_username(const std::string_view input) { - username = input; -} - -inline void url::update_base_password(const std::string_view input) { - password = input; -} - -inline void url::update_base_port(std::optional input) { - port = input; -} - -constexpr void url::clear_pathname() { path.clear(); } - -constexpr void url::clear_search() { query = std::nullopt; } - -[[nodiscard]] constexpr bool url::has_hash() const noexcept { - return hash.has_value(); -} - -[[nodiscard]] constexpr bool url::has_search() const noexcept { - return query.has_value(); -} - -constexpr void url::set_protocol_as_file() { type = ada::scheme::type::FILE; } - -inline void url::set_scheme(std::string&& new_scheme) noexcept { - type = ada::scheme::get_scheme_type(new_scheme); - // We only move the 'scheme' if it is non-special. - if (!is_special()) { - non_special_scheme = std::move(new_scheme); - } -} - -constexpr void url::copy_scheme(ada::url&& u) { - non_special_scheme = u.non_special_scheme; - type = u.type; -} - -constexpr void url::copy_scheme(const ada::url& u) { - non_special_scheme = u.non_special_scheme; - type = u.type; -} - -[[nodiscard]] ada_really_inline std::string url::get_href() const { - if (is_special() && host.has_value() && username.empty() && - password.empty() && !port.has_value()) [[likely]] { - const std::string_view scheme = ada::scheme::details::is_special_list[type]; - const size_t host_size = host->size(); - const size_t path_size = path.size(); - const size_t query_size = query.has_value() ? query->size() : 0; - const size_t hash_size = hash.has_value() ? hash->size() : 0; - const size_t total = scheme.size() + 3 + host_size + path_size + - (query.has_value() ? query_size + 1 : 0) + - (hash.has_value() ? hash_size + 1 : 0); - std::string output(total, '\0'); - char* p = output.data(); - std::memcpy(p, scheme.data(), scheme.size()); - p += scheme.size(); - p[0] = ':'; - p[1] = '/'; - p[2] = '/'; - p += 3; - // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - std::memcpy(p, host->data(), host_size); - p += host_size; - std::memcpy(p, path.data(), path_size); - p += path_size; - if (query.has_value()) { - *p++ = '?'; - // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - std::memcpy(p, query->data(), query_size); - p += query_size; - } - if (hash.has_value()) { - *p++ = '#'; - // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - std::memcpy(p, hash->data(), hash_size); - } - return output; - } - - std::string output; - output.reserve(get_href_size()); - - if (is_special()) { - output.append(ada::scheme::details::is_special_list[type]); - output += ':'; - } else { - output.append(non_special_scheme); - output += ':'; - } - - if (host.has_value()) { - output += '/'; - output += '/'; - if (has_credentials()) { - output.append(username); - if (!password.empty()) { - output += ':'; - output.append(password); - } - output += '@'; - } - output.append(*host); - if (port.has_value()) { - output += ':'; - char port_buf[5]; - auto [ptr, ec] = std::to_chars(port_buf, port_buf + 5, *port); - (void)ec; - output.append(port_buf, static_cast(ptr - port_buf)); - } - } else if (!has_opaque_path && path.starts_with("//")) { - // If url's host is null, url does not have an opaque path, url's path's - // size is greater than 1, and url's path[0] is the empty string, then - // append U+002F (/) followed by U+002E (.) to output. - output += '/'; - output += '.'; - } - output.append(path); - if (query.has_value()) { - output += '?'; - output.append(*query); - } - if (hash.has_value()) { - output += '#'; - output.append(*hash); - } - return output; -} - -[[nodiscard]] inline size_t url::get_href_size() const noexcept { - size_t size = 0; - if (is_special()) { - size += ada::scheme::details::is_special_list[type].size() + 1; - } else { - size += non_special_scheme.size() + 1; - } - if (host.has_value()) { - size += host->size(); - size += 2; - if (has_credentials()) { - size += username.size(); - if (!password.empty()) { - size += 1 + password.size(); - } - size += 1; - } - if (port.has_value()) { - size += 1; - uint16_t p = *port; - size += (p >= 10000) ? 5 - : (p >= 1000) ? 4 - : (p >= 100) ? 3 - : (p >= 10) ? 2 - : 1; - } - } else if (!has_opaque_path && path.starts_with("//")) { - size += 2; - } - size += path.size(); - if (query.has_value()) { - size += 1 + query->size(); - } - if (hash.has_value()) { - size += 1 + hash->size(); - } - return size; -} - -ada_really_inline size_t url::parse_port(std::string_view view, - bool check_trailing_content) noexcept { - ada_log("parse_port('", view, "') ", view.size()); - if (!view.empty() && view[0] == '-') { - ada_log("parse_port: view[0] == '0' && view.size() > 1"); - is_valid = false; - return 0; - } - uint16_t parsed_port{}; - auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port); - if (r.ec == std::errc::result_out_of_range) { - ada_log("parse_port: r.ec == std::errc::result_out_of_range"); - is_valid = false; - return 0; - } - ada_log("parse_port: ", parsed_port); - const auto consumed = size_t(r.ptr - view.data()); - ada_log("parse_port: consumed ", consumed); - if (check_trailing_content) { - is_valid &= - (consumed == view.size() || view[consumed] == '/' || - view[consumed] == '?' || (is_special() && view[consumed] == '\\')); - } - ada_log("parse_port: is_valid = ", is_valid); - if (is_valid) { - // scheme_default_port can return 0, and we should allow 0 as a base port. - auto default_port = scheme_default_port(); - bool is_port_valid = (default_port == 0 && parsed_port == 0) || - (default_port != parsed_port); - port = (r.ec == std::errc() && is_port_valid) ? std::optional(parsed_port) - : std::nullopt; - } - return consumed; -} - -} // namespace ada - -#endif // ADA_URL_H -/* end file include/ada/url-inl.h */ -/* begin file include/ada/url_components-inl.h */ -/** - * @file url_components.h - * @brief Declaration for the URL Components - */ -#ifndef ADA_URL_COMPONENTS_INL_H -#define ADA_URL_COMPONENTS_INL_H - - -namespace ada { - -[[nodiscard]] constexpr bool url_components::check_offset_consistency() - const noexcept { - /** - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - */ - // These conditions can be made more strict. - if (protocol_end == url_components::omitted) { - return false; - } - uint32_t index = protocol_end; - - if (username_end == url_components::omitted) { - return false; - } - if (username_end < index) { - return false; - } - index = username_end; - - if (host_start == url_components::omitted) { - return false; - } - if (host_start < index) { - return false; - } - index = host_start; - - if (port != url_components::omitted) { - if (port > 0xffff) { - return false; - } - uint32_t port_length = helpers::fast_digit_count(port) + 1; - if (index + port_length < index) { - return false; - } - index += port_length; - } - - if (pathname_start == url_components::omitted) { - return false; - } - if (pathname_start < index) { - return false; - } - index = pathname_start; - - if (search_start != url_components::omitted) { - if (search_start < index) { - return false; - } - index = search_start; - } - - if (hash_start != url_components::omitted) { - if (hash_start < index) { - return false; - } - } - - return true; -} - -} // namespace ada -#endif -/* end file include/ada/url_components-inl.h */ -/* begin file include/ada/url_aggregator.h */ -/** - * @file url_aggregator.h - * @brief Declaration for the `ada::url_aggregator` class. - * - * This file contains the `ada::url_aggregator` struct which represents a parsed - * URL using a single buffer with component offsets. This is the default and - * most memory-efficient URL representation in Ada. - * - * @see url.h for an alternative representation using separate strings - */ -#ifndef ADA_URL_AGGREGATOR_H -#define ADA_URL_AGGREGATOR_H - -#include -#include -#include -#include - - -namespace ada { - -namespace parser {} - -/** - * @brief Memory-efficient URL representation using a single buffer. - * - * The `url_aggregator` stores the entire normalized URL in a single string - * buffer and tracks component boundaries using offsets. This design minimizes - * memory allocations and is ideal for read-mostly access patterns. - * - * Getter methods return `std::string_view` pointing into the internal buffer. - * These views are lightweight (no allocation) but become invalid if the - * url_aggregator is modified or destroyed. - * - * @warning Views returned by getters (e.g., `get_pathname()`) are invalidated - * when any setter is called. Do not use a getter's result as input to a - * setter on the same object without copying first. - * - * @note This is the default URL type returned by `ada::parse()`. - * - * @see url For an alternative using separate std::string instances - */ -struct url_aggregator : url_base { - url_aggregator() = default; - url_aggregator(const url_aggregator& u) = default; - url_aggregator(url_aggregator&& u) noexcept = default; - url_aggregator& operator=(url_aggregator&& u) noexcept = default; - url_aggregator& operator=(const url_aggregator& u) = default; - ~url_aggregator() override = default; - - /** - * The setter functions follow the steps defined in the URL Standard. - * - * The url_aggregator has a single buffer that contains the entire normalized - * URL. The various components are represented as offsets into that buffer. - * When you call get_pathname(), for example, you get a std::string_view that - * points into that buffer. If the url_aggregator is modified, the buffer may - * be reallocated, and the std::string_view you obtained earlier may become - * invalid. In particular, this implies that you cannot modify the URL using - * a setter function with a std::string_view that points into the - * url_aggregator E.g., the following is incorrect: - * url->set_hostname(url->get_pathname()). - * You must first copy the pathname to a separate string. - * std::string pathname(url->get_pathname()); - * url->set_hostname(pathname); - * - * The caller is responsible for ensuring that the url_aggregator is not - * modified while any std::string_view obtained from it is in use. - */ - bool set_href(std::string_view input); - bool set_host(std::string_view input); - bool set_hostname(std::string_view input); - bool set_protocol(std::string_view input); - bool set_username(std::string_view input); - bool set_password(std::string_view input); - bool set_port(std::string_view input); - bool set_pathname(std::string_view input); - void set_search(std::string_view input); - void set_hash(std::string_view input); - - /** - * Validates whether the hostname is a valid domain according to RFC 1034. - * @return `true` if the domain is valid, `false` otherwise. - */ - [[nodiscard]] bool has_valid_domain() const noexcept override; - - /** - * Returns the URL's origin (scheme + host + port for special URLs). - * @return A newly allocated string containing the serialized origin. - * @see https://url.spec.whatwg.org/#concept-url-origin - */ - [[nodiscard]] std::string get_origin() const override; - - /** - * Returns the full serialized URL (the href) as a string_view. - * Does not allocate memory. The returned view becomes invalid if this - * url_aggregator is modified or destroyed. - * @return A string_view into the internal buffer. - * @see https://url.spec.whatwg.org/#dom-url-href - */ - [[nodiscard]] constexpr std::string_view get_href() const noexcept - ada_lifetime_bound; - - /** - * Returns the byte length of the serialized URL without allocating a string. - * @return Size of the href in bytes. - */ - [[nodiscard]] constexpr size_t get_href_size() const noexcept; - - /** - * Returns the URL's username component. - * Does not allocate memory. The returned view becomes invalid if this - * url_aggregator is modified or destroyed. - * @return A string_view of the username. - * @see https://url.spec.whatwg.org/#dom-url-username - */ - [[nodiscard]] std::string_view get_username() const ada_lifetime_bound; - - /** - * Returns the URL's password component. - * Does not allocate memory. The returned view becomes invalid if this - * url_aggregator is modified or destroyed. - * @return A string_view of the password. - * @see https://url.spec.whatwg.org/#dom-url-password - */ - [[nodiscard]] std::string_view get_password() const ada_lifetime_bound; - - /** - * Returns the URL's port as a string (e.g., "8080"). - * Does not allocate memory. Returns empty view if no port is set. - * The returned view becomes invalid if this url_aggregator is modified. - * @return A string_view of the port. - * @see https://url.spec.whatwg.org/#dom-url-port - */ - [[nodiscard]] std::string_view get_port() const ada_lifetime_bound; - - /** - * Returns the URL's fragment prefixed with '#' (e.g., "#section"). - * Does not allocate memory. Returns empty view if no fragment is set. - * The returned view becomes invalid if this url_aggregator is modified. - * @return A string_view of the hash. - * @see https://url.spec.whatwg.org/#dom-url-hash - */ - [[nodiscard]] std::string_view get_hash() const ada_lifetime_bound; - - /** - * Returns the URL's host and port (e.g., "example.com:8080"). - * Does not allocate memory. Returns empty view if no host is set. - * The returned view becomes invalid if this url_aggregator is modified. - * @return A string_view of host:port. - * @see https://url.spec.whatwg.org/#dom-url-host - */ - [[nodiscard]] std::string_view get_host() const ada_lifetime_bound; - - /** - * Returns the URL's hostname (without port). - * Does not allocate memory. Returns empty view if no host is set. - * The returned view becomes invalid if this url_aggregator is modified. - * @return A string_view of the hostname. - * @see https://url.spec.whatwg.org/#dom-url-hostname - */ - [[nodiscard]] std::string_view get_hostname() const ada_lifetime_bound; - - /** - * Returns the URL's path component. - * Does not allocate memory. The returned view becomes invalid if this - * url_aggregator is modified or destroyed. - * @return A string_view of the pathname. - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - [[nodiscard]] constexpr std::string_view get_pathname() const - ada_lifetime_bound; - - /** - * Returns the byte length of the pathname without creating a string. - * @return Size of the pathname in bytes. - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - [[nodiscard]] ada_really_inline uint32_t get_pathname_length() const noexcept; - - /** - * Returns the URL's query string prefixed with '?' (e.g., "?foo=bar"). - * Does not allocate memory. Returns empty view if no query is set. - * The returned view becomes invalid if this url_aggregator is modified. - * @return A string_view of the search/query. - * @see https://url.spec.whatwg.org/#dom-url-search - */ - [[nodiscard]] std::string_view get_search() const ada_lifetime_bound; - - /** - * Returns the URL's scheme followed by a colon (e.g., "https:"). - * Does not allocate memory. The returned view becomes invalid if this - * url_aggregator is modified or destroyed. - * @return A string_view of the protocol. - * @see https://url.spec.whatwg.org/#dom-url-protocol - */ - [[nodiscard]] std::string_view get_protocol() const ada_lifetime_bound; - - /** - * Checks if the URL has credentials (non-empty username or password). - * @return `true` if username or password is non-empty, `false` otherwise. - */ - [[nodiscard]] ada_really_inline constexpr bool has_credentials() - const noexcept; - - /** - * Returns the URL component offsets for efficient serialization. - * - * The components represent byte offsets into the serialized URL: - * ``` - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - * ``` - * @return A constant reference to the url_components struct. - * @see https://github.com/servo/rust-url - */ - [[nodiscard]] ada_really_inline const url_components& get_components() - const noexcept; - - /** - * Returns a JSON string representation of this URL for debugging. - * @return A JSON-formatted string with all URL components. - */ - [[nodiscard]] std::string to_string() const override; - - /** - * Returns a visual diagram showing component boundaries in the URL. - * Useful for debugging and understanding URL structure. - * @return A multi-line string diagram. - */ - [[nodiscard]] std::string to_diagram() const; - - /** - * Validates internal consistency of component offsets (for debugging). - * @return `true` if offsets are consistent, `false` if corrupted. - */ - [[nodiscard]] constexpr bool validate() const noexcept; - - /** - * Checks if the URL has an empty hostname (host is set but empty string). - * @return `true` if host exists but is empty, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_empty_hostname() const noexcept; - - /** - * Checks if the URL has a hostname (including empty hostnames). - * @return `true` if host is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_hostname() const noexcept; - - /** - * Checks if the URL has a non-empty username. - * @return `true` if username is non-empty, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_non_empty_username() const noexcept; - - /** - * Checks if the URL has a non-empty password. - * @return `true` if password is non-empty, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_non_empty_password() const noexcept; - - /** - * Checks if the URL has a non-default port explicitly specified. - * @return `true` if a port is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_port() const noexcept; - - /** - * Checks if the URL has a password component (may be empty). - * @return `true` if password is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_password() const noexcept; - - /** - * Checks if the URL has a fragment/hash component. - * @return `true` if hash is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_hash() const noexcept override; - - /** - * Checks if the URL has a query/search component. - * @return `true` if query is present, `false` otherwise. - */ - [[nodiscard]] constexpr bool has_search() const noexcept override; - - /** - * Removes the port from the URL. - */ - inline void clear_port(); - - /** - * Removes the hash/fragment from the URL. - */ - inline void clear_hash(); - - /** - * Removes the query/search string from the URL. - */ - inline void clear_search() override; - - private: - // helper methods - friend void helpers::strip_trailing_spaces_from_opaque_path( - url_aggregator& url); - // parse_url methods - friend url_aggregator parser::parse_url( - std::string_view, const url_aggregator*); - - friend url_aggregator parser::parse_url_impl( - std::string_view, const url_aggregator*); - friend url_aggregator parser::parse_url_impl( - std::string_view, const url_aggregator*); - template - friend bool parser::try_parse_simple_absolute(std::string_view, result_type&); - -#if ADA_INCLUDE_URL_PATTERN - // url_pattern methods - template - friend tl::expected, errors> - parse_url_pattern_impl( - std::variant&& input, - const std::string_view* base_url, const url_pattern_options* options); -#endif // ADA_INCLUDE_URL_PATTERN - - // components is declared before buffer so that the offset fields land - // close to url_base in memory, improving cache locality for getter calls. - // Note: exact cache-line placement is implementation- and platform-dependent. - url_components components{}; - std::string buffer{}; - - /** - * Returns true if neither the search, nor the hash nor the pathname - * have been set. - * @return true if the buffer is ready to receive the path. - */ - [[nodiscard]] ada_really_inline bool is_at_path() const noexcept; - - inline void add_authority_slashes_if_needed(); - - /** - * To optimize performance, you may indicate how much memory to allocate - * within this instance. - */ - constexpr void reserve(uint32_t capacity); - - ada_really_inline size_t parse_port(std::string_view view, - bool check_trailing_content) override; - - ada_really_inline size_t parse_port(std::string_view view) override { - return this->parse_port(view, false); - } - - /** - * Return true on success. The 'in_place' parameter indicates whether the - * the string_view input is pointing in the buffer. When in_place is false, - * we must nearly always update the buffer. - * @see https://url.spec.whatwg.org/#concept-ipv4-parser - */ - [[nodiscard]] bool parse_ipv4(std::string_view input, bool in_place); - - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-ipv6-parser - */ - [[nodiscard]] bool parse_ipv6(std::string_view input); - - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-opaque-host-parser - */ - [[nodiscard]] bool parse_opaque_host(std::string_view input); - - ada_really_inline void parse_path(std::string_view input); - - /** - * A URL cannot have a username/password/port if its host is null or the empty - * string, or its scheme is "file". - */ - [[nodiscard]] constexpr bool cannot_have_credentials_or_port() const; - - template - bool set_host_or_hostname(std::string_view input); - - ada_really_inline bool parse_host(std::string_view input); - - inline void update_base_authority(std::string_view base_buffer, - const url_components& base); - inline void update_unencoded_base_hash(std::string_view input); - inline void update_base_hostname(std::string_view input); - inline void update_base_search(std::string_view input); - inline void update_base_search(std::string_view input, - const uint8_t* query_percent_encode_set); - inline void update_base_pathname(std::string_view input); - inline void update_base_username(std::string_view input); - inline void append_base_username(std::string_view input); - inline void update_base_password(std::string_view input); - inline void append_base_password(std::string_view input); - inline void update_base_port(uint32_t input); - inline void append_base_pathname(std::string_view input); - [[nodiscard]] inline uint32_t retrieve_base_port() const; - constexpr void clear_hostname(); - constexpr void clear_password(); - constexpr void clear_pathname() override; - [[nodiscard]] constexpr bool has_dash_dot() const noexcept; - void delete_dash_dot(); - inline void consume_prepared_path(std::string_view input); - template - [[nodiscard]] ada_really_inline bool parse_scheme_with_colon( - std::string_view input); - ada_really_inline uint32_t replace_and_resize(uint32_t start, uint32_t end, - std::string_view input); - [[nodiscard]] constexpr bool has_authority() const noexcept; - constexpr void set_protocol_as_file(); - inline void set_scheme(std::string_view new_scheme); - /** - * Fast function to set the scheme from a view with a colon in the - * buffer, does not change type. - */ - inline void set_scheme_from_view_with_colon( - std::string_view new_scheme_with_colon); - inline void copy_scheme(const url_aggregator& u); - - inline void update_host_to_base_host(const std::string_view input); - -}; // url_aggregator - -inline std::ostream& operator<<(std::ostream& out, const url& u); -} // namespace ada - -#endif -/* end file include/ada/url_aggregator.h */ -/* begin file include/ada/url_aggregator-inl.h */ -/** - * @file url_aggregator-inl.h - * @brief Inline functions for url aggregator - */ -#ifndef ADA_URL_AGGREGATOR_INL_H -#define ADA_URL_AGGREGATOR_INL_H - -/* begin file include/ada/unicode-inl.h */ -/** - * @file unicode-inl.h - * @brief Definitions for unicode operations. - */ -#ifndef ADA_UNICODE_INL_H -#define ADA_UNICODE_INL_H - -/** - * Unicode operations. These functions are not part of our public API and may - * change at any time. - * - * private - * @namespace ada::unicode - * @brief Includes the declarations for unicode operations - */ -namespace ada::unicode { -ada_really_inline size_t percent_encode_index(const std::string_view input, - const uint8_t character_set[]) { - const char* data = input.data(); - const size_t size = input.size(); - - // Process 8 bytes at a time using unrolled loop - size_t i = 0; - for (; i + 8 <= size; i += 8) { - unsigned char chunk[8]; - std::memcpy(&chunk, data + i, - 8); // entices compiler to unconditionally process 8 characters - - // Check 8 characters at once - for (size_t j = 0; j < 8; j++) { - if (character_sets::bit_at(character_set, chunk[j])) { - return i + j; - } - } - } - - // Handle remaining bytes - for (; i < size; i++) { - if (character_sets::bit_at(character_set, data[i])) { - return i; - } - } - - return size; -} -} // namespace ada::unicode - -#endif // ADA_UNICODE_INL_H -/* end file include/ada/unicode-inl.h */ - -#include -#include -#include - -namespace ada { - -inline void url_aggregator::update_base_authority( - std::string_view base_buffer, const ada::url_components& base) { - std::string_view input = base_buffer.substr( - base.protocol_end, base.host_start - base.protocol_end); - ada_log("url_aggregator::update_base_authority ", input); - - bool input_starts_with_dash = input.starts_with("//"); - uint32_t diff = components.host_start - components.protocol_end; - - buffer.erase(components.protocol_end, - components.host_start - components.protocol_end); - components.username_end = components.protocol_end; - - if (input_starts_with_dash) { - input.remove_prefix(2); - diff += 2; // add "//" - buffer.insert(components.protocol_end, "//"); - components.username_end += 2; - } - - size_t password_delimiter = input.find(':'); - - // Check if input contains both username and password by checking the - // delimiter: ":" A typical input that contains authority would be "user:pass" - if (password_delimiter != std::string_view::npos) { - // Insert both username and password - std::string_view username = input.substr(0, password_delimiter); - std::string_view password = input.substr(password_delimiter + 1); - - buffer.insert(components.protocol_end + diff, username); - diff += uint32_t(username.size()); - buffer.insert(components.protocol_end + diff, ":"); - components.username_end = components.protocol_end + diff; - buffer.insert(components.protocol_end + diff + 1, password); - diff += uint32_t(password.size()) + 1; - } else if (!input.empty()) { - // Insert only username - buffer.insert(components.protocol_end + diff, input); - components.username_end = - components.protocol_end + diff + uint32_t(input.size()); - diff += uint32_t(input.size()); - } - - components.host_start += diff; - - if (buffer.size() > base.host_start && buffer[base.host_start] != '@') { - buffer.insert(components.host_start, "@"); - diff++; - } - components.host_end += diff; - components.pathname_start += diff; - if (components.search_start != url_components::omitted) { - components.search_start += diff; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += diff; - } -} - -inline void url_aggregator::update_unencoded_base_hash(std::string_view input) { - ada_log("url_aggregator::update_unencoded_base_hash ", input, " [", - input.size(), " bytes], buffer is '", buffer, "' [", buffer.size(), - " bytes] components.hash_start = ", components.hash_start); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (components.hash_start != url_components::omitted) { - buffer.resize(components.hash_start); - } - components.hash_start = uint32_t(buffer.size()); - buffer += "#"; - bool encoding_required = unicode::percent_encode( - input, ada::character_sets::FRAGMENT_PERCENT_ENCODE, buffer); - // When encoding_required is false, then buffer is left unchanged, and percent - // encoding was not deemed required. - if (!encoding_required) { - buffer.append(input); - } - ada_log("url_aggregator::update_unencoded_base_hash final buffer is '", - buffer, "' [", buffer.size(), " bytes]"); - ADA_ASSERT_TRUE(validate()); -} - -ada_really_inline uint32_t url_aggregator::replace_and_resize( - uint32_t start, uint32_t end, std::string_view input) { - uint32_t current_length = end - start; - uint32_t input_size = uint32_t(input.size()); - uint32_t new_difference = input_size - current_length; - - if (current_length == 0) { - buffer.insert(start, input); - } else if (input_size == current_length) { - buffer.replace(start, input_size, input); - } else if (input_size < current_length) { - buffer.erase(start, current_length - input_size); - buffer.replace(start, input_size, input); - } else { - buffer.replace(start, current_length, input.substr(0, current_length)); - buffer.insert(start + current_length, input.substr(current_length)); - } - - return new_difference; -} - -inline void url_aggregator::update_base_hostname(const std::string_view input) { - ada_log("url_aggregator::update_base_hostname ", input, " [", input.size(), - " bytes], buffer is '", buffer, "' [", buffer.size(), " bytes]"); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - - // This next line is required for when parsing a URL like `foo://` - add_authority_slashes_if_needed(); - - bool has_credentials = components.protocol_end + 2 < components.host_start; - uint32_t new_difference = - replace_and_resize(components.host_start, components.host_end, input); - - if (has_credentials) { - buffer.insert(components.host_start, "@"); - new_difference++; - } - components.host_end += new_difference; - components.pathname_start += new_difference; - if (components.search_start != url_components::omitted) { - components.search_start += new_difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += new_difference; - } - ADA_ASSERT_TRUE(validate()); -} - -[[nodiscard]] ada_really_inline uint32_t -url_aggregator::get_pathname_length() const noexcept { - ada_log("url_aggregator::get_pathname_length"); - uint32_t ending_index = uint32_t(buffer.size()); - if (components.search_start != url_components::omitted) { - ending_index = components.search_start; - } else if (components.hash_start != url_components::omitted) { - ending_index = components.hash_start; - } - return ending_index - components.pathname_start; -} - -[[nodiscard]] ada_really_inline bool url_aggregator::is_at_path() - const noexcept { - return buffer.size() == components.pathname_start; -} - -inline void url_aggregator::update_base_search(std::string_view input) { - ada_log("url_aggregator::update_base_search ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (input.empty()) { - clear_search(); - return; - } - - if (input[0] == '?') { - input.remove_prefix(1); - } - - if (components.hash_start == url_components::omitted) { - if (components.search_start == url_components::omitted) { - components.search_start = uint32_t(buffer.size()); - buffer += "?"; - } else { - buffer.resize(components.search_start + 1); - } - - buffer.append(input); - } else { - if (components.search_start == url_components::omitted) { - components.search_start = components.hash_start; - } else { - buffer.erase(components.search_start, - components.hash_start - components.search_start); - components.hash_start = components.search_start; - } - - buffer.insert(components.search_start, "?"); - buffer.insert(components.search_start + 1, input); - components.hash_start += uint32_t(input.size() + 1); // Do not forget `?` - } - - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::update_base_search( - std::string_view input, const uint8_t query_percent_encode_set[]) { - ada_log("url_aggregator::update_base_search ", input, - " with encoding parameter ", to_string(), "\n", to_diagram()); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - - if (components.hash_start == url_components::omitted) { - if (components.search_start == url_components::omitted) { - components.search_start = uint32_t(buffer.size()); - buffer += "?"; - } else { - buffer.resize(components.search_start + 1); - } - - bool encoding_required = - unicode::percent_encode(input, query_percent_encode_set, buffer); - // When encoding_required is false, then buffer is left unchanged, and - // percent encoding was not deemed required. - if (!encoding_required) { - buffer.append(input); - } - } else { - if (components.search_start == url_components::omitted) { - components.search_start = components.hash_start; - } else { - buffer.erase(components.search_start, - components.hash_start - components.search_start); - components.hash_start = components.search_start; - } - - buffer.insert(components.search_start, "?"); - size_t idx = - ada::unicode::percent_encode_index(input, query_percent_encode_set); - if (idx == input.size()) { - buffer.insert(components.search_start + 1, input); - components.hash_start += uint32_t(input.size() + 1); // Do not forget `?` - } else { - buffer.insert(components.search_start + 1, input, 0, idx); - input.remove_prefix(idx); - // We only create a temporary string if we need percent encoding and - // we attempt to create as small a temporary string as we can. - std::string encoded = - ada::unicode::percent_encode(input, query_percent_encode_set); - buffer.insert(components.search_start + idx + 1, encoded); - components.hash_start += - uint32_t(encoded.size() + idx + 1); // Do not forget `?` - } - } - - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::update_base_pathname(const std::string_view input) { - ada_log("url_aggregator::update_base_pathname '", input, "' [", input.size(), - " bytes] \n", to_diagram()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - ADA_ASSERT_TRUE(validate()); - - const bool begins_with_dashdash = input.starts_with("//"); - if (!begins_with_dashdash && has_dash_dot()) { - // We must delete the ./ - delete_dash_dot(); - } - - if (begins_with_dashdash && !has_opaque_path && !has_authority() && - !has_dash_dot()) { - // If url's host is null, url does not have an opaque path, url's path's - // size is greater than 1, then append U+002F (/) followed by U+002E (.) to - // output. - buffer.insert(components.pathname_start, "/."); - components.pathname_start += 2; - if (components.search_start != url_components::omitted) { - components.search_start += 2; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += 2; - } - } - - uint32_t difference = replace_and_resize( - components.pathname_start, - components.pathname_start + get_pathname_length(), input); - if (components.search_start != url_components::omitted) { - components.search_start += difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += difference; - } - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::append_base_pathname(const std::string_view input) { - ada_log("url_aggregator::append_base_pathname ", input, " ", to_string(), - "\n", to_diagram()); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); -#if ADA_DEVELOPMENT_CHECKS - // computing the expected password. - std::string path_expected(get_pathname()); - path_expected.append(input); -#endif // ADA_DEVELOPMENT_CHECKS - uint32_t ending_index = uint32_t(buffer.size()); - if (components.search_start != url_components::omitted) { - ending_index = components.search_start; - } else if (components.hash_start != url_components::omitted) { - ending_index = components.hash_start; - } - buffer.insert(ending_index, input); - - if (components.search_start != url_components::omitted) { - components.search_start += uint32_t(input.size()); - } - if (components.hash_start != url_components::omitted) { - components.hash_start += uint32_t(input.size()); - } -#if ADA_DEVELOPMENT_CHECKS - std::string path_after = std::string(get_pathname()); - ADA_ASSERT_EQUAL( - path_expected, path_after, - "append_base_pathname problem after inserting " + std::string(input)); -#endif // ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::update_base_username(const std::string_view input) { - ada_log("url_aggregator::update_base_username '", input, "' ", to_string(), - "\n", to_diagram()); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - - add_authority_slashes_if_needed(); - - bool has_password = has_non_empty_password(); - bool host_starts_with_at = buffer.size() > components.host_start && - buffer[components.host_start] == '@'; - uint32_t diff = replace_and_resize(components.protocol_end + 2, - components.username_end, input); - - components.username_end += diff; - components.host_start += diff; - - if (!input.empty() && !host_starts_with_at) { - buffer.insert(components.host_start, "@"); - diff++; - } else if (input.empty() && host_starts_with_at && !has_password) { - // Input is empty, there is no password, and we need to remove "@" from - // hostname - buffer.erase(components.host_start, 1); - diff--; - } - - components.host_end += diff; - components.pathname_start += diff; - if (components.search_start != url_components::omitted) { - components.search_start += diff; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += diff; - } - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::append_base_username(const std::string_view input) { - ada_log("url_aggregator::append_base_username ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); -#if ADA_DEVELOPMENT_CHECKS - // computing the expected password. - std::string username_expected(get_username()); - username_expected.append(input); -#endif // ADA_DEVELOPMENT_CHECKS - add_authority_slashes_if_needed(); - - // If input is empty, do nothing. - if (input.empty()) { - return; - } - - uint32_t difference = uint32_t(input.size()); - buffer.insert(components.username_end, input); - components.username_end += difference; - components.host_start += difference; - - if (buffer[components.host_start] != '@' && - components.host_start != components.host_end) { - buffer.insert(components.host_start, "@"); - difference++; - } - - components.host_end += difference; - components.pathname_start += difference; - if (components.search_start != url_components::omitted) { - components.search_start += difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += difference; - } -#if ADA_DEVELOPMENT_CHECKS - std::string username_after(get_username()); - ADA_ASSERT_EQUAL( - username_expected, username_after, - "append_base_username problem after inserting " + std::string(input)); -#endif // ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_TRUE(validate()); -} - -constexpr void url_aggregator::clear_password() { - ada_log("url_aggregator::clear_password ", to_string()); - ADA_ASSERT_TRUE(validate()); - if (!has_password()) { - return; - } - - uint32_t diff = components.host_start - components.username_end; - buffer.erase(components.username_end, diff); - components.host_start -= diff; - components.host_end -= diff; - components.pathname_start -= diff; - if (components.search_start != url_components::omitted) { - components.search_start -= diff; - } - if (components.hash_start != url_components::omitted) { - components.hash_start -= diff; - } -} - -inline void url_aggregator::update_base_password(const std::string_view input) { - ada_log("url_aggregator::update_base_password ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - - add_authority_slashes_if_needed(); - - // TODO: Optimization opportunity. Merge the following removal functions. - if (input.empty()) { - clear_password(); - - // Remove username too, if it is empty. - if (!has_non_empty_username()) { - update_base_username(""); - } - - return; - } - - bool password_exists = has_password(); - uint32_t difference = uint32_t(input.size()); - - if (password_exists) { - uint32_t current_length = - components.host_start - components.username_end - 1; - buffer.erase(components.username_end + 1, current_length); - difference -= current_length; - } else { - buffer.insert(components.username_end, ":"); - difference++; - } - - buffer.insert(components.username_end + 1, input); - components.host_start += difference; - - // The following line is required to add "@" to hostname. When updating - // password if hostname does not start with "@", it is "update_base_password"s - // responsibility to set it. - if (buffer[components.host_start] != '@') { - buffer.insert(components.host_start, "@"); - difference++; - } - - components.host_end += difference; - components.pathname_start += difference; - if (components.search_start != url_components::omitted) { - components.search_start += difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += difference; - } - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::append_base_password(const std::string_view input) { - ada_log("url_aggregator::append_base_password ", input, " ", to_string(), - "\n", to_diagram()); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); -#if ADA_DEVELOPMENT_CHECKS - // computing the expected password. - std::string password_expected = std::string(get_password()); - password_expected.append(input); -#endif // ADA_DEVELOPMENT_CHECKS - add_authority_slashes_if_needed(); - - // If input is empty, do nothing. - if (input.empty()) { - return; - } - - uint32_t difference = uint32_t(input.size()); - if (has_password()) { - buffer.insert(components.host_start, input); - } else { - difference++; // Increment for ":" - buffer.insert(components.username_end, ":"); - buffer.insert(components.username_end + 1, input); - } - components.host_start += difference; - - // The following line is required to add "@" to hostname. When updating - // password if hostname does not start with "@", it is "append_base_password"s - // responsibility to set it. - if (buffer[components.host_start] != '@') { - buffer.insert(components.host_start, "@"); - difference++; - } - - components.host_end += difference; - components.pathname_start += difference; - if (components.search_start != url_components::omitted) { - components.search_start += difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += difference; - } -#if ADA_DEVELOPMENT_CHECKS - std::string password_after(get_password()); - ADA_ASSERT_EQUAL( - password_expected, password_after, - "append_base_password problem after inserting " + std::string(input)); -#endif // ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::update_base_port(uint32_t input) { - ada_log("url_aggregator::update_base_port"); - ADA_ASSERT_TRUE(validate()); - if (input == url_components::omitted) { - clear_port(); - return; - } - // calling std::to_string(input.value()) is unfortunate given that the port - // value is probably already available as a string. - std::string value = helpers::concat(":", std::to_string(input)); - uint32_t difference = uint32_t(value.size()); - - if (components.port != url_components::omitted) { - difference -= components.pathname_start - components.host_end; - buffer.erase(components.host_end, - components.pathname_start - components.host_end); - } - - buffer.insert(components.host_end, value); - components.pathname_start += difference; - if (components.search_start != url_components::omitted) { - components.search_start += difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += difference; - } - components.port = input; - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::clear_port() { - ada_log("url_aggregator::clear_port"); - ADA_ASSERT_TRUE(validate()); - if (components.port == url_components::omitted) { - return; - } - uint32_t length = components.pathname_start - components.host_end; - buffer.erase(components.host_end, length); - components.pathname_start -= length; - if (components.search_start != url_components::omitted) { - components.search_start -= length; - } - if (components.hash_start != url_components::omitted) { - components.hash_start -= length; - } - components.port = url_components::omitted; - ADA_ASSERT_TRUE(validate()); -} - -[[nodiscard]] inline uint32_t url_aggregator::retrieve_base_port() const { - ada_log("url_aggregator::retrieve_base_port"); - return components.port; -} - -inline void url_aggregator::clear_search() { - ada_log("url_aggregator::clear_search"); - ADA_ASSERT_TRUE(validate()); - if (components.search_start == url_components::omitted) { - return; - } - - if (components.hash_start == url_components::omitted) { - buffer.resize(components.search_start); - } else { - buffer.erase(components.search_start, - components.hash_start - components.search_start); - components.hash_start = components.search_start; - } - - components.search_start = url_components::omitted; - -#if ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_EQUAL(get_search(), "", - "search should have been cleared on buffer=" + buffer + - " with " + components.to_string() + "\n" + to_diagram()); -#endif - ADA_ASSERT_TRUE(validate()); -} - -inline void url_aggregator::clear_hash() { - ada_log("url_aggregator::clear_hash"); - ADA_ASSERT_TRUE(validate()); - if (components.hash_start == url_components::omitted) { - return; - } - buffer.resize(components.hash_start); - components.hash_start = url_components::omitted; - -#if ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_EQUAL(get_hash(), "", - "hash should have been cleared on buffer=" + buffer + - " with " + components.to_string() + "\n" + to_diagram()); -#endif - ADA_ASSERT_TRUE(validate()); -} - -constexpr void url_aggregator::clear_pathname() { - ada_log("url_aggregator::clear_pathname"); - ADA_ASSERT_TRUE(validate()); - uint32_t ending_index = uint32_t(buffer.size()); - if (components.search_start != url_components::omitted) { - ending_index = components.search_start; - } else if (components.hash_start != url_components::omitted) { - ending_index = components.hash_start; - } - uint32_t pathname_length = ending_index - components.pathname_start; - buffer.erase(components.pathname_start, pathname_length); - uint32_t difference = pathname_length; - if (components.pathname_start == components.host_end + 2 && - buffer[components.host_end] == '/' && - buffer[components.host_end + 1] == '.') { - components.pathname_start -= 2; - buffer.erase(components.host_end, 2); - difference += 2; - } - if (components.search_start != url_components::omitted) { - components.search_start -= difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start -= difference; - } - ada_log("url_aggregator::clear_pathname completed, running checks..."); -#if ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_EQUAL(get_pathname(), "", - "pathname should have been cleared on buffer=" + buffer + - " with " + components.to_string() + "\n" + to_diagram()); -#endif - ADA_ASSERT_TRUE(validate()); - ada_log("url_aggregator::clear_pathname completed, running checks... ok"); -} - -constexpr void url_aggregator::clear_hostname() { - ada_log("url_aggregator::clear_hostname"); - ADA_ASSERT_TRUE(validate()); - if (!has_authority()) { - return; - } - ADA_ASSERT_TRUE(has_authority()); - - uint32_t hostname_length = components.host_end - components.host_start; - uint32_t start = components.host_start; - - // If hostname starts with "@", we should not remove that character. - if (hostname_length > 0 && buffer[start] == '@') { - start++; - hostname_length--; - } - buffer.erase(start, hostname_length); - components.host_end = start; - components.pathname_start -= hostname_length; - if (components.search_start != url_components::omitted) { - components.search_start -= hostname_length; - } - if (components.hash_start != url_components::omitted) { - components.hash_start -= hostname_length; - } -#if ADA_DEVELOPMENT_CHECKS - ADA_ASSERT_EQUAL(get_hostname(), "", - "hostname should have been cleared on buffer=" + buffer + - " with " + components.to_string() + "\n" + to_diagram()); -#endif - ADA_ASSERT_TRUE(has_authority()); - ADA_ASSERT_EQUAL(has_empty_hostname(), true, - "hostname should have been cleared on buffer=" + buffer + - " with " + components.to_string() + "\n" + to_diagram()); - ADA_ASSERT_TRUE(validate()); -} - -[[nodiscard]] constexpr bool url_aggregator::has_hash() const noexcept { - ada_log("url_aggregator::has_hash"); - return components.hash_start != url_components::omitted; -} - -[[nodiscard]] constexpr bool url_aggregator::has_search() const noexcept { - ada_log("url_aggregator::has_search"); - return components.search_start != url_components::omitted; -} - -constexpr bool url_aggregator::has_credentials() const noexcept { - ada_log("url_aggregator::has_credentials"); - return has_non_empty_username() || has_non_empty_password(); -} - -constexpr bool url_aggregator::cannot_have_credentials_or_port() const { - ada_log("url_aggregator::cannot_have_credentials_or_port"); - return type == ada::scheme::type::FILE || - components.host_start == components.host_end; -} - -[[nodiscard]] ada_really_inline const ada::url_components& -url_aggregator::get_components() const noexcept { - return components; -} - -[[nodiscard]] constexpr bool ada::url_aggregator::has_authority() - const noexcept { - ada_log("url_aggregator::has_authority"); - // Performance: instead of doing this potentially expensive check, we could - // have a boolean in the struct. - return components.protocol_end + 2 <= components.host_start && - buffer[components.protocol_end] == '/' && - buffer[components.protocol_end + 1] == '/'; -} - -inline void ada::url_aggregator::add_authority_slashes_if_needed() { - ada_log("url_aggregator::add_authority_slashes_if_needed"); - ADA_ASSERT_TRUE(validate()); - // Protocol setter will insert `http:` to the URL. It is up to hostname setter - // to insert - // `//` initially to the buffer, since it depends on the hostname existence. - if (has_authority()) { - return; - } - // Performance: the common case is components.protocol_end == buffer.size() - // Optimization opportunity: in many cases, the "//" is part of the input and - // the insert could be fused with another insert. - buffer.insert(components.protocol_end, "//"); - components.username_end += 2; - components.host_start += 2; - components.host_end += 2; - components.pathname_start += 2; - if (components.search_start != url_components::omitted) { - components.search_start += 2; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += 2; - } - ADA_ASSERT_TRUE(validate()); -} - -constexpr void ada::url_aggregator::reserve(uint32_t capacity) { - buffer.reserve(capacity); -} - -constexpr bool url_aggregator::has_non_empty_username() const noexcept { - ada_log("url_aggregator::has_non_empty_username"); - return components.protocol_end + 2 < components.username_end; -} - -constexpr bool url_aggregator::has_non_empty_password() const noexcept { - ada_log("url_aggregator::has_non_empty_password"); - return components.host_start > components.username_end; -} - -constexpr bool url_aggregator::has_password() const noexcept { - ada_log("url_aggregator::has_password"); - // This function does not care about the length of the password - return components.host_start > components.username_end && - buffer[components.username_end] == ':'; -} - -constexpr bool url_aggregator::has_empty_hostname() const noexcept { - if (!has_hostname()) { - return false; - } - if (components.host_start == components.host_end) { - return true; - } - if (components.host_end > components.host_start + 1) { - return false; - } - return components.username_end != components.host_start; -} - -constexpr bool url_aggregator::has_hostname() const noexcept { - return has_authority(); -} - -constexpr bool url_aggregator::has_port() const noexcept { - ada_log("url_aggregator::has_port"); - // A URL cannot have a username/password/port if its host is null or the empty - // string, or its scheme is "file". - return has_hostname() && components.pathname_start != components.host_end; -} - -[[nodiscard]] constexpr bool url_aggregator::has_dash_dot() const noexcept { - // If url's host is null, url does not have an opaque path, url's path's size - // is greater than 1, and url's path[0] is the empty string, then append - // U+002F (/) followed by U+002E (.) to output. - ada_log("url_aggregator::has_dash_dot"); -#if ADA_DEVELOPMENT_CHECKS - // If pathname_start and host_end are exactly two characters apart, then we - // either have a one-digit port such as http://test.com:5?param=1 or else we - // have a /.: sequence such as "non-spec:/.//". We test that this is the case. - if (components.pathname_start == components.host_end + 2) { - ADA_ASSERT_TRUE((buffer[components.host_end] == '/' && - buffer[components.host_end + 1] == '.') || - (buffer[components.host_end] == ':' && - checkers::is_digit(buffer[components.host_end + 1]))); - } - if (components.pathname_start == components.host_end + 2 && - buffer[components.host_end] == '/' && - buffer[components.host_end + 1] == '.') { - ADA_ASSERT_TRUE(components.pathname_start + 1 < buffer.size()); - ADA_ASSERT_TRUE(buffer[components.pathname_start] == '/'); - ADA_ASSERT_TRUE(buffer[components.pathname_start + 1] == '/'); - } -#endif - // Performance: it should be uncommon for components.pathname_start == - // components.host_end + 2 to be true. So we put this check first in the - // sequence. Most times, we do not have an opaque path. Checking for '/.' is - // more expensive, but should be uncommon. - return components.pathname_start == components.host_end + 2 && - !has_opaque_path && buffer[components.host_end] == '/' && - buffer[components.host_end + 1] == '.'; -} - -[[nodiscard]] constexpr std::string_view url_aggregator::get_href() - const noexcept ada_lifetime_bound { - ada_log("url_aggregator::get_href"); - return buffer; -} - -[[nodiscard]] constexpr size_t url_aggregator::get_href_size() const noexcept { - return buffer.size(); -} - -ada_really_inline size_t -url_aggregator::parse_port(std::string_view view, bool check_trailing_content) { - ada_log("url_aggregator::parse_port('", view, "') ", view.size()); - if (!view.empty() && view[0] == '-') { - ada_log("parse_port: view[0] == '0' && view.size() > 1"); - is_valid = false; - return 0; - } - uint16_t parsed_port{}; - auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port); - if (r.ec == std::errc::result_out_of_range) { - ada_log("parse_port: r.ec == std::errc::result_out_of_range"); - is_valid = false; - return 0; - } - ada_log("parse_port: ", parsed_port); - const size_t consumed = size_t(r.ptr - view.data()); - ada_log("parse_port: consumed ", consumed); - if (check_trailing_content) { - is_valid &= - (consumed == view.size() || view[consumed] == '/' || - view[consumed] == '?' || (is_special() && view[consumed] == '\\')); - } - ada_log("parse_port: is_valid = ", is_valid); - if (is_valid) { - ada_log("parse_port", r.ec == std::errc()); - // scheme_default_port can return 0, and we should allow 0 as a base port. - auto default_port = scheme_default_port(); - bool is_port_valid = (default_port == 0 && parsed_port == 0) || - (default_port != parsed_port); - if (r.ec == std::errc() && is_port_valid) { - update_base_port(parsed_port); - } else { - clear_port(); - } - } - return consumed; -} - -constexpr void url_aggregator::set_protocol_as_file() { - ada_log("url_aggregator::set_protocol_as_file "); - ADA_ASSERT_TRUE(validate()); - type = ada::scheme::type::FILE; - // next line could overflow but unsigned arithmetic has well-defined - // overflows. - uint32_t new_difference = 5 - components.protocol_end; - - if (buffer.empty()) { - buffer.append("file:"); - } else { - buffer.erase(0, components.protocol_end); - buffer.insert(0, "file:"); - } - components.protocol_end = 5; - - // Update the rest of the components. - components.username_end += new_difference; - components.host_start += new_difference; - components.host_end += new_difference; - components.pathname_start += new_difference; - if (components.search_start != url_components::omitted) { - components.search_start += new_difference; - } - if (components.hash_start != url_components::omitted) { - components.hash_start += new_difference; - } - ADA_ASSERT_TRUE(validate()); -} - -[[nodiscard]] constexpr bool url_aggregator::validate() const noexcept { - if (!is_valid) { - return true; - } - if (!components.check_offset_consistency()) { - ada_log("url_aggregator::validate inconsistent components \n", - to_diagram()); - return false; - } - // We have a credible components struct, but let us investivate more - // carefully: - /** - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - */ - if (components.protocol_end == url_components::omitted) { - ada_log("url_aggregator::validate omitted protocol_end \n", to_diagram()); - return false; - } - if (components.username_end == url_components::omitted) { - ada_log("url_aggregator::validate omitted username_end \n", to_diagram()); - return false; - } - if (components.host_start == url_components::omitted) { - ada_log("url_aggregator::validate omitted host_start \n", to_diagram()); - return false; - } - if (components.host_end == url_components::omitted) { - ada_log("url_aggregator::validate omitted host_end \n", to_diagram()); - return false; - } - if (components.pathname_start == url_components::omitted) { - ada_log("url_aggregator::validate omitted pathname_start \n", to_diagram()); - return false; - } - - if (components.protocol_end > buffer.size()) { - ada_log("url_aggregator::validate protocol_end overflow \n", to_diagram()); - return false; - } - if (components.username_end > buffer.size()) { - ada_log("url_aggregator::validate username_end overflow \n", to_diagram()); - return false; - } - if (components.host_start > buffer.size()) { - ada_log("url_aggregator::validate host_start overflow \n", to_diagram()); - return false; - } - if (components.host_end > buffer.size()) { - ada_log("url_aggregator::validate host_end overflow \n", to_diagram()); - return false; - } - if (components.pathname_start > buffer.size()) { - ada_log("url_aggregator::validate pathname_start overflow \n", - to_diagram()); - return false; - } - - if (components.protocol_end > 0) { - if (buffer[components.protocol_end - 1] != ':') { - ada_log( - "url_aggregator::validate missing : at the end of the protocol \n", - to_diagram()); - return false; - } - } - - if (components.username_end != buffer.size() && - components.username_end > components.protocol_end + 2) { - if (buffer[components.username_end] != ':' && - buffer[components.username_end] != '@') { - ada_log( - "url_aggregator::validate missing : or @ at the end of the username " - "\n", - to_diagram()); - return false; - } - } - - if (components.host_start != buffer.size()) { - if (components.host_start > components.username_end) { - if (buffer[components.host_start] != '@') { - ada_log( - "url_aggregator::validate missing @ at the end of the password \n", - to_diagram()); - return false; - } - } else if (components.host_start == components.username_end && - components.host_end > components.host_start) { - if (components.host_start == components.protocol_end + 2) { - if (buffer[components.protocol_end] != '/' || - buffer[components.protocol_end + 1] != '/') { - ada_log( - "url_aggregator::validate missing // between protocol and host " - "\n", - to_diagram()); - return false; - } - } else { - if (components.host_start > components.protocol_end && - buffer[components.host_start] != '@') { - ada_log( - "url_aggregator::validate missing @ at the end of the username " - "\n", - to_diagram()); - return false; - } - } - } else { - if (components.host_end != components.host_start) { - ada_log("url_aggregator::validate expected omitted host \n", - to_diagram()); - return false; - } - } - } - if (components.host_end != buffer.size() && - components.pathname_start > components.host_end) { - if (components.pathname_start == components.host_end + 2 && - buffer[components.host_end] == '/' && - buffer[components.host_end + 1] == '.') { - if (components.pathname_start + 1 >= buffer.size() || - buffer[components.pathname_start] != '/' || - buffer[components.pathname_start + 1] != '/') { - ada_log( - "url_aggregator::validate expected the path to begin with // \n", - to_diagram()); - return false; - } - } else if (buffer[components.host_end] != ':') { - ada_log("url_aggregator::validate missing : at the port \n", - to_diagram()); - return false; - } - } - if (components.pathname_start != buffer.size() && - components.pathname_start < components.search_start && - components.pathname_start < components.hash_start && !has_opaque_path) { - if (buffer[components.pathname_start] != '/') { - ada_log("url_aggregator::validate missing / at the path \n", - to_diagram()); - return false; - } - } - if (components.search_start != url_components::omitted) { - if (buffer[components.search_start] != '?') { - ada_log("url_aggregator::validate missing ? at the search \n", - to_diagram()); - return false; - } - } - if (components.hash_start != url_components::omitted) { - if (buffer[components.hash_start] != '#') { - ada_log("url_aggregator::validate missing # at the hash \n", - to_diagram()); - return false; - } - } - - return true; -} - -[[nodiscard]] constexpr std::string_view url_aggregator::get_pathname() const - ada_lifetime_bound { - ada_log("url_aggregator::get_pathname pathname_start = ", - components.pathname_start, " buffer.size() = ", buffer.size(), - " components.search_start = ", components.search_start, - " components.hash_start = ", components.hash_start); - auto ending_index = uint32_t(buffer.size()); - if (components.search_start != url_components::omitted) { - ending_index = components.search_start; - } else if (components.hash_start != url_components::omitted) { - ending_index = components.hash_start; - } - return helpers::substring(buffer, components.pathname_start, ending_index); -} - -inline std::ostream& operator<<(std::ostream& out, - const ada::url_aggregator& u) { - return out << u.to_string(); -} - -void url_aggregator::update_host_to_base_host(const std::string_view input) { - ada_log("url_aggregator::update_host_to_base_host ", input); - ADA_ASSERT_TRUE(validate()); - ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (type != ada::scheme::type::FILE) { - // Let host be the result of host parsing host_view with url is not special. - if (input.empty() && !is_special()) { - if (has_hostname()) { - clear_hostname(); - } else if (has_dash_dot()) { - add_authority_slashes_if_needed(); - delete_dash_dot(); - } - return; - } - } - update_base_hostname(input); - ADA_ASSERT_TRUE(validate()); - return; -} -} // namespace ada - -#endif // ADA_URL_AGGREGATOR_INL_H -/* end file include/ada/url_aggregator-inl.h */ -/* begin file include/ada/url_search_params.h */ -/** - * @file url_search_params.h - * @brief URL query string parameter manipulation. - * - * This file provides the `url_search_params` class for parsing, manipulating, - * and serializing URL query strings. It implements the URLSearchParams API - * from the WHATWG URL Standard. - * - * @see https://url.spec.whatwg.org/#interface-urlsearchparams - */ -#ifndef ADA_URL_SEARCH_PARAMS_H -#define ADA_URL_SEARCH_PARAMS_H - -#include -#include -#include -#include - -namespace ada { - -/** - * @brief Iterator types for url_search_params iteration. - */ -enum class url_search_params_iter_type { - KEYS, /**< Iterate over parameter keys only */ - VALUES, /**< Iterate over parameter values only */ - ENTRIES, /**< Iterate over key-value pairs */ -}; - -template -struct url_search_params_iter; - -/** Type alias for a key-value pair of string views. */ -typedef std::pair key_value_view_pair; - -/** Iterator over search parameter keys. */ -using url_search_params_keys_iter = - url_search_params_iter; -/** Iterator over search parameter values. */ -using url_search_params_values_iter = - url_search_params_iter; -/** Iterator over search parameter key-value pairs. */ -using url_search_params_entries_iter = - url_search_params_iter; - -/** - * @brief Class for parsing and manipulating URL query strings. - * - * The `url_search_params` class provides methods to parse, modify, and - * serialize URL query parameters (the part after '?' in a URL). It handles - * percent-encoding and decoding automatically. - * - * All string inputs must be valid UTF-8. The caller is responsible for - * ensuring UTF-8 validity. - * - * Construction and `reset` refuse query strings longer than - * `get_max_input_length()` (the object is left empty). Individual `append` / - * `set` calls are not length-capped. - * - * @see https://url.spec.whatwg.org/#interface-urlsearchparams - */ -struct url_search_params { - url_search_params() = default; - - /** - * Constructs url_search_params by parsing a query string. - * @param input A query string (with or without leading '?'). Must be UTF-8. - * If longer than `get_max_input_length()`, the object stays empty. - */ - explicit url_search_params(const std::string_view input) { - initialize(input); - } - - url_search_params(const url_search_params& u) = default; - url_search_params(url_search_params&& u) noexcept = default; - url_search_params& operator=(url_search_params&& u) noexcept = default; - url_search_params& operator=(const url_search_params& u) = default; - ~url_search_params() = default; - - /** - * Returns the number of key-value pairs. - * @return The total count of parameters. - */ - [[nodiscard]] inline size_t size() const noexcept; - - /** - * Appends a new key-value pair to the parameter list. - * @param key The parameter name (must be valid UTF-8). - * @param value The parameter value (must be valid UTF-8). - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-append - */ - inline void append(std::string_view key, std::string_view value); - - /** - * Removes all pairs with the given key. - * @param key The parameter name to remove. - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-delete - */ - inline void remove(std::string_view key); - - /** - * Removes all pairs with the given key and value. - * @param key The parameter name. - * @param value The parameter value to match. - */ - inline void remove(std::string_view key, std::string_view value); - - /** - * Returns the value of the first pair with the given key. - * @param key The parameter name to search for. - * @return The value if found, or std::nullopt if not present. - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-get - */ - inline std::optional get(std::string_view key); - - /** - * Returns all values for pairs with the given key. - * @param key The parameter name to search for. - * @return A vector of all matching values (may be empty). - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall - */ - inline std::vector get_all(std::string_view key); - - /** - * Checks if any pair has the given key. - * @param key The parameter name to search for. - * @return `true` if at least one pair has this key. - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has - */ - inline bool has(std::string_view key) noexcept; - - /** - * Checks if any pair matches the given key and value. - * @param key The parameter name to search for. - * @param value The parameter value to match. - * @return `true` if a matching pair exists. - */ - inline bool has(std::string_view key, std::string_view value) noexcept; - - /** - * Sets a parameter value, replacing any existing pairs with the same key. - * @param key The parameter name (must be valid UTF-8). - * @param value The parameter value (must be valid UTF-8). - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set - */ - inline void set(std::string_view key, std::string_view value); - - /** - * Sorts all key-value pairs by their keys using code unit comparison. - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort - */ - inline void sort(); - - /** - * Serializes the parameters to a query string (without leading '?'). - * @return The percent-encoded query string. - * @see https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior - */ - inline std::string to_string() const; - - /** - * Returns an iterator over all parameter keys. - * Keys may repeat if there are duplicate parameters. - * @return An iterator yielding string_view keys. - * @note The iterator is invalidated if this object is modified. - */ - inline url_search_params_keys_iter get_keys(); - - /** - * Returns an iterator over all parameter values. - * @return An iterator yielding string_view values. - * @note The iterator is invalidated if this object is modified. - */ - inline url_search_params_values_iter get_values(); - - /** - * Returns an iterator over all key-value pairs. - * @return An iterator yielding key-value pair views. - * @note The iterator is invalidated if this object is modified. - */ - inline url_search_params_entries_iter get_entries(); - - /** - * C++ style conventional iterator support. const only because we - * do not really want the params to be modified via the iterator. - */ - inline auto begin() const { return params.begin(); } - inline auto end() const { return params.end(); } - inline auto front() const { return params.front(); } - inline auto back() const { return params.back(); } - inline auto operator[](size_t index) const { return params[index]; } - - /** - * @private - * Used to reset the search params to a new input. - * Used primarily for C API. - * @param input - */ - void reset(std::string_view input); - - private: - typedef std::pair key_value_pair; - std::vector params{}; - - /** - * The init parameter must be valid UTF-8. - * @see https://url.spec.whatwg.org/#concept-urlencoded-parser - */ - void initialize(std::string_view init); - - template - friend struct url_search_params_iter; -}; // url_search_params - -/** - * @brief JavaScript-style iterator for url_search_params. - * - * Provides a `next()` method that returns successive values until exhausted. - * This matches the iterator pattern used in the Web Platform. - * - * @tparam T The type of value returned by the iterator. - * @tparam Type The type of iteration (KEYS, VALUES, or ENTRIES). - * - * @see https://webidl.spec.whatwg.org/#idl-iterable - */ -template -struct url_search_params_iter { - inline url_search_params_iter() : params(EMPTY) {} - url_search_params_iter(const url_search_params_iter& u) = default; - url_search_params_iter(url_search_params_iter&& u) noexcept = default; - url_search_params_iter& operator=(url_search_params_iter&& u) noexcept = - default; - url_search_params_iter& operator=(const url_search_params_iter& u) = default; - ~url_search_params_iter() = default; - - /** - * Returns the next value in the iteration sequence. - * @return The next value, or std::nullopt if iteration is complete. - */ - inline std::optional next(); - - /** - * Checks if more values are available. - * @return `true` if `next()` will return a value, `false` if exhausted. - */ - inline bool has_next() const; - - private: - static url_search_params EMPTY; - inline url_search_params_iter(url_search_params& params_) : params(params_) {} - - url_search_params& params; - size_t pos = 0; - - friend struct url_search_params; -}; - -} // namespace ada -#endif -/* end file include/ada/url_search_params.h */ -/* begin file include/ada/url_search_params-inl.h */ -/** - * @file url_search_params-inl.h - * @brief Inline declarations for the URL Search Params - */ -#ifndef ADA_URL_SEARCH_PARAMS_INL_H -#define ADA_URL_SEARCH_PARAMS_INL_H - - -#include -#include -#include -#include -#include -#include -#include - -namespace ada { - -// Declared in implementation.h; used here as a DoS bound on untrusted query -// strings (ada.h includes both headers). -uint32_t get_max_input_length(); - -// A default, empty url_search_params for use with empty iterators. -template -url_search_params url_search_params_iter::EMPTY; - -inline void url_search_params::reset(std::string_view input) { - params.clear(); - initialize(input); -} - -inline void url_search_params::initialize(std::string_view input) { - if (!input.empty() && input.front() == '?') { - input.remove_prefix(1); - } - if (input.empty()) { - return; - } - // Refuse overlong query strings (same process-wide cap as URL parsing). - if (input.size() > get_max_input_length()) { - return; - } - - params.reserve(size_t(std::count(input.begin(), input.end(), '&')) + 1); - - auto process_key_value = [&](const std::string_view current) { - const auto equal = current.find('='); - if (equal == std::string_view::npos) { - params.emplace_back(unicode::form_urlencoded_decode(current), ""); - } else { - params.emplace_back( - unicode::form_urlencoded_decode(current.substr(0, equal)), - unicode::form_urlencoded_decode(current.substr(equal + 1))); - } - }; - - while (!input.empty()) { - const auto ampersand_index = input.find('&'); - - if (ampersand_index == std::string_view::npos) { - if (!input.empty()) { - process_key_value(input); - } - break; - } else if (ampersand_index != 0) { - process_key_value(input.substr(0, ampersand_index)); - } - - input.remove_prefix(ampersand_index + 1); - } -} - -inline void url_search_params::append(const std::string_view key, - const std::string_view value) { - params.emplace_back(key, value); -} - -inline size_t url_search_params::size() const noexcept { return params.size(); } - -inline std::optional url_search_params::get( - const std::string_view key) { - auto entry = std::ranges::find_if( - params, [&key](const auto& param) { return param.first == key; }); - - if (entry == params.end()) { - return std::nullopt; - } - - return entry->second; -} - -inline std::vector url_search_params::get_all( - const std::string_view key) { - std::vector out{}; - - for (auto& param : params) { - if (param.first == key) { - out.emplace_back(param.second); - } - } - - return out; -} - -inline bool url_search_params::has(const std::string_view key) noexcept { - auto entry = std::ranges::find_if( - params, [&key](const auto& param) { return param.first == key; }); - return entry != params.end(); -} - -inline bool url_search_params::has(std::string_view key, - std::string_view value) noexcept { - auto entry = std::ranges::find_if(params, [&key, &value](const auto& param) { - return param.first == key && param.second == value; - }); - return entry != params.end(); -} - -inline std::string url_search_params::to_string() const { - auto character_set = ada::character_sets::WWW_FORM_URLENCODED_PERCENT_ENCODE; - std::string out{}; - for (size_t i = 0; i < params.size(); i++) { - auto key = ada::unicode::percent_encode(params[i].first, character_set); - auto value = ada::unicode::percent_encode(params[i].second, character_set); - - // Performance optimization: Move this inside percent_encode. - std::ranges::replace(key, ' ', '+'); - std::ranges::replace(value, ' ', '+'); - - if (i != 0) { - out += "&"; - } - out.append(key); - out += "="; - out.append(value); - } - return out; -} - -inline void url_search_params::set(const std::string_view key, - const std::string_view value) { - const auto find = [&key](const auto& param) { return param.first == key; }; - - auto it = std::ranges::find_if(params, find); - - if (it == params.end()) { - params.emplace_back(key, value); - } else { - it->second = value; - params.erase(std::remove_if(std::next(it), params.end(), find), - params.end()); - } -} - -inline void url_search_params::remove(const std::string_view key) { - std::erase_if(params, - [&key](const auto& param) { return param.first == key; }); -} - -inline void url_search_params::remove(const std::string_view key, - const std::string_view value) { - std::erase_if(params, [&key, &value](const auto& param) { - return param.first == key && param.second == value; - }); -} - -inline void url_search_params::sort() { - // Keys are expected to be valid UTF-8, but percent_decode can produce - // arbitrary byte sequences. Handle truncated/invalid sequences gracefully. - std::ranges::stable_sort(params, [](const key_value_pair& lhs, - const key_value_pair& rhs) { - size_t i = 0, j = 0; - uint32_t low_surrogate1 = 0, low_surrogate2 = 0; - while ((i < lhs.first.size() || low_surrogate1 != 0) && - (j < rhs.first.size() || low_surrogate2 != 0)) { - uint32_t codePoint1 = 0, codePoint2 = 0; - - if (low_surrogate1 != 0) { - codePoint1 = low_surrogate1; - low_surrogate1 = 0; - } else { - uint8_t c1 = uint8_t(lhs.first[i]); - if (c1 > 0x7F && c1 <= 0xDF && i + 1 < lhs.first.size()) { - codePoint1 = ((c1 & 0x1F) << 6) | (uint8_t(lhs.first[i + 1]) & 0x3F); - i += 2; - } else if (c1 > 0xDF && c1 <= 0xEF && i + 2 < lhs.first.size()) { - codePoint1 = ((c1 & 0x0F) << 12) | - ((uint8_t(lhs.first[i + 1]) & 0x3F) << 6) | - (uint8_t(lhs.first[i + 2]) & 0x3F); - i += 3; - } else if (c1 > 0xEF && c1 <= 0xF7 && i + 3 < lhs.first.size()) { - codePoint1 = ((c1 & 0x07) << 18) | - ((uint8_t(lhs.first[i + 1]) & 0x3F) << 12) | - ((uint8_t(lhs.first[i + 2]) & 0x3F) << 6) | - (uint8_t(lhs.first[i + 3]) & 0x3F); - i += 4; - - codePoint1 -= 0x10000; - uint16_t high_surrogate = uint16_t(0xD800 + (codePoint1 >> 10)); - low_surrogate1 = uint16_t(0xDC00 + (codePoint1 & 0x3FF)); - codePoint1 = high_surrogate; - } else { - // ASCII (c1 <= 0x7F) or truncated/invalid UTF-8: treat as raw byte - codePoint1 = c1; - i++; - } - } - - if (low_surrogate2 != 0) { - codePoint2 = low_surrogate2; - low_surrogate2 = 0; - } else { - uint8_t c2 = uint8_t(rhs.first[j]); - if (c2 > 0x7F && c2 <= 0xDF && j + 1 < rhs.first.size()) { - codePoint2 = ((c2 & 0x1F) << 6) | (uint8_t(rhs.first[j + 1]) & 0x3F); - j += 2; - } else if (c2 > 0xDF && c2 <= 0xEF && j + 2 < rhs.first.size()) { - codePoint2 = ((c2 & 0x0F) << 12) | - ((uint8_t(rhs.first[j + 1]) & 0x3F) << 6) | - (uint8_t(rhs.first[j + 2]) & 0x3F); - j += 3; - } else if (c2 > 0xEF && c2 <= 0xF7 && j + 3 < rhs.first.size()) { - codePoint2 = ((c2 & 0x07) << 18) | - ((uint8_t(rhs.first[j + 1]) & 0x3F) << 12) | - ((uint8_t(rhs.first[j + 2]) & 0x3F) << 6) | - (uint8_t(rhs.first[j + 3]) & 0x3F); - j += 4; - codePoint2 -= 0x10000; - uint16_t high_surrogate = uint16_t(0xD800 + (codePoint2 >> 10)); - low_surrogate2 = uint16_t(0xDC00 + (codePoint2 & 0x3FF)); - codePoint2 = high_surrogate; - } else { - // ASCII (c2 <= 0x7F) or truncated/invalid UTF-8: treat as raw byte - codePoint2 = c2; - j++; - } - } - - if (codePoint1 != codePoint2) { - return (codePoint1 < codePoint2); - } - } - return (j < rhs.first.size() || low_surrogate2 != 0); - }); -} - -inline url_search_params_keys_iter url_search_params::get_keys() { - return url_search_params_keys_iter(*this); -} - -/** - * @see https://url.spec.whatwg.org/#interface-urlsearchparams - */ -inline url_search_params_values_iter url_search_params::get_values() { - return url_search_params_values_iter(*this); -} - -/** - * @see https://url.spec.whatwg.org/#interface-urlsearchparams - */ -inline url_search_params_entries_iter url_search_params::get_entries() { - return url_search_params_entries_iter(*this); -} - -template -inline bool url_search_params_iter::has_next() const { - return pos < params.params.size(); -} - -template <> -inline std::optional url_search_params_keys_iter::next() { - if (!has_next()) { - return std::nullopt; - } - return params.params[pos++].first; -} - -template <> -inline std::optional url_search_params_values_iter::next() { - if (!has_next()) { - return std::nullopt; - } - return params.params[pos++].second; -} - -template <> -inline std::optional -url_search_params_entries_iter::next() { - if (!has_next()) { - return std::nullopt; - } - return params.params[pos++]; -} - -} // namespace ada - -#endif // ADA_URL_SEARCH_PARAMS_INL_H -/* end file include/ada/url_search_params-inl.h */ - -/* begin file include/ada/url_pattern-inl.h */ -/** - * @file url_pattern-inl.h - * @brief Declaration for the URLPattern inline functions. - */ -#ifndef ADA_URL_PATTERN_INL_H -#define ADA_URL_PATTERN_INL_H - - -#include -#include -#include - -#if ADA_INCLUDE_URL_PATTERN -namespace ada { - -inline bool url_pattern_init::operator==(const url_pattern_init& other) const { - return protocol == other.protocol && username == other.username && - password == other.password && hostname == other.hostname && - port == other.port && search == other.search && hash == other.hash && - pathname == other.pathname; -} - -inline bool url_pattern_component_result::operator==( - const url_pattern_component_result& other) const { - return input == other.input && groups == other.groups; -} - -template -url_pattern_component_result -url_pattern_component::create_component_match_result( - std::string&& input, - std::vector>&& exec_result) { - // Let result be a new URLPatternComponentResult. - // Set result["input"] to input. - // Let groups be a record. - auto result = - url_pattern_component_result{.input = std::move(input), .groups = {}}; - - // We explicitly start iterating from 0 even though the spec - // says we should start from 1. This case is handled by the - // std_regex_provider which removes the full match from index 0. - // Use min() to guard against potential mismatches between - // exec_result size and group_name_list size. - const size_t size = std::min(exec_result.size(), group_name_list.size()); - result.groups.reserve(size); - for (size_t index = 0; index < size; index++) { - result.groups.emplace(group_name_list[index], - std::move(exec_result[index])); - } - return result; -} - -template -std::string_view url_pattern::get_protocol() const - ada_lifetime_bound { - // Return this's associated URL pattern's protocol component's pattern string. - return protocol_component.pattern; -} -template -std::string_view url_pattern::get_username() const - ada_lifetime_bound { - // Return this's associated URL pattern's username component's pattern string. - return username_component.pattern; -} -template -std::string_view url_pattern::get_password() const - ada_lifetime_bound { - // Return this's associated URL pattern's password component's pattern string. - return password_component.pattern; -} -template -std::string_view url_pattern::get_hostname() const - ada_lifetime_bound { - // Return this's associated URL pattern's hostname component's pattern string. - return hostname_component.pattern; -} -template -std::string_view url_pattern::get_port() const - ada_lifetime_bound { - // Return this's associated URL pattern's port component's pattern string. - return port_component.pattern; -} -template -std::string_view url_pattern::get_pathname() const - ada_lifetime_bound { - // Return this's associated URL pattern's pathname component's pattern string. - return pathname_component.pattern; -} -template -std::string_view url_pattern::get_search() const - ada_lifetime_bound { - // Return this's associated URL pattern's search component's pattern string. - return search_component.pattern; -} -template -std::string_view url_pattern::get_hash() const - ada_lifetime_bound { - // Return this's associated URL pattern's hash component's pattern string. - return hash_component.pattern; -} -template -bool url_pattern::ignore_case() const { - return ignore_case_; -} -template -bool url_pattern::has_regexp_groups() const { - // If this's associated URL pattern's has regexp groups, then return true. - return protocol_component.has_regexp_groups || - username_component.has_regexp_groups || - password_component.has_regexp_groups || - hostname_component.has_regexp_groups || - port_component.has_regexp_groups || - pathname_component.has_regexp_groups || - search_component.has_regexp_groups || hash_component.has_regexp_groups; -} - -inline bool url_pattern_part::is_regexp() const noexcept { - return type == url_pattern_part_type::REGEXP; -} - -inline std::string_view url_pattern_compile_component_options::get_delimiter() - const { - if (delimiter) { - return {&delimiter.value(), 1}; - } - return {}; -} - -inline std::string_view url_pattern_compile_component_options::get_prefix() - const { - if (prefix) { - return {&prefix.value(), 1}; - } - return {}; -} - -template -template -tl::expected, errors> -url_pattern_component::compile( - std::string_view input, F& encoding_callback, - url_pattern_compile_component_options& options) { - ada_log("url_pattern_component::compile input: ", input); - // Let part list be the result of running parse a pattern string given input, - // options, and encoding callback. - auto part_list = url_pattern_helpers::parse_pattern_string(input, options, - encoding_callback); - - if (!part_list) { - ada_log("parse_pattern_string failed"); - return tl::unexpected(part_list.error()); - } - - // Detect pattern type early to potentially skip expensive regex compilation - const auto has_regexp = [](const auto& part) { return part.is_regexp(); }; - const bool has_regexp_groups = std::ranges::any_of(*part_list, has_regexp); - - url_pattern_component_type component_type = - url_pattern_component_type::REGEXP; - std::string exact_match_value{}; - - if (part_list->empty()) { - component_type = url_pattern_component_type::EMPTY; - } else if (part_list->size() == 1) { - const auto& part = (*part_list)[0]; - if (part.type == url_pattern_part_type::FIXED_TEXT && - part.modifier == url_pattern_part_modifier::none && - !options.ignore_case) { - component_type = url_pattern_component_type::EXACT_MATCH; - exact_match_value = part.value; - } else if (part.type == url_pattern_part_type::FULL_WILDCARD && - part.modifier == url_pattern_part_modifier::none && - part.prefix.empty() && part.suffix.empty()) { - component_type = url_pattern_component_type::FULL_WILDCARD; - } - } - - // For simple patterns, skip regex generation and compilation entirely - if (component_type != url_pattern_component_type::REGEXP) { - auto pattern_string = - url_pattern_helpers::generate_pattern_string(*part_list, options); - // For FULL_WILDCARD, we need the group name from - // generate_regular_expression - std::vector name_list; - if (component_type == url_pattern_component_type::FULL_WILDCARD && - !part_list->empty()) { - name_list.push_back((*part_list)[0].name); - } - return url_pattern_component( - std::move(pattern_string), typename regex_provider::regex_type{}, - std::move(name_list), has_regexp_groups, component_type, - std::move(exact_match_value)); - } - - // Generate regex for complex patterns - auto [regular_expression_string, name_list] = - url_pattern_helpers::generate_regular_expression_and_name_list(*part_list, - options); - auto pattern_string = - url_pattern_helpers::generate_pattern_string(*part_list, options); - - std::optional regular_expression = - regex_provider::create_instance(regular_expression_string, - options.ignore_case); - if (!regular_expression) { - return tl::unexpected(errors::type_error); - } - - return url_pattern_component( - std::move(pattern_string), std::move(*regular_expression), - std::move(name_list), has_regexp_groups, component_type, - std::move(exact_match_value)); -} - -template -bool url_pattern_component::fast_test( - std::string_view input) const noexcept { - // Fast path for simple patterns - avoid regex evaluation - // Using if-else for better branch prediction on common cases - if (type == url_pattern_component_type::FULL_WILDCARD) { - return true; - } - if (type == url_pattern_component_type::EXACT_MATCH) { - return input == exact_match_value; - } - if (type == url_pattern_component_type::EMPTY) { - return input.empty(); - } - // type == REGEXP - return regex_provider::regex_match(input, regexp); -} - -template -std::optional>> -url_pattern_component::fast_match( - std::string_view input) const { - // Handle each type directly without redundant checks - if (type == url_pattern_component_type::FULL_WILDCARD) { - // FULL_WILDCARD always matches - capture the input (even if empty) - // If there's no group name, return empty groups - if (group_name_list.empty()) { - return std::vector>{}; - } - // Capture the matched input (including empty strings) - return std::vector>{std::string(input)}; - } - if (type == url_pattern_component_type::EXACT_MATCH) { - if (input == exact_match_value) { - return std::vector>{}; - } - return std::nullopt; - } - if (type == url_pattern_component_type::EMPTY) { - if (input.empty()) { - return std::vector>{}; - } - return std::nullopt; - } - // type == REGEXP - use regex - return regex_provider::regex_search(input, regexp); -} - -template -result> url_pattern::exec( - const url_pattern_input& input, const std::string_view* base_url) { - // Return the result of match given this's associated URL pattern, input, and - // baseURL if given. - return match(input, base_url); -} - -template -bool url_pattern::test_components( - std::string_view protocol, std::string_view username, - std::string_view password, std::string_view hostname, std::string_view port, - std::string_view pathname, std::string_view search, - std::string_view hash) const { - return protocol_component.fast_test(protocol) && - username_component.fast_test(username) && - password_component.fast_test(password) && - hostname_component.fast_test(hostname) && - port_component.fast_test(port) && - pathname_component.fast_test(pathname) && - search_component.fast_test(search) && hash_component.fast_test(hash); -} - -template -result url_pattern::test( - const url_pattern_input& input, const std::string_view* base_url_string) { - // If input is a URLPatternInit - if (std::holds_alternative(input)) { - if (base_url_string) { - return tl::unexpected(errors::type_error); - } - - std::string protocol{}, username{}, password{}, hostname{}; - std::string port{}, pathname{}, search{}, hash{}; - - auto apply_result = url_pattern_init::process( - std::get(input), url_pattern_init::process_type::url, - protocol, username, password, hostname, port, pathname, search, hash); - - if (!apply_result) { - return false; - } - - std::string_view search_view = *apply_result->search; - if (search_view.starts_with("?")) { - search_view.remove_prefix(1); - } - - return test_components(*apply_result->protocol, *apply_result->username, - *apply_result->password, *apply_result->hostname, - *apply_result->port, *apply_result->pathname, - search_view, *apply_result->hash); - } - - // URL string input path - result base_url; - if (base_url_string) { - base_url = ada::parse(*base_url_string, nullptr); - if (!base_url) { - return false; - } - } - - auto url = - ada::parse(std::get(input), - base_url.has_value() ? &*base_url : nullptr); - if (!url) { - return false; - } - - // Extract components as string_view - auto protocol_view = url->get_protocol(); - if (protocol_view.ends_with(":")) { - protocol_view.remove_suffix(1); - } - - auto search_view = url->get_search(); - if (search_view.starts_with("?")) { - search_view.remove_prefix(1); - } - - auto hash_view = url->get_hash(); - if (hash_view.starts_with("#")) { - hash_view.remove_prefix(1); - } - - return test_components(protocol_view, url->get_username(), - url->get_password(), url->get_hostname(), - url->get_port(), url->get_pathname(), search_view, - hash_view); -} - -template -result> url_pattern::match( - const url_pattern_input& input, const std::string_view* base_url_string) { - std::string protocol{}; - std::string username{}; - std::string password{}; - std::string hostname{}; - std::string port{}; - std::string pathname{}; - std::string search{}; - std::string hash{}; - - // Let inputs be an empty list. - // Append input to inputs. - std::vector inputs{input}; - - // If input is a URLPatternInit then: - if (std::holds_alternative(input)) { - ada_log( - "url_pattern::match called with url_pattern_init and base_url_string=", - base_url_string); - // If baseURLString was given, throw a TypeError. - if (base_url_string) { - ada_log("failed to match because base_url_string was given"); - return tl::unexpected(errors::type_error); - } - - // Let applyResult be the result of process a URLPatternInit given input, - // "url", protocol, username, password, hostname, port, pathname, search, - // and hash. - auto apply_result = url_pattern_init::process( - std::get(input), url_pattern_init::process_type::url, - protocol, username, password, hostname, port, pathname, search, hash); - - // If this throws an exception, catch it, and return null. - if (!apply_result.has_value()) { - ada_log("match returned std::nullopt because process threw"); - return std::nullopt; - } - - // Set protocol to applyResult["protocol"]. - ADA_ASSERT_TRUE(apply_result->protocol.has_value()); - protocol = std::move(apply_result->protocol.value()); - - // Set username to applyResult["username"]. - ADA_ASSERT_TRUE(apply_result->username.has_value()); - username = std::move(apply_result->username.value()); - - // Set password to applyResult["password"]. - ADA_ASSERT_TRUE(apply_result->password.has_value()); - password = std::move(apply_result->password.value()); - - // Set hostname to applyResult["hostname"]. - ADA_ASSERT_TRUE(apply_result->hostname.has_value()); - hostname = std::move(apply_result->hostname.value()); - - // Set port to applyResult["port"]. - ADA_ASSERT_TRUE(apply_result->port.has_value()); - port = std::move(apply_result->port.value()); - - // Set pathname to applyResult["pathname"]. - ADA_ASSERT_TRUE(apply_result->pathname.has_value()); - pathname = std::move(apply_result->pathname.value()); - - // Set search to applyResult["search"]. - ADA_ASSERT_TRUE(apply_result->search.has_value()); - if (apply_result->search->starts_with("?")) { - search = apply_result->search->substr(1); - } else { - search = std::move(apply_result->search.value()); - } - - // Set hash to applyResult["hash"]. - ADA_ASSERT_TRUE(apply_result->hash.has_value()); - ADA_ASSERT_TRUE(!apply_result->hash->starts_with("#")); - hash = std::move(apply_result->hash.value()); - } else { - ADA_ASSERT_TRUE(std::holds_alternative(input)); - - // Let baseURL be null. - result base_url; - - // If baseURLString was given, then: - if (base_url_string) { - // Let baseURL be the result of parsing baseURLString. - base_url = ada::parse(*base_url_string, nullptr); - - // If baseURL is failure, return null. - if (!base_url) { - ada_log("match returned std::nullopt because failed to parse base_url=", - *base_url_string); - return std::nullopt; - } - - // Append baseURLString to inputs. - inputs.emplace_back(*base_url_string); - } - - url_aggregator* base_url_value = - base_url.has_value() ? &*base_url : nullptr; - - // Set url to the result of parsing input given baseURL. - auto url = ada::parse(std::get(input), - base_url_value); - - // If url is failure, return null. - if (!url) { - ada_log("match returned std::nullopt because url failed"); - return std::nullopt; - } - - // Set protocol to url's scheme. - // IMPORTANT: Not documented on the URLPattern spec, but protocol suffix ':' - // is removed. Similar work was done on workerd: - // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2038 - protocol = url->get_protocol().substr(0, url->get_protocol().size() - 1); - // Set username to url's username. - username = url->get_username(); - // Set password to url's password. - password = url->get_password(); - // Set hostname to url's host, serialized, or the empty string if the value - // is null. - hostname = url->get_hostname(); - // Set port to url's port, serialized, or the empty string if the value is - // null. - port = url->get_port(); - // Set pathname to the result of URL path serializing url. - pathname = url->get_pathname(); - // Set search to url's query or the empty string if the value is null. - // IMPORTANT: Not documented on the URLPattern spec, but search prefix '?' - // is removed. Similar work was done on workerd: - // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2232 - if (url->has_search()) { - auto view = url->get_search(); - search = view.starts_with("?") ? url->get_search().substr(1) : view; - } - // Set hash to url's fragment or the empty string if the value is null. - // IMPORTANT: Not documented on the URLPattern spec, but hash prefix '#' is - // removed. Similar work was done on workerd: - // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2242 - if (url->has_hash()) { - auto view = url->get_hash(); - hash = view.starts_with("#") ? url->get_hash().substr(1) : view; - } - } - - // Use fast_match which skips regex for simple patterns (EMPTY, EXACT_MATCH, - // FULL_WILDCARD) and only falls back to regex for complex REGEXP patterns. - - // Let protocolExecResult be RegExpBuiltinExec(urlPattern's protocol - // component's regular expression, protocol). - auto protocol_exec_result = protocol_component.fast_match(protocol); - if (!protocol_exec_result) { - return std::nullopt; - } - - // Let usernameExecResult be RegExpBuiltinExec(urlPattern's username - // component's regular expression, username). - auto username_exec_result = username_component.fast_match(username); - if (!username_exec_result) { - return std::nullopt; - } - - // Let passwordExecResult be RegExpBuiltinExec(urlPattern's password - // component's regular expression, password). - auto password_exec_result = password_component.fast_match(password); - if (!password_exec_result) { - return std::nullopt; - } - - // Let hostnameExecResult be RegExpBuiltinExec(urlPattern's hostname - // component's regular expression, hostname). - auto hostname_exec_result = hostname_component.fast_match(hostname); - if (!hostname_exec_result) { - return std::nullopt; - } - - // Let portExecResult be RegExpBuiltinExec(urlPattern's port component's - // regular expression, port). - auto port_exec_result = port_component.fast_match(port); - if (!port_exec_result) { - return std::nullopt; - } - - // Let pathnameExecResult be RegExpBuiltinExec(urlPattern's pathname - // component's regular expression, pathname). - auto pathname_exec_result = pathname_component.fast_match(pathname); - if (!pathname_exec_result) { - return std::nullopt; - } - - // Let searchExecResult be RegExpBuiltinExec(urlPattern's search component's - // regular expression, search). - auto search_exec_result = search_component.fast_match(search); - if (!search_exec_result) { - return std::nullopt; - } - - // Let hashExecResult be RegExpBuiltinExec(urlPattern's hash component's - // regular expression, hash). - auto hash_exec_result = hash_component.fast_match(hash); - if (!hash_exec_result) { - return std::nullopt; - } - - // Let result be a new URLPatternResult. - auto result = url_pattern_result{}; - // Set result["inputs"] to inputs. - result.inputs = std::move(inputs); - // Set result["protocol"] to the result of creating a component match result - // given urlPattern's protocol component, protocol, and protocolExecResult. - result.protocol = protocol_component.create_component_match_result( - std::move(protocol), std::move(*protocol_exec_result)); - - // Set result["username"] to the result of creating a component match result - // given urlPattern's username component, username, and usernameExecResult. - result.username = username_component.create_component_match_result( - std::move(username), std::move(*username_exec_result)); - - // Set result["password"] to the result of creating a component match result - // given urlPattern's password component, password, and passwordExecResult. - result.password = password_component.create_component_match_result( - std::move(password), std::move(*password_exec_result)); - - // Set result["hostname"] to the result of creating a component match result - // given urlPattern's hostname component, hostname, and hostnameExecResult. - result.hostname = hostname_component.create_component_match_result( - std::move(hostname), std::move(*hostname_exec_result)); - - // Set result["port"] to the result of creating a component match result given - // urlPattern's port component, port, and portExecResult. - result.port = port_component.create_component_match_result( - std::move(port), std::move(*port_exec_result)); - - // Set result["pathname"] to the result of creating a component match result - // given urlPattern's pathname component, pathname, and pathnameExecResult. - result.pathname = pathname_component.create_component_match_result( - std::move(pathname), std::move(*pathname_exec_result)); - - // Set result["search"] to the result of creating a component match result - // given urlPattern's search component, search, and searchExecResult. - result.search = search_component.create_component_match_result( - std::move(search), std::move(*search_exec_result)); - - // Set result["hash"] to the result of creating a component match result given - // urlPattern's hash component, hash, and hashExecResult. - result.hash = hash_component.create_component_match_result( - std::move(hash), std::move(*hash_exec_result)); - - return result; -} - -} // namespace ada -#endif // ADA_INCLUDE_URL_PATTERN -#endif -/* end file include/ada/url_pattern-inl.h */ -/* begin file include/ada/url_pattern_helpers-inl.h */ -/** - * @file url_pattern_helpers-inl.h - * @brief Declaration for the URLPattern helpers. - */ -#ifndef ADA_URL_PATTERN_HELPERS_INL_H -#define ADA_URL_PATTERN_HELPERS_INL_H - -#include -#include - - -#if ADA_INCLUDE_URL_PATTERN -namespace ada::url_pattern_helpers { -#if defined(ADA_TESTING) || defined(ADA_LOGGING) -inline std::string to_string(token_type type) { - switch (type) { - case token_type::INVALID_CHAR: - return "INVALID_CHAR"; - case token_type::OPEN: - return "OPEN"; - case token_type::CLOSE: - return "CLOSE"; - case token_type::REGEXP: - return "REGEXP"; - case token_type::NAME: - return "NAME"; - case token_type::CHAR: - return "CHAR"; - case token_type::ESCAPED_CHAR: - return "ESCAPED_CHAR"; - case token_type::OTHER_MODIFIER: - return "OTHER_MODIFIER"; - case token_type::ASTERISK: - return "ASTERISK"; - case token_type::END: - return "END"; - default: - ada::unreachable(); - } -} -#endif // defined(ADA_TESTING) || defined(ADA_LOGGING) - -template -constexpr void constructor_string_parser::rewind() { - // Set parser's token index to parser's component start. - token_index = component_start; - // Set parser's token increment to 0. - token_increment = 0; -} - -template -constexpr bool constructor_string_parser::is_hash_prefix() { - // Return the result of running is a non-special pattern char given parser, - // parser's token index and "#". - return is_non_special_pattern_char(token_index, '#'); -} - -template -constexpr bool constructor_string_parser::is_search_prefix() { - // If result of running is a non-special pattern char given parser, parser's - // token index and "?" is true, then return true. - if (is_non_special_pattern_char(token_index, '?')) { - return true; - } - - // If parser's token list[parser's token index]'s value is not "?", then - // return false. - if (token_list[token_index].value != "?") { - return false; - } - - // If previous index is less than 0, then return true. - if (token_index == 0) return true; - // Let previous index be parser's token index - 1. - auto previous_index = token_index - 1; - // Let previous token be the result of running get a safe token given parser - // and previous index. - auto previous_token = get_safe_token(previous_index); - ADA_ASSERT_TRUE(previous_token); - // If any of the following are true, then return false: - // - previous token's type is "name". - // - previous token's type is "regexp". - // - previous token's type is "close". - // - previous token's type is "asterisk". - return !(previous_token->type == token_type::NAME || - previous_token->type == token_type::REGEXP || - previous_token->type == token_type::CLOSE || - previous_token->type == token_type::ASTERISK); -} - -template -constexpr bool -constructor_string_parser::is_non_special_pattern_char( - size_t index, uint32_t value) const { - // Let token be the result of running get a safe token given parser and index. - auto token = get_safe_token(index); - ADA_ASSERT_TRUE(token); - - // If token's value is not value, then return false. - // TODO: Remove this once we make sure get_safe_token returns a non-empty - // string. - if (!token->value.empty() && - static_cast(token->value[0]) != value) { - return false; - } - - // If any of the following are true: - // - token's type is "char"; - // - token's type is "escaped-char"; or - // - token's type is "invalid-char", - // - then return true. - return token->type == token_type::CHAR || - token->type == token_type::ESCAPED_CHAR || - token->type == token_type::INVALID_CHAR; -} - -template -constexpr const token* -constructor_string_parser::get_safe_token(size_t index) const { - // If index is less than parser's token list's size, then return parser's - // token list[index]. - if (index < token_list.size()) [[likely]] { - return &token_list[index]; - } - - // Assert: parser's token list's size is greater than or equal to 1. - ADA_ASSERT_TRUE(!token_list.empty()); - - // Let token be parser's token list[last index]. - // Assert: token's type is "end". - ADA_ASSERT_TRUE(token_list.back().type == token_type::END); - - // Return token. - return &token_list.back(); -} - -template -constexpr bool constructor_string_parser::is_group_open() - const { - // If parser's token list[parser's token index]'s type is "open", then return - // true. - return token_list[token_index].type == token_type::OPEN; -} - -template -constexpr bool constructor_string_parser::is_group_close() - const { - // If parser's token list[parser's token index]'s type is "close", then return - // true. - return token_list[token_index].type == token_type::CLOSE; -} - -template -constexpr bool -constructor_string_parser::next_is_authority_slashes() const { - // If the result of running is a non-special pattern char given parser, - // parser's token index + 1, and "/" is false, then return false. - if (!is_non_special_pattern_char(token_index + 1, '/')) { - return false; - } - // If the result of running is a non-special pattern char given parser, - // parser's token index + 2, and "/" is false, then return false. - if (!is_non_special_pattern_char(token_index + 2, '/')) { - return false; - } - return true; -} - -template -constexpr bool constructor_string_parser::is_protocol_suffix() - const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and ":". - return is_non_special_pattern_char(token_index, ':'); -} - -template -void constructor_string_parser::change_state(State new_state, - size_t skip) { - // If parser's state is not "init", not "authority", and not "done", then set - // parser's result[parser's state] to the result of running make a component - // string given parser. - if (state != State::INIT && state != State::AUTHORITY && - state != State::DONE) { - auto value = make_component_string(); - // TODO: Simplify this. - switch (state) { - case State::PROTOCOL: { - result.protocol = value; - break; - } - case State::USERNAME: { - result.username = value; - break; - } - case State::PASSWORD: { - result.password = value; - break; - } - case State::HOSTNAME: { - result.hostname = value; - break; - } - case State::PORT: { - result.port = value; - break; - } - case State::PATHNAME: { - result.pathname = value; - break; - } - case State::SEARCH: { - result.search = value; - break; - } - case State::HASH: { - result.hash = value; - break; - } - default: - ada::unreachable(); - } - } - - // If parser's state is not "init" and new state is not "done", then: - if (state != State::INIT && new_state != State::DONE) { - // If parser's state is "protocol", "authority", "username", or "password"; - // new state is "port", "pathname", "search", or "hash"; and parser's - // result["hostname"] does not exist, then set parser's result["hostname"] - // to the empty string. - if ((state == State::PROTOCOL || state == State::AUTHORITY || - state == State::USERNAME || state == State::PASSWORD) && - (new_state == State::PORT || new_state == State::PATHNAME || - new_state == State::SEARCH || new_state == State::HASH) && - !result.hostname) - result.hostname = ""; - } - - // If parser's state is "protocol", "authority", "username", "password", - // "hostname", or "port"; new state is "search" or "hash"; and parser's - // result["pathname"] does not exist, then: - if ((state == State::PROTOCOL || state == State::AUTHORITY || - state == State::USERNAME || state == State::PASSWORD || - state == State::HOSTNAME || state == State::PORT) && - (new_state == State::SEARCH || new_state == State::HASH) && - !result.pathname) { - if (protocol_matches_a_special_scheme_flag) { - result.pathname = "/"; - } else { - // Otherwise, set parser's result["pathname"] to the empty string. - result.pathname = ""; - } - } - - // If parser's state is "protocol", "authority", "username", "password", - // "hostname", "port", or "pathname"; new state is "hash"; and parser's - // result["search"] does not exist, then set parser's result["search"] to - // the empty string. - if ((state == State::PROTOCOL || state == State::AUTHORITY || - state == State::USERNAME || state == State::PASSWORD || - state == State::HOSTNAME || state == State::PORT || - state == State::PATHNAME) && - new_state == State::HASH && !result.search) { - result.search = ""; - } - - // Set parser's state to new state. - state = new_state; - // Increment parser's token index by skip. - token_index += skip; - // Set parser's component start to parser's token index. - component_start = token_index; - // Set parser's token increment to 0. - token_increment = 0; -} - -template -std::string constructor_string_parser::make_component_string() { - // Assert: parser's token index is less than parser's token list's size. - ADA_ASSERT_TRUE(token_index < token_list.size()); - - // Let token be parser's token list[parser's token index]. - // Let end index be token's index. - const auto end_index = token_list[token_index].index; - // Let component start token be the result of running get a safe token given - // parser and parser's component start. - const auto component_start_token = get_safe_token(component_start); - ADA_ASSERT_TRUE(component_start_token); - // Let component start input index be component start token's index. - const auto component_start_input_index = component_start_token->index; - // Return the code point substring from component start input index to end - // index within parser's input. - return std::string(input.substr(component_start_input_index, - end_index - component_start_input_index)); -} - -template -constexpr bool -constructor_string_parser::is_an_identity_terminator() const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and "@". - return is_non_special_pattern_char(token_index, '@'); -} - -template -constexpr bool constructor_string_parser::is_pathname_start() - const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and "/". - return is_non_special_pattern_char(token_index, '/'); -} - -template -constexpr bool constructor_string_parser::is_password_prefix() - const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and ":". - return is_non_special_pattern_char(token_index, ':'); -} - -template -constexpr bool constructor_string_parser::is_an_ipv6_open() - const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and "[". - return is_non_special_pattern_char(token_index, '['); -} - -template -constexpr bool constructor_string_parser::is_an_ipv6_close() - const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and "]". - return is_non_special_pattern_char(token_index, ']'); -} - -template -constexpr bool constructor_string_parser::is_port_prefix() - const { - // Return the result of running is a non-special pattern char given parser, - // parser's token index, and ":". - return is_non_special_pattern_char(token_index, ':'); -} - -constexpr void Tokenizer::get_next_code_point() { - ada_log("Tokenizer::get_next_code_point called with index=", next_index); - ADA_ASSERT_TRUE(next_index < input.size()); - // Decode the next UTF-8 code point. If malformed or truncated, mark it as - // invalid, return the offending byte as the code point, and advance by one - // to guarantee forward progress. - invalid_code_point = false; - code_point = 0; - size_t number_bytes = 0; - const size_t initial_index = next_index; - unsigned char first_byte = input[next_index]; - - if ((first_byte & 0x80) == 0) { - // 1-byte character (ASCII) - next_index++; - code_point = first_byte; - ada_log("Tokenizer::get_next_code_point returning ASCII code point=", - uint32_t(code_point)); - ada_log("Tokenizer::get_next_code_point next_index =", next_index, - " input.size()=", input.size()); - return; - } - ada_log("Tokenizer::get_next_code_point read first byte=", - uint32_t(first_byte)); - if ((first_byte & 0xE0) == 0xC0) { - code_point = first_byte & 0x1F; - number_bytes = 2; - ada_log("Tokenizer::get_next_code_point two bytes"); - } else if ((first_byte & 0xF0) == 0xE0) { - code_point = first_byte & 0x0F; - number_bytes = 3; - ada_log("Tokenizer::get_next_code_point three bytes"); - } else if ((first_byte & 0xF8) == 0xF0) { - code_point = first_byte & 0x07; - number_bytes = 4; - ada_log("Tokenizer::get_next_code_point four bytes"); - } - - // Invalid leading bytes that still match a multi-byte prefix. - if ((number_bytes == 2 && first_byte < 0xC2) || - (number_bytes == 4 && first_byte > 0xF4)) { - invalid_code_point = true; - code_point = first_byte; - next_index = initial_index + 1; - return; - } - - // Invalid leading byte (e.g., continuation byte outside a sequence). - if (number_bytes == 0) { - invalid_code_point = true; - code_point = first_byte; - next_index = initial_index + 1; - return; - } - - // Truncated UTF-8 sequence. - if (number_bytes + next_index > input.size()) { - invalid_code_point = true; - code_point = first_byte; - next_index = initial_index + 1; - return; - } - - for (size_t i = 1 + next_index; i < number_bytes + next_index; ++i) { - unsigned char byte = input[i]; - if ((byte & 0xC0) != 0x80) { - invalid_code_point = true; - code_point = first_byte; - next_index = initial_index + 1; - return; - } - ada_log("Tokenizer::get_next_code_point read byte=", uint32_t(byte)); - code_point = (code_point << 6) | (byte & 0x3F); - } - ada_log("Tokenizer::get_next_code_point returning non-ASCII code point=", - uint32_t(code_point)); - ada_log("Tokenizer::get_next_code_point next_index =", next_index, - " input.size()=", input.size()); - next_index += number_bytes; -} - -constexpr void Tokenizer::seek_and_get_next_code_point(size_t new_index) { - ada_log("Tokenizer::seek_and_get_next_code_point called with new_index=", - new_index); - // Set tokenizer's next index to index. - next_index = new_index; - // Run get the next code point given tokenizer. - get_next_code_point(); -} - -inline void Tokenizer::add_token(token_type type, size_t next_position, - size_t value_position, size_t value_length) { - ada_log("Tokenizer::add_token called with type=", to_string(type), - " next_position=", next_position, " value_position=", value_position); - ADA_ASSERT_TRUE(next_position >= value_position); - - // Let token be a new token. - // Set token's type to type. - // Set token's index to tokenizer's index. - // Set token's value to the code point substring from value position with - // length value length within tokenizer's input. - // Append token to the back of tokenizer's token list. - token_list.emplace_back(type, index, - input.substr(value_position, value_length)); - // Set tokenizer's index to next position. - index = next_position; -} - -inline void Tokenizer::add_token_with_default_length(token_type type, - size_t next_position, - size_t value_position) { - // Let computed length be next position - value position. - auto computed_length = next_position - value_position; - // Run add a token given tokenizer, type, next position, value position, and - // computed length. - add_token(type, next_position, value_position, computed_length); -} - -inline void Tokenizer::add_token_with_defaults(token_type type) { - ada_log("Tokenizer::add_token_with_defaults called with type=", - to_string(type)); - // Run add a token with default length given tokenizer, type, tokenizer's next - // index, and tokenizer's index. - add_token_with_default_length(type, next_index, index); -} - -inline ada_warn_unused std::optional -Tokenizer::process_tokenizing_error(size_t next_position, - size_t value_position) { - // If tokenizer's policy is "strict", then throw a TypeError. - if (policy == token_policy::strict) { - ada_log("process_tokenizing_error failed with next_position=", - next_position, " value_position=", value_position); - return errors::type_error; - } - // Assert: tokenizer's policy is "lenient". - ADA_ASSERT_TRUE(policy == token_policy::lenient); - // Run add a token with default length given tokenizer, "invalid-char", next - // position, and value position. - add_token_with_default_length(token_type::INVALID_CHAR, next_position, - value_position); - return std::nullopt; -} - -template -token* url_pattern_parser::try_consume_modifier_token() { - // Let token be the result of running try to consume a token given parser and - // "other-modifier". - auto token = try_consume_token(token_type::OTHER_MODIFIER); - // If token is not null, then return token. - if (token) return token; - // Set token to the result of running try to consume a token given parser and - // "asterisk". - // Return token. - return try_consume_token(token_type::ASTERISK); -} - -template -token* url_pattern_parser::try_consume_regexp_or_wildcard_token( - const token* name_token) { - // Let token be the result of running try to consume a token given parser and - // "regexp". - auto token = try_consume_token(token_type::REGEXP); - // If name token is null and token is null, then set token to the result of - // running try to consume a token given parser and "asterisk". - if (!name_token && !token) { - token = try_consume_token(token_type::ASTERISK); - } - // Return token. - return token; -} - -template -token* url_pattern_parser::try_consume_token(token_type type) { - ada_log("url_pattern_parser::try_consume_token called with type=", - to_string(type)); - // Assert: parser's index is less than parser's token list size. - ADA_ASSERT_TRUE(index < tokens.size()); - // Let next token be parser's token list[parser's index]. - auto& next_token = tokens[index]; - // If next token's type is not type return null. - if (next_token.type != type) return nullptr; - // Increase parser's index by 1. - index++; - // Return next token. - return &next_token; -} - -template -std::string url_pattern_parser::consume_text() { - // Let result be the empty string. - std::string result{}; - // While true: - while (true) { - // Let token be the result of running try to consume a token given parser - // and "char". - auto token = try_consume_token(token_type::CHAR); - // If token is null, then set token to the result of running try to consume - // a token given parser and "escaped-char". - if (!token) token = try_consume_token(token_type::ESCAPED_CHAR); - // If token is null, then break. - if (!token) break; - // Append token's value to the end of result. - result.append(token->value); - } - // Return result. - return result; -} - -template -bool url_pattern_parser::consume_required_token(token_type type) { - ada_log("url_pattern_parser::consume_required_token called with type=", - to_string(type)); - // Let result be the result of running try to consume a token given parser and - // type. - return try_consume_token(type) != nullptr; -} - -template -std::optional -url_pattern_parser::maybe_add_part_from_the_pending_fixed_value() { - // If parser's pending fixed value is the empty string, then return. - if (pending_fixed_value.empty()) { - ada_log("pending_fixed_value is empty"); - return std::nullopt; - } - // Let encoded value be the result of running parser's encoding callback given - // parser's pending fixed value. - auto encoded_value = encoding_callback(pending_fixed_value); - if (!encoded_value) { - ada_log("failed to encode pending_fixed_value: ", pending_fixed_value); - return encoded_value.error(); - } - // Set parser's pending fixed value to the empty string. - pending_fixed_value.clear(); - // Let part be a new part whose type is "fixed-text", value is encoded value, - // and modifier is "none". - // Append part to parser's part list. - parts.emplace_back(url_pattern_part_type::FIXED_TEXT, - std::move(*encoded_value), - url_pattern_part_modifier::none); - return std::nullopt; -} - -template -std::optional url_pattern_parser::add_part( - std::string_view prefix, token* name_token, token* regexp_or_wildcard_token, - std::string_view suffix, token* modifier_token) { - // Let modifier be "none". - auto modifier = url_pattern_part_modifier::none; - // If modifier token is not null: - if (modifier_token) { - // If modifier token's value is "?" then set modifier to "optional". - if (modifier_token->value == "?") { - modifier = url_pattern_part_modifier::optional; - } else if (modifier_token->value == "*") { - // Otherwise if modifier token's value is "*" then set modifier to - // "zero-or-more". - modifier = url_pattern_part_modifier::zero_or_more; - } else if (modifier_token->value == "+") { - // Otherwise if modifier token's value is "+" then set modifier to - // "one-or-more". - modifier = url_pattern_part_modifier::one_or_more; - } - } - // If name token is null and regexp or wildcard token is null and modifier - // is "none": - if (!name_token && !regexp_or_wildcard_token && - modifier == url_pattern_part_modifier::none) { - // Append prefix to the end of parser's pending fixed value. - pending_fixed_value.append(prefix); - return std::nullopt; - } - // Run maybe add a part from the pending fixed value given parser. - if (auto error = maybe_add_part_from_the_pending_fixed_value()) { - return *error; - } - // If name token is null and regexp or wildcard token is null: - if (!name_token && !regexp_or_wildcard_token) { - // Assert: suffix is the empty string. - ADA_ASSERT_TRUE(suffix.empty()); - // If prefix is the empty string, then return. - if (prefix.empty()) return std::nullopt; - // Let encoded value be the result of running parser's encoding callback - // given prefix. - auto encoded_value = encoding_callback(prefix); - if (!encoded_value) { - return encoded_value.error(); - } - // Let part be a new part whose type is "fixed-text", value is encoded - // value, and modifier is modifier. - // Append part to parser's part list. - parts.emplace_back(url_pattern_part_type::FIXED_TEXT, - std::move(*encoded_value), modifier); - return std::nullopt; - } - // Let regexp value be the empty string. - std::string regexp_value{}; - // If regexp or wildcard token is null, then set regexp value to parser's - // segment wildcard regexp. - if (!regexp_or_wildcard_token) { - regexp_value = segment_wildcard_regexp; - } else if (regexp_or_wildcard_token->type == token_type::ASTERISK) { - // Otherwise if regexp or wildcard token's type is "asterisk", then set - // regexp value to the full wildcard regexp value. - regexp_value = ".*"; - } else { - // Otherwise set regexp value to regexp or wildcard token's value. - regexp_value = regexp_or_wildcard_token->value; - } - // Let type be "regexp". - auto type = url_pattern_part_type::REGEXP; - // If regexp value is parser's segment wildcard regexp: - if (regexp_value == segment_wildcard_regexp) { - // Set type to "segment-wildcard". - type = url_pattern_part_type::SEGMENT_WILDCARD; - // Set regexp value to the empty string. - regexp_value.clear(); - } else if (regexp_value == ".*") { - // Otherwise if regexp value is the full wildcard regexp value: - // Set type to "full-wildcard". - type = url_pattern_part_type::FULL_WILDCARD; - // Set regexp value to the empty string. - regexp_value.clear(); - } - // Let name be the empty string. - std::string name{}; - // If name token is not null, then set name to name token's value. - if (name_token) { - name = name_token->value; - } else if (regexp_or_wildcard_token != nullptr) { - // Otherwise if regexp or wildcard token is not null: - // Set name to parser's next numeric name, serialized. - name = std::to_string(next_numeric_name); - // Increment parser's next numeric name by 1. - next_numeric_name++; - } - // If the result of running is a duplicate name given parser and name is - // true, then throw a TypeError. - if (std::ranges::any_of( - parts, [&name](const auto& part) { return part.name == name; })) { - return errors::type_error; - } - // Let encoded prefix be the result of running parser's encoding callback - // given prefix. - auto encoded_prefix = encoding_callback(prefix); - if (!encoded_prefix) return encoded_prefix.error(); - // Let encoded suffix be the result of running parser's encoding callback - // given suffix. - auto encoded_suffix = encoding_callback(suffix); - if (!encoded_suffix) return encoded_suffix.error(); - // Let part be a new part whose type is type, value is regexp value, - // modifier is modifier, name is name, prefix is encoded prefix, and suffix - // is encoded suffix. - // Append part to parser's part list. - parts.emplace_back(type, std::move(regexp_value), modifier, std::move(name), - std::move(*encoded_prefix), std::move(*encoded_suffix)); - return std::nullopt; -} - -template -tl::expected, errors> parse_pattern_string( - std::string_view input, url_pattern_compile_component_options& options, - F& encoding_callback) { - ada_log("parse_pattern_string input=", input); - // Let parser be a new pattern parser whose encoding callback is encoding - // callback and segment wildcard regexp is the result of running generate a - // segment wildcard regexp given options. - auto parser = url_pattern_parser( - encoding_callback, generate_segment_wildcard_regexp(options)); - // Set parser's token list to the result of running tokenize given input and - // "strict". - auto tokenize_result = tokenize(input, token_policy::strict); - if (!tokenize_result) { - ada_log("parse_pattern_string tokenize failed"); - return tl::unexpected(tokenize_result.error()); - } - parser.tokens = std::move(*tokenize_result); - - // While parser's index is less than parser's token list's size: - while (parser.can_continue()) { - // Let char token be the result of running try to consume a token given - // parser and "char". - auto char_token = parser.try_consume_token(token_type::CHAR); - // Let name token be the result of running try to consume a token given - // parser and "name". - auto name_token = parser.try_consume_token(token_type::NAME); - // Let regexp or wildcard token be the result of running try to consume a - // regexp or wildcard token given parser and name token. - auto regexp_or_wildcard_token = - parser.try_consume_regexp_or_wildcard_token(name_token); - // If name token is not null or regexp or wildcard token is not null: - if (name_token || regexp_or_wildcard_token) { - // Let prefix be the empty string. - std::string prefix{}; - // If char token is not null then set prefix to char token's value. - if (char_token) prefix = char_token->value; - // If prefix is not the empty string and not options's prefix code point: - if (!prefix.empty() && prefix != options.get_prefix()) { - // Append prefix to the end of parser's pending fixed value. - parser.pending_fixed_value.append(prefix); - // Set prefix to the empty string. - prefix.clear(); - } - // Run maybe add a part from the pending fixed value given parser. - if (auto error = parser.maybe_add_part_from_the_pending_fixed_value()) { - ada_log("maybe_add_part_from_the_pending_fixed_value failed"); - return tl::unexpected(*error); - } - // Let modifier token be the result of running try to consume a modifier - // token given parser. - auto modifier_token = parser.try_consume_modifier_token(); - // Run add a part given parser, prefix, name token, regexp or wildcard - // token, the empty string, and modifier token. - if (auto error = - parser.add_part(prefix, name_token, regexp_or_wildcard_token, "", - modifier_token)) { - ada_log("parser.add_part failed"); - return tl::unexpected(*error); - } - // Continue. - continue; - } - - // Let fixed token be char token. - auto fixed_token = char_token; - // If fixed token is null, then set fixed token to the result of running try - // to consume a token given parser and "escaped-char". - if (!fixed_token) - fixed_token = parser.try_consume_token(token_type::ESCAPED_CHAR); - // If fixed token is not null: - if (fixed_token) { - // Append fixed token's value to parser's pending fixed value. - parser.pending_fixed_value.append(fixed_token->value); - // Continue. - continue; - } - // Let open token be the result of running try to consume a token given - // parser and "open". - auto open_token = parser.try_consume_token(token_type::OPEN); - // If open token is not null: - if (open_token) { - // Set prefix be the result of running consume text given parser. - auto prefix_ = parser.consume_text(); - // Set name token to the result of running try to consume a token given - // parser and "name". - name_token = parser.try_consume_token(token_type::NAME); - // Set regexp or wildcard token to the result of running try to consume a - // regexp or wildcard token given parser and name token. - regexp_or_wildcard_token = - parser.try_consume_regexp_or_wildcard_token(name_token); - // Let suffix be the result of running consume text given parser. - auto suffix_ = parser.consume_text(); - // Run consume a required token given parser and "close". - if (!parser.consume_required_token(token_type::CLOSE)) { - ada_log("parser.consume_required_token failed"); - return tl::unexpected(errors::type_error); - } - // Set modifier token to the result of running try to consume a modifier - // token given parser. - auto modifier_token = parser.try_consume_modifier_token(); - // Run add a part given parser, prefix, name token, regexp or wildcard - // token, suffix, and modifier token. - if (auto error = - parser.add_part(prefix_, name_token, regexp_or_wildcard_token, - suffix_, modifier_token)) { - return tl::unexpected(*error); - } - // Continue. - continue; - } - // Run maybe add a part from the pending fixed value given parser. - if (auto error = parser.maybe_add_part_from_the_pending_fixed_value()) { - ada_log("maybe_add_part_from_the_pending_fixed_value failed on line 992"); - return tl::unexpected(*error); - } - // Run consume a required token given parser and "end". - if (!parser.consume_required_token(token_type::END)) { - return tl::unexpected(errors::type_error); - } - } - ada_log("parser.parts size is: ", parser.parts.size()); - // Return parser's part list. - return parser.parts; -} - -template -bool protocol_component_matches_special_scheme( - url_pattern_component& component) { - // Optimization: Use fast_test for simple patterns to avoid regex overhead - switch (component.type) { - case url_pattern_component_type::EMPTY: - // Empty pattern can't match any special scheme - return false; - case url_pattern_component_type::EXACT_MATCH: - // Direct string comparison for exact match patterns - return component.exact_match_value == "http" || - component.exact_match_value == "https" || - component.exact_match_value == "ws" || - component.exact_match_value == "wss" || - component.exact_match_value == "ftp" || - component.exact_match_value == "file"; - case url_pattern_component_type::FULL_WILDCARD: - // Full wildcard matches everything including special schemes - return true; - case url_pattern_component_type::REGEXP: - // Fall back to regex matching for complex patterns - auto& regex = component.regexp; - return regex_provider::regex_match("http", regex) || - regex_provider::regex_match("https", regex) || - regex_provider::regex_match("ws", regex) || - regex_provider::regex_match("wss", regex) || - regex_provider::regex_match("ftp", regex) || - regex_provider::regex_match("file", regex); - } - ada::unreachable(); -} - -template -inline std::optional constructor_string_parser< - regex_provider>::compute_protocol_matches_special_scheme_flag() { - ada_log( - "constructor_string_parser::compute_protocol_matches_special_scheme_" - "flag"); - // Let protocol string be the result of running make a component string given - // parser. - auto protocol_string = make_component_string(); - // Let protocol component be the result of compiling a component given - // protocol string, canonicalize a protocol, and default options. - auto protocol_component = url_pattern_component::compile( - protocol_string, canonicalize_protocol, - url_pattern_compile_component_options::DEFAULT); - if (!protocol_component) { - ada_log("url_pattern_component::compile failed for protocol_string ", - protocol_string); - return protocol_component.error(); - } - // If the result of running protocol component matches a special scheme given - // protocol component is true, then set parser's protocol matches a special - // scheme flag to true. - if (protocol_component_matches_special_scheme(*protocol_component)) { - protocol_matches_a_special_scheme_flag = true; - } - return std::nullopt; -} - -template -tl::expected -constructor_string_parser::parse(std::string_view input) { - ada_log("constructor_string_parser::parse input=", input); - // Let parser be a new constructor string parser whose input is input and - // token list is the result of running tokenize given input and "lenient". - auto token_list = tokenize(input, token_policy::lenient); - if (!token_list) { - return tl::unexpected(token_list.error()); - } - auto parser = constructor_string_parser(input, std::move(*token_list)); - - // While parser's token index is less than parser's token list size: - while (parser.token_index < parser.token_list.size()) { - // Set parser's token increment to 1. - parser.token_increment = 1; - - // If parser's token list[parser's token index]'s type is "end" then: - if (parser.token_list[parser.token_index].type == token_type::END) { - // If parser's state is "init": - if (parser.state == State::INIT) { - // Run rewind given parser. - parser.rewind(); - // If the result of running is a hash prefix given parser is true, then - // run change state given parser, "hash" and 1. - if (parser.is_hash_prefix()) { - parser.change_state(State::HASH, 1); - } else if (parser.is_search_prefix()) { - // Otherwise if the result of running is a search prefix given parser - // is true: Run change state given parser, "search" and 1. - parser.change_state(State::SEARCH, 1); - } else { - // Run change state given parser, "pathname" and 0. - parser.change_state(State::PATHNAME, 0); - } - // Increment parser's token index by parser's token increment. - parser.token_index += parser.token_increment; - // Continue. - continue; - } - - if (parser.state == State::AUTHORITY) { - // If parser's state is "authority": - // Run rewind and set state given parser, and "hostname". - parser.rewind(); - parser.change_state(State::HOSTNAME, 0); - // Increment parser's token index by parser's token increment. - parser.token_index += parser.token_increment; - // Continue. - continue; - } - - // Run change state given parser, "done" and 0. - parser.change_state(State::DONE, 0); - // Break. - break; - } - - // If the result of running is a group open given parser is true: - if (parser.is_group_open()) { - // Increment parser's group depth by 1. - parser.group_depth += 1; - // Increment parser's token index by parser's token increment. - parser.token_index += parser.token_increment; - // Continue. - continue; - } - - // If parser's group depth is greater than 0: - if (parser.group_depth > 0) { - // If the result of running is a group close given parser is true, then - // decrement parser's group depth by 1. - if (parser.is_group_close()) { - parser.group_depth -= 1; - } else { - // Increment parser's token index by parser's token increment. - parser.token_index += parser.token_increment; - continue; - } - } - - // Switch on parser's state and run the associated steps: - switch (parser.state) { - case State::INIT: { - // If the result of running is a protocol suffix given parser is true: - if (parser.is_protocol_suffix()) { - // Run rewind and set state given parser and "protocol". - parser.rewind(); - parser.change_state(State::PROTOCOL, 0); - } - break; - } - case State::PROTOCOL: { - // If the result of running is a protocol suffix given parser is true: - if (parser.is_protocol_suffix()) { - // Run compute protocol matches a special scheme flag given parser. - if (const auto error = - parser.compute_protocol_matches_special_scheme_flag()) { - ada_log("compute_protocol_matches_special_scheme_flag failed"); - return tl::unexpected(*error); - } - // Let next state be "pathname". - auto next_state = State::PATHNAME; - // Let skip be 1. - auto skip = 1; - // If the result of running next is authority slashes given parser is - // true: - if (parser.next_is_authority_slashes()) { - // Set next state to "authority". - next_state = State::AUTHORITY; - // Set skip to 3. - skip = 3; - } else if (parser.protocol_matches_a_special_scheme_flag) { - // Otherwise if parser's protocol matches a special scheme flag is - // true, then set next state to "authority". - next_state = State::AUTHORITY; - } - - // Run change state given parser, next state, and skip. - parser.change_state(next_state, skip); - } - break; - } - case State::AUTHORITY: { - // If the result of running is an identity terminator given parser is - // true, then run rewind and set state given parser and "username". - if (parser.is_an_identity_terminator()) { - parser.rewind(); - parser.change_state(State::USERNAME, 0); - } else if (parser.is_pathname_start() || parser.is_search_prefix() || - parser.is_hash_prefix()) { - // Otherwise if any of the following are true: - // - the result of running is a pathname start given parser; - // - the result of running is a search prefix given parser; or - // - the result of running is a hash prefix given parser, - // then run rewind and set state given parser and "hostname". - parser.rewind(); - parser.change_state(State::HOSTNAME, 0); - } - break; - } - case State::USERNAME: { - // If the result of running is a password prefix given parser is true, - // then run change state given parser, "password", and 1. - if (parser.is_password_prefix()) { - parser.change_state(State::PASSWORD, 1); - } else if (parser.is_an_identity_terminator()) { - // Otherwise if the result of running is an identity terminator given - // parser is true, then run change state given parser, "hostname", - // and 1. - parser.change_state(State::HOSTNAME, 1); - } - break; - } - case State::PASSWORD: { - // If the result of running is an identity terminator given parser is - // true, then run change state given parser, "hostname", and 1. - if (parser.is_an_identity_terminator()) { - parser.change_state(State::HOSTNAME, 1); - } - break; - } - case State::HOSTNAME: { - // If the result of running is an IPv6 open given parser is true, then - // increment parser's hostname IPv6 bracket depth by 1. - if (parser.is_an_ipv6_open()) { - parser.hostname_ipv6_bracket_depth += 1; - } else if (parser.is_an_ipv6_close()) { - // Otherwise if the result of running is an IPv6 close given parser is - // true, then decrement parser's hostname IPv6 bracket depth by 1. - parser.hostname_ipv6_bracket_depth -= 1; - } else if (parser.is_port_prefix() && - parser.hostname_ipv6_bracket_depth == 0) { - // Otherwise if the result of running is a port prefix given parser is - // true and parser's hostname IPv6 bracket depth is zero, then run - // change state given parser, "port", and 1. - parser.change_state(State::PORT, 1); - } else if (parser.is_pathname_start()) { - // Otherwise if the result of running is a pathname start given parser - // is true, then run change state given parser, "pathname", and 0. - parser.change_state(State::PATHNAME, 0); - } else if (parser.is_search_prefix()) { - // Otherwise if the result of running is a search prefix given parser - // is true, then run change state given parser, "search", and 1. - parser.change_state(State::SEARCH, 1); - } else if (parser.is_hash_prefix()) { - // Otherwise if the result of running is a hash prefix given parser is - // true, then run change state given parser, "hash", and 1. - parser.change_state(State::HASH, 1); - } - - break; - } - case State::PORT: { - // If the result of running is a pathname start given parser is true, - // then run change state given parser, "pathname", and 0. - if (parser.is_pathname_start()) { - parser.change_state(State::PATHNAME, 0); - } else if (parser.is_search_prefix()) { - // Otherwise if the result of running is a search prefix given parser - // is true, then run change state given parser, "search", and 1. - parser.change_state(State::SEARCH, 1); - } else if (parser.is_hash_prefix()) { - // Otherwise if the result of running is a hash prefix given parser is - // true, then run change state given parser, "hash", and 1. - parser.change_state(State::HASH, 1); - } - break; - } - case State::PATHNAME: { - // If the result of running is a search prefix given parser is true, - // then run change state given parser, "search", and 1. - if (parser.is_search_prefix()) { - parser.change_state(State::SEARCH, 1); - } else if (parser.is_hash_prefix()) { - // Otherwise if the result of running is a hash prefix given parser is - // true, then run change state given parser, "hash", and 1. - parser.change_state(State::HASH, 1); - } - break; - } - case State::SEARCH: { - // If the result of running is a hash prefix given parser is true, then - // run change state given parser, "hash", and 1. - if (parser.is_hash_prefix()) { - parser.change_state(State::HASH, 1); - } - break; - } - case State::HASH: { - // Do nothing - break; - } - default: { - // Assert: This step is never reached. - unreachable(); - } - } - - // Increment parser's token index by parser's token increment. - parser.token_index += parser.token_increment; - } - - // If parser's result contains "hostname" and not "port", then set parser's - // result["port"] to the empty string. - if (parser.result.hostname && !parser.result.port) { - parser.result.port = ""; - } - - // Return parser's result. - return parser.result; -} - -} // namespace ada::url_pattern_helpers -#endif // ADA_INCLUDE_URL_PATTERN -#endif -/* end file include/ada/url_pattern_helpers-inl.h */ - -// Public API -/* begin file include/ada/ada_version.h */ -/** - * @file ada_version.h - * @brief Definitions for Ada's version number. - */ -#ifndef ADA_ADA_VERSION_H -#define ADA_ADA_VERSION_H - -#define ADA_VERSION "4.0.0" - -namespace ada { - -enum { - ADA_VERSION_MAJOR = 4, - ADA_VERSION_MINOR = 0, - ADA_VERSION_REVISION = 0, -}; - -} // namespace ada - -#endif // ADA_ADA_VERSION_H -/* end file include/ada/ada_version.h */ -/* begin file include/ada/implementation-inl.h */ -/** - * @file implementation-inl.h - */ -#ifndef ADA_IMPLEMENTATION_INL_H -#define ADA_IMPLEMENTATION_INL_H - - - -#include -#include - -namespace ada { - -#if ADA_INCLUDE_URL_PATTERN -template -ada_warn_unused tl::expected, errors> -parse_url_pattern(std::variant&& input, - const std::string_view* base_url, - const url_pattern_options* options) { - return parser::parse_url_pattern_impl(std::move(input), - base_url, options); -} -#endif // ADA_INCLUDE_URL_PATTERN - -} // namespace ada - -#endif // ADA_IMPLEMENTATION_INL_H -/* end file include/ada/implementation-inl.h */ - -#endif // ADA_H -/* end file include/ada.h */ diff --git a/deps/ada_c.h b/deps/ada_c.h deleted file mode 100644 index 98e4ce2..0000000 --- a/deps/ada_c.h +++ /dev/null @@ -1,209 +0,0 @@ -/** - * @file ada_c.h - * @brief Includes the C definitions for Ada. This is a C file, not C++. - */ -#ifndef ADA_C_H -#define ADA_C_H - -#include -#include -#include - -// This is a reference to ada::url_components::omitted -// It represents "uint32_t(-1)" -#define ada_url_omitted 0xffffffff - -// string that points to data stored in the ada_url instance -// -// Lifetime rules: -// - This pointer remains valid only while the underlying ada_url is unchanged. -// - Any mutation (e.g., ada_set_* and ada_clear_*) may invalidate previously -// returned ada_string pointers. -typedef struct { - const char* data; - size_t length; -} ada_string; - -// string that must be freed by the caller -typedef struct { - const char* data; - size_t length; -} ada_owned_string; - -typedef struct { - uint32_t protocol_end; - uint32_t username_end; - uint32_t host_start; - uint32_t host_end; - uint32_t port; - uint32_t pathname_start; - uint32_t search_start; - uint32_t hash_start; -} ada_url_components; - -typedef void* ada_url; - -// input should be a null terminated C string (ASCII or UTF-8) -// you must call ada_free on the returned pointer -ada_url ada_parse(const char* input, size_t length); -ada_url ada_parse_with_base(const char* input, size_t input_length, - const char* base, size_t base_length); - -// input and base should be a null terminated C strings -bool ada_can_parse(const char* input, size_t length); -bool ada_can_parse_with_base(const char* input, size_t input_length, - const char* base, size_t base_length); - -void ada_free(ada_url result); -void ada_free_owned_string(ada_owned_string owned); -ada_url ada_copy(ada_url input); - -bool ada_is_valid(ada_url result); - -// url_aggregator getters -// if ada_is_valid(result)) is false, an empty string is returned -// -// Important: this invalidation rule applies only to getters that return -// ada_string. The ada_get_origin getter returns ada_owned_string. -ada_owned_string ada_get_origin(ada_url result); -ada_string ada_get_href(ada_url result); -ada_string ada_get_username(ada_url result); -ada_string ada_get_password(ada_url result); -ada_string ada_get_port(ada_url result); -ada_string ada_get_hash(ada_url result); -ada_string ada_get_host(ada_url result); -ada_string ada_get_hostname(ada_url result); -ada_string ada_get_pathname(ada_url result); -ada_string ada_get_search(ada_url result); -ada_string ada_get_protocol(ada_url result); -uint8_t ada_get_host_type(ada_url result); -uint8_t ada_get_scheme_type(ada_url result); - -// url_aggregator setters -// if ada_is_valid(result)) is false, the setters have no effect -// input should be a null terminated C string -bool ada_set_href(ada_url result, const char* input, size_t length); -bool ada_set_host(ada_url result, const char* input, size_t length); -bool ada_set_hostname(ada_url result, const char* input, size_t length); -bool ada_set_protocol(ada_url result, const char* input, size_t length); -bool ada_set_username(ada_url result, const char* input, size_t length); -bool ada_set_password(ada_url result, const char* input, size_t length); -bool ada_set_port(ada_url result, const char* input, size_t length); -bool ada_set_pathname(ada_url result, const char* input, size_t length); -void ada_set_search(ada_url result, const char* input, size_t length); -void ada_set_hash(ada_url result, const char* input, size_t length); - -// url_aggregator clear methods -void ada_clear_port(ada_url result); -void ada_clear_hash(ada_url result); -void ada_clear_search(ada_url result); - -// url_aggregator functions -// if ada_is_valid(result) is false, functions below will return false -bool ada_has_credentials(ada_url result); -bool ada_has_empty_hostname(ada_url result); -bool ada_has_hostname(ada_url result); -bool ada_has_non_empty_username(ada_url result); -bool ada_has_non_empty_password(ada_url result); -bool ada_has_port(ada_url result); -bool ada_has_password(ada_url result); -bool ada_has_hash(ada_url result); -bool ada_has_search(ada_url result); - -// returns a pointer to the internal url_aggregator::url_components -const ada_url_components* ada_get_components(ada_url result); - -// idna methods -ada_owned_string ada_idna_to_unicode(const char* input, size_t length); -ada_owned_string ada_idna_to_ascii(const char* input, size_t length); - -// url search params -typedef void* ada_url_search_params; - -// Represents an std::vector -typedef void* ada_strings; -typedef void* ada_url_search_params_keys_iter; -typedef void* ada_url_search_params_values_iter; - -typedef struct { - ada_string key; - ada_string value; -} ada_string_pair; - -typedef void* ada_url_search_params_entries_iter; - -ada_url_search_params ada_parse_search_params(const char* input, size_t length); -void ada_free_search_params(ada_url_search_params result); - -size_t ada_search_params_size(ada_url_search_params result); -void ada_search_params_sort(ada_url_search_params result); -ada_owned_string ada_search_params_to_string(ada_url_search_params result); - -void ada_search_params_append(ada_url_search_params result, const char* key, - size_t key_length, const char* value, - size_t value_length); -void ada_search_params_set(ada_url_search_params result, const char* key, - size_t key_length, const char* value, - size_t value_length); -void ada_search_params_remove(ada_url_search_params result, const char* key, - size_t key_length); -void ada_search_params_remove_value(ada_url_search_params result, - const char* key, size_t key_length, - const char* value, size_t value_length); -bool ada_search_params_has(ada_url_search_params result, const char* key, - size_t key_length); -bool ada_search_params_has_value(ada_url_search_params result, const char* key, - size_t key_length, const char* value, - size_t value_length); -ada_string ada_search_params_get(ada_url_search_params result, const char* key, - size_t key_length); -ada_strings ada_search_params_get_all(ada_url_search_params result, - const char* key, size_t key_length); -void ada_search_params_reset(ada_url_search_params result, const char* input, - size_t length); -ada_url_search_params_keys_iter ada_search_params_get_keys( - ada_url_search_params result); -ada_url_search_params_values_iter ada_search_params_get_values( - ada_url_search_params result); -ada_url_search_params_entries_iter ada_search_params_get_entries( - ada_url_search_params result); - -void ada_free_strings(ada_strings result); -size_t ada_strings_size(ada_strings result); -ada_string ada_strings_get(ada_strings result, size_t index); - -void ada_free_search_params_keys_iter(ada_url_search_params_keys_iter result); -ada_string ada_search_params_keys_iter_next( - ada_url_search_params_keys_iter result); -bool ada_search_params_keys_iter_has_next( - ada_url_search_params_keys_iter result); - -void ada_free_search_params_values_iter( - ada_url_search_params_values_iter result); -ada_string ada_search_params_values_iter_next( - ada_url_search_params_values_iter result); -bool ada_search_params_values_iter_has_next( - ada_url_search_params_values_iter result); - -void ada_free_search_params_entries_iter( - ada_url_search_params_entries_iter result); -ada_string_pair ada_search_params_entries_iter_next( - ada_url_search_params_entries_iter result); -bool ada_search_params_entries_iter_has_next( - ada_url_search_params_entries_iter result); - -// max URL length configuration -void ada_set_max_input_length(uint32_t length); -uint32_t ada_get_max_input_length(void); - -// Definitions for Ada's version number. -typedef struct { - int major; - int minor; - int revision; -} ada_version_components; - -const char* ada_get_version(); -ada_version_components ada_get_version_components(); - -#endif // ADA_C_H diff --git a/deps/wasi_to_unknown.cpp b/deps/wasi_to_unknown.cpp deleted file mode 100644 index 06b06ed..0000000 --- a/deps/wasi_to_unknown.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Some shims for WASI symbols used by the WASI libc environment initializer, -// but not actually required by Ada. This allows to compile Ada Rust to -// wasm32-unknown-unknown with WASI SDK. - -#include - -extern "C" { - -int32_t __imported_wasi_snapshot_preview1_environ_get(int32_t, int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_environ_sizes_get(int32_t, int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_fd_close(int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_fd_fdstat_get(int32_t, int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_fd_read(int32_t, - int32_t, - int32_t, - int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t, - int64_t, - int32_t, - int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_fd_write(int32_t, - int32_t, - int32_t, - int32_t) { - __builtin_unreachable(); -} - -int32_t __imported_wasi_snapshot_preview1_sched_yield() { - return 0; -} - -_Noreturn void __imported_wasi_snapshot_preview1_proc_exit(int32_t) { - __builtin_unreachable(); -} - -} // extern "C" diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..05744f6 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,90 @@ +# Architecture + +## Upstream pin and scope + +The native Rust implementation is mapped against `ada-url/ada` commit +`30f3f3020c5a979b62f90dc9c37fd45de3cc84d7`. Full scope means: + +1. URL parsing, serialization, base resolution, file URLs, opaque URLs, IPv4, + IPv6, IDNA, origin calculation, setters, and configurable length limits. +2. URLSearchParams, including stable UTF-16 sorting semantics. +3. The upstream WPT, Ada-extra, UTS #46, and setter fixtures. + +The C ABI and the multi-string `ada::url` representation are not ported. Rust's +public `Url` corresponds to `ada::url_aggregator`. + +## Data layout + +`Url` owns exactly one normalized UTF-8 `String`. `Components` stores eight +32-bit fields: scheme end, username end, host start/end, numeric port, pathname +start, query start, and fragment start. `u32::MAX` denotes an absent component. +Small enums and boolean properties are packed separately. + +Every public component getter is a checked slice of that buffer and allocates +nothing. Parser construction and every mutation validate the component ordering +before publishing the value. + +The standard `String` is the baseline. Inline storage will be adopted only if +allocation and end-to-end benchmarks show that its larger object and extra +branches are a net win on the corpus. + +## Parser pipeline + +1. Enforce the raw-input length limit. +2. Try conservative native parsers for normalized HTTP(S), normalized file + URLs, common authority URLs, opaque URLs, WHATWG IPv4/IPv6 hosts, and direct + references. Inputs that can be copied directly avoid intermediate + allocations. +3. Continue through the in-tree state handlers for file, opaque, host, base + resolution, and setter edge cases. +4. Enforce normalized-output length, derive compact offsets, validate them, + and publish the immutable `Url`. + +The runtime parser, IDNA implementation, percent encoder, and delimiter scans +are dependency-free. The `url` crate remains a dev-only differential benchmark +and test oracle. + +## Scanning and SIMD + +Character properties are compile-time byte lookup tables. The in-tree byte +scanners use 16-byte NEON on AArch64 and SSE2 on x86-64, matching Ada's +architecture-specific delimiter scans. Short inputs, unsupported targets, and +Miri use a word-at-a-time scalar implementation. + +The crate denies unsafe code everywhere except the architecture-specific +scanner module. Each kernel: + +- lives in an architecture-specific module with a safe wrapper; +- uses only target-baseline instructions (NEON on AArch64 and SSE2 on x86-64); +- never reads outside the source allocation; +- has a byte-for-byte scalar oracle and randomized differential tests; +- documents every unsafe operation and compiles with + `unsafe_op_in_unsafe_fn` denied. + +## Correctness gates + +- 100% of pinned `urltestdata.json`, Ada extras, long-input tests, setters, + percent-encoding, `IdnaTestV2`, `toascii`, and URLSearchParams fixtures. +- Mutation failures are transactional. +- All offsets validate after every parse and setter in debug/test builds. +- `cargo test`, Clippy, rustfmt, docs, Miri for safe targets, and fuzz smoke + tests pass. + +## Performance protocol + +Rust, C++ Ada, and comparison parsers receive the same preloaded byte strings. +The benchmark consumes the normalized serialization so validation-only work +cannot masquerade as parsing. Build settings, target features, CPU model, +compiler versions, warmup, sample count, and dataset revision are recorded. + +Track: + +- nanoseconds per URL and GiB/s; +- p50/p95/p99 by input family; +- allocations and allocated bytes per URL; +- `parse`, `can_parse`, component access, setters, IDNA, IPv4, percent + encoding, and SearchParams separately; +- portable binaries and `target-cpu=native` binaries. + +A performance result is accepted only when normalized outputs match and the +median regression is no worse than 3% on any primary corpus. diff --git a/src/bytes.rs b/src/bytes.rs new file mode 100644 index 0000000..5ee7bdc --- /dev/null +++ b/src/bytes.rs @@ -0,0 +1,477 @@ +//! Small dependency-free byte-search primitives. +//! +//! The SIMD kernels mirror Ada's delimiter scanners: NEON is baseline on +//! AArch64 and SSE2 is baseline on x86-64. Other targets, Miri, and short +//! inputs use the scalar implementation. + +/// Returns the index of the first matching byte. +#[inline] +pub(crate) fn find_byte(needle: u8, haystack: &[u8]) -> Option { + #[cfg(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) + ))] + { + simd::find_byte(needle, haystack) + } + + #[cfg(not(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) + )))] + scalar::find_byte(needle, haystack) +} + +/// Returns the index of the first byte matching either needle. +#[inline] +pub(crate) fn find_byte2(first: u8, second: u8, haystack: &[u8]) -> Option { + #[cfg(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) + ))] + { + simd::find_byte2(first, second, haystack) + } + + #[cfg(not(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) + )))] + scalar::find_byte2(first, second, haystack) +} + +/// Returns the index of the first byte matching any needle. +#[inline] +pub(crate) fn find_byte3(first: u8, second: u8, third: u8, haystack: &[u8]) -> Option { + #[cfg(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) + ))] + { + simd::find_byte3(first, second, third, haystack) + } + + #[cfg(not(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) + )))] + scalar::find_byte3(first, second, third, haystack) +} + +mod scalar { + const BYTE_ONES: u64 = u64::MAX / 0xff; + const BYTE_HIGHS: u64 = BYTE_ONES * 0x80; + + #[inline] + const fn contains_zero_byte(value: u64) -> bool { + value.wrapping_sub(BYTE_ONES) & !value & BYTE_HIGHS != 0 + } + + #[inline] + pub(super) fn find_byte(needle: u8, haystack: &[u8]) -> Option { + let repeated = BYTE_ONES * u64::from(needle); + let mut chunks = haystack.chunks_exact(8); + for (chunk_index, chunk) in chunks.by_ref().enumerate() { + let word = u64::from_ne_bytes(chunk.try_into().expect("eight-byte chunk")); + if contains_zero_byte(word ^ repeated) + && let Some(offset) = chunk.iter().position(|byte| *byte == needle) + { + return Some(chunk_index * 8 + offset); + } + } + let tail_start = haystack.len() - chunks.remainder().len(); + chunks + .remainder() + .iter() + .position(|byte| *byte == needle) + .map(|offset| tail_start + offset) + } + + #[inline] + pub(super) fn find_byte2(first: u8, second: u8, haystack: &[u8]) -> Option { + let repeated_first = BYTE_ONES * u64::from(first); + let repeated_second = BYTE_ONES * u64::from(second); + find_chunked(haystack, |word| { + contains_zero_byte(word ^ repeated_first) || contains_zero_byte(word ^ repeated_second) + }) + .and_then(|start| { + haystack[start..start + 8.min(haystack.len() - start)] + .iter() + .position(|byte| *byte == first || *byte == second) + .map(|offset| start + offset) + }) + .or_else(|| { + let tail_start = haystack.len() / 8 * 8; + haystack[tail_start..] + .iter() + .position(|byte| *byte == first || *byte == second) + .map(|offset| tail_start + offset) + }) + } + + #[inline] + pub(super) fn find_byte3(first: u8, second: u8, third: u8, haystack: &[u8]) -> Option { + let repeated_first = BYTE_ONES * u64::from(first); + let repeated_second = BYTE_ONES * u64::from(second); + let repeated_third = BYTE_ONES * u64::from(third); + find_chunked(haystack, |word| { + contains_zero_byte(word ^ repeated_first) + || contains_zero_byte(word ^ repeated_second) + || contains_zero_byte(word ^ repeated_third) + }) + .and_then(|start| { + haystack[start..start + 8.min(haystack.len() - start)] + .iter() + .position(|byte| *byte == first || *byte == second || *byte == third) + .map(|offset| start + offset) + }) + .or_else(|| { + let tail_start = haystack.len() / 8 * 8; + haystack[tail_start..] + .iter() + .position(|byte| *byte == first || *byte == second || *byte == third) + .map(|offset| tail_start + offset) + }) + } + + #[inline] + fn find_chunked(haystack: &[u8], mut matches: impl FnMut(u64) -> bool) -> Option { + haystack + .chunks_exact(8) + .enumerate() + .find_map(|(index, chunk)| { + let word = u64::from_ne_bytes(chunk.try_into().expect("eight-byte chunk")); + matches(word).then_some(index * 8) + }) + } +} + +#[cfg(all( + not(miri), + any( + target_arch = "aarch64", + target_arch = "x86_64", + all(target_arch = "x86", target_feature = "sse2") + ) +))] +#[allow(unsafe_code)] +mod simd { + const LANES: usize = 16; + + #[inline] + pub(super) fn find_byte(needle: u8, haystack: &[u8]) -> Option { + if haystack.len() < LANES { + return super::scalar::find_byte(needle, haystack); + } + + // SAFETY: this module is compiled only where its SIMD ISA is a target + // baseline (NEON on AArch64 or SSE2 on x86-64/enabled x86). + let splat = unsafe { architecture::splat(needle) }; + search(haystack, |pointer| { + // SAFETY: `search` supplies a pointer to 16 readable bytes. + unsafe { architecture::mask1(pointer, splat) } + }) + } + + #[inline] + pub(super) fn find_byte2(first: u8, second: u8, haystack: &[u8]) -> Option { + if haystack.len() < LANES { + return super::scalar::find_byte2(first, second, haystack); + } + + // SAFETY: this module is compiled only where its SIMD ISA is a target + // baseline (NEON on AArch64 or SSE2 on x86-64/enabled x86). + let first = unsafe { architecture::splat(first) }; + // SAFETY: same target guarantee as above. + let second = unsafe { architecture::splat(second) }; + search(haystack, |pointer| { + // SAFETY: `search` supplies a pointer to 16 readable bytes. + unsafe { architecture::mask2(pointer, first, second) } + }) + } + + #[inline] + pub(super) fn find_byte3(first: u8, second: u8, third: u8, haystack: &[u8]) -> Option { + if haystack.len() < LANES { + return super::scalar::find_byte3(first, second, third, haystack); + } + + // SAFETY: this module is compiled only where its SIMD ISA is a target + // baseline (NEON on AArch64 or SSE2 on x86-64/enabled x86). + let first = unsafe { architecture::splat(first) }; + // SAFETY: same target guarantee as above. + let second = unsafe { architecture::splat(second) }; + // SAFETY: same target guarantee as above. + let third = unsafe { architecture::splat(third) }; + search(haystack, |pointer| { + // SAFETY: `search` supplies a pointer to 16 readable bytes. + unsafe { architecture::mask3(pointer, first, second, third) } + }) + } + + #[inline] + fn search(haystack: &[u8], mut first_match_at: impl FnMut(*const u8) -> u32) -> Option { + let mut offset = 0; + while offset + LANES <= haystack.len() { + // SAFETY: the loop condition proves that 16 bytes remain. + let lane = first_match_at(unsafe { haystack.as_ptr().add(offset) }); + if lane != 0 { + return Some(offset + lane as usize - 1); + } + offset += LANES; + } + + if offset < haystack.len() { + let tail_start = haystack.len() - LANES; + // SAFETY: `len >= LANES`, so the final 16-byte load is in bounds. + let lane = first_match_at(unsafe { haystack.as_ptr().add(tail_start) }); + if lane != 0 { + return Some(tail_start + lane as usize - 1); + } + } + None + } + + #[cfg(target_arch = "aarch64")] + mod architecture { + use core::arch::aarch64::{ + uint8x16_t, vceqq_u8, vdupq_n_u8, vget_lane_u64, vld1q_u8, vorrq_u8, + vreinterpret_u64_u8, vreinterpretq_u16_u8, vshrn_n_u16, + }; + + pub(super) type Vector = uint8x16_t; + + #[inline(always)] + pub(super) unsafe fn splat(byte: u8) -> Vector { + // SAFETY: NEON is mandatory on AArch64. + unsafe { vdupq_n_u8(byte) } + } + + #[inline(always)] + unsafe fn load(pointer: *const u8) -> Vector { + // SAFETY: the caller guarantees 16 readable bytes at `pointer`. + unsafe { vld1q_u8(pointer) } + } + + #[inline(always)] + unsafe fn bitmask(comparison: Vector) -> u32 { + // Each matching byte is 0xff. Shift four bits out of every u16, + // producing four consecutive set bits per matching byte. The + // trailing-zero count therefore identifies the first byte. + // SAFETY: NEON is mandatory on AArch64. + let nibbles = unsafe { vshrn_n_u16::<4>(vreinterpretq_u16_u8(comparison)) }; + // SAFETY: NEON is mandatory on AArch64. + let mask = unsafe { vget_lane_u64::<0>(vreinterpret_u64_u8(nibbles)) }; + if mask == 0 { + 0 + } else { + (mask.trailing_zeros() >> 2) + 1 + } + } + + #[inline(always)] + pub(super) unsafe fn mask1(pointer: *const u8, first: Vector) -> u32 { + // SAFETY: inherited from this function's caller. + let input = unsafe { load(pointer) }; + // SAFETY: NEON is mandatory on AArch64. + let comparison = unsafe { vceqq_u8(input, first) }; + // SAFETY: NEON is mandatory on AArch64. + unsafe { bitmask(comparison) } + } + + #[inline(always)] + pub(super) unsafe fn mask2(pointer: *const u8, first: Vector, second: Vector) -> u32 { + // SAFETY: inherited from this function's caller. + let input = unsafe { load(pointer) }; + // SAFETY: NEON is mandatory on AArch64. + let comparison = unsafe { vorrq_u8(vceqq_u8(input, first), vceqq_u8(input, second)) }; + // SAFETY: NEON is mandatory on AArch64. + unsafe { bitmask(comparison) } + } + + #[inline(always)] + pub(super) unsafe fn mask3( + pointer: *const u8, + first: Vector, + second: Vector, + third: Vector, + ) -> u32 { + // SAFETY: inherited from this function's caller. + let input = unsafe { load(pointer) }; + // SAFETY: NEON is mandatory on AArch64. + let comparison = unsafe { + vorrq_u8( + vorrq_u8(vceqq_u8(input, first), vceqq_u8(input, second)), + vceqq_u8(input, third), + ) + }; + // SAFETY: NEON is mandatory on AArch64. + unsafe { bitmask(comparison) } + } + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + mod architecture { + #[cfg(target_arch = "x86")] + use core::arch::x86::{ + __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128, + _mm_set1_epi8, + }; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::{ + __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128, + _mm_set1_epi8, + }; + + pub(super) type Vector = __m128i; + + #[inline(always)] + pub(super) unsafe fn splat(byte: u8) -> Vector { + // SAFETY: SSE2 is a target baseline wherever this module compiles. + unsafe { _mm_set1_epi8(byte as i8) } + } + + #[inline(always)] + unsafe fn load(pointer: *const u8) -> Vector { + // SAFETY: the caller guarantees 16 readable bytes. The unaligned + // intrinsic imposes no alignment requirement. + unsafe { _mm_loadu_si128(pointer.cast::<__m128i>()) } + } + + #[inline(always)] + pub(super) unsafe fn mask1(pointer: *const u8, first: Vector) -> u32 { + // SAFETY: inherited from this function's caller. + let input = unsafe { load(pointer) }; + // SAFETY: SSE2 is a target baseline wherever this module compiles. + let mask = unsafe { _mm_movemask_epi8(_mm_cmpeq_epi8(input, first)) as u32 }; + if mask == 0 { + 0 + } else { + mask.trailing_zeros() + 1 + } + } + + #[inline(always)] + pub(super) unsafe fn mask2(pointer: *const u8, first: Vector, second: Vector) -> u32 { + // SAFETY: inherited from this function's caller. + let input = unsafe { load(pointer) }; + // SAFETY: SSE2 is a target baseline wherever this module compiles. + let mask = unsafe { + _mm_movemask_epi8(_mm_or_si128( + _mm_cmpeq_epi8(input, first), + _mm_cmpeq_epi8(input, second), + )) as u32 + }; + if mask == 0 { + 0 + } else { + mask.trailing_zeros() + 1 + } + } + + #[inline(always)] + pub(super) unsafe fn mask3( + pointer: *const u8, + first: Vector, + second: Vector, + third: Vector, + ) -> u32 { + // SAFETY: inherited from this function's caller. + let input = unsafe { load(pointer) }; + // SAFETY: SSE2 is a target baseline wherever this module compiles. + let mask = unsafe { + _mm_movemask_epi8(_mm_or_si128( + _mm_or_si128(_mm_cmpeq_epi8(input, first), _mm_cmpeq_epi8(input, second)), + _mm_cmpeq_epi8(input, third), + )) as u32 + }; + if mask == 0 { + 0 + } else { + mask.trailing_zeros() + 1 + } + } + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use super::{find_byte, find_byte2, find_byte3, scalar}; + + #[test] + fn finds_needles_across_vector_boundaries() { + let input = b"0123456789abcdefghijklmnopqrstuv"; + assert_eq!(find_byte(b'g', input), Some(16)); + assert_eq!(find_byte2(b'x', b'v', input), Some(31)); + assert_eq!(find_byte3(b'x', b'h', b'v', input), Some(17)); + assert_eq!(find_byte(b'X', input), None); + assert_eq!(find_byte2(b'X', b'Y', input), None); + assert_eq!(find_byte3(b'X', b'Y', b'Z', input), None); + } + + #[test] + fn finds_needles_in_short_inputs() { + assert_eq!(find_byte(b'c', b"abc"), Some(2)); + assert_eq!(find_byte2(b'x', b'b', b"abc"), Some(1)); + assert_eq!(find_byte3(b'x', b'y', b'a', b"abc"), Some(0)); + } + + #[test] + fn simd_and_scalar_match_oracles_at_every_alignment() { + let storage: Vec = (0..192) + .map(|index| (index * 37 + index / 7 * 11) as u8) + .collect(); + // Miri exercises the scalar fallback; a smaller matrix keeps the + // interpreted run practical. Native tests retain all alignments and + // cross several SIMD-vector boundaries. + let alignment_count = if cfg!(miri) { 4 } else { 32 }; + let maximum_length = if cfg!(miri) { 40 } else { 128 }; + + for start in 0..alignment_count { + for length in 0..=maximum_length { + let input = &storage[start..start + length]; + for needle in [0, 1, 9, 0x7f, 0x80, 0xfe, 0xff] { + let expected = input.iter().position(|byte| *byte == needle); + assert_eq!(scalar::find_byte(needle, input), expected); + assert_eq!(find_byte(needle, input), expected); + } + + let expected2 = input.iter().position(|byte| matches!(*byte, 1 | 0xff)); + assert_eq!(scalar::find_byte2(1, 0xff, input), expected2); + assert_eq!(find_byte2(1, 0xff, input), expected2); + + let expected3 = input + .iter() + .position(|byte| matches!(*byte, 0 | 0x80 | 0xff)); + assert_eq!(scalar::find_byte3(0, 0x80, 0xff, input), expected3); + assert_eq!(find_byte3(0, 0x80, 0xff, input), expected3); + } + } + } +} diff --git a/src/components.rs b/src/components.rs new file mode 100644 index 0000000..3f29e49 --- /dev/null +++ b/src/components.rs @@ -0,0 +1,205 @@ +//! Compact URL component metadata. + +/// Sentinel stored in a component offset when that component is absent. +pub(crate) const OMITTED: u32 = u32::MAX; + +/// Byte offsets into a URL's normalized serialization. +/// +/// Offsets point at delimiters where useful: `protocol_end` is one byte after +/// `:`, while `search_start` and `hash_start` point at `?` and `#`. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(C)] +pub struct Components { + /// End of the protocol, including its trailing colon. + pub protocol_end: u32, + /// End of the username. + pub username_end: u32, + /// Start of the host. + pub host_start: u32, + /// End of the host. + pub host_end: u32, + /// Parsed numeric port, or the internal omitted sentinel. + port: u32, + /// Start of the pathname. + pub pathname_start: u32, + /// Start of the query delimiter, or the internal omitted sentinel. + search_start: u32, + /// Start of the fragment delimiter, or the internal omitted sentinel. + hash_start: u32, +} + +impl Components { + /// Creates component metadata. + #[allow(clippy::too_many_arguments)] + pub(crate) const fn new( + protocol_end: u32, + username_end: u32, + host_start: u32, + host_end: u32, + port: Option, + pathname_start: u32, + search_start: Option, + hash_start: Option, + ) -> Self { + Self { + protocol_end, + username_end, + host_start, + host_end, + port: match port { + Some(value) => value as u32, + None => OMITTED, + }, + pathname_start, + search_start: match search_start { + Some(value) => value, + None => OMITTED, + }, + hash_start: match hash_start { + Some(value) => value, + None => OMITTED, + }, + } + } + + /// Returns the parsed non-default port. + #[inline] + #[must_use] + pub const fn port(self) -> Option { + if self.port == OMITTED { + None + } else { + Some(self.port as u16) + } + } + + /// Returns the query delimiter offset. + #[inline] + #[must_use] + pub const fn search_start(self) -> Option { + if self.search_start == OMITTED { + None + } else { + Some(self.search_start) + } + } + + /// Returns the fragment delimiter offset. + #[inline] + #[must_use] + pub const fn hash_start(self) -> Option { + if self.hash_start == OMITTED { + None + } else { + Some(self.hash_start) + } + } + + pub(crate) fn validate(self, length: usize) -> bool { + let Ok(length) = u32::try_from(length) else { + return false; + }; + if !(self.protocol_end <= self.username_end + && self.username_end <= self.host_start + && self.host_start <= self.host_end + && self.host_end <= self.pathname_start + && self.pathname_start <= length) + { + return false; + } + + let hash = self.hash_start().unwrap_or(length); + let search = self.search_start().unwrap_or(hash); + self.pathname_start <= search && search <= hash && hash <= length + } +} + +/// Public URL component offsets, compatible with the historical `ada-url` +/// crate API. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct UrlComponents { + /// End of the protocol, including its trailing colon. + pub protocol_end: u32, + /// End of the username. + pub username_end: u32, + /// Start of the host, including the `@` delimiter when credentials exist. + pub host_start: u32, + /// End of the hostname, before an optional port. + pub host_end: u32, + /// Parsed non-default port. + pub port: Option, + /// Start of the pathname. + pub pathname_start: Option, + /// Start of the query delimiter. + pub search_start: Option, + /// Start of the fragment delimiter. + pub hash_start: Option, +} + +/// The normalized host representation. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum HostType { + /// A domain or opaque host. + #[default] + Domain, + /// An IPv4 address. + IPV4, + /// An IPv6 address. + IPV6, +} + +/// A compact classification of known schemes. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum SchemeType { + /// `http` + Http, + /// A non-special scheme. + #[default] + NotSpecial, + /// `https` + Https, + /// `ws` + Ws, + /// `ftp` + Ftp, + /// `wss` + Wss, + /// `file` + File, +} + +impl SchemeType { + #[inline] + pub(crate) fn from_scheme(scheme: &str) -> Self { + match scheme { + "http" => Self::Http, + "https" => Self::Https, + "ws" => Self::Ws, + "ftp" => Self::Ftp, + "wss" => Self::Wss, + "file" => Self::File, + _ => Self::NotSpecial, + } + } + + /// Returns whether this is a WHATWG special scheme. + #[inline] + #[must_use] + pub const fn is_special(self) -> bool { + !matches!(self, Self::NotSpecial) + } + + /// Returns the scheme's default port. + #[inline] + #[must_use] + pub const fn default_port(self) -> Option { + match self { + Self::Ftp => Some(21), + Self::Http | Self::Ws => Some(80), + Self::Https | Self::Wss => Some(443), + Self::File | Self::NotSpecial => None, + } + } +} diff --git a/src/encoding.rs b/src/encoding.rs new file mode 100644 index 0000000..5abc4fe --- /dev/null +++ b/src/encoding.rs @@ -0,0 +1,313 @@ +//! WHATWG percent-encoding helpers. + +use alloc::{borrow::Cow, string::String, vec::Vec}; +use core::{fmt, str::Bytes}; + +const HEX: &[u8; 16] = b"0123456789ABCDEF"; + +/// A WHATWG percent-encode set. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum PercentEncodeSet { + /// C0 controls and all non-ASCII bytes. + C0Control, + /// The fragment percent-encode set. + Fragment, + /// The query percent-encode set. + Query, + /// The special-query percent-encode set. + SpecialQuery, + /// The path percent-encode set. + Path, + /// The userinfo percent-encode set. + UserInfo, + /// The component percent-encode set. + Component, + /// The `application/x-www-form-urlencoded` percent-encode set. + FormUrlencoded, +} + +impl PercentEncodeSet { + #[inline] + const fn mask(self) -> &'static [u32; 4] { + match self { + Self::C0Control => &C0_CONTROL_MASK, + Self::Fragment => &FRAGMENT_MASK, + Self::Query => &QUERY_MASK, + Self::SpecialQuery => &SPECIAL_QUERY_MASK, + Self::Path => &PATH_MASK, + Self::UserInfo => &USERINFO_MASK, + Self::Component => &COMPONENT_MASK, + Self::FormUrlencoded => &FORM_URLENCODED_MASK, + } + } +} + +const fn add(mut mask: [u32; 4], byte: u8) -> [u32; 4] { + mask[byte as usize / 32] |= 1 << (byte as usize % 32); + mask +} + +const fn add_bytes(mut mask: [u32; 4], bytes: &[u8]) -> [u32; 4] { + let mut index = 0; + while index < bytes.len() { + mask = add(mask, bytes[index]); + index += 1; + } + mask +} + +#[inline] +const fn mask_contains(mask: &[u32; 4], byte: u8) -> bool { + mask[byte as usize / 32] & (1 << (byte as usize % 32)) != 0 +} + +const C0_CONTROL_MASK: [u32; 4] = [u32::MAX, 0, 0, 1 << 31]; +const FRAGMENT_MASK: [u32; 4] = add_bytes(C0_CONTROL_MASK, b" \"<>`"); +const QUERY_MASK: [u32; 4] = add_bytes(C0_CONTROL_MASK, b" \"#<>"); +const SPECIAL_QUERY_MASK: [u32; 4] = add(QUERY_MASK, b'\''); +const PATH_MASK: [u32; 4] = add_bytes(C0_CONTROL_MASK, b" \"#<>?^`{}"); +const USERINFO_MASK: [u32; 4] = add_bytes(PATH_MASK, b"/:;=@[\\]|"); +const COMPONENT_MASK: [u32; 4] = add_bytes(USERINFO_MASK, b"$%&+,"); + +const fn form_urlencoded_mask() -> [u32; 4] { + let mut mask = [u32::MAX; 4]; + let mut byte = b'0'; + while byte <= b'9' { + mask[byte as usize / 32] &= !(1 << (byte as usize % 32)); + byte += 1; + } + byte = b'A'; + while byte <= b'Z' { + mask[byte as usize / 32] &= !(1 << (byte as usize % 32)); + byte += 1; + } + byte = b'a'; + while byte <= b'z' { + mask[byte as usize / 32] &= !(1 << (byte as usize % 32)); + byte += 1; + } + for_remove(mask, b"*-._") +} + +const fn for_remove(mut mask: [u32; 4], bytes: &[u8]) -> [u32; 4] { + let mut index = 0; + while index < bytes.len() { + let byte = bytes[index]; + mask[byte as usize / 32] &= !(1 << (byte as usize % 32)); + index += 1; + } + mask +} + +const FORM_URLENCODED_MASK: [u32; 4] = form_urlencoded_mask(); + +/// Percent-encodes UTF-8 using a WHATWG encode set. +#[must_use] +pub fn percent_encode(input: &str, set: PercentEncodeSet) -> String { + let mut output = String::with_capacity(input.len()); + append_percent_encoded(&mut output, input, set); + output +} + +/// Appends a percent-encoded UTF-8 string without an intermediate allocation. +#[inline] +pub(crate) fn append_percent_encoded(output: &mut String, input: &str, set: PercentEncodeSet) { + let mask = set.mask(); + // Normalized web URLs overwhelmingly contain no byte from the active + // encode set. Scan bytes first so this case becomes one vectorizable pass + // followed by one bulk copy instead of UTF-8 decoding every character. + if input + .as_bytes() + .iter() + .all(|byte| *byte < 0x80 && !mask_contains(mask, *byte)) + { + output.push_str(input); + return; + } + + let mut copied = 0; + for (index, character) in input.char_indices() { + let byte = character as u32; + if byte >= 0x80 || mask_contains(mask, byte as u8) { + output.push_str(&input[copied..index]); + let mut utf8 = [0; 4]; + for &encoded in character.encode_utf8(&mut utf8).as_bytes() { + output.push('%'); + output.push(char::from(HEX[usize::from(encoded >> 4)])); + output.push(char::from(HEX[usize::from(encoded & 0x0f)])); + } + copied = index + character.len_utf8(); + } + } + output.push_str(&input[copied..]); +} + +/// Appends one raw filesystem path segment for a special `file:` URL. +#[cfg(feature = "std")] +pub(crate) fn append_file_path_segment(output: &mut String, input: &[u8]) { + for &byte in input { + if byte >= 0x80 + || byte <= 0x1f + || byte == 0x7f + || matches!( + byte, + b' ' | b'"' | b'#' | b'%' | b'/' | b'<' | b'>' | b'?' | b'\\' | b'`' | b'{' | b'}' + ) + { + output.push('%'); + output.push(char::from(HEX[usize::from(byte >> 4)])); + output.push(char::from(HEX[usize::from(byte & 0x0f)])); + } else { + output.push(char::from(byte)); + } + } +} + +/// Lazily percent-encodes UTF-8 for direct extension into a destination. +pub(crate) fn utf8_percent_encode(input: &str, set: PercentEncodeSet) -> PercentEncoded<'_> { + PercentEncoded { + bytes: input.bytes(), + mask: set.mask(), + pending: [0; 2], + pending_index: 2, + } +} + +pub(crate) struct PercentEncoded<'a> { + bytes: Bytes<'a>, + mask: &'static [u32; 4], + pending: [u8; 2], + pending_index: usize, +} + +impl Clone for PercentEncoded<'_> { + fn clone(&self) -> Self { + Self { + bytes: self.bytes.clone(), + mask: self.mask, + pending: self.pending, + pending_index: self.pending_index, + } + } +} + +impl Iterator for PercentEncoded<'_> { + type Item = char; + + #[inline] + fn next(&mut self) -> Option { + if self.pending_index < 2 { + let byte = self.pending[self.pending_index]; + self.pending_index += 1; + return Some(char::from(byte)); + } + let byte = self.bytes.next()?; + if byte < 0x80 && !mask_contains(self.mask, byte) { + return Some(char::from(byte)); + } + self.pending = [HEX[usize::from(byte >> 4)], HEX[usize::from(byte & 0x0f)]]; + self.pending_index = 0; + Some('%') + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.bytes.len() + (2 - self.pending_index); + (remaining, remaining.checked_mul(3)) + } +} + +impl fmt::Display for PercentEncoded<'_> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + for character in self.clone() { + let mut utf8 = [0; 4]; + formatter.write_str(character.encode_utf8(&mut utf8))?; + } + Ok(()) + } +} + +/// Appends one `application/x-www-form-urlencoded` component. +pub(crate) fn append_form_component(output: &mut String, input: &str) { + let mask = PercentEncodeSet::FormUrlencoded.mask(); + for byte in input.bytes() { + if byte == b' ' { + output.push('+'); + } else if byte >= 0x80 || mask_contains(mask, byte) { + output.push('%'); + output.push(char::from(HEX[usize::from(byte >> 4)])); + output.push(char::from(HEX[usize::from(byte & 0x0f)])); + } else { + output.push(char::from(byte)); + } + } +} + +/// Percent-decodes a UTF-8 string, replacing malformed UTF-8 with U+FFFD. +#[must_use] +pub fn percent_decode(input: &str) -> String { + match decode_bytes(input) { + Cow::Borrowed(_) => String::from(input), + Cow::Owned(bytes) => String::from_utf8_lossy(&bytes).into_owned(), + } +} + +/// Strictly percent-decodes UTF-8, borrowing when no escapes are present. +pub(crate) fn percent_decode_utf8(input: &str) -> Option> { + match decode_bytes(input) { + Cow::Borrowed(_) => Some(Cow::Borrowed(input)), + Cow::Owned(bytes) => String::from_utf8(bytes).ok().map(Cow::Owned), + } +} + +fn decode_bytes(input: &str) -> Cow<'_, [u8]> { + let bytes = input.as_bytes(); + let Some(first_percent) = bytes.iter().position(|byte| *byte == b'%') else { + return Cow::Borrowed(bytes); + }; + let mut output = Vec::with_capacity(bytes.len()); + output.extend_from_slice(&bytes[..first_percent]); + let mut index = first_percent; + while index < bytes.len() { + if index + 2 < bytes.len() + && bytes[index] == b'%' + && let (Some(high), Some(low)) = + (hex_value(bytes[index + 1]), hex_value(bytes[index + 2])) + { + output.push((high << 4) | low); + index += 3; + } else { + output.push(bytes[index]); + index += 1; + } + } + Cow::Owned(output) +} + +#[inline] +const fn hex_value(byte: u8) -> Option { + match byte { + b'0'..=b'9' => Some(byte - b'0'), + b'a'..=b'f' => Some(byte - b'a' + 10), + b'A'..=b'F' => Some(byte - b'A' + 10), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::{PercentEncodeSet, percent_decode, percent_encode}; + + #[test] + fn encodes_named_sets() { + assert_eq!( + percent_encode("a b/☃", PercentEncodeSet::Path), + "a%20b/%E2%98%83" + ); + assert_eq!( + percent_encode("a b+c", PercentEncodeSet::FormUrlencoded), + "a%20b%2Bc" + ); + assert_eq!(percent_decode("caf%C3%A9"), "café"); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f8dedfc --- /dev/null +++ b/src/error.rs @@ -0,0 +1,50 @@ +//! Parse errors. + +use core::fmt; + +/// The reason URL construction failed. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum ParseErrorKind { + /// The input is not a valid WHATWG URL. + InvalidUrl, + /// The supplied base URL is invalid. + InvalidBase, + /// The raw or normalized URL exceeds the configured limit. + TooLong, + /// A setter is not permitted for this URL. + InvalidSetter, +} + +/// An error returned while parsing or mutating a URL. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct ParseError { + kind: ParseErrorKind, +} + +impl ParseError { + pub(crate) const fn new(kind: ParseErrorKind) -> Self { + Self { kind } + } + + /// Returns the error category. + #[must_use] + pub const fn kind(self) -> ParseErrorKind { + self.kind + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self.kind { + ParseErrorKind::InvalidUrl => "invalid URL", + ParseErrorKind::InvalidBase => "invalid base URL", + ParseErrorKind::TooLong => "URL exceeds the configured length limit", + ParseErrorKind::InvalidSetter => "URL component cannot be changed", + }; + formatter.write_str(message) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseError {} diff --git a/src/fast_path.rs b/src/fast_path.rs new file mode 100644 index 0000000..8a8d3d7 --- /dev/null +++ b/src/fast_path.rs @@ -0,0 +1,1304 @@ +//! Conservative fast path for already-normalized HTTP(S) URLs. + +use crate::{ + bytes::{find_byte as memchr, find_byte2 as memchr2, find_byte3 as memchr3}, + components::{Components, HostType, SchemeType}, + encoding::{ + PercentEncodeSet, append_percent_encoded, percent_decode_utf8, utf8_percent_encode, + }, + idna::domain_to_ascii, +}; +use alloc::{ + borrow::Cow, + format, + string::{String, ToString}, +}; +use core::{ + fmt::{self, Write}, + net::{Ipv4Addr, Ipv6Addr}, +}; + +pub(crate) struct FastPath { + pub buffer: String, + pub components: Components, + pub scheme_type: SchemeType, + pub host_type: HostType, + pub has_authority: bool, + pub opaque_path: bool, +} + +/// Parse the overwhelmingly common `http(s)://host` shape without running the +/// general scanner. This path is particularly important for IPv4 workloads: +/// the general DNS scanner deliberately rejects numeric final labels, which +/// otherwise makes an IPv4 URL traverse the input twice. +#[inline] +pub(crate) fn parse_bare_special(input: &str) -> Option { + let (protocol_end, host_start, scheme_type) = special_prefix(input.as_bytes())?; + let tail = input.as_bytes().get(host_start..)?; + let (raw_host, has_path_slash) = tail + .strip_suffix(b"/") + .map_or((tail, false), |host| (host, true)); + if raw_host.is_empty() || raw_host.len() > 253 { + return None; + } + if scheme_type == SchemeType::File && raw_host.eq_ignore_ascii_case(b"localhost") { + return None; + } + + if is_simple_domain(raw_host) && !ends_in_number(raw_host) { + return finish_bare_copied_host( + input, + protocol_end, + host_start, + has_path_slash, + scheme_type, + HostType::Domain, + ); + } + + if let Some(address) = raw_host + .strip_prefix(b"[") + .and_then(|host| host.strip_suffix(b"]")) + .and_then(|host| core::str::from_utf8(host).ok()) + .and_then(|host| host.parse::().ok()) + { + let mut canonical = Ipv6Buffer::default(); + write!(&mut canonical, "{address}").ok()?; + if canonical.as_bytes() == &raw_host[1..raw_host.len() - 1] { + return finish_bare_copied_host( + input, + protocol_end, + host_start, + has_path_slash, + scheme_type, + HostType::IPV6, + ); + } + } + + let address = parse_ipv4_bytes(raw_host)?; + let mut buffer = String::with_capacity(host_start + 16); + buffer.push_str(&input[..host_start]); + push_ipv4(&mut buffer, address); + let host_end = buffer.len(); + buffer.push('/'); + let components = Components::new( + u32::try_from(protocol_end).ok()?, + u32::try_from(host_start).ok()?, + u32::try_from(host_start).ok()?, + u32::try_from(host_end).ok()?, + None, + u32::try_from(host_end).ok()?, + None, + None, + ); + debug_assert!(components.validate(buffer.len())); + Some(FastPath { + buffer, + components, + scheme_type, + host_type: HostType::IPV4, + has_authority: true, + opaque_path: false, + }) +} + +fn finish_bare_copied_host( + input: &str, + protocol_end: usize, + host_start: usize, + has_path_slash: bool, + scheme_type: SchemeType, + host_type: HostType, +) -> Option { + let mut buffer = String::with_capacity(input.len() + usize::from(!has_path_slash)); + buffer.push_str(input); + if !has_path_slash { + buffer.push('/'); + } + let host_end = input.len() - usize::from(has_path_slash); + let components = Components::new( + u32::try_from(protocol_end).ok()?, + u32::try_from(host_start).ok()?, + u32::try_from(host_start).ok()?, + u32::try_from(host_end).ok()?, + None, + u32::try_from(host_end).ok()?, + None, + None, + ); + debug_assert!(components.validate(buffer.len())); + Some(FastPath { + buffer, + components, + scheme_type, + host_type, + has_authority: true, + opaque_path: false, + }) +} + +struct Ipv6Buffer { + bytes: [u8; 39], + length: usize, +} + +impl Default for Ipv6Buffer { + fn default() -> Self { + Self { + bytes: [0; 39], + length: 0, + } + } +} + +impl Ipv6Buffer { + fn as_bytes(&self) -> &[u8] { + &self.bytes[..self.length] + } +} + +impl Write for Ipv6Buffer { + fn write_str(&mut self, input: &str) -> fmt::Result { + let end = self.length.checked_add(input.len()).ok_or(fmt::Error)?; + let destination = self.bytes.get_mut(self.length..end).ok_or(fmt::Error)?; + destination.copy_from_slice(input.as_bytes()); + self.length = end; + Ok(()) + } +} + +/// Validate a common absolute URL without allocating or producing +/// component offsets. `None` means the input needs the full WHATWG parser. +#[inline] +pub(crate) fn can_parse_special_absolute(input: &str) -> Option { + let bytes = input.as_bytes(); + let (host_start, special) = if let Some((_, host_start, scheme_type)) = special_prefix(bytes) { + if scheme_type == SchemeType::File { + return None; + } + (host_start, true) + } else { + let colon = memchr(b':', bytes)?; + let scheme = &bytes[..colon]; + if scheme.is_empty() + || !scheme[0].is_ascii_alphabetic() + || !scheme[1..] + .iter() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')) + || !bytes.get(colon + 1..)?.starts_with(b"//") + { + return None; + } + (colon + 3, false) + }; + let authority_tail = bytes.get(host_start..)?; + let authority_len = memchr3(b'/', b'?', b'#', authority_tail).unwrap_or(authority_tail.len()); + let authority = &authority_tail[..authority_len]; + if authority.is_empty() { + return Some(!special); + } + + if special { + let mut simple_domain = true; + for &byte in authority { + if !(byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'_')) { + simple_domain = false; + break; + } + } + if simple_domain { + return if ends_in_number(authority) { + Some(parse_ipv4_bytes(authority).is_some()) + } else { + Some(true) + }; + } + } + + if special && memchr(b'\\', authority).is_some() { + return None; + } + + let host_port = authority + .iter() + .rposition(|byte| *byte == b'@') + .map_or(authority, |index| &authority[index + 1..]); + if host_port.is_empty() { + return Some(false); + } + if host_port.starts_with(b"[") { + let closing = host_port.iter().position(|byte| *byte == b']')?; + let address = core::str::from_utf8(&host_port[1..closing]).ok()?; + if address.parse::().is_err() { + return Some(false); + } + let after = &host_port[closing + 1..]; + if after.is_empty() { + return Some(true); + } + let port = after.strip_prefix(b":")?; + return Some(port.is_empty() || parse_port(port).is_some()); + } + + let (host, port) = host_port + .iter() + .rposition(|byte| *byte == b':') + .map_or((host_port, None), |index| { + (&host_port[..index], Some(&host_port[index + 1..])) + }); + if host.is_empty() { + return Some(!special && port.is_none()); + } + if let Some(port) = port { + if port.is_empty() { + // An empty port is accepted and stripped by the URL parser. + } else if parse_port(port).is_none() { + return Some(false); + } + } + if !special { + if host.iter().copied().any(is_forbidden_opaque_host_byte) { + return Some(false); + } + return Some(true); + } + if ends_in_number(host) { + return Some(parse_ipv4_bytes(host).is_some()); + } + if !is_simple_domain_case_insensitive(host) { + return None; + } + Some(true) +} + +#[inline] +fn special_prefix(input: &[u8]) -> Option<(usize, usize, SchemeType)> { + if input.starts_with(b"http://") { + Some((5, 7, SchemeType::Http)) + } else if input.starts_with(b"https://") { + Some((6, 8, SchemeType::Https)) + } else if input.starts_with(b"ws://") { + Some((3, 5, SchemeType::Ws)) + } else if input.starts_with(b"wss://") { + Some((4, 6, SchemeType::Wss)) + } else if input.starts_with(b"ftp://") { + Some((4, 6, SchemeType::Ftp)) + } else if input.starts_with(b"file://") { + Some((5, 7, SchemeType::File)) + } else { + None + } +} + +#[inline] +fn is_simple_domain(host: &[u8]) -> bool { + host.iter().all(|byte| { + byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'-' | b'_') + }) +} + +#[inline] +fn is_simple_domain_case_insensitive(host: &[u8]) -> bool { + host.iter() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'_')) +} + +#[inline] +fn parse_port(input: &[u8]) -> Option { + let mut value = 0_u32; + for &byte in input { + let digit = byte.checked_sub(b'0')?; + if digit > 9 { + return None; + } + value = value.checked_mul(10)?.checked_add(u32::from(digit))?; + if value > u32::from(u16::MAX) { + return None; + } + } + Some(value as u16) +} + +#[derive(Clone, Copy)] +struct Scan { + protocol_end: usize, + host_start: usize, + host_end: usize, + path_start: usize, + query_start: Option, + hash_start: Option, + scheme_type: SchemeType, + needs_slash: bool, + host_has_uppercase: bool, +} + +const HOST_OK: u8 = 0; +const HOST_DELIMITER: u8 = 1; +const HOST_REJECT: u8 = 2; + +const fn host_table() -> [u8; 256] { + let mut table = [HOST_REJECT; 256]; + let mut byte = 0x21_usize; + while byte <= 0x7e { + table[byte] = HOST_OK; + byte += 1; + } + let reject = b"#/:<>?@[\\]^|%"; + let mut index = 0; + while index < reject.len() { + table[reject[index] as usize] = HOST_REJECT; + index += 1; + } + table[b'/' as usize] = HOST_DELIMITER; + table[b'?' as usize] = HOST_DELIMITER; + table[b'#' as usize] = HOST_DELIMITER; + table +} + +const HOST_TABLE: [u8; 256] = host_table(); + +const PATH_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::Path; +const QUERY_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::Query; +const SPECIAL_QUERY_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::SpecialQuery; +const FRAGMENT_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::Fragment; +const USERINFO_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::UserInfo; + +#[inline] +pub(crate) fn parse(input: &str) -> Option { + let scan = scan(input.as_bytes())?; + let mut buffer = String::from(input); + if scan.needs_slash { + buffer.insert(scan.host_end, '/'); + } + if scan.host_has_uppercase { + buffer[scan.host_start..scan.host_end].make_ascii_lowercase(); + } + + let shift = u32::from(scan.needs_slash); + let components = Components::new( + u32::try_from(scan.protocol_end).ok()?, + u32::try_from(scan.host_start).ok()?, + u32::try_from(scan.host_start).ok()?, + u32::try_from(scan.host_end).ok()?, + None, + u32::try_from(scan.path_start).ok()?, + scan.query_start + .map(u32::try_from) + .transpose() + .ok()? + .map(|offset| offset + shift), + scan.hash_start + .map(u32::try_from) + .transpose() + .ok()? + .map(|offset| offset + shift), + ); + debug_assert!(components.validate(buffer.len())); + + Some(FastPath { + buffer, + components, + scheme_type: scan.scheme_type, + host_type: HostType::Domain, + has_authority: true, + opaque_path: false, + }) +} + +#[inline] +pub(crate) fn normalized_len(input: &str) -> Option { + let scan = scan(input.as_bytes())?; + input.len().checked_add(usize::from(scan.needs_slash)) +} + +pub(crate) fn parse_normalized_file(input: &str) -> Option { + let tail = input.strip_prefix("file://")?; + let authority_length = memchr3(b'/', b'?', b'#', tail.as_bytes()).unwrap_or(tail.len()); + let host = &tail[..authority_length]; + let rest = &tail[authority_length..]; + if !host.bytes().all(|byte| { + byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'-' | b'_') + }) || host.eq_ignore_ascii_case("localhost") + || ends_in_number(host.as_bytes()) + || !rest.starts_with('/') + { + return None; + } + + let (path_end, query_start, hash_start) = scan_suffix(rest, 7 + authority_length)?; + let path = &input.as_bytes()[7 + authority_length..path_end]; + if (path.len() >= 3 && path[0] == b'/' && path[1].is_ascii_alphabetic() && path[2] == b'|') + || !valid_path(path) + { + return None; + } + let components = Components::new( + 5, + 7, + 7, + u32::try_from(7 + authority_length).ok()?, + None, + u32::try_from(7 + authority_length).ok()?, + query_start.map(u32::try_from).transpose().ok()?, + hash_start.map(u32::try_from).transpose().ok()?, + ); + debug_assert!(components.validate(input.len())); + Some(FastPath { + buffer: String::from(input), + components, + scheme_type: SchemeType::File, + host_type: HostType::Domain, + has_authority: true, + opaque_path: false, + }) +} + +pub(crate) enum CommonParse { + Parsed(FastPath), + Invalid, + Unsupported, +} + +pub(crate) fn parse_common_absolute(input: &str) -> CommonParse { + if memchr3(b'\t', b'\n', b'\r', input.as_bytes()).is_some() { + return CommonParse::Unsupported; + } + let (scheme, scheme_type, authority_start) = if input.starts_with("http://") { + ("http", SchemeType::Http, 7) + } else if input.starts_with("https://") { + ("https", SchemeType::Https, 8) + } else if input.starts_with("ws://") { + ("ws", SchemeType::Ws, 5) + } else if input.starts_with("wss://") { + ("wss", SchemeType::Wss, 6) + } else if input.starts_with("ftp://") { + ("ftp", SchemeType::Ftp, 6) + } else if input.starts_with("postgresql://") { + ("postgresql", SchemeType::NotSpecial, 13) + } else { + let Some(colon) = memchr(b':', input.as_bytes()) else { + return CommonParse::Unsupported; + }; + let scheme = &input[..colon]; + if scheme.is_empty() + || !scheme.as_bytes()[0].is_ascii_lowercase() + || !scheme.bytes().all(|byte| { + byte.is_ascii_lowercase() + || byte.is_ascii_digit() + || matches!(byte, b'+' | b'-' | b'.') + }) + || scheme == "file" + || !input[colon + 1..].starts_with("//") + { + return CommonParse::Unsupported; + } + (scheme, SchemeType::NotSpecial, colon + 3) + }; + let special = scheme_type.is_special(); + let authority_tail = &input[authority_start..]; + let authority_length = if special { + special_authority_length(authority_tail) + } else { + memchr3(b'/', b'?', b'#', authority_tail.as_bytes()).unwrap_or(authority_tail.len()) + }; + let authority = &authority_tail[..authority_length]; + let rest = &authority_tail[authority_length..]; + let (userinfo, host_port) = authority.rfind('@').map_or((None, authority), |index| { + (Some(&authority[..index]), &authority[index + 1..]) + }); + let Some((raw_host, raw_port)) = split_host_port(host_port) else { + return CommonParse::Invalid; + }; + if raw_host.is_empty() && (special || !authority.is_empty()) { + return CommonParse::Invalid; + } + + let (host, host_type): (Cow<'_, str>, HostType) = if let Some(address) = raw_host + .strip_prefix('[') + .and_then(|host| host.strip_suffix(']')) + { + let Ok(address) = address.parse::() else { + return CommonParse::Invalid; + }; + (Cow::Owned(format!("[{address}]")), HostType::IPV6) + } else if special { + let Some(host) = normalize_special_host(raw_host) else { + return CommonParse::Invalid; + }; + (Cow::Owned(host.0), host.1) + } else { + if raw_host.bytes().any(is_forbidden_opaque_host_byte) { + return CommonParse::Invalid; + } + let host = + if raw_host.is_ascii() && raw_host.bytes().all(|byte| byte > 0x1f && byte != 0x7f) { + Cow::Borrowed(raw_host) + } else { + Cow::Owned(utf8_percent_encode(raw_host, PercentEncodeSet::C0Control).to_string()) + }; + (host, HostType::Domain) + }; + + let port = match raw_port { + Some("") | None => None, + Some(port) if port.bytes().all(|byte| byte.is_ascii_digit()) => { + let Ok(port) = port.parse::() else { + return CommonParse::Invalid; + }; + Some(port) + } + Some(_) => return CommonParse::Invalid, + }; + let port = port.filter(|port| Some(*port) != scheme_type.default_port()); + + let (before_fragment, fragment) = rest.find('#').map_or((rest, None), |index| { + (&rest[..index], Some(&rest[index + 1..])) + }); + let (raw_path, query) = before_fragment + .find('?') + .map_or((before_fragment, None), |index| { + ( + &before_fragment[..index], + Some(&before_fragment[index + 1..]), + ) + }); + if special && raw_path.contains('\\') { + return CommonParse::Unsupported; + } + let mut buffer = String::with_capacity(input.len() + 16); + buffer.push_str(scheme); + buffer.push_str("://"); + let protocol_end = scheme.len() + 1; + let authority_offset = buffer.len(); + let username_end; + if let Some(userinfo) = userinfo { + let (username, password) = userinfo + .split_once(':') + .map_or((userinfo, None), |(username, password)| { + (username, Some(password)) + }); + if username.is_empty() && password.is_none_or(str::is_empty) { + username_end = authority_offset; + } else { + append_percent_encoded(&mut buffer, username, USERINFO_ENCODE_SET); + username_end = buffer.len(); + if let Some(password) = password.filter(|password| !password.is_empty()) { + buffer.push(':'); + append_percent_encoded(&mut buffer, password, USERINFO_ENCODE_SET); + } + buffer.push('@'); + } + } else { + username_end = authority_offset; + } + let host_start = buffer.len(); + buffer.push_str(&host); + let host_end = buffer.len(); + if let Some(port) = port { + buffer.push(':'); + push_u16_decimal(&mut buffer, port); + } + let pathname_start = buffer.len(); + if raw_path.is_empty() && special { + buffer.push('/'); + } else { + append_normalized_path(&mut buffer, raw_path); + } + let search_start = query.map(|query| { + let start = buffer.len(); + buffer.push('?'); + let encode_set = if special { + SPECIAL_QUERY_ENCODE_SET + } else { + QUERY_ENCODE_SET + }; + append_percent_encoded(&mut buffer, query, encode_set); + start + }); + let hash_start = fragment.map(|fragment| { + let start = buffer.len(); + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + start + }); + + let (Ok(protocol_end), Ok(username_end), Ok(host_start), Ok(host_end), Ok(pathname_start)) = ( + u32::try_from(protocol_end), + u32::try_from(username_end), + u32::try_from(host_start), + u32::try_from(host_end), + u32::try_from(pathname_start), + ) else { + return CommonParse::Invalid; + }; + let Ok(search_start) = search_start.map(u32::try_from).transpose() else { + return CommonParse::Invalid; + }; + let Ok(hash_start) = hash_start.map(u32::try_from).transpose() else { + return CommonParse::Invalid; + }; + let components = Components::new( + protocol_end, + username_end, + host_start, + host_end, + port, + pathname_start, + search_start, + hash_start, + ); + debug_assert!(components.validate(buffer.len())); + CommonParse::Parsed(FastPath { + buffer, + components, + scheme_type, + host_type, + has_authority: true, + opaque_path: false, + }) +} + +pub(crate) fn parse_non_special_hierarchical_absolute(input: &str) -> Option { + let colon = memchr(b':', input.as_bytes())?; + let scheme = &input[..colon]; + if scheme.is_empty() + || !scheme.as_bytes()[0].is_ascii_alphabetic() + || !scheme + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')) + || SchemeType::from_scheme(scheme).is_special() + || scheme.eq_ignore_ascii_case("file") + { + return None; + } + let rest = &input[colon + 1..]; + if !rest.starts_with('/') || rest.starts_with("//") { + return None; + } + let (before_fragment, fragment) = rest.find('#').map_or((rest, None), |index| { + (&rest[..index], Some(&rest[index + 1..])) + }); + let (path, query) = before_fragment + .find('?') + .map_or((before_fragment, None), |index| { + ( + &before_fragment[..index], + Some(&before_fragment[index + 1..]), + ) + }); + + let mut buffer = String::with_capacity(input.len() + 8); + for byte in scheme.bytes() { + buffer.push(char::from(byte.to_ascii_lowercase())); + } + buffer.push(':'); + let protocol_end = buffer.len(); + let mut normalized_path = String::with_capacity(path.len()); + append_normalized_path(&mut normalized_path, path); + if normalized_path.starts_with("//") { + buffer.push_str("/."); + } + let pathname_start = buffer.len(); + buffer.push_str(&normalized_path); + let search_start = query.map(|query| { + let start = buffer.len(); + buffer.push('?'); + append_percent_encoded(&mut buffer, query, QUERY_ENCODE_SET); + start + }); + let hash_start = fragment.map(|fragment| { + let start = buffer.len(); + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + start + }); + let components = Components::new( + u32::try_from(protocol_end).ok()?, + u32::try_from(protocol_end).ok()?, + u32::try_from(protocol_end).ok()?, + u32::try_from(protocol_end).ok()?, + None, + u32::try_from(pathname_start).ok()?, + search_start.map(u32::try_from).transpose().ok()?, + hash_start.map(u32::try_from).transpose().ok()?, + ); + Some(FastPath { + buffer, + components, + scheme_type: SchemeType::NotSpecial, + host_type: HostType::Domain, + has_authority: false, + opaque_path: false, + }) +} + +pub(crate) fn normalize_special_host(raw_host: &str) -> Option<(String, HostType)> { + if raw_host.is_empty() { + return None; + } + if let Some(address) = raw_host + .strip_prefix('[') + .and_then(|host| host.strip_suffix(']')) + { + let address = address.parse::().ok()?; + return Some((format!("[{address}]"), HostType::IPV6)); + } + + if raw_host.is_ascii() && !raw_host.contains('%') { + if ends_in_number(raw_host.as_bytes()) { + let address = parse_ipv4(raw_host)?; + return Some((Ipv4Addr::from(address).to_string(), HostType::IPV4)); + } + if is_simple_domain_case_insensitive(raw_host.as_bytes()) { + let mut host = String::from(raw_host); + host.make_ascii_lowercase(); + return Some((host, HostType::Domain)); + } + } + + let decoded = percent_decode_utf8(raw_host)?; + let ascii_host = domain_to_ascii(&decoded).ok()?; + if ascii_host.is_empty() { + return None; + } + if ends_in_number(ascii_host.as_bytes()) { + let address = parse_ipv4(&ascii_host)?; + Some((Ipv4Addr::from(address).to_string(), HostType::IPV4)) + } else { + Some((ascii_host, HostType::Domain)) + } +} + +pub(crate) fn parse_opaque_absolute(input: &str) -> Option { + if input + .chars() + .next() + .is_some_and(|character| character <= '\u{20}') + || input + .chars() + .next_back() + .is_some_and(|character| character <= '\u{20}') + { + return None; + } + + let colon = memchr(b':', input.as_bytes())?; + let raw_scheme = &input[..colon]; + if raw_scheme.is_empty() + || !raw_scheme.as_bytes()[0].is_ascii_alphabetic() + || !raw_scheme + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')) + { + return None; + } + let rest = &input[colon + 1..]; + if rest.starts_with('/') { + return None; + } + if ["http", "https", "ws", "wss", "ftp", "file"] + .iter() + .any(|special| raw_scheme.eq_ignore_ascii_case(special)) + { + return None; + } + if memchr3(b'\t', b'\n', b'\r', input.as_bytes()).is_some() { + return None; + } + let scheme_type = SchemeType::NotSpecial; + + let (before_fragment, fragment) = rest.find('#').map_or((rest, None), |index| { + (&rest[..index], Some(&rest[index + 1..])) + }); + let (mut raw_path, query) = + before_fragment + .find('?') + .map_or((before_fragment, None), |index| { + ( + &before_fragment[..index], + Some(&before_fragment[index + 1..]), + ) + }); + let encode_final_space = (query.is_some() || fragment.is_some()) && raw_path.ends_with(' '); + if query.is_none() && fragment.is_none() { + raw_path = raw_path.trim_end_matches(' '); + } else if encode_final_space { + raw_path = &raw_path[..raw_path.len() - 1]; + } + + let mut buffer = String::with_capacity(input.len()); + if raw_scheme.bytes().any(|byte| byte.is_ascii_uppercase()) { + buffer.extend( + raw_scheme + .chars() + .map(|character| character.to_ascii_lowercase()), + ); + } else { + buffer.push_str(raw_scheme); + } + buffer.push(':'); + let protocol_end = buffer.len(); + let pathname_start = protocol_end; + append_percent_encoded(&mut buffer, raw_path, PercentEncodeSet::C0Control); + if encode_final_space { + buffer.push_str("%20"); + } + let search_start = query.map(|query| { + let start = buffer.len(); + buffer.push('?'); + append_percent_encoded(&mut buffer, query, QUERY_ENCODE_SET); + start + }); + let hash_start = fragment.map(|fragment| { + let start = buffer.len(); + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + start + }); + let components = Components::new( + u32::try_from(protocol_end).ok()?, + u32::try_from(protocol_end).ok()?, + u32::try_from(protocol_end).ok()?, + u32::try_from(protocol_end).ok()?, + None, + u32::try_from(pathname_start).ok()?, + search_start.map(u32::try_from).transpose().ok()?, + hash_start.map(u32::try_from).transpose().ok()?, + ); + debug_assert!(components.validate(buffer.len())); + Some(FastPath { + buffer, + components, + scheme_type, + host_type: HostType::Domain, + has_authority: false, + opaque_path: true, + }) +} + +fn split_host_port(input: &str) -> Option<(&str, Option<&str>)> { + if input.starts_with('[') { + let closing = input.find(']')?; + let host = &input[..=closing]; + let after = &input[closing + 1..]; + if after.is_empty() { + Some((host, None)) + } else { + Some((host, Some(after.strip_prefix(':')?))) + } + } else { + Some(input.rfind(':').map_or((input, None), |index| { + (&input[..index], Some(&input[index + 1..])) + })) + } +} + +fn is_forbidden_opaque_host_byte(byte: u8) -> bool { + matches!( + byte, + 0x00 | b' ' + | b'#' + | b'/' + | b':' + | b'<' + | b'>' + | b'?' + | b'@' + | b'[' + | b'\\' + | b']' + | b'^' + | b'|' + ) +} + +fn is_single_dot(segment: &str) -> bool { + segment == "." || segment.eq_ignore_ascii_case("%2e") +} + +fn is_double_dot(segment: &str) -> bool { + segment == ".." + || segment.eq_ignore_ascii_case(".%2e") + || segment.eq_ignore_ascii_case("%2e.") + || segment.eq_ignore_ascii_case("%2e%2e") +} + +fn scan(input: &[u8]) -> Option { + let (protocol_end, host_start, scheme_type) = if input.starts_with(b"http://") { + (5, 7, SchemeType::Http) + } else if input.starts_with(b"https://") { + (6, 8, SchemeType::Https) + } else { + return None; + }; + if input.len() <= host_start || matches!(input[host_start], b'/' | b'\\') { + return None; + } + + let authority = &input[host_start..]; + let relative_host_end = memchr3(b'/', b'?', b'#', authority).unwrap_or(authority.len()); + let host_end = host_start + relative_host_end; + let host = &input[host_start..host_end]; + if host.is_empty() || host.len() > 253 { + return None; + } + + let mut has_upper = false; + for &byte in host { + if HOST_TABLE[byte as usize] != HOST_OK { + return None; + } + has_upper |= byte.is_ascii_uppercase(); + } + if ends_in_number(host) { + return None; + } + + let mut path_start = host_end; + let mut query_start = None; + let mut hash_start = None; + let mut needs_slash = true; + + if host_end < input.len() { + match input[host_end] { + b'/' => { + needs_slash = false; + let rest = &input[host_end..]; + let query_or_hash = memchr2(b'?', b'#', rest); + let path_end = query_or_hash.map_or(input.len(), |offset| host_end + offset); + if !valid_path(&input[host_end..path_end]) { + return None; + } + if let Some(offset) = query_or_hash { + let delimiter = host_end + offset; + if input[delimiter] == b'?' { + query_start = Some(delimiter); + hash_start = memchr(b'#', &input[delimiter + 1..]) + .map(|relative| delimiter + 1 + relative); + } else { + hash_start = Some(delimiter); + } + } + } + b'?' => { + query_start = Some(host_end); + hash_start = + memchr(b'#', &input[host_end + 1..]).map(|relative| host_end + 1 + relative); + } + b'#' => hash_start = Some(host_end), + _ => return None, + } + } + + let query_end = hash_start.unwrap_or(input.len()); + if let Some(start) = query_start + && !valid_query_or_fragment(&input[start + 1..query_end]) + { + return None; + } + if let Some(start) = hash_start + && !valid_query_or_fragment(&input[start + 1..]) + { + return None; + } + + if needs_slash { + path_start = host_end; + } + + Some(Scan { + protocol_end, + host_start, + host_end, + path_start, + query_start, + hash_start, + scheme_type, + needs_slash, + host_has_uppercase: has_upper, + }) +} + +#[inline] +fn valid_path(path: &[u8]) -> bool { + path.split(|&byte| byte == b'/').all(|segment| { + segment != b"." + && segment != b".." + && segment.iter().all(|&byte| { + matches!(byte, 0x21..=0x7e) + && !matches!( + byte, + b'"' | b'<' | b'>' | b'`' | b'{' | b'}' | b'^' | b'\\' | b'%' | b'\'' + ) + }) + }) +} + +#[inline] +fn valid_query_or_fragment(input: &[u8]) -> bool { + input.iter().all(|&byte| { + matches!(byte, 0x21..=0x7e) + && !matches!( + byte, + b'"' | b'<' | b'>' | b'`' | b'{' | b'}' | b'^' | b'\\' | b'\'' + ) + }) +} + +#[inline] +fn ends_in_number(host: &[u8]) -> bool { + let host = host.strip_suffix(b".").unwrap_or(host); + let final_label = host.rsplit(|&byte| byte == b'.').next().unwrap_or(host); + !final_label.is_empty() + && (final_label.iter().all(u8::is_ascii_digit) + || (final_label.len() >= 2 + && final_label[0] == b'0' + && matches!(final_label[1], b'x' | b'X') + && final_label[2..].iter().all(u8::is_ascii_hexdigit))) +} + +fn parse_ipv4(input: &str) -> Option { + parse_ipv4_bytes(input.as_bytes()) +} + +fn parse_ipv4_bytes(input: &[u8]) -> Option { + let input = input.strip_suffix(b".").unwrap_or(input); + let mut numbers = [0_u64; 4]; + let mut count = 0_usize; + for part in input.split(|byte| *byte == b'.') { + if part.is_empty() || count == numbers.len() { + return None; + } + numbers[count] = parse_ipv4_number(part)?; + count += 1; + } + if count == 0 || numbers[..count - 1].iter().any(|number| *number > 255) { + return None; + } + + let last_limit = 1_u64 << (8 * (5 - count)); + if numbers[count - 1] >= last_limit { + return None; + } + let mut value = numbers[count - 1]; + for (index, number) in numbers[..count - 1].iter().enumerate() { + value += number << (8 * (3 - index)); + } + u32::try_from(value).ok() +} + +#[inline] +fn parse_ipv4_number(input: &[u8]) -> Option { + let (digits, radix) = if let Some(digits) = input + .strip_prefix(b"0x") + .or_else(|| input.strip_prefix(b"0X")) + { + (digits, 16_u64) + } else if input.len() >= 2 && input.starts_with(b"0") { + (&input[1..], 8) + } else { + (input, 10) + }; + if digits.is_empty() { + return Some(0); + } + let mut value = 0_u64; + for &byte in digits { + let digit = match byte { + b'0'..=b'9' => u64::from(byte - b'0'), + b'a'..=b'f' => u64::from(byte - b'a' + 10), + b'A'..=b'F' => u64::from(byte - b'A' + 10), + _ => return None, + }; + if digit >= radix { + return None; + } + value = value.checked_mul(radix)?.checked_add(digit)?; + } + Some(value) +} + +#[inline] +fn push_ipv4(buffer: &mut String, address: u32) { + push_u8_decimal(buffer, (address >> 24) as u8); + buffer.push('.'); + push_u8_decimal(buffer, (address >> 16) as u8); + buffer.push('.'); + push_u8_decimal(buffer, (address >> 8) as u8); + buffer.push('.'); + push_u8_decimal(buffer, address as u8); +} + +#[inline] +fn push_u8_decimal(buffer: &mut String, value: u8) { + if value >= 100 { + buffer.push(char::from(b'0' + value / 100)); + buffer.push(char::from(b'0' + (value / 10) % 10)); + } else if value >= 10 { + buffer.push(char::from(b'0' + value / 10)); + } + buffer.push(char::from(b'0' + value % 10)); +} + +#[inline] +fn push_u16_decimal(buffer: &mut String, mut value: u16) { + let mut digits = [0_u8; 5]; + let mut start = digits.len(); + loop { + start -= 1; + digits[start] = b'0' + (value % 10) as u8; + value /= 10; + if value == 0 { + break; + } + } + for digit in &digits[start..] { + buffer.push(char::from(*digit)); + } +} + +fn append_normalized_path(buffer: &mut String, raw_path: &str) { + let path_start = buffer.len(); + let mut has_segment = false; + let mut segments = raw_path.split('/').peekable(); + while let Some(segment) = segments.next() { + if is_single_dot(segment) { + if segments.peek().is_none() { + append_empty_path_segment(buffer, has_segment); + has_segment = true; + } + continue; + } + if is_double_dot(segment) { + shorten_path(buffer, path_start); + if segments.peek().is_none() { + append_empty_path_segment(buffer, has_segment); + has_segment = true; + } + continue; + } + if has_segment { + buffer.push('/'); + } + append_percent_encoded(buffer, segment, PATH_ENCODE_SET); + has_segment = true; + } +} + +fn append_empty_path_segment(buffer: &mut String, has_segment: bool) { + if has_segment { + buffer.push('/'); + } +} + +fn shorten_path(buffer: &mut String, path_start: usize) { + if buffer.len() <= path_start { + return; + } + if buffer.as_bytes().last() == Some(&b'/') { + buffer.truncate(buffer.len() - 1); + return; + } + let relative = &buffer.as_bytes()[path_start..]; + if let Some(slash) = relative.iter().rposition(|byte| *byte == b'/') { + buffer.truncate(path_start + slash); + } else { + buffer.truncate(path_start); + } +} + +fn special_authority_length(authority_tail: &str) -> usize { + let bytes = authority_tail.as_bytes(); + let standard = memchr3(b'/', b'?', b'#', bytes).unwrap_or(bytes.len()); + memchr(b'\\', bytes).map_or(standard, |backslash| standard.min(backslash)) +} + +fn scan_suffix( + suffix: &str, + absolute_start: usize, +) -> Option<(usize, Option, Option)> { + let query_or_hash = memchr2(b'?', b'#', suffix.as_bytes()); + let path_end = query_or_hash.map_or(absolute_start + suffix.len(), |offset| { + absolute_start + offset + }); + let mut query_start = None; + let mut hash_start = None; + if let Some(offset) = query_or_hash { + let delimiter = absolute_start + offset; + if suffix.as_bytes()[offset] == b'?' { + query_start = Some(delimiter); + hash_start = memchr(b'#', &suffix.as_bytes()[offset + 1..]) + .map(|relative| delimiter + 1 + relative); + } else { + hash_start = Some(delimiter); + } + } + let query_end = hash_start.unwrap_or(absolute_start + suffix.len()); + if let Some(start) = query_start + && !valid_query_or_fragment( + &suffix.as_bytes()[start + 1 - absolute_start..query_end - absolute_start], + ) + { + return None; + } + if let Some(start) = hash_start + && !valid_query_or_fragment(&suffix.as_bytes()[start + 1 - absolute_start..]) + { + return None; + } + Some((path_end, query_start, hash_start)) +} + +#[cfg(test)] +mod tests { + use super::{CommonParse, parse, parse_common_absolute, parse_normalized_file}; + + #[test] + fn accepts_normalized_http() { + let parsed = parse("https://Example.COM/a/b?q=1#f").unwrap(); + assert_eq!(parsed.buffer, "https://example.com/a/b?q=1#f"); + assert_eq!(parsed.components.pathname_start, 19); + } + + #[test] + fn inserts_empty_path_slash() { + let parsed = parse("http://example.com?x").unwrap(); + assert_eq!(parsed.buffer, "http://example.com/?x"); + assert_eq!(parsed.components.search_start(), Some(19)); + } + + #[test] + fn rejects_work_for_the_full_parser() { + for input in [ + "HTTP://example.com", + "http://127.0.0.1", + "http://example.com/a/../b", + "http://example.com/a b", + ] { + assert!(parse(input).is_none(), "{input}"); + } + } + + #[test] + fn parses_whatwg_ipv4_and_dot_segments() { + let CommonParse::Parsed(parsed) = parse_common_absolute("http://0300.168.0xF0/a/b/../c") + else { + panic!("native common parser rejected a supported URL"); + }; + assert_eq!(parsed.buffer, "http://192.168.0.240/a/c"); + + assert!(matches!( + parse_common_absolute("http://256.256.256.256"), + CommonParse::Invalid + )); + } + + #[test] + fn parses_normalized_file_without_intermediates() { + let parsed = parse_normalized_file("file:///C:/Users/test/file.txt?x#y").unwrap(); + assert_eq!(parsed.buffer, "file:///C:/Users/test/file.txt?x#y"); + assert_eq!(parsed.components.pathname_start, 7); + assert_eq!(parsed.components.search_start(), Some(30)); + } +} diff --git a/src/ffi.rs b/src/ffi.rs deleted file mode 100644 index 9cea53a..0000000 --- a/src/ffi.rs +++ /dev/null @@ -1,310 +0,0 @@ -#![allow(non_camel_case_types)] -use core::ffi::{c_char, c_uint}; - -#[cfg(feature = "std")] -extern crate std; - -#[cfg(feature = "std")] -use std::fmt::Display; - -#[repr(C)] -pub struct ada_url { - _unused: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - -#[repr(C)] -pub struct ada_url_search_params { - _unused: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - -#[repr(C)] -pub struct ada_string { - pub data: *const c_char, - pub length: usize, -} - -impl ada_string { - #[must_use] - pub const fn as_str(&self) -> &'static str { - // We need to handle length 0 since data will be `nullptr` - // Not handling will result in a panic due to core::slice::from_raw_parts - // implementation - if self.length == 0 { - return ""; - } - unsafe { - let slice = core::slice::from_raw_parts(self.data.cast(), self.length); - core::str::from_utf8_unchecked(slice) - } - } -} - -#[repr(C)] -pub struct ada_owned_string { - pub data: *const c_char, - pub length: usize, -} - -impl AsRef for ada_owned_string { - fn as_ref(&self) -> &str { - // We need to handle length 0 since data will be `nullptr` - // Not handling will result in a panic due to core::slice::from_raw_parts - // implementation - if self.length == 0 { - return ""; - } - unsafe { - let slice = core::slice::from_raw_parts(self.data.cast(), self.length); - core::str::from_utf8_unchecked(slice) - } - } -} - -#[cfg(feature = "std")] -impl Display for ada_owned_string { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.as_ref().to_owned()) - } -} - -impl Drop for ada_owned_string { - fn drop(&mut self) { - // @note This is needed because ada_free_owned_string accepts by value - let copy = ada_owned_string { - data: self.data, - length: self.length, - }; - unsafe { - ada_free_owned_string(copy); - }; - } -} - -/// Represents an std::vector -#[repr(C)] -pub struct ada_strings { - _unused: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - -#[repr(C)] -pub struct ada_url_search_params_keys_iter { - _unused: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - -#[repr(C)] -pub struct ada_url_search_params_values_iter { - _unused: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - -#[repr(C)] -pub struct ada_url_search_params_entries_iter { - _unused: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - -/// Represents a key/value pair of strings -#[repr(C)] -pub struct ada_string_pair { - pub key: ada_string, - pub value: ada_string, -} - -#[repr(C)] -pub struct ada_url_components { - pub protocol_end: u32, - pub username_end: u32, - pub host_start: u32, - pub host_end: u32, - pub port: u32, - pub pathname_start: u32, - pub search_start: u32, - pub hash_start: u32, -} - -unsafe extern "C" { - pub fn ada_parse(input: *const c_char, length: usize) -> *mut ada_url; - pub fn ada_parse_with_base( - input: *const c_char, - input_length: usize, - base: *const c_char, - base_length: usize, - ) -> *mut ada_url; - pub fn ada_free(url: *mut ada_url); - pub fn ada_free_owned_string(url: ada_owned_string); - pub fn ada_copy(url: *mut ada_url) -> *mut ada_url; - pub fn ada_is_valid(url: *mut ada_url) -> bool; - pub fn ada_can_parse(url: *const c_char, length: usize) -> bool; - pub fn ada_can_parse_with_base( - input: *const c_char, - input_length: usize, - base: *const c_char, - base_length: usize, - ) -> bool; - pub fn ada_get_components(url: *mut ada_url) -> *mut ada_url_components; - - // Getters - #[cfg(feature = "std")] - pub fn ada_get_origin(url: *mut ada_url) -> ada_owned_string; - pub fn ada_get_href(url: *mut ada_url) -> ada_string; - pub fn ada_get_username(url: *mut ada_url) -> ada_string; - pub fn ada_get_password(url: *mut ada_url) -> ada_string; - pub fn ada_get_port(url: *mut ada_url) -> ada_string; - pub fn ada_get_hash(url: *mut ada_url) -> ada_string; - pub fn ada_get_host(url: *mut ada_url) -> ada_string; - pub fn ada_get_hostname(url: *mut ada_url) -> ada_string; - pub fn ada_get_pathname(url: *mut ada_url) -> ada_string; - pub fn ada_get_search(url: *mut ada_url) -> ada_string; - pub fn ada_get_protocol(url: *mut ada_url) -> ada_string; - pub fn ada_get_host_type(url: *mut ada_url) -> c_uint; - pub fn ada_get_scheme_type(url: *mut ada_url) -> c_uint; - - // Setters - pub fn ada_set_href(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_username(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_password(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_port(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_hash(url: *mut ada_url, input: *const c_char, length: usize); - pub fn ada_set_host(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_hostname(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_pathname(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - pub fn ada_set_search(url: *mut ada_url, input: *const c_char, length: usize); - pub fn ada_set_protocol(url: *mut ada_url, input: *const c_char, length: usize) -> bool; - - // Clear methods - pub fn ada_clear_search(url: *mut ada_url); - pub fn ada_clear_hash(url: *mut ada_url); - pub fn ada_clear_port(url: *mut ada_url); - - // Validators - pub fn ada_has_credentials(url: *mut ada_url) -> bool; - pub fn ada_has_empty_hostname(url: *mut ada_url) -> bool; - pub fn ada_has_hostname(url: *mut ada_url) -> bool; - pub fn ada_has_non_empty_username(url: *mut ada_url) -> bool; - pub fn ada_has_non_empty_password(url: *mut ada_url) -> bool; - pub fn ada_has_port(url: *mut ada_url) -> bool; - pub fn ada_has_password(url: *mut ada_url) -> bool; - pub fn ada_has_hash(url: *mut ada_url) -> bool; - pub fn ada_has_search(url: *mut ada_url) -> bool; - - // IDNA methods - #[cfg(feature = "std")] - pub fn ada_idna_to_unicode(input: *const c_char, length: usize) -> ada_owned_string; - #[cfg(feature = "std")] - pub fn ada_idna_to_ascii(input: *const c_char, length: usize) -> ada_owned_string; - - // URLSearchParams - pub fn ada_parse_search_params( - input: *const c_char, - length: usize, - ) -> *mut ada_url_search_params; - pub fn ada_free_search_params(search_params: *mut ada_url_search_params); - pub fn ada_search_params_size(search_params: *mut ada_url_search_params) -> usize; - pub fn ada_search_params_sort(search_params: *mut ada_url_search_params); - pub fn ada_search_params_to_string( - search_params: *mut ada_url_search_params, - ) -> ada_owned_string; - pub fn ada_search_params_append( - search_params: *mut ada_url_search_params, - name: *const c_char, - name_length: usize, - value: *const c_char, - value_length: usize, - ); - pub fn ada_search_params_set( - search_params: *mut ada_url_search_params, - name: *const c_char, - name_length: usize, - value: *const c_char, - value_length: usize, - ); - pub fn ada_search_params_remove( - search_params: *mut ada_url_search_params, - name: *const c_char, - name_length: usize, - ); - pub fn ada_search_params_remove_value( - search_params: *mut ada_url_search_params, - name: *const c_char, - name_length: usize, - value: *const c_char, - value_length: usize, - ); - pub fn ada_search_params_has( - search_params: *mut ada_url_search_params, - name: *const c_char, - name_length: usize, - ) -> bool; - pub fn ada_search_params_has_value( - search_params: *mut ada_url_search_params, - name: *const c_char, - name_length: usize, - value: *const c_char, - value_length: usize, - ) -> bool; - pub fn ada_search_params_get( - search_params: *mut ada_url_search_params, - key: *const c_char, - key_length: usize, - ) -> ada_string; - pub fn ada_search_params_get_all( - // not implemented - search_params: *mut ada_url_search_params, - key: *const c_char, - key_length: usize, - ) -> *mut ada_strings; - pub fn ada_search_params_get_keys( - search_params: *mut ada_url_search_params, - ) -> *mut ada_url_search_params_keys_iter; - pub fn ada_search_params_get_values( - search_params: *mut ada_url_search_params, - ) -> *mut ada_url_search_params_values_iter; - pub fn ada_search_params_get_entries( - search_params: *mut ada_url_search_params, - ) -> *mut ada_url_search_params_entries_iter; - - pub fn ada_free_strings(strings: *mut ada_strings); - pub fn ada_strings_size(strings: *mut ada_strings) -> usize; - pub fn ada_strings_get(strings: *mut ada_strings, index: usize) -> ada_string; - pub fn ada_free_search_params_keys_iter(iter: *mut ada_url_search_params_keys_iter); - pub fn ada_search_params_keys_iter_next( - iter: *mut ada_url_search_params_keys_iter, - ) -> ada_string; - pub fn ada_search_params_keys_iter_has_next(iter: *mut ada_url_search_params_keys_iter) - -> bool; - - pub fn ada_free_search_params_values_iter(iter: *mut ada_url_search_params_values_iter); - pub fn ada_search_params_values_iter_next( - iter: *mut ada_url_search_params_values_iter, - ) -> ada_string; - pub fn ada_search_params_values_iter_has_next( - iter: *mut ada_url_search_params_values_iter, - ) -> bool; - - pub fn ada_free_search_params_entries_iter(iter: *mut ada_url_search_params_entries_iter); - pub fn ada_search_params_entries_iter_next( - iter: *mut ada_url_search_params_entries_iter, - ) -> ada_string_pair; - pub fn ada_search_params_entries_iter_has_next( - iter: *mut ada_url_search_params_entries_iter, - ) -> bool; -} - -#[cfg(test)] -mod tests { - #[cfg(feature = "std")] - use crate::ffi; - - #[test] - #[cfg(feature = "std")] - fn ada_free_owned_string_works() { - let str = "meßagefactory.ca"; - let result = unsafe { ffi::ada_idna_to_ascii(str.as_ptr().cast(), str.len()) }; - assert_eq!(result.as_ref(), "xn--meagefactory-m9a.ca"); - unsafe { ffi::ada_free_owned_string(result) }; - } -} diff --git a/src/idna.rs b/src/idna.rs index d6d6832..7f9d459 100644 --- a/src/idna.rs +++ b/src/idna.rs @@ -1,63 +1,355 @@ -#[cfg(feature = "std")] -extern crate std; +//! Dependency-free UTS #46 domain conversion. + +use alloc::{string::String, vec::Vec}; +use core::fmt; + +#[path = "idna_joiners.rs"] +mod joiners; +#[path = "idna_normalization.rs"] +mod normalization; +#[path = "idna_punycode.rs"] +mod punycode; +#[path = "idna_tables.rs"] +mod tables; -#[cfg_attr(not(feature = "std"), allow(unused_imports))] -use crate::ffi; +use joiners::{JOINING_DUAL, JOINING_LEFT, JOINING_RIGHT, VIRAMA}; +use tables::Direction; + +const MAX_DOMAIN_INPUT_BYTES: usize = 16_384; + +/// An error produced during UTS #46 processing. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub struct IdnaError; + +impl fmt::Display for IdnaError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("invalid internationalized domain name") + } +} #[cfg(feature = "std")] -use std::string::String; +impl std::error::Error for IdnaError {} -/// IDNA struct implements the `to_ascii` and `to_unicode` functions from the Unicode Technical -/// Standard supporting a wide range of systems. It is suitable for URL parsing. -/// For more information, [read the specification](https://www.unicode.org/reports/tr46/#ToUnicode) -pub struct Idna {} +/// UTS #46 conversion helpers retained for API compatibility. +pub struct Idna; impl Idna { - /// Process international domains according to the UTS #46 standard. - /// Returns empty string if the input is invalid. - /// - /// For more information, [read the specification](https://www.unicode.org/reports/tr46/#ToUnicode) - /// - /// ``` - /// use ada_url::Idna; - /// assert_eq!(Idna::unicode("xn--meagefactory-m9a.ca"), "meßagefactory.ca"); - /// ``` + /// Converts an ASCII/Punycode domain to Unicode. #[must_use] - #[cfg(feature = "std")] pub fn unicode(input: &str) -> String { - unsafe { ffi::ada_idna_to_unicode(input.as_ptr().cast(), input.len()) }.to_string() - } - - /// Process international domains according to the UTS #46 standard. - /// Returns empty string if the input is invalid. - /// - /// For more information, [read the specification](https://www.unicode.org/reports/tr46/#ToASCII) - /// - /// ``` - /// use ada_url::Idna; - /// assert_eq!(Idna::ascii("meßagefactory.ca"), "xn--meagefactory-m9a.ca"); - /// ``` + domain_to_unicode(input).0 + } + + /// Converts a Unicode domain to ASCII, returning an empty string on error. #[must_use] - #[cfg(feature = "std")] pub fn ascii(input: &str) -> String { - unsafe { ffi::ada_idna_to_ascii(input.as_ptr().cast(), input.len()) }.to_string() + domain_to_ascii(input).unwrap_or_default() + } +} + +/// Converts a Unicode domain to its ASCII form using non-transitional UTS #46. +pub fn domain_to_ascii(domain: &str) -> Result { + if domain.len() > MAX_DOMAIN_INPUT_BYTES { + return Err(IdnaError); + } + if domain.is_ascii() { + if contains_forbidden_domain_byte(domain.as_bytes()) { + return Err(IdnaError); + } + return Ok(domain.to_ascii_lowercase()); + } + + let input: Vec = domain.chars().map(u32::from).collect(); + let mut mapped = map_code_points(&input)?; + if !normalization::is_nfc(&mapped) { + normalization::normalize(&mut mapped); + } + + let mut output = String::with_capacity(domain.len() + 8); + let mut label_start = 0; + loop { + let label_end = mapped[label_start..] + .iter() + .position(|code_point| *code_point == u32::from(b'.')) + .map_or(mapped.len(), |relative| label_start + relative); + let label = &mapped[label_start..label_end]; + append_ascii_label(label, &mut output)?; + if label_end == mapped.len() { + break; + } + output.push('.'); + label_start = label_end + 1; + } + + if contains_forbidden_domain_byte(output.as_bytes()) { + return Err(IdnaError); + } + Ok(output) +} + +/// Converts an ASCII/Punycode domain to Unicode. +pub fn domain_to_unicode(domain: &str) -> (String, Result<(), IdnaError>) { + if domain.len() > MAX_DOMAIN_INPUT_BYTES { + return (String::from(domain), Err(IdnaError)); + } + let mut output = String::with_capacity(domain.len()); + for (index, label) in domain.split('.').enumerate() { + if index != 0 { + output.push('.'); + } + let Some(punycode) = label.strip_prefix("xn--").filter(|_| label.is_ascii()) else { + output.push_str(label); + continue; + }; + let Some(decoded) = punycode::decode(punycode) else { + output.push_str(label); + continue; + }; + let accepted = !decoded.iter().all(|code_point| *code_point < 0x80) + && map_code_points(&decoded).is_ok_and(|mapped| mapped == decoded) + && normalization::is_nfc(&decoded) + && is_label_valid(&decoded); + if accepted { + for code_point in decoded { + if let Some(character) = char::from_u32(code_point) { + output.push(character); + } + } + } else { + output.push_str(label); + } + } + (output, Ok(())) +} + +fn append_ascii_label(label: &[u32], output: &mut String) -> Result<(), IdnaError> { + if label.is_empty() { + return Ok(()); + } + if label.starts_with(&[ + u32::from(b'x'), + u32::from(b'n'), + u32::from(b'-'), + u32::from(b'-'), + ]) { + if !label.iter().all(|code_point| *code_point < 0x80) { + return Err(IdnaError); + } + let ascii_start = output.len(); + for &code_point in label { + output.push(char::from(code_point as u8)); + } + let decoded = punycode::decode(&output[ascii_start + 4..]).ok_or(IdnaError)?; + if decoded.iter().all(|code_point| *code_point < 0x80) + || map_code_points(&decoded)? != decoded + || !normalization::is_nfc(&decoded) + || !is_label_valid(&decoded) + { + return Err(IdnaError); + } + } else if label.iter().all(|code_point| *code_point < 0x80) { + for &code_point in label { + output.push(char::from(code_point as u8)); + } + } else { + if !is_label_valid(label) { + return Err(IdnaError); + } + output.push_str("xn--"); + punycode::encode(label, output).ok_or(IdnaError)?; + } + Ok(()) +} + +fn map_code_points(input: &[u32]) -> Result, IdnaError> { + let mut length = 0; + for &code_point in input { + match tables::mapping(code_point) { + tables::Mapping::Valid => length += 1, + tables::Mapping::Disallowed => return Err(IdnaError), + tables::Mapping::Bytes(offset) => { + let bytes = tables::mapping_bytes(offset).ok_or(IdnaError)?; + length += core::str::from_utf8(bytes) + .map_err(|_| IdnaError)? + .chars() + .count(); + } + } + } + let mut output = Vec::with_capacity(length); + for &code_point in input { + match tables::mapping(code_point) { + tables::Mapping::Valid => output.push(code_point), + tables::Mapping::Disallowed => return Err(IdnaError), + tables::Mapping::Bytes(offset) => { + let bytes = tables::mapping_bytes(offset).ok_or(IdnaError)?; + output.extend( + core::str::from_utf8(bytes) + .map_err(|_| IdnaError)? + .chars() + .map(u32::from), + ); + } + } + } + debug_assert_eq!(output.len(), length); + Ok(output) +} + +fn is_label_valid(label: &[u32]) -> bool { + if label.is_empty() { + return true; + } + if tables::is_combining_mark(label[0]) { + return false; } + + for (index, &code_point) in label.iter().enumerate() { + if code_point == 0x200c { + if index != 0 && VIRAMA.binary_search(&label[index - 1]).is_ok() { + continue; + } + if index == 0 || index + 1 == label.len() { + return false; + } + let valid_before = label[..index].iter().any(|candidate| { + JOINING_LEFT.binary_search(candidate).is_ok() + || JOINING_DUAL.binary_search(candidate).is_ok() + }); + let valid_after = label[index + 1..].iter().any(|candidate| { + JOINING_RIGHT.binary_search(candidate).is_ok() + || JOINING_DUAL.binary_search(candidate).is_ok() + }); + if !valid_before || !valid_after { + return false; + } + } else if code_point == 0x200d + && (index == 0 || VIRAMA.binary_search(&label[index - 1]).is_err()) + { + return false; + } + } + + let rtl = label.iter().any(|code_point| { + matches!( + tables::direction(*code_point), + Direction::R | Direction::AL | Direction::AN + ) + }); + if !rtl { + return true; + } + let Some(last_non_nsm) = label + .iter() + .rposition(|code_point| tables::direction(*code_point) != Direction::NSM) + else { + return false; + }; + + if tables::direction(label[0]) == Direction::L { + if !label[..=last_non_nsm].iter().all(|code_point| { + matches!( + tables::direction(*code_point), + Direction::L + | Direction::EN + | Direction::ES + | Direction::CS + | Direction::ET + | Direction::ON + | Direction::BN + | Direction::NSM + ) + }) { + return false; + } + matches!( + tables::direction(label[last_non_nsm]), + Direction::L | Direction::EN + ) + } else { + if !matches!(tables::direction(label[0]), Direction::R | Direction::AL) { + return false; + } + let mut has_arabic_number = false; + let mut has_european_number = false; + for (index, &code_point) in label[..=last_non_nsm].iter().enumerate() { + let direction = tables::direction(code_point); + match direction { + Direction::AN => has_arabic_number = true, + Direction::EN => has_european_number = true, + _ => {} + } + if has_arabic_number && has_european_number + || !matches!( + direction, + Direction::R + | Direction::AL + | Direction::AN + | Direction::EN + | Direction::ES + | Direction::CS + | Direction::ET + | Direction::ON + | Direction::BN + | Direction::NSM + ) + || index == last_non_nsm + && !matches!( + direction, + Direction::R | Direction::AL | Direction::AN | Direction::EN + ) + { + return false; + } + } + true + } +} + +#[inline] +fn contains_forbidden_domain_byte(bytes: &[u8]) -> bool { + bytes.iter().any(|byte| { + matches!( + *byte, + 0x00..=0x20 + | 0x7f..=0xff + | b'#' + | b'%' + | b'/' + | b':' + | b'<' + | b'>' + | b'?' + | b'@' + | b'[' + | b'\\' + | b']' + | b'^' + | b'|' + ) + }) } #[cfg(test)] mod tests { - #[cfg_attr(not(feature = "std"), allow(unused_imports))] - use crate::idna::*; + use super::{domain_to_ascii, domain_to_unicode}; #[test] - fn unicode_should_work() { - #[cfg(feature = "std")] - assert_eq!(Idna::unicode("xn--meagefactory-m9a.ca"), "meßagefactory.ca"); + fn converts_unicode_and_punycode() { + assert_eq!( + domain_to_ascii("café.example").unwrap(), + "xn--caf-dma.example" + ); + assert_eq!(domain_to_unicode("xn--caf-dma.example").0, "café.example"); } #[test] - fn ascii_should_work() { - #[cfg(feature = "std")] - assert_eq!(Idna::ascii("meßagefactory.ca"), "xn--meagefactory-m9a.ca"); + fn applies_uts46_mapping_and_nfc() { + assert_eq!(domain_to_ascii("faß.de").unwrap(), "xn--fa-hia.de"); + assert_eq!( + domain_to_ascii("CAFE\u{301}.example").unwrap(), + "xn--caf-dma.example" + ); } } diff --git a/src/idna_joiners.rs b/src/idna_joiners.rs new file mode 100644 index 0000000..393c460 --- /dev/null +++ b/src/idna_joiners.rs @@ -0,0 +1,51 @@ +//! Generated ContextJ joining data from Ada 4.0.0 (Unicode 17). + +pub(super) const VIRAMA: &[u32] = &[ + 0x094D, 0x09CD, 0x0A4D, 0x0ACD, 0x0B4D, 0x0BCD, 0x0C4D, 0x0CCD, 0x0D3B, 0x0D3C, 0x0D4D, 0x0DCA, + 0x0E3A, 0x0EBA, 0x0F84, 0x1039, 0x103A, 0x1714, 0x1734, 0x17D2, 0x1A60, 0x1B44, 0x1BAA, 0x1BAB, + 0x1BF2, 0x1BF3, 0x2D7F, 0xA806, 0xA82C, 0xA8C4, 0xA953, 0xA9C0, 0xAAF6, 0xABED, 0x10A3F, + 0x11046, 0x1107F, 0x110B9, 0x11133, 0x11134, 0x111C0, 0x11235, 0x112EA, 0x1134D, 0x11442, + 0x114C2, 0x115BF, 0x1163F, 0x116B6, 0x1172B, 0x11839, 0x1193D, 0x1193E, 0x119E0, 0x11A34, + 0x11A47, 0x11A99, 0x11C3F, 0x11D44, 0x11D45, 0x11D97, +]; + +pub(super) const JOINING_RIGHT: &[u32] = &[ + 0x622, 0x623, 0x624, 0x625, 0x627, 0x629, 0x62f, 0x630, 0x631, 0x632, 0x648, 0x671, 0x672, + 0x673, 0x675, 0x676, 0x677, 0x688, 0x689, 0x68a, 0x68b, 0x68c, 0x68d, 0x68e, 0x68f, 0x690, + 0x691, 0x692, 0x693, 0x694, 0x695, 0x696, 0x697, 0x698, 0x699, 0x6c0, 0x6c3, 0x6c4, 0x6c5, + 0x6c6, 0x6c7, 0x6c8, 0x6c9, 0x6ca, 0x6cb, 0x6cd, 0x6cf, 0x6d2, 0x6d3, 0x6d5, 0x6ee, 0x6ef, + 0x710, 0x715, 0x716, 0x717, 0x718, 0x719, 0x71e, 0x728, 0x72a, 0x72c, 0x72f, 0x74d, 0x759, + 0x75a, 0x75b, 0x854, 0x8aa, 0x8ab, 0x8ac, +]; + +pub(super) const JOINING_LEFT: &[u32] = &[0xa872]; + +pub(super) const JOINING_DUAL: &[u32] = &[ + 0x620, 0x626, 0x628, 0x62a, 0x62b, 0x62c, 0x62d, 0x62e, 0x633, 0x634, 0x635, 0x636, 0x637, + 0x638, 0x639, 0x63a, 0x63b, 0x63c, 0x63d, 0x63e, 0x63f, 0x641, 0x642, 0x643, 0x644, 0x645, + 0x646, 0x647, 0x649, 0x64a, 0x66e, 0x66f, 0x678, 0x679, 0x67a, 0x67b, 0x67c, 0x67d, 0x67e, + 0x67f, 0x680, 0x681, 0x682, 0x683, 0x684, 0x685, 0x686, 0x687, 0x69a, 0x69b, 0x69c, 0x69d, + 0x69e, 0x69f, 0x6a0, 0x6a1, 0x6a2, 0x6a3, 0x6a4, 0x6a5, 0x6a6, 0x6a7, 0x6a8, 0x6a9, 0x6aa, + 0x6ab, 0x6ac, 0x6ad, 0x6ae, 0x6af, 0x6b0, 0x6b1, 0x6b2, 0x6b3, 0x6b4, 0x6b5, 0x6b6, 0x6b7, + 0x6b8, 0x6b9, 0x6ba, 0x6bb, 0x6bc, 0x6bd, 0x6be, 0x6bf, 0x6c1, 0x6c2, 0x6cc, 0x6ce, 0x6d0, + 0x6d1, 0x6fa, 0x6fb, 0x6fc, 0x6ff, 0x712, 0x713, 0x714, 0x71a, 0x71b, 0x71c, 0x71d, 0x71f, + 0x720, 0x721, 0x722, 0x723, 0x724, 0x725, 0x726, 0x727, 0x729, 0x72b, 0x72d, 0x72e, 0x74e, + 0x74f, 0x750, 0x751, 0x752, 0x753, 0x754, 0x755, 0x756, 0x757, 0x758, 0x75c, 0x75d, 0x75e, + 0x75f, 0x760, 0x761, 0x762, 0x763, 0x764, 0x765, 0x766, 0x850, 0x851, 0x852, 0x853, 0x855, + 0x8a0, 0x8a2, 0x8a3, 0x8a4, 0x8a5, 0x8a6, 0x8a7, 0x8a8, 0x8a9, 0x1807, 0x1820, 0x1821, 0x1822, + 0x1823, 0x1824, 0x1825, 0x1826, 0x1827, 0x1828, 0x1829, 0x182a, 0x182b, 0x182c, 0x182d, 0x182e, + 0x182f, 0x1830, 0x1831, 0x1832, 0x1833, 0x1834, 0x1835, 0x1836, 0x1837, 0x1838, 0x1839, 0x183a, + 0x183b, 0x183c, 0x183d, 0x183e, 0x183f, 0x1840, 0x1841, 0x1842, 0x1843, 0x1844, 0x1845, 0x1846, + 0x1847, 0x1848, 0x1849, 0x184a, 0x184b, 0x184c, 0x184d, 0x184e, 0x184f, 0x1850, 0x1851, 0x1852, + 0x1853, 0x1854, 0x1855, 0x1856, 0x1857, 0x1858, 0x1859, 0x185a, 0x185b, 0x185c, 0x185d, 0x185e, + 0x185f, 0x1860, 0x1861, 0x1862, 0x1863, 0x1864, 0x1865, 0x1866, 0x1867, 0x1868, 0x1869, 0x186a, + 0x186b, 0x186c, 0x186d, 0x186e, 0x186f, 0x1870, 0x1871, 0x1872, 0x1873, 0x1874, 0x1875, 0x1876, + 0x1877, 0x1887, 0x1888, 0x1889, 0x188a, 0x188b, 0x188c, 0x188d, 0x188e, 0x188f, 0x1890, 0x1891, + 0x1892, 0x1893, 0x1894, 0x1895, 0x1896, 0x1897, 0x1898, 0x1899, 0x189a, 0x189b, 0x189c, 0x189d, + 0x189e, 0x189f, 0x18a0, 0x18a1, 0x18a2, 0x18a3, 0x18a4, 0x18a5, 0x18a6, 0x18a7, 0x18a8, 0x18aa, + 0xa840, 0xa841, 0xa842, 0xa843, 0xa844, 0xa845, 0xa846, 0xa847, 0xa848, 0xa849, 0xa84a, 0xa84b, + 0xa84c, 0xa84d, 0xa84e, 0xa84f, 0xa850, 0xa851, 0xa852, 0xa853, 0xa854, 0xa855, 0xa856, 0xa857, + 0xa858, 0xa859, 0xa85a, 0xa85b, 0xa85c, 0xa85d, 0xa85e, 0xa85f, 0xa860, 0xa861, 0xa862, 0xa863, + 0xa864, 0xa865, 0xa866, 0xa867, 0xa868, 0xa869, 0xa86a, 0xa86b, 0xa86c, 0xa86d, 0xa86e, 0xa86f, + 0xa870, 0xa871, +]; diff --git a/src/idna_normalization.rs b/src/idna_normalization.rs new file mode 100644 index 0000000..5249288 --- /dev/null +++ b/src/idna_normalization.rs @@ -0,0 +1,199 @@ +//! Dependency-free Unicode NFC normalization over Ada's compact tables. + +use alloc::vec::Vec; + +use super::tables; + +const HANGUL_SBASE: u32 = 0xac00; +const HANGUL_TBASE: u32 = 0x11a7; +const HANGUL_VBASE: u32 = 0x1161; +const HANGUL_LBASE: u32 = 0x1100; +const HANGUL_LCOUNT: u32 = 19; +const HANGUL_VCOUNT: u32 = 21; +const HANGUL_TCOUNT: u32 = 28; +const HANGUL_NCOUNT: u32 = HANGUL_VCOUNT * HANGUL_TCOUNT; +const HANGUL_SCOUNT: u32 = HANGUL_LCOUNT * HANGUL_NCOUNT; + +pub(super) fn normalize(input: &mut Vec) { + let additional = input + .iter() + .map(|code_point| decomposition_length(*code_point).saturating_sub(1)) + .sum(); + if additional != 0 { + decompose(input, additional); + } + reorder_marks(input); + compose(input); +} + +pub(super) fn is_nfc(input: &[u32]) -> bool { + if input + .iter() + .any(|code_point| decomposition_length(*code_point) == 1) + { + return false; + } + let mut previous = 0; + for &code_point in input { + let class = tables::combining_class(code_point); + if class != 0 && previous > class { + return false; + } + previous = class; + } + !would_compose(input) +} + +fn decomposition_length(code_point: u32) -> usize { + if (HANGUL_SBASE..HANGUL_SBASE + HANGUL_SCOUNT).contains(&code_point) { + return if (code_point - HANGUL_SBASE).is_multiple_of(HANGUL_TCOUNT) { + 2 + } else { + 3 + }; + } + tables::decomposition(code_point).map_or(0, |bytes| bytes.len() / 4) +} + +fn decompose(input: &mut Vec, additional: usize) { + let original = input.len(); + input.resize(original + additional, 0); + let mut destination = input.len(); + for source in (0..original).rev() { + let code_point = input[source]; + if (HANGUL_SBASE..HANGUL_SBASE + HANGUL_SCOUNT).contains(&code_point) { + let syllable = code_point - HANGUL_SBASE; + if !syllable.is_multiple_of(HANGUL_TCOUNT) { + destination -= 1; + input[destination] = HANGUL_TBASE + syllable % HANGUL_TCOUNT; + } + destination -= 1; + input[destination] = HANGUL_VBASE + (syllable % HANGUL_NCOUNT) / HANGUL_TCOUNT; + destination -= 1; + input[destination] = HANGUL_LBASE + syllable / HANGUL_NCOUNT; + } else if let Some(decomposition) = tables::decomposition(code_point) { + for index in (0..decomposition.len() / 4).rev() { + destination -= 1; + input[destination] = tables::decomposition_code_point(decomposition, index); + } + } else { + destination -= 1; + input[destination] = code_point; + } + } +} + +fn reorder_marks(input: &mut [u32]) { + for index in 1..input.len() { + let class = tables::combining_class(input[index]); + if class == 0 { + continue; + } + let code_point = input[index]; + let mut destination = index; + while destination != 0 && tables::combining_class(input[destination - 1]) > class { + input[destination] = input[destination - 1]; + destination -= 1; + } + input[destination] = code_point; + } +} + +fn would_compose(input: &[u32]) -> bool { + let mut index = 0; + while index < input.len() { + let current = input[index]; + if (HANGUL_LBASE..HANGUL_LBASE + HANGUL_LCOUNT).contains(¤t) { + if input + .get(index + 1) + .is_some_and(|next| (HANGUL_VBASE..HANGUL_VBASE + HANGUL_VCOUNT).contains(next)) + { + return true; + } + } else if (HANGUL_SBASE..HANGUL_SBASE + HANGUL_SCOUNT).contains(¤t) { + if (current - HANGUL_SBASE).is_multiple_of(HANGUL_TCOUNT) + && input.get(index + 1).is_some_and(|next| { + (HANGUL_TBASE + 1..HANGUL_TBASE + HANGUL_TCOUNT).contains(next) + }) + { + return true; + } + } else { + let mut previous_class = -1_i16; + let mut next = index + 1; + while next < input.len() { + let class = tables::combining_class(input[next]); + if previous_class < i16::from(class) + && tables::compose(current, input[next]).is_some() + { + return true; + } + if class == 0 { + break; + } + previous_class = i16::from(class); + next += 1; + } + } + index += 1; + } + false +} + +fn compose(input: &mut Vec) { + let mut source = 0; + let mut destination = 0; + while source < input.len() { + let current = input[source]; + input[destination] = current; + if (HANGUL_LBASE..HANGUL_LBASE + HANGUL_LCOUNT).contains(¤t) + && input + .get(source + 1) + .is_some_and(|next| (HANGUL_VBASE..HANGUL_VBASE + HANGUL_VCOUNT).contains(next)) + { + source += 1; + input[destination] = HANGUL_SBASE + + ((current - HANGUL_LBASE) * HANGUL_VCOUNT + input[source] - HANGUL_VBASE) + * HANGUL_TCOUNT; + if input + .get(source + 1) + .is_some_and(|next| (HANGUL_TBASE + 1..HANGUL_TBASE + HANGUL_TCOUNT).contains(next)) + { + source += 1; + input[destination] += input[source] - HANGUL_TBASE; + } + } else if (HANGUL_SBASE..HANGUL_SBASE + HANGUL_SCOUNT).contains(¤t) + && (current - HANGUL_SBASE).is_multiple_of(HANGUL_TCOUNT) + && input + .get(source + 1) + .is_some_and(|next| (HANGUL_TBASE + 1..HANGUL_TBASE + HANGUL_TCOUNT).contains(next)) + { + source += 1; + input[destination] += input[source] - HANGUL_TBASE; + } else { + let starter = destination; + let mut previous_class = -1_i16; + while source + 1 < input.len() { + let next = input[source + 1]; + let class = tables::combining_class(next); + if previous_class < i16::from(class) + && let Some(composed) = tables::compose(input[starter], next) + { + input[starter] = composed; + source += 1; + continue; + } + if class == 0 { + break; + } + previous_class = i16::from(class); + destination += 1; + source += 1; + input[destination] = next; + } + } + source += 1; + destination += 1; + } + input.truncate(destination); +} diff --git a/src/idna_punycode.rs b/src/idna_punycode.rs new file mode 100644 index 0000000..b930f3b --- /dev/null +++ b/src/idna_punycode.rs @@ -0,0 +1,164 @@ +//! RFC 3492 Punycode. + +use alloc::{string::String, vec::Vec}; + +const BASE: i32 = 36; +const TMIN: i32 = 1; +const TMAX: i32 = 26; +const SKEW: i32 = 38; +const DAMP: i32 = 700; +const INITIAL_BIAS: i32 = 72; +const INITIAL_N: u32 = 128; + +#[inline] +const fn digit(byte: u8) -> Option { + match byte { + b'a'..=b'z' => Some((byte - b'a') as i32), + b'0'..=b'9' => Some((byte - b'0') as i32 + 26), + _ => None, + } +} + +#[inline] +const fn encode_digit(value: i32) -> u8 { + if value < 26 { + value as u8 + b'a' + } else { + value as u8 - 26 + b'0' + } +} + +#[inline] +const fn threshold(k: i32, bias: i32) -> i32 { + if k <= bias { + TMIN + } else if k >= bias + TMAX { + TMAX + } else { + k - bias + } +} + +fn adapt(mut delta: i32, count: i32, first: bool) -> i32 { + delta = if first { delta / DAMP } else { delta / 2 }; + delta += delta / count; + let mut k = 0; + while delta > ((BASE - TMIN) * TMAX) / 2 { + delta /= BASE - TMIN; + k += BASE; + } + k + ((BASE - TMIN + 1) * delta) / (delta + SKEW) +} + +pub(super) fn decode(input: &str) -> Option> { + let bytes = input.as_bytes(); + let mut output = Vec::with_capacity(bytes.len()); + let mut suffix_start = 0; + if let Some(delimiter) = bytes.iter().rposition(|byte| *byte == b'-') { + if bytes[..delimiter].iter().any(|byte| *byte >= 0x80) { + return None; + } + output.extend(bytes[..delimiter].iter().map(|byte| u32::from(*byte))); + suffix_start = delimiter + 1; + } + + let mut n = INITIAL_N; + let mut index = 0_i32; + let mut bias = INITIAL_BIAS; + let mut cursor = suffix_start; + while cursor < bytes.len() { + let old_index = index; + let mut weight = 1_i32; + let mut k = BASE; + loop { + let value = digit(*bytes.get(cursor)?)?; + cursor += 1; + index = index.checked_add(value.checked_mul(weight)?)?; + let limit = threshold(k, bias); + if value < limit { + break; + } + weight = weight.checked_mul(BASE - limit)?; + k = k.checked_add(BASE)?; + } + let count = i32::try_from(output.len()).ok()?.checked_add(1)?; + bias = adapt(index - old_index, count, old_index == 0); + n = n.checked_add(u32::try_from(index / count).ok()?)?; + index %= count; + if !(0x80..=0x10ffff).contains(&n) || (0xd800..=0xdfff).contains(&n) { + return None; + } + output.insert(usize::try_from(index).ok()?, n); + index += 1; + } + if output.starts_with(&[ + u32::from(b'x'), + u32::from(b'n'), + u32::from(b'-'), + u32::from(b'-'), + ]) { + return None; + } + Some(output) +} + +pub(super) fn encode(input: &[u32], output: &mut String) -> Option<()> { + let mut n = INITIAL_N; + let mut delta = 0_i32; + let mut bias = INITIAL_BIAS; + let mut handled = 0_usize; + for &code_point in input { + if code_point < 0x80 { + output.push(char::from(code_point as u8)); + handled += 1; + } + if code_point > 0x10ffff || (0xd800..=0xdfff).contains(&code_point) { + return None; + } + } + let basic = handled; + if basic != 0 { + output.push('-'); + } + + while handled < input.len() { + let next = input + .iter() + .copied() + .filter(|code_point| *code_point >= n) + .min()?; + let count = i32::try_from(handled).ok()?.checked_add(1)?; + delta = delta.checked_add( + i32::try_from(next.checked_sub(n)?) + .ok()? + .checked_mul(count)?, + )?; + n = next; + for &code_point in input { + if code_point < n { + delta = delta.checked_add(1)?; + } else if code_point == n { + let mut value = delta; + let mut k = BASE; + loop { + let limit = threshold(k, bias); + if value < limit { + break; + } + output.push(char::from(encode_digit( + limit + (value - limit) % (BASE - limit), + ))); + value = (value - limit) / (BASE - limit); + k = k.checked_add(BASE)?; + } + output.push(char::from(encode_digit(value))); + bias = adapt(delta, count, handled == basic); + delta = 0; + handled += 1; + } + } + delta = delta.checked_add(1)?; + n = n.checked_add(1)?; + } + Some(()) +} diff --git a/src/idna_tables.bin b/src/idna_tables.bin new file mode 100644 index 0000000..efac9e2 Binary files /dev/null and b/src/idna_tables.bin differ diff --git a/src/idna_tables.rs b/src/idna_tables.rs new file mode 100644 index 0000000..d207f26 --- /dev/null +++ b/src/idna_tables.rs @@ -0,0 +1,286 @@ +//! Compact Unicode 17 tables generated from Ada 4.0.0. + +const DATA: &[u8; 224_608] = include_bytes!("idna_tables.bin"); + +const OFF_IDNA_STAGE1: usize = 0; +const OFF_IDNA_STAGE2: usize = 6_564; +const OFF_IDNA_BOOL_BLOCKS: usize = 29_480; +const OFF_IDNA_UTF8_MAPPINGS: usize = 31_280; +const IDNA_UTF8_MAPPINGS_LEN: usize = 17_383; +const OFF_DECOMPOSITION_INDEX: usize = 48_663; +const OFF_DECOMPOSITION_BLOCK: usize = 53_016; +const OFF_DECOMPOSITION_DATA: usize = 87_456; +const DECOMPOSITION_DATA_LEN: usize = 9_102; +const OFF_CCC_INDEX: usize = 123_864; +const OFF_CCC_BLOCK: usize = 128_216; +const OFF_COMPOSITION_INDEX: usize = 145_368; +const OFF_COMPOSITION_BLOCK: usize = 149_720; +const OFF_COMPOSITION_DATA: usize = 184_160; +const COMPOSITION_DATA_LEN: usize = 1_883; +const OFF_DIR_START: usize = 209_244; +const OFF_DIR_FINAL: usize = 215_040; +const OFF_DIR_VALUE: usize = 220_836; +const DIR_COUNT: usize = 1_449; +const OFF_COMBINING_RANGES: usize = 222_288; +const COMBINING_RANGE_COUNT: usize = 290; + +const DECOMPOSITION_BLOCK_COLS: usize = 257; +const CCC_BLOCK_COLS: usize = 256; +const COMPOSITION_BLOCK_COLS: usize = 257; + +const IDNA_VALID: u16 = 0xffff; +const IDNA_DISALLOWED: u16 = 0xfffe; +const IDNA_BOOL_FLAG: u16 = 0x8000; +const IDNA_LOW_RANGE_END: u32 = 0x33480; +const IDNA_HIGH_IGNORED_START: u32 = 0xe0100; +const IDNA_HIGH_IGNORED_END: u32 = 0xe01f0; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) enum Mapping { + Valid, + Disallowed, + Bytes(usize), +} + +#[inline] +pub(super) fn mapping(code_point: u32) -> Mapping { + let status = if code_point < IDNA_LOW_RANGE_END { + let block = read_u16(OFF_IDNA_STAGE1, (code_point >> 6) as usize); + if block & IDNA_BOOL_FLAG != 0 { + let bit = usize::from(block & !IDNA_BOOL_FLAG) * 64 + (code_point as usize & 63); + if read_u64(OFF_IDNA_BOOL_BLOCKS, bit >> 6) & (1_u64 << (bit & 63)) != 0 { + IDNA_VALID + } else { + IDNA_DISALLOWED + } + } else { + read_u16( + OFF_IDNA_STAGE2, + usize::from(block) + (code_point as usize & 63), + ) + } + } else if (IDNA_HIGH_IGNORED_START..IDNA_HIGH_IGNORED_END).contains(&code_point) { + 0 + } else { + IDNA_DISALLOWED + }; + + match status { + IDNA_VALID => Mapping::Valid, + IDNA_DISALLOWED => Mapping::Disallowed, + offset => Mapping::Bytes(usize::from(offset)), + } +} + +#[inline] +pub(super) fn mapping_bytes(offset: usize) -> Option<&'static [u8]> { + if offset >= IDNA_UTF8_MAPPINGS_LEN { + return None; + } + let start = OFF_IDNA_UTF8_MAPPINGS + offset; + let tail = DATA.get(start..OFF_IDNA_UTF8_MAPPINGS + IDNA_UTF8_MAPPINGS_LEN)?; + let length = tail.iter().position(|byte| *byte == 0)?; + Some(&tail[..length]) +} + +#[inline] +pub(super) fn decomposition(code_point: u32) -> Option<&'static [u8]> { + if code_point >= 0x110000 { + return None; + } + let row = usize::from(DATA[OFF_DECOMPOSITION_INDEX + (code_point >> 8) as usize]); + let cell = row * DECOMPOSITION_BLOCK_COLS + (code_point as usize & 0xff); + let first = read_u16(OFF_DECOMPOSITION_BLOCK, cell); + let second = read_u16(OFF_DECOMPOSITION_BLOCK, cell + 1); + let length = usize::from((second >> 2).saturating_sub(first >> 2)); + if length == 0 || first & 1 != 0 { + return None; + } + let start = usize::from(first >> 2); + (start + length <= DECOMPOSITION_DATA_LEN).then(|| { + let byte_start = OFF_DECOMPOSITION_DATA + start * 4; + &DATA[byte_start..byte_start + length * 4] + }) +} + +#[inline] +pub(super) fn combining_class(code_point: u32) -> u8 { + if code_point >= 0x110000 { + return 0; + } + let row = usize::from(DATA[OFF_CCC_INDEX + (code_point >> 8) as usize]); + DATA[OFF_CCC_BLOCK + row * CCC_BLOCK_COLS + (code_point as usize & 0xff)] +} + +#[inline] +pub(super) fn composition_bounds(starter: u32) -> (usize, usize) { + if starter >= 0x110000 { + return (0, 0); + } + let row = usize::from(DATA[OFF_COMPOSITION_INDEX + (starter >> 8) as usize]); + let cell = row * COMPOSITION_BLOCK_COLS + (starter as usize & 0xff); + ( + usize::from(read_u16(OFF_COMPOSITION_BLOCK, cell)), + usize::from(read_u16(OFF_COMPOSITION_BLOCK, cell + 1)), + ) +} + +pub(super) fn compose(starter: u32, combining: u32) -> Option { + let (mut left, mut right) = composition_bounds(starter); + if left == right || right > COMPOSITION_DATA_LEN { + return None; + } + while left + 2 < right { + let middle = left + (((right - left) >> 1) & !1); + match composition_value(middle).cmp(&combining) { + core::cmp::Ordering::Less => left = middle, + core::cmp::Ordering::Greater => right = middle, + core::cmp::Ordering::Equal => return Some(composition_value(middle + 1)), + } + } + (left + 1 < COMPOSITION_DATA_LEN && composition_value(left) == combining) + .then(|| composition_value(left + 1)) +} + +#[inline] +pub(super) fn decomposition_code_point(bytes: &[u8], index: usize) -> u32 { + read_u32_slice(bytes, index) +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[allow(clippy::upper_case_acronyms)] +pub(super) enum Direction { + None, + BN, + CS, + ES, + ON, + EN, + L, + R, + NSM, + AL, + AN, + ET, + WS, + RLO, + LRO, + PDF, + RLE, + RLI, + FSI, + PDI, + LRI, + B, + S, + LRE, +} + +pub(super) fn direction(code_point: u32) -> Direction { + let mut low = 0; + let mut high = DIR_COUNT; + while low < high { + let middle = low + (high - low) / 2; + if read_u32(OFF_DIR_FINAL, middle) < code_point { + low = middle + 1; + } else { + high = middle; + } + } + if low == DIR_COUNT || code_point < read_u32(OFF_DIR_START, low) { + return Direction::None; + } + match DATA[OFF_DIR_VALUE + low] { + 1 => Direction::BN, + 2 => Direction::CS, + 3 => Direction::ES, + 4 => Direction::ON, + 5 => Direction::EN, + 6 => Direction::L, + 7 => Direction::R, + 8 => Direction::NSM, + 9 => Direction::AL, + 10 => Direction::AN, + 11 => Direction::ET, + 12 => Direction::WS, + 13 => Direction::RLO, + 14 => Direction::LRO, + 15 => Direction::PDF, + 16 => Direction::RLE, + 17 => Direction::RLI, + 18 => Direction::FSI, + 19 => Direction::PDI, + 20 => Direction::LRI, + 21 => Direction::B, + 22 => Direction::S, + 23 => Direction::LRE, + _ => Direction::None, + } +} + +pub(super) fn is_combining_mark(code_point: u32) -> bool { + let mut low = 0; + let mut high = COMBINING_RANGE_COUNT; + while low < high { + let middle = low + (high - low) / 2; + if range_end(OFF_COMBINING_RANGES, middle) < code_point { + low = middle + 1; + } else { + high = middle; + } + } + low < COMBINING_RANGE_COUNT && code_point >= range_start(OFF_COMBINING_RANGES, low) +} + +#[inline] +fn composition_value(index: usize) -> u32 { + read_u32(OFF_COMPOSITION_DATA, index) +} + +#[inline] +fn range_start(offset: usize, index: usize) -> u32 { + read_u32(offset, index * 2) +} + +#[inline] +fn range_end(offset: usize, index: usize) -> u32 { + read_u32(offset, index * 2 + 1) +} + +#[inline] +fn read_u16(offset: usize, index: usize) -> u16 { + let position = offset + index * 2; + u16::from_le_bytes([DATA[position], DATA[position + 1]]) +} + +#[inline] +fn read_u32(offset: usize, index: usize) -> u32 { + read_u32_slice(DATA, offset / 4 + index) +} + +#[inline] +fn read_u32_slice(bytes: &[u8], index: usize) -> u32 { + let position = index * 4; + u32::from_le_bytes([ + bytes[position], + bytes[position + 1], + bytes[position + 2], + bytes[position + 3], + ]) +} + +#[inline] +fn read_u64(offset: usize, index: usize) -> u64 { + let position = offset + index * 8; + u64::from_le_bytes([ + DATA[position], + DATA[position + 1], + DATA[position + 2], + DATA[position + 3], + DATA[position + 4], + DATA[position + 5], + DATA[position + 6], + DATA[position + 7], + ]) +} diff --git a/src/lib.rs b/src/lib.rs index 3afee2d..819acb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,1101 +1,51 @@ -//! # Ada URL +//! A memory-safe, high-performance WHATWG URL parser. //! -//! Ada is a fast and spec-compliant URL parser written in C++. -//! - It's widely tested by both Web Platform Tests and Google OSS Fuzzer. -//! - It is extremely fast. -//! - It's the default URL parser of Node.js since Node 18.16.0. -//! - It supports Unicode Technical Standard. -//! -//! The Ada library passes the full range of tests from the specification, across a wide range -//! of platforms (e.g., Windows, Linux, macOS). -//! -//! ## Performance -//! -//! Ada is extremely fast. -//! For more information read our [benchmark page](https://ada-url.com/docs/performance). -//! -//! ```text -//! ada ▏ 188 ns/URL ███▏ -//! servo url ▏ 664 ns/URL ███████████▎ -//! CURL ▏ 1471 ns/URL █████████████████████████ -//! ``` -//! -//! ## serde -//! -//! If you enable the `serde` feature, [`Url`](struct.Url.html) will implement -//! [`serde::Serialize`](https://docs.rs/serde/1/serde/trait.Serialize.html) and -//! [`serde::Deserialize`](https://docs.rs/serde/1/serde/trait.Deserialize.html). -//! See [serde documentation](https://serde.rs) for more information. -//! -//! ```toml -//! ada-url = { version = "1", features = ["serde"] } -//! ``` -//! -//! ## no-std -//! -//! Whilst `ada-url` has `std` feature enabled by default, you can set `no-default-features` -//! get a subset of features that work in no-std environment. -//! -//! ```toml -//! ada-url = { version = "1", no-default-features = true } -//! ``` +//! The primary type is [`Url`]. It stores one normalized serialization and +//! compact byte offsets, so component access is allocation-free. #![cfg_attr(not(feature = "std"), no_std)] +#![deny(unsafe_code)] +#![deny(unsafe_op_in_unsafe_fn)] -mod ffi; +extern crate alloc; +#[cfg(test)] +extern crate std; + +mod bytes; +mod components; +mod encoding; +mod error; +mod fast_path; mod idna; -mod url_search_params; -pub use idna::Idna; -pub use url_search_params::{ +mod search_params; +mod url; + +pub use components::{Components, HostType, SchemeType, UrlComponents}; +pub use encoding::{PercentEncodeSet, percent_decode, percent_encode}; +pub use error::{ParseError, ParseErrorKind}; +pub use idna::{Idna, IdnaError, domain_to_ascii, domain_to_unicode}; +pub use search_params::{ UrlSearchParams, UrlSearchParamsEntry, UrlSearchParamsEntryIterator, UrlSearchParamsKeyIterator, UrlSearchParamsValueIterator, }; - #[cfg(feature = "std")] -extern crate std; - -#[cfg(feature = "std")] -use std::string::String; +pub use url::href_from_file; +pub use url::{Url, can_parse, get_max_input_length, parse, set_max_input_length}; -use core::{borrow, ffi::c_uint, fmt, hash, ops}; +use core::fmt; -/// Error type of [`Url::parse`]. +/// Error type returned by [`Url::parse`]. #[derive(Debug, PartialEq, Eq)] pub struct ParseUrlError { - /// The invalid input that caused the error. + /// The invalid input that could not be parsed. pub input: Input, } -impl fmt::Display for ParseUrlError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Invalid url: {:?}", self.input) - } -} - -#[cfg(feature = "std")] // error still requires std: https://github.com/rust-lang/rust/issues/103765 -impl std::error::Error for ParseUrlError {} - -/// Defines the type of the host. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum HostType { - Domain = 0, - IPV4 = 1, - IPV6 = 2, -} - -impl From for HostType { - fn from(value: c_uint) -> Self { - match value { - 0 => Self::Domain, - 1 => Self::IPV4, - 2 => Self::IPV6, - _ => Self::Domain, - } - } -} - -/// Defines the scheme type of the url. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SchemeType { - Http = 0, - NotSpecial = 1, - Https = 2, - Ws = 3, - Ftp = 4, - Wss = 5, - File = 6, -} - -impl From for SchemeType { - fn from(value: c_uint) -> Self { - match value { - 0 => Self::Http, - 1 => Self::NotSpecial, - 2 => Self::Https, - 3 => Self::Ws, - 4 => Self::Ftp, - 5 => Self::Wss, - 6 => Self::File, - _ => Self::NotSpecial, - } - } -} - -/// Components are a serialization-free representation of a URL. -/// For usages where string serialization has a high cost, you can -/// use url components with `href` attribute. -/// -/// By using 32-bit integers, we implicitly assume that the URL string -/// cannot exceed 4 GB. -/// -/// ```text -/// https://user:pass@example.com:1234/foo/bar?baz#quux -/// | | | | ^^^^| | | -/// | | | | | | | `----- hash_start -/// | | | | | | `--------- search_start -/// | | | | | `----------------- pathname_start -/// | | | | `--------------------- port -/// | | | `----------------------- host_end -/// | | `---------------------------------- host_start -/// | `--------------------------------------- username_end -/// `--------------------------------------------- protocol_end -/// ``` -#[derive(Debug)] -pub struct UrlComponents { - pub protocol_end: u32, - pub username_end: u32, - pub host_start: u32, - pub host_end: u32, - pub port: Option, - pub pathname_start: Option, - pub search_start: Option, - pub hash_start: Option, -} - -impl From<&ffi::ada_url_components> for UrlComponents { - fn from(value: &ffi::ada_url_components) -> Self { - let port = (value.port != u32::MAX).then_some(value.port); - let pathname_start = (value.pathname_start != u32::MAX).then_some(value.pathname_start); - let search_start = (value.search_start != u32::MAX).then_some(value.search_start); - let hash_start = (value.hash_start != u32::MAX).then_some(value.hash_start); - Self { - protocol_end: value.protocol_end, - username_end: value.username_end, - host_start: value.host_start, - host_end: value.host_end, - port, - pathname_start, - search_start, - hash_start, - } - } -} - -/// A parsed URL struct according to WHATWG URL specification. -#[derive(Eq)] -pub struct Url(*mut ffi::ada_url); - -/// Clone trait by default uses bit-wise copy. -/// In Rust, FFI requires deep copy, which requires an additional/inexpensive FFI call. -impl Clone for Url { - fn clone(&self) -> Self { - unsafe { ffi::ada_copy(self.0).into() } - } -} - -impl Drop for Url { - fn drop(&mut self) { - unsafe { ffi::ada_free(self.0) } - } -} - -impl From<*mut ffi::ada_url> for Url { - fn from(value: *mut ffi::ada_url) -> Self { - Self(value) - } -} - -type SetterResult = Result<(), ()>; - -#[inline] -const fn setter_result(successful: bool) -> SetterResult { - if successful { Ok(()) } else { Err(()) } -} - -impl Url { - /// Parses the input with an optional base - /// - /// ``` - /// use ada_url::Url; - /// let out = Url::parse("https://ada-url.github.io/ada", None) - /// .expect("This is a valid URL. Should have parsed it."); - /// assert_eq!(out.protocol(), "https:"); - /// ``` - pub fn parse(input: Input, base: Option<&str>) -> Result> - where - Input: AsRef, - { - let url_aggregator = match base { - Some(base) => unsafe { - ffi::ada_parse_with_base( - input.as_ref().as_ptr().cast(), - input.as_ref().len(), - base.as_ptr().cast(), - base.len(), - ) - }, - None => unsafe { ffi::ada_parse(input.as_ref().as_ptr().cast(), input.as_ref().len()) }, - }; - - if unsafe { ffi::ada_is_valid(url_aggregator) } { - Ok(url_aggregator.into()) - } else { - // `ada_parse`/`ada_parse_with_base` always allocate a result on the - // heap, even when parsing fails. On the success path the pointer is - // owned by `Url` and freed via its `Drop` impl, but on the error path - // we must free it here to avoid leaking the allocation. - unsafe { ffi::ada_free(url_aggregator) }; - Err(ParseUrlError { input }) - } - } - - /// Returns whether or not the URL can be parsed or not. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-canparse) - /// - /// ``` - /// use ada_url::Url; - /// assert!(Url::can_parse("https://ada-url.github.io/ada", None)); - /// assert!(Url::can_parse("/pathname", Some("https://ada-url.github.io/ada"))); - /// ``` - #[must_use] - pub fn can_parse(input: &str, base: Option<&str>) -> bool { - unsafe { - if let Some(base) = base { - ffi::ada_can_parse_with_base( - input.as_ptr().cast(), - input.len(), - base.as_ptr().cast(), - base.len(), - ) - } else { - ffi::ada_can_parse(input.as_ptr().cast(), input.len()) - } - } - } - - /// Returns the type of the host such as default, ipv4 or ipv6. - #[must_use] - pub fn host_type(&self) -> HostType { - HostType::from(unsafe { ffi::ada_get_host_type(self.0) }) - } - - /// Returns the type of the scheme such as http, https, etc. - #[must_use] - pub fn scheme_type(&self) -> SchemeType { - SchemeType::from(unsafe { ffi::ada_get_scheme_type(self.0) }) - } - - /// Return the origin of this URL - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-origin) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("blob:https://example.com/foo", None).expect("Invalid URL"); - /// assert_eq!(url.origin(), "https://example.com"); - /// ``` - #[must_use] - #[cfg(feature = "std")] - pub fn origin(&self) -> String { - unsafe { ffi::ada_get_origin(self.0) }.to_string() - } - - /// Return the parsed version of the URL with all components. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-href) - #[must_use] - pub fn href(&self) -> &str { - unsafe { ffi::ada_get_href(self.0) }.as_str() - } - - /// Updates the href of the URL, and triggers the URL parser. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_href("https://lemire.me").unwrap(); - /// assert_eq!(url.href(), "https://lemire.me/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_href(&mut self, input: &str) -> SetterResult { - setter_result(unsafe { ffi::ada_set_href(self.0, input.as_ptr().cast(), input.len()) }) - } - - /// Return the username for this URL as a percent-encoded ASCII string. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-username) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("ftp://rms:secret123@example.com", None).expect("Invalid URL"); - /// assert_eq!(url.username(), "rms"); - /// ``` - #[must_use] - pub fn username(&self) -> &str { - unsafe { ffi::ada_get_username(self.0) }.as_str() - } - - /// Updates the `username` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_username(Some("username")).unwrap(); - /// assert_eq!(url.href(), "https://username@yagiz.co/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_username(&mut self, input: Option<&str>) -> SetterResult { - setter_result(unsafe { - ffi::ada_set_username( - self.0, - input.unwrap_or("").as_ptr().cast(), - input.map_or(0, str::len), - ) - }) - } - - /// Return the password for this URL, if any, as a percent-encoded ASCII string. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-password) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("ftp://rms:secret123@example.com", None).expect("Invalid URL"); - /// assert_eq!(url.password(), "secret123"); - /// ``` - #[must_use] - pub fn password(&self) -> &str { - unsafe { ffi::ada_get_password(self.0) }.as_str() - } - - /// Updates the `password` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_password(Some("password")).unwrap(); - /// assert_eq!(url.href(), "https://:password@yagiz.co/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_password(&mut self, input: Option<&str>) -> SetterResult { - setter_result(unsafe { - ffi::ada_set_password( - self.0, - input.unwrap_or("").as_ptr().cast(), - input.map_or(0, str::len), - ) - }) - } - - /// Return the port number for this URL, or an empty string. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-port) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("https://example.com", None).expect("Invalid URL"); - /// assert_eq!(url.port(), ""); - /// - /// let url = Url::parse("https://example.com:8080", None).expect("Invalid URL"); - /// assert_eq!(url.port(), "8080"); - /// ``` - #[must_use] - pub fn port(&self) -> &str { - unsafe { ffi::ada_get_port(self.0) }.as_str() - } - - /// Updates the `port` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_port(Some("8080")).unwrap(); - /// assert_eq!(url.href(), "https://yagiz.co:8080/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_port(&mut self, input: Option<&str>) -> SetterResult { - if let Some(value) = input { - setter_result(unsafe { ffi::ada_set_port(self.0, value.as_ptr().cast(), value.len()) }) - } else { - unsafe { ffi::ada_clear_port(self.0) } - Ok(()) - } - } - - /// Return this URL’s fragment identifier, or an empty string. - /// A fragment is the part of the URL with the # symbol. - /// The fragment is optional and, if present, contains a fragment identifier that identifies - /// a secondary resource, such as a section heading of a document. - /// In HTML, the fragment identifier is usually the id attribute of a an element that is - /// scrolled to on load. Browsers typically will not send the fragment portion of a URL to the - /// server. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-hash) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("https://example.com/data.csv#row=4", None).expect("Invalid URL"); - /// assert_eq!(url.hash(), "#row=4"); - /// assert!(url.has_hash()); - /// ``` - #[must_use] - pub fn hash(&self) -> &str { - unsafe { ffi::ada_get_hash(self.0) }.as_str() - } - - /// Updates the `hash` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_hash(Some("this-is-my-hash")); - /// assert_eq!(url.href(), "https://yagiz.co/#this-is-my-hash"); - /// ``` - pub fn set_hash(&mut self, input: Option<&str>) { - match input { - Some(value) => unsafe { ffi::ada_set_hash(self.0, value.as_ptr().cast(), value.len()) }, - None => unsafe { ffi::ada_clear_hash(self.0) }, - } - } - - /// Return the parsed representation of the host for this URL with an optional port number. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-host) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("https://127.0.0.1:8080/index.html", None).expect("Invalid URL"); - /// assert_eq!(url.host(), "127.0.0.1:8080"); - /// ``` - #[must_use] - pub fn host(&self) -> &str { - unsafe { ffi::ada_get_host(self.0) }.as_str() - } - - /// Updates the `host` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_host(Some("localhost:3000")).unwrap(); - /// assert_eq!(url.href(), "https://localhost:3000/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_host(&mut self, input: Option<&str>) -> SetterResult { - setter_result(unsafe { - ffi::ada_set_host( - self.0, - input.unwrap_or("").as_ptr().cast(), - input.map_or(0, str::len), - ) - }) - } - - /// Return the parsed representation of the host for this URL. Non-ASCII domain labels are - /// punycode-encoded per IDNA if this is the host of a special URL, or percent encoded for - /// non-special URLs. - /// - /// Hostname does not contain port number. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-hostname) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("https://127.0.0.1:8080/index.html", None).expect("Invalid URL"); - /// assert_eq!(url.hostname(), "127.0.0.1"); - /// ``` - #[must_use] - pub fn hostname(&self) -> &str { - unsafe { ffi::ada_get_hostname(self.0) }.as_str() - } - - /// Updates the `hostname` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_hostname(Some("localhost")).unwrap(); - /// assert_eq!(url.href(), "https://localhost/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_hostname(&mut self, input: Option<&str>) -> SetterResult { - setter_result(unsafe { - ffi::ada_set_hostname( - self.0, - input.unwrap_or("").as_ptr().cast(), - input.map_or(0, str::len), - ) - }) - } - - /// Return the path for this URL, as a percent-encoded ASCII string. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-pathname) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("https://example.com/api/versions?page=2", None).expect("Invalid URL"); - /// assert_eq!(url.pathname(), "/api/versions"); - /// ``` - #[must_use] - pub fn pathname(&self) -> &str { - unsafe { ffi::ada_get_pathname(self.0) }.as_str() - } - - /// Updates the `pathname` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_pathname(Some("/contact")).unwrap(); - /// assert_eq!(url.href(), "https://yagiz.co/contact"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_pathname(&mut self, input: Option<&str>) -> SetterResult { - setter_result(unsafe { - ffi::ada_set_pathname( - self.0, - input.unwrap_or("").as_ptr().cast(), - input.map_or(0, str::len), - ) - }) - } - - /// Return this URL’s query string, if any, as a percent-encoded ASCII string. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-search) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("https://example.com/products?page=2", None).expect("Invalid URL"); - /// assert_eq!(url.search(), "?page=2"); - /// - /// let url = Url::parse("https://example.com/products", None).expect("Invalid URL"); - /// assert_eq!(url.search(), ""); - /// ``` - #[must_use] - pub fn search(&self) -> &str { - unsafe { ffi::ada_get_search(self.0) }.as_str() - } - - /// Updates the `search` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("https://yagiz.co", None).expect("Invalid URL"); - /// url.set_search(Some("?page=1")); - /// assert_eq!(url.href(), "https://yagiz.co/?page=1"); - /// ``` - pub fn set_search(&mut self, input: Option<&str>) { - match input { - Some(value) => unsafe { - ffi::ada_set_search(self.0, value.as_ptr().cast(), value.len()); - }, - None => unsafe { ffi::ada_clear_search(self.0) }, - } - } - - /// Return the scheme of this URL, lower-cased, as an ASCII string with the ‘:’ delimiter. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-protocol) - /// - /// ``` - /// use ada_url::Url; - /// - /// let url = Url::parse("file:///tmp/foo", None).expect("Invalid URL"); - /// assert_eq!(url.protocol(), "file:"); - /// ``` - #[must_use] - pub fn protocol(&self) -> &str { - unsafe { ffi::ada_get_protocol(self.0) }.as_str() - } - - /// Updates the `protocol` of the URL. - /// - /// ``` - /// use ada_url::Url; - /// - /// let mut url = Url::parse("http://yagiz.co", None).expect("Invalid URL"); - /// url.set_protocol("http").unwrap(); - /// assert_eq!(url.href(), "http://yagiz.co/"); - /// ``` - #[allow(clippy::result_unit_err)] - pub fn set_protocol(&mut self, input: &str) -> SetterResult { - setter_result(unsafe { ffi::ada_set_protocol(self.0, input.as_ptr().cast(), input.len()) }) - } - - /// A URL includes credentials if its username or password is not the empty string. - #[must_use] - pub fn has_credentials(&self) -> bool { - unsafe { ffi::ada_has_credentials(self.0) } - } - - /// Returns true if it has an host but it is the empty string. - #[must_use] - pub fn has_empty_hostname(&self) -> bool { - unsafe { ffi::ada_has_empty_hostname(self.0) } - } - - /// Returns true if it has a host (included an empty host) - #[must_use] - pub fn has_hostname(&self) -> bool { - unsafe { ffi::ada_has_hostname(self.0) } - } - - /// Returns true if URL has a non-empty username. - #[must_use] - pub fn has_non_empty_username(&self) -> bool { - unsafe { ffi::ada_has_non_empty_username(self.0) } - } - - /// Returns true if URL has a non-empty password. - #[must_use] - pub fn has_non_empty_password(&self) -> bool { - unsafe { ffi::ada_has_non_empty_password(self.0) } - } - - /// Returns true if URL has a port. - #[must_use] - pub fn has_port(&self) -> bool { - unsafe { ffi::ada_has_port(self.0) } - } - - /// Returns true if URL has password. - #[must_use] - pub fn has_password(&self) -> bool { - unsafe { ffi::ada_has_password(self.0) } - } - - /// Returns true if URL has a hash/fragment. - #[must_use] - pub fn has_hash(&self) -> bool { - unsafe { ffi::ada_has_hash(self.0) } - } - - /// Returns true if URL has search/query. - #[must_use] - pub fn has_search(&self) -> bool { - unsafe { ffi::ada_has_search(self.0) } - } - - /// Returns the parsed version of the URL with all components. - /// - /// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-href) - #[must_use] - pub fn as_str(&self) -> &str { - self.href() - } - - /// Returns the URL components of the instance. - #[must_use] - pub fn components(&self) -> UrlComponents { - unsafe { ffi::ada_get_components(self.0).as_ref().unwrap() }.into() - } -} - -/// Serializes this URL into a `serde` stream. -/// -/// This implementation is only available if the `serde` Cargo feature is enabled. -#[cfg(feature = "serde")] -impl serde::Serialize for Url { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.as_str()) - } -} - -/// Deserializes this URL from a `serde` stream. -/// -/// This implementation is only available if the `serde` Cargo feature is enabled. -#[cfg(feature = "serde")] -#[cfg(feature = "std")] -impl<'de> serde::Deserialize<'de> for Url { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::{Error, Unexpected, Visitor}; - - struct UrlVisitor; - - impl Visitor<'_> for UrlVisitor { - type Value = Url; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string representing an URL") - } - - fn visit_str(self, s: &str) -> Result - where - E: Error, - { - Url::parse(s, None).map_err(|err| { - let err_s = std::format!("{}", err); - Error::invalid_value(Unexpected::Str(s), &err_s.as_str()) - }) - } - } - - deserializer.deserialize_str(UrlVisitor) - } -} - -/// Send is required for sharing Url between threads safely -unsafe impl Send for Url {} - -/// Sync is required for sharing Url between threads safely -unsafe impl Sync for Url {} - -/// URLs compare like their stringification. -impl PartialEq for Url { - fn eq(&self, other: &Self) -> bool { - self.href() == other.href() - } -} - -impl PartialOrd for Url { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Url { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.href().cmp(other.href()) - } -} - -impl hash::Hash for Url { - fn hash(&self, state: &mut H) { - self.href().hash(state); - } -} - -impl borrow::Borrow for Url { - fn borrow(&self) -> &str { - self.href() - } -} - -impl AsRef<[u8]> for Url { - fn as_ref(&self) -> &[u8] { - self.href().as_bytes() - } -} - -#[cfg(feature = "std")] -impl From for String { - fn from(val: Url) -> Self { - val.href().to_owned() - } -} - -impl fmt::Debug for Url { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Url") - .field("href", &self.href()) - .field("components", &self.components()) - .finish() - } -} - -impl<'input> TryFrom<&'input str> for Url { - type Error = ParseUrlError<&'input str>; - - fn try_from(value: &'input str) -> Result { - Self::parse(value, None) - } -} - -#[cfg(feature = "std")] -impl TryFrom for Url { - type Error = ParseUrlError; - - fn try_from(value: String) -> Result { - Self::parse(value, None) - } -} - -#[cfg(feature = "std")] -impl<'input> TryFrom<&'input String> for Url { - type Error = ParseUrlError<&'input String>; - - fn try_from(value: &'input String) -> Result { - Self::parse(value, None) - } -} - -impl ops::Deref for Url { - type Target = str; - fn deref(&self) -> &Self::Target { - self.href() - } -} - -impl AsRef for Url { - fn as_ref(&self) -> &str { - self.href() - } -} - -impl fmt::Display for Url { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.href()) +impl fmt::Display for ParseUrlError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "Invalid url: {:?}", self.input) } } #[cfg(feature = "std")] -impl core::str::FromStr for Url { - type Err = ParseUrlError>; - - fn from_str(s: &str) -> Result { - Self::parse(s, None).map_err(|ParseUrlError { input }| ParseUrlError { - input: input.into(), - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn should_display_serialization() { - let tests = [ - ("http://example.com/", "http://example.com/"), - ("HTTP://EXAMPLE.COM", "http://example.com/"), - ("http://user:pwd@domain.com", "http://user:pwd@domain.com/"), - ( - "HTTP://EXAMPLE.COM/FOO/BAR?K1=V1&K2=V2", - "http://example.com/FOO/BAR?K1=V1&K2=V2", - ), - ( - "http://example.com/🦀/❤️/", - "http://example.com/%F0%9F%A6%80/%E2%9D%A4%EF%B8%8F/", - ), - ( - "https://example.org/hello world.html", - "https://example.org/hello%20world.html", - ), - ( - "https://三十六計.org/走為上策/", - "https://xn--ehq95fdxbx86i.org/%E8%B5%B0%E7%82%BA%E4%B8%8A%E7%AD%96/", - ), - ]; - for (value, expected) in tests { - let url = Url::parse(value, None).expect("Should have parsed url"); - assert_eq!(url.as_str(), expected); - } - } - - #[test] - fn try_from_ok() { - let url = Url::try_from("http://example.com/foo/bar?k1=v1&k2=v2"); - #[cfg(feature = "std")] - std::dbg!(&url); - let url = url.unwrap(); - assert_eq!(url.href(), "http://example.com/foo/bar?k1=v1&k2=v2"); - assert_eq!( - url, - Url::parse("http://example.com/foo/bar?k1=v1&k2=v2", None).unwrap(), - ); - } - - #[test] - fn try_from_err() { - let url = Url::try_from("this is not a url"); - #[cfg(feature = "std")] - std::dbg!(&url); - let error = url.unwrap_err(); - #[cfg(feature = "std")] - assert_eq!(error.to_string(), r#"Invalid url: "this is not a url""#); - assert_eq!(error.input, "this is not a url"); - } - - #[test] - fn should_compare_urls() { - let tests = [ - ("http://example.com/", "http://example.com/", true), - ("http://example.com/", "https://example.com/", false), - ("http://example.com#", "https://example.com/#", false), - ("http://example.com", "https://example.com#", false), - ( - "https://user:pwd@example.com", - "https://user:pwd@example.com", - true, - ), - ]; - for (left, right, expected) in tests { - let left_url = Url::parse(left, None).expect("Should have parsed url"); - let right_url = Url::parse(right, None).expect("Should have parsed url"); - assert_eq!( - left_url == right_url, - expected, - "left: {left}, right: {right}, expected: {expected}", - ); - } - } - #[test] - fn should_order_alphabetically() { - let left = Url::parse("https://example.com/", None).expect("Should have parsed url"); - let right = Url::parse("https://zoo.tld/", None).expect("Should have parsed url"); - assert!(left < right); - let left = Url::parse("https://c.tld/", None).expect("Should have parsed url"); - let right = Url::parse("https://a.tld/", None).expect("Should have parsed url"); - assert!(right < left); - } - - #[test] - fn should_parse_simple_url() { - let mut out = Url::parse( - "https://username:password@google.com:9090/search?query#hash", - None, - ) - .expect("Should have parsed a simple url"); - - #[cfg(feature = "std")] - assert_eq!(out.origin(), "https://google.com:9090"); - - assert_eq!( - out.href(), - "https://username:password@google.com:9090/search?query#hash" - ); - - assert_eq!(out.scheme_type(), SchemeType::Https); - - out.set_username(Some("new-username")).unwrap(); - assert_eq!(out.username(), "new-username"); - - out.set_password(Some("new-password")).unwrap(); - assert_eq!(out.password(), "new-password"); - - out.set_port(Some("4242")).unwrap(); - assert_eq!(out.port(), "4242"); - out.set_port(None).unwrap(); - assert_eq!(out.port(), ""); - - out.set_hash(Some("#new-hash")); - assert_eq!(out.hash(), "#new-hash"); - - out.set_host(Some("yagiz.co:9999")).unwrap(); - assert_eq!(out.host(), "yagiz.co:9999"); - - out.set_hostname(Some("domain.com")).unwrap(); - assert_eq!(out.hostname(), "domain.com"); - - out.set_pathname(Some("/new-search")).unwrap(); - assert_eq!(out.pathname(), "/new-search"); - out.set_pathname(None).unwrap(); - assert_eq!(out.pathname(), "/"); - - out.set_search(Some("updated-query")); - assert_eq!(out.search(), "?updated-query"); - - out.set_protocol("wss").unwrap(); - assert_eq!(out.protocol(), "wss:"); - assert_eq!(out.scheme_type(), SchemeType::Wss); - - assert!(out.has_credentials()); - assert!(out.has_non_empty_username()); - assert!(out.has_non_empty_password()); - assert!(out.has_search()); - assert!(out.has_hash()); - assert!(out.has_password()); - - assert_eq!(out.host_type(), HostType::Domain); - } - - #[test] - fn scheme_types() { - assert_eq!( - Url::parse("file:///foo/bar", None) - .expect("bad url") - .scheme_type(), - SchemeType::File - ); - assert_eq!( - Url::parse("ws://example.com/ws", None) - .expect("bad url") - .scheme_type(), - SchemeType::Ws - ); - assert_eq!( - Url::parse("wss://example.com/wss", None) - .expect("bad url") - .scheme_type(), - SchemeType::Wss - ); - assert_eq!( - Url::parse("ftp://example.com/file.txt", None) - .expect("bad url") - .scheme_type(), - SchemeType::Ftp - ); - assert_eq!( - Url::parse("http://example.com/file.txt", None) - .expect("bad url") - .scheme_type(), - SchemeType::Http - ); - assert_eq!( - Url::parse("https://example.com/file.txt", None) - .expect("bad url") - .scheme_type(), - SchemeType::Https - ); - assert_eq!( - Url::parse("foo://example.com", None) - .expect("bad url") - .scheme_type(), - SchemeType::NotSpecial - ); - } - - #[test] - fn can_parse_simple_url() { - assert!(Url::can_parse("https://google.com", None)); - assert!(Url::can_parse("/helo", Some("https://www.google.com"))); - } - - #[cfg(feature = "std")] - #[cfg(feature = "serde")] - #[test] - fn test_serde_serialize_deserialize() { - let input = "https://www.google.com"; - let output = "\"https://www.google.com/\""; - let url = Url::parse(&input, None).unwrap(); - assert_eq!(serde_json::to_string(&url).unwrap(), output); - - let deserialized: Url = serde_json::from_str(&output).unwrap(); - assert_eq!(deserialized.href(), "https://www.google.com/"); - } - - #[test] - fn should_clone() { - let first = Url::parse("https://lemire.me", None).unwrap(); - let mut second = first.clone(); - second.set_href("https://yagiz.co").unwrap(); - assert_ne!(first.href(), second.href()); - assert_eq!(first.href(), "https://lemire.me/"); - assert_eq!(second.href(), "https://yagiz.co/"); - } - - #[test] - fn should_handle_empty_host() { - // Ref: https://github.com/ada-url/rust/issues/74 - let url = Url::parse("file:///C:/Users/User/Documents/example.pdf", None).unwrap(); - assert_eq!(url.host(), ""); - assert_eq!(url.hostname(), ""); - } -} +impl std::error::Error for ParseUrlError {} diff --git a/src/search_params.rs b/src/search_params.rs new file mode 100644 index 0000000..aa3a678 --- /dev/null +++ b/src/search_params.rs @@ -0,0 +1,627 @@ +//! WHATWG URLSearchParams. + +use alloc::{boxed::Box, string::String, vec::Vec}; +use core::{ + fmt, + hash::{Hash, Hasher}, +}; + +use crate::{ + ParseUrlError, + bytes::{find_byte, find_byte2}, + encoding::append_form_component, +}; + +const INLINE_CAPACITY: usize = 22; +const INVALID_HEX: u8 = u8::MAX; +const HEX_TABLE: [u8; 256] = make_hex_table(); + +const fn make_hex_table() -> [u8; 256] { + let mut table = [INVALID_HEX; 256]; + let mut digit = 0_u8; + while digit < 10 { + table[(b'0' + digit) as usize] = digit; + digit += 1; + } + let mut letter = 0_u8; + while letter < 6 { + table[(b'a' + letter) as usize] = letter + 10; + table[(b'A' + letter) as usize] = letter + 10; + letter += 1; + } + table +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +enum ParamString { + Inline { + length: u8, + bytes: [u8; INLINE_CAPACITY], + }, + Heap(String), +} + +impl ParamString { + #[inline] + fn new(input: &str) -> Self { + if input.len() <= INLINE_CAPACITY { + let mut bytes = [0_u8; INLINE_CAPACITY]; + bytes[..input.len()].copy_from_slice(input.as_bytes()); + Self::Inline { + length: input.len() as u8, + bytes, + } + } else { + Self::Heap(String::from(input)) + } + } + + #[inline] + fn as_str(&self) -> &str { + match self { + Self::Inline { length, bytes } => core::str::from_utf8(&bytes[..usize::from(*length)]) + .expect("inline query component is valid UTF-8"), + Self::Heap(value) => value, + } + } + + fn into_string(self) -> String { + match self { + Self::Inline { length, bytes } => String::from( + core::str::from_utf8(&bytes[..usize::from(length)]) + .expect("inline query component is valid UTF-8"), + ), + Self::Heap(value) => value, + } + } +} + +impl From for ParamString { + #[inline] + fn from(value: String) -> Self { + if value.len() <= INLINE_CAPACITY { + Self::new(&value) + } else { + Self::Heap(value) + } + } +} + +type ParamPair = (ParamString, ParamString); + +/// An ordered list of URL query name/value pairs. +#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +pub struct UrlSearchParams { + pairs: Vec, +} + +impl UrlSearchParams { + /// Parses an `application/x-www-form-urlencoded` query. + pub fn parse(input: Input) -> Result> + where + Input: AsRef, + { + Ok(Self::new(input.as_ref())) + } + + /// Parses an `application/x-www-form-urlencoded` query. + #[must_use] + pub fn new(input: &str) -> Self { + let input = input.strip_prefix('?').unwrap_or(input); + let bytes = input.as_bytes(); + let capacity = + usize::from(!bytes.is_empty()) + bytes.iter().filter(|byte| **byte == b'&').count(); + let mut pairs = Vec::with_capacity(capacity); + for sequence in bytes.split(|byte| *byte == b'&') { + if sequence.is_empty() { + continue; + } + let equals = find_byte(b'=', sequence); + let (name, value) = equals.map_or((sequence, &[][..]), |index| { + (&sequence[..index], &sequence[index + 1..]) + }); + pairs.push((decode_form_component(name), decode_form_component(value))); + } + Self { pairs } + } + + /// Returns the number of pairs. + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.pairs.len() + } + + /// Returns whether there are no pairs. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.pairs.is_empty() + } + + /// Appends a pair. + pub fn append(&mut self, name: impl Into, value: impl Into) { + self.pairs.push(( + ParamString::from(name.into()), + ParamString::from(value.into()), + )); + } + + /// Removes every pair with `name`. + pub fn delete(&mut self, name: &str) { + self.pairs.retain(|(key, _)| key.as_str() != name); + } + + /// Removes every pair matching both `name` and `value`. + pub fn delete_value(&mut self, name: &str, value: &str) { + self.pairs + .retain(|(key, candidate)| key.as_str() != name || candidate.as_str() != value); + } + + /// Removes every pair with `name`. + pub fn remove_key(&mut self, name: &str) { + self.delete(name); + } + + /// Removes every pair matching both `name` and `value`. + pub fn remove(&mut self, name: &str, value: &str) { + self.delete_value(name, value); + } + + /// Returns the first value associated with `name`. + #[must_use] + pub fn get(&self, name: &str) -> Option<&str> { + self.pairs + .iter() + .find_map(|(key, value)| (key.as_str() == name).then_some(value.as_str())) + } + + /// Returns all values associated with `name`. + pub fn get_all<'a>(&'a self, name: &'a str) -> UrlSearchParamsEntry<'a> { + let values = self + .pairs + .iter() + .filter_map(move |(key, value)| (key.as_str() == name).then_some(value.as_str())) + .collect(); + UrlSearchParamsEntry { + values, + position: 0, + } + } + + /// Returns whether at least one pair has `name`. + #[must_use] + pub fn has(&self, name: &str) -> bool { + self.pairs.iter().any(|(key, _)| key.as_str() == name) + } + + /// Returns whether a pair matches both `name` and `value`. + #[must_use] + pub fn has_value(&self, name: &str, value: &str) -> bool { + self.pairs + .iter() + .any(|(key, candidate)| key.as_str() == name && candidate.as_str() == value) + } + + /// Returns whether at least one pair has `name`. + #[must_use] + pub fn contains_key(&self, name: &str) -> bool { + self.has(name) + } + + /// Returns whether a pair matches both `name` and `value`. + #[must_use] + pub fn contains(&self, name: &str, value: &str) -> bool { + self.has_value(name, value) + } + + /// Replaces the first value for `name` and removes later duplicates. + pub fn set(&mut self, name: impl Into, value: impl Into) { + let name = ParamString::from(name.into()); + let value = ParamString::from(value.into()); + let mut found = false; + self.pairs.retain_mut(|(key, candidate)| { + if key.as_str() != name.as_str() { + return true; + } + if found { + return false; + } + *candidate = value.clone(); + found = true; + true + }); + if !found { + self.pairs.push((name, value)); + } + } + + /// Sorts pairs stably by name using UTF-16 code units. + pub fn sort(&mut self) { + self.pairs.sort_by(|(left, _), (right, _)| { + left.as_str() + .encode_utf16() + .cmp(right.as_str().encode_utf16()) + }); + } + + /// Iterates over pairs in insertion order. + pub fn iter(&self) -> impl ExactSizeIterator { + self.pairs + .iter() + .map(|(name, value)| (name.as_str(), value.as_str())) + } + + /// Iterates over names in insertion order. + pub fn keys(&self) -> UrlSearchParamsKeyIterator<'_> { + UrlSearchParamsKeyIterator { + inner: self.pairs.iter(), + } + } + + /// Iterates over values in insertion order. + pub fn values(&self) -> UrlSearchParamsValueIterator<'_> { + UrlSearchParamsValueIterator { + inner: self.pairs.iter(), + } + } + + /// Iterates over pairs in insertion order. + pub fn entries(&self) -> UrlSearchParamsEntryIterator<'_> { + UrlSearchParamsEntryIterator { + inner: self.pairs.iter(), + } + } + + /// Returns the first pair. + #[must_use] + pub fn front(&self) -> Option<(&str, &str)> { + self.pairs + .first() + .map(|(name, value)| (name.as_str(), value.as_str())) + } + + /// Returns the last pair. + #[must_use] + pub fn back(&self) -> Option<(&str, &str)> { + self.pairs + .last() + .map(|(name, value)| (name.as_str(), value.as_str())) + } + + /// Returns a pair by insertion-order index. + #[must_use] + pub fn get_index(&self, index: usize) -> Option<(&str, &str)> { + self.pairs + .get(index) + .map(|(name, value)| (name.as_str(), value.as_str())) + } + + /// Replaces all pairs by parsing a new query. + pub fn reset(&mut self, input: &str) { + *self = Self::new(input); + } +} + +#[inline] +fn decode_form_component(input: &[u8]) -> ParamString { + let first_escape = find_byte2(b'%', b'+', input); + let Some(first_escape) = first_escape else { + // `input` is a subslice of a valid UTF-8 string split only at ASCII + // delimiters, so it is itself valid UTF-8. + return ParamString::new(core::str::from_utf8(input).expect("valid UTF-8 query component")); + }; + let mut decoded = Vec::with_capacity(input.len()); + decoded.extend_from_slice(&input[..first_escape]); + let mut index = first_escape; + while index < input.len() { + match input[index] { + b'+' => { + decoded.push(b' '); + index += 1; + } + b'%' => { + while index + 2 < input.len() && input[index] == b'%' { + let high = HEX_TABLE[input[index + 1] as usize]; + let low = HEX_TABLE[input[index + 2] as usize]; + if (high | low) >= 16 { + break; + } + decoded.push((high << 4) | low); + index += 3; + } + if index < input.len() && input[index] == b'%' { + decoded.push(b'%'); + index += 1; + } + } + _ => { + let run_length = + find_byte2(b'%', b'+', &input[index..]).unwrap_or(input.len() - index); + decoded.extend_from_slice(&input[index..index + run_length]); + index += run_length; + } + } + } + + ParamString::from( + String::from_utf8(decoded) + .unwrap_or_else(|error| String::from_utf8_lossy(error.as_bytes()).into_owned()), + ) +} + +impl fmt::Display for UrlSearchParams { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let estimated = self + .pairs + .iter() + .map(|(name, value)| name.as_str().len() + value.as_str().len() + 2) + .sum(); + let mut output = String::with_capacity(estimated); + for (index, (name, value)) in self.iter().enumerate() { + if index != 0 { + output.push('&'); + } + append_form_component(&mut output, name); + output.push('='); + append_form_component(&mut output, value); + } + formatter.write_str(&output) + } +} + +impl core::str::FromStr for UrlSearchParams { + type Err = ParseUrlError>; + + fn from_str(input: &str) -> Result { + Self::parse(input).map_err(|ParseUrlError { input }| ParseUrlError { + input: input.into(), + }) + } +} + +/// Iterator over URL search-parameter keys. +pub struct UrlSearchParamsKeyIterator<'a> { + inner: core::slice::Iter<'a, ParamPair>, +} + +impl<'a> Iterator for UrlSearchParamsKeyIterator<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + self.inner.next().map(|(name, _)| name.as_str()) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for UrlSearchParamsKeyIterator<'_> {} + +impl Hash for UrlSearchParamsKeyIterator<'_> { + fn hash(&self, state: &mut H) { + for (name, _) in self.inner.as_slice() { + name.hash(state); + } + } +} + +impl Drop for UrlSearchParamsKeyIterator<'_> { + fn drop(&mut self) {} +} + +/// Iterator over URL search-parameter values. +pub struct UrlSearchParamsValueIterator<'a> { + inner: core::slice::Iter<'a, ParamPair>, +} + +impl<'a> Iterator for UrlSearchParamsValueIterator<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + self.inner.next().map(|(_, value)| value.as_str()) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for UrlSearchParamsValueIterator<'_> {} + +impl Hash for UrlSearchParamsValueIterator<'_> { + fn hash(&self, state: &mut H) { + for (_, value) in self.inner.as_slice() { + value.hash(state); + } + } +} + +impl Drop for UrlSearchParamsValueIterator<'_> { + fn drop(&mut self) {} +} + +/// Iterator over URL search-parameter pairs. +pub struct UrlSearchParamsEntryIterator<'a> { + inner: core::slice::Iter<'a, ParamPair>, +} + +impl<'a> Iterator for UrlSearchParamsEntryIterator<'a> { + type Item = (&'a str, &'a str); + + fn next(&mut self) -> Option { + self.inner + .next() + .map(|(name, value)| (name.as_str(), value.as_str())) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for UrlSearchParamsEntryIterator<'_> {} + +impl Hash for UrlSearchParamsEntryIterator<'_> { + fn hash(&self, state: &mut H) { + self.inner.as_slice().hash(state); + } +} + +impl Drop for UrlSearchParamsEntryIterator<'_> { + fn drop(&mut self) {} +} + +/// Values associated with one URL search-parameter key. +pub struct UrlSearchParamsEntry<'a> { + values: Vec<&'a str>, + position: usize, +} + +impl UrlSearchParamsEntry<'_> { + /// Returns whether no values matched. + #[must_use] + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + /// Returns the number of matched values. + #[must_use] + pub fn len(&self) -> usize { + self.values.len() + } + + /// Returns a matched value by index. + #[must_use] + pub fn get(&self, index: usize) -> Option<&str> { + self.values.get(index).copied() + } +} + +impl<'a> Iterator for UrlSearchParamsEntry<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + let value = self.values.get(self.position).copied(); + self.position += usize::from(value.is_some()); + value + } +} + +impl<'a> From> for Vec<&'a str> { + fn from(entry: UrlSearchParamsEntry<'a>) -> Self { + entry.values + } +} + +impl<'a> IntoIterator for &'a UrlSearchParams { + type Item = (&'a str, &'a str); + type IntoIter = UrlSearchParamsEntryIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.entries() + } +} + +impl IntoIterator for UrlSearchParams { + type Item = (String, String); + type IntoIter = alloc::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.pairs + .into_iter() + .map(|(name, value)| (name.into_string(), value.into_string())) + .collect::>() + .into_iter() + } +} + +impl FromIterator<(K, V)> for UrlSearchParams +where + K: Into, + V: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + pairs: iter + .into_iter() + .map(|(name, value)| { + ( + ParamString::from(name.into()), + ParamString::from(value.into()), + ) + }) + .collect(), + } + } +} + +impl Extend<(K, V)> for UrlSearchParams +where + K: Into, + V: Into, +{ + fn extend>(&mut self, iter: T) { + self.pairs.extend(iter.into_iter().map(|(name, value)| { + ( + ParamString::from(name.into()), + ParamString::from(value.into()), + ) + })); + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for UrlSearchParams { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for UrlSearchParams { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let input = ::deserialize(deserializer)?; + Ok(Self::new(&input)) + } +} + +#[cfg(test)] +mod tests { + use alloc::{string::ToString, vec::Vec}; + + use super::UrlSearchParams; + + #[test] + fn parses_and_serializes() { + let mut params = UrlSearchParams::new("?a=b+c&a=d&empty"); + assert_eq!(params.get("a"), Some("b c")); + assert_eq!(params.get_all("a").collect::>(), ["b c", "d"]); + params.set("a", "x"); + params.append("snow", "☃"); + assert_eq!(params.to_string(), "a=x&empty=&snow=%E2%98%83"); + } + + #[test] + fn preserves_incomplete_percent_escapes() { + let params = UrlSearchParams::new("trailing=%&short=%A&invalid=%GG"); + assert_eq!( + params.entries().collect::>(), + [("trailing", "%"), ("short", "%A"), ("invalid", "%GG")] + ); + } + + #[test] + fn sort_is_stable() { + let mut params = UrlSearchParams::new("z=1&a=first&a=second"); + params.sort(); + assert_eq!(params.to_string(), "a=first&a=second&z=1"); + } +} diff --git a/src/url.rs b/src/url.rs new file mode 100644 index 0000000..99dfe36 --- /dev/null +++ b/src/url.rs @@ -0,0 +1,2427 @@ +//! Single-buffer URL representation and public operations. + +use alloc::{ + borrow::Cow, + boxed::Box, + format, + string::{String, ToString}, + vec::Vec, +}; +use core::{ + borrow::Borrow, + fmt, + hash::{Hash, Hasher}, + ops::Deref, + str::FromStr, + sync::atomic::{AtomicU32, Ordering}, +}; +#[cfg(feature = "std")] +use std::path::Path; + +#[cfg(feature = "std")] +use crate::encoding::append_file_path_segment; +use crate::{ + ParseUrlError, + bytes::{find_byte as memchr, find_byte2 as memchr2}, + components::{Components, HostType, SchemeType, UrlComponents}, + encoding::{PercentEncodeSet, append_percent_encoded, utf8_percent_encode}, + error::{ParseError, ParseErrorKind}, + fast_path, + search_params::UrlSearchParams, +}; + +const FLAG_OPAQUE_PATH: u8 = 1 << 0; +const FLAG_AUTHORITY: u8 = 1 << 1; + +const PATH_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::Path; +const SPECIAL_QUERY_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::SpecialQuery; +const QUERY_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::Query; +const FRAGMENT_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::Fragment; +const USERINFO_ENCODE_SET: PercentEncodeSet = PercentEncodeSet::UserInfo; + +static MAX_INPUT_LENGTH: AtomicU32 = AtomicU32::new(u32::MAX); + +/// A parsed WHATWG URL backed by one normalized serialization. +#[derive(Clone)] +pub struct Url { + components: Components, + buffer: String, + scheme_type: SchemeType, + host_type: HostType, + flags: u8, +} + +impl Url { + /// Parses `input`, optionally resolving it against a serialized base URL. + pub fn parse(input: Input, base: Option<&str>) -> Result> + where + Input: AsRef, + { + let parsed = Self::parse_with_base(input.as_ref(), base); + parsed.map_err(|_| ParseUrlError { input }) + } + + /// Parses `input`, optionally resolving it against an already parsed base. + pub fn parse_with_url_base(input: &str, base: Option<&Self>) -> Result { + check_raw_length(input)?; + if base.is_none() + && input.len() <= 32 + && let Some(parsed) = fast_path::parse_bare_special(input) + { + return Self::from_fast_path(parsed); + } + if base.is_none() + && let Some(parsed) = fast_path::parse_normalized_file(input) + { + return Self::from_fast_path(parsed); + } + if let Some(parsed) = fast_path::parse(input) { + return Self::from_fast_path(parsed); + } + // Already-normalized non-special authority URLs do not need the + // cleanup and malformed-special probes below. Keep invalid/unsupported + // inputs on the general path because cleanup can still make them valid. + let has_outer_c0 = input.as_bytes().first().is_some_and(|byte| *byte <= b' ') + || input.as_bytes().last().is_some_and(|byte| *byte <= b' '); + if !has_outer_c0 + && let fast_path::CommonParse::Parsed(parsed) = fast_path::parse_common_absolute(input) + { + return Self::from_fast_path(parsed); + } + + let input = input.trim_matches(|character: char| character <= '\u{20}'); + let cleaned = remove_ascii_tab_or_newline(input); + let input = cleaned.as_ref(); + + if let Some(normalized) = normalize_scheme_case(input) { + return Self::parse_with_url_base(&normalized, base); + } + if base.is_none() + && input.len() <= 32 + && let Some(parsed) = fast_path::parse_bare_special(input) + { + return Self::from_fast_path(parsed); + } + if might_be_explicit_file(input) { + if base.is_none() + && let Some(parsed) = fast_path::parse_normalized_file(input) + { + return Self::from_fast_path(parsed); + } + if let Some(parsed) = parse_explicit_file(input, base) { + return parsed; + } + } + if let Some(parsed) = fast_path::parse(input) { + return Self::from_fast_path(parsed); + } + let opaque_candidate = memchr(b':', input.as_bytes()).is_some_and(|colon| { + input + .as_bytes() + .get(colon + 1) + .is_none_or(|byte| *byte != b'/') + }); + if opaque_candidate && let Some(parsed) = fast_path::parse_opaque_absolute(input) { + return Self::from_fast_path(parsed); + } + if let Some(normalized) = normalize_special_backslashes(input) { + return Self::parse_with_url_base(&normalized, base); + } + if let Some(base) = base + && base.scheme_type != SchemeType::File + && base.is_special() + && let Some(colon) = memchr(b':', input.as_bytes()) + && input[..colon].eq_ignore_ascii_case(base.scheme()) + { + let remainder = &input[colon + 1..]; + let leading = remainder + .bytes() + .take_while(|byte| matches!(byte, b'/' | b'\\')) + .count(); + if leading < 2 { + return Self::parse_with_url_base(remainder, Some(base)); + } + } + if let Some(normalized) = normalize_malformed_special_absolute(input) { + return Self::parse_with_url_base(&normalized, None); + } + match fast_path::parse_common_absolute(input) { + fast_path::CommonParse::Parsed(parsed) => return Self::from_fast_path(parsed), + fast_path::CommonParse::Invalid => { + return Err(ParseError::new(ParseErrorKind::InvalidUrl)); + } + fast_path::CommonParse::Unsupported => {} + } + if let Some(parsed) = fast_path::parse_non_special_hierarchical_absolute(input) { + return Self::from_fast_path(parsed); + } + if let Some(base) = base { + if let Some(parsed) = resolve_simple_reference(input, base) { + return parsed; + } + if base.has_opaque_path() { + return Err(ParseError::new(ParseErrorKind::InvalidUrl)); + } + if let Some(parsed) = resolve_authority_reference(input, base) { + return parsed; + } + if let Some(parsed) = resolve_normalized_file_relative(input, base) { + return parsed; + } + if let Some(parsed) = resolve_file_reference(input, base) { + return parsed; + } + if let Some(parsed) = resolve_custom_file_base(input, base) { + return parsed; + } + if let Some(parsed) = resolve_common_path_reference(input, base) { + return parsed; + } + } + Err(ParseError::new(ParseErrorKind::InvalidUrl)) + } + + #[inline] + fn from_fast_path(parsed: fast_path::FastPath) -> Result { + check_normalized_length(parsed.buffer.len())?; + Ok(Self { + components: parsed.components, + buffer: parsed.buffer, + scheme_type: parsed.scheme_type, + host_type: parsed.host_type, + flags: (u8::from(parsed.has_authority) * FLAG_AUTHORITY) + | (u8::from(parsed.opaque_path) * FLAG_OPAQUE_PATH), + }) + } + + /// Parses `input` with an optional base URL string. + /// + /// Unlike [`Self::parse`], this entry point also validates and parses the + /// base string. It is convenient for Web-compatible constructor APIs. + pub fn parse_with_base(input: &str, base: Option<&str>) -> Result { + let Some(base) = base else { + return Self::parse_with_url_base(input, None); + }; + check_raw_length(input)?; + check_raw_length(base).map_err(|_| ParseError::new(ParseErrorKind::InvalidBase))?; + let parsed_base = Self::parse_with_url_base(base, None) + .map_err(|_| ParseError::new(ParseErrorKind::InvalidBase))?; + Self::parse_with_url_base(input, Some(&parsed_base)) + } + + /// Returns whether `input` is parseable, optionally against a string base. + #[must_use] + pub fn can_parse(input: &str, base: Option<&str>) -> bool { + let max_input_length = get_max_input_length(); + if input.len() > max_input_length as usize { + return false; + } + if max_input_length == u32::MAX + && base.is_none() + && let Some(valid) = fast_path::can_parse_special_absolute(input) + { + return valid; + } + if base.is_none() + && let Some(normalized_len) = fast_path::normalized_len(input) + { + return normalized_len <= max_input_length as usize; + } + + Self::parse_with_base(input, base).is_ok() + } + + fn from_file_parts(host: &str, host_type: HostType, suffix: &str) -> Result { + let suffix = if suffix.starts_with('/') { + String::from(suffix) + } else { + format!("/{suffix}") + }; + let mut buffer = String::with_capacity(7 + host.len() + suffix.len()); + buffer.push_str("file://"); + buffer.push_str(host); + let pathname_start = buffer.len(); + buffer.push_str(&suffix); + check_normalized_length(buffer.len())?; + + let search_start = memchr(b'?', suffix.as_bytes()).map(|offset| pathname_start + offset); + let hash_start = memchr(b'#', suffix.as_bytes()).map(|offset| pathname_start + offset); + let components = Components::new( + 5, + 7, + 7, + to_u32(7 + host.len())?, + None, + to_u32(pathname_start)?, + search_start.map(to_u32).transpose()?, + hash_start.map(to_u32).transpose()?, + ); + if !components.validate(buffer.len()) { + return Err(ParseError::new(ParseErrorKind::InvalidUrl)); + } + Ok(Self { + components, + buffer, + scheme_type: SchemeType::File, + host_type, + flags: FLAG_AUTHORITY, + }) + } + + /// Replaces the entire URL. Failure leaves `self` unchanged. + fn set_href_value(&mut self, input: &str) -> Result<(), ParseError> { + let replacement = Self::parse_with_url_base(input, None)?; + *self = replacement; + Ok(()) + } + + /// Changes the scheme. Failure leaves `self` unchanged. + fn set_protocol_value(&mut self, protocol: &str) -> Result<(), ParseError> { + let cleaned = remove_ascii_tab_or_newline(protocol); + let protocol = cleaned.as_ref(); + let scheme = protocol.split(':').next().unwrap_or(protocol); + if scheme.is_empty() + || !scheme.as_bytes()[0].is_ascii_alphabetic() + || !scheme + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')) + { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let new_scheme_type = SchemeType::from_scheme(scheme); + if self.scheme_type.is_special() != new_scheme_type.is_special() + || (new_scheme_type == SchemeType::File + && (self.has_credentials() || self.has_password() || !self.port().is_empty())) + || (self.scheme_type == SchemeType::File + && new_scheme_type != SchemeType::File + && self.hostname().is_empty()) + { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let mut candidate = String::with_capacity(self.buffer.len() + scheme.len()); + candidate.extend( + scheme + .chars() + .map(|character| character.to_ascii_lowercase()), + ); + candidate.push(':'); + candidate.push_str(&self.buffer[self.components.protocol_end as usize..]); + let replacement = Self::parse_with_url_base(&candidate, None) + .map_err(|_| ParseError::new(ParseErrorKind::InvalidSetter))?; + *self = replacement; + Ok(()) + } + + /// Changes the username. Failure leaves `self` unchanged. + fn set_username_value(&mut self, username: &str) -> Result<(), ParseError> { + if !self.has_authority() || self.scheme_type == SchemeType::File { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let password = String::from(self.password()); + self.replace_credentials(username, Some(&password)) + } + + /// Changes the password. Failure leaves `self` unchanged. + fn set_password_value(&mut self, password: &str) -> Result<(), ParseError> { + if !self.has_authority() || self.scheme_type == SchemeType::File { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let username = String::from(self.username()); + self.replace_credentials(&username, Some(password)) + } + + fn replace_credentials( + &mut self, + username: &str, + password: Option<&str>, + ) -> Result<(), ParseError> { + let authority_start = self.components.protocol_end as usize + 2; + let mut candidate = String::with_capacity(self.buffer.len() + username.len() + 8); + candidate.push_str(&self.buffer[..authority_start]); + append_percent_encoded(&mut candidate, username, USERINFO_ENCODE_SET); + let password = password.filter(|password| !password.is_empty()); + if let Some(password) = password { + candidate.push(':'); + append_percent_encoded(&mut candidate, password, USERINFO_ENCODE_SET); + } + if !username.is_empty() || password.is_some() { + candidate.push('@'); + } + candidate.push_str(&self.buffer[self.components.host_start as usize..]); + let replacement = Self::parse_with_url_base(&candidate, None) + .map_err(|_| ParseError::new(ParseErrorKind::InvalidSetter))?; + *self = replacement; + Ok(()) + } + + /// Changes the host and optional port. Failure leaves `self` unchanged. + fn set_host_value(&mut self, host: &str) -> Result<(), ParseError> { + let cleaned = remove_ascii_tab_or_newline(host); + let host = setter_host_prefix(cleaned.as_ref(), self.is_special()); + let (hostname, port) = split_host_port(host); + if hostname.is_empty() + && (self.has_credentials() || self.has_password() || !self.port().is_empty()) + { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + if hostname.eq_ignore_ascii_case("xn--") && port.is_none() { + return self.replace_hostname_serialized("xn--"); + } + self.replace_host_from_setter(hostname, port, false) + } + + /// Changes only the hostname, preserving the port. + fn set_hostname_value(&mut self, hostname: &str) -> Result<(), ParseError> { + let cleaned = remove_ascii_tab_or_newline(hostname); + let hostname = setter_host_prefix(cleaned.as_ref(), self.is_special()); + let (hostname, port) = split_host_port(hostname); + if port.is_some() { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + if hostname.is_empty() + && (self.has_credentials() || self.has_password() || !self.port().is_empty()) + { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + if hostname.eq_ignore_ascii_case("xn--") { + return self.replace_hostname_serialized("xn--"); + } + self.replace_host_from_setter(hostname, None, true) + } + + fn replace_host_from_setter( + &mut self, + hostname: &str, + supplied_port: Option<&str>, + hostname_only: bool, + ) -> Result<(), ParseError> { + if self.has_opaque_path() { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + if hostname.contains('@') { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let port = if hostname_only || supplied_port.is_none_or(str::is_empty) { + self.components.port() + } else { + let supplied_port = supplied_port.unwrap_or_default(); + let digits = supplied_port + .as_bytes() + .iter() + .take_while(|byte| byte.is_ascii_digit()) + .count(); + if digits == 0 { + self.components.port() + } else { + supplied_port[..digits].parse::().ok() + } + }; + let port = port.filter(|port| Some(*port) != self.scheme_type.default_port()); + + let mut candidate = + String::with_capacity(self.buffer.len() + hostname.len() + usize::from(!hostname_only)); + if self.has_authority() { + candidate.push_str(&self.buffer[..self.components.host_start as usize]); + } else { + candidate.push_str(self.protocol()); + candidate.push_str("//"); + } + candidate.push_str(hostname); + if let Some(port) = port { + candidate.push(':'); + candidate.push_str(&port.to_string()); + } + candidate.push_str(self.pathname_and_later()); + let replacement = Self::parse_with_url_base(&candidate, None) + .map_err(|_| ParseError::new(ParseErrorKind::InvalidSetter))?; + *self = replacement; + Ok(()) + } + + fn replace_hostname_serialized(&mut self, hostname: &str) -> Result<(), ParseError> { + if !self.has_authority() { + return self.replace_host_from_setter(hostname, None, true); + } + let start = self.components.host_start as usize; + let end = self.components.host_end as usize; + let difference = isize::try_from(hostname.len()) + .ok() + .and_then(|new| isize::try_from(end - start).ok().map(|old| new - old)) + .ok_or_else(|| ParseError::new(ParseErrorKind::TooLong))?; + let mut buffer = self.buffer.clone(); + buffer.replace_range(start..end, hostname); + check_normalized_length(buffer.len())?; + let components = Components::new( + self.components.protocol_end, + self.components.username_end, + self.components.host_start, + shift_offset(self.components.host_end, difference)?, + self.components.port(), + shift_offset(self.components.pathname_start, difference)?, + self.components + .search_start() + .map(|offset| shift_offset(offset, difference)) + .transpose()?, + self.components + .hash_start() + .map(|offset| shift_offset(offset, difference)) + .transpose()?, + ); + if !components.validate(buffer.len()) { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + self.buffer = buffer; + self.components = components; + self.host_type = HostType::Domain; + Ok(()) + } + + fn replace_non_special_path(&mut self, pathname: &str) -> Result<(), ParseError> { + let pathname = parse_non_special_path(pathname); + let mut buffer = String::from(self.protocol()); + if pathname.starts_with("//") { + buffer.push_str("/."); + } + let pathname_start = buffer.len(); + buffer.push_str(&pathname); + let later_start = self + .components + .search_start() + .or(self.components.hash_start()) + .map_or(self.buffer.len(), |offset| offset as usize); + buffer.push_str(&self.buffer[later_start..]); + check_normalized_length(buffer.len())?; + + let search_start = + memchr(b'?', &buffer.as_bytes()[pathname_start..]).map(|index| pathname_start + index); + let hash_start = + memchr(b'#', &buffer.as_bytes()[pathname_start..]).map(|index| pathname_start + index); + let components = Components::new( + self.components.protocol_end, + self.components.protocol_end, + self.components.protocol_end, + self.components.protocol_end, + None, + to_u32(pathname_start)?, + search_start.map(to_u32).transpose()?, + hash_start.map(to_u32).transpose()?, + ); + if !components.validate(buffer.len()) { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + self.buffer = buffer; + self.components = components; + Ok(()) + } + + /// Changes or clears the port. + fn set_port_value(&mut self, port: &str) -> Result<(), ParseError> { + if !self.has_authority() + || self.scheme_type == SchemeType::File + || self.hostname().is_empty() + { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let was_empty = port.is_empty(); + let cleaned = remove_ascii_tab_or_newline(port); + let port = cleaned.as_ref(); + let parsed = if port.is_empty() { + if was_empty { + None + } else { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + } else { + let digits = port + .as_bytes() + .iter() + .take_while(|byte| byte.is_ascii_digit()) + .count(); + if digits == 0 { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + Some( + port[..digits] + .parse::() + .map_err(|_| ParseError::new(ParseErrorKind::InvalidSetter))?, + ) + }; + let parsed = parsed.filter(|port| Some(*port) != self.scheme_type.default_port()); + let start = self.components.host_end as usize; + let end = self.components.pathname_start as usize; + let mut serialized = String::new(); + if let Some(port) = parsed { + serialized.push(':'); + serialized.push_str(&port.to_string()); + } + let difference = isize::try_from(serialized.len()) + .ok() + .and_then(|new| isize::try_from(end - start).ok().map(|old| new - old)) + .ok_or_else(|| ParseError::new(ParseErrorKind::TooLong))?; + let mut buffer = self.buffer.clone(); + buffer.replace_range(start..end, &serialized); + check_normalized_length(buffer.len())?; + let components = Components::new( + self.components.protocol_end, + self.components.username_end, + self.components.host_start, + self.components.host_end, + parsed, + shift_offset(self.components.pathname_start, difference)?, + self.components + .search_start() + .map(|offset| shift_offset(offset, difference)) + .transpose()?, + self.components + .hash_start() + .map(|offset| shift_offset(offset, difference)) + .transpose()?, + ); + if !components.validate(buffer.len()) { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + self.buffer = buffer; + self.components = components; + Ok(()) + } + + /// Changes the pathname. + fn set_pathname_value(&mut self, pathname: &str) -> Result<(), ParseError> { + if self.has_opaque_path() { + return Err(ParseError::new(ParseErrorKind::InvalidSetter)); + } + let cleaned = remove_ascii_tab_or_newline(pathname); + let pathname = cleaned.as_ref(); + if self.scheme_type == SchemeType::File { + let encoded = utf8_percent_encode(pathname, PATH_ENCODE_SET).to_string(); + let path_input = encoded + .strip_prefix('/') + .or_else(|| encoded.strip_prefix('\\')) + .unwrap_or(&encoded); + let mut suffix = parse_file_suffix(path_input, Vec::new()); + let later_start = self + .components + .search_start() + .or(self.components.hash_start()) + .map_or(self.buffer.len(), |offset| offset as usize); + suffix.push_str(&self.buffer[later_start..]); + let replacement = Self::from_file_parts(self.hostname(), self.host_type, &suffix)?; + *self = replacement; + return Ok(()); + } + let pathname = if pathname.is_empty() && (!self.has_authority() || self.is_special()) { + "/" + } else { + pathname + }; + let pathname = utf8_percent_encode(pathname, PATH_ENCODE_SET).to_string(); + if self.scheme_type == SchemeType::NotSpecial && !self.has_authority() { + return self.replace_non_special_path(&pathname); + } + let later_start = self + .components + .search_start() + .or(self.components.hash_start()) + .map_or(self.buffer.len(), |offset| offset as usize); + let mut candidate = String::with_capacity(self.buffer.len() + pathname.len() + 1); + candidate.push_str(&self.buffer[..self.components.pathname_start as usize]); + if !pathname.is_empty() + && !pathname.starts_with('/') + && !(self.is_special() && pathname.starts_with('\\')) + { + candidate.push('/'); + } + candidate.push_str(&pathname); + candidate.push_str(&self.buffer[later_start..]); + let replacement = Self::parse_with_url_base(&candidate, None) + .map_err(|_| ParseError::new(ParseErrorKind::InvalidSetter))?; + *self = replacement; + Ok(()) + } + + /// Changes or clears the query. An empty input clears it. + fn set_search_value(&mut self, search: &str) -> Result<(), ParseError> { + let cleaned = remove_ascii_tab_or_newline(search); + let search = cleaned.as_ref(); + let path_end = self + .components + .search_start() + .or(self.components.hash_start()) + .map_or(self.buffer.len(), |offset| offset as usize); + let hash = self + .components + .hash_start() + .map(|offset| String::from(&self.buffer[offset as usize..])); + let mut buffer = String::with_capacity(self.buffer.len() + search.len() + 1); + buffer.push_str(&self.buffer[..path_end]); + let search_start = if search.is_empty() { + None + } else { + let start = to_u32(buffer.len())?; + buffer.push('?'); + let value = search.strip_prefix('?').unwrap_or(search); + let encode_set = if self.is_special() { + SPECIAL_QUERY_ENCODE_SET + } else { + QUERY_ENCODE_SET + }; + append_percent_encoded(&mut buffer, value, encode_set); + Some(start) + }; + let hash_start = hash + .map(|hash| { + let start = to_u32(buffer.len())?; + buffer.push_str(&hash); + Ok(start) + }) + .transpose()?; + check_normalized_length(buffer.len())?; + self.buffer = buffer; + self.components = Components::new( + self.components.protocol_end, + self.components.username_end, + self.components.host_start, + self.components.host_end, + self.components.port(), + self.components.pathname_start, + search_start, + hash_start, + ); + self.trim_opaque_trailing_spaces(); + Ok(()) + } + + /// Replaces the query from serialized URL search parameters. + pub fn set_search_params(&mut self, params: &UrlSearchParams) -> Result<(), ParseError> { + self.set_search_value(¶ms.to_string()) + } + + /// Changes or clears the fragment. An empty input clears it. + fn set_hash_value(&mut self, hash: &str) -> Result<(), ParseError> { + let cleaned = remove_ascii_tab_or_newline(hash); + let hash = cleaned.as_ref(); + let fragment_start = self + .components + .hash_start() + .map_or(self.buffer.len(), |offset| offset as usize); + let mut buffer = String::with_capacity(fragment_start + hash.len() + 1); + buffer.push_str(&self.buffer[..fragment_start]); + let hash_start = if hash.is_empty() { + None + } else { + let start = to_u32(buffer.len())?; + buffer.push('#'); + append_percent_encoded( + &mut buffer, + hash.strip_prefix('#').unwrap_or(hash), + FRAGMENT_ENCODE_SET, + ); + Some(start) + }; + check_normalized_length(buffer.len())?; + self.buffer = buffer; + self.components = Components::new( + self.components.protocol_end, + self.components.username_end, + self.components.host_start, + self.components.host_end, + self.components.port(), + self.components.pathname_start, + self.components.search_start(), + hash_start, + ); + self.trim_opaque_trailing_spaces(); + Ok(()) + } + + fn trim_opaque_trailing_spaces(&mut self) { + if !self.has_opaque_path() + || self.components.search_start().is_some() + || self.components.hash_start().is_some() + { + return; + } + while self.buffer.ends_with(' ') { + self.buffer.pop(); + } + } + + /// Replaces the entire URL. + #[allow(clippy::result_unit_err)] + pub fn set_href(&mut self, input: &str) -> Result<(), ()> { + self.set_href_value(input).map_err(|_| ()) + } + + /// Changes the scheme. + #[allow(clippy::result_unit_err)] + pub fn set_protocol(&mut self, input: &str) -> Result<(), ()> { + self.set_protocol_value(input).map_err(|_| ()) + } + + /// Changes or clears the username. + #[allow(clippy::result_unit_err)] + pub fn set_username(&mut self, input: Option<&str>) -> Result<(), ()> { + self.set_username_value(input.unwrap_or("")).map_err(|_| ()) + } + + /// Changes or clears the password. + #[allow(clippy::result_unit_err)] + pub fn set_password(&mut self, input: Option<&str>) -> Result<(), ()> { + self.set_password_value(input.unwrap_or("")).map_err(|_| ()) + } + + /// Changes the host and optional port. + #[allow(clippy::result_unit_err)] + pub fn set_host(&mut self, input: Option<&str>) -> Result<(), ()> { + self.set_host_value(input.unwrap_or("")).map_err(|_| ()) + } + + /// Changes only the hostname. + #[allow(clippy::result_unit_err)] + pub fn set_hostname(&mut self, input: Option<&str>) -> Result<(), ()> { + self.set_hostname_value(input.unwrap_or("")).map_err(|_| ()) + } + + /// Changes or clears the port. + #[allow(clippy::result_unit_err)] + pub fn set_port(&mut self, input: Option<&str>) -> Result<(), ()> { + self.set_port_value(input.unwrap_or("")).map_err(|_| ()) + } + + /// Changes the pathname. + #[allow(clippy::result_unit_err)] + pub fn set_pathname(&mut self, input: Option<&str>) -> Result<(), ()> { + self.set_pathname_value(input.unwrap_or("")).map_err(|_| ()) + } + + /// Changes or clears the query. + pub fn set_search(&mut self, input: Option<&str>) { + let _ = self.set_search_value(input.unwrap_or("")); + } + + /// Changes or clears the fragment. + pub fn set_hash(&mut self, input: Option<&str>) { + let _ = self.set_hash_value(input.unwrap_or("")); + } + + /// Removes the port. + pub fn clear_port(&mut self) { + let _ = self.set_port_value(""); + } + + /// Removes the query. + pub fn clear_search(&mut self) { + let _ = self.set_search_value(""); + } + + /// Removes the fragment. + pub fn clear_hash(&mut self) { + let _ = self.set_hash_value(""); + } + + /// Returns the complete normalized serialization. + #[inline] + #[must_use] + pub fn href(&self) -> &str { + &self.buffer + } + + /// Returns the complete normalized serialization. + #[inline] + #[must_use] + pub fn as_str(&self) -> &str { + self.href() + } + + /// Returns the complete serialization's byte length. + #[inline] + #[must_use] + pub fn href_size(&self) -> usize { + self.buffer.len() + } + + /// Returns the protocol including `:`. + #[inline] + #[must_use] + pub fn protocol(&self) -> &str { + &self.buffer[..self.components.protocol_end as usize] + } + + /// Returns the scheme without `:`. + #[inline] + #[must_use] + pub fn scheme(&self) -> &str { + &self.protocol()[..self.protocol().len() - 1] + } + + /// Returns the username. + #[inline] + #[must_use] + pub fn username(&self) -> &str { + if !self.has_authority() { + return ""; + } + let start = self.components.protocol_end as usize + 2; + &self.buffer[start..self.components.username_end as usize] + } + + /// Returns the password without `:`. + #[inline] + #[must_use] + pub fn password(&self) -> &str { + if !self.has_password() { + return ""; + } + let start = self.components.username_end as usize + 1; + let end = self.components.host_start as usize - 1; + &self.buffer[start..end] + } + + /// Returns the host including an optional port. + #[inline] + #[must_use] + pub fn host(&self) -> &str { + if !self.has_authority() { + return ""; + } + &self.buffer[self.components.host_start as usize..self.components.pathname_start as usize] + } + + /// Returns the hostname without a port. + #[inline] + #[must_use] + pub fn hostname(&self) -> &str { + if !self.has_authority() { + return ""; + } + &self.buffer[self.components.host_start as usize..self.components.host_end as usize] + } + + /// Returns the explicit non-default port. + #[inline] + #[must_use] + pub fn port(&self) -> &str { + if self.components.port().is_none() { + return ""; + } + &self.buffer[self.components.host_end as usize + 1..self.components.pathname_start as usize] + } + + /// Returns the pathname. + #[inline] + #[must_use] + pub fn pathname(&self) -> &str { + let end = self + .components + .search_start() + .or(self.components.hash_start()) + .map_or(self.buffer.len(), |offset| offset as usize); + &self.buffer[self.components.pathname_start as usize..end] + } + + /// Returns the query including `?`, or an empty string. + #[inline] + #[must_use] + pub fn search(&self) -> &str { + let Some(start) = self.components.search_start() else { + return ""; + }; + let end = self + .components + .hash_start() + .map_or(self.buffer.len(), |offset| offset as usize); + if start as usize + 1 == end { + "" + } else { + &self.buffer[start as usize..end] + } + } + + /// Parses the query into an independent URL search-parameter collection. + /// + /// Call [`Self::set_search_params`] after mutation to publish changes back + /// to this URL. + #[must_use] + pub fn search_params(&self) -> UrlSearchParams { + UrlSearchParams::new(self.search()) + } + + /// Returns the fragment including `#`, or an empty string. + #[inline] + #[must_use] + pub fn hash(&self) -> &str { + let Some(start) = self.components.hash_start() else { + return ""; + }; + if start as usize + 1 == self.buffer.len() { + "" + } else { + &self.buffer[start as usize..] + } + } + + #[inline] + fn pathname_and_later(&self) -> &str { + &self.buffer[self.components.pathname_start as usize..] + } + + /// Returns the component metadata. + #[inline] + #[must_use] + pub fn components(&self) -> UrlComponents { + let host_start = if self + .components + .host_start + .checked_sub(1) + .is_some_and(|index| self.buffer.as_bytes()[index as usize] == b'@') + { + self.components.host_start - 1 + } else { + self.components.host_start + }; + UrlComponents { + protocol_end: self.components.protocol_end, + username_end: self.components.username_end, + host_start, + host_end: self.components.host_end, + port: self.components.port().map(u32::from), + pathname_start: Some(self.components.pathname_start), + search_start: self.components.search_start(), + hash_start: self.components.hash_start(), + } + } + + /// Returns the host representation. + #[inline] + #[must_use] + pub const fn host_type(&self) -> HostType { + self.host_type + } + + /// Returns the scheme classification. + #[inline] + #[must_use] + pub const fn scheme_type(&self) -> SchemeType { + self.scheme_type + } + + /// Returns whether the URL uses a special scheme. + #[inline] + #[must_use] + pub const fn is_special(&self) -> bool { + self.scheme_type.is_special() + } + + /// Returns whether this URL has an opaque path. + #[inline] + #[must_use] + pub const fn has_opaque_path(&self) -> bool { + self.flags & FLAG_OPAQUE_PATH != 0 + } + + /// Returns whether this URL has an authority section. + #[inline] + #[must_use] + pub const fn has_authority(&self) -> bool { + self.flags & FLAG_AUTHORITY != 0 + } + + /// Returns whether the URL contains a host, including an empty file host. + #[inline] + #[must_use] + pub const fn has_hostname(&self) -> bool { + self.has_authority() + } + + /// Returns whether the URL has an empty host. + #[inline] + #[must_use] + pub fn has_empty_hostname(&self) -> bool { + self.has_authority() && self.hostname().is_empty() + } + + /// Returns whether the URL has a non-empty username. + #[inline] + #[must_use] + pub fn has_non_empty_username(&self) -> bool { + !self.username().is_empty() + } + + /// Returns whether the URL has a non-empty password. + #[inline] + #[must_use] + pub fn has_non_empty_password(&self) -> bool { + !self.password().is_empty() + } + + /// Returns whether the URL has an explicit non-default port. + #[inline] + #[must_use] + pub const fn has_port(&self) -> bool { + self.components.port().is_some() + } + + /// Returns whether a query is present, including an empty query. + #[inline] + #[must_use] + pub const fn has_search(&self) -> bool { + self.components.search_start().is_some() + } + + /// Returns whether a fragment is present, including an empty fragment. + #[inline] + #[must_use] + pub const fn has_hash(&self) -> bool { + self.components.hash_start().is_some() + } + + /// Returns whether a password field is present. + #[inline] + #[must_use] + pub fn has_password(&self) -> bool { + let index = self.components.username_end as usize; + self.has_authority() && self.buffer.as_bytes().get(index) == Some(&b':') + } + + /// Returns whether non-empty credentials are present. + #[inline] + #[must_use] + pub fn has_credentials(&self) -> bool { + !self.username().is_empty() || !self.password().is_empty() + } + + /// Returns the serialized origin. + #[must_use] + pub fn origin(&self) -> String { + match self.scheme_type { + SchemeType::Ftp + | SchemeType::Http + | SchemeType::Https + | SchemeType::Ws + | SchemeType::Wss => format!("{}//{}", self.protocol(), self.host()), + SchemeType::File => String::from("null"), + SchemeType::NotSpecial => { + if self.scheme() == "blob" + && let Ok(inner) = Self::parse_with_url_base(self.pathname(), None) + && matches!(inner.scheme_type, SchemeType::Http | SchemeType::Https) + { + return inner.origin(); + } + String::from("null") + } + } + } + + /// Checks RFC 1034 DNS wire-length constraints. + #[must_use] + pub fn has_valid_domain(&self) -> bool { + if self.host_type != HostType::Domain { + return false; + } + let domain = self.hostname(); + if domain.is_empty() + || if domain.ends_with('.') { + domain.len() > 254 + } else { + domain.len() > 253 + } + { + return false; + } + domain + .strip_suffix('.') + .unwrap_or(domain) + .split('.') + .all(|label| !label.is_empty() && label.len() <= 63) + } + + /// Checks internal offset and serialization invariants. + #[must_use] + pub fn validate(&self) -> bool { + self.components.validate(self.buffer.len()) + && self.protocol().ends_with(':') + && self + .components + .search_start() + .is_none_or(|index| self.buffer.as_bytes()[index as usize] == b'?') + && self + .components + .hash_start() + .is_none_or(|index| self.buffer.as_bytes()[index as usize] == b'#') + } +} + +impl fmt::Display for Url { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.buffer) + } +} + +impl fmt::Debug for Url { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("Url") + .field("href", &self.buffer) + .field("components", &self.components) + .field("scheme_type", &self.scheme_type) + .field("host_type", &self.host_type) + .field("opaque_path", &self.has_opaque_path()) + .finish() + } +} + +impl PartialEq for Url { + fn eq(&self, other: &Self) -> bool { + self.buffer == other.buffer + } +} + +impl Eq for Url {} + +impl PartialOrd for Url { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Url { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.buffer.cmp(&other.buffer) + } +} + +impl Hash for Url { + fn hash(&self, state: &mut H) { + self.buffer.hash(state); + } +} + +impl Deref for Url { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl AsRef for Url { + fn as_ref(&self) -> &str { + &self.buffer + } +} + +impl AsRef<[u8]> for Url { + fn as_ref(&self) -> &[u8] { + self.buffer.as_bytes() + } +} + +impl Borrow for Url { + fn borrow(&self) -> &str { + &self.buffer + } +} + +impl<'input> TryFrom<&'input str> for Url { + type Error = ParseUrlError<&'input str>; + + fn try_from(input: &'input str) -> Result { + Self::parse(input, None) + } +} + +impl TryFrom for Url { + type Error = ParseUrlError; + + fn try_from(input: String) -> Result { + Self::parse(input, None) + } +} + +impl<'input> TryFrom<&'input String> for Url { + type Error = ParseUrlError<&'input String>; + + fn try_from(input: &'input String) -> Result { + Self::parse(input, None) + } +} + +impl From for String { + fn from(url: Url) -> Self { + url.buffer + } +} + +impl FromStr for Url { + type Err = ParseUrlError>; + + fn from_str(input: &str) -> Result { + Self::parse(input, None).map_err(|ParseUrlError { input }| ParseUrlError { + input: input.into(), + }) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Url { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.href()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Url { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let input = ::deserialize(deserializer)?; + Self::parse_with_url_base(&input, None).map_err(serde::de::Error::custom) + } +} + +/// Parses a URL. +pub fn parse(input: Input, base: Option<&str>) -> Result> +where + Input: AsRef, +{ + Url::parse(input, base) +} + +/// Returns whether a URL string can be parsed. +#[must_use] +pub fn can_parse(input: &str, base: Option<&str>) -> bool { + Url::can_parse(input, base) +} + +/// Sets the process-wide input and normalized-output length limit. +pub fn set_max_input_length(length: u32) { + MAX_INPUT_LENGTH.store(length, Ordering::Relaxed); +} + +/// Returns the process-wide URL length limit. +#[must_use] +pub fn get_max_input_length() -> u32 { + MAX_INPUT_LENGTH.load(Ordering::Relaxed) +} + +/// Converts an absolute filesystem path to a `file:` URL. +#[cfg(all( + feature = "std", + any(unix, target_os = "redox", target_os = "wasi", target_os = "hermit") +))] +pub fn href_from_file(path: impl AsRef) -> Result { + let path = path.as_ref(); + if !path.is_absolute() { + return Err(ParseError::new(ParseErrorKind::InvalidUrl)); + } + let mut output = String::from("file://"); + let mut empty = true; + for component in path.components().skip(1) { + empty = false; + output.push('/'); + #[cfg(any(unix, target_os = "redox"))] + { + use std::os::unix::ffi::OsStrExt; + append_file_path_segment(&mut output, component.as_os_str().as_bytes()); + } + #[cfg(not(any(unix, target_os = "redox")))] + append_file_path_segment( + &mut output, + component.as_os_str().to_string_lossy().as_bytes(), + ); + } + if empty { + output.push('/'); + } + check_normalized_length(output.len())?; + Ok(output) +} + +/// Converts an absolute Windows filesystem path to a `file:` URL. +#[cfg(all(feature = "std", windows))] +pub fn href_from_file(path: impl AsRef) -> Result { + use std::path::{Component, Prefix}; + + let path = path.as_ref(); + if !path.is_absolute() { + return Err(ParseError::new(ParseErrorKind::InvalidUrl)); + } + let mut components = path.components(); + let Some(Component::Prefix(prefix)) = components.next() else { + return Err(ParseError::new(ParseErrorKind::InvalidUrl)); + }; + let mut output = String::from("file://"); + let mut only_prefix = true; + match prefix.kind() { + Prefix::Disk(letter) | Prefix::VerbatimDisk(letter) => { + output.push('/'); + output.push(char::from(letter)); + output.push(':'); + } + Prefix::UNC(server, share) | Prefix::VerbatimUNC(server, share) => { + let server = server + .to_str() + .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidUrl))?; + let (server, _) = fast_path::normalize_special_host(server) + .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidUrl))?; + output.push_str(&server); + output.push('/'); + append_file_path_segment( + &mut output, + share + .to_str() + .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidUrl))? + .as_bytes(), + ); + only_prefix = false; + } + _ => return Err(ParseError::new(ParseErrorKind::InvalidUrl)), + } + for component in components { + if component == Component::RootDir { + continue; + } + only_prefix = false; + output.push('/'); + append_file_path_segment( + &mut output, + component + .as_os_str() + .to_str() + .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidUrl))? + .as_bytes(), + ); + } + if only_prefix { + output.push('/'); + } + check_normalized_length(output.len())?; + Ok(output) +} + +fn check_raw_length(input: &str) -> Result<(), ParseError> { + if input.len() > get_max_input_length() as usize { + Err(ParseError::new(ParseErrorKind::TooLong)) + } else { + Ok(()) + } +} + +fn check_normalized_length(length: usize) -> Result<(), ParseError> { + if length > get_max_input_length() as usize { + Err(ParseError::new(ParseErrorKind::TooLong)) + } else { + Ok(()) + } +} + +fn to_u32(value: usize) -> Result { + u32::try_from(value).map_err(|_| ParseError::new(ParseErrorKind::TooLong)) +} + +fn shift_offset(offset: u32, difference: isize) -> Result { + let difference = + i32::try_from(difference).map_err(|_| ParseError::new(ParseErrorKind::TooLong))?; + offset + .checked_add_signed(difference) + .ok_or_else(|| ParseError::new(ParseErrorKind::TooLong)) +} + +fn split_host_port(host: &str) -> (&str, Option<&str>) { + if host.starts_with('[') { + let Some(closing) = host.find(']') else { + return (host, None); + }; + let after = &host[closing + 1..]; + if let Some(port) = after.strip_prefix(':') { + (&host[..=closing], Some(port)) + } else { + (host, None) + } + } else { + host.find(':').map_or((host, None), |index| { + (&host[..index], Some(&host[index + 1..])) + }) + } +} + +fn setter_host_prefix(input: &str, special: bool) -> &str { + let end = input + .bytes() + .position(|byte| matches!(byte, b'/' | b'?' | b'#') || (special && byte == b'\\')) + .unwrap_or(input.len()); + &input[..end] +} + +fn remove_ascii_tab_or_newline(input: &str) -> Cow<'_, str> { + if !input + .as_bytes() + .iter() + .any(|byte| matches!(byte, b'\t' | b'\n' | b'\r')) + { + return Cow::Borrowed(input); + } + Cow::Owned( + input + .chars() + .filter(|character| !matches!(character, '\t' | '\n' | '\r')) + .collect(), + ) +} + +fn normalize_scheme_case(input: &str) -> Option { + let colon = memchr(b':', input.as_bytes())?; + let scheme = &input[..colon]; + if scheme.is_empty() + || !scheme.as_bytes()[0].is_ascii_alphabetic() + || !scheme + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')) + || !scheme.bytes().any(|byte| byte.is_ascii_uppercase()) + { + return None; + } + let mut normalized = String::with_capacity(input.len()); + normalized.extend( + scheme + .chars() + .map(|character| character.to_ascii_lowercase()), + ); + normalized.push_str(&input[colon..]); + Some(normalized) +} + +fn normalize_special_backslashes(input: &str) -> Option { + let colon = memchr(b':', input.as_bytes())?; + if !SchemeType::from_scheme(&input[..colon]).is_special() { + return None; + } + let suffix_end = memchr2(b'?', b'#', &input.as_bytes()[colon + 1..]) + .map_or(input.len(), |offset| colon + 1 + offset); + if !input.as_bytes()[colon + 1..suffix_end].contains(&b'\\') { + return None; + } + let mut normalized = String::with_capacity(input.len()); + normalized.push_str(&input[..colon + 1]); + normalized.extend( + input[colon + 1..suffix_end] + .chars() + .map(|character| if character == '\\' { '/' } else { character }), + ); + normalized.push_str(&input[suffix_end..]); + Some(normalized) +} + +fn normalize_malformed_special_absolute(input: &str) -> Option { + let colon = memchr(b':', input.as_bytes())?; + let scheme = &input[..colon]; + if !SchemeType::from_scheme(scheme).is_special() { + return None; + } + let remainder = &input[colon + 1..]; + let leading = remainder + .bytes() + .take_while(|byte| matches!(byte, b'/' | b'\\')) + .count(); + if leading == 2 + && remainder + .as_bytes() + .get(2) + .is_none_or(|byte| !matches!(byte, b'/' | b'\\')) + { + return None; + } + let authority = &remainder[leading..]; + if authority.is_empty() { + return None; + } + let mut normalized = String::with_capacity(input.len() + 2); + normalized.push_str(scheme); + normalized.push_str("://"); + normalized.push_str(authority); + Some(normalized) +} + +fn resolve_simple_reference(input: &str, base: &Url) -> Option> { + if input + .as_bytes() + .iter() + .any(|byte| matches!(byte, b'\t' | b'\n' | b'\r')) + { + return None; + } + if input.is_empty() { + if base.has_opaque_path() { + return Some(Err(ParseError::new(ParseErrorKind::InvalidUrl))); + } + let mut resolved = base.clone(); + if let Some(hash_start) = resolved.components.hash_start() { + resolved.buffer.truncate(hash_start as usize); + resolved.components = Components::new( + resolved.components.protocol_end, + resolved.components.username_end, + resolved.components.host_start, + resolved.components.host_end, + resolved.components.port(), + resolved.components.pathname_start, + resolved.components.search_start(), + None, + ); + } + return Some(Ok(resolved)); + } + if let Some(fragment) = input.strip_prefix('#') { + return Some((|| { + let mut buffer = base.buffer.clone(); + buffer.truncate( + base.components + .hash_start() + .map_or(buffer.len(), |offset| offset as usize), + ); + let hash_start = to_u32(buffer.len())?; + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + check_normalized_length(buffer.len())?; + let components = Components::new( + base.components.protocol_end, + base.components.username_end, + base.components.host_start, + base.components.host_end, + base.components.port(), + base.components.pathname_start, + base.components.search_start(), + Some(hash_start), + ); + Ok(Url { + components, + buffer, + scheme_type: base.scheme_type, + host_type: base.host_type, + flags: base.flags, + }) + })()); + } + if base.has_opaque_path() { + return Some(Err(ParseError::new(ParseErrorKind::InvalidUrl))); + } + let query_and_fragment = input.strip_prefix('?')?; + Some((|| { + let (query, fragment) = query_and_fragment + .split_once('#') + .map_or((query_and_fragment, None), |(query, fragment)| { + (query, Some(fragment)) + }); + let mut buffer = base.buffer.clone(); + let suffix_start = base + .components + .search_start() + .or_else(|| base.components.hash_start()) + .map_or(buffer.len(), |offset| offset as usize); + buffer.truncate(suffix_start); + let search_start = to_u32(buffer.len())?; + buffer.push('?'); + let encode_set = if base.is_special() { + SPECIAL_QUERY_ENCODE_SET + } else { + QUERY_ENCODE_SET + }; + append_percent_encoded(&mut buffer, query, encode_set); + let hash_start = fragment + .map(|fragment| { + let start = to_u32(buffer.len())?; + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + Ok(start) + }) + .transpose()?; + check_normalized_length(buffer.len())?; + let components = Components::new( + base.components.protocol_end, + base.components.username_end, + base.components.host_start, + base.components.host_end, + base.components.port(), + base.components.pathname_start, + Some(search_start), + hash_start, + ); + Ok(Url { + components, + buffer, + scheme_type: base.scheme_type, + host_type: base.host_type, + flags: base.flags, + }) + })()) +} + +fn resolve_authority_reference(input: &str, base: &Url) -> Option> { + if !base.has_authority() || base.scheme_type == SchemeType::File || has_url_scheme(input) { + return None; + } + let special = base.is_special(); + let leading = if special { + input + .bytes() + .take_while(|byte| matches!(byte, b'/' | b'\\')) + .count() + } else if input.starts_with("//") { + 2 + } else { + 0 + }; + if leading < 2 { + return None; + } + + Some({ + let tail = &input[leading..]; + let suffix_end = memchr2(b'?', b'#', tail.as_bytes()).unwrap_or(tail.len()); + let mut absolute = String::with_capacity(base.scheme().len() + 3 + input.len()); + absolute.push_str(base.scheme()); + absolute.push_str("://"); + if special && tail[..suffix_end].contains('\\') { + absolute.extend( + tail[..suffix_end] + .chars() + .map(|character| if character == '\\' { '/' } else { character }), + ); + } else { + absolute.push_str(&tail[..suffix_end]); + } + absolute.push_str(&tail[suffix_end..]); + Url::parse_with_url_base(&absolute, None) + }) +} + +fn resolve_common_path_reference(input: &str, base: &Url) -> Option> { + if base.scheme_type == SchemeType::File + || has_url_scheme(input) + || input.starts_with("//") + || (base.is_special() && input.as_bytes().starts_with(b"\\\\")) + || input + .as_bytes() + .iter() + .any(|byte| matches!(byte, b'\t' | b'\n' | b'\r')) + || input + .chars() + .next() + .is_some_and(|character| character <= '\u{20}') + || input + .chars() + .next_back() + .is_some_and(|character| character <= '\u{20}') + { + return None; + } + + Some((|| { + let (path, query, fragment) = split_path_query_fragment(input); + let rooted = path.starts_with('/') || (base.is_special() && path.starts_with('\\')); + let mut resolved_path = if rooted { + String::from("/") + } else { + let parent_end = base.pathname().rfind('/').map_or(0, |index| index + 1); + String::from(&base.pathname()[..parent_end]) + }; + let path = if rooted { &path[1..] } else { path }; + let mut input_segments = path + .split(if base.is_special() { + &['/', '\\'][..] + } else { + &['/'][..] + }) + .peekable(); + let mut needs_separator = false; + while let Some(segment) = input_segments.next() { + if is_single_dot_segment(segment) { + if input_segments.peek().is_none() && !resolved_path.ends_with('/') { + resolved_path.push('/'); + } + continue; + } + if is_double_dot_segment(segment) { + shorten_resolved_path(&mut resolved_path); + needs_separator = false; + if input_segments.peek().is_none() && !resolved_path.ends_with('/') { + resolved_path.push('/'); + } + continue; + } + if needs_separator || !resolved_path.ends_with('/') { + resolved_path.push('/'); + } + append_percent_encoded(&mut resolved_path, segment, PATH_ENCODE_SET); + needs_separator = true; + } + + let mut buffer = String::with_capacity( + base.components.pathname_start as usize + resolved_path.len() + input.len() + 8, + ); + if base.has_authority() { + buffer.push_str(&base.buffer[..base.components.pathname_start as usize]); + } else { + buffer.push_str(base.protocol()); + if resolved_path.starts_with("//") { + buffer.push_str("/."); + } + } + let pathname_start = buffer.len(); + buffer.push_str(&resolved_path); + let search_start = query + .map(|query| { + let start = to_u32(buffer.len())?; + buffer.push('?'); + let encode_set = if base.is_special() { + SPECIAL_QUERY_ENCODE_SET + } else { + QUERY_ENCODE_SET + }; + append_percent_encoded(&mut buffer, query, encode_set); + Ok(start) + }) + .transpose()?; + let hash_start = fragment + .map(|fragment| { + let start = to_u32(buffer.len())?; + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + Ok(start) + }) + .transpose()?; + check_normalized_length(buffer.len())?; + let components = Components::new( + base.components.protocol_end, + base.components.username_end, + base.components.host_start, + base.components.host_end, + base.components.port(), + to_u32(pathname_start)?, + search_start, + hash_start, + ); + Ok(Url { + components, + buffer, + scheme_type: base.scheme_type, + host_type: base.host_type, + flags: base.flags, + }) + })()) +} + +fn shorten_resolved_path(path: &mut String) { + if path == "/" { + return; + } + let end = path.len().saturating_sub(usize::from(path.ends_with('/'))); + let parent_end = path.as_bytes()[..end] + .iter() + .rposition(|byte| *byte == b'/') + .map_or(0, |index| index + 1); + path.truncate(parent_end.max(1)); +} + +fn parse_explicit_file(input: &str, base: Option<&Url>) -> Option> { + let cleaned; + let input = if input + .as_bytes() + .iter() + .any(|byte| matches!(byte, b'\t' | b'\n' | b'\r')) + { + cleaned = input + .chars() + .filter(|character| !matches!(character, '\t' | '\n' | '\r')) + .collect::(); + cleaned.as_str() + } else { + input + }; + let input = input.trim_matches(|character: char| character <= '\u{20}'); + let (scheme, rest) = input.split_at_checked(5)?; + if !scheme.eq_ignore_ascii_case("file:") { + return None; + } + + Some((|| { + let file_base = base.filter(|base| base.scheme_type == SchemeType::File); + let bytes = rest.as_bytes(); + let two_slashes = bytes.len() >= 2 + && matches!(bytes[0], b'/' | b'\\') + && matches!(bytes[1], b'/' | b'\\'); + + let (host, host_type, path_input, initial_segments) = if two_slashes { + let authority = &rest[2..]; + let host_end = authority + .bytes() + .position(|byte| matches!(byte, b'/' | b'\\' | b'?' | b'#')) + .unwrap_or(authority.len()); + let raw_host = &authority[..host_end]; + if is_windows_drive_letter(raw_host.as_bytes()) { + (String::new(), HostType::Domain, authority, Vec::new()) + } else { + let (host, host_type) = if raw_host.is_empty() { + (String::new(), HostType::Domain) + } else { + normalize_file_host(raw_host)? + }; + let remaining = &authority[host_end..]; + let path_input = remaining + .strip_prefix('/') + .or_else(|| remaining.strip_prefix('\\')) + .unwrap_or(remaining); + (host, host_type, path_input, Vec::new()) + } + } else if bytes + .first() + .is_some_and(|byte| matches!(byte, b'/' | b'\\')) + { + let path_input = &rest[1..]; + let (host, host_type) = file_base.map_or_else( + || (String::new(), HostType::Domain), + |base| (String::from(base.hostname()), base.host_type), + ); + let mut initial = Vec::new(); + if !starts_with_windows_drive_segment(path_only(path_input).as_bytes()) + && let Some(base) = file_base + && starts_with_windows_drive_path(base.pathname().as_bytes()) + { + initial.push(String::from(&base.pathname()[1..3])); + } + (host, host_type, path_input, initial) + } else if let Some(base) = file_base { + let path = path_only(rest); + if path.is_empty() { + let suffix = suffix_with_preserved_file_path(rest, base); + return Url::from_file_parts(base.hostname(), base.host_type, &suffix); + } + + let mut initial = file_path_segments(base.pathname()); + if starts_with_windows_drive_segment(path.as_bytes()) { + initial.clear(); + } else { + shorten_file_path(&mut initial); + } + (String::from(base.hostname()), base.host_type, rest, initial) + } else { + (String::new(), HostType::Domain, rest, Vec::new()) + }; + + let suffix = parse_file_suffix(path_input, initial_segments); + Url::from_file_parts(&host, host_type, &suffix) + })()) +} + +#[inline] +fn might_be_explicit_file(input: &str) -> bool { + input + .as_bytes() + .first() + .is_some_and(|byte| matches!(byte, b'f' | b'F' | 0x00..=0x20 | 0x7f)) +} + +fn resolve_file_reference(input: &str, base: &Url) -> Option> { + if base.scheme_type != SchemeType::File || has_url_scheme(input) { + return None; + } + let mut explicit = String::with_capacity(5 + input.len()); + explicit.push_str("file:"); + explicit.push_str(input); + parse_explicit_file(&explicit, Some(base)) +} + +fn resolve_normalized_file_relative(input: &str, base: &Url) -> Option> { + if base.scheme_type != SchemeType::File + || input.is_empty() + || input.starts_with(['/', '\\', '?', '#']) + || has_url_scheme(input) + || input + .as_bytes() + .iter() + .any(|byte| matches!(byte, b'\t' | b'\n' | b'\r')) + || input + .chars() + .next() + .is_some_and(|character| character <= '\u{20}') + || input + .chars() + .next_back() + .is_some_and(|character| character <= '\u{20}') + { + return None; + } + let (path, query, fragment) = split_path_query_fragment(input); + if starts_with_windows_drive_segment(path.as_bytes()) + || path + .split('/') + .any(|segment| is_single_dot_segment(segment) || is_double_dot_segment(segment)) + { + return None; + } + + Some((|| { + let prefix_end = base.components.pathname_start as usize; + let parent_end = base.pathname().rfind('/').map_or(0, |index| index + 1); + let mut buffer = String::with_capacity(prefix_end + parent_end + input.len() + 8); + buffer.push_str(&base.buffer[..prefix_end]); + buffer.push_str(&base.pathname()[..parent_end]); + append_percent_encoded(&mut buffer, path, PATH_ENCODE_SET); + let search_start = query + .map(|query| { + let start = to_u32(buffer.len())?; + buffer.push('?'); + append_percent_encoded(&mut buffer, query, SPECIAL_QUERY_ENCODE_SET); + Ok(start) + }) + .transpose()?; + let hash_start = fragment + .map(|fragment| { + let start = to_u32(buffer.len())?; + buffer.push('#'); + append_percent_encoded(&mut buffer, fragment, FRAGMENT_ENCODE_SET); + Ok(start) + }) + .transpose()?; + check_normalized_length(buffer.len())?; + Ok(Url { + components: Components::new( + base.components.protocol_end, + base.components.username_end, + base.components.host_start, + base.components.host_end, + base.components.port(), + base.components.pathname_start, + search_start, + hash_start, + ), + buffer, + scheme_type: base.scheme_type, + host_type: base.host_type, + flags: base.flags, + }) + })()) +} + +fn has_url_scheme(input: &str) -> bool { + if starts_with_windows_drive_segment(path_only(input).as_bytes()) { + return false; + } + let mut bytes = input.bytes(); + if !bytes.next().is_some_and(|byte| byte.is_ascii_alphabetic()) { + return false; + } + for byte in bytes { + match byte { + b':' => return true, + b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'+' | b'-' | b'.' => {} + _ => return false, + } + } + false +} + +fn parse_file_suffix(input: &str, mut segments: Vec) -> String { + let (path, query, fragment) = split_path_query_fragment(input); + let mut suffix = String::with_capacity( + segments + .iter() + .map(|segment| segment.len() + 1) + .sum::() + + input.len() + + 1, + ); + for segment in segments.drain(..) { + suffix.push('/'); + suffix.push_str(&segment); + } + let mut input_segments = path.split(['/', '\\']).peekable(); + while let Some(segment) = input_segments.next() { + if is_single_dot_segment(segment) { + if input_segments.peek().is_none() && !suffix.ends_with('/') { + suffix.push('/'); + } + continue; + } + if is_double_dot_segment(segment) { + shorten_file_suffix(&mut suffix); + if input_segments.peek().is_none() && !suffix.ends_with('/') { + suffix.push('/'); + } + continue; + } + + suffix.push('/'); + if suffix.len() == 1 && is_windows_drive_letter(segment.as_bytes()) { + suffix.push(char::from(segment.as_bytes()[0])); + suffix.push(':'); + } else { + append_percent_encoded(&mut suffix, segment, PATH_ENCODE_SET); + } + } + if suffix.is_empty() { + suffix.push('/'); + } + append_query_and_fragment(&mut suffix, query, fragment); + suffix +} + +fn shorten_file_suffix(suffix: &mut String) { + if suffix.is_empty() || suffix == "/" { + return; + } + let end = suffix + .len() + .saturating_sub(usize::from(suffix.ends_with('/'))); + if end == 3 + && suffix.as_bytes()[0] == b'/' + && suffix.as_bytes()[1].is_ascii_alphabetic() + && suffix.as_bytes()[2] == b':' + { + return; + } + let parent_end = suffix.as_bytes()[..end] + .iter() + .rposition(|byte| *byte == b'/') + .unwrap_or(0); + suffix.truncate(parent_end); +} + +fn parse_non_special_path(input: &str) -> String { + let input = input.strip_prefix('/').unwrap_or(input); + let mut segments = Vec::new(); + let mut input_segments = input.split('/').peekable(); + while let Some(segment) = input_segments.next() { + if is_single_dot_segment(segment) { + continue; + } + if is_double_dot_segment(segment) { + segments.pop(); + if input_segments.peek().is_none() { + segments.push(String::new()); + } + continue; + } + segments.push(String::from(segment)); + } + let mut pathname = String::new(); + for segment in segments { + pathname.push('/'); + pathname.push_str(&segment); + } + if pathname.is_empty() { + pathname.push('/'); + } + pathname +} + +fn suffix_with_preserved_file_path(input: &str, base: &Url) -> String { + let (_, query, fragment) = split_path_query_fragment(input); + let mut suffix = String::from(base.pathname()); + if input.is_empty() { + if let Some(start) = base.components.search_start() { + let end = base + .components + .hash_start() + .map_or(base.buffer.len(), |offset| offset as usize); + suffix.push_str(&base.buffer[start as usize..end]); + } + } else if input.starts_with('#') { + if let Some(start) = base.components.search_start() { + let end = base + .components + .hash_start() + .map_or(base.buffer.len(), |offset| offset as usize); + suffix.push_str(&base.buffer[start as usize..end]); + } + } else if let Some(query) = query { + suffix.push('?'); + append_percent_encoded(&mut suffix, query, SPECIAL_QUERY_ENCODE_SET); + } + if let Some(fragment) = fragment { + suffix.push('#'); + append_percent_encoded(&mut suffix, fragment, FRAGMENT_ENCODE_SET); + } + suffix +} + +fn append_query_and_fragment(suffix: &mut String, query: Option<&str>, fragment: Option<&str>) { + if let Some(query) = query { + suffix.push('?'); + append_percent_encoded(suffix, query, SPECIAL_QUERY_ENCODE_SET); + } + if let Some(fragment) = fragment { + suffix.push('#'); + append_percent_encoded(suffix, fragment, FRAGMENT_ENCODE_SET); + } +} + +fn split_path_query_fragment(input: &str) -> (&str, Option<&str>, Option<&str>) { + let (before_fragment, fragment) = input.find('#').map_or((input, None), |index| { + (&input[..index], Some(&input[index + 1..])) + }); + let (path, query) = before_fragment + .find('?') + .map_or((before_fragment, None), |index| { + ( + &before_fragment[..index], + Some(&before_fragment[index + 1..]), + ) + }); + (path, query, fragment) +} + +fn path_only(input: &str) -> &str { + split_path_query_fragment(input).0 +} + +fn file_path_segments(pathname: &str) -> Vec { + pathname + .strip_prefix('/') + .unwrap_or(pathname) + .split('/') + .map(String::from) + .collect() +} + +fn shorten_file_path(segments: &mut Vec) { + if segments.len() == 1 && is_windows_drive_letter(segments[0].as_bytes()) { + return; + } + segments.pop(); +} + +fn starts_with_windows_drive_segment(input: &[u8]) -> bool { + input.len() >= 2 + && input[0].is_ascii_alphabetic() + && matches!(input[1], b':' | b'|') + && (input.len() == 2 || matches!(input[2], b'/' | b'\\' | b'?' | b'#')) +} + +fn is_single_dot_segment(segment: &str) -> bool { + segment == "." || segment.eq_ignore_ascii_case("%2e") +} + +fn is_double_dot_segment(segment: &str) -> bool { + segment == ".." + || segment.eq_ignore_ascii_case(".%2e") + || segment.eq_ignore_ascii_case("%2e.") + || segment.eq_ignore_ascii_case("%2e%2e") +} + +fn parse_absolute_file_with_drive_host(input: &str) -> Option> { + let input = input.trim_matches(|character: char| character <= '\u{20}'); + let (scheme, after_scheme) = input.split_at_checked(5)?; + if !scheme.eq_ignore_ascii_case("file:") { + return None; + } + let bytes = after_scheme.as_bytes(); + if bytes.len() < 3 || !matches!(bytes[0], b'/' | b'\\') || !matches!(bytes[1], b'/' | b'\\') { + return None; + } + + let authority = &after_scheme[2..]; + let host_end = authority + .bytes() + .position(|byte| matches!(byte, b'/' | b'\\' | b'?' | b'#')) + .unwrap_or(authority.len()); + let raw_host = &authority[..host_end]; + let suffix = &authority[host_end..]; + let path = suffix.as_bytes(); + if raw_host.is_empty() + || raw_host.eq_ignore_ascii_case("localhost") + || is_windows_drive_letter(raw_host.as_bytes()) + || path.len() < 3 + || !matches!(path[0], b'/' | b'\\') + || !path[1].is_ascii_alphabetic() + || !matches!(path[2], b':' | b'|') + || (path.len() > 3 && !matches!(path[3], b'/' | b'\\' | b'?' | b'#')) + { + return None; + } + + Some((|| { + let (host, host_type) = normalize_file_host(raw_host)?; + let dummy = format!("file://{suffix}"); + let parsed = Url::parse_with_url_base(&dummy, None)?; + Url::from_file_parts(&host, host_type, parsed.pathname_and_later()) + })()) +} + +fn resolve_custom_file_base(input: &str, base: &Url) -> Option> { + if base.scheme_type != SchemeType::File + || base.hostname().is_empty() + || !starts_with_windows_drive_path(base.pathname().as_bytes()) + { + return None; + } + + if let Some(absolute) = parse_absolute_file_with_drive_host(input) { + return Some(absolute); + } + + Some((|| { + let dummy_base = Url::from_file_parts("", HostType::Domain, base.pathname_and_later())?; + let parsed = Url::parse_with_url_base(input, Some(&dummy_base))?; + + if parsed.scheme_type != SchemeType::File || input_has_authority(input) { + return Ok(parsed); + } + + let suffix = if input_path(input) == "/" { + let root = &base.pathname()[..4]; + let later_start = parsed + .components + .search_start() + .or(parsed.components.hash_start()) + .map_or(parsed.buffer.len(), |offset| offset as usize); + format!("{}{}", root, &parsed.buffer[later_start..]) + } else { + String::from(parsed.pathname_and_later()) + }; + Url::from_file_parts(base.hostname(), base.host_type, &suffix) + })()) +} + +fn normalize_file_host(raw_host: &str) -> Result<(String, HostType), ParseError> { + if raw_host.eq_ignore_ascii_case("localhost") { + return Ok((String::new(), HostType::Domain)); + } + if raw_host.eq_ignore_ascii_case("xn--") { + return Ok((raw_host.to_ascii_lowercase(), HostType::Domain)); + } + let (normalized, host_type) = fast_path::normalize_special_host(raw_host) + .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidUrl))?; + if normalized.eq_ignore_ascii_case("localhost") { + return Ok((String::new(), HostType::Domain)); + } + Ok((normalized, host_type)) +} + +fn starts_with_windows_drive_path(path: &[u8]) -> bool { + path.len() >= 3 + && path[0] == b'/' + && path[1].is_ascii_alphabetic() + && path[2] == b':' + && (path.len() == 3 || path[3] == b'/') +} + +fn is_windows_drive_letter(input: &[u8]) -> bool { + input.len() == 2 && input[0].is_ascii_alphabetic() && matches!(input[1], b':' | b'|') +} + +fn input_has_authority(input: &str) -> bool { + let input = input.trim_start_matches(|character: char| character <= '\u{20}'); + let after_scheme = input + .get(..5) + .filter(|scheme| scheme.eq_ignore_ascii_case("file:")) + .map_or(input, |_| &input[5..]); + let bytes = after_scheme.as_bytes(); + bytes.len() >= 2 && matches!(bytes[0], b'/' | b'\\') && matches!(bytes[1], b'/' | b'\\') +} + +fn input_path(input: &str) -> &str { + let end = memchr2(b'?', b'#', input.as_bytes()).unwrap_or(input.len()); + &input[..end] +} + +#[cfg(test)] +mod tests { + use super::{ParseErrorKind, Url, get_max_input_length, set_max_input_length}; + use crate::{HostType, SchemeType}; + use std::sync::Mutex; + + static TEST_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn components_are_borrowed_from_one_buffer() { + let _guard = TEST_LOCK.lock().unwrap(); + let url = Url::parse_with_url_base("https://user:pass@Example.com:8443/a/b?q=1#frag", None) + .unwrap(); + assert_eq!( + url.href(), + "https://user:pass@example.com:8443/a/b?q=1#frag" + ); + assert_eq!(url.protocol(), "https:"); + assert_eq!(url.scheme(), "https"); + assert_eq!(url.username(), "user"); + assert_eq!(url.password(), "pass"); + assert_eq!(url.host(), "example.com:8443"); + assert_eq!(url.hostname(), "example.com"); + assert_eq!(url.port(), "8443"); + assert_eq!(url.pathname(), "/a/b"); + assert_eq!(url.search(), "?q=1"); + assert_eq!(url.hash(), "#frag"); + assert_eq!(url.scheme_type(), SchemeType::Https); + assert_eq!(url.host_type(), HostType::Domain); + assert!(url.validate()); + } + + #[test] + fn resolves_relative_urls() { + let _guard = TEST_LOCK.lock().unwrap(); + let base = Url::parse_with_url_base("https://example.com/a/b/", None).unwrap(); + let url = Url::parse_with_url_base("../c?q", Some(&base)).unwrap(); + assert_eq!(url.href(), "https://example.com/a/c?q"); + } + + #[test] + fn handles_ip_and_opaque_urls() { + let _guard = TEST_LOCK.lock().unwrap(); + let ipv4 = Url::parse_with_url_base("http://0x7f.1/", None).unwrap(); + assert_eq!(ipv4.hostname(), "127.0.0.1"); + assert_eq!(ipv4.host_type(), HostType::IPV4); + + let ipv6 = Url::parse_with_url_base("http://[2001:db8::1]/", None).unwrap(); + assert_eq!(ipv6.host_type(), HostType::IPV6); + + let opaque = Url::parse_with_url_base("mailto:user@example.com", None).unwrap(); + assert!(opaque.has_opaque_path()); + assert_eq!(opaque.pathname(), "user@example.com"); + assert_eq!(opaque.hostname(), ""); + } + + #[test] + fn setters_are_transactional() { + let _guard = TEST_LOCK.lock().unwrap(); + let mut url = + Url::parse_with_url_base("https://user:pass@example.com/a?q#f", None).unwrap(); + url.set_host(Some("example.org:8080")).unwrap(); + assert_eq!(url.href(), "https://user:pass@example.org:8080/a?q#f"); + url.set_pathname(Some("/x y")).unwrap(); + url.set_search(Some("?a=b c")); + url.set_hash(Some("#snow ☃")); + assert_eq!( + url.href(), + "https://user:pass@example.org:8080/x%20y?a=b%20c#snow%20%E2%98%83" + ); + + let before = url.clone(); + assert!(url.set_port(Some("99999")).is_err()); + assert_eq!(url, before); + } + + #[cfg(all(feature = "std", unix))] + #[test] + fn converts_file_paths_without_a_url_backend() { + assert_eq!( + super::href_from_file("/tmp/a%20b\\c").unwrap(), + "file:///tmp/a%2520b%5Cc" + ); + assert!(super::href_from_file("relative/path").is_err()); + } + + #[test] + fn enforces_normalized_length() { + let _guard = TEST_LOCK.lock().unwrap(); + let old = get_max_input_length(); + set_max_input_length(19); + let error = Url::parse_with_url_base("https://example.com", None).unwrap_err(); + assert_eq!(error.kind(), ParseErrorKind::TooLong); + assert!(!Url::can_parse("https://example.com", None)); + set_max_input_length(old); + } +} diff --git a/src/url_search_params.rs b/src/url_search_params.rs deleted file mode 100644 index 40e33b2..0000000 --- a/src/url_search_params.rs +++ /dev/null @@ -1,499 +0,0 @@ -use crate::{ParseUrlError, ffi}; - -#[derive(Hash)] -pub struct UrlSearchParams(*mut ffi::ada_url_search_params); - -impl Drop for UrlSearchParams { - fn drop(&mut self) { - unsafe { ffi::ada_free_search_params(self.0) } - } -} - -impl UrlSearchParams { - /// Parses an return a UrlSearchParams struct. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// assert_eq!(params.get("a"), Some("1")); - /// assert_eq!(params.get("b"), Some("2")); - /// ``` - pub fn parse(input: Input) -> Result> - where - Input: AsRef, - { - Ok(Self(unsafe { - ffi::ada_parse_search_params(input.as_ref().as_ptr().cast(), input.as_ref().len()) - })) - } - - /// Returns the unique keys in a UrlSearchParams. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// assert_eq!(params.len(), 2); - /// let keys = params.keys().into_iter(); - /// assert_eq!(keys.count(), params.len()); - /// ``` - pub fn len(&self) -> usize { - unsafe { ffi::ada_search_params_size(self.0) } - } - - /// Returns true if no entries exist in the UrlSearchParams. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Sorts the keys of the UrlSearchParams struct. - pub fn sort(&mut self) { - unsafe { ffi::ada_search_params_sort(self.0) } - } - - /// Appends a key/value to the UrlSearchParams struct. - pub fn append(&mut self, key: &str, value: &str) { - unsafe { - ffi::ada_search_params_append( - self.0, - key.as_ptr().cast(), - key.len(), - value.as_ptr().cast(), - value.len(), - ) - } - } - - /// Removes all pre-existing keys from the UrlSearchParams struct - /// and appends the new key/value. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let mut params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// params.set("a", "3"); - /// assert_eq!(params.get("a"), Some("3")); - /// ``` - pub fn set(&mut self, key: &str, value: &str) { - unsafe { - ffi::ada_search_params_set( - self.0, - key.as_ptr().cast(), - key.len(), - value.as_ptr().cast(), - value.len(), - ) - } - } - - /// Removes a key from the UrlSearchParams struct. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let mut params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// params.remove_key("a"); - /// assert_eq!(params.get("a"), None); - /// ``` - pub fn remove_key(&mut self, key: &str) { - unsafe { ffi::ada_search_params_remove(self.0, key.as_ptr().cast(), key.len()) } - } - - /// Removes a key with a value from the UrlSearchParams struct. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let mut params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// params.remove("a", "1"); - /// assert_eq!(params.get("a"), None); - /// ``` - pub fn remove(&mut self, key: &str, value: &str) { - unsafe { - ffi::ada_search_params_remove_value( - self.0, - key.as_ptr().cast(), - key.len(), - value.as_ptr().cast(), - value.len(), - ) - } - } - - /// Returns whether the [`UrlSearchParams`] contains the `key`. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// assert_eq!(params.contains_key("a"), true); - /// ``` - pub fn contains_key(&self, key: &str) -> bool { - unsafe { ffi::ada_search_params_has(self.0, key.as_ptr().cast(), key.len()) } - } - - /// Returns whether the [`UrlSearchParams`] contains the `key` with the `value`. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// assert_eq!(params.contains("a", "1"), true); - /// ``` - pub fn contains(&self, key: &str, value: &str) -> bool { - unsafe { - ffi::ada_search_params_has_value( - self.0, - key.as_ptr().cast(), - key.len(), - value.as_ptr().cast(), - value.len(), - ) - } - } - - /// Returns the value of the key. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// assert_eq!(params.get("a"), Some("1")); - /// assert_eq!(params.get("c"), None); - /// ``` - pub fn get(&self, key: &str) -> Option<&str> { - unsafe { - let out = ffi::ada_search_params_get(self.0, key.as_ptr().cast(), key.len()); - - if out.data.is_null() { - return None; - } - Some(out.as_str()) - } - } - - /// Returns all values of the key. - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&a=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let pairs = params.get_all("a"); - /// assert_eq!(pairs.len(), 2); - /// ``` - pub fn get_all(&self, key: &str) -> UrlSearchParamsEntry<'_> { - unsafe { - let strings = ffi::ada_search_params_get_all(self.0, key.as_ptr().cast(), key.len()); - let size = ffi::ada_strings_size(strings); - UrlSearchParamsEntry::new(strings, size) - } - } - - /// Returns all keys as an iterator - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let mut keys = params.keys(); - /// assert!(keys.next().is_some()); - pub fn keys(&self) -> UrlSearchParamsKeyIterator<'_> { - let iterator = unsafe { ffi::ada_search_params_get_keys(self.0) }; - UrlSearchParamsKeyIterator::new(iterator) - } - - /// Returns all values as an iterator - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let mut values = params.values(); - /// assert!(values.next().is_some()); - pub fn values(&self) -> UrlSearchParamsValueIterator<'_> { - let iterator = unsafe { ffi::ada_search_params_get_values(self.0) }; - UrlSearchParamsValueIterator::new(iterator) - } - - /// Returns all entries as an iterator - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let mut entries = params.entries(); - /// assert_eq!(entries.next(), Some(("a", "1"))); - /// ``` - pub fn entries(&self) -> UrlSearchParamsEntryIterator<'_> { - let iterator = unsafe { ffi::ada_search_params_get_entries(self.0) }; - UrlSearchParamsEntryIterator::new(iterator) - } -} - -#[cfg(feature = "std")] -impl core::str::FromStr for UrlSearchParams { - type Err = ParseUrlError>; - - fn from_str(s: &str) -> Result { - Self::parse(s).map_err(|ParseUrlError { input }| ParseUrlError { - input: input.into(), - }) - } -} - -/// Returns the stringified version of the UrlSearchParams struct. -/// -/// ``` -/// use ada_url::UrlSearchParams; -/// let params = UrlSearchParams::parse("a=1&b=2") -/// .expect("String should have been able to be parsed into an UrlSearchParams."); -/// assert_eq!(params.to_string(), "a=1&b=2"); -/// ``` -impl core::fmt::Display for UrlSearchParams { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let str = unsafe { ffi::ada_search_params_to_string(self.0) }; - f.write_str(str.as_ref()) - } -} - -#[cfg(feature = "std")] -impl Extend<(Input, Input)> for UrlSearchParams -where - Input: AsRef, -{ - /// Supports extending UrlSearchParams through an iterator. - /// - ///``` - /// use ada_url::UrlSearchParams; - /// let mut params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// assert_eq!(params.len(), 2); - /// params.extend([("foo", "bar")]); - /// assert_eq!(params.len(), 3); - /// ``` - fn extend>(&mut self, iter: T) { - for item in iter { - self.append(item.0.as_ref(), item.1.as_ref()); - } - } -} - -#[cfg(feature = "std")] -impl FromIterator<(Input, Input)> for UrlSearchParams -where - Input: AsRef, -{ - /// Converts an iterator to UrlSearchParams - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let iterator = std::iter::repeat(("hello", "world")).take(5); - /// let params = UrlSearchParams::from_iter(iterator); - /// assert_eq!(params.len(), 5); - /// ``` - fn from_iter>(iter: T) -> Self { - let mut params = UrlSearchParams::parse("") - .expect("Should be able to parse empty string. This is likely due to a bug"); - for item in iter { - params.append(item.0.as_ref(), item.1.as_ref()); - } - params - } -} - -#[derive(Hash)] -pub struct UrlSearchParamsKeyIterator<'a> { - iterator: *mut ffi::ada_url_search_params_keys_iter, - _phantom: core::marker::PhantomData<&'a str>, -} - -impl Drop for UrlSearchParamsKeyIterator<'_> { - fn drop(&mut self) { - unsafe { ffi::ada_free_search_params_keys_iter(self.iterator) } - } -} - -impl<'a> Iterator for UrlSearchParamsKeyIterator<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - let has_next = unsafe { ffi::ada_search_params_keys_iter_has_next(self.iterator) }; - if has_next { - let string = unsafe { ffi::ada_search_params_keys_iter_next(self.iterator) }; - Some(string.as_str()) - } else { - None - } - } -} - -#[derive(Hash)] -pub struct UrlSearchParamsValueIterator<'a> { - iterator: *mut ffi::ada_url_search_params_values_iter, - _phantom: core::marker::PhantomData<&'a str>, -} - -impl<'a> UrlSearchParamsKeyIterator<'a> { - fn new(iterator: *mut ffi::ada_url_search_params_keys_iter) -> UrlSearchParamsKeyIterator<'a> { - UrlSearchParamsKeyIterator { - iterator, - _phantom: core::marker::PhantomData, - } - } -} - -impl Drop for UrlSearchParamsValueIterator<'_> { - fn drop(&mut self) { - unsafe { ffi::ada_free_search_params_values_iter(self.iterator) } - } -} - -impl<'a> Iterator for UrlSearchParamsValueIterator<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - let has_next = unsafe { ffi::ada_search_params_values_iter_has_next(self.iterator) }; - if has_next { - let string = unsafe { ffi::ada_search_params_values_iter_next(self.iterator) }; - Some(string.as_str()) - } else { - None - } - } -} - -impl<'a> UrlSearchParamsValueIterator<'a> { - fn new( - iterator: *mut ffi::ada_url_search_params_values_iter, - ) -> UrlSearchParamsValueIterator<'a> { - UrlSearchParamsValueIterator { - iterator, - _phantom: core::marker::PhantomData, - } - } -} - -pub struct UrlSearchParamsEntry<'a> { - strings: *mut ffi::ada_strings, - size: usize, - _phantom: core::marker::PhantomData<&'a str>, -} - -impl<'a> UrlSearchParamsEntry<'a> { - fn new(strings: *mut ffi::ada_strings, size: usize) -> UrlSearchParamsEntry<'a> { - UrlSearchParamsEntry { - strings, - size, - _phantom: core::marker::PhantomData, - } - } - - /// Returns whether the key value pair is empty or not - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let pairs = params.get_all("a"); - /// assert_eq!(pairs.is_empty(), false); - /// ``` - pub fn is_empty(&self) -> bool { - self.size == 0 - } - - /// Returns the size of the key value pairs - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&b=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let pairs = params.get_all("a"); - /// assert_eq!(pairs.len(), 1); - /// ``` - pub fn len(&self) -> usize { - self.size - } - - /// Get an entry by index - /// - /// ``` - /// use ada_url::UrlSearchParams; - /// let params = UrlSearchParams::parse("a=1&a=2") - /// .expect("String should have been able to be parsed into an UrlSearchParams."); - /// let pairs = params.get_all("a"); - /// assert_eq!(pairs.len(), 2); - /// assert_eq!(pairs.get(0), Some("1")); - /// assert_eq!(pairs.get(1), Some("2")); - /// assert_eq!(pairs.get(2), None); - /// assert_eq!(pairs.get(55), None); - /// ``` - pub fn get(&self, index: usize) -> Option<&str> { - if self.size == 0 || index > self.size - 1 { - return None; - } - - unsafe { - let string = ffi::ada_strings_get(self.strings, index); - Some(string.as_str()) - } - } -} - -impl Drop for UrlSearchParamsEntry<'_> { - fn drop(&mut self) { - unsafe { ffi::ada_free_strings(self.strings) } - } -} - -#[cfg(feature = "std")] -impl<'a> From> for Vec<&'a str> { - fn from(val: UrlSearchParamsEntry<'a>) -> Self { - let mut vec = Vec::with_capacity(val.size); - unsafe { - for index in 0..val.size { - let string = ffi::ada_strings_get(val.strings, index); - let slice = core::slice::from_raw_parts(string.data.cast(), string.length); - vec.push(core::str::from_utf8_unchecked(slice)); - } - } - vec - } -} - -#[derive(Hash)] -pub struct UrlSearchParamsEntryIterator<'a> { - iterator: *mut ffi::ada_url_search_params_entries_iter, - _phantom: core::marker::PhantomData<&'a str>, -} - -impl<'a> UrlSearchParamsEntryIterator<'a> { - fn new( - iterator: *mut ffi::ada_url_search_params_entries_iter, - ) -> UrlSearchParamsEntryIterator<'a> { - UrlSearchParamsEntryIterator { - iterator, - _phantom: core::marker::PhantomData, - } - } -} - -impl Drop for UrlSearchParamsEntryIterator<'_> { - fn drop(&mut self) { - unsafe { ffi::ada_free_search_params_entries_iter(self.iterator) } - } -} - -impl<'a> Iterator for UrlSearchParamsEntryIterator<'a> { - type Item = (&'a str, &'a str); - - fn next(&mut self) -> Option { - let has_next = unsafe { ffi::ada_search_params_entries_iter_has_next(self.iterator) }; - if has_next { - let pair = unsafe { ffi::ada_search_params_entries_iter_next(self.iterator) }; - Some((pair.key.as_str(), pair.value.as_str())) - } else { - None - } - } -} diff --git a/tests/unsupported.rs b/tests/unsupported.rs deleted file mode 100644 index cca1f7e..0000000 --- a/tests/unsupported.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![cfg(feature = "std")] - -#[test] -#[ignore = "href_from_file is not exposed by the Rust public API"] -fn from_file_tests_basics() {} - -#[test] -#[ignore = "user choice: keep IdnaTestV2 large case ignored"] -fn wpt_url_tests_idna_test_v2_to_ascii() {} - -#[test] -#[ignore = "percent_encode internals are not exposed by the Rust public API"] -fn wpt_url_tests_percent_encoding() {} - -#[test] -#[ignore = "has_valid_domain is not exposed by the Rust public API"] -fn wpt_url_tests_verify_dns_length() {} - -#[test] -#[ignore = "URLPattern is not exposed by the Rust public API"] -fn basic_tests_test_workerd_issue_5144_4() {} - -#[test] -#[ignore = "URLPattern suite is out of current wrapper scope"] -fn wpt_urlpattern_tests_all() {} - -#[test] -#[ignore = "ada_search_params_reset is not exposed by the Rust public API"] -fn ada_c_ada_search_params_reset() {} - -#[test] -#[ignore = "ada_get_version APIs are not exposed by the Rust public API"] -fn ada_c_ada_get_version() {} diff --git a/tests/wpt/UPSTREAM b/tests/wpt/UPSTREAM new file mode 100644 index 0000000..7d2372c --- /dev/null +++ b/tests/wpt/UPSTREAM @@ -0,0 +1,14 @@ +Repository: https://github.com/ada-url/ada +Commit: 30f3f3020c5a979b62f90dc9c37fd45de3cc84d7 +Files: +- tests/wpt/urltestdata.json +- tests/wpt/ada_extra_urltestdata.json +- tests/wpt/ada_long_urltestdata.json +- tests/wpt/setters_tests.json +- tests/wpt/ada_extra_setters_tests.json +- tests/wpt/toascii.json +- tests/wpt/IdnaTestV2.json +- tests/wpt/IdnaTestV2-removed.json +- tests/wpt/percent-encoding.json +- tests/wpt/urltestdata-javascript-only.json +- tests/wpt/verifydnslength_tests.json diff --git a/tests/wpt/urlpattern-compare-test-data.json b/tests/wpt/urlpattern-compare-test-data.json deleted file mode 100644 index 0043ac0..0000000 --- a/tests/wpt/urlpattern-compare-test-data.json +++ /dev/null @@ -1,152 +0,0 @@ -[ - { - "component": "pathname", - "left": { "pathname": "/foo/a" }, - "right": { "pathname": "/foo/b" }, - "expected": -1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/b" }, - "right": { "pathname": "/foo/bar" }, - "expected": -1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/bar" }, - "right": { "pathname": "/foo/:bar" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/" }, - "right": { "pathname": "/foo/:bar" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/:bar" }, - "right": { "pathname": "/foo/*" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/{bar}" }, - "right": { "pathname": "/foo/(bar)" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/{bar}" }, - "right": { "pathname": "/foo/{bar}+" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/{bar}+" }, - "right": { "pathname": "/foo/{bar}?" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/{bar}?" }, - "right": { "pathname": "/foo/{bar}*" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/(123)" }, - "right": { "pathname": "/foo/(12)" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/:b" }, - "right": { "pathname": "/foo/:a" }, - "expected": 0 - }, - { - "component": "pathname", - "left": { "pathname": "*/foo" }, - "right": { "pathname": "*" }, - "expected": 1 - }, - { - "component": "port", - "left": { "port": "9" }, - "right": { "port": "100" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "foo/:bar?/baz" }, - "right": { "pathname": "foo/{:bar}?/baz" }, - "expected": -1 - }, - { - "component": "pathname", - "left": { "pathname": "foo/:bar?/baz" }, - "right": { "pathname": "foo{/:bar}?/baz" }, - "expected": 0 - }, - { - "component": "pathname", - "left": { "pathname": "foo/:bar?/baz" }, - "right": { "pathname": "fo{o/:bar}?/baz" }, - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "foo/:bar?/baz" }, - "right": { "pathname": "foo{/:bar/}?baz" }, - "expected": -1 - }, - { - "component": "pathname", - "left": "https://a.example.com/b?a", - "right": "https://b.example.com/a?b", - "expected": 1 - }, - { - "component": "pathname", - "left": { "pathname": "/foo/{bar}/baz" }, - "right": { "pathname": "/foo/bar/baz" }, - "expected": 0 - }, - { - "component": "protocol", - "left": { "protocol": "a" }, - "right": { "protocol": "b" }, - "expected": -1 - }, - { - "component": "username", - "left": { "username": "a" }, - "right": { "username": "b" }, - "expected": -1 - }, - { - "component": "password", - "left": { "password": "a" }, - "right": { "password": "b" }, - "expected": -1 - }, - { - "component": "hostname", - "left": { "hostname": "a" }, - "right": { "hostname": "b" }, - "expected": -1 - }, - { - "component": "search", - "left": { "search": "a" }, - "right": { "search": "b" }, - "expected": -1 - }, - { - "component": "hash", - "left": { "hash": "a" }, - "right": { "hash": "b" }, - "expected": -1 - } -] diff --git a/tests/wpt/urlpattern-generate-test-data.json b/tests/wpt/urlpattern-generate-test-data.json deleted file mode 100644 index c118f0a..0000000 --- a/tests/wpt/urlpattern-generate-test-data.json +++ /dev/null @@ -1,116 +0,0 @@ -[ - { - "pattern": { "pathname": "/foo" }, - "component": "invalid", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/foo" }, - "component": "pathname", - "groups": {}, - "expected": "/foo" - }, - { - "pattern": { "pathname": "/:foo" }, - "component": "pathname", - "groups": { "foo": "bar" }, - "expected": "/bar" - }, - { - "pattern": { "pathname": "/:foo" }, - "component": "pathname", - "groups": { "foo": "🍅" }, - "expected": "/%F0%9F%8D%85" - }, - { - "pattern": { "hostname": "{:foo}.example.com" }, - "component": "hostname", - "groups": { "foo": "🍅" }, - "expected": "xn--fi8h.example.com" - }, - { - "pattern": { "pathname": "/:foo" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/foo/:bar" }, - "component": "pathname", - "groups": { "bar": "baz" }, - "expected": "/foo/baz" - }, - { - "pattern": { "pathname": "/foo:bar" }, - "component": "pathname", - "groups": { "bar": "baz" }, - "expected": "/foobaz" - }, - { - "pattern": { "pathname": "/:foo/:bar" }, - "component": "pathname", - "groups": { "foo": "baz", "bar": "qux" }, - "expected": "/baz/qux" - }, - { - "pattern": "https://example.com/:foo", - "component": "pathname", - "groups": { "foo": " " }, - "expected": "/%20" - }, - { - "pattern": "original-scheme://example.com/:foo", - "component": "pathname", - "groups": { "foo": " " }, - "expected": "/ " - }, - { - "pattern": { "pathname": "/:foo" }, - "component": "pathname", - "groups": { "foo": "bar/baz" }, - "expected": null - }, - { - "pattern": { "pathname": "*" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/{foo}+" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/{foo}?" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/{foo}*" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/(regexp)" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "pathname": "/([^\\/]+?)" }, - "component": "pathname", - "groups": {}, - "expected": null - }, - { - "pattern": { "port": "([^\\:]+?)" }, - "component": "port", - "groups": {}, - "expected": null - } -] diff --git a/tests/wpt/urlpatterntestdata.json b/tests/wpt/urlpatterntestdata.json deleted file mode 100644 index 272b51e..0000000 --- a/tests/wpt/urlpatterntestdata.json +++ /dev/null @@ -1,3181 +0,0 @@ -[ - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/ba" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/bar/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ "https://example.com/foo/bar" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": { "0": "https" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ "https://example.com/foo/bar/baz" ], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar" }], - "expected_match": { - "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], - "expected_match": { - "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": { "0": "https" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/bar/baz", - "baseURL": "https://example.com" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar", "search": "otherquery", - "hash": "otherhash" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "otherquery", "groups": { "0": "otherquery" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar", "search": "otherquery", - "hash": "otherhash" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "otherquery", "groups": { "0": "otherquery" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?otherquery#otherhash" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar", "search": "otherquery", - "hash": "otherhash" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "otherquery", "groups": { "0": "otherquery" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar" ], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar?otherquery#otherhash" ], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "otherquery", "groups": { "0": "otherquery" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar?query#hash" ], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hash": { "input": "hash", "groups": { "0": "hash" } }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "query", "groups": { "0": "query" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar/baz" ], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://other.com/foo/bar" ], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "http://other.com/foo/bar" ], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar/baz", - "baseURL": "https://example.com" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://other.com" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar", "baseURL": "http://example.com" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/([^\\/]+?)" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar" }], - "inputs": [{ "pathname": "/foo/index.html" }], - "expected_match": { - "pathname": { "input": "/foo/index.html", "groups": { "bar": "index.html" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar" }], - "inputs": [{ "pathname": "/foo/bar/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_obj": { - "pathname": "/foo/*" - }, - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_obj": { - "pathname": "/foo/*" - }, - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_obj": { - "pathname": "/foo/*" - }, - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)" }], - "inputs": [{ "pathname": "/foo" }], - "expected_obj": { - "pathname": "/foo/*" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/*" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar(.*)" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar(.*)" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar(.*)" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "bar": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar(.*)" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar?" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar?" }], - "inputs": [{ "pathname": "/foo" }], - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "/foo", "groups": { "bar": null } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar?" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar?" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar?" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar+" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar+" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar+" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar+" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar+" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar*" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar*" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar*" }], - "inputs": [{ "pathname": "/foo" }], - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "/foo", "groups": { "bar": null } } - } - }, - { - "pattern": [{ "pathname": "/foo/:bar*" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/:bar*" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)?" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_obj": { - "pathname": "/foo/*?" - }, - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*?" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)?" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_obj": { - "pathname": "/foo/*?" - }, - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*?" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)?" }], - "inputs": [{ "pathname": "/foo" }], - "expected_obj": { - "pathname": "/foo/*?" - }, - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "/foo", "groups": { "0": null } } - } - }, - { - "pattern": [{ "pathname": "/foo/*?" }], - "inputs": [{ "pathname": "/foo" }], - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "/foo", "groups": { "0": null } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)?" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_obj": { - "pathname": "/foo/*?" - }, - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*?" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)?" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_obj": { - "pathname": "/foo/*?" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/*?" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)?" }], - "inputs": [{ "pathname": "/fo" }], - "expected_obj": { - "pathname": "/foo/*?" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/*?" }], - "inputs": [{ "pathname": "/fo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)+" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_obj": { - "pathname": "/foo/*+" - }, - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*+" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)+" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_obj": { - "pathname": "/foo/*+" - }, - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*+" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)+" }], - "inputs": [{ "pathname": "/foo" }], - "expected_obj": { - "pathname": "/foo/*+" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/*+" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)+" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_obj": { - "pathname": "/foo/*+" - }, - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/*+" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)+" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_obj": { - "pathname": "/foo/*+" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/*+" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)+" }], - "inputs": [{ "pathname": "/fo" }], - "expected_obj": { - "pathname": "/foo/*+" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/*+" }], - "inputs": [{ "pathname": "/fo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)*" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_obj": { - "pathname": "/foo/**" - }, - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/**" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)*" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_obj": { - "pathname": "/foo/**" - }, - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/**" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": { - "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)*" }], - "inputs": [{ "pathname": "/foo" }], - "expected_obj": { - "pathname": "/foo/**" - }, - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "/foo", "groups": { "0": null } } - } - }, - { - "pattern": [{ "pathname": "/foo/**" }], - "inputs": [{ "pathname": "/foo" }], - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "/foo", "groups": { "0": null } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)*" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_obj": { - "pathname": "/foo/**" - }, - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/**" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": { - "pathname": { "input": "/foo/", "groups": { "0": "" } } - } - }, - { - "pattern": [{ "pathname": "/foo/(.*)*" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_obj": { - "pathname": "/foo/**" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/**" }], - "inputs": [{ "pathname": "/foobar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/(.*)*" }], - "inputs": [{ "pathname": "/fo" }], - "expected_obj": { - "pathname": "/foo/**" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/**" }], - "inputs": [{ "pathname": "/fo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_obj": { - "pathname": "/foo/bar" - }, - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_obj": { - "pathname": "/foo/bar" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}" }], - "inputs": [{ "pathname": "/foo" }], - "expected_obj": { - "pathname": "/foo/bar" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_obj": { - "pathname": "/foo/bar" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}?" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}?" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}?" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": { - "pathname": { "input": "/foo", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}?" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}+" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}+" }], - "inputs": [{ "pathname": "/foo/bar/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}+" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}+" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}+" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}*" }], - "inputs": [{ "pathname": "/foo/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}*" }], - "inputs": [{ "pathname": "/foo/bar/bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar/bar", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}*" }], - "inputs": [{ "pathname": "/foo/bar/baz" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo{/bar}*" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": { - "pathname": { "input": "/foo", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/foo{/bar}*" }], - "inputs": [{ "pathname": "/foo/" }], - "expected_match": null - }, - { - "pattern": [{ "protocol": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "username": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "password": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "pathname": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "search": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hash": "(café)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "protocol": ":café" }], - "inputs": [{ "protocol": "foo" }], - "expected_match": { - "protocol": { "input": "foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "username": ":café" }], - "inputs": [{ "username": "foo" }], - "expected_match": { - "username": { "input": "foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "password": ":café" }], - "inputs": [{ "password": "foo" }], - "expected_match": { - "password": { "input": "foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "hostname": ":café" }], - "inputs": [{ "hostname": "foo" }], - "expected_match": { - "hostname": { "input": "foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "pathname": "/:café" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": { - "pathname": { "input": "/foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "search": ":café" }], - "inputs": [{ "search": "foo" }], - "expected_match": { - "search": { "input": "foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "hash": ":café" }], - "inputs": [{ "hash": "foo" }], - "expected_match": { - "hash": { "input": "foo", "groups": { "café": "foo" } } - } - }, - { - "pattern": [{ "protocol": ":\u2118" }], - "inputs": [{ "protocol": "foo" }], - "expected_match": { - "protocol": { "input": "foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "username": ":\u2118" }], - "inputs": [{ "username": "foo" }], - "expected_match": { - "username": { "input": "foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "password": ":\u2118" }], - "inputs": [{ "password": "foo" }], - "expected_match": { - "password": { "input": "foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "hostname": ":\u2118" }], - "inputs": [{ "hostname": "foo" }], - "expected_match": { - "hostname": { "input": "foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "pathname": "/:\u2118" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": { - "pathname": { "input": "/foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "search": ":\u2118" }], - "inputs": [{ "search": "foo" }], - "expected_match": { - "search": { "input": "foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "hash": ":\u2118" }], - "inputs": [{ "hash": "foo" }], - "expected_match": { - "hash": { "input": "foo", "groups": { "\u2118": "foo" } } - } - }, - { - "pattern": [{ "protocol": ":\u3400" }], - "inputs": [{ "protocol": "foo" }], - "expected_match": { - "protocol": { "input": "foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "username": ":\u3400" }], - "inputs": [{ "username": "foo" }], - "expected_match": { - "username": { "input": "foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "password": ":\u3400" }], - "inputs": [{ "password": "foo" }], - "expected_match": { - "password": { "input": "foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "hostname": ":\u3400" }], - "inputs": [{ "hostname": "foo" }], - "expected_match": { - "hostname": { "input": "foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "pathname": "/:\u3400" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": { - "pathname": { "input": "/foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "search": ":\u3400" }], - "inputs": [{ "search": "foo" }], - "expected_match": { - "search": { "input": "foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "hash": ":\u3400" }], - "inputs": [{ "hash": "foo" }], - "expected_match": { - "hash": { "input": "foo", "groups": { "\u3400": "foo" } } - } - }, - { - "pattern": [{ "protocol": "(.*)" }], - "inputs": [{ "protocol" : "café" }], - "expected_obj": { - "protocol": "*" - }, - "expected_match": null - }, - { - "pattern": [{ "protocol": "(.*)" }], - "inputs": [{ "protocol": "cafe" }], - "expected_obj": { - "protocol": "*" - }, - "expected_match": { - "protocol": { "input": "cafe", "groups": { "0": "cafe" }} - } - }, - { - "pattern": [{ "protocol": "foo-bar" }], - "inputs": [{ "protocol": "foo-bar" }], - "expected_match": { - "protocol": { "input": "foo-bar", "groups": {} } - } - }, - { - "pattern": [{ "username": "caf%C3%A9" }], - "inputs": [{ "username" : "café" }], - "expected_match": { - "username": { "input": "caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "username": "café" }], - "inputs": [{ "username" : "café" }], - "expected_obj": { - "username": "caf%C3%A9" - }, - "expected_match": { - "username": { "input": "caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "username": "caf%c3%a9" }], - "inputs": [{ "username" : "café" }], - "expected_match": null - }, - { - "pattern": [{ "password": "caf%C3%A9" }], - "inputs": [{ "password" : "café" }], - "expected_match": { - "password": { "input": "caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "password": "café" }], - "inputs": [{ "password" : "café" }], - "expected_obj": { - "password": "caf%C3%A9" - }, - "expected_match": { - "password": { "input": "caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "password": "caf%c3%a9" }], - "inputs": [{ "password" : "café" }], - "expected_match": null - }, - { - "pattern": [{ "hostname": "xn--caf-dma.com" }], - "inputs": [{ "hostname" : "café.com" }], - "expected_match": { - "hostname": { "input": "xn--caf-dma.com", "groups": {}} - } - }, - { - "pattern": [{ "hostname": "café.com" }], - "inputs": [{ "hostname" : "café.com" }], - "expected_obj": { - "hostname": "xn--caf-dma.com" - }, - "expected_match": { - "hostname": { "input": "xn--caf-dma.com", "groups": {}} - } - }, - { - "pattern": ["http://\uD83D\uDEB2.com/"], - "inputs": ["http://\uD83D\uDEB2.com/"], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "http", - "hostname": "xn--h78h.com", - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {}}, - "hostname": { "input": "xn--h78h.com", "groups": {}}, - "pathname": { "input": "/", "groups": {}} - } - }, - { - "pattern": ["http://\uD83D \uDEB2"], - "expected_obj": "error" - }, - { - "pattern": [{"hostname":"\uD83D \uDEB2"}], - "expected_obj": "error" - }, - { - "pattern": [{"pathname":"\uD83D \uDEB2"}], - "inputs": [], - "expected_obj": { - "pathname": "%EF%BF%BD%20%EF%BF%BD" - }, - "expected_match": null - }, - { - "pattern": [{"pathname":":\uD83D \uDEB2"}], - "expected_obj": "error" - }, - { - "pattern": [{"pathname":":a\uDB40\uDD00b"}], - "inputs": [], - "expected_obj": { - "pathname": ":a\uDB40\uDD00b" - }, - "expected_match": null - }, - { - "pattern": [{"pathname":"test/:a\uD801\uDC50b"}], - "inputs": [{"pathname":"test/foo"}], - "expected_obj": { - "pathname": "test/:a\uD801\uDC50b" - }, - "expected_match": { - "pathname": { "input": "test/foo", "groups": { "a\uD801\uDC50b": "foo" }} - } - }, - { - "pattern": [{"pathname":":\uD83D\uDEB2"}], - "expected_obj": "error" - }, - { - "pattern": [{ "port": "" }], - "inputs": [{ "protocol": "http", "port": "80" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "protocol": { "input": "http", "groups": { "0": "http" }} - } - }, - { - "pattern": [{ "protocol": "http", "port": "80" }], - "inputs": [{ "protocol": "http", "port": "80" }], - "exactly_empty_components": [ "port" ], - "expected_match": { - "protocol": { "input": "http", "groups": {}} - } - }, - { - "pattern": [{ "protocol": "http", "port": "80{20}?" }], - "inputs": [{ "protocol": "http", "port": "80" }], - "expected_match": null - }, - { - "pattern": [{ "protocol": "http", "port": "80 " }], - "inputs": [{ "protocol": "http", "port": "80" }], - "expected_obj": { - "protocol": "http", - "port": "80" - }, - "expected_match": null - }, - { - "pattern": [{ "protocol": "http", "port": "100000" }], - "inputs": [{ "protocol": "http", "port": "100000" }], - "expected_obj": "error" - }, - { - "pattern": [{ "port": "80" }], - "inputs": [{ "protocol": "http", "port": "80" }], - "expected_match": null - }, - { - "pattern": [{ "protocol": "http{s}?", "port": "80" }], - "inputs": [{ "protocol": "http", "port": "80" }], - "expected_match": null - }, - { - "pattern": [{ "port": "80" }], - "inputs": [{ "port": "80" }], - "expected_match": { - "port": { "input": "80", "groups": {}} - } - }, - { - "pattern": [{ "port": "80" }], - "inputs": [{ "port": "8\t0" }], - "expected_match": { - "port": { "input": "80", "groups": {}} - } - }, - { - "pattern": [{ "port": "80" }], - "inputs": [{ "port": "80x" }], - "expected_match": { - "port": { "input": "80", "groups": {}} - } - }, - { - "pattern": [{ "port": "80" }], - "inputs": [{ "port": "80?x" }], - "expected_match": { - "port": { "input": "80", "groups": {}} - } - }, - { - "pattern": [{ "port": "80" }], - "inputs": [{ "port": "80\\x" }], - "expected_match": { - "port": { "input": "80", "groups": {}} - } - }, - { - "pattern": [{ "port": "(.*)" }], - "inputs": [{ "port": "invalid80" }], - "expected_obj": { - "port": "*" - }, - "expected_match": null - }, - { - "pattern": [{ "protocol": "https", "port": "443*" }], - "inputs": [{ "protocol": "https", "port": "4430" }], - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "port": { "input": "4430", "groups": { "0": "0" }} - } - }, - { - "pattern": [{ "protocol": "https", "port": "*443" }], - "inputs": [{ "protocol": "https", "port": "1443" }], - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "port": { "input": "1443", "groups": { "0": "1" }} - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/./bar" }], - "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/foo/baz" }], - "inputs": [{ "pathname": "/foo/bar/../baz" }], - "expected_match": { - "pathname": { "input": "/foo/baz", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/caf%C3%A9" }], - "inputs": [{ "pathname": "/café" }], - "expected_match": { - "pathname": { "input": "/caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/café" }], - "inputs": [{ "pathname": "/café" }], - "expected_obj": { - "pathname": "/caf%C3%A9" - }, - "expected_match": { - "pathname": { "input": "/caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/caf%c3%a9" }], - "inputs": [{ "pathname": "/café" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "foo/bar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], - "expected_match": { - "protocol": { "input": "https", "groups": { "0": "https" }}, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/foo/bar", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/foo/../bar" }], - "inputs": [{ "pathname": "/bar" }], - "expected_obj": { - "pathname": "/bar" - }, - "expected_match": { - "pathname": { "input": "/bar", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "./foo/bar", "baseURL": "https://example.com" }], - "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/foo/bar" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo/bar", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "", "baseURL": "https://example.com" }], - "inputs": [{ "pathname": "/", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "{/bar}", "baseURL": "https://example.com/foo/" }], - "inputs": [{ "pathname": "./bar", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/bar" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "\\/bar", "baseURL": "https://example.com/foo/" }], - "inputs": [{ "pathname": "./bar", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/bar" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "b", "baseURL": "https://example.com/foo/" }], - "inputs": [{ "pathname": "./b", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/foo/b" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo/b", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "foo/bar" }], - "inputs": [ "https://example.com/foo/bar" ], - "expected_match": null - }, - { - "pattern": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], - "inputs": [ "https://example.com/foo/bar" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/foo/bar" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo/bar", "groups": {}} - } - }, - { - "pattern": [{ "pathname": ":name.html", "baseURL": "https://example.com" }], - "inputs": [ "https://example.com/foo.html"] , - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/:name.html" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo.html", "groups": { "name": "foo" }} - } - }, - { - "pattern": [{ "search": "q=caf%C3%A9" }], - "inputs": [{ "search": "q=café" }], - "expected_match": { - "search": { "input": "q=caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "search": "q=café" }], - "inputs": [{ "search": "q=café" }], - "expected_obj": { - "search": "q=caf%C3%A9" - }, - "expected_match": { - "search": { "input": "q=caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "search": "q=caf%c3%a9" }], - "inputs": [{ "search": "q=café" }], - "expected_match": null - }, - { - "pattern": [{ "hash": "caf%C3%A9" }], - "inputs": [{ "hash": "café" }], - "expected_match": { - "hash": { "input": "caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "hash": "café" }], - "inputs": [{ "hash": "café" }], - "expected_obj": { - "hash": "caf%C3%A9" - }, - "expected_match": { - "hash": { "input": "caf%C3%A9", "groups": {}} - } - }, - { - "pattern": [{ "hash": "caf%c3%a9" }], - "inputs": [{ "hash": "café" }], - "expected_match": null - }, - { - "pattern": [{ "protocol": "about", "pathname": "(blank|sourcedoc)" }], - "inputs": [ "about:blank" ], - "expected_match": { - "protocol": { "input": "about", "groups": {}}, - "pathname": { "input": "blank", "groups": { "0": "blank" }} - } - }, - { - "pattern": [{ "protocol": "data", "pathname": ":number([0-9]+)" }], - "inputs": [ "data:8675309" ], - "expected_match": { - "protocol": { "input": "data", "groups": {}}, - "pathname": { "input": "8675309", "groups": { "number": "8675309" }} - } - }, - { - "pattern": [{ "pathname": "/(\\m)" }], - "expected_obj": "error" - }, - { - "pattern": [{ "pathname": "/foo!" }], - "inputs": [{ "pathname": "/foo!" }], - "expected_match": { - "pathname": { "input": "/foo!", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/foo\\:" }], - "inputs": [{ "pathname": "/foo:" }], - "expected_match": { - "pathname": { "input": "/foo:", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/foo\\{" }], - "inputs": [{ "pathname": "/foo{" }], - "expected_obj": { - "pathname": "/foo%7B" - }, - "expected_match": { - "pathname": { "input": "/foo%7B", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/foo\\(" }], - "inputs": [{ "pathname": "/foo(" }], - "expected_match": { - "pathname": { "input": "/foo(", "groups": {}} - } - }, - { - "pattern": [{ "protocol": "javascript", "pathname": "var x = 1;" }], - "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }], - "expected_match": { - "protocol": { "input": "javascript", "groups": {}}, - "pathname": { "input": "var x = 1;", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "var x = 1;" }], - "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }], - "expected_obj": { - "pathname": "var%20x%20=%201;" - }, - "expected_match": null - }, - { - "pattern": [{ "protocol": "javascript", "pathname": "var x = 1;" }], - "inputs": [{ "baseURL": "javascript:var x = 1;" }], - "expected_match": { - "protocol": { "input": "javascript", "groups": {}}, - "pathname": { "input": "var x = 1;", "groups": {}} - } - }, - { - "pattern": [{ "protocol": "(data|javascript)", "pathname": "var x = 1;" }], - "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }], - "expected_match": { - "protocol": { "input": "javascript", "groups": {"0": "javascript"}}, - "pathname": { "input": "var x = 1;", "groups": {}} - } - }, - { - "pattern": [{ "protocol": "(https|javascript)", "pathname": "var x = 1;" }], - "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }], - "expected_obj": { - "pathname": "var%20x%20=%201;" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "var x = 1;" }], - "inputs": [{ "pathname": "var x = 1;" }], - "expected_obj": { - "pathname": "var%20x%20=%201;" - }, - "expected_match": { - "pathname": { "input": "var%20x%20=%201;", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ "./foo/bar", "https://example.com" ], - "expected_match": { - "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, - "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": { "0": "https" } } - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ { "pathname": "/foo/bar" }, "https://example.com" ], - "expected_match": "error" - }, - { - "pattern": [ "https://example.com:8080/foo?bar#baz" ], - "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", - "baseURL": "https://example.com:8080" }], - "expected_obj": { - "protocol": "https", - "username": "*", - "password": "*", - "hostname": "example.com", - "port": "8080", - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "search": { "input": "bar", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } - }, - { - "pattern": [ "/foo?bar#baz", "https://example.com:8080" ], - "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", - "baseURL": "https://example.com:8080" }], - "expected_obj": { - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "search": { "input": "bar", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } - }, - { - "pattern": [ "/foo" ], - "expected_obj": "error" - }, - { - "pattern": [ "example.com/foo" ], - "expected_obj": "error" - }, - { - "pattern": [ "http{s}?://{*.}?example.com/:product/:endpoint" ], - "inputs": [ "https://sub.example.com/foo/bar" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "http{s}?", - "hostname": "{*.}?example.com", - "pathname": "/:product/:endpoint" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "sub.example.com", "groups": { "0": "sub" } }, - "pathname": { "input": "/foo/bar", "groups": { "product": "foo", - "endpoint": "bar" } } - } - }, - { - "pattern": [ "https://example.com?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com#foo" ], - "inputs": [ "https://example.com/#foo" ], - "exactly_empty_components": [ "port", "search" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/", - "hash": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "hash": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com:8080?foo" ], - "inputs": [ "https://example.com:8080/?foo" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "port": "8080", - "pathname": "/", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com:8080#foo" ], - "inputs": [ "https://example.com:8080/#foo" ], - "exactly_empty_components": [ "search" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "port": "8080", - "pathname": "/", - "hash": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "hash": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/#foo" ], - "inputs": [ "https://example.com/#foo" ], - "exactly_empty_components": [ "port", "search" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/", - "hash": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "hash": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/*?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/*?foo" - }, - "expected_match": null - }, - { - "pattern": [ "https://example.com/*\\?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/*", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "" } }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/:name?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/:name?foo" - }, - "expected_match": null - }, - { - "pattern": [ "https://example.com/:name\\?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/:name", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/bar", "groups": { "name": "bar" } }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/(bar)?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/(bar)?foo" - }, - "expected_match": null - }, - { - "pattern": [ "https://example.com/(bar)\\?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/(bar)", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/bar", "groups": { "0": "bar" } }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/{bar}?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/{bar}?foo" - }, - "expected_match": null - }, - { - "pattern": [ "https://example.com/{bar}\\?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/bar", - "search": "foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/bar", "groups": {} }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "https://example.com/" ], - "inputs": [ "https://example.com:8080/" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "port": "", - "pathname": "/" - }, - "expected_match": null - }, - { - "pattern": [ "data:foobar" ], - "inputs": [ "data:foobar" ], - "expected_obj": "error" - }, - { - "pattern": [ "data\\:foobar" ], - "inputs": [ "data:foobar" ], - "exactly_empty_components": [ "hostname", "port" ], - "expected_obj": { - "protocol": "data", - "pathname": "foobar" - }, - "expected_match": { - "protocol": { "input": "data", "groups": {} }, - "pathname": { "input": "foobar", "groups": {} } - } - }, - { - "pattern": [ "https://{sub.}?example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "{sub.}?example.com", - "pathname": "/foo" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} } - } - }, - { - "pattern": [ "https://{sub.}?example{.com/}foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "{sub.}?example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": { "0": "/foo" } } - } - }, - { - "pattern": [ "{https://}example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "expected_obj": "error" - }, - { - "pattern": [ "https://(sub.)?example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "(sub.)?example.com", - "pathname": "/foo" - }, - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": { "0": null } }, - "pathname": { "input": "/foo", "groups": {} } - } - }, - { - "pattern": [ "https://(sub.)?example(.com/)foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "(sub.)?example(.com/)foo", - "pathname": "*" - }, - "expected_match": null - }, - { - "pattern": [ "(https://)example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "expected_obj": "error" - }, - { - "pattern": [ "https://{sub{.}}example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "expected_obj": "error" - }, - { - "pattern": [ "https://(sub(?:.))?example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "(sub(?:.))?example.com", - "pathname": "/foo" - }, - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": { "0": null } }, - "pathname": { "input": "/foo", "groups": {} } - } - }, - { - "pattern": [ "file:///foo/bar" ], - "inputs": [ "file:///foo/bar" ], - "exactly_empty_components": [ "hostname", "port" ], - "expected_obj": { - "protocol": "file", - "pathname": "/foo/bar" - }, - "expected_match": { - "protocol": { "input": "file", "groups": {} }, - "pathname": { "input": "/foo/bar", "groups": {} } - } - }, - { - "pattern": [ "data:" ], - "inputs": [ "data:" ], - "exactly_empty_components": [ "hostname", "port", "pathname" ], - "expected_obj": { - "protocol": "data" - }, - "expected_match": { - "protocol": { "input": "data", "groups": {} } - } - }, - { - "pattern": [ "foo://bar" ], - "inputs": [ "foo://bad_url_browser_interop" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "foo", - "hostname": "bar" - }, - "expected_match": null - }, - { - "pattern": [ "(café)://foo" ], - "expected_obj": "error" - }, - { - "pattern": [ "https://example.com/foo?bar#baz" ], - "inputs": [{ "protocol": "https:", - "search": "?bar", - "hash": "#baz", - "baseURL": "http://example.com/foo" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": null - }, - { - "pattern": [{ "protocol": "http{s}?:", - "search": "?bar", - "hash": "#baz" }], - "inputs": [ "http://example.com/foo?bar#baz" ], - "expected_obj": { - "protocol": "http{s}?", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/foo", "groups": { "0": "/foo" }}, - "search": { "input": "bar", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } - }, - { - "pattern": [ "?bar#baz", "https://example.com/foo" ], - "inputs": [ "?bar#baz", "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "search": { "input": "bar", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } - }, - { - "pattern": [ "?bar", "https://example.com/foo#baz" ], - "inputs": [ "?bar", "https://example.com/foo#snafu" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/foo", - "search": "bar", - "hash": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "search": { "input": "bar", "groups": {} } - } - }, - { - "pattern": [ "#baz", "https://example.com/foo?bar" ], - "inputs": [ "#baz", "https://example.com/foo?bar" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "search": { "input": "bar", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } - }, - { - "pattern": [ "#baz", "https://example.com/foo" ], - "inputs": [ "#baz", "https://example.com/foo" ], - "exactly_empty_components": [ "port", "search" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/foo", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "*" }], - "inputs": [ "foo", "data:data-urls-cannot-be-base-urls" ], - "expected_match": null - }, - { - "pattern": [{ "pathname": "*" }], - "inputs": [ "foo", "not|a|valid|url" ], - "expected_match": null - }, - { - "pattern": [ "https://foo\\:bar@example.com" ], - "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "username": "foo", - "password": "bar", - "hostname": "example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "username": { "input": "foo", "groups": {} }, - "password": { "input": "bar", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [ "https://foo@example.com" ], - "inputs": [ "https://foo@example.com" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "username": "foo", - "hostname": "example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "username": { "input": "foo", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [ "https://\\:bar@example.com" ], - "inputs": [ "https://:bar@example.com" ], - "exactly_empty_components": [ "username", "port" ], - "expected_obj": { - "protocol": "https", - "password": "bar", - "hostname": "example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "password": { "input": "bar", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [ "https://:user::pass@example.com" ], - "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "username": ":user", - "password": ":pass", - "hostname": "example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "username": { "input": "foo", "groups": { "user": "foo" } }, - "password": { "input": "bar", "groups": { "pass": "bar" } }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [ "https\\:foo\\:bar@example.com" ], - "inputs": [ "https:foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "username": "foo", - "password": "bar", - "hostname": "example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "username": { "input": "foo", "groups": {} }, - "password": { "input": "bar", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [ "data\\:foo\\:bar@example.com" ], - "inputs": [ "data:foo:bar@example.com" ], - "exactly_empty_components": [ "hostname", "port" ], - "expected_obj": { - "protocol": "data", - "pathname": "foo\\:bar@example.com" - }, - "expected_match": { - "protocol": { "input": "data", "groups": {} }, - "pathname": { "input": "foo:bar@example.com", "groups": {} } - } - }, - { - "pattern": [ "https://foo{\\:}bar@example.com" ], - "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "username": "foo%3Abar", - "hostname": "example.com" - }, - "expected_match": null - }, - { - "pattern": [ "data{\\:}channel.html", "https://example.com" ], - "inputs": [ "https://example.com/data:channel.html" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "pathname": "/data\\:channel.html", - "search": "*", - "hash": "*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/data:channel.html", "groups": {} } - } - }, - { - "pattern": [ "http://[\\:\\:1]/" ], - "inputs": [ "http://[::1]/" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "http", - "hostname": "[\\:\\:1]", - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::1]", "groups": {} }, - "pathname": { "input": "/", "groups": {} } - } - }, - { - "pattern": [ "http://[\\:\\:1]:8080/" ], - "inputs": [ "http://[::1]:8080/" ], - "expected_obj": { - "protocol": "http", - "hostname": "[\\:\\:1]", - "port": "8080", - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::1]", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/", "groups": {} } - } - }, - { - "pattern": [ "http://[\\:\\:a]/" ], - "inputs": [ "http://[::a]/" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "http", - "hostname": "[\\:\\:a]", - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::a]", "groups": {} }, - "pathname": { "input": "/", "groups": {} } - } - }, - { - "pattern": [ "http://[:address]/" ], - "inputs": [ "http://[::1]/" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "http", - "hostname": "[:address]", - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::1]", "groups": { "address": "::1" }}, - "pathname": { "input": "/", "groups": {} } - } - }, - { - "pattern": [ "http://[\\:\\:AB\\::num]/" ], - "inputs": [ "http://[::ab:1]/" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "protocol": "http", - "hostname": "[\\:\\:ab\\::num]", - "pathname": "/" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::ab:1]", "groups": { "num": "1" }}, - "pathname": { "input": "/", "groups": {} } - } - }, - { - "pattern": [{ "hostname": "[\\:\\:AB\\::num]" }], - "inputs": [{ "hostname": "[::ab:1]" }], - "expected_obj": { - "hostname": "[\\:\\:ab\\::num]" - }, - "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "num": "1" }} - } - }, - { - "pattern": [{ "hostname": "[\\:\\:xY\\::num]" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "{[\\:\\:ab\\::num]}" }], - "inputs": [{ "hostname": "[::ab:1]" }], - "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "num": "1" }} - } - }, - { - "pattern": [{ "hostname": "{[\\:\\:fé\\::num]}" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "{[\\:\\::num\\:1]}" }], - "inputs": [{ "hostname": "[::ab:1]" }], - "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "num": "ab" }} - } - }, - { - "pattern": [{ "hostname": "{[\\:\\::num\\:fé]}" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "[*\\:1]" }], - "inputs": [{ "hostname": "[::ab:1]" }], - "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "0": "::ab" }} - } - }, - { - "pattern": [{ "hostname": "*\\:1]" }], - "expected_obj": "error" - }, - { - "pattern": [ "https://foo{{@}}example.com" ], - "inputs": [ "https://foo@example.com" ], - "expected_obj": "error" - }, - { - "pattern": [ "https://foo{@example.com" ], - "inputs": [ "https://foo@example.com" ], - "expected_obj": "error" - }, - { - "pattern": [ "data\\:text/javascript,let x = 100/:tens?5;" ], - "inputs": [ "data:text/javascript,let x = 100/5;" ], - "exactly_empty_components": [ "hostname", "port" ], - "expected_obj": { - "protocol": "data", - "pathname": "text/javascript,let x = 100/:tens?5;" - }, - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "protocol": { "input": "data", "groups": {} }, - "pathname": { "input": "text/javascript,let x = 100/5;", "groups": { "tens": null } } - } - }, - { - "pattern": [{ "pathname": "/:id/:id" }], - "expected_obj": "error" - }, - { - "pattern": [{ "pathname": "/foo", "baseURL": "" }], - "expected_obj": "error" - }, - { - "pattern": [ "/foo", "" ], - "expected_obj": "error" - }, - { - "pattern": [{ "pathname": "/foo" }, "https://example.com" ], - "expected_obj": "error" - }, - { - "pattern": [{ "pathname": ":name*" }], - "inputs": [{ "pathname": "foobar" }], - "expected_match": { - "pathname": { "input": "foobar", "groups": { "name": "foobar" }} - } - }, - { - "pattern": [{ "pathname": ":name+" }], - "inputs": [{ "pathname": "foobar" }], - "expected_match": { - "pathname": { "input": "foobar", "groups": { "name": "foobar" }} - } - }, - { - "pattern": [{ "pathname": ":name" }], - "inputs": [{ "pathname": "foobar" }], - "expected_match": { - "pathname": { "input": "foobar", "groups": { "name": "foobar" }} - } - }, - { - "pattern": [{ "protocol": ":name*" }], - "inputs": [{ "protocol": "foobar" }], - "expected_match": { - "protocol": { "input": "foobar", "groups": { "name": "foobar" }} - } - }, - { - "pattern": [{ "protocol": ":name+" }], - "inputs": [{ "protocol": "foobar" }], - "expected_match": { - "protocol": { "input": "foobar", "groups": { "name": "foobar" }} - } - }, - { - "pattern": [{ "protocol": ":name" }], - "inputs": [{ "protocol": "foobar" }], - "expected_match": { - "protocol": { "input": "foobar", "groups": { "name": "foobar" }} - } - }, - { - "pattern": [{ "hostname": "bad hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad#hostname" }], - "inputs": [{ "hostname": "bad" }], - "expected_obj": { - "hostname": "bad" - }, - "expected_match": { - "hostname": { "input": "bad", "groups": {} } - } - }, - { - "pattern": [{ "hostname": "bad%hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad/hostname" }], - "inputs": [{ "hostname": "bad" }], - "expected_obj": { - "hostname": "bad" - }, - "expected_match": { - "hostname": { "input": "bad", "groups": {} } - } - }, - { - "pattern": [{ "hostname": "bad\\:hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "badhostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad?hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad@hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad[hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad]hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad\\\\hostname" }], - "inputs": [{ "hostname": "badhostname" }], - "expected_obj": { - "hostname": "bad" - }, - "expected_match": null - }, - { - "pattern": [{ "hostname": "bad^hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad|hostname" }], - "expected_obj": "error" - }, - { - "pattern": [{ "hostname": "bad\nhostname" }], - "inputs": [{ "hostname": "badhostname" }], - "expected_obj": { - "hostname": "badhostname" - }, - "expected_match": { - "hostname": { "input": "badhostname", "groups": {} } - } - }, - { - "pattern": [{ "hostname": "bad\rhostname" }], - "inputs": [{ "hostname": "badhostname" }], - "expected_obj": { - "hostname": "badhostname" - }, - "expected_match": { - "hostname": { "input": "badhostname", "groups": {} } - } - }, - { - "pattern": [{ "hostname": "bad\thostname" }], - "inputs": [{ "hostname": "badhostname" }], - "expected_obj": { - "hostname": "badhostname" - }, - "expected_match": { - "hostname": { "input": "badhostname", "groups": {} } - } - }, - { - "pattern": [{}], - "inputs": ["https://example.com/"], - "expected_match": { - "protocol": { "input": "https", "groups": { "0": "https" }}, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/", "groups": { "0": "/" }} - } - }, - { - "pattern": [], - "inputs": ["https://example.com/"], - "expected_match": { - "protocol": { "input": "https", "groups": { "0": "https" }}, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/", "groups": { "0": "/" }} - } - }, - { - "pattern": [], - "inputs": [{}], - "expected_match": {} - }, - { - "pattern": [], - "inputs": [], - "expected_match": { "inputs": [{}] } - }, - { - "pattern": [{ "pathname": "(foo)(.*)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "barbaz" }} - } - }, - { - "pattern": [{ "pathname": "{(foo)bar}(.*)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "baz" }} - } - }, - { - "pattern": [{ "pathname": "(foo)?(.*)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_obj": { - "pathname": "(foo)?*" - }, - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "barbaz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}(.*)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}(barbaz)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "foo", "0": "barbaz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}{(.*)}" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_obj": { - "pathname": "{:foo}(.*)" - }, - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}{(.*)bar}" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_obj": { - "pathname": ":foo{*bar}" - }, - "expected_match": null - }, - { - "pattern": [{ "pathname": "{:foo}{bar(.*)}" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_obj": { - "pathname": ":foo{bar*}" - }, - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "foo", "0": "baz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}:bar(.*)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_obj": { - "pathname": ":foo:bar(.*)" - }, - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "bar": "oobarbaz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}?(.*)" }], - "inputs": [{ "pathname": "foobarbaz" }], - "expected_obj": { - "pathname": ":foo?*" - }, - "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" }} - } - }, - { - "pattern": [{ "pathname": "{:foo\\bar}" }], - "inputs": [{ "pathname": "foobar" }], - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": "{:foo\\.bar}" }], - "inputs": [{ "pathname": "foo.bar" }], - "expected_obj": { - "pathname": "{:foo.bar}" - }, - "expected_match": { - "pathname": { "input": "foo.bar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": "{:foo(foo)bar}" }], - "inputs": [{ "pathname": "foobar" }], - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": "{:foo}bar" }], - "inputs": [{ "pathname": "foobar" }], - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": ":foo\\bar" }], - "inputs": [{ "pathname": "foobar" }], - "expected_obj": { - "pathname": "{:foo}bar" - }, - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": ":foo{}(.*)" }], - "inputs": [{ "pathname": "foobar" }], - "expected_obj": { - "pathname": "{:foo}(.*)" - }, - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "f", "0": "oobar" }} - } - }, - { - "pattern": [{ "pathname": ":foo{}bar" }], - "inputs": [{ "pathname": "foobar" }], - "expected_obj": { - "pathname": "{:foo}bar" - }, - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": ":foo{}?bar" }], - "inputs": [{ "pathname": "foobar" }], - "expected_obj": { - "pathname": "{:foo}bar" - }, - "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} - } - }, - { - "pattern": [{ "pathname": "*{}**?" }], - "inputs": [{ "pathname": "foobar" }], - "expected_obj": { - "pathname": "*(.*)?" - }, - "//": "The `null` below is translated to undefined in the test harness.", - "expected_match": { - "pathname": { "input": "foobar", "groups": { "0": "foobar", "1": null }} - } - }, - { - "pattern": [{ "pathname": ":foo(baz)(.*)" }], - "inputs": [{ "pathname": "bazbar" }], - "expected_match": { - "pathname": { "input": "bazbar", "groups": { "foo": "baz", "0": "bar" }} - } - }, - { - "pattern": [{ "pathname": ":foo(baz)bar" }], - "inputs": [{ "pathname": "bazbar" }], - "expected_match": { - "pathname": { "input": "bazbar", "groups": { "foo": "baz" }} - } - }, - { - "pattern": [{ "pathname": "*/*" }], - "inputs": [{ "pathname": "foo/bar" }], - "expected_match": { - "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" }} - } - }, - { - "pattern": [{ "pathname": "*\\/*" }], - "inputs": [{ "pathname": "foo/bar" }], - "expected_obj": { - "pathname": "*/{*}" - }, - "expected_match": { - "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" }} - } - }, - { - "pattern": [{ "pathname": "*/{*}" }], - "inputs": [{ "pathname": "foo/bar" }], - "expected_match": { - "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" }} - } - }, - { - "pattern": [{ "pathname": "*//*" }], - "inputs": [{ "pathname": "foo/bar" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/:foo." }], - "inputs": [{ "pathname": "/bar." }], - "expected_match": { - "pathname": { "input": "/bar.", "groups": { "foo": "bar" } } - } - }, - { - "pattern": [{ "pathname": "/:foo.." }], - "inputs": [{ "pathname": "/bar.." }], - "expected_match": { - "pathname": { "input": "/bar..", "groups": { "foo": "bar" } } - } - }, - { - "pattern": [{ "pathname": "./foo" }], - "inputs": [{ "pathname": "./foo" }], - "expected_match": { - "pathname": { "input": "./foo", "groups": {}} - } - }, - { - "pattern": [{ "pathname": "../foo" }], - "inputs": [{ "pathname": "../foo" }], - "expected_match": { - "pathname": { "input": "../foo", "groups": {}} - } - }, - { - "pattern": [{ "pathname": ":foo./" }], - "inputs": [{ "pathname": "bar./" }], - "expected_match": { - "pathname": { "input": "bar./", "groups": { "foo": "bar" }} - } - }, - { - "pattern": [{ "pathname": ":foo../" }], - "inputs": [{ "pathname": "bar../" }], - "expected_match": { - "pathname": { "input": "bar../", "groups": { "foo": "bar" }} - } - }, - { - "pattern": [{ "pathname": "/:foo\\bar" }], - "inputs": [{ "pathname": "/bazbar" }], - "expected_obj": { - "pathname": "{/:foo}bar" - }, - "expected_match": { - "pathname": { "input": "/bazbar", "groups": { "foo": "baz" }} - } - }, - { - "pattern": [{ "pathname": "/foo/bar" }, { "ignoreCase": true }], - "inputs": [{ "pathname": "/FOO/BAR" }], - "expected_match": { - "pathname": { "input": "/FOO/BAR", "groups": {} } - } - }, - { - "pattern": [{ "ignoreCase": true }], - "inputs": [{ "pathname": "/FOO/BAR" }], - "expected_match": { - "pathname": { "input": "/FOO/BAR", "groups": { "0": "/FOO/BAR" } } - } - }, - { - "pattern": [ "https://example.com:8080/foo?bar#baz", - { "ignoreCase": true }], - "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", - "baseURL": "https://example.com:8080" }], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "port": "8080", - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/FOO", "groups": {} }, - "search": { "input": "BAR", "groups": {} }, - "hash": { "input": "BAZ", "groups": {} } - } - }, - { - "pattern": [ "/foo?bar#baz", "https://example.com:8080", - { "ignoreCase": true }], - "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", - "baseURL": "https://example.com:8080" }], - "expected_obj": { - "protocol": "https", - "hostname": "example.com", - "port": "8080", - "pathname": "/foo", - "search": "bar", - "hash": "baz" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "port": { "input": "8080", "groups": {} }, - "pathname": { "input": "/FOO", "groups": {} }, - "search": { "input": "BAR", "groups": {} }, - "hash": { "input": "BAZ", "groups": {} } - } - }, - { - "pattern": [ "/foo?bar#baz", { "ignoreCase": true }, - "https://example.com:8080" ], - "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", - "baseURL": "https://example.com:8080" }], - "expected_obj": "error" - }, - { - "pattern": [{ "search": "foo", "baseURL": "https://example.com/a/+/b" }], - "inputs": [{ "search": "foo", "baseURL": "https://example.com/a/+/b" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "pathname": "/a/\\+/b" - }, - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/a/+/b", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [{ "hash": "foo", "baseURL": "https://example.com/?q=*&v=?&hmm={}&umm=()" }], - "inputs": [{ "hash": "foo", "baseURL": "https://example.com/?q=*&v=?&hmm={}&umm=()" }], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "search": "q=\\*&v=\\?&hmm=\\{\\}&umm=\\(\\)" - }, - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "q=*&v=?&hmm={}&umm=()", "groups": {} }, - "hash": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [ "#foo", "https://example.com/?q=*&v=?&hmm={}&umm=()" ], - "inputs": [ "https://example.com/?q=*&v=?&hmm={}&umm=()#foo" ], - "exactly_empty_components": [ "port" ], - "expected_obj": { - "search": "q=\\*&v=\\?&hmm=\\{\\}&umm=\\(\\)", - "hash": "foo" - }, - "expected_match": { - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "q=*&v=?&hmm={}&umm=()", "groups": {} }, - "hash": { "input": "foo", "groups": {} } - } - }, - { - "pattern": [{ "pathname": "/([[a-z]--a])" }], - "inputs": [{ "pathname": "/a" }], - "expected_match": null - }, - { - "pattern": [{ "pathname": "/([[a-z]--a])" }], - "inputs": [{ "pathname": "/z" }], - "expected_match": { - "pathname": { "input": "/z", "groups": { "0": "z" } } - } - }, - { - "pattern": [{ "pathname": "/([\\d&&[0-1]])" }], - "inputs": [{ "pathname": "/0" }], - "expected_match": { - "pathname": { "input": "/0", "groups": { "0": "0" } } - } - }, - { - "pattern": [{ "pathname": "/([\\d&&[0-1]])" }], - "inputs": [{ "pathname": "/3" }], - "expected_match": null - }, - { - "pattern": [{ "protocol": "http", "hostname": "example.com/ignoredpath" }], - "inputs": ["http://example.com/"], - "expected_obj": { - "protocol": "http", - "hostname": "example.com", - "pathname": "*" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [{ "protocol": "http", "hostname": "example.com\\?ignoredsearch" }], - "inputs": ["http://example.com/"], - "expected_obj": { - "protocol": "http", - "hostname": "example.com", - "search": "*" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": [{ "protocol": "http", "hostname": "example.com#ignoredhash" }], - "inputs": ["http://example.com/"], - "expected_obj": { - "protocol": "http", - "hostname": "example.com", - "hash": "*" - }, - "expected_match": { - "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": { "0": "/" } } - } - }, - { - "pattern": ["https://www.example.com/*"], - "inputs": ["https://www.example.com/x"], - "exactly_empty_components": ["port"], - "expected_obj": { - "protocol": "https", - "hostname": "www.example.com", - "pathname": "/*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "www.example.com", "groups": {} }, - "pathname": { "input": "/x", "groups": { "0": "x" } } - } - }, - { - "pattern": ["https://www.example.com/*"], - "inputs": ["https://www.example.com/xyz"], - "exactly_empty_components": ["port"], - "expected_obj": { - "protocol": "https", - "hostname": "www.example.com", - "pathname": "/*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "www.example.com", "groups": {} }, - "pathname": { "input": "/xyz", "groups": { "0": "xyz" } } - } - }, - { - "pattern": ["https://www.example.com/*"], - "inputs": ["https://www.example.com/example"], - "exactly_empty_components": ["port"], - "expected_obj": { - "protocol": "https", - "hostname": "www.example.com", - "pathname": "/*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "www.example.com", "groups": {} }, - "pathname": { "input": "/example", "groups": { "0": "example" } } - } - }, - { - "pattern": ["https://www.example.com/*"], - "inputs": ["https://www.example.com/text"], - "exactly_empty_components": ["port"], - "expected_obj": { - "protocol": "https", - "hostname": "www.example.com", - "pathname": "/*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "www.example.com", "groups": {} }, - "pathname": { "input": "/text", "groups": { "0": "text" } } - } - }, - { - "pattern": ["https://www.example.com/*"], - "inputs": ["https://www.example.com/path/with/x"], - "exactly_empty_components": ["port"], - "expected_obj": { - "protocol": "https", - "hostname": "www.example.com", - "pathname": "/*" - }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "www.example.com", "groups": {} }, - "pathname": { "input": "/path/with/x", "groups": { "0": "path/with/x" } } - } - }, - { - "pattern": [{ "hostname": ":domain(.*)" }], - "inputs": [{ "hostname": "localhost" }], - "expected_obj": { - "hostname": ":domain(.*)" - }, - "expected_match": { - "hostname": { "input": "localhost", "groups": { "domain" : "localhost"} } - } - }, - { - "pattern": ["((?R)):"], - "expected_obj": "error" - }, - { - "pattern": ["(\\H):"], - "expected_obj": "error" - }, - { - "pattern": [ - {"pathname": "/:foo((?a))"} - ], - "inputs": [ - {"pathname": "/a"} - ], - "expected_match": { - "pathname": { - "input": "/a", - "groups": {"foo": "a"} - } - } - }, - { - "pattern": [ - {"pathname": "/foo/(bar(?baz))"} - ], - "inputs": [ - {"pathname": "/foo/barbaz"} - ], - "expected_match": { - "pathname": { - "input": "/foo/barbaz", - "groups": {"0": "barbaz"} - } - } - }, - { - "pattern": [{ "pathname": "/:𠀀" }], - "inputs": [{ "pathname": "/foo" }], - "expected_match": { - "pathname": { "input": "/foo", "groups": { "𠀀": "foo" } } - } - } -] diff --git a/tests/wpt_url_tests.rs b/tests/wpt_url_tests.rs index 4734303..ccbcee2 100644 --- a/tests/wpt_url_tests.rs +++ b/tests/wpt_url_tests.rs @@ -1,211 +1,415 @@ -#![cfg(feature = "std")] - -mod common; - -use ada_url::{Idna, Url}; -use common::{as_array, as_object, get_str, maybe_str, read_json}; -use serde_json::Value; - -fn assert_expected_field(url: &Url, key: &str, expected: &str) { - match key { - "protocol" => assert_eq!(url.protocol(), expected), - "username" => assert_eq!(url.username(), expected), - "password" => assert_eq!(url.password(), expected), - "host" => assert_eq!(url.host(), expected), - "hostname" => assert_eq!(url.hostname(), expected), - "port" => assert_eq!(url.port(), expected), - "pathname" => assert_eq!(url.pathname(), expected), - "search" => assert_eq!(url.search(), expected), - "hash" => assert_eq!(url.hash(), expected), - "href" => assert_eq!(url.href(), expected), - "origin" => assert_eq!(url.origin(), expected), - other => panic!("unexpected expected-field key: {other}"), +//! Pinned WHATWG URL parser conformance tests. + +use ada_url::{PercentEncodeSet, Url, domain_to_ascii, percent_encode}; +use serde_json::{Map, Value}; + +const URL_SOURCES: &[&str] = &[ + "tests/wpt/urltestdata.json", + "tests/wpt/ada_extra_urltestdata.json", + "tests/wpt/ada_long_urltestdata.json", +]; +const SETTER_SOURCES: &[&str] = &[ + "tests/wpt/setters_tests.json", + "tests/wpt/ada_extra_setters_tests.json", +]; + +fn read_json(path: &str) -> Value { + let source = std::fs::read_to_string(path).unwrap_or_else(|error| { + panic!("failed to read {path}: {error}"); + }); + serde_json::from_str(&source).unwrap_or_else(|error| { + panic!("failed to parse {path}: {error}"); + }) +} + +fn read_json_with_replacement(path: &str) -> Value { + let source = std::fs::read_to_string(path).unwrap_or_else(|error| { + panic!("failed to read {path}: {error}"); + }); + let source = replace_unpaired_surrogate_escapes(&source); + serde_json::from_str(&source).unwrap_or_else(|error| { + panic!("failed to parse {path} after surrogate replacement: {error}"); + }) +} + +fn replace_unpaired_surrogate_escapes(source: &str) -> String { + let bytes = source.as_bytes(); + let mut output = String::with_capacity(source.len()); + let mut index = 0; + while index < bytes.len() { + let is_escape = + index + 6 <= bytes.len() && bytes[index] == b'\\' && bytes[index + 1] == b'u'; + if !is_escape { + let character = source[index..].chars().next().expect("valid UTF-8 source"); + output.push(character); + index += character.len_utf8(); + continue; + } + let Some(code_unit) = parse_hex_u16(&bytes[index + 2..index + 6]) else { + output.push('\\'); + index += 1; + continue; + }; + if (0xd800..=0xdbff).contains(&code_unit) { + let has_low = index + 12 <= bytes.len() + && bytes[index + 6] == b'\\' + && bytes[index + 7] == b'u' + && parse_hex_u16(&bytes[index + 8..index + 12]) + .is_some_and(|low| (0xdc00..=0xdfff).contains(&low)); + if has_low { + output.push_str(&source[index..index + 12]); + index += 12; + } else { + output.push_str("\\uFFFD"); + index += 6; + } + } else if (0xdc00..=0xdfff).contains(&code_unit) { + output.push_str("\\uFFFD"); + index += 6; + } else { + output.push_str(&source[index..index + 6]); + index += 6; + } } + output +} + +fn parse_hex_u16(input: &[u8]) -> Option { + input.iter().try_fold(0_u16, |value, byte| { + let digit = match byte { + b'0'..=b'9' => u16::from(byte - b'0'), + b'a'..=b'f' => u16::from(byte - b'a' + 10), + b'A'..=b'F' => u16::from(byte - b'A' + 10), + _ => return None, + }; + Some(value * 16 + digit) + }) +} + +fn object<'a>(value: &'a Value, context: &str) -> &'a Map { + value + .as_object() + .unwrap_or_else(|| panic!("{context} is not an object")) +} + +fn string<'a>(value: &'a Map, key: &str) -> &'a str { + value + .get(key) + .and_then(Value::as_str) + .unwrap_or_else(|| panic!("missing string field {key}")) +} + +fn optional_string<'a>(value: &'a Map, key: &str) -> Option<&'a str> { + value.get(key).and_then(Value::as_str) +} + +fn assert_field(url: &Url, field: &str, expected: &str, context: &str) { + let actual = match field { + "protocol" => url.protocol(), + "username" => url.username(), + "password" => url.password(), + "host" => url.host(), + "hostname" => url.hostname(), + "port" => url.port(), + "pathname" => url.pathname(), + "search" => url.search(), + "hash" => url.hash(), + "href" => url.href(), + other => panic!("{context}: unsupported expected field {other}"), + }; + assert_eq!(actual, expected, "{context}: field={field}"); } #[test] -fn setters_tests_encoding() { - for source in [ - "tests/wpt/setters_tests.json", - "tests/wpt/ada_extra_setters_tests.json", - ] { - let doc = read_json(source); - let root = as_object(&doc, "setters_tests root"); - - for (category, cases_value) in root { - if category == "comment" { +fn urltestdata() { + for source in URL_SOURCES { + let document = read_json(source); + let entries = document + .as_array() + .unwrap_or_else(|| panic!("{source} root is not an array")); + let mut executed = 0_usize; + + for (index, entry) in entries.iter().enumerate() { + if entry.is_string() { + continue; + } + let case = object(entry, "URL test entry"); + let input = string(case, "input"); + let base = optional_string(case, "base"); + let should_fail = case + .get("failure") + .and_then(Value::as_bool) + .unwrap_or(false); + let parsed = Url::parse_with_base(input, base); + + if should_fail { + assert!( + parsed.is_err(), + "{source}:{index}: expected failure for input={input:?}, base={base:?}" + ); continue; } - let cases = cases_value - .as_array() - .unwrap_or_else(|| panic!("{source}:{category} is not an array")); + let url = parsed.unwrap_or_else(|error| { + panic!( + "{source}:{index}: expected success for input={input:?}, \ + base={base:?}, error={error}" + ) + }); + let context = || format!("{source}:{index}: input={input:?}, base={base:?}"); + assert_eq!(url.protocol(), string(case, "protocol"), "{}", context()); + assert_eq!(url.username(), string(case, "username"), "{}", context()); + assert_eq!(url.password(), string(case, "password"), "{}", context()); + assert_eq!(url.host(), string(case, "host"), "{}", context()); + assert_eq!(url.hostname(), string(case, "hostname"), "{}", context()); + assert_eq!(url.port(), string(case, "port"), "{}", context()); + assert_eq!(url.pathname(), string(case, "pathname"), "{}", context()); + assert_eq!(url.search(), string(case, "search"), "{}", context()); + assert_eq!(url.hash(), string(case, "hash"), "{}", context()); + assert_eq!(url.href(), string(case, "href"), "{}", context()); + if let Some(expected_origin) = optional_string(case, "origin") { + assert_eq!(url.origin(), expected_origin, "{}", context()); + } + assert!(url.validate(), "{}: invalid internal offsets", context()); + executed += 1; + } + + assert!(executed > 0, "no URL tests executed from {source}"); + } +} + +#[test] +fn can_parse_matches_parse() { + for source in URL_SOURCES { + let document = read_json(source); + let entries = document + .as_array() + .unwrap_or_else(|| panic!("{source} root is not an array")); + + for (index, entry) in entries.iter().enumerate() { + if entry.is_string() { + continue; + } + let case = object(entry, "URL test entry"); + let input = string(case, "input"); + let base = optional_string(case, "base"); + assert_eq!( + Url::can_parse(input, base), + Url::parse_with_base(input, base).is_ok(), + "{source}:{index}: input={input:?}, base={base:?}" + ); + } + } + + let source = "tests/wpt/verifydnslength_tests.json"; + let document = read_json(source); + let entries = document + .as_array() + .unwrap_or_else(|| panic!("{source} root is not an array")); + for (index, entry) in entries.iter().enumerate() { + if entry.is_string() { + continue; + } + let input = string(object(entry, "DNS length entry"), "input"); + assert_eq!( + Url::can_parse(input, None), + Url::parse(input, None).is_ok(), + "{source}:{index}: input={input:?}" + ); + } +} - for case in cases { - let case_obj = as_object(case, "setters_tests case"); - let href = get_str(case_obj, "href"); - let new_value = get_str(case_obj, "new_value"); +#[test] +fn setters() { + for source in SETTER_SOURCES { + let document = read_json(source); + let categories = document + .as_object() + .unwrap_or_else(|| panic!("{source} root is not an object")); - let mut base = Url::parse(href, None).unwrap_or_else(|_| { - panic!("source={source}, category={category}, href={href}") + for (category, cases) in categories { + if category == "comment" { + continue; + } + let cases = cases + .as_array() + .unwrap_or_else(|| panic!("{source}:{category} is not an array")); + for (index, case) in cases.iter().enumerate() { + let case = object(case, "setter entry"); + let href = string(case, "href"); + let new_value = string(case, "new_value"); + let mut url = Url::parse(href, None).unwrap_or_else(|error| { + panic!("{source}:{category}:{index}: invalid fixture URL {href:?}: {error}") }); - match category.as_str() { - "protocol" => { - let _ = base.set_protocol(new_value); - } - "username" => { - let _ = base.set_username(Some(new_value)); - } - "password" => { - let _ = base.set_password(Some(new_value)); - } - "host" => { - let _ = base.set_host(Some(new_value)); - } - "hostname" => { - let _ = base.set_hostname(Some(new_value)); - } - "port" => { - let _ = base.set_port(Some(new_value)); - } - "pathname" => { - let _ = base.set_pathname(Some(new_value)); - } + let _result = match category.as_str() { + "protocol" => url.set_protocol(new_value), + "username" => url.set_username(Some(new_value)), + "password" => url.set_password(Some(new_value)), + "host" => url.set_host(Some(new_value)), + "hostname" => url.set_hostname(Some(new_value)), + "port" => url.set_port(Some(new_value)), + "pathname" => url.set_pathname(Some(new_value)), "search" => { - base.set_search(Some(new_value)); + url.set_search(Some(new_value)); + Ok(()) } "hash" => { - base.set_hash(Some(new_value)); + url.set_hash(Some(new_value)); + Ok(()) } - "href" => { - let _ = base.set_href(new_value); - } - other => panic!("unsupported category in {source}: {other}"), - } + "href" => url.set_href(new_value), + other => panic!("{source}: unsupported setter category {other}"), + }; - let expected_obj = case_obj + let expected = case .get("expected") .and_then(Value::as_object) - .unwrap_or_else(|| panic!("missing expected object in {source}:{category}")); - for (key, value) in expected_obj { - if let Some(expected_str) = value.as_str() { - assert_expected_field(&base, key, expected_str); + .unwrap_or_else(|| { + panic!("{source}:{category}:{index}: missing expected object") + }); + let context = + format!("{source}:{category}:{index}: href={href:?}, new_value={new_value:?}"); + for (field, expected) in expected { + if let Some(expected) = expected.as_str() { + assert_field(&url, field, expected, &context); } } + assert!(url.validate(), "{context}: invalid internal offsets"); } } } } #[test] -fn toascii_encoding() { - let doc = read_json("tests/wpt/toascii.json"); - let mut counter = 0usize; +fn idna_test_v2() { + let source = "tests/wpt/IdnaTestV2.json"; + let document = read_json_with_replacement(source); + let entries = document + .as_array() + .unwrap_or_else(|| panic!("{source} root is not an array")); + let mut executed = 0_usize; - for element in as_array(&doc, "toascii") { - if element.is_string() { + for (index, entry) in entries.iter().enumerate() { + if entry.is_string() { continue; } - - let object = as_object(element, "toascii entry"); - let input = get_str(object, "input"); - let expected_output = object - .get("output") - .unwrap_or_else(|| panic!("missing output in toascii entry")); - - let output = Idna::ascii(input); - - if let Some(expected) = expected_output.as_str() { - assert_eq!(output, expected, "toascii mismatch for input={input:?}"); - - let url_string = format!("https://{output}/x"); - let current = Url::parse(&url_string, None) - .unwrap_or_else(|_| panic!("failed to parse generated URL {url_string:?}")); - assert_eq!(current.host(), expected); - assert_eq!(current.hostname(), expected); - assert_eq!(current.pathname(), "/x"); - assert_eq!(current.href(), format!("https://{expected}/x")); - - let mut setter = Url::parse("https://x/x", None).expect("valid setter URL"); - assert!(setter.set_host(Some(input)).is_ok()); - assert!(setter.set_hostname(Some(input)).is_ok()); - assert_eq!(setter.host(), expected); - assert_eq!(setter.hostname(), expected); - } else { - assert!(expected_output.is_null()); - let _ = output; - - let mut setter = Url::parse("https://x/x", None).expect("valid setter URL"); - assert!(setter.set_host(Some(input)).is_err()); - assert!(setter.set_hostname(Some(input)).is_err()); - assert_eq!(setter.host(), "x"); - assert_eq!(setter.hostname(), "x"); + let case = object(entry, "IDNA entry"); + let input = string(case, "input"); + let expected = case.get("output").unwrap_or_else(|| { + panic!("{source}:{index}: missing output"); + }); + let parsed = domain_to_ascii(input); + match expected.as_str() { + Some(expected) => { + let output = parsed.unwrap_or_else(|error| { + panic!("{source}:{index}: input={input:?} should pass: {error}") + }); + assert_eq!(output, expected, "{source}:{index}: input={input:?}"); + } + None => assert!( + parsed.is_err(), + "{source}:{index}: input={input:?} should fail" + ), } - - counter += 1; + executed += 1; } - - assert!(counter > 0); + assert!(executed > 2_000, "unexpectedly small IDNA corpus"); } #[test] -fn urltestdata_encoding() { - for source in [ - "tests/wpt/urltestdata.json", - "tests/wpt/ada_extra_urltestdata.json", - ] { - let doc = read_json(source); - let mut counter = 0usize; - - for element in as_array(&doc, "urltestdata") { - if element.is_string() { - continue; - } +fn toascii() { + let source = "tests/wpt/toascii.json"; + let document = read_json_with_replacement(source); + let entries = document + .as_array() + .unwrap_or_else(|| panic!("{source} root is not an array")); + let mut executed = 0_usize; - let object = as_object(element, "urltestdata entry"); - let input = get_str(object, "input"); - let base = maybe_str(object, "base"); - let failure = object - .get("failure") - .and_then(Value::as_bool) - .unwrap_or(false); - - let parsed = match base { - Some(base) => Url::parse(input, Some(base)), - None => Url::parse(input, None), - }; - - if failure { - assert!( - parsed.is_err(), - "source={source} input should fail: {input:?} base={base:?}" - ); - continue; - } + for (index, entry) in entries.iter().enumerate() { + if entry.is_string() { + continue; + } + let case = object(entry, "toASCII entry"); + let input = string(case, "input"); + let expected = case.get("output").unwrap_or_else(|| { + panic!("{source}:{index}: missing output"); + }); + let converted = domain_to_ascii(input); + match expected.as_str() { + Some(expected) => assert_eq!( + converted.unwrap_or_else(|error| { + panic!("{source}:{index}: input={input:?} should pass: {error}") + }), + expected, + "{source}:{index}: input={input:?}" + ), + None => assert!( + converted.is_err(), + "{source}:{index}: input={input:?} should fail" + ), + } + executed += 1; + } + assert!(executed > 50, "unexpectedly small toASCII corpus"); +} - let url = parsed.unwrap_or_else(|_| { - panic!("source={source} input should parse: {input:?} base={base:?}") - }); +#[test] +fn percent_encoding() { + let cases = read_json("tests/wpt/percent-encoding.json"); + let cases = cases.as_array().expect("percent-encoding root is an array"); + let mut executed = 0; + for case in cases { + let Some(case) = case.as_object() else { + continue; + }; + let input = string(case, "input"); + let expected = case + .get("output") + .and_then(Value::as_object) + .and_then(|output| output.get("utf-8")) + .and_then(Value::as_str) + .expect("UTF-8 percent-encoding output"); + assert_eq!( + percent_encode(input, PercentEncodeSet::Query), + expected, + "percent-encoding input {input:?}" + ); + executed += 1; + } + assert_eq!(executed, 7, "the pinned percent-encoding corpus changed"); +} - assert_eq!(url.protocol(), get_str(object, "protocol")); - assert_eq!(url.username(), get_str(object, "username")); - assert_eq!(url.password(), get_str(object, "password")); - assert_eq!(url.host(), get_str(object, "host")); - assert_eq!(url.hostname(), get_str(object, "hostname")); - assert_eq!(url.port(), get_str(object, "port")); - assert_eq!(url.pathname(), get_str(object, "pathname")); - assert_eq!(url.search(), get_str(object, "search")); - assert_eq!(url.hash(), get_str(object, "hash")); - assert_eq!(url.href(), get_str(object, "href")); - - if let Some(origin) = maybe_str(object, "origin") { - assert_eq!(url.origin(), origin); - } +#[test] +fn verify_dns_length() { + let source = "tests/wpt/verifydnslength_tests.json"; + let document = read_json(source); + let entries = document + .as_array() + .unwrap_or_else(|| panic!("{source} root is not an array")); + let mut executed = 0_usize; - counter += 1; + for (index, entry) in entries.iter().enumerate() { + if entry.is_string() { + continue; } - - assert!(counter > 0, "no executed cases for source={source}"); + let case = object(entry, "DNS length entry"); + let input = string(case, "input"); + let message = string(case, "message"); + let should_fail = case + .get("failure") + .and_then(Value::as_bool) + .unwrap_or(false); + let url = Url::parse(input, None).unwrap_or_else(|error| { + panic!("{source}:{index}: could not parse input={input:?}: {error}") + }); + assert_eq!( + url.has_valid_domain(), + !should_fail, + "{source}:{index}: {message}" + ); + executed += 1; } + + assert_eq!(executed, 17, "the pinned DNS-length corpus changed"); }