rust_web_server/otel/
baggage.rs1pub const MAX_MEMBERS: usize = 64;
13pub const MAX_BYTES: usize = 8192;
14
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
17pub struct Baggage {
18 entries: Vec<(String, String, String)>,
19}
20
21impl Baggage {
22 pub fn parse(header: &str) -> Baggage {
25 let mut baggage = Baggage::default();
26 let mut bytes = 0;
27 for member in header.split(',') {
28 let member = member.trim_matches(|c| c == ' ' || c == '\t');
29 if member.is_empty() {
30 continue;
31 }
32 if baggage.entries.len() == MAX_MEMBERS || bytes + member.len() > MAX_BYTES {
33 break;
34 }
35 let (pair, properties) = match member.split_once(';') {
36 Some((pair, properties)) => (pair, properties.trim_matches(|c| c == ' ' || c == '\t')),
37 None => (member, ""),
38 };
39 let Some((key, value)) = pair.split_once('=') else { continue };
40 let key = key.trim_matches(|c| c == ' ' || c == '\t');
41 let value = value.trim_matches(|c| c == ' ' || c == '\t');
42 if !is_token(key) || !value.bytes().all(is_baggage_octet) {
43 continue;
44 }
45 let Some(value) = percent_decode(value) else { continue };
46 bytes += member.len() + 1;
47 baggage.set_with_properties(key, &value, properties);
48 }
49 baggage
50 }
51
52 pub fn to_header(&self) -> String {
54 let mut out = String::new();
55 for (key, value, properties) in &self.entries {
56 let member = if properties.is_empty() {
57 format!("{key}={}", percent_encode(value))
58 } else {
59 format!("{key}={};{properties}", percent_encode(value))
60 };
61 if out.len() + member.len() + 1 > MAX_BYTES {
62 break;
63 }
64 if !out.is_empty() {
65 out.push(',');
66 }
67 out.push_str(&member);
68 }
69 out
70 }
71
72 pub fn get(&self, key: &str) -> Option<&str> {
74 self.entries.iter().find(|(k, _, _)| k == key).map(|(_, v, _)| v.as_str())
75 }
76
77 pub fn set(&mut self, key: &str, value: &str) -> bool {
81 self.set_with_properties(key, value, "")
82 }
83
84 fn set_with_properties(&mut self, key: &str, value: &str, properties: &str) -> bool {
85 if !is_token(key) {
86 return false;
87 }
88 if let Some(entry) = self.entries.iter_mut().find(|(k, _, _)| k == key) {
89 entry.1 = value.to_string();
90 entry.2 = properties.to_string();
91 return true;
92 }
93 if self.entries.len() >= MAX_MEMBERS {
94 return false;
95 }
96 self.entries.push((key.to_string(), value.to_string(), properties.to_string()));
97 true
98 }
99
100 pub fn remove(&mut self, key: &str) {
102 self.entries.retain(|(k, _, _)| k != key);
103 }
104
105 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
107 self.entries.iter().map(|(k, v, _)| (k.as_str(), v.as_str()))
108 }
109
110 pub fn is_empty(&self) -> bool {
111 self.entries.is_empty()
112 }
113
114 pub fn len(&self) -> usize {
115 self.entries.len()
116 }
117}
118
119fn is_token(s: &str) -> bool {
121 !s.is_empty() && s.bytes().all(|b| b.is_ascii_alphanumeric() || b"!#$%&'*+-.^_`|~".contains(&b))
122}
123
124fn is_baggage_octet(b: u8) -> bool {
126 matches!(b, 0x21 | 0x23..=0x2B | 0x2D..=0x3A | 0x3C..=0x5B | 0x5D..=0x7E)
127}
128
129fn percent_decode(value: &str) -> Option<String> {
130 let bytes = value.as_bytes();
131 let mut out = Vec::with_capacity(bytes.len());
132 let mut i = 0;
133 while i < bytes.len() {
134 if bytes[i] == b'%' {
135 let hex = std::str::from_utf8(bytes.get(i + 1..i + 3)?).ok()?;
136 out.push(u8::from_str_radix(hex, 16).ok()?);
137 i += 3;
138 } else {
139 out.push(bytes[i]);
140 i += 1;
141 }
142 }
143 String::from_utf8(out).ok()
144}
145
146fn percent_encode(value: &str) -> String {
147 let mut out = String::with_capacity(value.len());
148 for b in value.bytes() {
149 if is_baggage_octet(b) && b != b'%' {
150 out.push(b as char);
151 } else {
152 out.push_str(&format!("%{b:02X}"));
153 }
154 }
155 out
156}
157
158#[cfg(test)]
159mod tests;