sark_core/http/codec/decode/
head.rs1use 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}