///|
/// One decoded header of an `HMSG` message.
pub(all) struct Header {
name : String
value : String
} derive(Eq, Debug)
///|
/// The headers block of an `HMSG` body: a `NATS/1.0` line optionally
/// followed by an inline status, then CRLF-separated `Name: Value` pairs
/// ended by an empty line.
///
/// `NATS/1.0 100 Idle Heartbeat\r\nK: V\r\n\r\n`
pub(all) struct HeaderBlock {
/// The protocol marker line without the `NATS/1.0` prefix (inline status
/// code and description when present, e.g. `100 Idle Heartbeat`).
version_line : String
headers : Array[Header]
} derive(Eq, Debug)
///|
/// Decode a raw headers block. Tolerates LF-only line endings, as nats-server
/// itself produces CRLF but user headers may be re-encoded by intermediaries.
pub fn parse_header_block(raw : Bytes) -> Result[HeaderBlock, String] {
let text = ascii_text(raw)
// Locate the end of the version line, CRLF first, bare LF as fallback.
let version_end : (Int, Int) = match text.find("\r\n") {
Some(i) => (i, 2)
None =>
match text.find("\n") {
Some(i) => (i, 1)
None => return Err("headers block missing version line")
}
}
let version_line = text[:version_end.0].to_owned()
guard version_line.has_prefix("NATS/1.0") else {
return Err("headers block does not start with NATS/1.0")
}
let headers : Array[Header] = []
let body = text[version_end.0 + version_end.1:].to_owned()
for line in split_lines(body) {
if line.is_empty() {
continue
}
let colon = match line.find(":") {
Some(c) if c > 0 => c
_ => return Err("malformed header line: \"\{line}\"")
}
let name = line[:colon].to_owned()
let mut value = line[colon + 1:].to_owned()
// Single leading space after the colon is not part of the value.
if value.has_prefix(" ") {
value = value[1:].to_owned()
}
headers.push({ name, value, })
}
Ok({ version_line: version_line[8:].to_owned(), headers, })
}
///|
fn ascii_text(raw : Bytes) -> String {
let sb = StringBuilder()
for b in raw {
sb.write_char((b.to_int() & 0x7F).unsafe_to_char())
}
sb.to_string()
}
///|
fn split_lines(text : String) -> Array[String] {
let lines : Array[String] = []
let mut start = 0
let len = text.length()
let mut i = 0
while i < len {
if text[i] == u16('\n') {
let mut end = i
if end > start && text[end - 1] == u16('\r') {
end -= 1
}
lines.push(text[start:end].to_owned())
start = i + 1
}
i += 1
}
if start < len {
lines.push(text[start:].to_owned())
}
lines
}