// field_components.mbt — Resolution of HTTP field components (RFC 9421 §2.1).
//
// A field component canonicalizes one or more HTTP field values. The default
// combination rule joins values with `", "` after trimming OWS (RFC 9110
// §5.2). The `sf`, `key`, `bs`, `tr`, and `req` parameters override the
// value handling as described in RFC 9421 §2.1.
///|
/// Resolves a field component to its canonical string value.
pub fn resolve_field_component(
component : FieldComponent,
target : SignTarget,
) -> Result[String, HsError] {
Ok(resolve_field_component_raise(component, target)) catch {
e => Err(e)
}
}
///|
/// Internal: resolves a field component, raising on failure.
fn resolve_field_component_raise(
component : FieldComponent,
target : SignTarget,
) -> String raise HsError {
let p = component.parameters
let headers = effective_headers(target, p.req)
let name = component.name.to_lower()
let values = field_values_for(headers, name, p.tr)
if values.is_empty() {
raise hs_error(
ComponentResolution,
MissingComponent,
"no field named: " + name,
)
}
if p.bs {
resolve_bs_component(values)
} else {
let combined = combine_values(values)
if p.key is Some(k) {
resolve_key_member(combined, k)
} else if p.sf {
resolve_sf_value(combined)
} else {
combined
}
}
}
///|
/// Returns the header set to resolve from: the request, or the response's
/// related request when `req` is set.
fn effective_headers(
target : SignTarget,
use_req : Bool,
) -> OrderedHeaders raise HsError {
match target {
TargetRequest(req) => req.headers
TargetResponse(resp) =>
if use_req {
match resp.related_request {
Some(req) => req.headers
None =>
raise hs_error(
ComponentResolution,
MissingComponent,
"req component requested but no related request present",
)
}
} else {
resp.headers
}
}
}
///|
/// Returns the values of a field (headers or trailers per `tr`) in order.
fn field_values_for(
headers : OrderedHeaders,
name : String,
tr : Bool,
) -> Array[String] {
let out : Array[String] = Array::new()
for f in headers.entries() {
if f.name.equal_ignore_ascii_case(name) && f.trailer == tr {
out.push(f.value)
}
}
out
}
///|
/// Combines multiple field values with `", "` after trimming OWS per value.
fn combine_values(values : Array[String]) -> String {
if values.length() == 1 {
return strip_ows(values[0])
}
let mut out = strip_ows(values[0])
for i = 1; i < values.length(); i = i + 1 {
out = out + ", " + strip_ows(values[i])
}
out
}
///|
/// Resolves the `bs` parameter: each value is base64-decoded, wrapped as an
/// sf-byte-sequence, and the resulting sf-list is serialized strictly.
fn resolve_bs_component(values : Array[String]) -> String raise HsError {
let out = Buffer::Buffer()
out.write_char_utf8('(')
for i, v in values {
if i > 0 {
out.write_char_utf8(' ')
}
let trimmed = strip_ows(v)
let bytes = match base64_decode_bytes(trimmed) {
Ok(b) => b
Err(_) =>
raise hs_error(
ComponentResolution,
InvalidBase64,
"bs value is not valid base64",
)
}
out.write_string_utf8(":" + base64_encode_bytes(bytes) + ":")
}
out.write_char_utf8(')')
buffer_to_string(out)
}
///|
/// Resolves the `sf` parameter: parse the combined value as a structured
/// field and re-serialize it canonically. Items are tried first, then
/// dictionaries; inner lists alone are handled via the inner-list parser.
fn resolve_sf_value(combined : String) -> String raise HsError {
let item = match parse_sf_item_string(combined, Limits::default()) {
Ok(item) => Some(item)
Err(_) => None
}
if item is Some(it) {
return match serialize_sf_item(it) {
Ok(s) => s
Err(e) => raise e
}
}
let inner = match parse_sf_inner_list_string(combined, Limits::default()) {
Ok(il) => Some(il)
Err(_) => None
}
if inner is Some(il) {
return match serialize_sf_inner_list(il) {
Ok(s) => s
Err(e) => raise e
}
}
let dict = match parse_sf_dictionary_string(combined, Limits::default()) {
Ok(d) => d
Err(e) => raise e
}
match serialize_sf_dictionary(dict) {
Ok(s) => s
Err(e) => raise e
}
}
///|
/// Resolves the `key` parameter: parse the combined value as a dictionary
/// and serialize the named member strictly.
fn resolve_key_member(combined : String, key : String) -> String raise HsError {
let dict = match parse_sf_dictionary_string(combined, Limits::default()) {
Ok(d) => d
Err(e) => raise e
}
for entry in dict {
if entry.key == key {
match entry.value {
ItemMember(item) =>
match serialize_sf_item(item) {
Ok(s) => return s
Err(e) => raise e
}
InnerListMember(il) =>
match serialize_sf_inner_list(il) {
Ok(s) => return s
Err(e) => raise e
}
}
}
}
raise hs_error(
ComponentResolution,
MissingComponent,
"dictionary member not found: " + key,
)
}