diff --git a/.anvil.lock b/.anvil.lock
index 644a0e3f2..d8fbcf66e 100644
--- a/.anvil.lock
+++ b/.anvil.lock
@@ -446,6 +446,16 @@ host = "crates/http_extensions/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"
+[[region]]
+host = "crates/http_headers/Cargo.toml"
+id = "anvil-lints"
+checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"
+
+[[region]]
+host = "crates/http_headers_simd/Cargo.toml"
+id = "anvil-lints"
+checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"
+
[[region]]
host = "crates/http_path_template/Cargo.toml"
id = "anvil-lints"
diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml
index 9d0bf3faa..17389807c 100644
--- a/.cargo/mutants.toml
+++ b/.cargo/mutants.toml
@@ -5,6 +5,8 @@ examine_globs = ["crates/**"]
exclude_globs = [
# Fixture scaffolding for the `fetch_winhttp` tests, benchmarks and examples.
"crates/fetch_winhttp_impl/src/testing/**",
+ "crates/http_headers/**",
+ "crates/http_headers_simd/**",
"crates/observed_testing/**",
"crates/rest_over_grpc_examples/**",
"crates/rest_over_grpc_tests/**",
diff --git a/.github/workflows/repository-checks.yml b/.github/workflows/repository-checks.yml
index 771505518..15fde2f22 100644
--- a/.github/workflows/repository-checks.yml
+++ b/.github/workflows/repository-checks.yml
@@ -40,17 +40,62 @@ jobs:
shell: pwsh
run: just test-scripts
+ simd-no-std-arm:
+ name: SIMD no_std contract (native AArch64)
+ runs-on: ubuntu-24.04-arm
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/actions/anvil-setup
+ with:
+ group: none
+ - name: Install Rust
+ run: just anvil-toolchain-stable-install
+ - name: Test isolated AArch64 no_std configuration
+ run: just test-http-headers-simd-no-std-arm
+
+ simd-no-std-x86:
+ name: "SIMD no_std contract (baseline ${{ matrix.target }})"
+ strategy:
+ fail-fast: false
+ matrix:
+ target: [x86_64-unknown-linux-gnu, i686-unknown-linux-gnu, i586-unknown-linux-gnu]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: ./.github/actions/anvil-setup
+ with:
+ group: none
+ - name: Install 32-bit linker and C runtime
+ if: matrix.target != 'x86_64-unknown-linux-gnu'
+ run: |
+ sudo apt-get update --quiet
+ sudo apt-get install --yes --quiet gcc-multilib
+ - name: Install Rust target
+ run: just setup-http-headers-simd-no-std-x86 --target "${{ matrix.target }}"
+ - name: Test isolated baseline no_std configuration
+ run: just test-http-headers-simd-no-std-x86 --target "${{ matrix.target }}"
+
required-repository-checks:
name: Required repository checks
if: always()
- needs: [release-script-tests]
+ needs: [release-script-tests, simd-no-std-arm, simd-no-std-x86]
runs-on: ubuntu-latest
steps:
- name: Verify repository checks
env:
RELEASE_SCRIPT_TESTS: ${{ needs.release-script-tests.result }}
+ SIMD_NO_STD_ARM: ${{ needs.simd-no-std-arm.result }}
+ SIMD_NO_STD_X86: ${{ needs.simd-no-std-x86.result }}
run: |
if [[ "$RELEASE_SCRIPT_TESTS" != "success" ]]; then
echo "::error::release-script-tests concluded $RELEASE_SCRIPT_TESTS"
exit 1
fi
+ if [[ "$SIMD_NO_STD_ARM" != "success" ]]; then
+ echo "::error::simd-no-std-arm concluded $SIMD_NO_STD_ARM"
+ exit 1
+ fi
+ if [[ "$SIMD_NO_STD_X86" != "success" ]]; then
+ echo "::error::simd-no-std-x86 concluded $SIMD_NO_STD_X86"
+ exit 1
+ fi
diff --git a/.spelling b/.spelling
index 90ea592d8..09e1b4e1c 100644
--- a/.spelling
+++ b/.spelling
@@ -72,6 +72,7 @@ DevOps
DotNet
Dyn
Enum
+EPYC
Extended
FFI
FFI-compatible
@@ -204,6 +205,54 @@ Win32
Xamarin
ZST
ZSTs
+AArch64
+CORS
+DQUOTE
+Fetch
+Fetch's
+GUID
+HSTS
+HTAB
+HTTP's
+IDNA
+LF
+OWS
+SSE4
+SSSE3
+codings
+cryptographically
+formatters
+indexable
+intrinsics
+lowercased
+lowercases
+lowercasing
+movemasks
+nonces
+preload
+reparse
+revalidate
+revalidated
+revalidating
+splitter
+subdomains
+subprotocol
+subprotocols
+subsecond
+subtag
+subtags
+subtype
+token68
+unaccelerated
+unescaping
+unmarks
+userinfo
+username
+validator
+validators
+vectorizes
+zeroized
+zeroizes
_arc
_rc
accessor
@@ -922,3 +971,5 @@ JIT
CSV
subcommands
POSIX
+preflight
+predecoded
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ca20cd9b..4288bb6af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@ Please see each crate's change log below:
- [`fundle_macros_impl`](./crates/fundle_macros_impl/CHANGELOG.md)
- [`http_compression`](./crates/http_compression/CHANGELOG.md)
- [`http_extensions`](./crates/http_extensions/CHANGELOG.md)
+- [`http_headers`](./crates/http_headers/CHANGELOG.md)
+- [`http_headers_simd`](./crates/http_headers_simd/CHANGELOG.md)
- [`http_path_template`](./crates/http_path_template/CHANGELOG.md)
- [`internity`](./crates/internity/CHANGELOG.md)
- [`layered`](./crates/layered/CHANGELOG.md)
diff --git a/Cargo.lock b/Cargo.lock
index 0ca36ed3f..1e105f48f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -546,6 +546,8 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"serde_core",
+ "serde_json",
+ "serde_path_to_error",
"sync_wrapper",
"tokio",
"tower",
@@ -725,6 +727,15 @@ version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "blocking"
version = "1.7.0"
@@ -1210,6 +1221,19 @@ dependencies = [
"static_assertions",
]
+[[package]]
+name = "compact_str"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79fcda08c33bb58b97008b2cdada6622500e949e060f5913361763121abd2416"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "static_assertions",
+ "zmij",
+]
+
[[package]]
name = "compressors"
version = "0.1.1"
@@ -1418,6 +1442,16 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
[[package]]
name = "ctor"
version = "1.0.13"
@@ -1638,6 +1672,16 @@ dependencies = [
"unicode-xid",
]
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
[[package]]
name = "displaydoc"
version = "0.2.7"
@@ -2010,6 +2054,15 @@ dependencies = [
"zlib-rs",
]
+[[package]]
+name = "fluent-uri"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -2227,6 +2280,16 @@ dependencies = [
"windows-result",
]
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -2441,6 +2504,30 @@ dependencies = [
"foldhash 0.2.0",
]
+[[package]]
+name = "headers"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
+dependencies = [
+ "http",
+]
+
[[package]]
name = "heapless"
version = "0.9.3"
@@ -2549,6 +2636,44 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "http_headers"
+version = "0.1.0"
+dependencies = [
+ "axum",
+ "base64 0.23.1",
+ "bolero",
+ "bytes",
+ "compact_str 0.10.0",
+ "criterion",
+ "fluent-uri",
+ "headers",
+ "http",
+ "http_headers_simd",
+ "httpdate",
+ "idna",
+ "itoa",
+ "metabench",
+ "mutants",
+ "pastey",
+ "serde",
+ "serde_json",
+ "sha1",
+ "smallvec",
+ "tokio",
+ "tower",
+ "zeroize",
+]
+
+[[package]]
+name = "http_headers_simd"
+version = "0.1.0"
+dependencies = [
+ "bolero",
+ "criterion",
+ "metabench",
+]
+
[[package]]
name = "http_path_template"
version = "0.2.1"
@@ -4667,7 +4792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c"
dependencies = [
"bitflags 2.13.1",
- "compact_str",
+ "compact_str 0.9.1",
"hashbrown 0.17.1",
"itertools 0.14.0",
"kasuari",
@@ -5316,6 +5441,17 @@ dependencies = [
"zmij",
]
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
+dependencies = [
+ "itoa",
+ "serde",
+ "serde_core",
+]
+
[[package]]
name = "serde_spanned"
version = "1.1.1"
@@ -5359,6 +5495,17 @@ dependencies = [
"syn 3.0.5",
]
+[[package]]
+name = "sha1"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest",
+]
+
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -6224,6 +6371,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
[[package]]
name = "typespec"
version = "1.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 14d88238a..9fa29fa1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,8 +33,11 @@ homepage = "https://github.com/microsoft/oxidizer"
#
# The `docs/**/*.md` glob matches only Markdown, so compile-time `include_str!`
# doc fragments are packaged while binary diagram assets beside them are not.
+# Bolero corpus and crash files are development artifacts regardless of
+# whether their targets are integration tests or private source-level tests.
include = [
"/src/**",
+ "!/src/__fuzz__/**",
"/build.rs",
"/tests/**",
"!/tests/__fuzz__/**",
@@ -100,6 +103,7 @@ chumsky = { version = "0.13.0", default-features = false }
clap = { version = "4.6.4", default-features = false }
# The latest command-group release still uses nix 0.27 on Unix; accept the duplicate until upstream updates.
command-group = { version = "5.0.1", default-features = false }
+compact_str = { version = "0.10.0", default-features = false }
compressors = { path = "crates/compressors", default-features = false, version = "0.1.1" }
const-hex = { version = "1.15.0", default-features = false }
criterion = { version = "0.8.2", default-features = false }
@@ -125,6 +129,7 @@ fetch_tls = { path = "crates/fetch_tls", default-features = false, version = "0.
fetch_winhttp = { path = "crates/fetch_winhttp", default-features = false, version = "0.2.1" }
fetch_winhttp_impl = { path = "crates/fetch_winhttp_impl", default-features = false, version = "0.2.1" }
flate2 = { version = "1.1.10", default-features = false }
+fluent-uri = { version = "0.1.4", default-features = false }
foldhash = { version = "0.2.0", default-features = false }
fundle = { path = "crates/fundle", default-features = false, version = "0.4.0" }
fundle_macros = { path = "crates/fundle_macros", default-features = false, version = "=0.4.0" }
@@ -140,17 +145,23 @@ gungraun-summary = { version = "=6.0.0", default-features = false }
h3 = { version = "0.0.8", default-features = false }
h3-quinn = { version = "0.0.10", default-features = false }
hashbrown = { version = "0.17.0", default-features = false }
+# Pinned exactly because the differential benchmarks use this release as their baseline.
+headers = { version = "=0.4.1", default-features = false }
heck = { version = "0.5.0", default-features = false }
http = { version = "1.4.1", default-features = false, features = ["std"] }
http-body = { version = "1.0.1", default-features = false }
http-body-util = { version = "0.1.3", default-features = false }
http_compression = { path = "crates/http_compression", default-features = false, version = "0.1.1" }
http_extensions = { path = "crates/http_extensions", default-features = false, version = "0.11.1" }
+http_headers = { path = "crates/http_headers", default-features = false, version = "0.1.0" }
+http_headers_simd = { path = "crates/http_headers_simd", default-features = false, version = "=0.1.0" }
http_path_template = { path = "crates/http_path_template", default-features = false, version = "0.2.1" }
+httpdate = { version = "1.0.3", default-features = false }
hyper = { version = "1.10.1", default-features = false }
hyper-rustls = { version = "0.27.9", default-features = false }
hyper-tls = { version = "0.6.0", default-features = false }
hyper-util = { version = "0.1.20", default-features = false }
+idna = { version = "1.1.0", default-features = false }
infinity_pool = { version = "0.8.1", default-features = false }
insta = { version = "1.44.1", default-features = false }
internity = { path = "crates/internity", default-features = false, version = "0.2.1" }
@@ -196,6 +207,7 @@ opentelemetry-stdout = { version = "0.32.0", default-features = false }
opentelemetry_sdk = { version = "0.32.0", default-features = false }
opool = { version = "0.2.0", default-features = false }
parking_lot = { version = "0.12.5", default-features = false }
+paste = { package = "pastey", version = "0.2.3", default-features = false }
path-tree = { version = "0.8.3", default-features = false }
pbjson = { version = "0.9.0", default-features = false }
pbjson-build = { version = "0.9.0", default-features = false }
@@ -250,6 +262,7 @@ serde_html_form = { version = "0.4.1", default-features = false }
serde_json = { version = "1.0.145", default-features = false }
serde_urlencoded = { version = "0.7.1", default-features = false }
serial_test = { version = "4.0.1", default-features = false }
+sha1 = { version = "0.10.7", default-features = false }
sharded-slab = { version = "0.1.7", default-features = false }
slab = { version = "0.4.12", default-features = false }
slotmap = { version = "1.1.1", default-features = false }
@@ -301,6 +314,7 @@ windows-sys = { version = "0.61.2", default-features = false }
wiremock = { version = "0.6.5", default-features = false }
xxhash-rust = { version = "0.8.15", default-features = false }
zerocopy = { version = "0.8.26", default-features = false }
+zeroize = { version = "1.9.0", default-features = false }
zstd-safe = { version = "8.0.0", default-features = false }
# >>> anvil-managed: anvil-workspace-lints
@@ -380,6 +394,7 @@ clippy.multiple_unsafe_ops_per_block = "warn"
clippy.redundant_type_annotations = "warn"
clippy.renamed_function_params = "warn"
clippy.semicolon_outside_block = "warn"
+clippy.too_long_first_doc_paragraph = "warn"
clippy.undocumented_unsafe_blocks = "warn"
clippy.unnecessary_safety_comment = "warn"
clippy.unnecessary_safety_doc = "warn"
diff --git a/README.md b/README.md
index 46037e98b..084161d33 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ These are the primary crates built out of this repo:
- [`fundle`](./crates/fundle/README.md) - Compile-time safe dependency injection for Rust.
- [`http_compression`](./crates/http_compression/README.md) - HTTP request and response body compression and decompression.
- [`http_extensions`](./crates/http_extensions/README.md) - Shared HTTP types and extension traits for clients and servers.
+- [`http_headers`](./crates/http_headers/README.md) - Fast, ergonomic typed HTTP headers with borrowed views.
- [`http_path_template`](./crates/http_path_template/README.md) - Parser for the google.api.http path-template grammar.
- [`internity`](./crates/internity/README.md) - Blazingly fast string interning with compact handles, compact storage, and concurrent fill support.
- [`layered`](./crates/layered/README.md) - A foundational service abstraction for building composable, middleware-driven systems.
diff --git a/crates/http_headers/CHANGELOG.md b/crates/http_headers/CHANGELOG.md
new file mode 100644
index 000000000..6a8522106
--- /dev/null
+++ b/crates/http_headers/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## [0.1.0]
+
+- Initial integration into the Oxidizer workspace.
diff --git a/crates/http_headers/Cargo.toml b/crates/http_headers/Cargo.toml
new file mode 100644
index 000000000..6ae7ba327
--- /dev/null
+++ b/crates/http_headers/Cargo.toml
@@ -0,0 +1,183 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+[package]
+name = "http_headers"
+description = "Fast, ergonomic typed HTTP headers with borrowed views."
+version = "0.1.0"
+readme = "README.md"
+edition.workspace = true
+rust-version.workspace = true
+authors.workspace = true
+license.workspace = true
+homepage.workspace = true
+include.workspace = true
+repository = "https://github.com/microsoft/oxidizer/tree/main/crates/http_headers"
+documentation = "https://docs.rs/http_headers"
+keywords = ["http", "headers", "zero-copy", "simd"]
+categories = ["web-programming"]
+autobenches = false
+
+[package.metadata.cargo_check_external_types]
+allowed_external_types = ["bytes::bytes::Bytes", "http::*", "serde_core::de::*", "serde_core::ser::*"]
+
+[package.metadata.docs.rs]
+all-features = true
+
+[features]
+default = ["headers-all"]
+benchmarking = ["http_headers_simd/benchmarking"]
+headers-all = [
+ "headers-authorization",
+ "headers-cache-control",
+ "headers-conditional",
+ "headers-content-length",
+ "headers-content-type",
+ "headers-cors",
+ "headers-etag",
+ "headers-location",
+ "headers-negotiation",
+ "headers-range",
+ "headers-security",
+ "headers-set-cookie",
+ "headers-user-agent",
+ "headers-websocket",
+]
+headers-authorization = ["dep:base64", "dep:zeroize"]
+headers-cache-control = ["dep:compact_str"]
+headers-conditional = ["dep:httpdate", "headers-etag"]
+headers-content-length = []
+headers-content-type = []
+headers-cors = []
+headers-etag = []
+headers-location = ["dep:fluent-uri"]
+headers-negotiation = ["dep:idna"]
+headers-range = []
+headers-security = ["dep:compact_str"]
+headers-set-cookie = []
+headers-user-agent = []
+headers-websocket = ["dep:base64", "dep:sha1"]
+# Optional adapter for the external `http` crate. The adapter may retain
+# `HeaderValue` storage internally without changing typed-header semantics.
+http = ["dep:http"]
+serde = ["dep:serde"]
+
+[dependencies]
+base64 = { workspace = true, optional = true, features = ["std"] }
+bytes = { workspace = true, features = ["std"] }
+compact_str = { workspace = true, optional = true, features = ["std"] }
+fluent-uri = { workspace = true, optional = true, features = ["ipv_future"] }
+http = { workspace = true, optional = true }
+http_headers_simd = { workspace = true, features = ["std"] }
+httpdate = { workspace = true, optional = true }
+idna = { workspace = true, optional = true, features = ["compiled_data", "std"] }
+itoa.workspace = true
+serde = { workspace = true, optional = true, features = ["derive", "std"] }
+sha1 = { workspace = true, optional = true, features = ["std"] }
+smallvec = { workspace = true, features = ["const_new"] }
+zeroize = { workspace = true, optional = true, features = ["alloc"] }
+
+[dev-dependencies]
+axum = { workspace = true, features = ["http1", "json", "tokio"] }
+base64 = { workspace = true, features = ["std"] }
+bolero = { workspace = true, features = ["std"] }
+compact_str = { workspace = true, features = ["std"] }
+criterion = { workspace = true }
+fluent-uri = { workspace = true, features = ["ipv_future"] }
+headers = { workspace = true }
+http = { workspace = true }
+httpdate = { workspace = true }
+idna = { workspace = true, features = ["compiled_data", "std"] }
+metabench = { workspace = true }
+mutants = { workspace = true }
+paste = { workspace = true }
+serde = { workspace = true, features = ["derive", "std"] }
+serde_json = { workspace = true, features = ["std"] }
+sha1 = { workspace = true, features = ["std"] }
+tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread"] }
+tower = { workspace = true, features = ["util"] }
+zeroize = { workspace = true, features = ["alloc"] }
+
+[[example]]
+name = "axum"
+required-features = ["headers-cache-control", "headers-user-agent", "http"]
+
+[[bench]]
+name = "http_headers_micro"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_storage"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_per_header"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_name_recognition"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_policy"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_typed_tokens"
+harness = false
+required-features = ["benchmarking", "http", "headers-negotiation"]
+
+[[bench]]
+name = "http_headers_location_semantics"
+harness = false
+required-features = ["benchmarking", "http", "headers-location"]
+
+[[bench]]
+name = "http_headers_authority_semantics"
+harness = false
+required-features = ["benchmarking", "http", "headers-negotiation", "headers-cors"]
+
+[[bench]]
+name = "http_headers_negotiation_semantics"
+harness = false
+required-features = ["benchmarking", "http", "headers-negotiation"]
+
+[[bench]]
+name = "http_headers_protocol"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_websocket_shapes"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_negotiation_shapes"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_auth_cors_shapes"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_conditional_range_shapes"
+harness = false
+required-features = ["benchmarking", "http"]
+
+[[bench]]
+name = "http_headers_policy_shapes"
+harness = false
+required-features = ["benchmarking", "http"]
+
+# >>> anvil-managed: anvil-lints
+[lints]
+workspace = true
+# <<< anvil-managed: anvil-lints
diff --git a/crates/http_headers/README.md b/crates/http_headers/README.md
new file mode 100644
index 000000000..116ac1cfe
--- /dev/null
+++ b/crates/http_headers/README.md
@@ -0,0 +1,465 @@
+
+

+
+# Http Headers
+
+[](https://crates.io/crates/http_headers)
+[](https://docs.rs/http_headers)
+[](https://crates.io/crates/http_headers)
+[](https://github.com/microsoft/oxidizer/actions/workflows/anvil-pr.yml)
+[](https://codecov.io/gh/microsoft/oxidizer)
+[](https://github.com/microsoft/oxidizer/blob/main/LICENSE)
+

+
+
+
+Efficient and robust HTTP header parsing and creation.
+
+This crate provides:
+
+* Highly optimized parsing of incoming HTTP headers which produce owned or borrowed
+ strongly-typed Rust structs. These parsers insulate your code from badly formed
+ headers.
+
+* Highly optimized production of headers, ensuring the headers are well-formed.
+
+Header parsing and production are abstracted over their source and destination.
+The optional `http` feature integrates with
+[`HeaderMap`][__link0]
+plus generic [`Request`][__link1]
+and [`Response`][__link2]
+values from the [`http`][__link3] crate.
+
+## Parsing headers
+
+Headers are parsed from an implementation of the [`source::FieldSource`][__link4] trait. The `http` crate feature
+implements this trait for [`HeaderMap`][__link5],
+[`Request`][__link6], and
+[`Response`][__link7].
+Once you have a source, you can choose to parse into borrowed views or owned structs.
+Prefer borrowed views when the decoded value does not need to outlive the
+source as they are generally faster. Use owned structs when the parsed header
+data needs to be retained (such as in a cache).
+
+Source and sink operations use static field-name descriptors, including
+custom names stored in a `static LazyLock`. Locally constructed
+runtime names are supported by [`FieldName`][__link8] for validation and conversion,
+but dynamic lookup and mutation must use the container’s native API.
+
+[`Field::view`][__link9] returns a header’s
+borrowed `*View` type, whose lifetime is tied to the source.
+
+```rust
+use http::HeaderMap;
+use http_headers::Field;
+use http_headers::headers::{ContentType, UserAgent};
+
+// create a HeaderMap to show how to read from it
+let mut headers = HeaderMap::new();
+headers.insert(
+ http::header::USER_AGENT,
+ http::HeaderValue::from_static("example-client/1.0"),
+);
+headers.insert(
+ http::header::CONTENT_TYPE,
+ http::HeaderValue::from_static("application/json; charset=utf-8"),
+);
+
+if let Some(agent) = UserAgent::view(&headers)? {
+ assert_eq!(agent.as_str()?, "example-client/1.0");
+}
+
+if let Some(content_type) = ContentType::view(&headers)? {
+ assert_eq!(content_type.type_()?, "application");
+ assert_eq!(content_type.subtype()?, "json");
+ assert_eq!(
+ content_type.parameter("charset")?,
+ Some(b"utf-8".as_slice())
+ );
+}
+```
+
+Prefer `view` unless the decoded value must outlive the source. Use
+[`Field::owned`][__link10] when you need to retain, move, or independently
+store the result:
+
+```rust
+use http::HeaderMap;
+use http_headers::Field;
+use http_headers::headers::UserAgent;
+
+let mut headers = HeaderMap::new();
+headers.insert(
+ http::header::USER_AGENT,
+ http::HeaderValue::from_static("example-client/1.0"),
+);
+
+let owned = UserAgent::owned(&headers)?.expect("User-Agent is present");
+drop(headers);
+assert_eq!(owned.as_bytes(), b"example-client/1.0");
+```
+
+Both methods return `Ok(None)` when the header is absent and `Err` when a
+present value is malformed.
+
+### Reading validated members
+
+Structured headers expose semantic values as well as their original wire
+representation. `AllowOwned::methods()` yields case-sensitive method tokens;
+`VaryOwned::entries()` distinguishes wildcard members from case-insensitive
+field names. These borrowed member reads do not allocate.
+
+```rust
+use http_headers::headers::{AllowOwned, MethodView, VaryOwned};
+
+let allow = AllowOwned::try_from("GET, HEAD, CUSTOM")?;
+assert!(allow.methods().any(|method| method == MethodView::GET));
+assert!(!allow.methods().any(|method| method == MethodView::POST));
+
+let vary = VaryOwned::try_from("Accept-Encoding, X-Tenant")?;
+assert!(!vary.contains_wildcard());
+assert!(vary.entries().any(|entry| {
+ entry
+ .field_name()
+ .is_some_and(|name| name.eq_ignore_ascii_case("x-tenant"))
+}));
+```
+
+The Accept family exposes typed ranges, parameters, and exact quality
+weights through `entries()`. Location exposes URI-reference components
+through `uri_reference()`. Host and Allow-Origin retain parsed authority
+components. Semantic access does not sort lists or replace the original
+field lines used for forwarding.
+
+## Producing headers
+
+You produce headers by populating an implementation of the [`sink::FieldSink`][__link11] trait. The
+`http` cargo feature implements this trait for
+[`HeaderMap`][__link12],
+[`Request`][__link13], and
+[`Response`][__link14].
+
+Enabling a header-family feature exposes `sink::FieldSinkExt`, whose fluent
+methods work for any sink. Core-only builds use [`sink::FieldSink`][__link15] directly.
+
+```rust
+use std::time::Duration;
+
+use http::HeaderMap;
+use http_headers::headers::{CacheControl, ContentType};
+use http_headers::sink::FieldSinkExt;
+
+let mut headers = HeaderMap::new();
+headers
+ .set_content_type(ContentType::json())?
+ .set_content_length(1_024)?
+ .set_cache_control(CacheControl::public().max_age(Duration::from_secs(60)))?;
+```
+
+An owned value can insert itself when it has already been constructed:
+
+```rust
+use http::HeaderMap;
+use http_headers::headers::LocationOwned;
+
+let mut headers = HeaderMap::new();
+LocationOwned::try_from("/next")?.insert_into(&mut headers)?;
+```
+
+A borrowed view can also be forwarded directly to another sink:
+
+```rust
+use http::HeaderMap;
+use http_headers::Field;
+use http_headers::headers::UserAgent;
+
+let mut incoming = HeaderMap::new();
+incoming.insert(
+ http::header::USER_AGENT,
+ http::HeaderValue::from_static("example-client/1.0"),
+);
+
+let mut outgoing = HeaderMap::new();
+if let Some(agent) = UserAgent::view(&incoming)? {
+ agent.insert_into(&mut outgoing)?;
+}
+```
+
+[`Field::insert`][__link16] is the generic alternative when the descriptor type is
+already known. It replaces all existing field lines for that header;
+[`Field::remove`][__link17] removes them instead.
+
+```rust
+use http::HeaderMap;
+use http_headers::Field;
+use http_headers::headers::{UserAgent, UserAgentOwned};
+
+let mut headers = HeaderMap::new();
+UserAgent::insert(
+ &mut headers,
+ UserAgentOwned::try_from_static("example-client/1.0")?,
+)?;
+UserAgent::remove(&mut headers);
+```
+
+Repeated field lines remain separate. In particular, `Set-Cookie` values are
+never comma-joined:
+
+```rust
+use http::HeaderMap;
+use http_headers::Field;
+use http_headers::headers::{SetCookie, SetCookieOwned};
+
+let mut cookies = SetCookieOwned::new();
+cookies.push_str("session=abc; Path=/; HttpOnly")?;
+cookies.push_str("theme=dark; Path=/")?;
+
+let mut headers = HeaderMap::new();
+SetCookie::insert(&mut headers, cookies)?;
+assert_eq!(headers.get_all(http::header::SET_COOKIE).iter().count(), 2);
+```
+
+## Serialization
+
+The `serde` cargo feature implements Serde serialization and deserialization for every owned header
+struct, [`FieldName`][__link18], [`FieldValue`][__link19], and [`sink::EncodedValues`][__link20].
+Headers serialize as an ordered sequence of physical field values and
+deserialize through relaxed validation, which includes strict syntax and
+the documented interoperability deviations. This preserves round trips for
+every owned value produced by the public API:
+
+```rust
+use http_headers::headers::UserAgentOwned;
+
+let header = UserAgentOwned::try_from("example-client/1.0")?;
+let json = serde_json::to_string(&header)?;
+let decoded: UserAgentOwned = serde_json::from_str(&json)?;
+assert_eq!(decoded.as_bytes(), header.as_bytes());
+```
+
+Repeated lines retain their boundaries:
+
+```rust
+use http_headers::headers::SetCookieOwned;
+
+let mut cookies = SetCookieOwned::new();
+cookies.push_str("session=abc")?;
+cookies.push_str("theme=dark")?;
+let json = serde_json::to_string(&cookies)?;
+let decoded: SetCookieOwned = serde_json::from_str(&json)?;
+assert_eq!(decoded.len(), 2);
+```
+
+Serialization is not redaction. Sensitive values include their original
+bytes and an explicit sensitivity marker, so serialized data must be
+protected like the header value itself:
+
+```rust
+use http_headers::{FieldSensitivity, FieldValue};
+
+let secret =
+ FieldValue::from_static("credential").with_sensitivity(FieldSensitivity::Sensitive);
+let json = serde_json::to_string(&secret)?;
+let decoded: FieldValue = serde_json::from_str(&json)?;
+assert_eq!(decoded.as_bytes(), b"credential");
+assert!(decoded.is_sensitive());
+```
+
+## Strict and relaxed reads
+
+[`Field::view`][__link21] and [`Field::owned`][__link22] use strict syntax. Applications that
+must accept specific common deviations can request [`DecodeMode::Relaxed`][__link23]
+through [`Field::view_with`][__link24] or [`Field::owned_with`][__link25]:
+
+```rust
+use http_headers::headers::AcceptEncoding;
+use http_headers::{DecodeMode, Field};
+
+let mut headers = http::HeaderMap::new();
+headers.insert(
+ http::header::ACCEPT_ENCODING,
+ http::HeaderValue::from_static("gzip; q = .5"),
+);
+
+assert!(AcceptEncoding::view(&headers).is_err());
+assert!(AcceptEncoding::view_with(&headers, DecodeMode::Relaxed)?.is_some());
+```
+
+Relaxed mode is not a general validation bypass. Each header documents the
+additional forms it accepts, and the original field bytes are preserved.
+
+## Sensitive values
+
+Authorization, `Location`, and cookie values are marked sensitive so their
+`Debug` representations and compatible sinks do not reveal their contents.
+Basic authentication can be read through a borrowed view while reusing
+caller-owned decode storage:
+
+```rust
+use http::HeaderMap;
+use http_headers::Field;
+use http_headers::headers::{Authorization, Basic, BasicCredentials};
+
+let mut headers = HeaderMap::new();
+headers.insert(
+ http::header::AUTHORIZATION,
+ http::HeaderValue::from_static("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
+);
+
+let authorization = Authorization::::view(&headers)?.expect("Authorization is present");
+let mut credentials = BasicCredentials::new();
+let decoded = authorization.extract(&mut credentials)?;
+assert_eq!(decoded.username(), b"Aladdin");
+credentials.clear();
+```
+
+`BasicCredentials` zeroizes decoded bytes when cleared, reused, or dropped.
+
+## Defining a custom single-value header
+
+Implement [`SingleValueField`][__link26] when a custom header is represented by exactly
+one field line. The crate then supplies its [`Field`][__link27] implementation,
+including borrowed and owned reads, singleton cardinality checks, insertion,
+and removal.
+
+```rust
+use std::sync::LazyLock;
+
+use http_headers::{
+ DecodeError, DecodeErrorKind, FieldName, FieldValue, FieldValueRef, SingleValueField,
+};
+
+static REQUEST_ID: LazyLock =
+ LazyLock::new(|| FieldName::from_static("x-request-id"));
+
+struct RequestId;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct RequestIdOwned(FieldValue);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+struct RequestIdView<'a>(FieldValueRef<'a>);
+
+fn is_token(bytes: &[u8]) -> bool {
+ !bytes.is_empty()
+ && bytes
+ .iter()
+ .all(|byte| byte.is_ascii_alphanumeric() || b"!#$%&'*+-.^_`|~".contains(byte))
+}
+
+impl SingleValueField for RequestId {
+ type View<'a> = RequestIdView<'a>;
+ type Owned = RequestIdOwned;
+
+ fn name() -> &'static FieldName {
+ &REQUEST_ID
+ }
+
+ fn decode_view(value: FieldValueRef<'_>) -> Result, DecodeError> {
+ if is_token(value.as_bytes()) {
+ Ok(RequestIdView(value))
+ } else {
+ Err(DecodeError::new(&REQUEST_ID, DecodeErrorKind::InvalidToken))
+ }
+ }
+
+ fn decode_owned(value: FieldValue) -> Result {
+ if is_token(value.as_bytes()) {
+ Ok(RequestIdOwned(value))
+ } else {
+ Err(DecodeError::new(&REQUEST_ID, DecodeErrorKind::InvalidToken))
+ }
+ }
+
+ fn as_field_value(value: &Self::Owned) -> &FieldValue {
+ &value.0
+ }
+
+ fn into_field_value(value: Self::Owned) -> FieldValue {
+ value.0
+ }
+}
+```
+
+## Performance
+
+[`docs/PERF.md`][__link28]
+records comparative typed-decode-and-read measurements against `headers 0.4.1`.
+Results vary by header, ownership mode, and hardware, and include both faster
+and slower cases. The table does not measure comparative header production or
+end-to-end request processing. Borrowed reads generally avoid allocations.
+
+## Cargo features
+
+* `headers-all` (enabled by default): all built-in typed header families.
+* `headers-authorization`, `headers-cache-control`, `headers-conditional`,
+ `headers-content-length`, `headers-content-type`, `headers-cors`,
+ `headers-etag`, `headers-location`, `headers-negotiation`, `headers-range`,
+ `headers-security`, `headers-set-cookie`, `headers-user-agent`, and
+ `headers-websocket`: individual built-in header families.
+* `http`: optional adapter for `http::HeaderMap` and the `http` crate’s name,
+ value, and method types.
+* `serde`: serialization and deserialization for owned headers,
+ [`FieldName`][__link29], [`FieldValue`][__link30], and [`sink::EncodedValues`][__link31].
+
+Disable default features to use only the core source, sink, name, and value
+APIs, then enable only the header families an application needs.
+
+## What about trailers?
+
+Although this crate is named `http_headers`, it fully supports trailers as well.
+The crate doesn’t currently expose any trailer-specific structs however, so you
+would need to define those structs and implement the parsers yourself as implementations
+of the traits in this crate.
+
+## Alternate crates
+
+This crate is an alternative to the popular [`headers`][__link32] crate.
+`http_headers` has the following benefits:
+
+* Faster decoding for some headers in the measured configurations
+* Supports more headers
+* Performs more robust validation to avoid downstream surprises
+* Supports explicit relaxed parsing options to support common malformed headers
+* Supports serde
+
+
+
+
+This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
+
+
+ [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQborR2_k_xJd4bTcf2krrNPIcbP72Pw1UdRjkbim_eMDe2BBthYvRhcoQbfVFs3NqFhWgbBn84idxhrs4bC_HSVxhRRoYbYww44PeKrh5hZIGCbGh0dHBfaGVhZGVyc2UwLjEuMA
+ [__link0]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
+ [__link1]: https://docs.rs/http/latest/http/request/struct.Request.html
+ [__link10]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::owned
+ [__link11]: https://docs.rs/http_headers/0.1.0/http_headers/?search=sink::FieldSink
+ [__link12]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
+ [__link13]: https://docs.rs/http/latest/http/request/struct.Request.html
+ [__link14]: https://docs.rs/http/latest/http/response/struct.Response.html
+ [__link15]: https://docs.rs/http_headers/0.1.0/http_headers/?search=sink::FieldSink
+ [__link16]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::insert
+ [__link17]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::remove
+ [__link18]: https://docs.rs/http_headers/0.1.0/http_headers/?search=FieldName
+ [__link19]: https://docs.rs/http_headers/0.1.0/http_headers/?search=FieldValue
+ [__link2]: https://docs.rs/http/latest/http/response/struct.Response.html
+ [__link20]: https://docs.rs/http_headers/0.1.0/http_headers/?search=sink::EncodedValues
+ [__link21]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::view
+ [__link22]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::owned
+ [__link23]: https://docs.rs/http_headers/0.1.0/http_headers/?search=DecodeMode::Relaxed
+ [__link24]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::view_with
+ [__link25]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::owned_with
+ [__link26]: https://docs.rs/http_headers/0.1.0/http_headers/?search=SingleValueField
+ [__link27]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field
+ [__link28]: https://github.com/microsoft/oxidizer/blob/main/crates/http_headers/docs/PERF.md
+ [__link29]: https://docs.rs/http_headers/0.1.0/http_headers/?search=FieldName
+ [__link3]: https://crates.io/crates/http
+ [__link30]: https://docs.rs/http_headers/0.1.0/http_headers/?search=FieldValue
+ [__link31]: https://docs.rs/http_headers/0.1.0/http_headers/?search=sink::EncodedValues
+ [__link32]: https://crates.io/crates/headers
+ [__link4]: https://docs.rs/http_headers/0.1.0/http_headers/?search=source::FieldSource
+ [__link5]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
+ [__link6]: https://docs.rs/http/latest/http/request/struct.Request.html
+ [__link7]: https://docs.rs/http/latest/http/response/struct.Response.html
+ [__link8]: https://docs.rs/http_headers/0.1.0/http_headers/?search=FieldName
+ [__link9]: https://docs.rs/http_headers/0.1.0/http_headers/?search=Field::view
diff --git a/crates/http_headers/benches/http_headers_auth_cors_shapes.rs b/crates/http_headers/benches/http_headers_auth_cors_shapes.rs
new file mode 100644
index 000000000..337bd9c2d
--- /dev/null
+++ b/crates/http_headers/benches/http_headers_auth_cors_shapes.rs
@@ -0,0 +1,488 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! HTTP-backed and raw-source decode shapes for authorization, CORS and content length.
+
+use http_headers::DecodeErrorKind;
+use http_headers::headers::{
+ AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlAllowOrigin,
+ AccessControlExposeHeaders, AccessControlMaxAge, AccessControlRequestHeaders, AccessControlRequestMethod, Authorization, Basic, Bearer,
+ ContentLength,
+};
+
+#[path = "http_headers_shapes_common.rs"]
+mod shapes;
+
+use shapes::Expected;
+
+shapes::define_shapes!(
+ "http_headers_auth_cors_shapes/parse";
+ (authorization_basic_short, Authorization, &["Basic dTpw"], Strict, Expected::Valid),
+ (
+ authorization_basic_existing_fixture,
+ Authorization,
+ &["Basic YWxhZGRpbjpvcGVuc2VzYW1l"],
+ Strict,
+ Expected::Valid
+ ),
+ (authorization_basic_padded, Authorization, &["Basic dXNlcjpwYXNzd29yZA=="], Strict, Expected::Valid),
+ (
+ authorization_basic_service,
+ Authorization,
+ &["Basic Z2F0ZXdheS1zZXJ2aWNlLWFjY291bnQ6c3ludGhldGljLXBhc3N3b3JkLXdpdGgtNjQtY2hhcmFjdGVycy0wMTIzNDU2Nzg5LWFiY2RlZmdoaWprbG1ub3A="],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ authorization_basic_long_username,
+ Authorization,
+ &["Basic bG9uZy1zZXJ2aWNlLWFjY291bnQtbmFtZS1mb3ItYXV0aGVudGljYXRpb24tcHJveHktdGVzdGluZzpzaG9ydA=="],
+ Strict,
+ Expected::Valid
+ ),
+ (authorization_basic_binary_password, Authorization, &["Basic dXNlcjoA/w=="], Strict, Expected::Valid),
+ (authorization_basic_mixed_case_spaces, Authorization, &["bAsIc dTpw"], Strict, Expected::Valid),
+ (authorization_basic_relaxed, Authorization, &["bAsIc dTpw"], Relaxed, Expected::Valid),
+ (authorization_basic_absent, Authorization, &[], Strict, Expected::Absent),
+ (
+ authorization_basic_noncanonical_pad_bits,
+ Authorization,
+ &["Basic Oh=="],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ authorization_basic_missing_colon,
+ Authorization,
+ &["Basic bm8tY29sb24="],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ authorization_basic_repeated,
+ Authorization,
+ &["Basic dTpw", "Basic dTpw"],
+ Strict,
+ Expected::Error(DecodeErrorKind::UnexpectedMultipleValues)
+ ),
+ (authorization_bearer_short, Authorization, &["Bearer abc.def"], Strict, Expected::Valid),
+ (authorization_bearer_token_eight, Authorization, &["Bearer test1234"], Strict, Expected::Valid),
+ (authorization_bearer_token_nine, Authorization, &["Bearer test12345"], Strict, Expected::Valid),
+ (
+ authorization_bearer_token_31,
+ Authorization,
+ &["Bearer abcdefghijklmnopqrstuvwxyz01234"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ authorization_bearer_token_32,
+ Authorization,
+ &["Bearer abcdefghijklmnopqrstuvwxyz012345"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ authorization_bearer_token_33,
+ Authorization,
+ &["Bearer abcdefghijklmnopqrstuvwxyz0123456"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ authorization_bearer_synthetic_jwt_shape,
+ Authorization,
+ &["Bearer syntheticHeader0123456789.syntheticPayloadForGatewayServiceAccountWithScopesReadWriteAndExpiry0123456789.syntheticSignatureAbCdEfGhIjKlMnOpQrStUvWxYz0123456789_-"],
+ Strict,
+ Expected::Valid
+ ),
+ (authorization_bearer_padding, Authorization, &["Bearer YWJjZA=="], Strict, Expected::Valid),
+ (authorization_bearer_mixed_case_spaces, Authorization, &["bEaReR abc.def"], Strict, Expected::Valid),
+ (authorization_bearer_absent, Authorization, &[], Strict, Expected::Absent),
+ (
+ authorization_bearer_interior_padding,
+ Authorization,
+ &["Bearer ab=c"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ authorization_bearer_invalid_relaxed,
+ Authorization,
+ &["Bearer ab=c"],
+ Relaxed,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ authorization_bearer_repeated,
+ Authorization,
+ &["Bearer abc.def", "Bearer abc.def"],
+ Strict,
+ Expected::Error(DecodeErrorKind::UnexpectedMultipleValues)
+ ),
+ (access_control_allow_credentials_true, AccessControlAllowCredentials, &["true"], Strict, Expected::Valid),
+ (access_control_allow_credentials_leading_ows, AccessControlAllowCredentials, &[" true"], Strict, Expected::Valid),
+ (access_control_allow_credentials_trailing_ows, AccessControlAllowCredentials, &["true\t"], Strict, Expected::Valid),
+ (access_control_allow_credentials_framed, AccessControlAllowCredentials, &[" \ttrue\t "], Strict, Expected::Valid),
+ (
+ access_control_allow_credentials_wide_ows,
+ AccessControlAllowCredentials,
+ &[" true "],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_credentials_absent, AccessControlAllowCredentials, &[], Strict, Expected::Absent),
+ (
+ access_control_allow_credentials_empty,
+ AccessControlAllowCredentials,
+ &[""],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_allow_credentials_case_relaxed,
+ AccessControlAllowCredentials,
+ &["TRUE"],
+ Relaxed,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_allow_credentials_suffix,
+ AccessControlAllowCredentials,
+ &[" truee "],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_allow_credentials_repeated,
+ AccessControlAllowCredentials,
+ &["true", "true"],
+ Strict,
+ Expected::Error(DecodeErrorKind::UnexpectedMultipleValues)
+ ),
+ (
+ access_control_allow_headers_common_pair,
+ AccessControlAllowHeaders,
+ &["content-type, x-request-id"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_headers_custom, AccessControlAllowHeaders, &["x-custom-header"], Strict, Expected::Valid),
+ (
+ access_control_allow_headers_mixed_case,
+ AccessControlAllowHeaders,
+ &["Content-Type, X-Correlation-Id"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_allow_headers_large,
+ AccessControlAllowHeaders,
+ &["authorization, content-type, x-request-id, x-correlation-id, x-client-version, x-tenant-id, x-trace-id, x-idempotency-key, x-api-version, x-requested-with, traceparent, tracestate"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_allow_headers_repeated,
+ AccessControlAllowHeaders,
+ &["content-type, x-request-id", "authorization", "x-custom-header, content-type"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_allow_headers_sixteen_lines,
+ AccessControlAllowHeaders,
+ &["x-00", "x-01", "x-02", "x-03", "x-04", "x-05", "x-06", "x-07", "x-08", "x-09", "x-10", "x-11", "x-12", "x-13", "x-14", "x-15"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_headers_empty, AccessControlAllowHeaders, &[""], Strict, Expected::Valid),
+ (
+ access_control_allow_headers_empty_slots,
+ AccessControlAllowHeaders,
+ &[" ,\t, content-type,, X-Trace-Id, "],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_headers_wildcard, AccessControlAllowHeaders, &["*"], Strict, Expected::Valid),
+ (
+ access_control_allow_headers_late_error,
+ AccessControlAllowHeaders,
+ &["content-type", "x-request-id", "bad:name"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidToken)
+ ),
+ (access_control_allow_headers_absent, AccessControlAllowHeaders, &[], Strict, Expected::Absent),
+ (access_control_allow_methods_pair, AccessControlAllowMethods, &["GET, POST"], Strict, Expected::Valid),
+ (
+ access_control_allow_methods_crud,
+ AccessControlAllowMethods,
+ &["GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_allow_methods_extensions,
+ AccessControlAllowMethods,
+ &["PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_allow_methods_repeated,
+ AccessControlAllowMethods,
+ &["GET, X-PURGE,,", "PATCH, GET", "POST"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_methods_empty, AccessControlAllowMethods, &[""], Strict, Expected::Valid),
+ (
+ access_control_allow_methods_whitespace,
+ AccessControlAllowMethods,
+ &["\t GET ,\tPOST, , PATCH \t"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_methods_wildcard, AccessControlAllowMethods, &["*"], Strict, Expected::Valid),
+ (access_control_allow_methods_absent, AccessControlAllowMethods, &[], Strict, Expected::Absent),
+ (
+ access_control_allow_methods_late_error,
+ AccessControlAllowMethods,
+ &["GET, POST", "PATCH", "bad method"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidToken)
+ ),
+ (access_control_allow_methods_relaxed, AccessControlAllowMethods, &["get, X-PURGE"], Relaxed, Expected::Valid),
+ (access_control_allow_origin_https, AccessControlAllowOrigin, &["https://example.com"], Strict, Expected::Valid),
+ (access_control_allow_origin_http, AccessControlAllowOrigin, &["http://example.com"], Strict, Expected::Valid),
+ (access_control_allow_origin_wildcard, AccessControlAllowOrigin, &["*"], Strict, Expected::Valid),
+ (access_control_allow_origin_null, AccessControlAllowOrigin, &["null"], Strict, Expected::Valid),
+ (access_control_allow_origin_port, AccessControlAllowOrigin, &["https://api.example.com:8443"], Strict, Expected::Valid),
+ (
+ access_control_allow_origin_long_domain,
+ AccessControlAllowOrigin,
+ &["https://service-authentication.production.westus2.customer-tenant-0123456789.internal.example.com"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_origin_ipv4, AccessControlAllowOrigin, &["http://127.0.0.1"], Strict, Expected::Valid),
+ (access_control_allow_origin_ipv6_port, AccessControlAllowOrigin, &["https://[2001:db8::1]:8443"], Strict, Expected::Valid),
+ (
+ access_control_allow_origin_ipv6_uncompressed,
+ AccessControlAllowOrigin,
+ &["https://[2001:db8:1:2:3:4:5:6]"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_allow_origin_wss, AccessControlAllowOrigin, &["wss://example.com"], Strict, Expected::Valid),
+ (access_control_allow_origin_whitespace, AccessControlAllowOrigin, &[" \thttps://example.com\t "], Strict, Expected::Valid),
+ (access_control_allow_origin_absent, AccessControlAllowOrigin, &[], Strict, Expected::Absent),
+ (
+ access_control_allow_origin_uppercase_host,
+ AccessControlAllowOrigin,
+ &["https://Example.com"],
+ Relaxed,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_allow_origin_default_port,
+ AccessControlAllowOrigin,
+ &["https://example.com:443"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_allow_origin_ipv6_leading_zero,
+ AccessControlAllowOrigin,
+ &["https://[2001:0db8::1]"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_allow_origin_repeated,
+ AccessControlAllowOrigin,
+ &["https://example.com", "https://example.com"],
+ Strict,
+ Expected::Error(DecodeErrorKind::UnexpectedMultipleValues)
+ ),
+ (access_control_expose_headers_pair, AccessControlExposeHeaders, &["etag, x-request-id"], Strict, Expected::Valid),
+ (
+ access_control_expose_headers_response,
+ AccessControlExposeHeaders,
+ &["content-length, content-range, etag"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_expose_headers_mixed_case,
+ AccessControlExposeHeaders,
+ &["X-Request-Id, Content-Length"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_expose_headers_large,
+ AccessControlExposeHeaders,
+ &["etag, content-length, content-range, x-request-id, x-correlation-id, x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset, retry-after, server-timing, x-api-version, x-total-count"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_expose_headers_repeated,
+ AccessControlExposeHeaders,
+ &["etag, x-request-id", "X-Trace-Id", "etag, content-length"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_expose_headers_empty, AccessControlExposeHeaders, &[""], Strict, Expected::Valid),
+ (access_control_expose_headers_wildcard, AccessControlExposeHeaders, &["*"], Strict, Expected::Valid),
+ (
+ access_control_expose_headers_empty_slots,
+ AccessControlExposeHeaders,
+ &["\t, etag,, X-Request-Id,\t"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_expose_headers_late_error,
+ AccessControlExposeHeaders,
+ &["etag", "x-request-id", "x-bad:name"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidToken)
+ ),
+ (access_control_expose_headers_absent, AccessControlExposeHeaders, &[], Strict, Expected::Absent),
+ (access_control_max_age_600, AccessControlMaxAge, &["600"], Strict, Expected::Valid),
+ (access_control_max_age_zero, AccessControlMaxAge, &["0"], Strict, Expected::Valid),
+ (access_control_max_age_whitespace, AccessControlMaxAge, &[" \t00600\t "], Strict, Expected::Valid),
+ (access_control_max_age_nineteen_digits, AccessControlMaxAge, &["9999999999999999999"], Strict, Expected::Valid),
+ (access_control_max_age_maximum, AccessControlMaxAge, &["18446744073709551615"], Strict, Expected::Valid),
+ (access_control_max_age_many_zeroes, AccessControlMaxAge, &["00000000000000000000000000000600"], Strict, Expected::Valid),
+ (access_control_max_age_absent, AccessControlMaxAge, &[], Strict, Expected::Absent),
+ (
+ access_control_max_age_overflow,
+ AccessControlMaxAge,
+ &["18446744073709551616"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidNumber)
+ ),
+ (access_control_max_age_invalid_relaxed, AccessControlMaxAge, &["-1"], Relaxed, Expected::Error(DecodeErrorKind::InvalidNumber)),
+ (
+ access_control_max_age_repeated,
+ AccessControlMaxAge,
+ &["600", "600"],
+ Strict,
+ Expected::Error(DecodeErrorKind::UnexpectedMultipleValues)
+ ),
+ (access_control_request_headers_pair, AccessControlRequestHeaders, &["content-type, x-request-id"], Strict, Expected::Valid),
+ (access_control_request_headers_single, AccessControlRequestHeaders, &["content-type"], Strict, Expected::Valid),
+ (
+ access_control_request_headers_mixed_case,
+ AccessControlRequestHeaders,
+ &["X-Trace-Id, Content-Type"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_request_headers_custom, AccessControlRequestHeaders, &["x-idempotency-key"], Strict, Expected::Valid),
+ (
+ access_control_request_headers_large,
+ AccessControlRequestHeaders,
+ &["authorization, content-type, traceparent, tracestate, x-api-version, x-client-version, x-correlation-id, x-idempotency-key, x-request-id, x-tenant-id, x-trace-id"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_request_headers_repeated,
+ AccessControlRequestHeaders,
+ &["X-Trace-Id, content-type", "x-trace-id", "authorization"],
+ Strict,
+ Expected::Valid
+ ),
+ (
+ access_control_request_headers_empty_then_member,
+ AccessControlRequestHeaders,
+ &[" , ", "content-type", ""],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_request_headers_absent, AccessControlRequestHeaders, &[], Strict, Expected::Absent),
+ (
+ access_control_request_headers_empty_repeated,
+ AccessControlRequestHeaders,
+ &[" , ", "\t,,", ""],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ access_control_request_headers_late_error,
+ AccessControlRequestHeaders,
+ &["content-type", "x-trace-id", "bad:name"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidToken)
+ ),
+ (access_control_request_method_post, AccessControlRequestMethod, &["POST"], Strict, Expected::Valid),
+ (access_control_request_method_get, AccessControlRequestMethod, &["GET"], Strict, Expected::Valid),
+ (access_control_request_method_patch, AccessControlRequestMethod, &["PATCH"], Strict, Expected::Valid),
+ (access_control_request_method_options, AccessControlRequestMethod, &["OPTIONS"], Strict, Expected::Valid),
+ (access_control_request_method_extension, AccessControlRequestMethod, &["PROPFIND"], Strict, Expected::Valid),
+ (
+ access_control_request_method_long_extension,
+ AccessControlRequestMethod,
+ &["X-REBUILD-SEARCH-INDEX-FOR-TENANT"],
+ Strict,
+ Expected::Valid
+ ),
+ (access_control_request_method_lowercase, AccessControlRequestMethod, &["post"], Strict, Expected::Valid),
+ (access_control_request_method_framed_registered, AccessControlRequestMethod, &[" \tPOST\t "], Strict, Expected::Valid),
+ (access_control_request_method_absent, AccessControlRequestMethod, &[], Strict, Expected::Absent),
+ (
+ access_control_request_method_invalid_relaxed,
+ AccessControlRequestMethod,
+ &["GET, POST"],
+ Relaxed,
+ Expected::Error(DecodeErrorKind::InvalidToken)
+ ),
+ (
+ access_control_request_method_repeated,
+ AccessControlRequestMethod,
+ &["POST", "POST"],
+ Strict,
+ Expected::Error(DecodeErrorKind::UnexpectedMultipleValues)
+ ),
+ (content_length_348, ContentLength, &["348"], Strict, Expected::Valid),
+ (content_length_zero, ContentLength, &["0"], Strict, Expected::Valid),
+ (content_length_megabyte, ContentLength, &["1048576"], Strict, Expected::Valid),
+ (content_length_nineteen_digits, ContentLength, &["9999999999999999999"], Strict, Expected::Valid),
+ (content_length_maximum, ContentLength, &["18446744073709551615"], Strict, Expected::Valid),
+ (content_length_many_zeroes, ContentLength, &["00000000000000000000000000000348"], Strict, Expected::Valid),
+ (content_length_whitespace, ContentLength, &[" \t348\t "], Strict, Expected::Valid),
+ (content_length_joined_equal, ContentLength, &["348, 348"], Strict, Expected::Valid),
+ (
+ content_length_repeated_numeric_equivalence,
+ ContentLength,
+ &["000348, 348", " 348 ", "348,348", "00348"],
+ Strict,
+ Expected::Valid
+ ),
+ (content_length_absent, ContentLength, &[], Strict, Expected::Absent),
+ (
+ content_length_overflow,
+ ContentLength,
+ &["18446744073709551616"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidNumber)
+ ),
+ (
+ content_length_repeated_conflict,
+ ContentLength,
+ &["348", "348", "349"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidSyntax)
+ ),
+ (
+ content_length_late_malformed,
+ ContentLength,
+ &["348", "348", "348x"],
+ Strict,
+ Expected::Error(DecodeErrorKind::InvalidNumber)
+ ),
+);
diff --git a/crates/http_headers/benches/http_headers_authority_semantics.rs b/crates/http_headers/benches/http_headers_authority_semantics.rs
new file mode 100644
index 000000000..67fe21906
--- /dev/null
+++ b/crates/http_headers/benches/http_headers_authority_semantics.rs
@@ -0,0 +1,341 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! Authority inspection for routing, allow-list comparison, and forwarding.
+//!
+//! Each pair compares retained components with explicit consumer-side parsing
+//! of the existing textual accessors. Both use the current header decoder, so
+//! these are workload comparisons, not historical decoder baselines. The
+//! `decode` groups include validation; `reads` groups reuse predecoded values.
+
+use std::borrow::Cow;
+use std::hint::black_box;
+use std::net::{Ipv4Addr, Ipv6Addr};
+use std::sync::OnceLock;
+
+use criterion::{BatchSize, BenchmarkId, Criterion};
+use http_headers::headers::{
+ AccessControlAllowOrigin, AccessControlAllowOriginKind, AccessControlAllowOriginOwned, AccessControlAllowOriginView, Host, HostKind,
+ HostOwned, HostView, OriginHost, OriginScheme, PortConversionError, PortConversionErrorKind,
+};
+use http_headers::source::{FieldLines, FieldSource};
+use http_headers::{DecodeMode, Field, FieldName, FieldValue, SingleValueField};
+
+#[derive(Debug, Eq, PartialEq)]
+enum HostSemantic<'a> {
+ Name(Cow<'a, str>),
+ Ipv4(Ipv4Addr),
+ Ipv6(Ipv6Addr),
+ Future(&'a str, &'a str),
+}
+
+fn retained_host<'a>(view: &'a HostView<'_>) -> (HostSemantic<'a>, Result