nntp_proxy/protocol/article/
headers.rs1use super::error::ParseError;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Headers<'a> {
14 data: &'a [u8],
15}
16
17impl<'a> Headers<'a> {
18 pub fn parse(data: &'a [u8]) -> Result<Self, ParseError> {
29 Self::validate_headers(data)?;
30 Ok(Headers { data })
31 }
32
33 fn validate_headers(data: &[u8]) -> Result<(), ParseError> {
35 let mut pos = 0;
36 let len = data.len();
37
38 while pos < len {
39 let line_end = Self::find_line_end(data, pos)?;
41 let line = &data[pos..line_end];
42
43 if line.is_empty() {
45 pos = line_end + 2; continue;
47 }
48
49 if line[0] == b' ' || line[0] == b'\t' {
51 if pos == 0 {
53 return Err(ParseError::InvalidHeader(
54 "Header cannot start with folding whitespace".to_string(),
55 ));
56 }
57 pos = line_end + 2;
58 continue;
59 }
60
61 let colon_pos = memchr::memchr(b':', line).ok_or_else(|| {
63 ParseError::InvalidHeader(format!(
64 "Header missing colon: {}",
65 String::from_utf8_lossy(line)
66 ))
67 })?;
68
69 let name = &line[..colon_pos];
71 if name.is_empty() {
72 return Err(ParseError::InvalidHeader("Empty header name".to_string()));
73 }
74
75 for &byte in name {
77 if byte == b' ' || byte == b'\t' || !(33..=126).contains(&byte) {
78 return Err(ParseError::InvalidHeader(format!(
79 "Invalid character in header name: {}",
80 String::from_utf8_lossy(name)
81 )));
82 }
83 }
84
85 pos = line_end + 2; }
87
88 Ok(())
89 }
90
91 fn find_line_end(data: &[u8], start: usize) -> Result<usize, ParseError> {
93 for i in start..data.len() {
94 if data[i] == b'\n' {
95 if i > 0 && data[i - 1] == b'\r' {
97 return Ok(i - 1); }
99 return Err(ParseError::InvalidHeader(
100 "LF not preceded by CR".to_string(),
101 ));
102 }
103 if data[i] == b'\r' {
104 if i + 1 < data.len() && data[i + 1] == b'\n' {
106 return Ok(i);
107 } else if i + 1 >= data.len() {
108 return Ok(i);
110 }
111 return Err(ParseError::InvalidHeader(
112 "CR not followed by LF".to_string(),
113 ));
114 }
115 }
116
117 Ok(data.len())
119 }
120
121 #[must_use]
129 pub fn get(&self, name: &str) -> Option<&'a [u8]> {
130 let name_lower = name.to_ascii_lowercase();
131 let mut pos = 0;
132
133 while pos < self.data.len() {
134 let line_end = Self::find_line_end(self.data, pos).ok()?;
136 let line = &self.data[pos..line_end];
137
138 if line.is_empty() {
139 pos = line_end + 2;
140 continue;
141 }
142
143 if line[0] == b' ' || line[0] == b'\t' {
145 pos = line_end + 2;
146 continue;
147 }
148
149 let colon_pos = memchr::memchr(b':', line)?;
151 let header_name = &line[..colon_pos];
152
153 if header_name.eq_ignore_ascii_case(name_lower.as_bytes()) {
155 let mut value_start = colon_pos + 1;
157
158 while value_start < line.len()
160 && (line[value_start] == b' ' || line[value_start] == b'\t')
161 {
162 value_start += 1;
163 }
164
165 let value = &line[value_start..];
166
167 let mut next_pos = line_end + 2;
169 let mut folded_value = Vec::new();
170
171 while next_pos < self.data.len() {
172 let next_line_end = Self::find_line_end(self.data, next_pos).ok()?;
173 let next_line = &self.data[next_pos..next_line_end];
174
175 if next_line.is_empty() || (next_line[0] != b' ' && next_line[0] != b'\t') {
177 break;
178 }
179
180 if folded_value.is_empty() {
182 folded_value.extend_from_slice(value);
183 }
184 folded_value.push(b' '); folded_value.extend_from_slice(next_line.trim_ascii_start());
186
187 next_pos = next_line_end + 2;
188 }
189
190 if folded_value.is_empty() {
193 return Some(value);
194 }
195 return Some(value);
200 }
201
202 pos = line_end + 2;
203 }
204
205 None
206 }
207
208 #[must_use]
210 pub const fn iter(&self) -> HeaderIter<'a> {
211 HeaderIter {
212 data: self.data,
213 pos: 0,
214 }
215 }
216
217 #[must_use]
219 pub const fn as_bytes(&self) -> &'a [u8] {
220 self.data
221 }
222}
223
224impl<'a> IntoIterator for &Headers<'a> {
225 type Item = (&'a [u8], &'a [u8]);
226 type IntoIter = HeaderIter<'a>;
227
228 fn into_iter(self) -> Self::IntoIter {
229 self.iter()
230 }
231}
232
233pub struct HeaderIter<'a> {
235 data: &'a [u8],
236 pos: usize,
237}
238
239impl<'a> Iterator for HeaderIter<'a> {
240 type Item = (&'a [u8], &'a [u8]); fn next(&mut self) -> Option<Self::Item> {
243 while self.pos < self.data.len() {
244 let line_end = Headers::find_line_end(self.data, self.pos).ok()?;
246 let line = &self.data[self.pos..line_end];
247
248 if line.is_empty() {
249 self.pos = line_end + 2;
250 continue;
251 }
252
253 if line[0] == b' ' || line[0] == b'\t' {
255 self.pos = line_end + 2;
256 continue;
257 }
258
259 let colon_pos = memchr::memchr(b':', line)?;
261 let name = &line[..colon_pos];
262 let mut value_start = colon_pos + 1;
263
264 while value_start < line.len()
266 && (line[value_start] == b' ' || line[value_start] == b'\t')
267 {
268 value_start += 1;
269 }
270
271 let value = &line[value_start..];
272
273 self.pos = line_end + 2;
274 return Some((name, value));
275 }
276
277 None
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_valid_headers() {
287 let data = b"Subject: Test\r\nFrom: test@example.com\r\n";
288 let headers = Headers::parse(data).unwrap();
289 assert_eq!(headers.get("Subject"), Some(&b"Test"[..]));
290 assert_eq!(headers.get("From"), Some(&b"test@example.com"[..]));
291 }
292
293 #[test]
294 fn test_case_insensitive() {
295 let data = b"Subject: Test\r\n";
296 let headers = Headers::parse(data).unwrap();
297 assert_eq!(headers.get("subject"), headers.get("Subject"));
298 assert_eq!(headers.get("SUBJECT"), headers.get("Subject"));
299 }
300
301 #[test]
302 fn test_missing_colon() {
303 let data = b"Invalid Header\r\n";
304 assert!(matches!(
305 Headers::parse(data),
306 Err(ParseError::InvalidHeader(_))
307 ));
308 }
309
310 #[test]
311 fn test_empty_name() {
312 let data = b": Value\r\n";
313 assert!(matches!(
314 Headers::parse(data),
315 Err(ParseError::InvalidHeader(_))
316 ));
317 }
318
319 #[test]
320 fn test_iteration() {
321 let data = b"Subject: Test\r\nFrom: user@example.com\r\n";
322 let headers = Headers::parse(data).unwrap();
323
324 let items: Vec<_> = headers.iter().collect();
325 assert_eq!(items.len(), 2);
326 assert_eq!(items[0].0, b"Subject");
327 assert_eq!(items[0].1, b"Test");
328 }
329}