use crate::Error;
use std::{borrow::Cow, ops::Range, str};
pub(crate) fn parse(input: &str) -> Result<Vec<(&str, Value<'_>)>, Error> {
let mut results = Vec::new();
let b = input.as_bytes();
let mut state = State { i: 0, error: false };
while state.i < b.len() {
let key = parse_key(input, &mut state).trim_ascii();
state.i += 1;
let (value, properties, escaped) = parse_value(input, &mut state);
state.i += 1;
let value = if properties > 1 {
let end = state.i;
state.i = 0;
let properties = parse_properties(value, &mut state, properties);
state.i = end;
Value::List(properties)
} else if escaped {
Value::Single(Cow::Owned(unescape(value.trim_ascii(), &mut state)))
} else {
Value::Single(Cow::Borrowed(value.trim_ascii()))
};
results.push((key, value));
}
if !state.error {
Ok(results)
} else {
Err(Error::msg("baggage parser failed"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Value<'a> {
Single(Cow<'a, str>),
List(Vec<(&'a str, Property<'a>)>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Property<'a> {
None,
Single(Cow<'a, str>),
}
struct State {
i: usize,
error: bool,
}
fn parse_key<'a>(input: &'a str, state: &mut State) -> &'a str {
let b = input.as_bytes();
let key_start = state.i;
while state.i < b.len() {
if b[state.i] == b'=' {
break;
} else {
state.i += 1;
continue;
}
}
let key_end = state.i;
if key_start == key_end {
state.error = true;
""
} else {
slice(b, key_start..key_end)
}
}
fn parse_value<'a>(input: &'a str, state: &mut State) -> (&'a str, usize, bool) {
let b = input.as_bytes();
let value_start = state.i;
let mut escaped = false;
let mut properties = 1;
while state.i < b.len() {
match b[state.i] {
b',' => break,
b';' | b'=' => {
properties += 1;
}
b'%' => {
escaped = true;
}
_ => (),
}
state.i += 1;
continue;
}
let value_end = state.i;
if value_start == value_end {
state.error = true;
("", 1, false)
} else {
(slice(b, value_start..value_end), properties, escaped)
}
}
fn parse_properties<'a>(
input: &'a str,
state: &mut State,
properties: usize,
) -> Vec<(&'a str, Property<'a>)> {
let mut properties = Vec::with_capacity(properties);
let b = input.as_bytes();
while state.i < b.len() {
let (key, has_value) = parse_property_key(input, state);
state.i += 1;
let value = if has_value {
let (value, escaped) = parse_property_value(input, state);
state.i += 1;
if escaped {
Property::Single(Cow::Owned(unescape(value.trim_ascii(), state)))
} else {
Property::Single(Cow::Borrowed(value.trim_ascii()))
}
} else {
Property::None
};
properties.push((key.trim_ascii(), value));
}
properties
}
fn parse_property_key<'a>(input: &'a str, state: &mut State) -> (&'a str, bool) {
let b = input.as_bytes();
let key_start = state.i;
let mut has_value = false;
while state.i < b.len() {
match b[state.i] {
b';' => break,
b'=' => {
has_value = true;
break;
}
_ => (),
}
state.i += 1;
continue;
}
let key_end = state.i;
(slice(b, key_start..key_end), has_value)
}
fn parse_property_value<'a>(input: &'a str, state: &mut State) -> (&'a str, bool) {
let b = input.as_bytes();
let value_start = state.i;
let mut escaped = false;
while state.i < b.len() {
match b[state.i] {
b';' => break,
b'%' => {
escaped = true;
}
_ => (),
}
state.i += 1;
continue;
}
let value_end = state.i;
(slice(b, value_start..value_end), escaped)
}
fn unescape(escaped: &str, state: &mut State) -> String {
let b = escaped.as_bytes();
let mut unescaped = Vec::with_capacity(b.len());
let mut s = 0;
let mut i = 0;
while i < b.len() {
if b[i] == b'%' {
unescaped.extend_from_slice(&b[s..i]);
i += 1;
if b.len() < i + 2 {
state.error = true;
break;
}
unescaped.push(hex_byte(b[i], b[i + 1], state));
i += 2;
s = i;
continue;
}
i += 1;
}
unescaped.extend_from_slice(&b[s..]);
if let Ok(unescaped) = String::from_utf8(unescaped) {
unescaped
} else {
state.error = true;
String::new()
}
}
fn hex_byte(a: u8, b: u8, state: &mut State) -> u8 {
const HEX_DECODE_TABLE: &[u8; 256] = &{
let mut buf = [0; 256];
let mut i: u8 = 0;
loop {
buf[i as usize] = match i {
b'0'..=b'9' => i - b'0',
b'a'..=b'f' => i - b'a' + 10,
b'A'..=b'F' => i - b'A' + 10,
_ => 0xff,
};
if i == 255 {
break buf;
}
i += 1
}
};
let h1 = HEX_DECODE_TABLE[a as usize];
let h2 = HEX_DECODE_TABLE[b as usize];
if h1 | h2 == 0xff {
state.error = true;
0
} else {
(h1 << 4) | h2
}
}
fn slice(b: &[u8], range: Range<usize>) -> &str {
str::from_utf8(&b[range]).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_valid() {
for (case, expected) in [
("", Vec::<(&str, Value)>::new()),
("a=b", vec![("a", Value::Single(Cow::Borrowed("b")))]),
("a = b", vec![("a", Value::Single(Cow::Borrowed("b")))]),
("a=b,c=d", vec![("a", Value::Single(Cow::Borrowed("b"))), ("c", Value::Single(Cow::Borrowed("d")))]),
("a=b,", vec![("a", Value::Single(Cow::Borrowed("b")))]),
("a=b=c", vec![("a", Value::List(vec![("b", Property::Single(Cow::Borrowed("c")))]))]),
(
"a=b;c=d",
vec![("a", Value::List(vec![("b", Property::None), ("c", Property::Single(Cow::Borrowed("d")))]))],
),
(
"a = b; c = d",
vec![("a", Value::List(vec![("b", Property::None), ("c", Property::Single(Cow::Borrowed("d")))]))],
),
("a=b;", vec![("a", Value::List(vec![("b", Property::None)]))]),
("a=b%20", vec![("a", Value::Single(Cow::Owned("b ".into())))]),
(
"key1=value1;property1;property2 , key2 = value2, key3=value3; propertyKey=property%20Value",
vec![
(
"key1",
Value::List(vec![
("value1", Property::None),
("property1", Property::None),
("property2", Property::None),
]),
),
("key2", Value::Single(Cow::Borrowed("value2"))),
(
"key3",
Value::List(vec![
("value3", Property::None),
("propertyKey", Property::Single(Cow::Owned("property Value".into()))),
]),
),
],
),
] {
let Ok(actual) = parse(case) else {
panic!("parsing {case} failed");
};
assert_eq!(expected, actual, "parsing {case}");
}
}
#[test]
fn parse_invalid() {
for case in [
"a", "a=", "=a", "a;b", "a=,", "=,", "a,b", "a=b%", "a=b%1", "a=b%gg", "a=b%ff",
] {
if let Ok(actual) = parse(case) {
panic!("expected parsing {case} to fail but it produced {actual:?}");
};
}
}
}