capsule_lib/
ascii_header.rs1use crate::error::{CapsuleError, CapsuleResult};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct HeaderField {
8 pub key: String,
9 pub value: String,
10}
11
12fn is_allowed_key_byte(b: u8) -> bool {
13 matches!(b,
14 b'A'..=b'Z'
15 | b'a'..=b'z'
16 | b'0'..=b'9'
17 | b'_'
18 | b'-'
19 | b'.'
20 )
21}
22
23pub fn parse_ascii_header_kv(header_bytes: &[u8]) -> CapsuleResult<Vec<HeaderField>> {
24 for (i, &b) in header_bytes.iter().enumerate() {
25 if b > 0x7F {
26 return Err(CapsuleError::NonAsciiByte { which: "header", offset: i });
27 }
28 }
29
30 let mut fields: Vec<HeaderField> = Vec::new();
31 let mut seen_keys = std::collections::BTreeSet::<String>::new();
32
33 for (line_index, line) in header_bytes.split(|&b| b == b'\n').enumerate() {
34 if line.is_empty() {
35 continue;
36 }
37
38 let Some(eq_pos) = line.iter().position(|&b| b == b'=') else {
39 return Err(CapsuleError::InvalidAsciiHeader(format!(
40 "line {line_index} is non-empty but contains no '='"
41 )));
42 };
43
44 if eq_pos == 0 {
45 return Err(CapsuleError::InvalidAsciiHeader(format!(
46 "line {line_index} has empty key"
47 )));
48 }
49
50 let key_bytes = &line[..eq_pos];
51 let value_bytes = &line[eq_pos + 1..];
52
53 if key_bytes.iter().any(|&b| !is_allowed_key_byte(b)) {
54 return Err(CapsuleError::InvalidAsciiHeader(format!(
55 "line {line_index} key contains invalid characters"
56 )));
57 }
58
59 let key = String::from_utf8(key_bytes.to_vec()).map_err(|e| {
60 CapsuleError::InvalidAsciiHeader(format!("line {line_index} key is not valid UTF-8: {e}"))
61 })?;
62 let value = String::from_utf8(value_bytes.to_vec()).map_err(|e| {
63 CapsuleError::InvalidAsciiHeader(format!("line {line_index} value is not valid UTF-8: {e}"))
64 })?;
65
66 if !seen_keys.insert(key.clone()) {
67 return Err(CapsuleError::InvalidAsciiHeader(format!(
68 "duplicate key '{key}'"
69 )));
70 }
71
72 fields.push(HeaderField { key, value });
73 }
74
75 Ok(fields)
76}
77
78pub fn encode_ascii_header_kv(fields: &[HeaderField]) -> CapsuleResult<Vec<u8>> {
79 let mut out = Vec::new();
80 let mut seen_keys = std::collections::BTreeSet::<&str>::new();
81
82 for field in fields {
83 if field.key.is_empty() {
84 return Err(CapsuleError::InvalidAsciiHeader("empty key".to_string()));
85 }
86
87 if !seen_keys.insert(field.key.as_str()) {
88 return Err(CapsuleError::InvalidAsciiHeader(format!(
89 "duplicate key '{}'",
90 field.key
91 )));
92 }
93
94 if field.key.as_bytes().iter().any(|&b| !is_allowed_key_byte(b)) {
95 return Err(CapsuleError::InvalidAsciiHeader(format!(
96 "invalid key '{}'",
97 field.key
98 )));
99 }
100
101 if field.key.as_bytes().iter().any(|&b| b > 0x7F) || field.value.as_bytes().iter().any(|&b| b > 0x7F) {
102 return Err(CapsuleError::InvalidAsciiHeader(
103 "non-ASCII key/value".to_string(),
104 ));
105 }
106
107 out.extend_from_slice(field.key.as_bytes());
108 out.push(b'=');
109 out.extend_from_slice(field.value.as_bytes());
110 out.push(b'\n');
111 }
112
113 Ok(out)
114}