Skip to content
Merged
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
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,24 @@ on demand without changing them.
These helpers expose credentials metadata only. RTTP does not evaluate CORS
requests, attach credentials to requests, or grant credentials automatically.

### Bounded Access-Control-Allow-Private-Network response metadata

`Response::access_control_allow_private_network()` parses a singleton
`Access-Control-Allow-Private-Network` response field into bounded
`AccessControlAllowPrivateNetwork` metadata. The value is the exact,
case-sensitive `true` token after surrounding SP and HTAB trimming and is
serialized canonically as `true`. Empty, malformed, duplicate, control-byte,
or over-64-KiB values return an error while raw response fields remain
available through `Response::header_value()` and `Response::header_values()`.

On the server, `HttpAccessControlAllowPrivateNetwork::parse()` validates the
same syntax, `HttpResponse::with_access_control_allow_private_network()`
replaces same-name fields only after successful validation, and
`HttpResponse::access_control_allow_private_network()` parses attached fields
without changing them. These helpers expose metadata only; RTTP does not grant
private-network access, decide preflight behavior, or apply CORS or Private
Network Access policy.

### Bounded NEL response metadata

`Response::nel()` parses the `NEL` response field as bounded W3C Network Error
Expand Down Expand Up @@ -1865,8 +1883,9 @@ gain additional HTTP/2 header-block handling.
| X-Forwarded compatibility metadata | Client `x_forwarded_for`, `x_forwarded_host`, and `x_forwarded_proto` emit bounded compatibility request metadata through shared protocol types; server `Request`/`HttpRequest` helpers parse ordered node, authority, and scheme values while preserving raw headers on errors | No forwarded identity trust, client address selection, routing rewrite, scheme rewrite, redirect, upgrade, enforcement, or trusted-proxy selection; applications must choose trusted proxies |
| Via | Client `via` emits bounded HTTP `Via` hop metadata through the shared protocol type; `Response::via` parses received hops; server `Request`/`HttpRequest` helpers and `HttpResponse::with_via`/`via` parse or declare caller-supplied chains while preserving raw headers on errors | No automatic hop insertion or removal, trusted-proxy inference, identity rewrite, or HTTP/1.1 or HTTP/2 proxy-policy changes |
| Accept-Language | Client `accept_language` emits bounded `Accept-Language` request metadata through the protocol `AcceptLanguage` type; server `Request::accept_language()` and `HttpRequest::accept_language()` parse typed received values as `HttpAcceptLanguages` while preserving raw headers on errors | No locale matching, fallback selection, translation lookup, routing, or automatic response choice |
| Preflight request metadata | Client `origin`, `access_control_request_method`, `access_control_request_headers`, and `access_control_request_private_network` emit bounded `Origin`, `Access-Control-Request-Method`, `Access-Control-Request-Headers`, and `Access-Control-Request-Private-Network` request metadata and reject invalid input before connecting | No automatic preflight decision, `Access-Control-Allow-*` response parsing, CORS policy, or Private Network Access policy |
| Preflight request metadata | Client `origin`, `access_control_request_method`, `access_control_request_headers`, and `access_control_request_private_network` emit bounded `Origin`, `Access-Control-Request-Method`, `Access-Control-Request-Headers`, and `Access-Control-Request-Private-Network` request metadata and reject invalid input before connecting | No automatic preflight decision, CORS policy, or Private Network Access policy |
| Access-Control-Allow-Credentials | Client `Response::access_control_allow_credentials` and server `HttpAccessControlAllowCredentials`, `HttpResponse::with_access_control_allow_credentials`, and `HttpResponse::access_control_allow_credentials` parse or declare bounded singleton `Access-Control-Allow-Credentials` `true`-token metadata while preserving raw headers on parse failures | No CORS request evaluation, automatic credential attachment, or automatic credentials granting |
| Access-Control-Allow-Private-Network | Client `Response::access_control_allow_private_network` and server `HttpAccessControlAllowPrivateNetwork`, `HttpResponse::with_access_control_allow_private_network`, and `HttpResponse::access_control_allow_private_network` parse or declare bounded singleton `Access-Control-Allow-Private-Network` `true`-token metadata while preserving raw headers on parse failures | No private-network access grant, preflight decision, CORS policy, or Private Network Access policy |
| Digest preferences | `want_content_digest`, `want_content_digest_with_q`, `want_repr_digest`, and `want_repr_digest_with_q` emit bounded `Want-Content-Digest` and `Want-Repr-Digest` request metadata; server `Request::want_content_digest()`, `HttpRequest::want_content_digest()`, `Request::want_repr_digest()`, and `HttpRequest::want_repr_digest()` parse received preference fields | No algorithm selection, digest computation, response body hash validation, retries, or signing |
| Accept-Encoding | Client `accept_encoding`, `accept_encoding_with_q`, and gzip/deflate/br/identity helpers format bounded `Accept-Encoding` request metadata through the shared `rttp-protocol` type; server `Request::accept_encoding()` and `HttpRequest::accept_encoding()` parse received fields into `HttpRequestAcceptEncodings` | No compression, decompression, content negotiation, retries, or transport changes |
| Upgrade and tunnel handoff | `CONNECT` returns the tunnel socket after a successful `200`; `upgrade()` returns the socket after `101 Switching Protocols` and skips interim `1xx` responses | Upgraded protocols are handed to the caller and are not parsed by `rttp_client` |
Expand Down
21 changes: 18 additions & 3 deletions crates/rttp-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1329,8 +1329,22 @@ retain raw-header control with `header(("Origin", "..."))` and the other
`header` forms.

These are declaration helpers only. RTTP does not decide whether a preflight
is needed, read `Access-Control-Allow-*` response fields, apply CORS policy, or
apply Private Network Access policy.
is needed, apply CORS policy, or apply Private Network Access policy.

## Bounded Access-Control-Allow-Private-Network response metadata

`Response::access_control_allow_private_network()` parses a singleton
`Access-Control-Allow-Private-Network` response field through the shared
`AccessControlAllowPrivateNetwork` protocol type. The value is the exact,
case-sensitive `true` token with surrounding SP or HTAB trimmed and canonical
lowercase `true` serialization. Values and duplicate fields are bounded and
validated to reject malformed, control-byte, and oversized input. Absent
metadata returns `Ok(None)`; parse errors leave the original raw field
available through `Response::header_value()` and `Response::header_values()`.

This accessor exposes response metadata only. It does not grant private-network
access, decide whether a preflight is needed, apply CORS policy, or apply
Private Network Access policy.

## Bounded Save-Data request metadata

Expand Down Expand Up @@ -1802,7 +1816,8 @@ header-block model.
| W3C Baggage | `baggage` validates and emits bounded W3C Baggage request metadata through the shared protocol type, replacing an existing same-name field and redacting member and property values from typed debug output | No application-data interpretation, request-context storage, tracing backend, span model, or automatic propagation |
| CDN-Loop | `cdn_loop` validates and emits bounded RFC 8586 `CDN-Loop` request metadata through the shared protocol type, combining an existing same-name field with the new member in wire order and rejecting malformed or oversized values before connecting | No CDN identifier insertion, loop detection or rejection, automatic forwarding, or hop-by-hop handling |
| Via | `via` validates and emits bounded HTTP `Via` request metadata through the shared protocol type, combining an existing same-name field with the new hops in wire order and rejecting malformed or oversized values before connecting; `Response::via` parses received hop chains while preserving raw headers on parse failures | No automatic hop insertion or removal, trusted-proxy inference, identity rewrite, or HTTP/1.1 or HTTP/2 proxy-policy changes |
| Preflight request metadata | `origin`, `access_control_request_method`, `access_control_request_headers`, and `access_control_request_private_network` emit bounded `Origin`, `Access-Control-Request-Method`, `Access-Control-Request-Headers`, and `Access-Control-Request-Private-Network` request metadata and reject invalid input before connecting | No automatic preflight decision, `Access-Control-Allow-*` response parsing, CORS policy, or Private Network Access policy |
| Preflight request metadata | `origin`, `access_control_request_method`, `access_control_request_headers`, and `access_control_request_private_network` emit bounded `Origin`, `Access-Control-Request-Method`, `Access-Control-Request-Headers`, and `Access-Control-Request-Private-Network` request metadata and reject invalid input before connecting | No automatic preflight decision, CORS policy, or Private Network Access policy |
| Access-Control-Allow-Private-Network | `Response::access_control_allow_private_network` parses bounded singleton `Access-Control-Allow-Private-Network` `true` metadata while preserving raw headers on parse failures | No private-network access grant, preflight decision, CORS policy, or Private Network Access policy |
| Digest preferences | `want_content_digest`, `want_content_digest_with_q`, `want_repr_digest`, and `want_repr_digest_with_q` emit bounded `Want-Content-Digest` and `Want-Repr-Digest` request metadata; server `Request::want_content_digest()`, `HttpRequest::want_content_digest()`, `Request::want_repr_digest()`, and `HttpRequest::want_repr_digest()` parse received preference fields | No algorithm selection, digest computation, response body hash validation, retries, or signing |
| Accept | `accept` and `accept_with_q` format bounded `Accept` request metadata through the shared `rttp-protocol` type, replacing existing same-name fields after validating helper-built and existing raw values | No content negotiation, representation selection, MIME sniffing, body decoding, cache `Vary` synthesis, or response choice |
| Accept-Charset | `accept_charset` and `accept_charset_with_q` format bounded `Accept-Charset` request metadata through the shared `rttp-protocol` type | No content negotiation, charset transcoding, body decoding, MIME sniffing, or response selection |
Expand Down
3 changes: 3 additions & 0 deletions crates/rttp-client/src/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub use rttp_protocol::access_control_allow_methods::{
pub use rttp_protocol::access_control_allow_origin::{
AccessControlAllowOrigin, AccessControlAllowOriginParseError,
};
pub use rttp_protocol::access_control_allow_private_network::{
AccessControlAllowPrivateNetwork, AccessControlAllowPrivateNetworkParseError,
};
pub use rttp_protocol::access_control_expose_headers::{
AccessControlExposeHeaders, AccessControlExposeHeadersParseError,
};
Expand Down
15 changes: 15 additions & 0 deletions crates/rttp-client/src/response/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use rttp_protocol::access_control_allow_credentials::AccessControlAllowCredentia
use rttp_protocol::access_control_allow_headers::AccessControlAllowHeaders;
use rttp_protocol::access_control_allow_methods::AccessControlAllowMethods;
use rttp_protocol::access_control_allow_origin::AccessControlAllowOrigin;
use rttp_protocol::access_control_allow_private_network::AccessControlAllowPrivateNetwork;
use rttp_protocol::access_control_expose_headers::AccessControlExposeHeaders;
use rttp_protocol::access_control_max_age::AccessControlMaxAge;
use rttp_protocol::age::Age;
Expand Down Expand Up @@ -687,6 +688,20 @@ impl Response {
.map_err(|parse_error| error::bad_response(parse_error.to_string()))
}

/// Parses bounded `Access-Control-Allow-Private-Network` response metadata
/// without applying Private Network Access or CORS policy.
pub fn access_control_allow_private_network(
&self,
) -> error::Result<Option<AccessControlAllowPrivateNetwork>> {
let values = self.header_values("access-control-allow-private-network");
if values.is_empty() {
return Ok(None);
}
AccessControlAllowPrivateNetwork::parse_values(values.into_iter().map(String::as_str))
.map(Some)
.map_err(|parse_error| error::bad_response(parse_error.to_string()))
}

/// Parses bounded `Access-Control-Allow-Methods` response metadata without
/// applying CORS method policy.
pub fn access_control_allow_methods(&self) -> error::Result<Option<AccessControlAllowMethods>> {
Expand Down
9 changes: 8 additions & 1 deletion crates/rttp-client/tests/metadata_facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use rttp_client::response::{
AcceptCh, AcceptCharset, AcceptEncoding, AccessControlAllowCredentials,
AccessControlAllowCredentialsParseError, AccessControlAllowHeaders,
AccessControlAllowHeadersParseError, AccessControlAllowMethods,
AccessControlAllowMethodsParseError, AccessControlExposeHeaders, AccessControlMaxAge,
AccessControlAllowMethodsParseError, AccessControlAllowPrivateNetwork,
AccessControlAllowPrivateNetworkParseError, AccessControlExposeHeaders, AccessControlMaxAge,
AccessControlMaxAgeParseError, Age, AgeParseError, AltSvc, AltUsed, AltUsedParseError,
AuthenticationInfo, AuthenticationInfoParameter, AuthenticationInfoParseError, CacheStatus,
CacheStatusParseError, Connection, ConnectionParseError, ContentDpr, ContentDprParseError,
Expand Down Expand Up @@ -82,6 +83,12 @@ fn response_facade_exports_representative_bounded_metadata_types() {
.expect("Access-Control-Allow-Credentials should parse");
let _: AccessControlAllowCredentialsParseError = AccessControlAllowCredentials::parse("false")
.expect_err("false Access-Control-Allow-Credentials should be rejected");
let allow_private_network = AccessControlAllowPrivateNetwork::parse("true")
.expect("Access-Control-Allow-Private-Network should parse");
assert_eq!("true", allow_private_network.header_value());
let _: AccessControlAllowPrivateNetworkParseError =
AccessControlAllowPrivateNetwork::parse("false")
.expect_err("false Access-Control-Allow-Private-Network should be rejected");
let allow_methods = AccessControlAllowMethods::parse("GET, POST")
.expect("Access-Control-Allow-Methods should parse");
let _: AccessControlAllowMethodsParseError = AccessControlAllowMethods::parse("")
Expand Down
74 changes: 74 additions & 0 deletions crates/rttp-client/tests/test_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7743,6 +7743,80 @@ fn test_access_control_allow_credentials_response_helper_parses_valid_metadata_a
);
}

#[test]
fn access_control_allow_private_network_response_helper_preserves_raw_parse_failures() {
let valid = Response::new(
RoUrl::with("https://example.test"),
b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Private-Network: \ttrue \r\nContent-Length: 0\r\n\r\n"
.to_vec(),
)
.expect("valid response should remain usable");
assert_eq!(
"true",
valid
.access_control_allow_private_network()
.expect("valid metadata should parse")
.expect("valid metadata should be present")
.header_value()
);
assert_eq!(
Some("true"),
valid
.header_value("Access-Control-Allow-Private-Network")
.map(String::as_str)
);

let absent = Response::new(
RoUrl::with("https://example.test"),
b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".to_vec(),
)
.expect("response without metadata should remain usable");
assert_eq!(
None,
absent
.access_control_allow_private_network()
.expect("absence should parse")
);

for value in [
"TRUE".to_string(),
"True".to_string(),
"false".to_string(),
"true, true".to_string(),
"true\0".to_string(),
"x".repeat(64 * 1024 + 1),
] {
let raw = format!(
"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Private-Network: {value}\r\nContent-Length: 0\r\n\r\n"
);
let response = Response::new(RoUrl::with("https://example.test"), raw.into_bytes())
.expect("malformed response metadata should remain usable");
assert!(response.access_control_allow_private_network().is_err());
assert_eq!(
response.header_value("Access-Control-Allow-Private-Network"),
Some(&value)
);
}

let duplicate = Response::new(
RoUrl::with("https://example.test"),
concat!(
"HTTP/1.1 200 OK\r\n",
"Access-Control-Allow-Private-Network: true\r\n",
"access-control-allow-private-network: true\r\n",
"Content-Length: 0\r\n\r\n"
)
.as_bytes()
.to_vec(),
)
.expect("duplicate response metadata should remain usable");
assert!(duplicate.access_control_allow_private_network().is_err());
assert_eq!(
duplicate.header_values("Access-Control-Allow-Private-Network"),
[&"true".to_string(), &"true".to_string()]
);
}

#[test]
fn test_access_control_allow_headers_response_helper_parses_valid_lists_wildcard_and_multiple_fields(
) {
Expand Down
12 changes: 12 additions & 0 deletions crates/rttp-protocol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,18 @@ whitespace. Unknown tokens, lists, quoted values, empty values, control
bytes, and other unparsable input are errors.
This parser does not evaluate CORS requests or grant credentials automatically.

## Access-Control-Allow-Private-Network

`access_control_allow_private_network` parses a singleton
`Access-Control-Allow-Private-Network` response field. Each field value is
bounded to 64 KiB, and a second field is rejected after every supplied field
is bound-checked. The value must be exactly the case-sensitive Fetch
`true` token and is serialized canonically as lowercase `true`; surrounding
SP and HTAB are trimmed. Unknown tokens, lists, empty values, control bytes,
oversized values, and other malformed input are errors. This parser reports
response metadata only; it does not grant private-network access or apply
CORS or Private Network Access policy.

## Access-Control-Request-Method

`access_control_request_method` parses a singleton
Expand Down
Loading