Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,28 @@ These helpers only declare or parse metadata. RTTP does not translate `Pragma`
into `Cache-Control`, store cache entries, or apply cache, freshness,
revalidation, intermediary, or HTTP/1.0 compatibility policy.

### Bounded Sec-Required-Document-Policy request metadata

`rttp-protocol` owns the shared `SecRequiredDocumentPolicy` primitive. Client
helpers format through that type, and server `Request` / `HttpRequest` helpers
parse with the same Document Policy Structured Fields dictionary rules.

`HttpClient::sec_required_document_policy(value)` emits bounded
`Sec-Required-Document-Policy` request metadata, combining and replacing
already-attached same-name fields with one canonical value. On the server,
`Request::sec_required_document_policy()` and
`HttpRequest::sec_required_document_policy()` parse received fields into
`HttpSecRequiredDocumentPolicy`. Absent fields return `Ok(None)`. Multiple
fields are combined in wire order, duplicate directive names are rejected,
each field value is bounded to 64 KiB, combined raw bytes are bounded to
64 KiB, and the combined directive count is bounded to 256. Malformed members,
control bytes, unknown parameters, empty dictionaries, and bound violations
return a parser error while raw headers remain available.

These helpers only declare or parse request metadata. RTTP does not enforce
document policy, compare required policies with `Document-Policy`, block
document loads, disable browser features, or echo response fields.

### Bounded Upgrade-Insecure-Requests request metadata

`HttpClient::upgrade_insecure_requests()` emits `Upgrade-Insecure-Requests: 1`.
Expand Down Expand Up @@ -1908,6 +1930,7 @@ gain additional HTTP/2 header-block handling.
| Permissions-Policy | `Response::permissions_policy` parses bounded W3C Permissions Policy dictionary metadata through the shared protocol type, combining fields in wire order and preserving raw headers on parse failures | No browser permission grants or denials, origin comparison, `self` resolution, API enablement, origin-policy enforcement, or report sending |
| Document-Policy | `Response::document_policy` parses bounded WICG Document Policy dictionary metadata through the shared protocol type, combining fields in wire order, retaining `*` and `report-to`, and preserving raw headers on parse failures | No configuration-point execution, document-load blocking, required-policy comparison, `Sec-Required-Document-Policy` echoing, feature enablement, or report sending |
| Document-Policy-Report-Only | `Response::document_policy_report_only` parses bounded WICG Document Policy Report-Only dictionary metadata through the same shared protocol parser and formatter, retaining report-only type identity, `*`, and `report-to`, and preserving raw headers on parse failures | No policy enforcement, document-load blocking, required-policy comparison, `Sec-Required-Document-Policy` echoing, feature enablement, report delivery, scheduling, retry, or endpoint validation |
| Sec-Required-Document-Policy | Client `sec_required_document_policy` and server `Request::sec_required_document_policy` / `HttpRequest::sec_required_document_policy` share the bounded protocol `SecRequiredDocumentPolicy` representation, combining fields in wire order and preserving raw headers on errors | No document-policy enforcement, required-policy comparison with `Document-Policy`, document-load blocking, feature enablement, or response echoing |
| Supports-Loading-Mode | `Response::supports_loading_mode` parses bounded Structured Fields token-list response metadata through the shared protocol type, combining fields in wire order, retaining unknown tokens, and preserving raw headers on parse failures | No prerendering, fenced-frame admission, navigation changes, redirects, retries, or resource-loading behavior |
| Trailers | Chunked response trailers are exposed for blocking and async APIs; streaming chunked uploads can send declared request trailers | Application metadata trailers such as `X-Trace` are allowed; pseudo-header, connection-specific, routing, authentication/cookie, and framing trailer fields are rejected |
| Bounded h2c client | With `http2`, direct `socket2` h2c sends GET, HEAD, bodyless DELETE, OPTIONS, or TRACE, buffered POST, PUT, or PATCH requests, and opt-in RFC 8441 extended CONNECT request HEADERS via `http2_extended_connect`, opens at most one request stream, supports prior-knowledge with `emit_http2_prior_knowledge`, supports explicit HTTP/1.1 `Upgrade: h2c` negotiation with `emit_http2_upgrade`, advertises `SETTINGS_ENABLE_PUSH = 0`, advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL = 1` only for the explicit extended CONNECT path, validates received `SETTINGS_ENABLE_PUSH` values as only `0` or `1`, honors initial peer `SETTINGS_MAX_CONCURRENT_STREAMS` by failing before request HEADERS when the peer allows zero streams, honors peer-advertised `SETTINGS_MAX_HEADER_LIST_SIZE` request metadata limits, accepts only legal `SETTINGS_MAX_FRAME_SIZE` values from 16,384 through 16,777,215 bytes, splits outbound HEADERS, DATA, and trailers to the active peer frame-size limit, rejects oversized inbound frames when a configured local frame-size limit is exceeded, bounds HPACK dynamic table use with `SETTINGS_HEADER_TABLE_SIZE`, strips HTTP/1.x connection-specific request fields before emission, rejects connection-specific peer response fields, suppresses HEAD response bodies, treats `RST_STREAM` on the active stream as a bounded reset/cancellation signal, acknowledges inbound PING without ACK on stream 0 and exactly 8 octets with matching opaque data, ignores inbound PING ACK, rejects malformed PING frames, DATA bodies, trailers, HPACK static Huffman strings, bounded large header blocks, padded incoming frames, `GOAWAY` shutdown boundaries, PRIORITY metadata validation without scheduling, HTTP/2-allowed unknown/extension frame ignoring inside this bounded path, reserved stream-id high-bit normalization, and conservative DATA flow control | Ordinary `CONNECT`, header-configured `:protocol` metadata, non-h2c HTTP/1.1 `Upgrade` handoff requests, and proxies are rejected deterministically, and `PUSH_PROMISE`/server push is rejected instead of managed; bounded direct h2c only, with no keepalive timers, no automatic client/server initiated PING policy, no public cancellation callback API, no dynamic policy API, no extension callback API, no full extension negotiation, TLS ALPN, external h2 integration, proxy tunneling to h2, proxy h2, tunnel handoff, connection pooling, persistent HTTP/2 session management, automatic retry/replay, server push, full session manager, full stream state machine, full multiplex scheduler, unbounded multiplex scheduling, general multiplexing, priority scheduling, request bodies or trailers for extended CONNECT, or request bodies for GET, HEAD, DELETE, OPTIONS, or TRACE |
Expand Down Expand Up @@ -2563,6 +2586,30 @@ These helpers declare and parse metadata only. They do not translate `Pragma`
into `Cache-Control`, store cache entries, or apply cache, freshness,
revalidation, intermediary, or HTTP/1.0 compatibility policy.

### Bounded Sec-Required-Document-Policy request metadata

Server-side `Sec-Required-Document-Policy` helpers expose request metadata
through the shared `rttp-protocol` primitive.
`Request::sec_required_document_policy()` and
`HttpRequest::sec_required_document_policy()` parse received fields in wire
order into `HttpSecRequiredDocumentPolicy`. Absent fields return `Ok(None)`.
HTTP/1.1 and HTTP/2 share the same `Request` helpers. The shared protocol type
is the authority for directive, parameter, duplicate, member-count, and size
validation.

Parsing is bounded and validation-oriented. Each field value is limited to
64 KiB, combined raw bytes are limited to 64 KiB, and the combined directive
count is limited to 256. Empty dictionaries, malformed members, unknown
parameters, duplicate names, control bytes, and bound violations return
`HttpSecRequiredDocumentPolicyParseError` from the helper. Raw
`Request::header("Sec-Required-Document-Policy")` values remain preserved
exactly as ordinary headers; helper parse errors do not remove existing
headers.

These helpers parse request metadata only. They do not enforce document
policy, compare required policies with `Document-Policy`, block document
loads, disable browser features, or echo response fields.

### Bounded Accept-Encoding request metadata

Server-side `Accept-Encoding` helpers expose request metadata through the
Expand Down Expand Up @@ -3165,6 +3212,7 @@ TLS or async accept loops.
| Permissions-Policy | `HttpPermissionsPolicy`, `HttpResponse::with_permissions_policy`, and `HttpResponse::permissions_policy` parse and declare bounded W3C Permissions Policy dictionary response metadata through the shared protocol type, replacing raw duplicates on declaration and preserving raw headers on parse failures | No browser permission grants or denials, origin comparison, `self` resolution, API enablement, origin-policy enforcement, or report sending |
| Document-Policy | `HttpDocumentPolicy`, `HttpResponse::with_document_policy`, and `HttpResponse::document_policy` parse and declare bounded WICG Document Policy dictionary response metadata through the shared protocol type, replacing raw duplicates on declaration, retaining `*` and `report-to`, and preserving raw headers on parse failures | No configuration-point execution, document-load blocking, required-policy comparison, `Sec-Required-Document-Policy` echoing, feature enablement, or report sending |
| Document-Policy-Report-Only | `HttpDocumentPolicyReportOnly`, `HttpResponse::with_document_policy_report_only`, and `HttpResponse::document_policy_report_only` parse and declare bounded WICG Document Policy Report-Only dictionary metadata through the same shared protocol parser and formatter, replacing raw duplicates on declaration, retaining report-only type identity, `*`, and `report-to`, and preserving raw headers on parse failures | No policy enforcement, document-load blocking, required-policy comparison, `Sec-Required-Document-Policy` echoing, feature enablement, report delivery, scheduling, retry, or endpoint validation |
| Sec-Required-Document-Policy | `HttpSecRequiredDocumentPolicy`, `Request::sec_required_document_policy`, and `HttpRequest::sec_required_document_policy` parse bounded WICG required Document Policy dictionary request metadata through the shared protocol type, combining fields in wire order and preserving raw headers on parse failures | No document-policy enforcement, required-policy comparison with `Document-Policy`, document-load blocking, feature enablement, or response echoing |
| Supports-Loading-Mode | `HttpSupportsLoadingMode`, `HttpResponse::with_supports_loading_mode`, and `HttpResponse::supports_loading_mode` parse and declare bounded Structured Fields token-list response metadata through the shared protocol type, replacing raw duplicates on declaration, retaining unknown tokens, and preserving raw headers on parse failures | No prerendering, fenced-frame admission, navigation changes, redirects, retries, or resource-loading behavior |
| Allow | `HttpAllowedMethods`, `HttpResponse::with_allow`, and `HttpResponse::allow` declare and parse bounded `Allow` method-list metadata | No route dispatch, automatic `405` generation, `OPTIONS` policy, fallback method selection, retry/replay, or status-code policy engine |
| Content-Security-Policy-Report-Only | `HttpContentSecurityPolicyReportOnly`, `HttpResponse::with_content_security_policy_report_only`, `content_security_policy_report_only`, and client `Response::content_security_policy_report_only` parse or declare bounded opaque `Content-Security-Policy-Report-Only` response metadata while preserving repeated fields in wire order and raw headers on parse failures | No CSP enforcement, directive evaluation, report delivery, browser policy state, retry, redirect, cache behavior, or status-policy behavior |
Expand Down
17 changes: 17 additions & 0 deletions crates/rttp-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,23 @@ into `Cache-Control`, store cache entries, or apply cache, intermediary, or
HTTP/1.0 compatibility policy. Callers that need unusual values can retain
raw-header control with `header(("Pragma", "..."))`.

## Bounded Sec-Required-Document-Policy request metadata

`HttpClient::sec_required_document_policy(value)` validates WICG Document
Policy Structured Fields dictionary metadata through the shared protocol
`SecRequiredDocumentPolicy` type and emits one normalized
`Sec-Required-Document-Policy` field. Already-attached
`Sec-Required-Document-Policy` fields are combined in wire order and replaced
by that single canonical field, so duplicate directive names, unknown
parameters, control bytes, empty dictionaries, and per-field or combined-size
bound violations fail before a socket opens.

This helper only declares request metadata. RTTP does not enforce document
policy, compare required policies with `Document-Policy`, block document
loads, disable browser features, or echo response fields. Callers that need
unusual values can retain raw-header control with
`header(("Sec-Required-Document-Policy", "..."))`.

## Bounded HTTP/1.1 Content-Disposition behavior

`Response::content_disposition()` parses a singleton response
Expand Down
36 changes: 36 additions & 0 deletions crates/rttp-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use rttp_protocol::range::{Range, MAX_RANGE_COUNT};
use rttp_protocol::referer::Referer;
use rttp_protocol::save_data::SaveData;
use rttp_protocol::sec_gpc::SecGpc;
use rttp_protocol::sec_required_document_policy::SecRequiredDocumentPolicy;
use rttp_protocol::sec_websocket_extensions::SecWebSocketExtensions;
use rttp_protocol::sec_websocket_key::SecWebSocketKey;
use rttp_protocol::sec_websocket_protocol::SecWebSocketProtocol;
Expand Down Expand Up @@ -577,6 +578,41 @@ impl HttpClient {
self.pragma("no-cache")
}

/// Set bounded `Sec-Required-Document-Policy` request metadata.
///
/// The value is validated through the shared protocol
/// `SecRequiredDocumentPolicy` type using the same Document Policy
/// Structured Fields dictionary model and bounds as `Document-Policy`. Any
/// already-attached `Sec-Required-Document-Policy` fields are combined in
/// wire order and replaced by one canonical field. Malformed members,
/// control bytes, duplicate directive names, and per-field or combined-size
/// bound violations fail before a socket opens. This declares request
/// metadata only; it does not enforce document policy, compare required
/// policies with `Document-Policy`, or echo response fields. Use `header`
/// directly for unusual values.
pub fn sec_required_document_policy<S: AsRef<str>>(
&mut self,
value: S,
) -> error::Result<&mut Self> {
let mut values: Vec<String> = self
.request
.headers()
.iter()
.filter(|header| header.name().eq_ignore_ascii_case("Sec-Required-Document-Policy"))
.map(|header| header.value().clone())
.collect();
values.push(value.as_ref().to_string());
let policy = SecRequiredDocumentPolicy::parse_values(values.iter().map(String::as_str))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include canonical separators in the total-size bound

When this helper combines two valid values whose raw lengths total exactly 64 KiB, parse_values accepts them because the shared parser counts only the input bytes, but header_value() inserts ", " between fields and the resulting single emitted header exceeds the advertised 64 KiB per-field bound. The client can therefore emit a value that SecRequiredDocumentPolicy::parse and the server accessor reject when received as one field; account for separator overhead or validate the canonical value before replacing the headers.

Useful? React with 👍 / 👎.

.map_err(|error| error::builder_with_message(error.to_string()))?;
let headers = self.request.headers_mut();
headers.retain(|header| !header.name().eq_ignore_ascii_case("Sec-Required-Document-Policy"));
headers.push(Header::new(
"Sec-Required-Document-Policy",
policy.header_value(),
));
Ok(self)
}

/// Append a validated `Accept` media range with its supplied quality value.
///
/// This declares request metadata only; it does not select a response
Expand Down
4 changes: 4 additions & 0 deletions crates/rttp-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ pub use rttp_protocol::overwrite::{Overwrite, OverwriteParseError};
pub use rttp_protocol::referer::{Referer, RefererParseError};
pub use rttp_protocol::schedule_tag::{ScheduleTag, ScheduleTagParseError};
pub use rttp_protocol::sec_gpc::{SecGpc, SecGpcParseError};
pub use rttp_protocol::sec_required_document_policy::{
SecRequiredDocumentPolicy, SecRequiredDocumentPolicyDirective,
SecRequiredDocumentPolicyParseError, SecRequiredDocumentPolicyValue,
};
pub use rttp_protocol::sec_websocket_extensions::{
SecWebSocketExtension, SecWebSocketExtensionParameter, SecWebSocketExtensionParameterValue,
SecWebSocketExtensions, SecWebSocketExtensionsParseError,
Expand Down
32 changes: 26 additions & 6 deletions crates/rttp-client/tests/metadata_facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ use rttp_client::{
HttpClient, If, IfCondition, IfList, IfParseError, IfPredicate, IfResourceTag,
IfScheduleTagMatch, IfScheduleTagMatchParseError, IfStateToken, Negotiate, NegotiateDirective,
NegotiateParseError, Overwrite, OverwriteParseError, SecFetchDest, SecFetchMode, SecFetchSite,
SecFetchUser, SecGpc, SecGpcParseError, SecPurpose, SecWebSocketKey, SecWebSocketKeyParseError,
Tcn, TcnDirective, TcnParseError, Timeout, TimeoutParseError, TimeoutType, TraceParent,
TraceParentParseError, TraceState, TraceStateMember, TraceStateParseError,
UpgradeInsecureRequests, UpgradeInsecureRequestsParseError, UserAgent, UserAgentMember,
UserAgentParseError, Via as ClientVia, ViaParseError as ClientViaParseError, XForwardedFor,
XForwardedForParseError, XForwardedHost, XForwardedHostParseError, XForwardedProto,
SecFetchUser, SecGpc, SecGpcParseError, SecPurpose, SecRequiredDocumentPolicy,
SecRequiredDocumentPolicyParseError, SecRequiredDocumentPolicyValue, SecWebSocketKey,
SecWebSocketKeyParseError, Tcn, TcnDirective, TcnParseError, Timeout, TimeoutParseError,
TimeoutType, TraceParent, TraceParentParseError, TraceState, TraceStateMember,
TraceStateParseError, UpgradeInsecureRequests, UpgradeInsecureRequestsParseError, UserAgent,
UserAgentMember, UserAgentParseError, Via as ClientVia, ViaParseError as ClientViaParseError,
XForwardedFor, XForwardedForParseError, XForwardedHost, XForwardedHostParseError, XForwardedProto,
XForwardedProtoParseError,
};
use rttp_test_support as support;
Expand Down Expand Up @@ -331,6 +332,13 @@ fn response_facade_exports_representative_bounded_metadata_types() {
let _: DocumentPolicyReportOnlyParseError =
DocumentPolicyReportOnly::parse("unsized-media=src;foo=bar")
.expect_err("unknown Document-Policy-Report-Only parameter should be rejected");
let sec_required_document_policy = SecRequiredDocumentPolicy::parse(
"oversized-images=2.0, unsized-media=?0, *;report-to=default",
)
.expect("Sec-Required-Document-Policy should parse");
let _: SecRequiredDocumentPolicyParseError =
SecRequiredDocumentPolicy::parse("unsized-media=src;foo=bar")
.expect_err("unknown Sec-Required-Document-Policy parameter should be rejected");
let supports_loading_mode = SupportsLoadingMode::parse("fenced-frame, credentialed-prerender")
.expect("Supports-Loading-Mode should parse");
let _: SupportsLoadingModeParseError =
Expand Down Expand Up @@ -648,6 +656,18 @@ fn response_facade_exports_representative_bounded_metadata_types() {
document_policy_report_only.header_value(),
"oversized-images=2.0, unsized-media=?0, *;report-to=default"
);
assert_eq!(sec_required_document_policy.directives().len(), 3);
assert_eq!(
sec_required_document_policy
.directive("oversized-images")
.unwrap()
.value(),
&SecRequiredDocumentPolicyValue::Decimal("2.0".to_string())
);
assert_eq!(
sec_required_document_policy.header_value(),
"oversized-images=2.0, unsized-media=?0, *;report-to=default"
);
assert_eq!(
supports_loading_mode.tokens(),
["fenced-frame", "credentialed-prerender"]
Expand Down
Loading
Loading