1use bytes::{Bytes, BytesMut};
10use http::{HeaderMap, Method, StatusCode, Version};
11use tokio::io::{AsyncRead, AsyncReadExt};
12
13use crate::error::HttpError;
14use crate::head::{BodyKind, ResponseHead};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Limits {
19 pub max_head_bytes: usize,
21 pub max_headers: usize,
23 pub max_chunk_bytes: u64,
25 pub max_body_bytes: u64,
27}
28
29impl Default for Limits {
30 fn default() -> Self {
31 Self {
32 max_head_bytes: 64 * 1024,
33 max_headers: 128,
34 max_chunk_bytes: 16 * 1024 * 1024,
35 max_body_bytes: 256 * 1024 * 1024,
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41enum BodyState {
42 Head,
44 Empty,
45 Length {
46 remaining: u64,
47 },
48 Chunked {
49 remaining: u64,
50 need_crlf: bool,
51 },
52 UntilClose,
53 Done,
54}
55
56#[derive(Debug)]
58pub struct Http1Reader<R> {
59 io: R,
60 buf: BytesMut,
61 limits: Limits,
62 body: BodyState,
63}
64
65impl<R: AsyncRead + Unpin> Http1Reader<R> {
66 pub fn new(io: R) -> Self {
67 Self::with_limits(io, Limits::default())
68 }
69
70 pub fn with_limits(io: R, limits: Limits) -> Self {
71 Self::with_limits_and_buffer(io, limits, BytesMut::new())
72 }
73
74 pub fn with_limits_and_buffer(io: R, limits: Limits, buffered: BytesMut) -> Self {
77 let mut buf = BytesMut::with_capacity(8 * 1024 + buffered.len());
78 buf.extend_from_slice(&buffered);
79 Self {
80 io,
81 buf,
82 limits,
83 body: BodyState::Head,
84 }
85 }
86
87 pub fn into_inner(self) -> (R, BytesMut) {
89 (self.io, self.buf)
90 }
91
92 pub async fn read_response_head(
95 &mut self,
96 request_method: &Method,
97 ) -> Result<ResponseHead, HttpError> {
98 if self.body != BodyState::Head {
99 return Err(HttpError::Protocol("head already read"));
100 }
101
102 let status_line = self.read_line(self.limits.max_head_bytes).await?;
103 let status_line = std::str::from_utf8(&status_line)
104 .map_err(|_| HttpError::Protocol("status line is not UTF-8"))?;
105 let mut parts = status_line.splitn(3, ' ');
106 let version = match parts.next() {
107 Some("HTTP/1.1") => Version::HTTP_11,
108 Some("HTTP/1.0") => Version::HTTP_10,
109 _ => return Err(HttpError::Protocol("unsupported HTTP version")),
110 };
111 let code = parts
112 .next()
113 .ok_or(HttpError::Protocol("status line has no status code"))?;
114 let status = code
115 .parse::<u16>()
116 .ok()
117 .filter(|code| (100..1000).contains(code))
118 .and_then(|code| StatusCode::from_u16(code).ok())
119 .ok_or(HttpError::Protocol("invalid status code"))?;
120
121 let mut headers = HeaderMap::new();
122 let mut count = 0usize;
123 let mut head_bytes = status_line.len();
124 loop {
125 let line = self
126 .read_line(self.limits.max_head_bytes.saturating_sub(head_bytes))
127 .await?;
128 head_bytes = head_bytes.saturating_add(line.len() + 2);
129 if head_bytes > self.limits.max_head_bytes {
130 return Err(HttpError::LimitExceeded("max_head_bytes"));
131 }
132 if line.is_empty() {
133 break;
134 }
135 count += 1;
136 if count > self.limits.max_headers {
137 return Err(HttpError::LimitExceeded("max_headers"));
138 }
139 if matches!(line.first(), Some(b' ') | Some(b'\t')) {
140 return Err(HttpError::Protocol("obsolete header line folding"));
143 }
144 let colon = line
145 .iter()
146 .position(|&b| b == b':')
147 .ok_or(HttpError::Protocol("header line has no colon"))?;
148 let (name, rest) = line.split_at(colon);
149 let value = &rest[1..];
150 let name = http::header::HeaderName::from_bytes(name)
151 .map_err(|_| HttpError::Protocol("invalid header name"))?;
152 let value = http::header::HeaderValue::from_bytes(trim_ows(value))
153 .map_err(|_| HttpError::Protocol("invalid header value"))?;
154 headers.append(name, value);
155 }
156
157 let body = Self::body_kind(&headers, &status, request_method)?;
158 self.body = match body {
159 BodyKind::Empty => BodyState::Empty,
160 BodyKind::Length(len) => BodyState::Length { remaining: len },
161 BodyKind::Chunked => BodyState::Chunked {
162 remaining: 0,
163 need_crlf: false,
164 },
165 BodyKind::UntilClose => BodyState::UntilClose,
166 };
167 Ok(ResponseHead {
168 version,
169 status,
170 headers,
171 body,
172 })
173 }
174
175 fn body_kind(
176 headers: &HeaderMap,
177 status: &StatusCode,
178 request_method: &Method,
179 ) -> Result<BodyKind, HttpError> {
180 if request_method == Method::HEAD || ResponseHead::status_forbids_body(*status) {
181 return Ok(BodyKind::Empty);
182 }
183 let chunked = headers
184 .get_all(http::header::TRANSFER_ENCODING)
185 .iter()
186 .filter_map(|value| value.to_str().ok())
187 .flat_map(|value| value.split(','))
188 .any(|coding| coding.trim().eq_ignore_ascii_case("chunked"));
189 let lengths: Vec<u64> = headers
190 .get_all(http::header::CONTENT_LENGTH)
191 .iter()
192 .filter_map(|value| value.to_str().ok())
193 .flat_map(|value| value.split(','))
194 .filter_map(|value| value.trim().parse::<u64>().ok())
195 .collect();
196 if chunked {
197 if !lengths.is_empty() {
198 return Err(HttpError::Protocol(
201 "both transfer-encoding and content-length",
202 ));
203 }
204 return Ok(BodyKind::Chunked);
205 }
206 if !lengths.is_empty() {
207 if lengths.iter().any(|len| *len != lengths[0]) {
208 return Err(HttpError::Protocol("conflicting content-length headers"));
209 }
210 return Ok(BodyKind::Length(lengths[0]));
211 }
212 Ok(BodyKind::UntilClose)
213 }
214
215 pub async fn read_body_chunk(&mut self) -> Result<Option<Bytes>, HttpError> {
219 loop {
220 match self.body {
221 BodyState::Head => return Err(HttpError::Protocol("head not read")),
222 BodyState::Empty | BodyState::Done => return Ok(None),
223 BodyState::Length { remaining } => {
224 if remaining == 0 {
225 self.body = BodyState::Done;
226 return Ok(None);
227 }
228 if self.buf.is_empty() {
229 if self.fill().await? == 0 {
230 return Err(HttpError::Incomplete("body truncated"));
231 }
232 continue;
233 }
234 let take = self
235 .buf
236 .len()
237 .min(usize::try_from(remaining).unwrap_or(usize::MAX));
238 let chunk = self.buf.split_to(take).freeze();
239 self.body = BodyState::Length {
240 remaining: remaining - take as u64,
241 };
242 return Ok(Some(chunk));
243 }
244 BodyState::Chunked {
245 remaining,
246 need_crlf,
247 } => {
248 if need_crlf {
249 let crlf = self.read_exact(2).await?;
250 if &crlf[..] != b"\r\n" {
251 return Err(HttpError::Protocol("chunk data not CRLF-terminated"));
252 }
253 self.body = BodyState::Chunked {
254 remaining,
255 need_crlf: false,
256 };
257 continue;
258 }
259 if remaining == 0 {
260 let size = self.read_chunk_size().await?;
261 if size == 0 {
262 self.read_trailers().await?;
263 self.body = BodyState::Done;
264 return Ok(None);
265 }
266 if size > self.limits.max_chunk_bytes {
267 return Err(HttpError::LimitExceeded("max_chunk_bytes"));
268 }
269 self.body = BodyState::Chunked {
270 remaining: size,
271 need_crlf: false,
272 };
273 continue;
274 }
275 if self.buf.is_empty() {
276 if self.fill().await? == 0 {
277 return Err(HttpError::Incomplete("chunk truncated"));
278 }
279 continue;
280 }
281 let take = self
282 .buf
283 .len()
284 .min(usize::try_from(remaining).unwrap_or(usize::MAX));
285 let chunk = self.buf.split_to(take).freeze();
286 let remaining = remaining - take as u64;
287 self.body = BodyState::Chunked {
288 remaining,
289 need_crlf: remaining == 0,
290 };
291 return Ok(Some(chunk));
292 }
293 BodyState::UntilClose => {
294 if !self.buf.is_empty() {
295 let chunk = self.buf.split().freeze();
296 return Ok(Some(chunk));
297 }
298 if self.fill().await? == 0 {
299 self.body = BodyState::Done;
300 return Ok(None);
301 }
302 }
303 }
304 }
305 }
306
307 pub async fn read_body_to_end(&mut self) -> Result<Bytes, HttpError> {
309 let mut out = BytesMut::new();
310 while let Some(chunk) = self.read_body_chunk().await? {
311 if out.len() as u64 + chunk.len() as u64 > self.limits.max_body_bytes {
312 return Err(HttpError::LimitExceeded("max_body_bytes"));
313 }
314 out.extend_from_slice(&chunk);
315 }
316 Ok(out.freeze())
317 }
318
319 async fn read_chunk_size(&mut self) -> Result<u64, HttpError> {
320 let line = self.read_line(8 * 1024).await?;
322 let digits = line
323 .iter()
324 .position(|&b| b == b';')
325 .map_or(&line[..], |semicolon| &line[..semicolon]);
326 let text = std::str::from_utf8(digits)
327 .map_err(|_| HttpError::Protocol("chunk size is not ASCII"))?
328 .trim();
329 if text.is_empty() || text.len() > 16 {
330 return Err(HttpError::Protocol("invalid chunk size"));
331 }
332 u64::from_str_radix(text, 16).map_err(|_| HttpError::Protocol("invalid chunk size"))
333 }
334
335 async fn read_trailers(&mut self) -> Result<(), HttpError> {
336 let mut count = 0usize;
337 let mut bytes = 0usize;
338 loop {
339 let line = self
340 .read_line(self.limits.max_head_bytes.saturating_sub(bytes))
341 .await?;
342 bytes = bytes.saturating_add(line.len() + 2);
343 if bytes > self.limits.max_head_bytes {
344 return Err(HttpError::LimitExceeded("max_head_bytes"));
345 }
346 if line.is_empty() {
347 return Ok(());
348 }
349 count += 1;
350 if count > self.limits.max_headers {
351 return Err(HttpError::LimitExceeded("max_headers"));
352 }
353 }
354 }
355
356 async fn read_line(&mut self, max: usize) -> Result<Bytes, HttpError> {
360 loop {
361 if let Some(position) = self.buf.iter().position(|&byte| byte == b'\n') {
362 let mut line = self.buf.split_to(position + 1);
363 line.truncate(position);
364 if line.last() == Some(&b'\r') {
365 line.truncate(position - 1);
366 }
367 return Ok(line.freeze());
368 }
369 if self.buf.len() >= max {
370 return Err(HttpError::LimitExceeded("max_head_bytes"));
371 }
372 if self.fill().await? == 0 {
373 return Err(HttpError::Incomplete("connection closed inside head"));
374 }
375 }
376 }
377
378 async fn read_exact(&mut self, n: usize) -> Result<Bytes, HttpError> {
380 while self.buf.len() < n {
381 if self.fill().await? == 0 {
382 return Err(HttpError::Protocol("body truncated"));
383 }
384 }
385 Ok(self.buf.split_to(n).freeze())
386 }
387
388 async fn fill(&mut self) -> Result<usize, HttpError> {
390 let read = self.io.read_buf(&mut self.buf).await?;
391 Ok(read)
392 }
393}
394
395fn trim_ows(mut value: &[u8]) -> &[u8] {
398 while matches!(value.first(), Some(b' ') | Some(b'\t')) {
399 value = &value[1..];
400 }
401 while matches!(value.last(), Some(b' ') | Some(b'\t')) {
402 value = &value[..value.len() - 1];
403 }
404 value
405}