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 let skip_ct_te = match status {
48 StatusCode::SWITCHING_PROTOCOLS => true,
49 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 self.replace_headers(headers);
66
67 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 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 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 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 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 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 if self.is_head_method() {
172 try_remove_body(buf, skip_ct_te, size, &mut encoding);
173 } 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 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}