1pub mod chunk;
4pub mod request;
5pub mod response;
6pub mod transfer;
7
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ParseError {
14 BadRequestLine,
15 BadStatusLine,
16 HeaderTooLarge,
17 InvalidChunkSize,
18 InvalidContentLength,
19 UnexpectedEof,
20 InvalidHeaderName,
21 InvalidHeaderValue,
22 Other(String),
23}
24
25impl fmt::Display for ParseError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 Self::BadRequestLine => write!(f, "malformed HTTP request line"),
29 Self::BadStatusLine => write!(f, "malformed HTTP status line"),
30 Self::HeaderTooLarge => write!(f, "header exceeds size limit"),
31 Self::InvalidChunkSize => write!(f, "invalid chunk size"),
32 Self::InvalidContentLength => write!(f, "invalid Content-Length"),
33 Self::UnexpectedEof => write!(f, "unexpected EOF"),
34 Self::InvalidHeaderName => write!(f, "invalid header name"),
35 Self::InvalidHeaderValue => write!(f, "invalid header value"),
36 Self::Other(s) => write!(f, "{s}"),
37 }
38 }
39}
40
41impl std::error::Error for ParseError {}
42
43pub(crate) const MAX_LINE: usize = 4096;
49
50pub(crate) fn read_line(r: &mut impl std::io::Read) -> Result<String, ParseError> {
55 let mut buf = Vec::with_capacity(128);
56 let mut byte = [0u8; 1];
57 loop {
58 match r.read(&mut byte) {
59 Ok(0) => {
60 if buf.is_empty() {
61 return Err(ParseError::UnexpectedEof);
62 }
63 break;
64 }
65 Ok(_) => {
66 if byte[0] == b'\n' {
67 break;
68 }
69 buf.push(byte[0]);
70 if buf.len() > MAX_LINE {
71 return Err(ParseError::HeaderTooLarge);
72 }
73 }
74 Err(_) => return Err(ParseError::UnexpectedEof),
75 }
76 }
77 if buf.last() == Some(&b'\r') {
79 buf.pop();
80 }
81 String::from_utf8(buf).map_err(|_| ParseError::Other("non-UTF-8 in header line".into()))
82}
83
84pub(crate) fn read_headers(
87 r: &mut impl std::io::Read,
88 max_bytes: usize,
89) -> Result<crate::header::Header, ParseError> {
90 use crate::header::Header;
91 let mut h = Header::new();
92 let mut total = 0usize;
93
94 loop {
95 let line = read_line(r)?;
96 total += line.len() + 2; if total > max_bytes {
98 return Err(ParseError::HeaderTooLarge);
99 }
100 if line.is_empty() {
101 break; }
103 if line.starts_with(' ') || line.starts_with('\t') {
105 return Err(ParseError::Other("obsolete header folding not supported".into()));
106 }
107 let colon = line.find(':').ok_or(ParseError::InvalidHeaderName)?;
108 let name = line[..colon].trim();
109 let value = line[colon + 1..].trim();
110 if name.is_empty() {
111 return Err(ParseError::InvalidHeaderName);
112 }
113 if !name.bytes().all(is_token_char) {
115 return Err(ParseError::InvalidHeaderName);
116 }
117 h.add(name, value);
118 }
119 Ok(h)
120}
121
122pub(crate) fn is_token_char(b: u8) -> bool {
124 matches!(b,
125 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9'
126 | b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+'
127 | b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~'
128 )
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use std::io::Cursor;
135
136 #[test]
137 fn read_line_crlf() {
138 let mut c = Cursor::new(b"Hello\r\n");
139 assert_eq!(read_line(&mut c).unwrap(), "Hello");
140 }
141
142 #[test]
143 fn read_line_lf_only() {
144 let mut c = Cursor::new(b"World\n");
145 assert_eq!(read_line(&mut c).unwrap(), "World");
146 }
147
148 #[test]
149 fn read_headers_basic() {
150 let raw = b"Content-Type: text/plain\r\nContent-Length: 5\r\n\r\n";
151 let mut c = Cursor::new(raw.as_ref());
152 let h = read_headers(&mut c, 8192).unwrap();
153 assert_eq!(h.get("content-type"), Some("text/plain"));
154 assert_eq!(h.get("content-length"), Some("5"));
155 }
156
157 #[test]
158 fn read_headers_too_large() {
159 let big: Vec<u8> = (0..5000).flat_map(|_| b"X-H: v\r\n".iter().copied()).collect();
160 let mut bytes = big;
161 bytes.extend_from_slice(b"\r\n");
162 let mut c = Cursor::new(bytes);
163 assert_eq!(read_headers(&mut c, 1024), Err(ParseError::HeaderTooLarge));
164 }
165}