Service + region (and optional bucket) decoded from a Host header.
Covers both the LocalStack hostname convention
(<service>.<region>.localhost.localstack.cloud[:port],
<bucket>.s3.<region>.localhost.localstack.cloud[:port]) and real AWS
service hostnames (<service>.<region>.amazonaws.com, S3 path-style
and virtual-hosted-style including the legacy no-region
s3.amazonaws.com / <bucket>.s3.amazonaws.com forms, the older
dash-separated s3-<region>.amazonaws.com form, the dualstack, FIPS and
static-website endpoints), under every AWS partition’s DNS suffix.
Detect a Smithy RPC v2 CBOR request: the smithy-protocol: rpc-v2-cbor
header plus a /service/{ServiceName}/operation/{Operation} path. The
service shape name is the same identifier awsJson services use as their
X-Amz-Target prefix, so it resolves through the same table.
Header-only service detection. Skips the form-encoded body sniff so
the dispatch path can decide whether to stream or buffer the body
without first reading it. Returns None when only a body sniff
would succeed; the caller must then fall back to detect_service
after buffering. Used to opt streaming routes (S3 PutObject /
UploadPart, ECR OCI v2 blob upload) out of the global body cap.
Whether the request is addressed to an S3 Control endpoint
([<account-id>.]s3-control.<region>...). S3 Control is served by the
s3 handler, so the host is the only thing telling the two apart. The
s3-control label must lead the host (after the SDK’s account-ID
prefix), so a bucket whose name merely contains s3-control stays S3.
Normalize a Host header value for comparing against configured domain
names: drop the port (api.example.com:4566), any trailing root dot, and
lowercase it (DNS names are case-insensitive). A bracketed IPv6 literal
keeps its brackets.
Parse a Host header value for a LocalStack- or AWS-shaped hostname.
Returns None for anything that doesn’t match — callers fall through
to their existing detection path.
Decode one application/x-www-form-urlencoded component (a query-string
key or value, or a form-body field): + is a space and %XX escapes are
decoded on the raw bytes, then read as UTF-8 (lossily). For URI path
segments, where + is literal, use crate::path::percent_decode_segment.