Skip to main content

xitca_http/h1/proto/
encode.rs

1use crate::body::Body;
2use tracing::{error, warn};
3
4use crate::{
5    body::SizeHint,
6    bytes::{Bytes, BytesMut},
7    date::DateTime,
8    http::{
9        StatusCode,
10        header::{CONNECTION, CONTENT_LENGTH, DATE, HeaderMap, SET_COOKIE, TE, TRANSFER_ENCODING, UPGRADE},
11        response::Parts,
12    },
13};
14
15use super::{buf_write::H1BufWrite, context::Context, error::ProtoError, header, trasnder_coding::TransferCoding};
16
17pub const CONTINUE: &[u8; 25] = b"HTTP/1.1 100 Continue\r\n\r\n";
18
19#[allow(clippy::declare_interior_mutable_const)]
20pub const CONTINUE_BYTES: Bytes = Bytes::from_static(CONTINUE);
21
22impl<D, const MAX_HEADERS: usize> Context<'_, D, MAX_HEADERS>
23where
24    D: DateTime,
25{
26    pub fn encode_head<B, W>(&mut self, parts: Parts, body: &B, buf: &mut W) -> Result<TransferCoding, ProtoError>
27    where
28        B: Body,
29        W: H1BufWrite,
30    {
31        buf.write_buf_head(|buf| self.encode_head_inner(parts, body, buf))
32    }
33
34    fn encode_head_inner<B>(&mut self, parts: Parts, body: &B, buf: &mut BytesMut) -> Result<TransferCoding, ProtoError>
35    where
36        B: Body,
37    {
38        let Parts {
39            mut headers,
40            mut extensions,
41            version: _,
42            status,
43            ..
44        } = parts;
45
46        // decide if content-length or transfer-encoding header would be skipped.
47        let skip_ct_te = match status {
48            StatusCode::SWITCHING_PROTOCOLS => true,
49            // Sending content-length or transfer-encoding header on 2xx response
50            // to CONNECT is forbidden in RFC 7231.
51            s if self.is_connect_method() && s.is_success() => true,
52            s if s.is_informational() => {
53                error!("response with 1xx status code not supported");
54                return Err(ProtoError::Status);
55            }
56            _ => false,
57        };
58
59        self.encode_version_status_reason(buf, status);
60
61        let size = body.size_hint();
62
63        self.encode_headers(&mut headers, size, buf, skip_ct_te).inspect(|_| {
64            // put header map back to cache.
65            self.replace_headers(headers);
66
67            // put extension back to cache;
68            extensions.clear();
69            self.replace_extensions(extensions);
70        })
71    }
72
73    #[inline]
74    fn encode_version_status_reason(&self, buf: &mut BytesMut, status: StatusCode) {
75        // encode version, status code and reason
76        match (status, self.is_http10()) {
77            (StatusCode::OK, false) => {
78                buf.extend_from_slice(b"HTTP/1.1 200 OK");
79                return;
80            }
81            (_, false) => buf.extend_from_slice(b"HTTP/1.1 "),
82            (_, true) => buf.extend_from_slice(b"HTTP/1.0 "),
83        };
84
85        // a reason MUST be written, as many parsers will expect it.
86        let reason = status.canonical_reason().unwrap_or("<none>").as_bytes();
87        let status = status.as_str().as_bytes();
88        buf.reserve(status.len() + reason.len() + 1);
89        buf.extend_from_slice(status);
90        buf.extend_from_slice(b" ");
91        buf.extend_from_slice(reason);
92    }
93}
94
95impl<D, const MAX_HEADERS: usize> Context<'_, D, MAX_HEADERS>
96where
97    D: DateTime,
98{
99    pub fn encode_headers(
100        &mut self,
101        headers: &mut HeaderMap,
102        size: SizeHint,
103        buf: &mut BytesMut,
104        mut skip_ct_te: bool,
105    ) -> Result<TransferCoding, ProtoError> {
106        let mut skip_date = false;
107
108        // use the shortest header name as default
109        let mut name = TE;
110
111        let mut encoding = TransferCoding::eof();
112
113        for (next_name, value) in headers.drain() {
114            let mut is_multi_value = next_name
115                .map(|next_name| {
116                    name = next_name;
117                    false
118                })
119                .unwrap_or(true);
120
121            match name {
122                CONNECTION => {
123                    if self.is_connection_closed() {
124                        // skip write header on close condition.
125                        // the header is checked again and written properly afterwards.
126                        continue;
127                    }
128                    self.try_set_close_from_header(&value)?;
129                }
130                UPGRADE => encoding = TransferCoding::upgrade(),
131                DATE => skip_date = true,
132                CONTENT_LENGTH => {
133                    debug_assert!(!skip_ct_te, "CONTENT_LENGTH header can not be set");
134                    let value = header::parse_content_length(&value)?;
135                    encoding = TransferCoding::length(value);
136                    skip_ct_te = true;
137                }
138                TRANSFER_ENCODING => {
139                    debug_assert!(!skip_ct_te, "TRANSFER_ENCODING header can not be set");
140                    for val in value.to_str().map_err(|_| ProtoError::HeaderValue)?.split(',') {
141                        let val = val.trim();
142                        if val.eq_ignore_ascii_case("chunked") {
143                            encoding = TransferCoding::encode_chunked();
144                            skip_ct_te = true;
145                        }
146                    }
147                }
148                // multiple header lines for set-cookie header is allowed
149                // https://www.rfc-editor.org/rfc/rfc6265#section-3
150                SET_COOKIE => is_multi_value = false,
151                _ => {}
152            }
153
154            let value = value.as_bytes();
155
156            if is_multi_value {
157                buf.reserve(value.len() + 2);
158                buf.extend_from_slice(b", ");
159                buf.extend_from_slice(value);
160            } else {
161                let name = name.as_str().as_bytes();
162                buf.reserve(name.len() + value.len() + 4);
163                buf.extend_from_slice(b"\r\n");
164                buf.extend_from_slice(name);
165                buf.extend_from_slice(b": ");
166                buf.extend_from_slice(value);
167            }
168        }
169
170        // special handling for head method request by removing potential unwanted response body.
171        if self.is_head_method() {
172            try_remove_body(buf, skip_ct_te, size, &mut encoding);
173        // encode transfer-encoding or content-length if header map didn't provide them.
174        } else if !skip_ct_te {
175            encoding = match size {
176                SizeHint::None => TransferCoding::eof(),
177                SizeHint::Unknown => {
178                    buf.extend_from_slice(CHUNKED_HEADER);
179                    TransferCoding::encode_chunked()
180                }
181                SizeHint::Exact(size) => {
182                    write_length_header(buf, size);
183                    TransferCoding::length(size)
184                }
185            };
186        }
187
188        if self.is_connection_closed() {
189            buf.extend_from_slice(CLOSE_HEADER);
190        }
191
192        // set date header if there is not any.
193        if !skip_date {
194            buf.reserve(D::DATE_SIZE_HINT + 12);
195            buf.extend_from_slice(b"\r\ndate: ");
196            self.date().with_date(|slice| buf.extend_from_slice(slice));
197        }
198
199        buf.extend_from_slice(b"\r\n\r\n");
200
201        Ok(encoding)
202    }
203}
204
205const CHUNKED_HEADER: &[u8; 28] = b"\r\ntransfer-encoding: chunked";
206const CLOSE_HEADER: &[u8; 19] = b"\r\nconnection: close";
207
208#[cold]
209#[inline(never)]
210fn try_remove_body(buf: &mut BytesMut, skip_ct_te: bool, size: SizeHint, encoding: &mut TransferCoding) {
211    *encoding = TransferCoding::eof();
212
213    match size {
214        SizeHint::None => return,
215        SizeHint::Unknown if !skip_ct_te => {
216            buf.extend_from_slice(CHUNKED_HEADER);
217        }
218        SizeHint::Exact(size) if !skip_ct_te => {
219            write_length_header(buf, size);
220        }
221        _ => {}
222    }
223
224    warn!("response to HEAD request should not bearing body. It will been dropped without polling.");
225}
226
227pub(crate) fn write_length_header(buf: &mut BytesMut, size: u64) {
228    let mut buffer = itoa::Buffer::new();
229    let buffer = buffer.format(size).as_bytes();
230
231    buf.reserve(buffer.len() + 18);
232    buf.extend_from_slice(b"\r\ncontent-length: ");
233    buf.extend_from_slice(buffer);
234}
235
236#[cfg(test)]
237mod test {
238    use crate::{
239        body::{BoxBody, Full},
240        date::SystemTimeDateTimeHandler,
241        http::{HeaderValue, Response},
242    };
243
244    use super::*;
245
246    #[test]
247    fn append_header() {
248        let mut ctx = Context::<_, 64>::new(&SystemTimeDateTimeHandler);
249
250        let mut res = Response::new(BoxBody::new(Full::new(Bytes::new())));
251
252        res.headers_mut()
253            .insert(CONNECTION, HeaderValue::from_static("keep-alive"));
254        res.headers_mut()
255            .append(CONNECTION, HeaderValue::from_static("upgrade"));
256
257        let (parts, body) = res.into_parts();
258
259        let mut buf = BytesMut::new();
260        ctx.encode_head(parts, &body, &mut buf).unwrap();
261
262        let mut header = [httparse::EMPTY_HEADER; 8];
263        let mut res = httparse::Response::new(&mut header);
264
265        let httparse::Status::Complete(_) = res.parse(buf.as_ref()).unwrap() else {
266            panic!("failed to parse response")
267        };
268
269        for h in header {
270            if h.name == "connection" {
271                assert_eq!(h.value, b"keep-alive, upgrade");
272            }
273        }
274    }
275
276    #[test]
277    fn multi_set_cookie() {
278        let mut ctx = Context::<_, 64>::new(&SystemTimeDateTimeHandler);
279
280        let mut res = Response::new(BoxBody::new(Full::new(Bytes::new())));
281
282        res.headers_mut()
283            .insert(SET_COOKIE, HeaderValue::from_static("foo=foo"));
284        res.headers_mut()
285            .append(SET_COOKIE, HeaderValue::from_static("bar=bar"));
286
287        let (parts, body) = res.into_parts();
288
289        let mut buf = BytesMut::new();
290        ctx.encode_head(parts, &body, &mut buf).unwrap();
291
292        let mut header = [httparse::EMPTY_HEADER; 8];
293        let mut res = httparse::Response::new(&mut header);
294
295        let httparse::Status::Complete(_) = res.parse(buf.as_ref()).unwrap() else {
296            panic!("failed to parse response")
297        };
298
299        assert_eq!(header[0].name, "set-cookie");
300        assert_eq!(header[0].value, b"foo=foo");
301        assert_eq!(header[1].name, "set-cookie");
302        assert_eq!(header[1].value, b"bar=bar");
303    }
304}