// accept_signature.mbt — The Accept-Signature field (RFC 9421 §5.1).
//
// Accept-Signature is a Dictionary whose members request future signatures:
// the key is the desired label, the value is an Inner List of desired covered
// components plus signature metadata parameters. Unlike Signature-Input,
// `created` and `expires` appear here as bare request flags (no value).
// This module only parses and serializes the negotiation content; it does not
// implement automatic HTTP client negotiation.
///|
/// The signature-request parameters of one Accept-Signature entry.
pub(all) struct AcceptSignatureRequest {
/// `created` was requested (bare flag, no value).
request_created : Bool
/// `expires` was requested (bare flag, no value).
request_expires : Bool
/// `keyid` — the requested key identifier.
keyid : String?
/// `alg` — the requested algorithm.
alg : String?
/// `nonce` — the requested nonce value.
nonce : String?
/// `tag` — the requested tag value.
tag : String?
/// Unknown/extension parameters, preserved in order.
extensions : Array[SfParameter]
}
///|
/// A single Accept-Signature entry.
pub(all) struct AcceptSignatureEntry {
label : String
covered_components : Array[CoveredComponent]
request : AcceptSignatureRequest
}
///|
/// The parsed Accept-Signature field.
pub(all) struct AcceptSignature {
entries : Array[AcceptSignatureEntry]
}
///|
/// Creates an empty Accept-Signature field.
pub fn AcceptSignature::new() -> AcceptSignature {
{ entries: Array::new() }
}
///|
/// Parses a raw Accept-Signature field value.
pub fn parse_accept_signature(
input : String,
limits : Limits,
) -> Result[AcceptSignature, HsError] {
Ok(parse_accept_signature_raise(input, limits)) catch {
e => Err(e)
}
}
///|
/// Internal: parses Accept-Signature, raising on violation.
fn parse_accept_signature_raise(
input : String,
limits : Limits,
) -> AcceptSignature raise HsError {
limits.check_signature_field_size(input.length(), "Accept-Signature")
let dict = match parse_sf_dictionary_string(input, limits) {
Ok(d) => d
Err(e) => raise e
}
let entries : Array[AcceptSignatureEntry] = Array::new()
let seen : Array[String] = Array::new()
for entry in dict {
for s in seen {
if s == entry.key {
raise hs_error(
StructuredFieldParsing,
DuplicateLabel,
"duplicate label in Accept-Signature: " + entry.key,
)
}
}
seen.push(entry.key)
let il = match entry.value {
InnerListMember(il) => il
ItemMember(_) =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"Accept-Signature member must be an Inner List",
)
}
let components = match
parse_covered_components(il.items, limits.max_components_per_signature) {
Ok(cs) => cs
Err(e) => raise e
}
let request = match parse_accept_request_parameters(il.parameters) {
Ok(r) => r
Err(e) => raise e
}
entries.push({ label: entry.key, covered_components: components, request })
}
{ entries, }
}
///|
/// Parses the request parameters of one Accept-Signature entry. `created` and
/// `expires` are accepted as bare booleans (request flags) or integers.
pub fn parse_accept_request_parameters(
params : Array[SfParameter],
) -> Result[AcceptSignatureRequest, HsError] {
try {
let mut request_created = false
let mut request_expires = false
let mut keyid : String? = None
let mut alg : String? = None
let mut nonce : String? = None
let mut tag : String? = None
let extensions : Array[SfParameter] = Array::new()
for p in params {
match p.name {
"created" =>
match p.value {
SfBoolean(true) => request_created = true
SfBoolean(false) => ()
SfInteger(_) => request_created = true
_ =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"created must be a bare flag",
)
}
"expires" =>
match p.value {
SfBoolean(true) => request_expires = true
SfBoolean(false) => ()
SfInteger(_) => request_expires = true
_ =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"expires must be a bare flag",
)
}
"keyid" =>
match p.value {
SfString(v) => keyid = Some(v)
_ =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"keyid must be a string",
)
}
"alg" =>
match p.value {
SfString(v) => alg = Some(v)
_ =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"alg must be a string",
)
}
"nonce" =>
match p.value {
SfString(v) => nonce = Some(v)
_ =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"nonce must be a string",
)
}
"tag" =>
match p.value {
SfString(v) => tag = Some(v)
_ =>
raise hs_error(
StructuredFieldParsing,
InvalidAcceptSignature,
"tag must be a string",
)
}
_ => extensions.push({ name: p.name, value: p.value })
}
}
Ok({ request_created, request_expires, keyid, alg, nonce, tag, extensions })
} catch {
e => Err(e)
}
}
///|
/// Serializes an Accept-Signature field back to its canonical form.
pub fn serialize_accept_signature(
accept : AcceptSignature,
) -> Result[String, HsError] {
try {
let out = Buffer::Buffer()
for i, entry in accept.entries {
if i > 0 {
out.write_string_utf8(", ")
}
out.write_string_utf8(entry.label)
out.write_char_utf8('=')
let items : Array[SfItem] = Array::new()
for cc in entry.covered_components {
items.push(covered_component_to_sf_item(cc))
}
let parameters : Array[SfParameter] = Array::new()
let r = entry.request
if r.request_created {
parameters.push({ name: "created", value: SfBoolean(true) })
}
if r.request_expires {
parameters.push({ name: "expires", value: SfBoolean(true) })
}
if r.keyid is Some(v) {
parameters.push({ name: "keyid", value: SfString(v) })
}
if r.alg is Some(v) {
parameters.push({ name: "alg", value: SfString(v) })
}
if r.nonce is Some(v) {
parameters.push({ name: "nonce", value: SfString(v) })
}
if r.tag is Some(v) {
parameters.push({ name: "tag", value: SfString(v) })
}
for e in r.extensions {
parameters.push({ name: e.name, value: e.value })
}
let il = { items, parameters }
out.write_string_utf8(serialize_sf_inner_list_raise(il))
}
Ok(buffer_to_string(out))
} catch {
e => Err(e)
}
}