// sf_parser.mbt — Structured field parser (RFC 9651 subset).
//
// Implements the grammar subset required by RFC 9421 signature fields:
// dictionary, inner list, item, parameters, string, token, integer,
// byte sequence, and boolean. Decimals are intentionally not supported
// (RFC 9421 signature fields never carry them); encountering one raises
// `InvalidStructuredField`.
//
// All parse functions take an `SfCursor` and advance it past the parsed
// construct. Callers are responsible for checking trailing garbage.
///|
/// Parses a `key` per RFC 9651 §4.2.3.2.
///
/// `key = (lcalpha / "*") *( lcalpha / DIGIT / "_" / "-" / "." / "*")`
fn parse_sf_key(c : SfCursor) -> String raise HsError {
let start = c.pos()
let first = c.peek()
let first_ok = (first >= 'a' && first <= 'z') || first == '*'
if !first_ok {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"invalid key start",
)
}
c.advance()
while !c.at_end() {
let ch = c.peek()
let ok = (ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
ch == '_' ||
ch == '-' ||
ch == '.' ||
ch == '*'
if !ok {
break
}
c.advance()
}
c.slice_from(start)
}
///|
/// Parses a quoted string `"..."` with `\"` and `\\` escapes.
fn parse_sf_string(c : SfCursor) -> String raise HsError {
let start = c.pos()
if c.take() != '"' {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
start,
"expected opening DQUOTE for string",
)
}
let out = Buffer::Buffer()
while true {
if c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
UnexpectedEnd,
c.pos(),
"unterminated string",
)
}
let ch = c.take()
if ch == '"' {
break
}
if ch == '\\' {
if c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
UnexpectedEnd,
c.pos(),
"unterminated string escape",
)
}
let esc = c.take()
if esc == '"' {
out.write_char_utf8('"')
} else if esc == '\\' {
out.write_char_utf8('\\')
} else {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos() - 2,
"invalid string escape",
)
}
} else {
out.write_char_utf8(ch)
}
}
buffer_to_string(out)
}
///|
/// Parses a token: `tchar *( tchar / ":" / "/" )`.
fn parse_sf_token(c : SfCursor) -> String raise HsError {
let start = c.pos()
let first = c.peek()
if !is_tchar(first.to_string()) {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
start,
"invalid token start",
)
}
c.advance()
while !c.at_end() {
let ch = c.peek()
if ch == ':' || ch == '/' {
c.advance()
continue
}
if is_tchar(ch.to_string()) {
c.advance()
continue
}
break
}
c.slice_from(start)
}
///|
/// Parses an integer: `["-"] 1*15DIGIT` fitting in `Int64`.
fn parse_sf_integer(c : SfCursor) -> Int64 raise HsError {
let mut negative = false
if !c.at_end() && c.peek() == '-' {
negative = true
c.advance()
}
if c.at_end() || !(c.peek() >= '0' && c.peek() <= '9') {
raise hs_error_at(
StructuredFieldParsing,
InvalidInteger,
c.pos(),
"expected digits for integer",
)
}
let mut value : Int64 = 0
let mut digits = 0
while !c.at_end() {
let ch = c.peek()
if ch < '0' || ch > '9' {
break
}
digits = digits + 1
if digits > 15 {
raise hs_error_at(
StructuredFieldParsing,
InvalidInteger,
c.pos(),
"integer too long",
)
}
value = value * 10L + Int64::from_int(ch.to_int() - '0'.to_int())
c.advance()
}
if negative {
value = -value
}
// A digit followed immediately by '.' is a decimal, which is unsupported.
if !c.at_end() && c.peek() == '.' {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"sf-decimal is not supported",
)
}
value
}
///|
/// Parses a boolean: `?0` / `?1`.
fn parse_sf_boolean(c : SfCursor) -> Bool raise HsError {
let start = c.pos()
if c.take() != '?' {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
start,
"expected '?' for boolean",
)
}
if c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
UnexpectedEnd,
c.pos(),
"unterminated boolean",
)
}
let ch = c.take()
if ch == '0' {
false
} else if ch == '1' {
true
} else {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos() - 1,
"boolean must be ?0 or ?1",
)
}
}
///|
/// Parses a byte sequence: `":" *(base64) ":"`.
fn parse_sf_byte_sequence(c : SfCursor) -> Bytes raise HsError {
let start = c.pos()
if c.take() != ':' {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
start,
"expected ':' for byte sequence",
)
}
let b64_start = c.pos()
let mut end = -1
while !c.at_end() {
if c.peek() == ':' {
end = c.pos()
c.advance()
break
}
c.advance()
}
if end < 0 {
raise hs_error_at(
StructuredFieldParsing,
UnexpectedEnd,
c.pos(),
"unterminated byte sequence",
)
}
let encoded = c.input.sub(start=b64_start, end~).to_owned()
match base64_decode_bytes(encoded) {
Ok(b) => b
Err(_) =>
raise hs_error_at(
StructuredFieldParsing,
InvalidBase64,
b64_start,
"invalid base64 in byte sequence",
)
}
}
///|
/// Parses zero or more parameters.
///
/// `parameters = *( ";" *SP parameter )`
fn parse_sf_parameters(
c : SfCursor,
max_parameter_count : Int,
) -> Array[SfParameter] raise HsError {
let out : Array[SfParameter] = Array::new()
while !c.at_end() {
if c.peek() != ';' {
break
}
c.advance()
c.skip_sp()
let name = parse_sf_key(c)
for p in out {
if p.name == name {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"duplicate parameter: " + name,
)
}
}
if out.length() >= max_parameter_count {
raise hs_error_at(
StructuredFieldParsing,
TooManyParameters,
c.pos(),
"too many parameters",
)
}
let value = if !c.at_end() && c.peek() == '=' {
c.advance()
parse_sf_bare_item(c)
} else {
SfBoolean(true)
}
out.push({ name, value })
}
out
}
///|
/// Parses a bare item (string/token/integer/byte-sequence/boolean).
fn parse_sf_bare_item(c : SfCursor) -> SfBareItem raise HsError {
let ch = c.peek()
if ch == '"' {
SfString(parse_sf_string(c))
} else if ch == ':' {
SfByteSequence(parse_sf_byte_sequence(c))
} else if ch == '?' {
SfBoolean(parse_sf_boolean(c))
} else if ch == '-' || (ch >= '0' && ch <= '9') {
SfInteger(parse_sf_integer(c))
} else {
SfToken(parse_sf_token(c))
}
}
///|
/// Parses a single item: bare item plus parameters.
fn parse_sf_item(
c : SfCursor,
max_parameter_count : Int,
) -> SfItem raise HsError {
let value = parse_sf_bare_item(c)
let parameters = parse_sf_parameters(c, max_parameter_count)
{ value, parameters }
}
///|
/// Parses an inner list: `"(" *SP [ sf-item *( 1*SP sf-item ) *SP ] ")"`.
fn parse_sf_inner_list(
c : SfCursor,
max_parameter_count : Int,
max_members : Int,
) -> SfInnerList raise HsError {
let start = c.pos()
if c.take() != '(' {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
start,
"expected '(' for inner list",
)
}
let items = Array::new()
c.skip_sp()
if !c.at_end() && c.peek() != ')' {
while true {
items.push(parse_sf_item(c, max_parameter_count))
if items.length() > max_members {
raise hs_error_at(
StructuredFieldParsing,
TooManyComponents,
c.pos(),
"too many inner-list members",
)
}
c.skip_sp()
if c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
UnexpectedEnd,
c.pos(),
"unterminated inner list",
)
}
if c.peek() == ')' {
break
}
}
}
if c.at_end() || c.take() != ')' {
raise hs_error_at(
StructuredFieldParsing,
UnexpectedEnd,
c.pos(),
"unterminated inner list",
)
}
let parameters = parse_sf_parameters(c, max_parameter_count)
{ items, parameters }
}
///|
/// Parses a dictionary into ordered entries.
///
/// `sf-dictionary = dict-member *( *SP ";" *SP dict-member )`
fn parse_sf_dictionary(
c : SfCursor,
max_parameter_count : Int,
max_entries : Int,
max_members : Int,
) -> Array[SfDictionaryEntry] raise HsError {
let out = Array::new()
c.skip_sp()
if c.at_end() {
return out
}
while true {
let key = parse_sf_key(c)
for e in out {
if e.key == key {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"duplicate dictionary key: " + key,
)
}
}
if out.length() >= max_entries {
raise hs_error_at(
StructuredFieldParsing,
TooManySignatures,
c.pos(),
"too many dictionary entries",
)
}
let value = if !c.at_end() && c.peek() == '=' {
c.advance()
if !c.at_end() && c.peek() == '(' {
let il = parse_sf_inner_list(c, max_parameter_count, max_members)
InnerListMember(il)
} else {
ItemMember(parse_sf_item(c, max_parameter_count))
}
} else {
ItemMember({ value: SfBoolean(true), parameters: Array::new() })
}
out.push({ key, value })
c.skip_sp()
if c.at_end() {
break
}
if c.peek() != ',' {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"expected ',' between dictionary members",
)
}
c.advance()
c.skip_sp()
if c.at_end() {
// A trailing ',' must be followed by another dict-member.
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"trailing ',' in dictionary",
)
}
}
out
}
///|
/// Parses a complete dictionary from a string, requiring that no trailing
/// garbage remains. Returns the ordered entries.
pub fn parse_sf_dictionary_string(
input : String,
limits : Limits,
) -> Result[Array[SfDictionaryEntry], HsError] {
try {
let _ = limits.check_signature_field_size(
input.length(),
"structured field",
)
let c = SfCursor::new(input)
let entries = parse_sf_dictionary(
c,
limits.max_parameter_count,
limits.max_signature_count,
limits.max_components_per_signature,
)
c.skip_sp()
if !c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"trailing garbage after dictionary",
)
}
Ok(entries)
} catch {
e => Err(e)
}
}
///|
/// Parses a single item from a string, requiring no trailing garbage.
pub fn parse_sf_item_string(
input : String,
limits : Limits,
) -> Result[SfItem, HsError] {
try {
let _ = limits.check_signature_field_size(
input.length(),
"structured field",
)
let c = SfCursor::new(input)
let item = parse_sf_item(c, limits.max_parameter_count)
c.skip_sp()
if !c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"trailing garbage after item",
)
}
Ok(item)
} catch {
e => Err(e)
}
}
///|
/// Parses a single inner list from a string, requiring no trailing garbage.
pub fn parse_sf_inner_list_string(
input : String,
limits : Limits,
) -> Result[SfInnerList, HsError] {
try {
let _ = limits.check_signature_field_size(
input.length(),
"structured field",
)
let c = SfCursor::new(input)
let il = parse_sf_inner_list(
c,
limits.max_parameter_count,
limits.max_components_per_signature,
)
c.skip_sp()
if !c.at_end() {
raise hs_error_at(
StructuredFieldParsing,
InvalidStructuredField,
c.pos(),
"trailing garbage after inner list",
)
}
Ok(il)
} catch {
e => Err(e)
}
}