///|
pub fn escape_header(value : String) -> String raise ProtocolError {
escape_versioned(value, V12)
}
///|
fn escape_versioned(
value : String,
version : Version,
) -> String raise ProtocolError {
let chars : Array[Char] = []
for c in value {
match c {
'\u0000' => raise Invalid("NUL in header")
'\\' => {
chars.push('\\')
chars.push('\\')
}
'\r' =>
if version == V12 {
chars.push('\\')
chars.push('r')
} else {
chars.push('\r')
}
'\n' => {
chars.push('\\')
chars.push('n')
}
':' => {
chars.push('\\')
chars.push('c')
}
_ => chars.push(c)
}
}
String::from_array(chars)
}
///|
pub fn unescape_header(value : String) -> String raise ProtocolError {
unescape_versioned(value, V12)
}
///|
fn unescape_versioned(
value : String,
version : Version,
) -> String raise ProtocolError {
let chars : Array[Char] = []
let mut escaped = false
for c in value {
if escaped {
chars.push(
match c {
'r' =>
if version == V12 {
'\r'
} else {
raise Invalid("undefined 1.1 header escape")
}
'n' => '\n'
'c' => ':'
'\\' => '\\'
_ => raise Invalid("undefined header escape")
},
)
escaped = false
} else if c == '\\' {
escaped = true
} else {
if c == '\u0000' || (c == '\r' && version == V12) || c == '\n' || c == ':' {
raise Invalid("raw delimiter in header")
}
chars.push(c)
}
}
if escaped {
raise Invalid("trailing header escape")
}
String::from_array(chars)
}
///|
fn raw_header(
s : String,
key : Bool,
version~ : Version = V12,
) -> String raise ProtocolError {
for c in s {
if c == '\u0000' ||
c == '\n' ||
(c == '\r' && version == V12) ||
(key && c == ':') {
raise Invalid("invalid raw header")
}
}
s
}
///|
/// Emits LF and a content-length for every nonempty body (including binary NUL).
pub fn encode(frame : Frame) -> Bytes raise ProtocolError {
encode_versioned(frame, V12)
}
///|
/// Emits LF in all versions. 1.0 headers are literal; 1.1 CR is data, never a \r escape.
pub fn encode_versioned(
frame : Frame,
version : Version,
) -> Bytes raise ProtocolError {
if version == V10 && (frame.command == "NACK" || frame.command == "STOMP") {
raise Invalid("command unavailable in STOMP 1.0")
}
if !valid_command(frame.command) {
raise Invalid("unknown command")
}
if !body_allowed(frame.command) && frame.body.length() != 0 {
raise Invalid("body forbidden for command")
}
let mut text = frame.command + "\n"
for pair in frame.headers {
if pair.0.length() == 0 {
raise Invalid("empty header name")
}
let key = if version == V10 || unescaped(frame.command) {
raw_header(pair.0, true, version~)
} else {
escape_versioned(pair.0, version)
}
let value = if version == V10 || unescaped(frame.command) {
raw_header(pair.1, false, version~)
} else {
escape_versioned(pair.1, version)
}
text = text + key + ":" + value + "\n"
}
match frame.header("content-length") {
Some(n) =>
if decimal(n, 2147483647) != frame.body.length() {
raise Invalid("length mismatch")
}
None =>
if frame.body.length() > 0 {
text = text + "content-length:" + frame.body.length().to_string() + "\n"
}
}
let bytes = @utf8.encode(text + "\n").to_array()
for b in frame.body {
bytes.push(b)
}
bytes.push(b'\x00')
Bytes::from_array(bytes)
}