khttp/parser/
response.rs

1use super::{HttpParsingError, HttpParsingError::*, parse_headers, parse_version};
2use crate::{Headers, Status};
3
4#[derive(Debug)]
5pub struct Response<'b> {
6    pub http_version: u8,
7    pub status: Status<'b>,
8    pub headers: Headers<'b>,
9    pub buf_offset: usize,
10}
11
12impl<'b> Response<'b> {
13    pub fn parse(buf: &'b [u8]) -> Result<Response<'b>, HttpParsingError> {
14        let start = buf.len();
15        let (http_version, rest) = parse_version(buf)?;
16        let rest = rest.get(1..).ok_or(MalformedStatusLine)?; // skip single SP
17        let (status, rest) = parse_response_status(rest)?;
18        let (headers, rest) = parse_headers(rest)?;
19
20        Ok(Response {
21            http_version,
22            status,
23            headers,
24            buf_offset: start - rest.len(),
25        })
26    }
27}
28
29#[inline]
30fn parse_response_status(buf: &[u8]) -> Result<(Status<'_>, &[u8]), HttpParsingError> {
31    let code = parse_response_status_code(buf)?;
32    // check SP
33    if buf.get(3).ok_or(MalformedStatusLine)? != &b' ' {
34        return Err(MalformedStatusLine);
35    }
36
37    let buf = buf.get(4..).ok_or(MalformedStatusLine)?;
38    let mut i = 0;
39    while i + 1 < buf.len() {
40        let c = buf[i];
41        if c == b'\r' && buf[i + 1] == b'\n' {
42            // safety: we just validated that all chars in buf[..i] are utf8
43            let reason = unsafe { std::str::from_utf8_unchecked(&buf[..i]) };
44            let rest = buf
45                .get(i + 2..) // skip \r\n
46                .ok_or(MalformedStatusLine)?;
47            return Ok((Status::borrowed(code, reason), rest));
48        }
49        if !(c == b'\t' || c == b' ' || (0x21..=0x7E).contains(&c)) {
50            // NB! extended Latin-1 is not allowed because not utf-8
51            return Err(MalformedStatusLine);
52        }
53        i += 1;
54    }
55    Err(MalformedStatusLine)
56}
57
58#[inline]
59fn parse_response_status_code(buf: &[u8]) -> Result<u16, HttpParsingError> {
60    let hundreds = match buf.first().ok_or(MalformedStatusLine)? {
61        x if (*x >= b'0' && *x <= b'9') => *x,
62        _ => return Err(MalformedStatusLine),
63    };
64    let tens = match buf.get(1).ok_or(MalformedStatusLine)? {
65        x if (*x >= b'0' && *x <= b'9') => *x,
66        _ => return Err(MalformedStatusLine),
67    };
68    let ones = match buf.get(2).ok_or(MalformedStatusLine)? {
69        x if (*x >= b'0' && *x <= b'9') => *x,
70        _ => return Err(MalformedStatusLine),
71    };
72
73    Ok((hundreds - b'0') as u16 * 100 + (tens - b'0') as u16 * 10 + (ones - b'0') as u16)
74}