httparse-1.10.1/.cargo_vcs_info.json0000644000000001360000000000100127460ustar { "git": { "sha1": "9f29e79f9832dbd0ae5220acb17c1866745bdecd" }, "path_in_vcs": "" }httparse-1.10.1/.github/FUNDING.yml000064400000000000000000000000261046102023000147110ustar 00000000000000github: [seanmonstar] httparse-1.10.1/.github/workflows/ci.yml000064400000000000000000000150041046102023000162510ustar 00000000000000name: CI on: pull_request: push: branches: - master env: RUST_BACKTRACE: 1 jobs: ci-pass: name: CI is green runs-on: ubuntu-latest needs: - test - simd - check_x86 - aarch64 - msrv_x64 - msrv_aarch64 - miri - clippy_check steps: - run: exit 0 test: name: Test ${{ matrix.rust }} on ${{ matrix.os }} strategy: matrix: rust: - stable - beta - nightly os: - ubuntu-latest - windows-latest - macOS-latest include: - rust: nightly benches: true runs-on: ${{ matrix.os }} env: CARGO_CFG_HTTPARSE_DISABLE_SIMD: 1 steps: - name: Checkout uses: actions/checkout@v1 - name: Install Rust (${{ matrix.rust }}) uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - name: no_std uses: actions-rs/cargo@v1 with: command: build args: --no-default-features - name: Test uses: actions-rs/cargo@v1 with: command: test - name: Test all benches if: matrix.benches uses: actions-rs/cargo@v1 with: command: test args: --benches simd: name: SIMD ${{ matrix.target_feature }} on ${{ matrix.rust }} runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly target_feature: - "+sse4.2" - "+avx2" - "+sse4.2,+avx2" disable_compiletime: - 0 - 1 steps: - name: Checkout uses: actions/checkout@v1 - name: Install Rust (${{ matrix.rust }}) uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - name: Test uses: actions-rs/cargo@v1 with: command: test env: RUSTFLAGS: -C target_feature=${{ matrix.target_feature }} CARGO_CFG_HTTPARSE_DISABLE_SIMD_COMPILETIME: ${{ matrix.disable_compiletime }} check_x86: name: check x86 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Setup Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true target: i686-unknown-linux-musl - name: Test without SIMD uses: actions-rs/cargo@v1 with: command: test args: --target i686-unknown-linux-musl env: CARGO_CFG_HTTPARSE_DISABLE_SIMD_COMPILETIME: 1 - name: Test uses: actions-rs/cargo@v1 with: command: test args: --target i686-unknown-linux-musl msrv_x64: name: msrv (x64) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.47.0 override: true # Only build, dev-dependencies don't compile on 1.47.0 - name: Build uses: actions-rs/cargo@v1 with: command: build # This tests that aarch64 gracefully fallbacks to SWAR if neon_intrinsics aren't available (<1.59) msrv_aarch64: name: msrv (aarch64) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Install cross-compiling dependencies run: | sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu - name: Setup Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.47.0 override: true target: aarch64-unknown-linux-gnu # Only build, dev-dependencies don't compile on 1.47.0 - name: Build uses: actions-rs/cargo@v1 with: command: build args: --target aarch64-unknown-linux-gnu clippy_check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run clippy run: cargo clippy --all-targets --all-features miri: name: Test with Miri runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly components: miri override: true - name: Test run: cargo miri test # # mirai: # name: MIRAI static analysis # runs-on: ubuntu-latest # # steps: # - name: Checkout # uses: actions/checkout@v1 # # - name: Install Rust # uses: actions-rs/toolchain@v1 # with: # profile: minimal # toolchain: nightly-2023-05-09 # components: clippy, rustfmt, rustc-dev, rust-src, rust-std, llvm-tools-preview # override: true # # - name: install mirai # run: cargo install --locked --git https://github.com/facebookexperimental/MIRAI/ mirai # env: # # MIRAI_FLAGS: --diag=(default|verify|library|paranoid) # MIRAI_FLAGS: --diag=default # # - name: cargo mirai # run: cargo mirai --lib aarch64: name: Test aarch64 (neon) runs-on: ubuntu-latest env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc steps: - name: Checkout repository uses: actions/checkout@v2 - name: Setup Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true target: aarch64-unknown-linux-gnu - name: Install QEMU and dependencies run: | sudo apt-get update sudo apt-get install -y qemu-user gcc-aarch64-linux-gnu - name: Build tests run: cargo build --tests --target aarch64-unknown-linux-gnu - name: Run tests with QEMU run: | test_binaries=$(find target/aarch64-unknown-linux-gnu/debug/deps/ -type f -executable -name 'httparse-*') if [ -n "$test_binaries" ]; then for test_binary in $test_binaries do echo "Running tests in ${test_binary}" /usr/bin/qemu-aarch64 -L /usr/aarch64-linux-gnu/ "${test_binary}" done else echo "No test binaries found." fi httparse-1.10.1/.github/workflows/cibench.yml000064400000000000000000000100311046102023000172440ustar 00000000000000name: CI Benchmarks on: pull_request: branches: [master] env: CARGO_TERM_COLOR: always RUSTFLAGS: "-C target-cpu=native" jobs: benchmark: name: Run benchmarks runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Install critcmp run: cargo install critcmp - name: Run benchmarks (master) run: | git checkout master cargo bench --bench parse -- --save-baseline master - name: Run benchmarks (PR) run: | git checkout ${{ github.event.pull_request.head.sha }} cargo bench --bench parse -- --save-baseline pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} - name: Compare benchmarks run: | critcmp -t 5 master pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} benchmark-x64: name: Run x64 benchmarks runs-on: ubuntu-latest strategy: matrix: feature: [swar, sse42, avx2] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Install critcmp run: cargo install critcmp - name: Run benchmarks (master) run: | git checkout master cargo bench --bench parse -- --save-baseline master-${{ matrix.feature }} env: CARGO_CFG_HTTPARSE_DISABLE_SIMD: ${{ matrix.feature == 'swar' && '1' || '0' }} RUSTFLAGS: ${{ matrix.feature != 'swar' && format('-C target-feature=+{0}', matrix.feature) || '' }} - name: Run benchmarks (PR) run: | git checkout ${{ github.event.pull_request.head.sha }} cargo bench --bench parse -- --save-baseline pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-${{ matrix.feature }} env: CARGO_CFG_HTTPARSE_DISABLE_SIMD: ${{ matrix.feature == 'swar' && '1' || '0' }} RUSTFLAGS: ${{ matrix.feature != 'swar' && format('-C target-feature=+{0}', matrix.feature) || '' }} - name: Compare benchmarks run: | critcmp -t 5 master-${{ matrix.feature }} pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-${{ matrix.feature }} benchmark-aarch64: name: Run aarch64 benchmarks runs-on: macos-latest strategy: matrix: feature: [swar, neon] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Install critcmp run: cargo install critcmp - name: Run benchmarks (master) run: | git checkout master cargo bench --bench parse -- --save-baseline master-aarch64-${{ matrix.feature }} env: CARGO_CFG_HTTPARSE_DISABLE_SIMD: ${{ matrix.feature == 'swar' && '1' || '0' }} RUSTFLAGS: ${{ matrix.feature == 'neon' && '-C target-feature=+neon' || '' }} - name: Run benchmarks (PR) run: | git checkout ${{ github.event.pull_request.head.sha }} cargo bench --bench parse -- --save-baseline pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-aarch64-${{ matrix.feature }} env: CARGO_CFG_HTTPARSE_DISABLE_SIMD: ${{ matrix.feature == 'swar' && '1' || '0' }} RUSTFLAGS: ${{ matrix.feature == 'neon' && '-C target-feature=+neon' || '' }} - name: Compare benchmarks run: | critcmp -t 5 master-aarch64-${{ matrix.feature }} pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-aarch64-${{ matrix.feature }} httparse-1.10.1/.github/workflows/cifuzz.yml000064400000000000000000000012771046102023000171770ustar 00000000000000name: CIFuzz on: [pull_request] jobs: Fuzzing: runs-on: ubuntu-latest steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'httparse' dry-run: false language: rust - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'httparse' fuzz-seconds: 300 dry-run: false language: rust - name: Upload Crash uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts httparse-1.10.1/.gitignore000064400000000000000000000000221046102023000135200ustar 00000000000000target Cargo.lock httparse-1.10.1/Cargo.lock0000644000000422410000000000100107240ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "criterion" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_cbor", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-channel" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", "itoa 0.4.8", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "either" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "httparse" version = "1.10.1" dependencies = [ "criterion", "rand", ] [[package]] name = "itertools" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "plotters" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "regex" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "ryu" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" [[package]] name = "serde_cbor" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", ] [[package]] name = "serde_derive" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" dependencies = [ "proc-macro2", "quote", "syn 1.0.98", ] [[package]] name = "serde_json" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa 1.0.2", "ryu", "serde", ] [[package]] name = "syn" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "unicode-ident" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" [[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 1.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", "syn 1.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "web-sys" version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.96", ] httparse-1.10.1/Cargo.toml0000644000000032060000000000100107450ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "httparse" version = "1.10.1" authors = ["Sean McArthur "] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A tiny, safe, speedy, zero-copy HTTP/1.x parser." documentation = "https://docs.rs/httparse" readme = "README.md" keywords = [ "http", "parser", "no_std", ] categories = [ "network-programming", "no-std", "parser-implementations", "web-programming", ] license = "MIT OR Apache-2.0" repository = "https://github.com/seanmonstar/httparse" [features] default = ["std"] std = [] [lib] name = "httparse" path = "src/lib.rs" bench = false [[test]] name = "uri" path = "tests/uri.rs" [[bench]] name = "parse" path = "benches/parse.rs" harness = false [dev-dependencies.criterion] version = "0.3.5" [dev-dependencies.rand] version = "0.8.5" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = [ "cfg(httparse_simd)", "cfg(httparse_simd_target_feature_avx2)", "cfg(httparse_simd_target_feature_sse42)", "cfg(httparse_simd_neon_intrinsics)", ] [profile.bench] opt-level = 3 lto = true codegen-units = 1 httparse-1.10.1/Cargo.toml.orig000064400000000000000000000016361046102023000144330ustar 00000000000000[package] name = "httparse" version = "1.10.1" authors = ["Sean McArthur "] license = "MIT OR Apache-2.0" description = "A tiny, safe, speedy, zero-copy HTTP/1.x parser." repository = "https://github.com/seanmonstar/httparse" documentation = "https://docs.rs/httparse" readme = "README.md" keywords = ["http", "parser", "no_std"] categories = ["network-programming", "no-std", "parser-implementations", "web-programming"] edition = "2018" build = "build.rs" [features] default = ["std"] std = [] [dev-dependencies] criterion = "0.3.5" rand = "0.8.5" [lib] bench = false [[bench]] name = "parse" harness = false [profile.bench] lto = true codegen-units = 1 opt-level = 3 [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(httparse_simd)', 'cfg(httparse_simd_target_feature_avx2)', 'cfg(httparse_simd_target_feature_sse42)', 'cfg(httparse_simd_neon_intrinsics)', ] } httparse-1.10.1/LICENSE-APACHE000064400000000000000000000251371046102023000134720ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. httparse-1.10.1/LICENSE-MIT000064400000000000000000000020471046102023000131750ustar 00000000000000Copyright (c) 2015-2025 Sean McArthur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. httparse-1.10.1/README.md000064400000000000000000000031731046102023000130210ustar 00000000000000# httparse [![crates.io](https://img.shields.io/crates/v/httparse.svg)](https://crates.io/crates/httparse) [![Released API docs](https://docs.rs/httparse/badge.svg)](https://docs.rs/httparse) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE-MIT) [![CI](https://github.com/seanmonstar/httparse/workflows/CI/badge.svg)](https://github.com/seanmonstar/httparse/actions?query=workflow%3ACI) [![Discord chat][discord-badge]][discord-url] A push parser for the HTTP 1.x protocol. Avoids allocations. No copy. **Fast.** Works with `no_std`, simply disable the `std` Cargo feature. [Changelog](https://github.com/seanmonstar/httparse/releases) [discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord [discord-url]: https://discord.gg/kkwpueZ ## Usage ```rust let mut headers = [httparse::EMPTY_HEADER; 64]; let mut req = httparse::Request::new(&mut headers); let buf = b"GET /index.html HTTP/1.1\r\nHost"; assert!(req.parse(buf)?.is_partial()); // a partial request, so we try again once we have more data let buf = b"GET /index.html HTTP/1.1\r\nHost: example.domain\r\n\r\n"; assert!(req.parse(buf)?.is_complete()); ``` ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. httparse-1.10.1/benches/parse.rs000064400000000000000000000217311046102023000146310ustar 00000000000000use std::time::Duration; use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput, BatchSize}; const REQ_SHORT: &[u8] = b"\ GET / HTTP/1.0\r\n\ Host: example.com\r\n\ Cookie: session=60; user_id=1\r\n\r\n"; const REQ: &[u8] = b"\ GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\ Host: www.kittyhell.com\r\n\ User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\ Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\ Accept-Encoding: gzip,deflate\r\n\ Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\ Keep-Alive: 115\r\n\ Connection: keep-alive\r\n\ Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n"; fn req(c: &mut Criterion) { c.benchmark_group("req") .throughput(Throughput::Bytes(REQ.len() as u64)) .bench_function("req", |b| b.iter_batched_ref(|| { [httparse::Header { name: "", value: &[], }; 16] },|headers| { let mut req = httparse::Request::new(headers); assert_eq!( black_box(req.parse(REQ).unwrap()), httparse::Status::Complete(REQ.len()) ); }, BatchSize::SmallInput)); } fn req_short(c: &mut Criterion) { c.benchmark_group("req_short") .throughput(Throughput::Bytes(REQ_SHORT.len() as u64)) .bench_function("req_short", |b| b.iter_batched_ref(|| { [httparse::Header { name: "", value: &[], }; 16] },|headers| { let mut req = httparse::Request::new(headers); assert_eq!( req.parse(black_box(REQ_SHORT)).unwrap(), httparse::Status::Complete(REQ_SHORT.len()) ); }, BatchSize::SmallInput)); } const RESP_SHORT: &[u8] = b"\ HTTP/1.0 200 OK\r\n\ Date: Wed, 21 Oct 2015 07:28:00 GMT\r\n\ Set-Cookie: session=60; user_id=1\r\n\r\n"; // These particular headers don't all make semantic sense for a response, but they're syntactically valid. const RESP: &[u8] = b"\ HTTP/1.1 200 OK\r\n\ Date: Wed, 21 Oct 2015 07:28:00 GMT\r\n\ Host: www.kittyhell.com\r\n\ User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\ Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\ Accept-Encoding: gzip,deflate\r\n\ Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\ Keep-Alive: 115\r\n\ Connection: keep-alive\r\n\ Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n"; fn resp(c: &mut Criterion) { c.benchmark_group("resp") .throughput(Throughput::Bytes(RESP.len() as u64)) .bench_function("resp", |b| b.iter_batched_ref(|| { [httparse::Header { name: "", value: &[], }; 16] }, |headers| { let mut resp = httparse::Response::new(headers); assert_eq!( resp.parse(black_box(RESP)).unwrap(), httparse::Status::Complete(RESP.len()) ); }, BatchSize::SmallInput)); } fn resp_short(c: &mut Criterion) { c.benchmark_group("resp_short") .throughput(Throughput::Bytes(RESP_SHORT.len() as u64)) .bench_function("resp_short", |b| b.iter_batched_ref(|| { [httparse::Header { name: "", value: &[], }; 16] }, |headers| { let mut resp = httparse::Response::new(headers); assert_eq!( resp.parse(black_box(RESP_SHORT)).unwrap(), httparse::Status::Complete(RESP_SHORT.len()) ); }, BatchSize::SmallInput)); } fn uri(c: &mut Criterion) { fn _uri(c: &mut Criterion, name: &str, input: &'static [u8]) { c.benchmark_group("uri") .throughput(Throughput::Bytes(input.len() as u64)) .bench_function(name, |b| b.iter(|| { let mut b = httparse::_benchable::Bytes::new(black_box(input)); httparse::_benchable::parse_uri(&mut b).unwrap() })); } const S: &[u8] = b" "; const CHUNK64: &[u8] = b"/wp-content/uploads/2022/08/31/hello-kitty-darth-vader-pink.webp"; let chunk_4k = CHUNK64.repeat(64); // 1b to 4096b for p in 0..=12 { let n = 1 << p; _uri(c, &format!("uri_{:04}b", n), [chunk_4k[..n].to_vec(), S.into()].concat().leak()); } } fn header(c: &mut Criterion) { fn _header(c: &mut Criterion, name: &str, input: &'static [u8]) { c.benchmark_group("header") .throughput(Throughput::Bytes(input.len() as u64)) .bench_function(name, |b| b.iter_batched_ref(|| [httparse::EMPTY_HEADER; 128],|headers| { let status = httparse::parse_headers(black_box(input), headers).unwrap(); black_box(status.unwrap()).0 }, BatchSize::SmallInput)); } const RN: &[u8] = b"\r\n"; const RNRN: &[u8] = b"\r\n\r\n"; const TINY_RN: &[u8] = b"a: b\r\n"; // minimal header line const XFOOBAR: &[u8] = b"X-Foobar"; let xfoobar_4k = XFOOBAR.repeat(4096/XFOOBAR.len()); // header names 1b to 4096b for p in 0..=12 { let n = 1 << p; let payload = [&xfoobar_4k[..n], b": b", RNRN].concat().leak(); _header(c, &format!("name_{:04}b", n), payload); } // header values 1b to 4096b for p in 0..=12 { let n = 1 << p; let payload = [b"a: ", &xfoobar_4k[..n], RNRN].concat().leak(); _header(c, &format!("value_{:04}b", n), payload); } // 1 to 128 for p in 0..=7 { let n = 1 << p; _header(c, &format!("count_{:03}", n), [TINY_RN.repeat(n), RN.into()].concat().leak()); } } fn version(c: &mut Criterion) { fn _version(c: &mut Criterion, name: &str, input: &'static [u8]) { c.benchmark_group("version") .throughput(Throughput::Bytes(input.len() as u64)) .bench_function(name, |b| b.iter(|| { let mut b = httparse::_benchable::Bytes::new(black_box(input)); httparse::_benchable::parse_version(&mut b).unwrap() })); } _version(c, "http10", b"HTTP/1.0\r\n"); _version(c, "http11", b"HTTP/1.1\r\n"); _version(c, "partial", b"HTTP/1."); } fn method(c: &mut Criterion) { fn _method(c: &mut Criterion, name: &str, input: &[u8]) { c.benchmark_group("method") .throughput(Throughput::Bytes(input.len() as u64)) .bench_function(name, |b| b.iter(|| { let mut b = httparse::_benchable::Bytes::new(black_box(input)); httparse::_benchable::parse_method(&mut b).unwrap() })); } // Common methods should be fast-pathed const COMMON_METHODS: &[&str] = &["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"]; for method in COMMON_METHODS { _method(c, &method.to_lowercase(), format!("{} / HTTP/1.1\r\n", method).as_bytes()); } // Custom methods should be infrequent and thus not worth optimizing _method(c, "custom", b"CUSTOM / HTTP/1.1\r\n"); _method(c, "w3!rd", b"w3!rd / HTTP/1.1\r\n"); } fn many_requests(c: &mut Criterion) { use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng}; let mut requests = [ ("GET", 500), ("POST", 300), ("OPTIONS", 100), ("HEAD", 50), ("w3!r`d", 20), ] .iter() .flat_map(|&(method, count)| std::iter::repeat(method).take(count)) .map(|method| format!("{method} / HTTP/1.1\r\n\r\n")) .collect::>(); SliceRandom::shuffle(&mut *requests, &mut StdRng::seed_from_u64(0)); let total_bytes: usize = requests.iter().map(String::len).sum(); c.benchmark_group("many_requests") .throughput(Throughput::Bytes(total_bytes as u64)) .measurement_time(Duration::from_secs(1)) .sample_size(1000) .bench_function("_", |b| { b.iter(|| { requests.iter().for_each(|req| { let mut b = httparse::_benchable::Bytes::new(black_box(req.as_bytes())); httparse::_benchable::parse_method(&mut b).unwrap(); }); }) }); } const WARMUP: Duration = Duration::from_millis(100); const MTIME: Duration = Duration::from_millis(100); const SAMPLES: usize = 200; criterion_group!{ name = benches; config = Criterion::default().sample_size(SAMPLES).warm_up_time(WARMUP).measurement_time(MTIME); targets = req, req_short, resp, resp_short, uri, header, version, method, many_requests } criterion_main!(benches); httparse-1.10.1/build.rs000064400000000000000000000103411046102023000132020ustar 00000000000000use std::env; use std::ffi::OsString; use std::process::Command; fn main() { // We check rustc version to enable features beyond MSRV, such as: // - 1.59 => neon_intrinsics let rustc = env::var_os("RUSTC").unwrap_or(OsString::from("rustc")); let output = Command::new(rustc) .arg("--version") .output() .expect("failed to check 'rustc --version'") .stdout; let raw_version = String::from_utf8(output) .expect("rustc version output should be utf-8"); let version = match Version::parse(&raw_version) { Ok(version) => version, Err(err) => { println!("cargo:warning=failed to parse `rustc --version`: {}", err); return; } }; enable_new_features(version); } fn enable_new_features(version: Version) { enable_simd(version); } fn enable_simd(version: Version) { if env::var_os("CARGO_FEATURE_STD").is_none() { println!("cargo:warning=building for no_std disables httparse SIMD"); return; } if env::var_os("CARGO_CFG_MIRI").is_some() { println!("cargo:warning=building for Miri disables httparse SIMD"); return; } let env_disable = "CARGO_CFG_HTTPARSE_DISABLE_SIMD"; if var_is(env_disable, "1") { println!("cargo:warning=detected {} environment variable, disabling SIMD", env_disable); return; } // 1.59.0 is the first version to support neon_intrinsics if version >= Version(1, 59, 0) { println!("cargo:rustc-cfg=httparse_simd_neon_intrinsics"); } println!("cargo:rustc-cfg=httparse_simd"); // cfg(target_feature) isn't stable yet, but CARGO_CFG_TARGET_FEATURE has // a list... We aren't doing anything unsafe, since the is_x86_feature_detected // macro still checks in the actual lib, BUT! // // By peeking at the list here, we can change up slightly how we do feature // detection in the lib. If our features aren't in the feature list, we // stick with a cached runtime detection strategy. // // But if the features *are* in the list, we benefit from removing our cache, // since the compiler will eliminate several branches with its internal // cfg(target_feature) usage. let env_runtime_only = "CARGO_CFG_HTTPARSE_DISABLE_SIMD_COMPILETIME"; if var_is(env_runtime_only, "1") { println!("cargo:warning=detected {} environment variable, using runtime SIMD detection only", env_runtime_only); return; } let feature_list = match env::var_os("CARGO_CFG_TARGET_FEATURE") { Some(var) => match var.into_string() { Ok(s) => s, Err(_) => { println!("cargo:warning=CARGO_CFG_TARGET_FEATURE was not valid utf-8"); return; }, }, None => { println!("cargo:warning=CARGO_CFG_TARGET_FEATURE was not set"); return }, }; let features = feature_list.split(',').map(|s| s.trim()); if features.clone().any(|f| f == "sse4.2") { println!("cargo:rustc-cfg=httparse_simd_target_feature_sse42"); } if features.clone().any(|f| f == "avx2") { println!("cargo:rustc-cfg=httparse_simd_target_feature_avx2"); } } #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] struct Version (u32, u32, u32); impl Version { fn parse(s: &str) -> Result { if !s.starts_with("rustc ") { return Err(format!("unrecognized version string: {}", s)); } let s = s.trim_start_matches("rustc "); let mut iter = s .split('.') .take(3) .map(|s| match s.find(|c: char| !c.is_ascii_digit()) { Some(end) => &s[..end], None => s, }) .map(|s| s.parse::().map_err(|e| e.to_string())); if iter.clone().count() != 3 { return Err(format!("not enough version parts: {:?}", s)); } let major = iter.next().unwrap()?; let minor = iter.next().unwrap()?; let patch = iter.next().unwrap()?; Ok(Version(major, minor, patch)) } } fn var_is(key: &str, val: &str) -> bool { match env::var(key) { Ok(v) => v == val, Err(_) => false, } } httparse-1.10.1/clippy.toml000064400000000000000000000000161046102023000137300ustar 00000000000000msrv = "1.47" httparse-1.10.1/src/iter.rs000064400000000000000000000123611046102023000136410ustar 00000000000000use core::convert::TryFrom; use core::convert::TryInto; #[allow(missing_docs)] pub struct Bytes<'a> { start: *const u8, end: *const u8, /// INVARIANT: start <= cursor && cursor <= end cursor: *const u8, phantom: core::marker::PhantomData<&'a ()>, } #[allow(missing_docs)] impl<'a> Bytes<'a> { #[inline] pub fn new(slice: &'a [u8]) -> Bytes<'a> { let start = slice.as_ptr(); // SAFETY: obtain pointer to slice end; start points to slice start. let end = unsafe { start.add(slice.len()) }; let cursor = start; Bytes { start, end, cursor, phantom: core::marker::PhantomData, } } #[inline] pub fn pos(&self) -> usize { self.cursor as usize - self.start as usize } #[inline] pub fn peek(&self) -> Option { if self.cursor < self.end { // SAFETY: bounds checked Some(unsafe { *self.cursor }) } else { None } } /// Peek at byte `n` ahead of cursor /// /// # Safety /// /// Caller must ensure that `n <= self.len()`, otherwise `self.cursor.add(n)` is UB. /// That means there are at least `n-1` bytes between `self.cursor` and `self.end` /// and `self.cursor.add(n)` is either `self.end` or points to a valid byte. #[inline] pub unsafe fn peek_ahead(&self, n: usize) -> Option { debug_assert!(n <= self.len()); // SAFETY: by preconditions let p = unsafe { self.cursor.add(n) }; if p < self.end { // SAFETY: by preconditions, if this is not `self.end`, // then it is safe to dereference Some(unsafe { *p }) } else { None } } #[inline] pub fn peek_n<'b: 'a, U: TryFrom<&'a [u8]>>(&'b self, n: usize) -> Option { // TODO: once we bump MSRC, use const generics to allow only [u8; N] reads // TODO: drop `n` arg in favour of const // let n = core::mem::size_of::(); self.as_ref().get(..n)?.try_into().ok() } /// Advance by 1, equivalent to calling `advance(1)`. /// /// # Safety /// /// Caller must ensure that Bytes hasn't been advanced/bumped by more than [`Bytes::len()`]. #[inline] pub unsafe fn bump(&mut self) { self.advance(1) } /// Advance cursor by `n` /// /// # Safety /// /// Caller must ensure that Bytes hasn't been advanced/bumped by more than [`Bytes::len()`]. #[inline] pub unsafe fn advance(&mut self, n: usize) { self.cursor = self.cursor.add(n); debug_assert!(self.cursor <= self.end, "overflow"); } #[inline] pub fn len(&self) -> usize { self.end as usize - self.cursor as usize } #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline] pub fn slice(&mut self) -> &'a [u8] { // SAFETY: not moving position at all, so it's safe let slice = unsafe { slice_from_ptr_range(self.start, self.cursor) }; self.commit(); slice } // TODO: this is an anti-pattern, should be removed /// Deprecated. Do not use! /// # Safety /// /// Caller must ensure that `skip` is at most the number of advances (i.e., `bytes.advance(3)` /// implies a skip of at most 3). #[inline] pub unsafe fn slice_skip(&mut self, skip: usize) -> &'a [u8] { debug_assert!(skip <= self.cursor.offset_from(self.start) as usize); let head = slice_from_ptr_range(self.start, self.cursor.sub(skip)); self.commit(); head } #[inline] pub fn commit(&mut self) { self.start = self.cursor } /// # Safety /// /// see [`Bytes::advance`] safety comment. #[inline] pub unsafe fn advance_and_commit(&mut self, n: usize) { self.advance(n); self.commit(); } #[inline] pub fn as_ptr(&self) -> *const u8 { self.cursor } #[inline] pub fn start(&self) -> *const u8 { self.start } #[inline] pub fn end(&self) -> *const u8 { self.end } /// # Safety /// /// Must ensure invariant `bytes.start() <= ptr && ptr <= bytes.end()`. #[inline] pub unsafe fn set_cursor(&mut self, ptr: *const u8) { debug_assert!(ptr >= self.start); debug_assert!(ptr <= self.end); self.cursor = ptr; } } impl AsRef<[u8]> for Bytes<'_> { #[inline] fn as_ref(&self) -> &[u8] { // SAFETY: not moving position at all, so it's safe unsafe { slice_from_ptr_range(self.cursor, self.end) } } } /// # Safety /// /// Must ensure start and end point to the same memory object to uphold memory safety. #[inline] unsafe fn slice_from_ptr_range<'a>(start: *const u8, end: *const u8) -> &'a [u8] { debug_assert!(start <= end); core::slice::from_raw_parts(start, end as usize - start as usize) } impl Iterator for Bytes<'_> { type Item = u8; #[inline] fn next(&mut self) -> Option { if self.cursor < self.end { // SAFETY: bounds checked dereference unsafe { let b = *self.cursor; self.bump(); Some(b) } } else { None } } } httparse-1.10.1/src/lib.rs000064400000000000000000003141131046102023000134440ustar 00000000000000#![cfg_attr(not(feature = "std"), no_std)] #![deny( missing_docs, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks )] #![cfg_attr(test, deny(warnings))] //! # httparse //! //! A push library for parsing HTTP/1.x requests and responses. //! //! The focus is on speed and safety. Unsafe code is used to keep parsing fast, //! but unsafety is contained in a submodule, with invariants enforced. The //! parsing internals use an `Iterator` instead of direct indexing, while //! skipping bounds checks. //! //! With Rust 1.27.0 or later, support for SIMD is enabled automatically. //! If building an executable to be run on multiple platforms, and thus //! not passing `target_feature` or `target_cpu` flags to the compiler, //! runtime detection can still detect SSE4.2 or AVX2 support to provide //! massive wins. //! //! If compiling for a specific target, remembering to include //! `-C target_cpu=native` allows the detection to become compile time checks, //! making it *even* faster. use core::{fmt, mem, result, str}; use core::mem::MaybeUninit; use crate::iter::Bytes; mod iter; #[macro_use] mod macros; mod simd; #[doc(hidden)] // Expose some internal functions so we can bench them individually // WARNING: Exported for internal benchmarks, not fit for public consumption pub mod _benchable { pub use super::parse_uri; pub use super::parse_version; pub use super::parse_method; pub use super::iter::Bytes; } /// Determines if byte is a method token char. /// /// > ```notrust /// > token = 1*tchar /// > /// > tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" /// > / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" /// > / DIGIT / ALPHA /// > ; any VCHAR, except delimiters /// > ``` #[inline] fn is_method_token(b: u8) -> bool { match b { // For the majority case, this can be faster than the table lookup. b'A'..=b'Z' => true, _ => TOKEN_MAP[b as usize], } } // char codes to accept URI string. // i.e. b'!' <= char and char != 127 // TODO: Make a stricter checking for URI string? static URI_MAP: [bool; 256] = byte_map!( b'!'..=0x7e | 0x80..=0xFF ); #[inline] pub(crate) fn is_uri_token(b: u8) -> bool { URI_MAP[b as usize] } static TOKEN_MAP: [bool; 256] = byte_map!( b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' | b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~' ); #[inline] pub(crate) fn is_header_name_token(b: u8) -> bool { TOKEN_MAP[b as usize] } static HEADER_VALUE_MAP: [bool; 256] = byte_map!( b'\t' | b' '..=0x7e | 0x80..=0xFF ); #[inline] pub(crate) fn is_header_value_token(b: u8) -> bool { HEADER_VALUE_MAP[b as usize] } /// An error in parsing. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Error { /// Invalid byte in header name. HeaderName, /// Invalid byte in header value. HeaderValue, /// Invalid byte in new line. NewLine, /// Invalid byte in Response status. Status, /// Invalid byte where token is required. Token, /// Parsed more headers than provided buffer can contain. TooManyHeaders, /// Invalid byte in HTTP version. Version, } impl Error { #[inline] fn description_str(&self) -> &'static str { match *self { Error::HeaderName => "invalid header name", Error::HeaderValue => "invalid header value", Error::NewLine => "invalid new line", Error::Status => "invalid response status", Error::Token => "invalid token", Error::TooManyHeaders => "too many headers", Error::Version => "invalid HTTP version", } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.description_str()) } } #[cfg(feature = "std")] impl std::error::Error for Error { fn description(&self) -> &str { self.description_str() } } /// An error in parsing a chunk size. // Note: Move this into the error enum once v2.0 is released. #[derive(Debug, PartialEq, Eq)] pub struct InvalidChunkSize; impl fmt::Display for InvalidChunkSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("invalid chunk size") } } /// A Result of any parsing action. /// /// If the input is invalid, an `Error` will be returned. Note that incomplete /// data is not considered invalid, and so will not return an error, but rather /// a `Ok(Status::Partial)`. pub type Result = result::Result, Error>; /// The result of a successful parse pass. /// /// `Complete` is used when the buffer contained the complete value. /// `Partial` is used when parsing did not reach the end of the expected value, /// but no invalid data was found. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Status { /// The completed result. Complete(T), /// A partial result. Partial } impl Status { /// Convenience method to check if status is complete. #[inline] pub fn is_complete(&self) -> bool { match *self { Status::Complete(..) => true, Status::Partial => false } } /// Convenience method to check if status is partial. #[inline] pub fn is_partial(&self) -> bool { match *self { Status::Complete(..) => false, Status::Partial => true } } /// Convenience method to unwrap a Complete value. Panics if the status is /// `Partial`. #[inline] pub fn unwrap(self) -> T { match self { Status::Complete(t) => t, Status::Partial => panic!("Tried to unwrap Status::Partial") } } } /// Parser configuration. #[derive(Clone, Debug, Default)] pub struct ParserConfig { allow_spaces_after_header_name_in_responses: bool, allow_obsolete_multiline_headers_in_responses: bool, allow_multiple_spaces_in_request_line_delimiters: bool, allow_multiple_spaces_in_response_status_delimiters: bool, allow_space_before_first_header_name: bool, ignore_invalid_headers_in_responses: bool, ignore_invalid_headers_in_requests: bool, } impl ParserConfig { /// Sets whether spaces and tabs should be allowed after header names in responses. pub fn allow_spaces_after_header_name_in_responses( &mut self, value: bool, ) -> &mut Self { self.allow_spaces_after_header_name_in_responses = value; self } /// Sets whether multiple spaces are allowed as delimiters in request lines. /// /// # Background /// /// The [latest version of the HTTP/1.1 spec][spec] allows implementations to parse multiple /// whitespace characters in place of the `SP` delimiters in the request line, including: /// /// > SP, HTAB, VT (%x0B), FF (%x0C), or bare CR /// /// This option relaxes the parser to allow for multiple spaces, but does *not* allow the /// request line to contain the other mentioned whitespace characters. /// /// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.3.p.3 pub fn allow_multiple_spaces_in_request_line_delimiters(&mut self, value: bool) -> &mut Self { self.allow_multiple_spaces_in_request_line_delimiters = value; self } /// Whether multiple spaces are allowed as delimiters in request lines. pub fn multiple_spaces_in_request_line_delimiters_are_allowed(&self) -> bool { self.allow_multiple_spaces_in_request_line_delimiters } /// Sets whether multiple spaces are allowed as delimiters in response status lines. /// /// # Background /// /// The [latest version of the HTTP/1.1 spec][spec] allows implementations to parse multiple /// whitespace characters in place of the `SP` delimiters in the response status line, /// including: /// /// > SP, HTAB, VT (%x0B), FF (%x0C), or bare CR /// /// This option relaxes the parser to allow for multiple spaces, but does *not* allow the status /// line to contain the other mentioned whitespace characters. /// /// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.3 pub fn allow_multiple_spaces_in_response_status_delimiters(&mut self, value: bool) -> &mut Self { self.allow_multiple_spaces_in_response_status_delimiters = value; self } /// Whether multiple spaces are allowed as delimiters in response status lines. pub fn multiple_spaces_in_response_status_delimiters_are_allowed(&self) -> bool { self.allow_multiple_spaces_in_response_status_delimiters } /// Sets whether obsolete multiline headers should be allowed. /// /// This is an obsolete part of HTTP/1. Use at your own risk. If you are /// building an HTTP library, the newlines (`\r` and `\n`) should be /// replaced by spaces before handing the header value to the user. /// /// # Example /// /// ```rust /// let buf = b"HTTP/1.1 200 OK\r\nFolded-Header: hello\r\n there \r\n\r\n"; /// let mut headers = [httparse::EMPTY_HEADER; 16]; /// let mut response = httparse::Response::new(&mut headers); /// /// let res = httparse::ParserConfig::default() /// .allow_obsolete_multiline_headers_in_responses(true) /// .parse_response(&mut response, buf); /// /// assert_eq!(res, Ok(httparse::Status::Complete(buf.len()))); /// /// assert_eq!(response.headers.len(), 1); /// assert_eq!(response.headers[0].name, "Folded-Header"); /// assert_eq!(response.headers[0].value, b"hello\r\n there"); /// ``` pub fn allow_obsolete_multiline_headers_in_responses( &mut self, value: bool, ) -> &mut Self { self.allow_obsolete_multiline_headers_in_responses = value; self } /// Whether obsolete multiline headers should be allowed. pub fn obsolete_multiline_headers_in_responses_are_allowed(&self) -> bool { self.allow_obsolete_multiline_headers_in_responses } /// Sets whether white space before the first header is allowed /// /// This is not allowed by spec but some browsers ignore it. So this an option for /// compatibility. /// See https://github.com/curl/curl/issues/11605 for reference /// # Example /// /// ```rust /// let buf = b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n"; /// let mut headers = [httparse::EMPTY_HEADER; 1]; /// let mut response = httparse::Response::new(&mut headers[..]); /// let result = httparse::ParserConfig::default() /// .allow_space_before_first_header_name(true) /// .parse_response(&mut response, buf); /// /// assert_eq!(result, Ok(httparse::Status::Complete(buf.len()))); /// assert_eq!(response.version.unwrap(), 1); /// assert_eq!(response.code.unwrap(), 200); /// assert_eq!(response.reason.unwrap(), "OK"); /// assert_eq!(response.headers.len(), 1); /// assert_eq!(response.headers[0].name, "Space-Before-Header"); /// assert_eq!(response.headers[0].value, &b"hello there"[..]); /// ``` pub fn allow_space_before_first_header_name(&mut self, value: bool) -> &mut Self { self.allow_space_before_first_header_name = value; self } /// Whether white space before first header is allowed or not pub fn space_before_first_header_name_are_allowed(&self) -> bool { self.allow_space_before_first_header_name } /// Parses a request with the given config. pub fn parse_request<'buf>( &self, request: &mut Request<'_, 'buf>, buf: &'buf [u8], ) -> Result { request.parse_with_config(buf, self) } /// Parses a request with the given config and buffer for headers pub fn parse_request_with_uninit_headers<'headers, 'buf>( &self, request: &mut Request<'headers, 'buf>, buf: &'buf [u8], headers: &'headers mut [MaybeUninit>], ) -> Result { request.parse_with_config_and_uninit_headers(buf, self, headers) } /// Sets whether invalid header lines should be silently ignored in responses. /// /// This mimicks the behaviour of major browsers. You probably don't want this. /// You should only want this if you are implementing a proxy whose main /// purpose is to sit in front of browsers whose users access arbitrary content /// which may be malformed, and they expect everything that works without /// the proxy to keep working with the proxy. /// /// This option will prevent `ParserConfig::parse_response` from returning /// an error encountered when parsing a header, except if the error was caused /// by the character NUL (ASCII code 0), as Chrome specifically always reject /// those, or if the error was caused by a lone character `\r`, as Firefox and /// Chrome behave differently in that case. /// /// The ignorable errors are: /// * empty header names; /// * characters that are not allowed in header names, except for `\0` and `\r`; /// * when `allow_spaces_after_header_name_in_responses` is not enabled, /// spaces and tabs between the header name and the colon; /// * missing colon between header name and value; /// * when `allow_obsolete_multiline_headers_in_responses` is not enabled, /// headers using obsolete line folding. /// * characters that are not allowed in header values except for `\0` and `\r`. /// /// If an ignorable error is encountered, the parser tries to find the next /// line in the input to resume parsing the rest of the headers. As lines /// contributing to a header using obsolete line folding always start /// with whitespace, those will be ignored too. An error will be emitted /// nonetheless if it finds `\0` or a lone `\r` while looking for the /// next line. pub fn ignore_invalid_headers_in_responses( &mut self, value: bool, ) -> &mut Self { self.ignore_invalid_headers_in_responses = value; self } /// Sets whether invalid header lines should be silently ignored in requests. pub fn ignore_invalid_headers_in_requests( &mut self, value: bool, ) -> &mut Self { self.ignore_invalid_headers_in_requests = value; self } /// Parses a response with the given config. pub fn parse_response<'buf>( &self, response: &mut Response<'_, 'buf>, buf: &'buf [u8], ) -> Result { response.parse_with_config(buf, self) } /// Parses a response with the given config and buffer for headers pub fn parse_response_with_uninit_headers<'headers, 'buf>( &self, response: &mut Response<'headers, 'buf>, buf: &'buf [u8], headers: &'headers mut [MaybeUninit>], ) -> Result { response.parse_with_config_and_uninit_headers(buf, self, headers) } } /// A parsed Request. /// /// The optional values will be `None` if a parse was not complete, and did not /// parse the associated property. This allows you to inspect the parts that /// could be parsed, before reading more, in case you wish to exit early. /// /// # Example /// /// ```no_run /// let buf = b"GET /404 HTTP/1.1\r\nHost:"; /// let mut headers = [httparse::EMPTY_HEADER; 16]; /// let mut req = httparse::Request::new(&mut headers); /// let res = req.parse(buf).unwrap(); /// if res.is_partial() { /// match req.path { /// Some(ref path) => { /// // check router for path. /// // /404 doesn't exist? we could stop parsing /// }, /// None => { /// // must read more and parse again /// } /// } /// } /// ``` #[derive(Debug, Eq, PartialEq)] pub struct Request<'headers, 'buf> { /// The request method, such as `GET`. pub method: Option<&'buf str>, /// The request path, such as `/about-us`. pub path: Option<&'buf str>, /// The request minor version, such as `1` for `HTTP/1.1`. pub version: Option, /// The request headers. pub headers: &'headers mut [Header<'buf>] } impl<'h, 'b> Request<'h, 'b> { /// Creates a new Request, using a slice of headers you allocate. #[inline] pub fn new(headers: &'h mut [Header<'b>]) -> Request<'h, 'b> { Request { method: None, path: None, version: None, headers, } } fn parse_with_config_and_uninit_headers( &mut self, buf: &'b [u8], config: &ParserConfig, mut headers: &'h mut [MaybeUninit>], ) -> Result { let orig_len = buf.len(); let mut bytes = Bytes::new(buf); complete!(skip_empty_lines(&mut bytes)); let method = complete!(parse_method(&mut bytes)); self.method = Some(method); if config.allow_multiple_spaces_in_request_line_delimiters { complete!(skip_spaces(&mut bytes)); } self.path = Some(complete!(parse_uri(&mut bytes))); if config.allow_multiple_spaces_in_request_line_delimiters { complete!(skip_spaces(&mut bytes)); } self.version = Some(complete!(parse_version(&mut bytes))); newline!(bytes); let len = orig_len - bytes.len(); let headers_len = complete!(parse_headers_iter_uninit( &mut headers, &mut bytes, &HeaderParserConfig { allow_spaces_after_header_name: false, allow_obsolete_multiline_headers: false, allow_space_before_first_header_name: config.allow_space_before_first_header_name, ignore_invalid_headers: config.ignore_invalid_headers_in_requests }, )); /* SAFETY: see `parse_headers_iter_uninit` guarantees */ self.headers = unsafe { assume_init_slice(headers) }; Ok(Status::Complete(len + headers_len)) } /// Try to parse a buffer of bytes into the Request, /// except use an uninitialized slice of `Header`s. /// /// For more information, see `parse` pub fn parse_with_uninit_headers( &mut self, buf: &'b [u8], headers: &'h mut [MaybeUninit>], ) -> Result { self.parse_with_config_and_uninit_headers(buf, &Default::default(), headers) } fn parse_with_config(&mut self, buf: &'b [u8], config: &ParserConfig) -> Result { let headers = mem::take(&mut self.headers); /* SAFETY: see `parse_headers_iter_uninit` guarantees */ unsafe { let headers: *mut [Header<'_>] = headers; let headers = headers as *mut [MaybeUninit>]; match self.parse_with_config_and_uninit_headers(buf, config, &mut *headers) { Ok(Status::Complete(idx)) => Ok(Status::Complete(idx)), other => { // put the original headers back self.headers = &mut *(headers as *mut [Header<'_>]); other }, } } } /// Try to parse a buffer of bytes into the Request. /// /// Returns byte offset in `buf` to start of HTTP body. pub fn parse(&mut self, buf: &'b [u8]) -> Result { self.parse_with_config(buf, &Default::default()) } } #[inline] fn skip_empty_lines(bytes: &mut Bytes<'_>) -> Result<()> { loop { let b = bytes.peek(); match b { Some(b'\r') => { // SAFETY: peeked and found `\r`, so it's safe to bump 1 pos unsafe { bytes.bump() }; expect!(bytes.next() == b'\n' => Err(Error::NewLine)); } Some(b'\n') => { // SAFETY: peeked and found `\n`, so it's safe to bump 1 pos unsafe { bytes.bump(); } } Some(..) => { bytes.slice(); return Ok(Status::Complete(())); } None => return Ok(Status::Partial), } } } #[inline] fn skip_spaces(bytes: &mut Bytes<'_>) -> Result<()> { loop { let b = bytes.peek(); match b { Some(b' ') => { // SAFETY: peeked and found ` `, so it's safe to bump 1 pos unsafe { bytes.bump() }; } Some(..) => { bytes.slice(); return Ok(Status::Complete(())); } None => return Ok(Status::Partial), } } } /// A parsed Response. /// /// See `Request` docs for explanation of optional values. #[derive(Debug, Eq, PartialEq)] pub struct Response<'headers, 'buf> { /// The response minor version, such as `1` for `HTTP/1.1`. pub version: Option, /// The response code, such as `200`. pub code: Option, /// The response reason-phrase, such as `OK`. /// /// Contains an empty string if the reason-phrase was missing or contained invalid characters. pub reason: Option<&'buf str>, /// The response headers. pub headers: &'headers mut [Header<'buf>] } impl<'h, 'b> Response<'h, 'b> { /// Creates a new `Response` using a slice of `Header`s you have allocated. #[inline] pub fn new(headers: &'h mut [Header<'b>]) -> Response<'h, 'b> { Response { version: None, code: None, reason: None, headers, } } /// Try to parse a buffer of bytes into this `Response`. pub fn parse(&mut self, buf: &'b [u8]) -> Result { self.parse_with_config(buf, &ParserConfig::default()) } fn parse_with_config(&mut self, buf: &'b [u8], config: &ParserConfig) -> Result { let headers = mem::take(&mut self.headers); // SAFETY: see guarantees of [`parse_headers_iter_uninit`], which leaves no uninitialized // headers around. On failure, the original headers are restored. unsafe { let headers: *mut [Header<'_>] = headers; let headers = headers as *mut [MaybeUninit>]; match self.parse_with_config_and_uninit_headers(buf, config, &mut *headers) { Ok(Status::Complete(idx)) => Ok(Status::Complete(idx)), other => { // put the original headers back self.headers = &mut *(headers as *mut [Header<'_>]); other }, } } } fn parse_with_config_and_uninit_headers( &mut self, buf: &'b [u8], config: &ParserConfig, mut headers: &'h mut [MaybeUninit>], ) -> Result { let orig_len = buf.len(); let mut bytes = Bytes::new(buf); complete!(skip_empty_lines(&mut bytes)); self.version = Some(complete!(parse_version(&mut bytes))); space!(bytes or Error::Version); if config.allow_multiple_spaces_in_response_status_delimiters { complete!(skip_spaces(&mut bytes)); } self.code = Some(complete!(parse_code(&mut bytes))); // RFC7230 says there must be 'SP' and then reason-phrase, but admits // its only for legacy reasons. With the reason-phrase completely // optional (and preferred to be omitted) in HTTP2, we'll just // handle any response that doesn't include a reason-phrase, because // it's more lenient, and we don't care anyways. // // So, a SP means parse a reason-phrase. // A newline means go to headers. // Anything else we'll say is a malformed status. match next!(bytes) { b' ' => { if config.allow_multiple_spaces_in_response_status_delimiters { complete!(skip_spaces(&mut bytes)); } bytes.slice(); self.reason = Some(complete!(parse_reason(&mut bytes))); }, b'\r' => { expect!(bytes.next() == b'\n' => Err(Error::Status)); bytes.slice(); self.reason = Some(""); }, b'\n' => { bytes.slice(); self.reason = Some(""); } _ => return Err(Error::Status), } let len = orig_len - bytes.len(); let headers_len = complete!(parse_headers_iter_uninit( &mut headers, &mut bytes, &HeaderParserConfig { allow_spaces_after_header_name: config.allow_spaces_after_header_name_in_responses, allow_obsolete_multiline_headers: config.allow_obsolete_multiline_headers_in_responses, allow_space_before_first_header_name: config.allow_space_before_first_header_name, ignore_invalid_headers: config.ignore_invalid_headers_in_responses } )); /* SAFETY: see `parse_headers_iter_uninit` guarantees */ self.headers = unsafe { assume_init_slice(headers) }; Ok(Status::Complete(len + headers_len)) } } /// Represents a parsed header. #[derive(Copy, Clone, Eq, PartialEq)] pub struct Header<'a> { /// The name portion of a header. /// /// A header name must be valid ASCII-US, so it's safe to store as a `&str`. pub name: &'a str, /// The value portion of a header. /// /// While headers **should** be ASCII-US, the specification allows for /// values that may not be, and so the value is stored as bytes. pub value: &'a [u8], } impl fmt::Debug for Header<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("Header"); f.field("name", &self.name); if let Ok(value) = str::from_utf8(self.value) { f.field("value", &value); } else { f.field("value", &self.value); } f.finish() } } /// An empty header, useful for constructing a `Header` array to pass in for /// parsing. /// /// # Example /// /// ``` /// let headers = [httparse::EMPTY_HEADER; 64]; /// ``` pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" }; #[inline] #[doc(hidden)] #[allow(missing_docs)] // WARNING: Exported for internal benchmarks, not fit for public consumption pub fn parse_version(bytes: &mut Bytes) -> Result { if let Some(eight) = bytes.peek_n::<[u8; 8]>(8) { // NOTE: should be const once MSRV >= 1.44 let h10: u64 = u64::from_ne_bytes(*b"HTTP/1.0"); let h11: u64 = u64::from_ne_bytes(*b"HTTP/1.1"); // SAFETY: peek_n(8) before ensure within bounds unsafe { bytes.advance(8); } let block = u64::from_ne_bytes(eight); // NOTE: should be match once h10 & h11 are consts return if block == h10 { Ok(Status::Complete(0)) } else if block == h11 { Ok(Status::Complete(1)) } else { Err(Error::Version) }; } // else (but not in `else` because of borrow checker) // If there aren't at least 8 bytes, we still want to detect early // if this is a valid version or not. If it is, we'll return Partial. expect!(bytes.next() == b'H' => Err(Error::Version)); expect!(bytes.next() == b'T' => Err(Error::Version)); expect!(bytes.next() == b'T' => Err(Error::Version)); expect!(bytes.next() == b'P' => Err(Error::Version)); expect!(bytes.next() == b'/' => Err(Error::Version)); expect!(bytes.next() == b'1' => Err(Error::Version)); expect!(bytes.next() == b'.' => Err(Error::Version)); Ok(Status::Partial) } #[inline] #[doc(hidden)] #[allow(missing_docs)] // WARNING: Exported for internal benchmarks, not fit for public consumption pub fn parse_method<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { const GET: [u8; 4] = *b"GET "; const POST: [u8; 4] = *b"POST"; match bytes.peek_n::<[u8; 4]>(4) { Some(GET) => { // SAFETY: we matched "GET " which has 4 bytes and is ASCII let method = unsafe { bytes.advance(4); // advance cursor past "GET " str::from_utf8_unchecked(bytes.slice_skip(1)) // "GET" without space }; Ok(Status::Complete(method)) } // SAFETY: // If `bytes.peek_n...` returns a Some([u8; 4]), // then we are assured that `bytes` contains at least 4 bytes. // Thus `bytes.len() >= 4`, // and it is safe to peek at byte 4 with `bytes.peek_ahead(4)`. Some(POST) if unsafe { bytes.peek_ahead(4) } == Some(b' ') => { // SAFETY: we matched "POST " which has 5 bytes let method = unsafe { bytes.advance(5); // advance cursor past "POST " str::from_utf8_unchecked(bytes.slice_skip(1)) // "POST" without space }; Ok(Status::Complete(method)) } _ => parse_token(bytes), } } /// From [RFC 7230](https://tools.ietf.org/html/rfc7230): /// /// > ```notrust /// > reason-phrase = *( HTAB / SP / VCHAR / obs-text ) /// > HTAB = %x09 ; horizontal tab /// > VCHAR = %x21-7E ; visible (printing) characters /// > obs-text = %x80-FF /// > ``` /// /// > A.2. Changes from RFC 2616 /// > /// > Non-US-ASCII content in header fields and the reason phrase /// > has been obsoleted and made opaque (the TEXT rule was removed). #[inline] fn parse_reason<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { let mut seen_obs_text = false; loop { let b = next!(bytes); if b == b'\r' { expect!(bytes.next() == b'\n' => Err(Error::Status)); return Ok(Status::Complete( // SAFETY: (1) calling bytes.slice_skip(2) is safe, because at least two next! calls // advance the bytes iterator. // (2) calling from_utf8_unchecked is safe, because the bytes returned by slice_skip // were validated to be allowed US-ASCII chars by the other arms of the if/else or // otherwise `seen_obs_text` is true and an empty string is returned instead. unsafe { let bytes = bytes.slice_skip(2); if !seen_obs_text { // all bytes up till `i` must have been HTAB / SP / VCHAR str::from_utf8_unchecked(bytes) } else { // obs-text characters were found, so return the fallback empty string "" } }, )); } else if b == b'\n' { return Ok(Status::Complete( // SAFETY: (1) calling bytes.slice_skip(1) is safe, because at least one next! call // advance the bytes iterator. // (2) see (2) of safety comment above. unsafe { let bytes = bytes.slice_skip(1); if !seen_obs_text { // all bytes up till `i` must have been HTAB / SP / VCHAR str::from_utf8_unchecked(bytes) } else { // obs-text characters were found, so return the fallback empty string "" } }, )); } else if !(b == 0x09 || b == b' ' || (0x21..=0x7E).contains(&b) || b >= 0x80) { return Err(Error::Status); } else if b >= 0x80 { seen_obs_text = true; } } } #[inline] fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { let b = next!(bytes); if !is_method_token(b) { // First char must be a token char, it can't be a space which would indicate an empty token. return Err(Error::Token); } loop { let b = next!(bytes); if b == b' ' { return Ok(Status::Complete( // SAFETY: all bytes up till `i` must have been `is_method_token` and therefore also utf-8. unsafe { str::from_utf8_unchecked(bytes.slice_skip(1)) }, )); } else if !is_method_token(b) { return Err(Error::Token); } } } #[inline] #[doc(hidden)] #[allow(missing_docs)] // WARNING: Exported for internal benchmarks, not fit for public consumption pub fn parse_uri<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> { let start = bytes.pos(); simd::match_uri_vectored(bytes); let end = bytes.pos(); if next!(bytes) == b' ' { // URI must have at least one char if end == start { return Err(Error::Token); } // SAFETY: all bytes up till `i` must have been `is_token` and therefore also utf-8. match str::from_utf8(unsafe { bytes.slice_skip(1) }) { Ok(uri) => Ok(Status::Complete(uri)), Err(_) => Err(Error::Token), } } else { Err(Error::Token) } } #[inline] fn parse_code(bytes: &mut Bytes<'_>) -> Result { let hundreds = expect!(bytes.next() == b'0'..=b'9' => Err(Error::Status)); let tens = expect!(bytes.next() == b'0'..=b'9' => Err(Error::Status)); let ones = expect!(bytes.next() == b'0'..=b'9' => Err(Error::Status)); Ok(Status::Complete((hundreds - b'0') as u16 * 100 + (tens - b'0') as u16 * 10 + (ones - b'0') as u16)) } /// Parse a buffer of bytes as headers. /// /// The return value, if complete and successful, includes the index of the /// buffer that parsing stopped at, and a sliced reference to the parsed /// headers. The length of the slice will be equal to the number of properly /// parsed headers. /// /// # Example /// /// ``` /// let buf = b"Host: foo.bar\nAccept: */*\n\nblah blah"; /// let mut headers = [httparse::EMPTY_HEADER; 4]; /// assert_eq!(httparse::parse_headers(buf, &mut headers), /// Ok(httparse::Status::Complete((27, &[ /// httparse::Header { name: "Host", value: b"foo.bar" }, /// httparse::Header { name: "Accept", value: b"*/*" } /// ][..])))); /// ``` pub fn parse_headers<'b: 'h, 'h>( src: &'b [u8], mut dst: &'h mut [Header<'b>], ) -> Result<(usize, &'h [Header<'b>])> { let mut iter = Bytes::new(src); let pos = complete!(parse_headers_iter(&mut dst, &mut iter, &HeaderParserConfig::default())); Ok(Status::Complete((pos, dst))) } #[inline] fn parse_headers_iter<'a>( headers: &mut &mut [Header<'a>], bytes: &mut Bytes<'a>, config: &HeaderParserConfig, ) -> Result { parse_headers_iter_uninit( /* SAFETY: see `parse_headers_iter_uninit` guarantees */ unsafe { deinit_slice_mut(headers) }, bytes, config, ) } unsafe fn deinit_slice_mut<'a, 'b, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { let s: *mut &mut [T] = s; let s = s as *mut &mut [MaybeUninit]; &mut *s } unsafe fn assume_init_slice(s: &mut [MaybeUninit]) -> &mut [T] { let s: *mut [MaybeUninit] = s; let s = s as *mut [T]; &mut *s } #[derive(Clone, Debug, Default)] struct HeaderParserConfig { allow_spaces_after_header_name: bool, allow_obsolete_multiline_headers: bool, allow_space_before_first_header_name: bool, ignore_invalid_headers: bool, } /* Function which parsers headers into uninitialized buffer. * * Guarantees that it doesn't write garbage, so casting * &mut &mut [Header] -> &mut &mut [MaybeUninit
] * is safe here. * * Also it promises `headers` get shrunk to number of initialized headers, * so casting the other way around after calling this function is safe */ fn parse_headers_iter_uninit<'a>( headers: &mut &mut [MaybeUninit>], bytes: &mut Bytes<'a>, config: &HeaderParserConfig ) -> Result { /* Flow of this function is pretty complex, especially with macros, * so this struct makes sure we shrink `headers` to only parsed ones. * Comparing to previous code, this only may introduce some additional * instructions in case of early return */ struct ShrinkOnDrop<'r1, 'r2, 'a> { headers: &'r1 mut &'r2 mut [MaybeUninit>], num_headers: usize, } impl Drop for ShrinkOnDrop<'_, '_, '_> { fn drop(&mut self) { let headers = mem::take(self.headers); /* SAFETY: num_headers is the number of initialized headers */ let headers = unsafe { headers.get_unchecked_mut(..self.num_headers) }; *self.headers = headers; } } let mut autoshrink = ShrinkOnDrop { headers, num_headers: 0, }; // Track starting pointer to calculate the number of bytes parsed. let start = bytes.as_ref().as_ptr() as usize; let mut result = Err(Error::TooManyHeaders); let mut iter = autoshrink.headers.iter_mut(); macro_rules! maybe_continue_after_obsolete_line_folding { ($bytes:ident, $label:lifetime) => { if config.allow_obsolete_multiline_headers { match $bytes.peek() { None => { // Next byte may be a space, in which case that header // is using obsolete line folding, so we may have more // whitespace to skip after colon. return Ok(Status::Partial); } Some(b' ') | Some(b'\t') => { // The space will be consumed next iteration. continue $label; } _ => { // There is another byte after the end of the line, // but it's not whitespace, so it's probably another // header or the final line return. This header is thus // empty. }, } } } } 'headers: loop { // Return the error `$err` if `ignore_invalid_headers_in_responses` // is false, otherwise find the end of the current line and resume // parsing on the next one. macro_rules! handle_invalid_char { ($bytes:ident, $b:ident, $err:ident) => { if !config.ignore_invalid_headers { return Err(Error::$err); } let mut b = $b; loop { if b == b'\r' { expect!(bytes.next() == b'\n' => Err(Error::$err)); break; } if b == b'\n' { break; } if b == b'\0' { return Err(Error::$err); } b = next!($bytes); } $bytes.slice(); continue 'headers; }; } // a newline here means the head is over! let b = next!(bytes); if b == b'\r' { expect!(bytes.next() == b'\n' => Err(Error::NewLine)); let end = bytes.as_ref().as_ptr() as usize; result = Ok(Status::Complete(end - start)); break; } if b == b'\n' { let end = bytes.as_ref().as_ptr() as usize; result = Ok(Status::Complete(end - start)); break; } if !is_header_name_token(b) { if config.allow_space_before_first_header_name && autoshrink.num_headers == 0 && (b == b' ' || b == b'\t') { //advance past white space and then try parsing header again while let Some(peek) = bytes.peek() { if peek == b' ' || peek == b'\t' { next!(bytes); } else { break; } } bytes.slice(); continue 'headers; } else { handle_invalid_char!(bytes, b, HeaderName); } } #[allow(clippy::never_loop)] // parse header name until colon let header_name: &str = 'name: loop { simd::match_header_name_vectored(bytes); let mut b = next!(bytes); // SAFETY: previously bumped by 1 with next! -> always safe. let bslice = unsafe { bytes.slice_skip(1) }; // SAFETY: previous call to match_header_name_vectored ensured all bytes are valid // header name chars, and as such also valid utf-8. let name = unsafe { str::from_utf8_unchecked(bslice) }; if b == b':' { break 'name name; } if config.allow_spaces_after_header_name { while b == b' ' || b == b'\t' { b = next!(bytes); if b == b':' { bytes.slice(); break 'name name; } } } handle_invalid_char!(bytes, b, HeaderName); }; let mut b; #[allow(clippy::never_loop)] let value_slice = 'value: loop { // eat white space between colon and value 'whitespace_after_colon: loop { b = next!(bytes); if b == b' ' || b == b'\t' { bytes.slice(); continue 'whitespace_after_colon; } if is_header_value_token(b) { break 'whitespace_after_colon; } if b == b'\r' { expect!(bytes.next() == b'\n' => Err(Error::HeaderValue)); } else if b != b'\n' { handle_invalid_char!(bytes, b, HeaderValue); } maybe_continue_after_obsolete_line_folding!(bytes, 'whitespace_after_colon); let whitespace_slice = bytes.slice(); // This produces an empty slice that points to the beginning // of the whitespace. break 'value &whitespace_slice[0..0]; } 'value_lines: loop { // parse value till EOL simd::match_header_value_vectored(bytes); let b = next!(bytes); //found_ctl let skip = if b == b'\r' { expect!(bytes.next() == b'\n' => Err(Error::HeaderValue)); 2 } else if b == b'\n' { 1 } else { handle_invalid_char!(bytes, b, HeaderValue); }; maybe_continue_after_obsolete_line_folding!(bytes, 'value_lines); // SAFETY: having just checked that a newline exists, it's safe to skip it. unsafe { break 'value bytes.slice_skip(skip); } } }; let uninit_header = match iter.next() { Some(header) => header, None => break 'headers }; // trim trailing whitespace in the header let header_value = if let Some(last_visible) = value_slice .iter() .rposition(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') { // There is at least one non-whitespace character. &value_slice[0..last_visible+1] } else { // There is no non-whitespace character. This can only happen when value_slice is // empty. value_slice }; *uninit_header = MaybeUninit::new(Header { name: header_name, value: header_value, }); autoshrink.num_headers += 1; } result } /// Parse a buffer of bytes as a chunk size. /// /// The return value, if complete and successful, includes the index of the /// buffer that parsing stopped at, and the size of the following chunk. /// /// # Example /// /// ``` /// let buf = b"4\r\nRust\r\n0\r\n\r\n"; /// assert_eq!(httparse::parse_chunk_size(buf), /// Ok(httparse::Status::Complete((3, 4)))); /// ``` pub fn parse_chunk_size(buf: &[u8]) -> result::Result, InvalidChunkSize> { const RADIX: u64 = 16; let mut bytes = Bytes::new(buf); let mut size = 0; let mut in_chunk_size = true; let mut in_ext = false; let mut count = 0; loop { let b = next!(bytes); match b { b'0' ..= b'9' if in_chunk_size => { if count > 15 { return Err(InvalidChunkSize); } count += 1; if cfg!(debug_assertions) && size > (u64::MAX / RADIX) { // actually unreachable!(), because count stops the loop at 15 digits before // we can reach u64::MAX / RADIX == 0xfffffffffffffff, which requires 15 hex // digits. This stops mirai reporting a false alarm regarding the `size *= // RADIX` multiplication below. return Err(InvalidChunkSize); } size *= RADIX; size += (b - b'0') as u64; }, b'a' ..= b'f' if in_chunk_size => { if count > 15 { return Err(InvalidChunkSize); } count += 1; if cfg!(debug_assertions) && size > (u64::MAX / RADIX) { return Err(InvalidChunkSize); } size *= RADIX; size += (b + 10 - b'a') as u64; } b'A' ..= b'F' if in_chunk_size => { if count > 15 { return Err(InvalidChunkSize); } count += 1; if cfg!(debug_assertions) && size > (u64::MAX / RADIX) { return Err(InvalidChunkSize); } size *= RADIX; size += (b + 10 - b'A') as u64; } b'\r' => { match next!(bytes) { b'\n' => break, _ => return Err(InvalidChunkSize), } } // If we weren't in the extension yet, the ";" signals its start b';' if !in_ext => { in_ext = true; in_chunk_size = false; } // "Linear white space" is ignored between the chunk size and the // extension separator token (";") due to the "implied *LWS rule". b'\t' | b' ' if !in_ext && !in_chunk_size => {} // LWS can follow the chunk size, but no more digits can come b'\t' | b' ' if in_chunk_size => in_chunk_size = false, // We allow any arbitrary octet once we are in the extension, since // they all get ignored anyway. According to the HTTP spec, valid // extensions would have a more strict syntax: // (token ["=" (token | quoted-string)]) // but we gain nothing by rejecting an otherwise valid chunk size. _ if in_ext => {} // Finally, if we aren't in the extension and we're reading any // other octet, the chunk size line is invalid! _ => return Err(InvalidChunkSize), } } Ok(Status::Complete((bytes.pos(), size))) } #[cfg(test)] mod tests { use super::{Error, Request, Response, Status, EMPTY_HEADER, parse_chunk_size}; const NUM_OF_HEADERS: usize = 4; macro_rules! req { ($name:ident, $buf:expr, |$arg:ident| $body:expr) => ( req! {$name, $buf, Ok(Status::Complete($buf.len())), |$arg| $body } ); ($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => ( #[test] fn $name() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut req = Request::new(&mut headers[..]); let status = req.parse($buf.as_ref()); assert_eq!(status, $len); closure(req); fn closure($arg: Request) { $body } } ) } req! { test_request_simple, b"GET / HTTP/1.1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { test_request_simple_with_query_params, b"GET /thing?data=a HTTP/1.1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/thing?data=a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { test_request_simple_with_whatwg_query_params, b"GET /thing?data=a^ HTTP/1.1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/thing?data=a^"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { test_request_headers, b"GET / HTTP/1.1\r\nHost: foo.com\r\nCookie: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 2); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.com"); assert_eq!(req.headers[1].name, "Cookie"); assert_eq!(req.headers[1].value, b""); } } req! { test_request_headers_optional_whitespace, b"GET / HTTP/1.1\r\nHost: \tfoo.com\t \r\nCookie: \t \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 2); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.com"); assert_eq!(req.headers[1].name, "Cookie"); assert_eq!(req.headers[1].value, b""); } } req! { // test the scalar parsing test_request_header_value_htab_short, b"GET / HTTP/1.1\r\nUser-Agent: some\tagent\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "User-Agent"); assert_eq!(req.headers[0].value, b"some\tagent"); } } req! { // test the sse42 parsing test_request_header_value_htab_med, b"GET / HTTP/1.1\r\nUser-Agent: 1234567890some\tagent\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "User-Agent"); assert_eq!(req.headers[0].value, b"1234567890some\tagent"); } } req! { // test the avx2 parsing test_request_header_value_htab_long, b"GET / HTTP/1.1\r\nUser-Agent: 1234567890some\t1234567890agent1234567890\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "User-Agent"); assert_eq!(req.headers[0].value, &b"1234567890some\t1234567890agent1234567890"[..]); } } req! { // test the avx2 parsing test_request_header_no_space_after_colon, b"GET / HTTP/1.1\r\nUser-Agent:omg-no-space1234567890some1234567890agent1234567890\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "User-Agent"); assert_eq!(req.headers[0].value, &b"omg-no-space1234567890some1234567890agent1234567890"[..]); } } req! { test_request_headers_max, b"GET / HTTP/1.1\r\nA: A\r\nB: B\r\nC: C\r\nD: D\r\n\r\n", |req| { assert_eq!(req.headers.len(), NUM_OF_HEADERS); } } req! { test_request_multibyte, b"GET / HTTP/1.1\r\nHost: foo.com\r\nUser-Agent: \xe3\x81\xb2\xe3/1.0\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 2); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.com"); assert_eq!(req.headers[1].name, "User-Agent"); assert_eq!(req.headers[1].value, b"\xe3\x81\xb2\xe3/1.0"); } } // A single byte which is part of a method is not invalid req! { test_request_one_byte_method, b"G", Ok(Status::Partial), |_req| {} } // A subset of a method is a partial method, not invalid req! { test_request_partial_method, b"GE", Ok(Status::Partial), |_req| {} } // A method, without the delimiting space, is a partial request req! { test_request_method_no_delimiter, b"GET", Ok(Status::Partial), |_req| {} } // Regression test: assert that a partial read with just the method and // space results in a partial, rather than a token error from uri parsing. req! { test_request_method_only, b"GET ", Ok(Status::Partial), |_req| {} } req! { test_request_partial, b"GET / HTTP/1.1\r\n\r", Ok(Status::Partial), |_req| {} } req! { test_request_partial_version, b"GET / HTTP/1.", Ok(Status::Partial), |_req| {} } req! { test_request_method_path_no_delimiter, b"GET /", Ok(Status::Partial), |_req| {} } req! { test_request_method_path_only, b"GET / ", Ok(Status::Partial), |_req| {} } req! { test_request_partial_parses_headers_as_much_as_it_can, b"GET / HTTP/1.1\r\nHost: yolo\r\n", Ok(crate::Status::Partial), |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), NUM_OF_HEADERS); // doesn't slice since not Complete assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"yolo"); } } req! { test_request_newlines, b"GET / HTTP/1.1\nHost: foo.bar\n\n", |_r| {} } req! { test_request_empty_lines_prefix, b"\r\n\r\nGET / HTTP/1.1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { test_request_empty_lines_prefix_lf_only, b"\n\nGET / HTTP/1.1\n\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { test_request_path_backslash, b"\n\nGET /\\?wayne\\=5 HTTP/1.1\n\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/\\?wayne\\=5"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { test_request_with_invalid_token_delimiter, b"GET\n/ HTTP/1.1\r\nHost: foo.bar\r\n\r\n", Err(crate::Error::Token), |_r| {} } req! { test_request_with_invalid_but_short_version, b"GET / HTTP/1!", Err(crate::Error::Version), |_r| {} } req! { test_request_with_empty_method, b" / HTTP/1.1\r\n\r\n", Err(crate::Error::Token), |_r| {} } req! { test_request_with_empty_path, b"GET HTTP/1.1\r\n\r\n", Err(crate::Error::Token), |_r| {} } req! { test_request_with_empty_method_and_path, b" HTTP/1.1\r\n\r\n", Err(crate::Error::Token), |_r| {} } macro_rules! res { ($name:ident, $buf:expr, |$arg:ident| $body:expr) => ( res! {$name, $buf, Ok(Status::Complete($buf.len())), |$arg| $body } ); ($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => ( #[test] fn $name() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut res = Response::new(&mut headers[..]); let status = res.parse($buf.as_ref()); assert_eq!(status, $len); closure(res); fn closure($arg: Response) { $body } } ) } res! { test_response_simple, b"HTTP/1.1 200 OK\r\n\r\n", |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 200); assert_eq!(res.reason.unwrap(), "OK"); } } res! { test_response_newlines, b"HTTP/1.0 403 Forbidden\nServer: foo.bar\n\n", |_r| {} } res! { test_response_reason_missing, b"HTTP/1.1 200 \r\n\r\n", |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 200); assert_eq!(res.reason.unwrap(), ""); } } res! { test_response_reason_missing_no_space, b"HTTP/1.1 200\r\n\r\n", |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 200); assert_eq!(res.reason.unwrap(), ""); } } res! { test_response_reason_missing_no_space_with_headers, b"HTTP/1.1 200\r\nFoo: bar\r\n\r\n", |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 200); assert_eq!(res.reason.unwrap(), ""); assert_eq!(res.headers.len(), 1); assert_eq!(res.headers[0].name, "Foo"); assert_eq!(res.headers[0].value, b"bar"); } } res! { test_response_reason_with_space_and_tab, b"HTTP/1.1 101 Switching Protocols\t\r\n\r\n", |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 101); assert_eq!(res.reason.unwrap(), "Switching Protocols\t"); } } static RESPONSE_REASON_WITH_OBS_TEXT_BYTE: &[u8] = b"HTTP/1.1 200 X\xFFZ\r\n\r\n"; res! { test_response_reason_with_obsolete_text_byte, RESPONSE_REASON_WITH_OBS_TEXT_BYTE, |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 200); // Empty string fallback in case of obs-text assert_eq!(res.reason.unwrap(), ""); } } res! { test_response_reason_with_nul_byte, b"HTTP/1.1 200 \x00\r\n\r\n", Err(crate::Error::Status), |_res| {} } res! { test_response_version_missing_space, b"HTTP/1.1", Ok(Status::Partial), |_res| {} } res! { test_response_code_missing_space, b"HTTP/1.1 200", Ok(Status::Partial), |_res| {} } res! { test_response_partial_parses_headers_as_much_as_it_can, b"HTTP/1.1 200 OK\r\nServer: yolo\r\n", Ok(crate::Status::Partial), |res| { assert_eq!(res.version.unwrap(), 1); assert_eq!(res.code.unwrap(), 200); assert_eq!(res.reason.unwrap(), "OK"); assert_eq!(res.headers.len(), NUM_OF_HEADERS); // doesn't slice since not Complete assert_eq!(res.headers[0].name, "Server"); assert_eq!(res.headers[0].value, b"yolo"); } } res! { test_response_empty_lines_prefix_lf_only, b"\n\nHTTP/1.1 200 OK\n\n", |_res| {} } res! { test_response_no_cr, b"HTTP/1.0 200\nContent-type: text/html\n\n", |res| { assert_eq!(res.version.unwrap(), 0); assert_eq!(res.code.unwrap(), 200); assert_eq!(res.reason.unwrap(), ""); assert_eq!(res.headers.len(), 1); assert_eq!(res.headers[0].name, "Content-type"); assert_eq!(res.headers[0].value, b"text/html"); } } /// Check all subset permutations of a partial request line with no headers #[test] fn partial_permutations() { let req_str = "GET / HTTP/1.1\r\n\r\n"; let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut req = Request::new(&mut headers[..]); for i in 0..req_str.len() { let status = req.parse(req_str[..i].as_bytes()); assert_eq!( status, Ok(Status::Partial), "partial request line should return partial. \ Portion which failed: '{seg}' (below {i})", seg = &req_str[..i] ); } } static RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials : true\r\nBread: baguette\r\n\r\n"; #[test] fn test_forbid_response_with_whitespace_between_header_name_and_colon() { let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_allow_response_with_whitespace_between_header_name_and_colon() { let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_spaces_after_header_name_in_responses(true) .parse_response(&mut response, RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON); assert_eq!(result, Ok(Status::Complete(77))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 2); assert_eq!(response.headers[0].name, "Access-Control-Allow-Credentials"); assert_eq!(response.headers[0].value, &b"true"[..]); assert_eq!(response.headers[1].name, "Bread"); assert_eq!(response.headers[1].value, &b"baguette"[..]); } #[test] fn test_ignore_header_line_with_whitespaces_after_header_name_in_response() { let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON); assert_eq!(result, Ok(Status::Complete(77))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); } static REQUEST_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON: &[u8] = b"GET / HTTP/1.1\r\nHost : localhost\r\n\r\n"; #[test] fn test_forbid_request_with_whitespace_between_header_name_and_colon() { let mut headers = [EMPTY_HEADER; 1]; let mut request = Request::new(&mut headers[..]); let result = request.parse(REQUEST_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_ignore_header_line_with_whitespaces_after_header_name_in_request() { let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON); assert_eq!(result, Ok(Status::Complete(36))); } static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START: &[u8] = b"HTTP/1.1 200 OK\r\nLine-Folded-Header: \r\n \r\n hello there\r\n\r\n"; #[test] fn test_forbid_response_with_obsolete_line_folding_at_start() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_allow_response_with_obsolete_line_folding_at_start() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_obsolete_multiline_headers_in_responses(true) .parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START); assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START.len()))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Line-Folded-Header"); assert_eq!(response.headers[0].value, &b"hello there"[..]); } static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END: &[u8] = b"HTTP/1.1 200 OK\r\nLine-Folded-Header: hello there\r\n \r\n \r\n\r\n"; #[test] fn test_forbid_response_with_obsolete_line_folding_at_end() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_allow_response_with_obsolete_line_folding_at_end() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_obsolete_multiline_headers_in_responses(true) .parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END); assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END.len()))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Line-Folded-Header"); assert_eq!(response.headers[0].value, &b"hello there"[..]); } static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE: &[u8] = b"HTTP/1.1 200 OK\r\nLine-Folded-Header: hello \r\n \r\n there\r\n\r\n"; #[test] fn test_forbid_response_with_obsolete_line_folding_in_middle() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_allow_response_with_obsolete_line_folding_in_middle() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_obsolete_multiline_headers_in_responses(true) .parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE); assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE.len()))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Line-Folded-Header"); assert_eq!(response.headers[0].value, &b"hello \r\n \r\n there"[..]); } static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER: &[u8] = b"HTTP/1.1 200 OK\r\nLine-Folded-Header: \r\n \r\n \r\n\r\n"; #[test] fn test_forbid_response_with_obsolete_line_folding_in_empty_header() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_allow_response_with_obsolete_line_folding_in_empty_header() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_obsolete_multiline_headers_in_responses(true) .parse_response(&mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER); assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER.len()))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Line-Folded-Header"); assert_eq!(response.headers[0].value, &b""[..]); } #[test] fn test_chunk_size() { assert_eq!(parse_chunk_size(b"0\r\n"), Ok(Status::Complete((3, 0)))); assert_eq!(parse_chunk_size(b"12\r\nchunk"), Ok(Status::Complete((4, 18)))); assert_eq!(parse_chunk_size(b"3086d\r\n"), Ok(Status::Complete((7, 198765)))); assert_eq!(parse_chunk_size(b"3735AB1;foo bar*\r\n"), Ok(Status::Complete((18, 57891505)))); assert_eq!(parse_chunk_size(b"3735ab1 ; baz \r\n"), Ok(Status::Complete((16, 57891505)))); assert_eq!(parse_chunk_size(b"77a65\r"), Ok(Status::Partial)); assert_eq!(parse_chunk_size(b"ab"), Ok(Status::Partial)); assert_eq!(parse_chunk_size(b"567f8a\rfoo"), Err(crate::InvalidChunkSize)); assert_eq!(parse_chunk_size(b"567f8a\rfoo"), Err(crate::InvalidChunkSize)); assert_eq!(parse_chunk_size(b"567xf8a\r\n"), Err(crate::InvalidChunkSize)); assert_eq!(parse_chunk_size(b"ffffffffffffffff\r\n"), Ok(Status::Complete((18, u64::MAX)))); assert_eq!(parse_chunk_size(b"1ffffffffffffffff\r\n"), Err(crate::InvalidChunkSize)); assert_eq!(parse_chunk_size(b"Affffffffffffffff\r\n"), Err(crate::InvalidChunkSize)); assert_eq!(parse_chunk_size(b"fffffffffffffffff\r\n"), Err(crate::InvalidChunkSize)); } static RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS: &[u8] = b"HTTP/1.1 200 OK\r\n\r\n"; #[test] fn test_forbid_response_with_multiple_space_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS); assert_eq!(result, Err(crate::Error::Status)); } #[test] fn test_allow_response_with_multiple_space_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_response_status_delimiters(true) .parse_response(&mut response, RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS); assert_eq!(result, Ok(Status::Complete(RESPONSE_WITH_MULTIPLE_SPACE_DELIMITERS.len()))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 0); } /// This is technically allowed by the spec, but we only support multiple spaces as an option, /// not stray `\r`s. static RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS: &[u8] = b"HTTP/1.1 200\rOK\r\n\r\n"; #[test] fn test_forbid_response_with_weird_whitespace_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS); assert_eq!(result, Err(crate::Error::Status)); } #[test] fn test_still_forbid_response_with_weird_whitespace_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_response_status_delimiters(true) .parse_response(&mut response, RESPONSE_WITH_WEIRD_WHITESPACE_DELIMITERS); assert_eq!(result, Err(crate::Error::Status)); } static REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS: &[u8] = b"GET / HTTP/1.1\r\n\r\n"; #[test] fn test_forbid_request_with_multiple_space_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let result = request.parse(REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS); assert_eq!(result, Err(crate::Error::Token)); } #[test] fn test_allow_request_with_multiple_space_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS); assert_eq!(result, Ok(Status::Complete(REQUEST_WITH_MULTIPLE_SPACE_DELIMITERS.len()))); assert_eq!(request.method.unwrap(), "GET"); assert_eq!(request.path.unwrap(), "/"); assert_eq!(request.version.unwrap(), 1); assert_eq!(request.headers.len(), 0); } /// This is technically allowed by the spec, but we only support multiple spaces as an option, /// not stray `\r`s. static REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS: &[u8] = b"GET\r/\rHTTP/1.1\r\n\r\n"; #[test] fn test_forbid_request_with_weird_whitespace_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let result = request.parse(REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS); assert_eq!(result, Err(crate::Error::Token)); } #[test] fn test_still_forbid_request_with_weird_whitespace_delimiters() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, REQUEST_WITH_WEIRD_WHITESPACE_DELIMITERS); assert_eq!(result, Err(crate::Error::Token)); } static REQUEST_WITH_MULTIPLE_SPACES_AND_BAD_PATH: &[u8] = b"GET /foo ohno HTTP/1.1\r\n\r\n"; #[test] fn test_request_with_multiple_spaces_and_bad_path() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, REQUEST_WITH_MULTIPLE_SPACES_AND_BAD_PATH); assert_eq!(result, Err(crate::Error::Version)); } // This test ensure there is an error when there is a DEL character in the path // since we allow all char from 0x21 code except DEL, this test ensure that DEL // is not allowed in the path static REQUEST_WITH_DEL_IN_PATH: &[u8] = b"GET /foo\x7Fohno HTTP/1.1\r\n\r\n"; #[test] fn test_request_with_del_in_path() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, crate::tests::REQUEST_WITH_DEL_IN_PATH); assert_eq!(result, Err(crate::Error::Token)); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow for this test fn test_all_utf8_char_in_paths() { // two code points for i in 128..256 { for j in 128..256 { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let bytes = [i as u8, j as u8]; match core::str::from_utf8(&bytes) { Ok(s) => { let first_line = format!("GET /{} HTTP/1.1\r\n\r\n", s); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, first_line.as_bytes()); assert_eq!(result, Ok(Status::Complete(20)), "failed for utf8 char i: {}, j: {}", i, j); }, Err(_) => { let mut first_line = b"GET /".to_vec(); first_line.extend(&bytes); first_line.extend(b" HTTP/1.1\r\n\r\n"); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, first_line.as_slice()); assert_eq!(result, Err(crate::Error::Token), "failed for utf8 char i: {}, j: {}", i, j); }, }; // three code points starting from 0xe0 if i < 0xe0 { continue; } for k in 128..256 { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let bytes = [i as u8, j as u8, k as u8]; match core::str::from_utf8(&bytes) { Ok(s) => { let first_line = format!("GET /{} HTTP/1.1\r\n\r\n", s); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, first_line.as_bytes()); assert_eq!(result, Ok(Status::Complete(21)), "failed for utf8 char i: {}, j: {}, k: {}", i, j, k); }, Err(_) => { let mut first_line = b"GET /".to_vec(); first_line.extend(&bytes); first_line.extend(b" HTTP/1.1\r\n\r\n"); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, first_line.as_slice()); assert_eq!(result, Err(crate::Error::Token), "failed for utf8 char i: {}, j: {}, k: {}", i, j, k); }, }; // four code points starting from 0xf0 if i < 0xf0 { continue; } for l in 128..256 { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut request = Request::new(&mut headers[..]); let bytes = [i as u8, j as u8, k as u8, l as u8]; match core::str::from_utf8(&bytes) { Ok(s) => { let first_line = format!("GET /{} HTTP/1.1\r\n\r\n", s); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, first_line.as_bytes()); assert_eq!(result, Ok(Status::Complete(22)), "failed for utf8 char i: {}, j: {}, k: {}, l: {}", i, j, k, l); }, Err(_) => { let mut first_line = b"GET /".to_vec(); first_line.extend(&bytes); first_line.extend(b" HTTP/1.1\r\n\r\n"); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_request_line_delimiters(true) .parse_request(&mut request, first_line.as_slice()); assert_eq!(result, Err(crate::Error::Token), "failed for utf8 char i: {}, j: {}, k: {}, l: {}", i, j, k, l); }, }; } } } } } static RESPONSE_WITH_SPACES_IN_CODE: &[u8] = b"HTTP/1.1 99 200 OK\r\n\r\n"; #[test] fn test_response_with_spaces_in_code() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_multiple_spaces_in_response_status_delimiters(true) .parse_response(&mut response, RESPONSE_WITH_SPACES_IN_CODE); assert_eq!(result, Err(crate::Error::Status)); } #[test] fn test_response_with_empty_header_name() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\n: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_spaces_after_header_name_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Ok(Status::Complete(45))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); } #[test] fn test_request_with_empty_header_name() { const RESPONSE: &[u8] = b"GET / HTTP/1.1\r\n: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, RESPONSE); assert_eq!(result, Ok(Status::Complete(44))); } #[test] fn test_request_with_whitespace_between_header_name_and_colon() { const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials : true\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_spaces_after_header_name_in_responses(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_response_with_invalid_char_between_header_name_and_colon() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials\xFF : true\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_spaces_after_header_name_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Ok(Status::Complete(79))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); } #[test] fn test_request_with_invalid_char_between_header_name_and_colon() { const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials\xFF : true\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Ok(Status::Complete(78))); } #[test] fn test_ignore_header_line_with_missing_colon_in_response() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Ok(Status::Complete(70))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); } #[test] fn test_ignore_header_line_with_missing_colon_in_request() { const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Ok(Status::Complete(69))); } #[test] fn test_response_header_with_missing_colon_with_folding() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials \r\n hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_obsolete_multiline_headers_in_responses(true) .allow_spaces_after_header_name_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Ok(Status::Complete(81))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); } #[test] fn test_request_header_with_missing_colon_with_folding() { const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials \r\n hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Ok(Status::Complete(80))); } #[test] fn test_response_header_with_nul_in_header_name() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Cred\0entials: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_request_header_with_nul_in_header_name() { const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Cred\0entials: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_header_with_cr_in_header_name() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Cred\rentials: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Cred\rentials: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_header_with_nul_in_whitespace_before_colon() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials \0: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_spaces_after_header_name_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); let result = crate::ParserConfig::default() .allow_spaces_after_header_name_in_responses(true) .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderName)); const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials \0: hello\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_header_with_nul_in_value() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials: hell\0o\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderValue)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderValue)); const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials: hell\0o\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderValue)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderValue)); } #[test] fn test_header_with_invalid_char_in_value() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials: hell\x01o\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderValue)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Ok(Status::Complete(78))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials: hell\x01o\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderValue)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Ok(Status::Complete(77))); assert_eq!(request.version.unwrap(), 1); assert_eq!(request.method.unwrap(), "GET"); assert_eq!(request.path.unwrap(), "/"); assert_eq!(request.headers.len(), 1); assert_eq!(request.headers[0].name, "Bread"); assert_eq!(request.headers[0].value, &b"baguette"[..]); } #[test] fn test_header_with_invalid_char_in_value_with_folding() { const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials: hell\x01o \n world!\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, RESPONSE); assert_eq!(result, Err(crate::Error::HeaderValue)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_responses(true) .parse_response(&mut response, RESPONSE); assert_eq!(result, Ok(Status::Complete(88))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Bread"); assert_eq!(response.headers[0].value, &b"baguette"[..]); const REQUEST: &[u8] = b"GET / HTTP/1.1\r\nAccess-Control-Allow-Credentials: hell\x01o \n world!\r\nBread: baguette\r\n\r\n"; let mut headers = [EMPTY_HEADER; 2]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, REQUEST); assert_eq!(result, Err(crate::Error::HeaderValue)); let result = crate::ParserConfig::default() .ignore_invalid_headers_in_requests(true) .parse_request(&mut request, REQUEST); assert_eq!(result, Ok(Status::Complete(87))); assert_eq!(request.version.unwrap(), 1); assert_eq!(request.method.unwrap(), "GET"); assert_eq!(request.path.unwrap(), "/"); assert_eq!(request.headers.len(), 1); assert_eq!(request.headers[0].name, "Bread"); assert_eq!(request.headers[0].value, &b"baguette"[..]); } #[test] fn test_method_within_buffer() { const REQUEST: &[u8] = b"GET / HTTP/1.1\r\n\r\n"; let mut headers = [EMPTY_HEADER; 0]; let mut request = Request::new(&mut headers[..]); crate::ParserConfig::default() .parse_request(&mut request, REQUEST) .unwrap(); // SAFETY: will not wrap let buf_end = unsafe { REQUEST.as_ptr().add(REQUEST.len()) }; // Check that the method str is within the buffer let method = request.method.unwrap(); assert!(REQUEST.as_ptr() <= method.as_ptr()); assert!(method.as_ptr() <= buf_end); } static RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER: &[u8] = b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n"; #[test] fn test_forbid_response_with_space_before_first_header() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = response.parse(RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER); assert_eq!(result, Err(crate::Error::HeaderName)); } #[test] fn test_allow_response_response_with_space_before_first_header() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .allow_space_before_first_header_name(true) .parse_response(&mut response, RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER); assert_eq!( result, Ok(Status::Complete( RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER.len() )) ); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "Space-Before-Header"); assert_eq!(response.headers[0].value, &b"hello there"[..]); } #[test] fn test_no_space_after_colon() { let mut headers = [EMPTY_HEADER; 1]; let mut response = Response::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_response(&mut response, b"HTTP/1.1 200 OK\r\nfoo:bar\r\n\r\n"); assert_eq!(result, Ok(Status::Complete(28))); assert_eq!(response.version.unwrap(), 1); assert_eq!(response.code.unwrap(), 200); assert_eq!(response.reason.unwrap(), "OK"); assert_eq!(response.headers.len(), 1); assert_eq!(response.headers[0].name, "foo"); assert_eq!(response.headers[0].value, &b"bar"[..]); } #[test] fn test_request_with_leading_space() { let mut headers = [EMPTY_HEADER; 1]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, b" GET / HTTP/1.1\r\nfoo:bar\r\n\r\n"); assert_eq!(result, Err(Error::Token)); } #[test] fn test_request_with_invalid_method() { let mut headers = [EMPTY_HEADER; 1]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default() .parse_request(&mut request, b"P()ST / HTTP/1.1\r\nfoo:bar\r\n\r\n"); assert_eq!(result, Err(Error::Token)); } #[test] fn test_utf8_in_path_ok() { let mut headers = [EMPTY_HEADER; 1]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default().parse_request(&mut request, b"GET /test?post=I\xE2\x80\x99msorryIforkedyou HTTP/1.1\r\nHost: example.org\r\n\r\n"); assert_eq!(result, Ok(Status::Complete(67))); assert_eq!(request.version.unwrap(), 1); assert_eq!(request.method.unwrap(), "GET"); assert_eq!(request.path.unwrap(), "/test?post=I’msorryIforkedyou"); assert_eq!(request.headers.len(), 1); assert_eq!(request.headers[0].name, "Host"); assert_eq!(request.headers[0].value, &b"example.org"[..]); } #[test] fn test_bad_utf8_in_path() { let mut headers = [EMPTY_HEADER; 1]; let mut request = Request::new(&mut headers[..]); let result = crate::ParserConfig::default().parse_request(&mut request, b"GET /test?post=I\xE2msorryIforkedyou HTTP/1.1\r\nHost: example.org\r\n\r\n"); assert_eq!(result, Err(crate::Error::Token)); } } httparse-1.10.1/src/macros.rs000064400000000000000000000026741046102023000141700ustar 00000000000000//! Utility macros macro_rules! next { ($bytes:ident) => ({ match $bytes.next() { Some(b) => b, None => return Ok(Status::Partial) } }) } macro_rules! expect { ($bytes:ident.next() == $pat:pat => $ret:expr) => { expect!(next!($bytes) => $pat |? $ret) }; ($e:expr => $pat:pat |? $ret:expr) => { match $e { v@$pat => v, _ => return $ret } }; } macro_rules! complete { ($e:expr) => { match $e? { Status::Complete(v) => v, Status::Partial => return Ok(Status::Partial) } } } macro_rules! byte_map { ($($p:pat)|+) => {{ const fn make_map() -> [bool; 256] { let mut ret = [false; 256]; let mut i = 0; while i < 256 { ret[i] = matches!(i as u8, $($p)|+); i += 1; } ret } make_map() }} } macro_rules! space { ($bytes:ident or $err:expr) => ({ expect!($bytes.next() == b' ' => Err($err)); $bytes.slice(); }) } macro_rules! newline { ($bytes:ident) => ({ match next!($bytes) { b'\r' => { expect!($bytes.next() == b'\n' => Err(Error::NewLine)); $bytes.slice(); }, b'\n' => { $bytes.slice(); }, _ => return Err(Error::NewLine) } }) } httparse-1.10.1/src/simd/avx2.rs000064400000000000000000000154321046102023000145140ustar 00000000000000use crate::iter::Bytes; #[inline] #[target_feature(enable = "avx2")] pub unsafe fn match_uri_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 32 { let advance = match_url_char_32_avx(bytes.as_ref()); bytes.advance(advance); if advance != 32 { return; } } // NOTE: use SWAR for <32B, more efficient than falling back to SSE4.2 super::swar::match_uri_vectored(bytes) } #[inline(always)] #[allow(non_snake_case, overflowing_literals)] #[allow(unused)] unsafe fn match_url_char_32_avx(buf: &[u8]) -> usize { // NOTE: This check might be not necessary since this function is only used in // `match_uri_vectored` where buffer overflow is taken care of. debug_assert!(buf.len() >= 32); #[cfg(target_arch = "x86")] use core::arch::x86::*; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; // pointer to buffer let ptr = buf.as_ptr(); // %x21-%x7e %x80-%xff // // Character ranges allowed by this function, can also be interpreted as: // 33 =< (x != 127) =< 255 // // Create a vector full of DEL (0x7f) characters. let DEL: __m256i = _mm256_set1_epi8(0x7f); // Create a vector full of exclamation mark (!) (0x21) characters. // Used as lower threshold, characters in URLs cannot be smaller than this. let LOW: __m256i = _mm256_set1_epi8(0x21); // Load a chunk of 32 bytes from `ptr` as a vector. // We can check 32 bytes in parallel at most with AVX2 since // YMM registers can only have 256 bits most. let dat = _mm256_lddqu_si256(ptr as *const _); // unsigned comparison dat >= LOW // // `_mm256_max_epu8` creates a new vector by comparing vectors `dat` and `LOW` // and picks the max. values from each for all indices. // So if a byte in `dat` is <= 32, it'll be represented as 33 // which is the smallest valid character. // // Then, we compare the new vector with `dat` for equality. // // `_mm256_cmpeq_epi8` returns a new vector where; // * matching bytes are set to 0xFF (all bits set), // * nonmatching bytes are set to 0 (no bits set). let low = _mm256_cmpeq_epi8(_mm256_max_epu8(dat, LOW), dat); // Similar to what we did before, but now invalid characters are set to 0xFF. let del = _mm256_cmpeq_epi8(dat, DEL); // We glue the both comparisons via `_mm256_andnot_si256`. // // Since the representation of truthiness differ in these comparisons, // we are in need of bitwise NOT to convert valid characters of `del`. let bit = _mm256_andnot_si256(del, low); // This creates a bitmask from the most significant bit of each byte. // Simply, we're converting a vector value to scalar value here. let res = _mm256_movemask_epi8(bit) as u32; // Count trailing zeros to find the first encountered invalid character. // Bitwise NOT is required once again to flip truthiness. // TODO: use .trailing_ones() once MSRV >= 1.46 (!res).trailing_zeros() as usize } #[target_feature(enable = "avx2")] pub unsafe fn match_header_value_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 32 { let advance = match_header_value_char_32_avx(bytes.as_ref()); bytes.advance(advance); if advance != 32 { return; } } // NOTE: use SWAR for <32B, more efficient than falling back to SSE4.2 super::swar::match_header_value_vectored(bytes) } #[inline(always)] #[allow(non_snake_case)] #[allow(unused)] unsafe fn match_header_value_char_32_avx(buf: &[u8]) -> usize { debug_assert!(buf.len() >= 32); #[cfg(target_arch = "x86")] use core::arch::x86::*; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; let ptr = buf.as_ptr(); // %x09 %x20-%x7e %x80-%xff // Create a vector full of horizontal tab (\t) (0x09) characters. let TAB: __m256i = _mm256_set1_epi8(0x09); // Create a vector full of DEL (0x7f) characters. let DEL: __m256i = _mm256_set1_epi8(0x7f); // Create a vector full of space (0x20) characters. let LOW: __m256i = _mm256_set1_epi8(0x20); // Load a chunk of 32 bytes from `ptr` as a vector. let dat = _mm256_lddqu_si256(ptr as *const _); // unsigned comparison dat >= LOW // // Same as what we do in `match_url_char_32_avx`. // This time the lower threshold is set to space character though. let low = _mm256_cmpeq_epi8(_mm256_max_epu8(dat, LOW), dat); // Check if `dat` includes `TAB` characters. let tab = _mm256_cmpeq_epi8(dat, TAB); // Check if `dat` includes `DEL` characters. let del = _mm256_cmpeq_epi8(dat, DEL); // Combine all comparisons together, notice that we're also using OR // to connect `low` and `tab` but flip bits of `del`. // // In the end, this is simply: // ~del & (low | tab) let bit = _mm256_andnot_si256(del, _mm256_or_si256(low, tab)); // This creates a bitmask from the most significant bit of each byte. // Creates a scalar value from vector value. let res = _mm256_movemask_epi8(bit) as u32; // Count trailing zeros to find the first encountered invalid character. // Bitwise NOT is required once again to flip truthiness. // TODO: use .trailing_ones() once MSRV >= 1.46 (!res).trailing_zeros() as usize } #[test] fn avx2_code_matches_uri_chars_table() { if !is_x86_feature_detected!("avx2") { return; } #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_uri_vectored)); for (b, allowed) in crate::URI_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_uri_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[test] fn avx2_code_matches_header_value_chars_table() { if !is_x86_feature_detected!("avx2") { return; } #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_header_value_vectored)); for (b, allowed) in crate::HEADER_VALUE_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_header_value_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[cfg(test)] unsafe fn byte_is_allowed(byte: u8, f: unsafe fn(bytes: &mut Bytes<'_>)) -> bool { let slice = [ b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', byte, b'_', b'_', b'_', b'_', b'_', ]; let mut bytes = Bytes::new(&slice); f(&mut bytes); match bytes.pos() { 32 => true, 26 => false, _ => unreachable!(), } } httparse-1.10.1/src/simd/mod.rs000064400000000000000000000065321046102023000144140ustar 00000000000000mod swar; #[cfg(not(all( httparse_simd, any( target_arch = "x86", target_arch = "x86_64", all( target_arch = "aarch64", httparse_simd_neon_intrinsics, ) ), )))] pub use self::swar::*; #[cfg(all( httparse_simd, not(httparse_simd_target_feature_avx2), any( target_arch = "x86", target_arch = "x86_64", ), ))] mod sse42; #[cfg(all( httparse_simd, any( httparse_simd_target_feature_avx2, not(httparse_simd_target_feature_sse42), ), any( target_arch = "x86", target_arch = "x86_64", ), ))] mod avx2; #[cfg(all( httparse_simd, not(any( httparse_simd_target_feature_sse42, httparse_simd_target_feature_avx2, )), any( target_arch = "x86", target_arch = "x86_64", ), ))] mod runtime; #[cfg(all( httparse_simd, not(any( httparse_simd_target_feature_sse42, httparse_simd_target_feature_avx2, )), any( target_arch = "x86", target_arch = "x86_64", ), ))] pub use self::runtime::*; #[cfg(all( httparse_simd, httparse_simd_target_feature_sse42, not(httparse_simd_target_feature_avx2), any( target_arch = "x86", target_arch = "x86_64", ), ))] mod sse42_compile_time { #[inline(always)] pub fn match_header_name_vectored(b: &mut crate::iter::Bytes<'_>) { super::swar::match_header_name_vectored(b); } #[inline(always)] pub fn match_uri_vectored(b: &mut crate::iter::Bytes<'_>) { // SAFETY: calls are guarded by a compile time feature check unsafe { crate::simd::sse42::match_uri_vectored(b) } } #[inline(always)] pub fn match_header_value_vectored(b: &mut crate::iter::Bytes<'_>) { // SAFETY: calls are guarded by a compile time feature check unsafe { crate::simd::sse42::match_header_value_vectored(b) } } } #[cfg(all( httparse_simd, httparse_simd_target_feature_sse42, not(httparse_simd_target_feature_avx2), any( target_arch = "x86", target_arch = "x86_64", ), ))] pub use self::sse42_compile_time::*; #[cfg(all( httparse_simd, httparse_simd_target_feature_avx2, any( target_arch = "x86", target_arch = "x86_64", ), ))] mod avx2_compile_time { #[inline(always)] pub fn match_header_name_vectored(b: &mut crate::iter::Bytes<'_>) { super::swar::match_header_name_vectored(b); } #[inline(always)] pub fn match_uri_vectored(b: &mut crate::iter::Bytes<'_>) { // SAFETY: calls are guarded by a compile time feature check unsafe { crate::simd::avx2::match_uri_vectored(b) } } #[inline(always)] pub fn match_header_value_vectored(b: &mut crate::iter::Bytes<'_>) { // SAFETY: calls are guarded by a compile time feature check unsafe { crate::simd::avx2::match_header_value_vectored(b) } } } #[cfg(all( httparse_simd, httparse_simd_target_feature_avx2, any( target_arch = "x86", target_arch = "x86_64", ), ))] pub use self::avx2_compile_time::*; #[cfg(all( httparse_simd, target_arch = "aarch64", httparse_simd_neon_intrinsics, ))] mod neon; #[cfg(all( httparse_simd, target_arch = "aarch64", httparse_simd_neon_intrinsics, ))] pub use self::neon::*; httparse-1.10.1/src/simd/neon.rs000064400000000000000000000164501046102023000145740ustar 00000000000000use crate::iter::Bytes; use core::arch::aarch64::*; #[inline] pub fn match_header_name_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 16 { // SAFETY: ensured that there are at least 16 bytes remaining unsafe { let advance = match_header_name_char_16_neon(bytes.as_ref().as_ptr()); bytes.advance(advance); if advance != 16 { return; } } } super::swar::match_header_name_vectored(bytes); } #[inline] pub fn match_header_value_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 16 { // SAFETY: ensured that there are at least 16 bytes remaining unsafe { let advance = match_header_value_char_16_neon(bytes.as_ref().as_ptr()); bytes.advance(advance); if advance != 16 { return; } } } super::swar::match_header_value_vectored(bytes); } #[inline] pub fn match_uri_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 16 { // SAFETY: ensured that there are at least 16 bytes remaining unsafe { let advance = match_url_char_16_neon(bytes.as_ref().as_ptr()); bytes.advance(advance); if advance != 16 { return; } } } super::swar::match_uri_vectored(bytes); } const fn bit_set(x: u8) -> bool { // Validates if a byte is a valid header name character // https://tools.ietf.org/html/rfc7230#section-3.2.6 matches!(x, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' | b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~') } // A 256-bit bitmap, split into two halves // lower half contains bits whose higher nibble is <= 7 // higher half contains bits whose higher nibble is >= 8 const fn build_bitmap() -> ([u8; 16], [u8; 16]) { let mut bitmap_0_7 = [0u8; 16]; // 0x00..0x7F let mut bitmap_8_15 = [0u8; 16]; // 0x80..0xFF let mut i = 0; while i < 256 { if bit_set(i as u8) { // Nibbles let (lo, hi) = (i & 0x0F, i >> 4); if i < 128 { bitmap_0_7[lo] |= 1 << hi; } else { bitmap_8_15[lo] |= 1 << hi; } } i += 1; } (bitmap_0_7, bitmap_8_15) } const BITMAPS: ([u8; 16], [u8; 16]) = build_bitmap(); // NOTE: adapted from 256-bit version, with upper 128-bit ops commented out #[inline] unsafe fn match_header_name_char_16_neon(ptr: *const u8) -> usize { let bitmaps = BITMAPS; // NOTE: ideally compile-time constants let (bitmap_0_7, _bitmap_8_15) = bitmaps; let bitmap_0_7 = vld1q_u8(bitmap_0_7.as_ptr()); // let bitmap_8_15 = vld1q_u8(bitmap_8_15.as_ptr()); // Initialize the bitmask_lookup. const BITMASK_LOOKUP_DATA: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128]; let bitmask_lookup = vld1q_u8(BITMASK_LOOKUP_DATA.as_ptr()); // Load 16 input bytes. let input = vld1q_u8(ptr); // Extract indices for row_0_7. let indices_0_7 = vandq_u8(input, vdupq_n_u8(0x8F)); // 0b1000_1111; // Extract indices for row_8_15. // let msb = vandq_u8(input, vdupq_n_u8(0x80)); // let indices_8_15 = veorq_u8(indices_0_7, msb); // Fetch row_0_7 and row_8_15. let row_0_7 = vqtbl1q_u8(bitmap_0_7, indices_0_7); // let row_8_15 = vqtbl1q_u8(bitmap_8_15, indices_8_15); // Calculate a bitmask, i.e. (1 << hi_nibble % 8). let bitmask = vqtbl1q_u8(bitmask_lookup, vshrq_n_u8(input, 4)); // Choose rows halves depending on higher nibbles. // let bitsets = vorrq_u8(row_0_7, row_8_15); let bitsets = row_0_7; // Finally check which bytes belong to the set. let tmp = vandq_u8(bitsets, bitmask); let result = vceqq_u8(tmp, bitmask); offsetz(result) as usize } #[inline] unsafe fn match_url_char_16_neon(ptr: *const u8) -> usize { let input = vld1q_u8(ptr); // Check that b'!' <= and b != 127 let result = vcleq_u8(vdupq_n_u8(b'!'), input); // Disallow del let del = vceqq_u8(input, vdupq_n_u8(0x7F)); let result = vbicq_u8(result, del); offsetz(result) as usize } #[inline] unsafe fn match_header_value_char_16_neon(ptr: *const u8) -> usize { let input = vld1q_u8(ptr); // Check that b' ' <= and b != 127 or b == 9 let result = vcleq_u8(vdupq_n_u8(b' '), input); // Allow tab let tab = vceqq_u8(input, vdupq_n_u8(0x09)); let result = vorrq_u8(result, tab); // Disallow del let del = vceqq_u8(input, vdupq_n_u8(0x7F)); let result = vbicq_u8(result, del); offsetz(result) as usize } #[inline] unsafe fn offsetz(x: uint8x16_t) -> u32 { // NOT the vector since it's faster to operate with zeros instead offsetnz(vmvnq_u8(x)) } #[inline] unsafe fn offsetnz(x: uint8x16_t) -> u32 { // Extract two u64 let x = vreinterpretq_u64_u8(x); // Extract to general purpose registers to perform clz let low: u64 = vgetq_lane_u64::<0>(x); let high: u64 = vgetq_lane_u64::<1>(x); #[inline] fn clz(x: u64) -> u32 { // perf: rust will unroll this loop // and it's much faster than rbit + clz so voila for (i, b) in x.to_ne_bytes().iter().copied().enumerate() { if b != 0 { return i as u32; } } 8 // Technically not reachable since zero-guarded } if low != 0 { clz(low) } else if high != 0 { return 8 + clz(high); } else { return 16; } } #[test] fn neon_code_matches_uri_chars_table() { #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_uri_vectored)); for (b, allowed) in crate::URI_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_uri_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[test] fn neon_code_matches_header_value_chars_table() { #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_header_value_vectored)); for (b, allowed) in crate::HEADER_VALUE_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_header_value_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[test] fn neon_code_matches_header_name_chars_table() { #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_header_name_vectored)); for (b, allowed) in crate::TOKEN_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_header_name_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[cfg(test)] unsafe fn byte_is_allowed(byte: u8, f: unsafe fn(bytes: &mut Bytes<'_>)) -> bool { let mut slice = [b'_'; 16]; slice[10] = byte; let mut bytes = Bytes::new(&slice); f(&mut bytes); match bytes.pos() { 16 => true, 10 => false, x => panic!("unexpected pos: {}", x), } } httparse-1.10.1/src/simd/runtime.rs000064400000000000000000000027301046102023000153140ustar 00000000000000use std::sync::atomic::{AtomicU8, Ordering}; use crate::iter::Bytes; use super::avx2; use super::sse42; const AVX2: u8 = 1; const SSE42: u8 = 2; const NOP: u8 = 3; fn detect_runtime_feature() -> u8 { if is_x86_feature_detected!("avx2") { AVX2 } else if is_x86_feature_detected!("sse4.2") { SSE42 } else { NOP } } static RUNTIME_FEATURE: AtomicU8 = AtomicU8::new(0); #[inline] fn get_runtime_feature() -> u8 { let mut feature = RUNTIME_FEATURE.load(Ordering::Relaxed); if feature == 0 { feature = detect_runtime_feature(); RUNTIME_FEATURE.store(feature, Ordering::Relaxed); } feature } pub fn match_header_name_vectored(bytes: &mut Bytes) { super::swar::match_header_name_vectored(bytes); } pub fn match_uri_vectored(bytes: &mut Bytes) { // SAFETY: calls are guarded by a feature check unsafe { match get_runtime_feature() { AVX2 => avx2::match_uri_vectored(bytes), SSE42 => sse42::match_uri_vectored(bytes), _ /* NOP */ => super::swar::match_uri_vectored(bytes), } } } pub fn match_header_value_vectored(bytes: &mut Bytes) { // SAFETY: calls are guarded by a feature check unsafe { match get_runtime_feature() { AVX2 => avx2::match_header_value_vectored(bytes), SSE42 => sse42::match_header_value_vectored(bytes), _ /* NOP */ => super::swar::match_header_value_vectored(bytes), } } } httparse-1.10.1/src/simd/sse42.rs000064400000000000000000000074531046102023000146000ustar 00000000000000use crate::iter::Bytes; #[target_feature(enable = "sse4.2")] pub unsafe fn match_uri_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 16 { let advance = match_url_char_16_sse(bytes.as_ref()); bytes.advance(advance); if advance != 16 { return; } } super::swar::match_uri_vectored(bytes); } #[inline(always)] #[allow(non_snake_case)] unsafe fn match_url_char_16_sse(buf: &[u8]) -> usize { debug_assert!(buf.len() >= 16); #[cfg(target_arch = "x86")] use core::arch::x86::*; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; let ptr = buf.as_ptr(); // %x21-%x7e %x80-%xff let DEL: __m128i = _mm_set1_epi8(0x7f); let LOW: __m128i = _mm_set1_epi8(0x21); let dat = _mm_lddqu_si128(ptr as *const _); // unsigned comparison dat >= LOW let low = _mm_cmpeq_epi8(_mm_max_epu8(dat, LOW), dat); let del = _mm_cmpeq_epi8(dat, DEL); let bit = _mm_andnot_si128(del, low); let res = _mm_movemask_epi8(bit) as u16; // TODO: use .trailing_ones() once MSRV >= 1.46 (!res).trailing_zeros() as usize } #[target_feature(enable = "sse4.2")] pub unsafe fn match_header_value_vectored(bytes: &mut Bytes) { while bytes.as_ref().len() >= 16 { let advance = match_header_value_char_16_sse(bytes.as_ref()); bytes.advance(advance); if advance != 16 { return; } } super::swar::match_header_value_vectored(bytes); } #[inline(always)] #[allow(non_snake_case)] unsafe fn match_header_value_char_16_sse(buf: &[u8]) -> usize { debug_assert!(buf.len() >= 16); #[cfg(target_arch = "x86")] use core::arch::x86::*; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; let ptr = buf.as_ptr(); // %x09 %x20-%x7e %x80-%xff let TAB: __m128i = _mm_set1_epi8(0x09); let DEL: __m128i = _mm_set1_epi8(0x7f); let LOW: __m128i = _mm_set1_epi8(0x20); let dat = _mm_lddqu_si128(ptr as *const _); // unsigned comparison dat >= LOW let low = _mm_cmpeq_epi8(_mm_max_epu8(dat, LOW), dat); let tab = _mm_cmpeq_epi8(dat, TAB); let del = _mm_cmpeq_epi8(dat, DEL); let bit = _mm_andnot_si128(del, _mm_or_si128(low, tab)); let res = _mm_movemask_epi8(bit) as u16; // TODO: use .trailing_ones() once MSRV >= 1.46 (!res).trailing_zeros() as usize } #[test] fn sse_code_matches_uri_chars_table() { if !is_x86_feature_detected!("sse4.2") { return; } #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_uri_vectored)); for (b, allowed) in crate::URI_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_uri_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[test] fn sse_code_matches_header_value_chars_table() { if !is_x86_feature_detected!("sse4.2") { return; } #[allow(clippy::undocumented_unsafe_blocks)] unsafe { assert!(byte_is_allowed(b'_', match_header_value_vectored)); for (b, allowed) in crate::HEADER_VALUE_MAP.iter().cloned().enumerate() { assert_eq!( byte_is_allowed(b as u8, match_header_value_vectored), allowed, "byte_is_allowed({:?}) should be {:?}", b, allowed, ); } } } #[allow(clippy::missing_safety_doc)] #[cfg(test)] unsafe fn byte_is_allowed(byte: u8, f: unsafe fn(bytes: &mut Bytes<'_>)) -> bool { let slice = [ b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', b'_', byte, b'_', b'_', b'_', b'_', b'_', ]; let mut bytes = Bytes::new(&slice); f(&mut bytes); match bytes.pos() { 16 => true, 10 => false, _ => unreachable!(), } } httparse-1.10.1/src/simd/swar.rs000064400000000000000000000163171046102023000146130ustar 00000000000000/// SWAR: SIMD Within A Register /// SIMD validator backend that validates register-sized chunks of data at a time. use crate::{is_header_name_token, is_header_value_token, is_uri_token, Bytes}; // Adapt block-size to match native register size, i.e: 32bit => 4, 64bit => 8 const BLOCK_SIZE: usize = core::mem::size_of::(); type ByteBlock = [u8; BLOCK_SIZE]; #[inline] pub fn match_uri_vectored(bytes: &mut Bytes) { loop { if let Some(bytes8) = bytes.peek_n::(BLOCK_SIZE) { let n = match_uri_char_8_swar(bytes8); // SAFETY: using peek_n to retrieve the bytes ensures that there are at least n more bytes // in `bytes`, so calling `advance(n)` is safe. unsafe { bytes.advance(n); } if n == BLOCK_SIZE { continue; } } if let Some(b) = bytes.peek() { if is_uri_token(b) { // SAFETY: using peek to retrieve the byte ensures that there is at least 1 more byte // in bytes, so calling advance is safe. unsafe { bytes.advance(1); } continue; } } break; } } #[inline] pub fn match_header_value_vectored(bytes: &mut Bytes) { loop { if let Some(bytes8) = bytes.peek_n::(BLOCK_SIZE) { let n = match_header_value_char_8_swar(bytes8); // SAFETY: using peek_n to retrieve the bytes ensures that there are at least n more bytes // in `bytes`, so calling `advance(n)` is safe. unsafe { bytes.advance(n); } if n == BLOCK_SIZE { continue; } } if let Some(b) = bytes.peek() { if is_header_value_token(b) { // SAFETY: using peek to retrieve the byte ensures that there is at least 1 more byte // in bytes, so calling advance is safe. unsafe { bytes.advance(1); } continue; } } break; } } #[inline] pub fn match_header_name_vectored(bytes: &mut Bytes) { while let Some(block) = bytes.peek_n::(BLOCK_SIZE) { let n = match_block(is_header_name_token, block); // SAFETY: using peek_n to retrieve the bytes ensures that there are at least n more bytes // in `bytes`, so calling `advance(n)` is safe. unsafe { bytes.advance(n); } if n != BLOCK_SIZE { return; } } // SAFETY: match_tail processes at most the remaining data in `bytes`. advances `bytes` to the // end, but no further. unsafe { bytes.advance(match_tail(is_header_name_token, bytes.as_ref())) }; } // Matches "tail", i.e: when we have bool, bytes: &[u8]) -> usize { for (i, &b) in bytes.iter().enumerate() { if !f(b) { return i; } } bytes.len() } // Naive fallback block matcher #[inline(always)] fn match_block(f: impl Fn(u8) -> bool, block: ByteBlock) -> usize { for (i, &b) in block.iter().enumerate() { if !f(b) { return i; } } BLOCK_SIZE } // A const alternative to u64::from_ne_bytes to avoid bumping MSRV (1.36 => 1.44) // creates a u64 whose bytes are each equal to b const fn uniform_block(b: u8) -> usize { (b as u64 * 0x01_01_01_01_01_01_01_01 /* [1_u8; 8] */) as usize } // A byte-wise range-check on an entire word/block, // ensuring all bytes in the word satisfy `33 <= (x != 127) <= 255` #[inline] fn match_uri_char_8_swar(block: ByteBlock) -> usize { // 33 <= (x != 127) <= 255 const M: u8 = 0x21; // uniform block full of exclamation mark (!) (33). const BM: usize = uniform_block(M); // uniform block full of 1. const ONE: usize = uniform_block(0x01); // uniform block full of DEL (127). const DEL: usize = uniform_block(0x7f); // uniform block full of 128. const M128: usize = uniform_block(128); let x = usize::from_ne_bytes(block); // Really just a transmute let lt = x.wrapping_sub(BM) & !x; // <= m let xor_del = x ^ DEL; let eq_del = xor_del.wrapping_sub(ONE) & !xor_del; // == DEL offsetnz((lt | eq_del) & M128) } // A byte-wise range-check on an entire word/block, // ensuring all bytes in the word satisfy `32 <= (x != 127) <= 255` #[inline] fn match_header_value_char_8_swar(block: ByteBlock) -> usize { // 32 <= (x != 127) <= 255 const M: u8 = 0x20; // uniform block full of exclamation mark (!) (33). const BM: usize = uniform_block(M); // uniform block full of 1. const ONE: usize = uniform_block(0x01); // uniform block full of DEL (127). const DEL: usize = uniform_block(0x7f); // uniform block full of 128. const M128: usize = uniform_block(128); let x = usize::from_ne_bytes(block); // Really just a transmute let lt = x.wrapping_sub(BM) & !x; // <= m let xor_del = x ^ DEL; let eq_del = xor_del.wrapping_sub(ONE) & !xor_del; // == DEL offsetnz((lt | eq_del) & M128) } /// Check block to find offset of first non-zero byte // NOTE: Curiously `block.trailing_zeros() >> 3` appears to be slower, maybe revisit #[inline] fn offsetnz(block: usize) -> usize { // fast path optimistic case (common for long valid sequences) if block == 0 { return BLOCK_SIZE; } // perf: rust will unroll this loop for (i, b) in block.to_ne_bytes().iter().copied().enumerate() { if b != 0 { return i; } } unreachable!() } #[test] fn test_is_header_value_block() { let is_header_value_block = |b| match_header_value_char_8_swar(b) == BLOCK_SIZE; // 0..32 => false for b in 0..32_u8 { assert!(!is_header_value_block([b; BLOCK_SIZE]), "b={}", b); } // 32..=126 => true for b in 32..=126_u8 { assert!(is_header_value_block([b; BLOCK_SIZE]), "b={}", b); } // 127 => false assert!(!is_header_value_block([b'\x7F'; BLOCK_SIZE]), "b={}", b'\x7F'); // 128..=255 => true for b in 128..=255_u8 { assert!(is_header_value_block([b; BLOCK_SIZE]), "b={}", b); } #[cfg(target_pointer_width = "64")] { // A few sanity checks on non-uniform bytes for safe-measure assert!(!is_header_value_block(*b"foo.com\n")); assert!(!is_header_value_block(*b"o.com\r\nU")); } } #[test] fn test_is_uri_block() { let is_uri_block = |b| match_uri_char_8_swar(b) == BLOCK_SIZE; // 0..33 => false for b in 0..33_u8 { assert!(!is_uri_block([b; BLOCK_SIZE]), "b={}", b); } // 33..=126 => true for b in 33..=126_u8 { assert!(is_uri_block([b; BLOCK_SIZE]), "b={}", b); } // 127 => false assert!(!is_uri_block([b'\x7F'; BLOCK_SIZE]), "b={}", b'\x7F'); // 128..=255 => true for b in 128..=255_u8 { assert!(is_uri_block([b; BLOCK_SIZE]), "b={}", b); } } #[test] fn test_offsetnz() { let seq = [0_u8; BLOCK_SIZE]; for i in 0..BLOCK_SIZE { let mut seq = seq; seq[i] = 1; let x = usize::from_ne_bytes(seq); assert_eq!(offsetnz(x), i); } } httparse-1.10.1/tests/uri.rs000064400000000000000000003110261046102023000140500ustar 00000000000000 use httparse::{Error, Request, Status, EMPTY_HEADER}; const NUM_OF_HEADERS: usize = 4; macro_rules! req { ($name:ident, $buf:expr, |$arg:ident| $body:expr) => ( req! {$name, $buf, Ok(Status::Complete($buf.len())), |$arg| $body } ); ($name:ident, $buf:expr, $len:expr, |$arg:ident| $body:expr) => ( #[test] fn $name() { let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS]; let mut req = Request::new(&mut headers[..]); let status = req.parse($buf.as_ref()); assert_eq!(status, $len); closure(req); fn closure($arg: Request<'_, '_>) { $body } } ) } req! { urltest_001, b"GET /bar;par?b HTTP/1.1\r\nHost: foo\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/bar;par?b"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo"); } } req! { urltest_002, b"GET /x HTTP/1.1\r\nHost: test\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"test"); } } req! { urltest_003, b"GET /x HTTP/1.1\r\nHost: test\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"test"); } } req! { urltest_004, b"GET /foo/foo.com HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/foo.com"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_005, b"GET /foo/:foo.com HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:foo.com"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_006, b"GET /foo/foo.com HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/foo.com"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_007, b"GET foo.com HTTP/1.1\r\nHost: \r\n\r\n", Err(Error::Token), |_r| {} } req! { urltest_008, b"GET /%20b%20?%20d%20 HTTP/1.1\r\nHost: f\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%20b%20?%20d%20"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"f"); } } req! { urltest_009, b"GET x x HTTP/1.1\r\nHost: \r\n\r\n", Err(Error::Version), |_r| {} } req! { urltest_010, b"GET /c HTTP/1.1\r\nHost: f\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"f"); } } req! { urltest_011, b"GET /c HTTP/1.1\r\nHost: f\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"f"); } } req! { urltest_012, b"GET /c HTTP/1.1\r\nHost: f\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"f"); } } req! { urltest_013, b"GET /c HTTP/1.1\r\nHost: f\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"f"); } } req! { urltest_014, b"GET /c HTTP/1.1\r\nHost: f\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"f"); } } req! { urltest_015, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_016, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_017, b"GET /foo/:foo.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:foo.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_018, b"GET /foo/:foo.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:foo.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_019, b"GET /foo/: HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_020, b"GET /foo/:a HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_021, b"GET /foo/:/ HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_022, b"GET /foo/:/ HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_023, b"GET /foo/: HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_024, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_025, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_026, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_027, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_028, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_029, b"GET /foo/:23 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:23"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_030, b"GET /:23 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/:23"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_031, b"GET /foo/:: HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/::"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_032, b"GET /foo/::23 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/::23"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_033, b"GET /d HTTP/1.1\r\nHost: c\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/d"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"c"); } } req! { urltest_034, b"GET /foo/:@c:29 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/:@c:29"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_035, b"GET //@ HTTP/1.1\r\nHost: foo.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "//@"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.com"); } } req! { urltest_036, b"GET /b:c/d@foo.com/ HTTP/1.1\r\nHost: a\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/b:c/d@foo.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"a"); } } req! { urltest_037, b"GET /bar.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/bar.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_038, b"GET /////// HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "///////"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_039, b"GET ///////bar.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "///////bar.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_040, b"GET //:///// HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "//://///"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_041, b"GET /foo HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_042, b"GET /bar HTTP/1.1\r\nHost: foo\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo"); } } req! { urltest_043, b"GET /path;a??e HTTP/1.1\r\nHost: foo\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/path;a??e"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo"); } } req! { urltest_044, b"GET /abcd?efgh?ijkl HTTP/1.1\r\nHost: foo\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/abcd?efgh?ijkl"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo"); } } req! { urltest_045, b"GET /abcd HTTP/1.1\r\nHost: foo\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/abcd"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo"); } } req! { urltest_046, b"GET /foo/[61:24:74]:98 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/[61:24:74]:98"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_047, b"GET /foo/[61:27]/:foo HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/[61:27]/:foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_048, b"GET /example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_049, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_050, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_051, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_052, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_053, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_054, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_055, b"GET /foo/example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_056, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_057, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_058, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_059, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_060, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_061, b"GET /a/b/c HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/a/b/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_062, b"GET /a/%20/c HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/a/%20/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_063, b"GET /a%2fc HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/a%2fc"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_064, b"GET /a/%2f/c HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/a/%2f/c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_065, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_066, b"GET text/html,test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "text/html,test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_067, b"GET 1234567890 HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "1234567890"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_068, b"GET /c:/foo/bar.html HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c:/foo/bar.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_069, b"GET /c:////foo/bar.html HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c:////foo/bar.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_070, b"GET /C:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_071, b"GET /C:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_072, b"GET /C:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_073, b"GET /file HTTP/1.1\r\nHost: server\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/file"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"server"); } } req! { urltest_074, b"GET /file HTTP/1.1\r\nHost: server\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/file"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"server"); } } req! { urltest_075, b"GET /file HTTP/1.1\r\nHost: server\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/file"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"server"); } } req! { urltest_076, b"GET /foo/bar.txt HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_077, b"GET /home/me HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/home/me"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_078, b"GET /test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_079, b"GET /test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_080, b"GET /tmp/mock/test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/tmp/mock/test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_081, b"GET /tmp/mock/test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/tmp/mock/test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_082, b"GET /foo HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_083, b"GET /.foo HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/.foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_084, b"GET /foo/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_085, b"GET /foo/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_086, b"GET /foo/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_087, b"GET /foo/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_088, b"GET /foo/..bar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/..bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_089, b"GET /foo/ton HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/ton"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_090, b"GET /a HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_091, b"GET /ton HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/ton"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_092, b"GET /foo/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_093, b"GET /foo/%2e%2 HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/%2e%2"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_094, b"GET /%2e.bar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%2e.bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_095, b"GET // HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "//"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_096, b"GET /foo/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_097, b"GET /foo/bar/ HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_098, b"GET /foo HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_099, b"GET /%20foo HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%20foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_100, b"GET /foo% HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_101, b"GET /foo%2 HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%2"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_102, b"GET /foo%2zbar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%2zbar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_103, b"GET /foo%2%C3%82%C2%A9zbar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%2%C3%82%C2%A9zbar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_104, b"GET /foo%41%7a HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%41%7a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_105, b"GET /foo%C2%91%91 HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%C2%91%91"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_106, b"GET /foo%00%51 HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%00%51"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_107, b"GET /(%28:%3A%29) HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/(%28:%3A%29)"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_108, b"GET /%3A%3a%3C%3c HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%3A%3a%3C%3c"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_109, b"GET /foobar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foobar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_110, b"GET //foo//bar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "//foo//bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_111, b"GET /%7Ffp3%3Eju%3Dduvgw%3Dd HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%7Ffp3%3Eju%3Dduvgw%3Dd"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_112, b"GET /@asdf%40 HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/@asdf%40"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_113, b"GET /%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_114, b"GET /%E2%80%A5/foo HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%E2%80%A5/foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_115, b"GET /%EF%BB%BF/foo HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%EF%BB%BF/foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_116, b"GET /%E2%80%AE/foo/%E2%80%AD/bar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%E2%80%AE/foo/%E2%80%AD/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_117, b"GET /foo?bar=baz HTTP/1.1\r\nHost: www.google.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo?bar=baz"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.google.com"); } } req! { urltest_118, b"GET /foo?bar=baz HTTP/1.1\r\nHost: www.google.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo?bar=baz"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.google.com"); } } req! { urltest_119, b"GET test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_120, b"GET /foo%2Ehtml HTTP/1.1\r\nHost: www\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo%2Ehtml"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www"); } } req! { urltest_121, b"GET /foo/html HTTP/1.1\r\nHost: www\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www"); } } req! { urltest_122, b"GET /foo HTTP/1.1\r\nHost: www.google.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.google.com"); } } req! { urltest_123, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_124, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_125, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_126, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_127, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_128, b"GET /example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_129, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_130, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_131, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_132, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_133, b"GET example.com/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "example.com/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_134, b"GET /test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_135, b"GET /test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_136, b"GET /test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_137, b"GET /test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_138, b"GET /aaa/test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/aaa/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_139, b"GET /test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_140, b"GET /%E4%B8%AD/test.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%E4%B8%AD/test.txt"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"www.example.com"); } } req! { urltest_141, b"GET /... HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/..."); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_142, b"GET /a HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_143, b"GET /%EF%BF%BD?%EF%BF%BD HTTP/1.1\r\nHost: x\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%EF%BF%BD?%EF%BF%BD"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"x"); } } req! { urltest_144, b"GET /bar HTTP/1.1\r\nHost: example.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.com"); } } req! { urltest_145, b"GET test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_146, b"GET x@x.com HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "x@x.com"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_147, b"GET , HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), ","); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_148, b"GET blank HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "blank"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_149, b"GET test?test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "test?test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_150, b"GET /%60%7B%7D?`{} HTTP/1.1\r\nHost: h\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/%60%7B%7D?`{}"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"h"); } } req! { urltest_151, b"GET /?%27 HTTP/1.1\r\nHost: host\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/?%27"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"host"); } } req! { urltest_152, b"GET /?' HTTP/1.1\r\nHost: host\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/?'"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"host"); } } req! { urltest_153, b"GET /some/path HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/some/path"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_154, b"GET /smth HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/smth"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_155, b"GET /some/path HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/some/path"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_156, b"GET /pa/i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_157, b"GET /i HTTP/1.1\r\nHost: ho\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"ho"); } } req! { urltest_158, b"GET /pa/i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_159, b"GET /i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_160, b"GET /i HTTP/1.1\r\nHost: ho\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"ho"); } } req! { urltest_161, b"GET /i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_162, b"GET /i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_163, b"GET /i HTTP/1.1\r\nHost: ho\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"ho"); } } req! { urltest_164, b"GET /i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_165, b"GET /pa/pa?i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa/pa?i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_166, b"GET /pa?i HTTP/1.1\r\nHost: ho\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa?i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"ho"); } } req! { urltest_167, b"GET /pa/pa?i HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa/pa?i"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_168, b"GET sd HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "sd"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_169, b"GET sd/sd HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "sd/sd"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_170, b"GET /pa/pa HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa/pa"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_171, b"GET /pa HTTP/1.1\r\nHost: ho\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"ho"); } } req! { urltest_172, b"GET /pa/pa HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pa/pa"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_173, b"GET /x HTTP/1.1\r\nHost: %C3%B1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"%C3%B1"); } } req! { urltest_174, b"GET \\.\\./ HTTP/1.1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "\\.\\./"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 0); } } req! { urltest_175, b"GET :a@example.net HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), ":a@example.net"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_176, b"GET %NBD HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "%NBD"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_177, b"GET %1G HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "%1G"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_178, b"GET /relative_import.html HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/relative_import.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"127.0.0.1"); } } req! { urltest_179, b"GET /?foo=%7B%22abc%22 HTTP/1.1\r\nHost: facebook.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/?foo=%7B%22abc%22"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"facebook.com"); } } req! { urltest_180, b"GET /jqueryui@1.2.3 HTTP/1.1\r\nHost: localhost\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/jqueryui@1.2.3"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"localhost"); } } req! { urltest_181, b"GET /path?query HTTP/1.1\r\nHost: host\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/path?query"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"host"); } } req! { urltest_182, b"GET /foo/bar?a=b&c=d HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar?a=b&c=d"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_183, b"GET /foo/bar??a=b&c=d HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar??a=b&c=d"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_184, b"GET /foo/bar HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_185, b"GET /baz?qux HTTP/1.1\r\nHost: foo.bar\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/baz?qux"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.bar"); } } req! { urltest_186, b"GET /baz?qux HTTP/1.1\r\nHost: foo.bar\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/baz?qux"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.bar"); } } req! { urltest_187, b"GET /baz?qux HTTP/1.1\r\nHost: foo.bar\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/baz?qux"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.bar"); } } req! { urltest_188, b"GET /baz?qux HTTP/1.1\r\nHost: foo.bar\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/baz?qux"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.bar"); } } req! { urltest_189, b"GET /baz?qux HTTP/1.1\r\nHost: foo.bar\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/baz?qux"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foo.bar"); } } req! { urltest_190, b"GET /C%3A/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C%3A/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_191, b"GET /C%7C/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C%7C/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_192, b"GET /C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_193, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_194, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_195, b"GET /d: HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/d:"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_196, b"GET /d:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/d:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_197, b"GET /test?test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_198, b"GET /test?test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_199, b"GET /test?x HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_200, b"GET /test?x HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_201, b"GET /test?test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_202, b"GET /test?test HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_203, b"GET /?fox HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/?fox"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_204, b"GET /localhost//cat HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/localhost//cat"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_205, b"GET /localhost//cat HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/localhost//cat"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_206, b"GET /mouse HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/mouse"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_207, b"GET /pig HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pig"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_208, b"GET /pig HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pig"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_209, b"GET /pig HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/pig"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_210, b"GET /localhost//pig HTTP/1.1\r\nHost: lion\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/localhost//pig"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"lion"); } } req! { urltest_211, b"GET /rooibos HTTP/1.1\r\nHost: tea\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/rooibos"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"tea"); } } req! { urltest_212, b"GET /?chai HTTP/1.1\r\nHost: tea\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/?chai"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"tea"); } } req! { urltest_213, b"GET /C: HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_214, b"GET /C: HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_215, b"GET /C: HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_216, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_217, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_218, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_219, b"GET /dir/C HTTP/1.1\r\nHost: host\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/dir/C"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"host"); } } req! { urltest_220, b"GET /dir/C|a HTTP/1.1\r\nHost: host\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/dir/C|a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"host"); } } req! { urltest_221, b"GET /c:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_222, b"GET /c:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_223, b"GET /c:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_224, b"GET /c:/foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/c:/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_225, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_226, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_227, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_228, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_229, b"GET /C:/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/C:/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_230, b"GET /?q=v HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/?q=v"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_231, b"GET ?x HTTP/1.1\r\nHost: %C3%B1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "?x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"%C3%B1"); } } req! { urltest_232, b"GET ?x HTTP/1.1\r\nHost: %C3%B1\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "?x"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"%C3%B1"); } } req! { urltest_233, b"GET // HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "//"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_234, b"GET //x/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "//x/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_235, b"GET /someconfig;mode=netascii HTTP/1.1\r\nHost: foobar.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/someconfig;mode=netascii"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"foobar.com"); } } req! { urltest_236, b"GET /Index.ut2 HTTP/1.1\r\nHost: 10.10.10.10\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/Index.ut2"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"10.10.10.10"); } } req! { urltest_237, b"GET /0?baz=bam&qux=baz HTTP/1.1\r\nHost: somehost\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/0?baz=bam&qux=baz"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"somehost"); } } req! { urltest_238, b"GET /sup HTTP/1.1\r\nHost: host\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/sup"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"host"); } } req! { urltest_239, b"GET /foo/bar.git HTTP/1.1\r\nHost: github.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar.git"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"github.com"); } } req! { urltest_240, b"GET /channel?passwd HTTP/1.1\r\nHost: myserver.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/channel?passwd"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"myserver.com"); } } req! { urltest_241, b"GET /foo.bar.org?type=TXT HTTP/1.1\r\nHost: fw.example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo.bar.org?type=TXT"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"fw.example.org"); } } req! { urltest_242, b"GET /ou=People,o=JNDITutorial HTTP/1.1\r\nHost: localhost\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/ou=People,o=JNDITutorial"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"localhost"); } } req! { urltest_243, b"GET /foo/bar HTTP/1.1\r\nHost: github.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"github.com"); } } req! { urltest_244, b"GET ietf:rfc:2648 HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "ietf:rfc:2648"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_245, b"GET joe@example.org,2001:foo/bar HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "joe@example.org,2001:foo/bar"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_246, b"GET /path HTTP/1.1\r\nHost: H%4fSt\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/path"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"H%4fSt"); } } req! { urltest_247, b"GET https://example.com:443/ HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "https://example.com:443/"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_248, b"GET d3958f5c-0777-0845-9dcf-2cb28783acaf HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "d3958f5c-0777-0845-9dcf-2cb28783acaf"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_249, b"GET /test?%22 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?%22"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_250, b"GET /test HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_251, b"GET /test?%3C HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?%3C"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_252, b"GET /test?%3E HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?%3E"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_253, b"GET /test?%E2%8C%A3 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?%E2%8C%A3"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_254, b"GET /test?%23%23 HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?%23%23"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_255, b"GET /test?%GH HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?%GH"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_256, b"GET /test?a HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_257, b"GET /test?a HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_258, b"GET /test-a-colon-slash.html HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test-a-colon-slash.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_259, b"GET /test-a-colon-slash-slash.html HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test-a-colon-slash-slash.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_260, b"GET /test-a-colon-slash-b.html HTTP/1.1\r\nHost: \r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test-a-colon-slash-b.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b""); } } req! { urltest_261, b"GET /test-a-colon-slash-slash-b.html HTTP/1.1\r\nHost: b\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test-a-colon-slash-slash-b.html"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"b"); } } req! { urltest_262, b"GET /test?a HTTP/1.1\r\nHost: example.org\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/test?a"); assert_eq!(req.version.unwrap(), 1); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"example.org"); } } req! { urltest_nvidia, b"GET /nvidia_web_services/controller.gfeclientcontent.php/com.nvidia.services.GFEClientContent.getShieldReady/{\"gcV\":\"2.2.2.0\",\"dID\":\"1341\",\"osC\":\"6.20\",\"is6\":\"1\",\"lg\":\"1033\",\"GFPV\":\"389.08\",\"isO\":\"1\",\"sM\":\"16777216\"} HTTP/1.0\r\nHost: gfwsl.geforce.com\r\n\r\n", |req| { assert_eq!(req.method.unwrap(), "GET"); assert_eq!(req.path.unwrap(), "/nvidia_web_services/controller.gfeclientcontent.php/com.nvidia.services.GFEClientContent.getShieldReady/{\"gcV\":\"2.2.2.0\",\"dID\":\"1341\",\"osC\":\"6.20\",\"is6\":\"1\",\"lg\":\"1033\",\"GFPV\":\"389.08\",\"isO\":\"1\",\"sM\":\"16777216\"}"); assert_eq!(req.version.unwrap(), 0); assert_eq!(req.headers.len(), 1); assert_eq!(req.headers[0].name, "Host"); assert_eq!(req.headers[0].value, b"gfwsl.geforce.com"); } }