noggin_parser/
header_parser.rs1use memchr::memmem;
2
3#[derive(thiserror::Error, PartialEq, Debug)]
4pub enum Error {
5 #[error("the http head was not complete")]
6 IncompleteHead,
7 #[error("the http head contained non-ascii characters")]
8 NonAscii,
9 #[error("missing http header: {0}")]
10 MissingHeader(&'static str),
11 #[error("malformed http header")]
12 MalformedHeader,
13 #[error("invalid http header value: {0}")]
14 InvalidHeaderValue(&'static str),
15}
16
17pub trait HeadParser<'de>: Sized {
24 fn parse_head_section(head: &'de str) -> Result<Self, Error>;
36
37 fn parse_headers(head_and_body: &'de [u8]) -> Result<(Self, &'de [u8]), Error> {
55 let head_end = memmem::find(head_and_body, b"\r\n\r\n").ok_or(Error::IncompleteHead)?;
56 let head_bytes = &head_and_body[..head_end];
57 if !head_bytes.is_ascii() {
58 return Err(Error::NonAscii);
59 }
60 let head = unsafe { std::str::from_utf8_unchecked(head_bytes) };
63 let headers = Self::parse_head_section(head)?;
64 let body = &head_and_body[head_end + 4..];
65 Ok((headers, body))
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[derive(Debug, PartialEq)]
74 pub struct SimpleHeaders {
75 pub content_length: usize,
76 }
78
79 impl<'de> HeadParser<'de> for SimpleHeaders {
80 fn parse_head_section(head: &'de str) -> Result<Self, Error> {
81 let content_length_str = head
83 .split("\r\n")
84 .find(|line| line.starts_with("Content-Length:"))
85 .ok_or(Error::MissingHeader("Content-Length"))?
86 .split(':')
87 .nth(1)
88 .ok_or(Error::MalformedHeader)?
89 .trim();
90
91 let content_length = content_length_str
92 .parse::<usize>()
93 .map_err(|_| Error::InvalidHeaderValue("Content-Length"))?;
94
95 Ok(SimpleHeaders {
96 content_length,
97 })
99 }
100 }
101
102 #[test]
103 fn parse_valid_head() {
104 let input_head = b"Content-Length: 5\r\nAnother-Header: value\r\n\r\nBodyHere";
105 let (headers, body) = SimpleHeaders::parse_headers(input_head).unwrap();
106
107 assert_eq!(headers, SimpleHeaders { content_length: 5 });
108 assert_eq!(body, b"BodyHere");
109 }
110
111 #[test]
112 fn error_on_non_ascii_head() {
113 let input_head = b"Content-Length: 5\r\nNon-Ascii: \x80\x81\x82\r\n\r\nBodyHere";
114 let result = SimpleHeaders::parse_headers(input_head);
115
116 assert_eq!(result, Err(Error::NonAscii));
117 }
118
119 #[test]
120 fn error_on_incomplete_head() {
121 let input_head = b"Content-Length: 5\r\nAnother-Header: value\r\nBodyWithoutHeadDelimiter";
122 let result = SimpleHeaders::parse_headers(input_head);
123
124 assert_eq!(result, Err(Error::IncompleteHead));
125 }
126
127 #[test]
128 fn error_on_missing_header() {
129 let input_head = b"Wrong-Header: 5\r\nAnother-Header: value\r\n\r\nBodyHere";
130 let result = SimpleHeaders::parse_headers(input_head);
131
132 assert_eq!(result, Err(Error::MissingHeader("Content-Length")));
133 }
134
135 #[test]
136 fn error_on_invalid_header_value() {
137 let input_head = b"Content-Length: invalid_value\r\nAnother-Header: value\r\n\r\nBodyHere";
138 let result = SimpleHeaders::parse_headers(input_head);
139
140 assert_eq!(result, Err(Error::InvalidHeaderValue("Content-Length")));
141 }
142}