Skip to main content

sark_core/http/codec/decode/
head.rs

1use http::{HeaderName, HeaderValue, StatusCode};
2
3use crate::error::{Error, Result};
4use crate::http::Response;
5use crate::http::codec::Header;
6
7const MAX_HEADERS: usize = 100;
8
9fn is_forbidden_trailer(name: &HeaderName) -> bool {
10    matches!(
11        name.as_str(),
12        "content-length"
13            | "transfer-encoding"
14            | "host"
15            | "trailer"
16            | "connection"
17            | "keep-alive"
18            | "te"
19            | "upgrade"
20            | "proxy-authenticate"
21            | "proxy-authorization"
22    )
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum DecodeMode {
27    Response,
28    Head,
29}
30
31impl DecodeMode {
32    const fn is_head(self) -> bool {
33        matches!(self, Self::Head)
34    }
35}
36
37pub(super) struct ParsedHead {
38    pub(super) status: StatusCode,
39    pub(super) headers: Vec<(HeaderName, HeaderValue)>,
40    pub(super) header_len: usize,
41    pub(super) content_length: Option<usize>,
42    pub(super) is_chunked: bool,
43}
44
45pub enum BodyKind {
46    NoBody,
47    ContentLength(usize),
48    Chunked,
49    UntilEof,
50}
51
52pub struct DecodedHead {
53    pub status: StatusCode,
54    pub headers: Vec<(HeaderName, HeaderValue)>,
55    pub header_len: usize,
56    pub body_kind: BodyKind,
57}
58
59impl crate::http::codec::Parse {
60    pub fn status_has_no_body(status: StatusCode) -> bool {
61        let code = status.as_u16();
62        code < 200 || code == 204 || code == 304
63    }
64
65    pub(super) fn parse(buf: &[u8]) -> Result<Option<ParsedHead>> {
66        let mut raw = [httparse::EMPTY_HEADER; MAX_HEADERS];
67        let mut parsed = httparse::Response::new(&mut raw);
68
69        match parsed.parse(buf)? {
70            httparse::Status::Partial => Ok(None),
71            httparse::Status::Complete(header_len) => {
72                let status_code = parsed
73                    .code
74                    .ok_or_else(|| Error::BadRequest("Missing status code".into()))?;
75                let status = StatusCode::from_u16(status_code)
76                    .map_err(|_| Error::BadRequest("Invalid status code".into()))?;
77
78                let mut headers = Vec::with_capacity(parsed.headers.len());
79                let mut content_length = None;
80                let mut is_chunked = false;
81                let mut has_transfer_encoding = false;
82
83                for h in parsed.headers.iter() {
84                    let name = HeaderName::from_bytes(h.name.as_bytes()).map_err(|_| {
85                        Error::BadRequest(format!("Invalid header name: {}", h.name).into())
86                    })?;
87                    let value = HeaderValue::from_bytes(h.value).map_err(|_| {
88                        Error::BadRequest(format!("Invalid header value for: {}", h.name).into())
89                    })?;
90
91                    if name == "content-length" {
92                        let len = Header::content_length(value.as_bytes())?;
93                        if let Some(existing) = content_length
94                            && existing != len
95                        {
96                            return Err(Error::BadRequest("Conflicting Content-Length".into()));
97                        }
98                        content_length = Some(len);
99                    }
100                    if name == "transfer-encoding" {
101                        let te = Self::parse_transfer_encoding_value(value.as_bytes())?;
102                        has_transfer_encoding = has_transfer_encoding || te.has_transfer_encoding;
103                        if te.is_chunked {
104                            is_chunked = true;
105                        }
106                    }
107                    headers.push((name, value));
108                }
109
110                if has_transfer_encoding && content_length.is_some() {
111                    return Err(Error::BadRequest(
112                        "Content-Length with Transfer-Encoding is not allowed".into(),
113                    ));
114                }
115
116                Ok(Some(ParsedHead {
117                    status,
118                    headers,
119                    header_len,
120                    content_length,
121                    is_chunked,
122                }))
123            }
124        }
125    }
126
127    pub(super) fn build_response(
128        head: ParsedHead,
129        body: &[u8],
130        trailers: &[(HeaderName, HeaderValue)],
131    ) -> Response {
132        let mut resp = Response::new(head.status);
133        for (name, value) in head.headers {
134            resp.headers_mut().insert(name, value);
135        }
136        for (name, value) in trailers {
137            if is_forbidden_trailer(name) {
138                continue;
139            }
140            resp.headers_mut().insert(name.clone(), value.clone());
141        }
142        if !body.is_empty() {
143            resp.set_body(body);
144        }
145        resp
146    }
147
148    pub fn head(buf: &[u8], mode: DecodeMode) -> Result<Option<DecodedHead>> {
149        let head = match Self::parse(buf)? {
150            Some(h) => h,
151            None => return Ok(None),
152        };
153
154        let body_kind = if mode.is_head() || Self::status_has_no_body(head.status) {
155            BodyKind::NoBody
156        } else if head.is_chunked {
157            BodyKind::Chunked
158        } else if let Some(len) = head.content_length {
159            BodyKind::ContentLength(len)
160        } else {
161            BodyKind::UntilEof
162        };
163
164        Ok(Some(DecodedHead {
165            status: head.status,
166            headers: head.headers,
167            header_len: head.header_len,
168            body_kind,
169        }))
170    }
171
172    pub fn response(buf: &[u8], mode: DecodeMode) -> Result<Option<Response>> {
173        let head = match Self::parse(buf)? {
174            Some(h) => h,
175            None => return Ok(None),
176        };
177
178        if mode.is_head() || Self::status_has_no_body(head.status) {
179            return Ok(Some(Self::build_response(head, &[], &[])));
180        }
181
182        let body_data = &buf[head.header_len..];
183
184        if head.is_chunked {
185            match Self::try_decode_chunked(body_data)? {
186                Some(result) => Ok(Some(Self::build_response(
187                    head,
188                    &result.body,
189                    &result.trailers,
190                ))),
191                None => Ok(None),
192            }
193        } else {
194            match head.content_length {
195                Some(len) if body_data.len() < len => Ok(None),
196                Some(len) => Ok(Some(Self::build_response(head, &body_data[..len], &[]))),
197                None => Ok(None),
198            }
199        }
200    }
201
202    pub fn response_after_eof(buf: &[u8]) -> Result<Response> {
203        let head = Self::parse(buf)?
204            .ok_or_else(|| Error::BadRequest("Incomplete HTTP response".into()))?;
205        let body_data = &buf[head.header_len..];
206
207        if head.is_chunked {
208            let result = Self::try_decode_chunked(body_data)?
209                .ok_or_else(|| Error::BadRequest("Incomplete chunked response".into()))?;
210            Ok(Self::build_response(head, &result.body, &result.trailers))
211        } else {
212            Ok(Self::build_response(head, body_data, &[]))
213        }
214    }
215}