Skip to main content

hyper/proto/h1/
encode.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::io::IoSlice;
4
5use bytes::buf::{Chain, Take};
6use bytes::{Buf, Bytes};
7use http::{
8    header::{
9        AUTHORIZATION, CACHE_CONTROL, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_RANGE,
10        CONTENT_TYPE, HOST, MAX_FORWARDS, SET_COOKIE, TE, TRAILER, TRANSFER_ENCODING,
11    },
12    HeaderMap, HeaderName,
13};
14
15use super::io::WriteBuf;
16use super::role::{write_headers, write_headers_title_case};
17
18type StaticBuf = &'static [u8];
19
20/// Encoders to handle different Transfer-Encodings.
21#[derive(Debug, Clone, PartialEq)]
22pub(crate) struct Encoder {
23    kind: Kind,
24    is_last: bool,
25}
26
27#[derive(Debug)]
28pub(crate) struct EncodedBuf<B> {
29    kind: BufKind<B>,
30}
31
32#[derive(Debug)]
33pub(crate) struct NotEof(u64);
34
35#[derive(Debug, PartialEq, Clone)]
36enum Kind {
37    /// An Encoder for when Transfer-Encoding includes `chunked`.
38    Chunked(Option<Vec<HeaderName>>),
39    /// An Encoder for when Content-Length is set.
40    ///
41    /// Enforces that the body is not longer than the Content-Length header.
42    Length(u64),
43    /// An Encoder for when neither Content-Length nor Chunked encoding is set.
44    ///
45    /// This is mostly only used with HTTP/1.0 with a length. This kind requires
46    /// the connection to be closed when the body is finished.
47    #[cfg(feature = "server")]
48    CloseDelimited,
49}
50
51#[derive(Debug)]
52enum BufKind<B> {
53    Exact(B),
54    Limited(Take<B>),
55    Chunked(Chain<Chain<ChunkSize, B>, StaticBuf>),
56    ChunkedEnd(StaticBuf),
57    Trailers(Chain<Chain<StaticBuf, Bytes>, StaticBuf>),
58}
59
60impl Encoder {
61    fn new(kind: Kind) -> Encoder {
62        Encoder {
63            kind,
64            is_last: false,
65        }
66    }
67    pub(crate) fn chunked() -> Encoder {
68        Encoder::new(Kind::Chunked(None))
69    }
70
71    pub(crate) fn length(len: u64) -> Encoder {
72        Encoder::new(Kind::Length(len))
73    }
74
75    #[cfg(feature = "server")]
76    pub(crate) fn close_delimited() -> Encoder {
77        Encoder::new(Kind::CloseDelimited)
78    }
79
80    pub(crate) fn into_chunked_with_trailing_fields(self, trailers: Vec<HeaderName>) -> Encoder {
81        match self.kind {
82            Kind::Chunked(_) => Encoder {
83                kind: Kind::Chunked(Some(trailers)),
84                is_last: self.is_last,
85            },
86            _ => self,
87        }
88    }
89
90    pub(crate) fn is_eof(&self) -> bool {
91        matches!(self.kind, Kind::Length(0))
92    }
93
94    #[cfg(feature = "server")]
95    pub(crate) fn set_last(mut self, is_last: bool) -> Self {
96        self.is_last = is_last;
97        self
98    }
99
100    pub(crate) fn is_last(&self) -> bool {
101        self.is_last
102    }
103
104    pub(crate) fn is_close_delimited(&self) -> bool {
105        match self.kind {
106            #[cfg(feature = "server")]
107            Kind::CloseDelimited => true,
108            _ => false,
109        }
110    }
111
112    pub(crate) fn is_chunked(&self) -> bool {
113        matches!(self.kind, Kind::Chunked(_))
114    }
115
116    pub(crate) fn end<B>(&self) -> Result<Option<EncodedBuf<B>>, NotEof> {
117        match self.kind {
118            Kind::Length(0) => Ok(None),
119            Kind::Chunked(_) => Ok(Some(EncodedBuf {
120                kind: BufKind::ChunkedEnd(b"0\r\n\r\n"),
121            })),
122            #[cfg(feature = "server")]
123            Kind::CloseDelimited => Ok(None),
124            Kind::Length(n) => Err(NotEof(n)),
125        }
126    }
127
128    pub(crate) fn encode<B>(&mut self, msg: B) -> EncodedBuf<B>
129    where
130        B: Buf,
131    {
132        let len = msg.remaining();
133        debug_assert!(len > 0, "encode() called with empty buf");
134
135        let kind = match &mut self.kind {
136            Kind::Chunked(_) => {
137                trace!("encoding chunked {}B", len);
138                let buf = ChunkSize::new(len)
139                    .chain(msg)
140                    .chain(b"\r\n" as &'static [u8]);
141                BufKind::Chunked(buf)
142            }
143            Kind::Length(remaining) => {
144                trace!("sized write, len = {}", len);
145                match usize::try_from(*remaining) {
146                    // Holding more than is owed, so write only what is left.
147                    Ok(limit) if limit < len => {
148                        *remaining = 0;
149                        BufKind::Limited(msg.take(limit))
150                    }
151                    // Ok(_) => Owed at least what we hold, write all of it.
152                    // Err(_) => Owed more than `usize` can represent, write all of it.
153                    Ok(_) | Err(_) => {
154                        *remaining -= len as u64;
155                        BufKind::Exact(msg)
156                    }
157                }
158            }
159            #[cfg(feature = "server")]
160            Kind::CloseDelimited => {
161                trace!("close delimited write {}B", len);
162                BufKind::Exact(msg)
163            }
164        };
165        EncodedBuf { kind }
166    }
167
168    pub(crate) fn encode_trailers<B>(
169        &self,
170        trailers: HeaderMap,
171        title_case_headers: bool,
172    ) -> Option<EncodedBuf<B>> {
173        trace!("encoding trailers");
174        match &self.kind {
175            Kind::Chunked(Some(allowed_trailer_fields)) => {
176                let allowed_set: HashSet<&HeaderName> = allowed_trailer_fields.iter().collect();
177
178                let mut cur_name = None;
179                let mut allowed_trailers = HeaderMap::new();
180
181                for (opt_name, value) in trailers {
182                    if let Some(n) = opt_name {
183                        cur_name = Some(n);
184                    }
185                    let name = cur_name.as_ref().expect("current header name");
186
187                    if allowed_set.contains(name) {
188                        if is_valid_trailer_field(name) {
189                            allowed_trailers.append(name, value);
190                        } else {
191                            debug!("trailer field is not valid: {}", &name);
192                        }
193                    } else {
194                        debug!("trailer header name not found in trailer header: {}", &name);
195                    }
196                }
197
198                let mut buf = Vec::new();
199                if title_case_headers {
200                    write_headers_title_case(&allowed_trailers, &mut buf);
201                } else {
202                    write_headers(&allowed_trailers, &mut buf);
203                }
204
205                if buf.is_empty() {
206                    return None;
207                }
208
209                Some(EncodedBuf {
210                    kind: BufKind::Trailers(b"0\r\n".chain(Bytes::from(buf)).chain(b"\r\n")),
211                })
212            }
213            Kind::Chunked(None) => {
214                debug!("attempted to encode trailers, but the trailer header is not set");
215                None
216            }
217            _ => {
218                debug!("attempted to encode trailers for non-chunked response");
219                None
220            }
221        }
222    }
223
224    pub(super) fn encode_and_end<B>(&self, msg: B, dst: &mut WriteBuf<EncodedBuf<B>>) -> bool
225    where
226        B: Buf,
227    {
228        let len = msg.remaining();
229        debug_assert!(len > 0, "encode() called with empty buf");
230
231        match self.kind {
232            Kind::Chunked(_) => {
233                trace!("encoding chunked {}B", len);
234                let buf = ChunkSize::new(len)
235                    .chain(msg)
236                    .chain(b"\r\n0\r\n\r\n" as &'static [u8]);
237                dst.buffer(buf);
238                !self.is_last
239            }
240            Kind::Length(remaining) => {
241                use std::cmp::Ordering;
242
243                trace!("sized write, len = {}", len);
244                match (len as u64).cmp(&remaining) {
245                    Ordering::Equal => {
246                        dst.buffer(msg);
247                        !self.is_last
248                    }
249                    #[allow(clippy::cast_possible_truncation)]
250                    Ordering::Greater => {
251                        dst.buffer(msg.take(remaining as usize));
252                        !self.is_last
253                    }
254                    Ordering::Less => {
255                        dst.buffer(msg);
256                        false
257                    }
258                }
259            }
260            #[cfg(feature = "server")]
261            Kind::CloseDelimited => {
262                trace!("close delimited write {}B", len);
263                dst.buffer(msg);
264                false
265            }
266        }
267    }
268}
269
270fn is_valid_trailer_field(name: &HeaderName) -> bool {
271    !matches!(
272        *name,
273        AUTHORIZATION
274            | CACHE_CONTROL
275            | CONTENT_ENCODING
276            | CONTENT_LENGTH
277            | CONTENT_RANGE
278            | CONTENT_TYPE
279            | HOST
280            | MAX_FORWARDS
281            | SET_COOKIE
282            | TRAILER
283            | TRANSFER_ENCODING
284            | TE
285    )
286}
287
288impl<B> Buf for EncodedBuf<B>
289where
290    B: Buf,
291{
292    #[inline]
293    fn remaining(&self) -> usize {
294        match &self.kind {
295            BufKind::Exact(b) => b.remaining(),
296            BufKind::Limited(b) => b.remaining(),
297            BufKind::Chunked(b) => b.remaining(),
298            BufKind::ChunkedEnd(b) => b.remaining(),
299            BufKind::Trailers(b) => b.remaining(),
300        }
301    }
302
303    #[inline]
304    fn chunk(&self) -> &[u8] {
305        match &self.kind {
306            BufKind::Exact(b) => b.chunk(),
307            BufKind::Limited(b) => b.chunk(),
308            BufKind::Chunked(b) => b.chunk(),
309            BufKind::ChunkedEnd(b) => b.chunk(),
310            BufKind::Trailers(b) => b.chunk(),
311        }
312    }
313
314    #[inline]
315    fn advance(&mut self, cnt: usize) {
316        match &mut self.kind {
317            BufKind::Exact(b) => b.advance(cnt),
318            BufKind::Limited(b) => b.advance(cnt),
319            BufKind::Chunked(b) => b.advance(cnt),
320            BufKind::ChunkedEnd(b) => b.advance(cnt),
321            BufKind::Trailers(b) => b.advance(cnt),
322        }
323    }
324
325    #[inline]
326    fn chunks_vectored<'data>(&'data self, dst: &mut [IoSlice<'data>]) -> usize {
327        match &self.kind {
328            BufKind::Exact(b) => b.chunks_vectored(dst),
329            BufKind::Limited(b) => b.chunks_vectored(dst),
330            BufKind::Chunked(b) => b.chunks_vectored(dst),
331            BufKind::ChunkedEnd(b) => b.chunks_vectored(dst),
332            BufKind::Trailers(b) => b.chunks_vectored(dst),
333        }
334    }
335}
336
337const USIZE_BYTES: usize = std::mem::size_of::<usize>();
338
339// each byte will become 2 hex
340const CHUNK_SIZE_MAX_BYTES: usize = USIZE_BYTES * 2;
341
342#[derive(Clone, Copy)]
343struct ChunkSize {
344    bytes: [u8; CHUNK_SIZE_MAX_BYTES + 2],
345    pos: u8,
346    len: u8,
347}
348
349impl ChunkSize {
350    fn new(len: usize) -> ChunkSize {
351        use std::fmt::Write;
352        let mut size = ChunkSize {
353            bytes: [0; CHUNK_SIZE_MAX_BYTES + 2],
354            pos: 0,
355            len: 0,
356        };
357        write!(&mut size, "{len:X}\r\n").expect("CHUNK_SIZE_MAX_BYTES should fit any usize");
358        size
359    }
360}
361
362impl Buf for ChunkSize {
363    #[inline]
364    fn remaining(&self) -> usize {
365        (self.len - self.pos).into()
366    }
367
368    #[inline]
369    fn chunk(&self) -> &[u8] {
370        &self.bytes[self.pos.into()..self.len.into()]
371    }
372
373    #[inline]
374    #[allow(clippy::cast_possible_truncation)]
375    fn advance(&mut self, cnt: usize) {
376        assert!(cnt <= self.remaining());
377        self.pos += cnt as u8; // just asserted cnt fits in u8
378    }
379}
380
381impl fmt::Debug for ChunkSize {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        f.debug_struct("ChunkSize")
384            .field("bytes", &&self.bytes[..self.len.into()])
385            .field("pos", &self.pos)
386            .finish()
387    }
388}
389
390impl fmt::Write for ChunkSize {
391    #[allow(clippy::cast_possible_truncation)]
392    fn write_str(&mut self, num: &str) -> fmt::Result {
393        use std::io::Write;
394        (&mut self.bytes[self.len.into()..])
395            .write_all(num.as_bytes())
396            .expect("&mut [u8].write() cannot error");
397        debug_assert!(u8::try_from(num.len()).is_ok());
398        self.len += num.len() as u8; // safe because bytes is never bigger than 255
399        Ok(())
400    }
401}
402
403impl<B: Buf> From<B> for EncodedBuf<B> {
404    fn from(buf: B) -> Self {
405        EncodedBuf {
406            kind: BufKind::Exact(buf),
407        }
408    }
409}
410
411impl<B: Buf> From<Take<B>> for EncodedBuf<B> {
412    fn from(buf: Take<B>) -> Self {
413        EncodedBuf {
414            kind: BufKind::Limited(buf),
415        }
416    }
417}
418
419impl<B: Buf> From<Chain<Chain<ChunkSize, B>, StaticBuf>> for EncodedBuf<B> {
420    fn from(buf: Chain<Chain<ChunkSize, B>, StaticBuf>) -> Self {
421        EncodedBuf {
422            kind: BufKind::Chunked(buf),
423        }
424    }
425}
426
427impl fmt::Display for NotEof {
428    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429        write!(f, "early end, expected {} more bytes", self.0)
430    }
431}
432
433impl std::error::Error for NotEof {}
434
435#[cfg(test)]
436mod tests {
437    use bytes::BufMut;
438    use http::{
439        header::{
440            AUTHORIZATION, CACHE_CONTROL, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_RANGE,
441            CONTENT_TYPE, HOST, MAX_FORWARDS, SET_COOKIE, TE, TRAILER, TRANSFER_ENCODING,
442        },
443        HeaderMap, HeaderName, HeaderValue,
444    };
445
446    use super::super::io::Cursor;
447    use super::Encoder;
448
449    #[test]
450    fn chunked() {
451        let mut encoder = Encoder::chunked();
452        let mut dst = Vec::new();
453
454        let msg1 = b"foo bar".as_ref();
455        let buf1 = encoder.encode(msg1);
456        dst.put(buf1);
457        assert_eq!(dst, b"7\r\nfoo bar\r\n");
458
459        let msg2 = b"baz quux herp".as_ref();
460        let buf2 = encoder.encode(msg2);
461        dst.put(buf2);
462
463        assert_eq!(dst, b"7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n");
464
465        let end = encoder.end::<Cursor<Vec<u8>>>().unwrap().unwrap();
466        dst.put(end);
467
468        assert_eq!(
469            dst,
470            b"7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n0\r\n\r\n".as_ref()
471        );
472    }
473
474    #[test]
475    fn length() {
476        let max_len = 8;
477        let mut encoder = Encoder::length(max_len as u64);
478        let mut dst = Vec::new();
479
480        let msg1 = b"foo bar".as_ref();
481        let buf1 = encoder.encode(msg1);
482        dst.put(buf1);
483
484        assert_eq!(dst, b"foo bar");
485        assert!(!encoder.is_eof());
486        encoder.end::<()>().unwrap_err();
487
488        let msg2 = b"baz".as_ref();
489        let buf2 = encoder.encode(msg2);
490        dst.put(buf2);
491
492        assert_eq!(dst.len(), max_len);
493        assert_eq!(dst, b"foo barb");
494        assert!(encoder.is_eof());
495        assert!(encoder.end::<()>().unwrap().is_none());
496    }
497
498    #[cfg(feature = "server")]
499    #[test]
500    fn eof() {
501        let mut encoder = Encoder::close_delimited();
502        let mut dst = Vec::new();
503
504        let msg1 = b"foo bar".as_ref();
505        let buf1 = encoder.encode(msg1);
506        dst.put(buf1);
507
508        assert_eq!(dst, b"foo bar");
509        assert!(!encoder.is_eof());
510        encoder.end::<()>().unwrap();
511
512        let msg2 = b"baz".as_ref();
513        let buf2 = encoder.encode(msg2);
514        dst.put(buf2);
515
516        assert_eq!(dst, b"foo barbaz");
517        assert!(!encoder.is_eof());
518        encoder.end::<()>().unwrap();
519    }
520
521    #[test]
522    fn chunked_with_valid_trailers() {
523        let encoder = Encoder::chunked();
524        let trailers = vec![HeaderName::from_static("chunky-trailer")];
525        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
526
527        let headers = HeaderMap::from_iter(vec![
528            (
529                HeaderName::from_static("chunky-trailer"),
530                HeaderValue::from_static("header data"),
531            ),
532            (
533                HeaderName::from_static("should-not-be-included"),
534                HeaderValue::from_static("oops"),
535            ),
536        ]);
537
538        let buf1 = encoder.encode_trailers::<&[u8]>(headers, false).unwrap();
539
540        let mut dst = Vec::new();
541        dst.put(buf1);
542        assert_eq!(dst, b"0\r\nchunky-trailer: header data\r\n\r\n");
543    }
544
545    #[test]
546    fn chunked_with_multiple_trailer_headers() {
547        let encoder = Encoder::chunked();
548        let trailers = vec![
549            HeaderName::from_static("chunky-trailer"),
550            HeaderName::from_static("chunky-trailer-2"),
551        ];
552        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
553
554        let headers = HeaderMap::from_iter(vec![
555            (
556                HeaderName::from_static("chunky-trailer"),
557                HeaderValue::from_static("header data"),
558            ),
559            (
560                HeaderName::from_static("chunky-trailer-2"),
561                HeaderValue::from_static("more header data"),
562            ),
563        ]);
564
565        let buf1 = encoder.encode_trailers::<&[u8]>(headers, false).unwrap();
566
567        let mut dst = Vec::new();
568        dst.put(buf1);
569        assert_eq!(
570            dst,
571            b"0\r\nchunky-trailer: header data\r\nchunky-trailer-2: more header data\r\n\r\n"
572        );
573    }
574
575    #[test]
576    fn chunked_with_duplicate_trailer_values() {
577        let encoder = Encoder::chunked();
578        let trailers = vec![HeaderName::from_static("chunky-trailer")];
579        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
580
581        let mut headers = HeaderMap::new();
582        headers.append(
583            HeaderName::from_static("chunky-trailer"),
584            HeaderValue::from_static("first"),
585        );
586        headers.append(
587            HeaderName::from_static("chunky-trailer"),
588            HeaderValue::from_static("second"),
589        );
590
591        let buf1 = encoder.encode_trailers::<&[u8]>(headers, false).unwrap();
592
593        let mut dst = Vec::new();
594        dst.put(buf1);
595        assert_eq!(
596            dst,
597            b"0\r\nchunky-trailer: first\r\nchunky-trailer: second\r\n\r\n"
598        );
599    }
600
601    #[test]
602    fn chunked_with_no_trailer_header() {
603        let encoder = Encoder::chunked();
604
605        let headers = HeaderMap::from_iter(vec![(
606            HeaderName::from_static("chunky-trailer"),
607            HeaderValue::from_static("header data"),
608        )]);
609
610        assert!(encoder
611            .encode_trailers::<&[u8]>(headers.clone(), false)
612            .is_none());
613
614        let trailers = vec![];
615        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
616
617        assert!(encoder.encode_trailers::<&[u8]>(headers, false).is_none());
618    }
619
620    #[test]
621    fn chunked_with_invalid_trailers() {
622        let encoder = Encoder::chunked();
623
624        let trailers = vec![
625            AUTHORIZATION,
626            CACHE_CONTROL,
627            CONTENT_ENCODING,
628            CONTENT_LENGTH,
629            CONTENT_RANGE,
630            CONTENT_TYPE,
631            HOST,
632            MAX_FORWARDS,
633            SET_COOKIE,
634            TRAILER,
635            TRANSFER_ENCODING,
636            TE,
637        ];
638        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
639
640        let mut headers = HeaderMap::new();
641        headers.insert(AUTHORIZATION, HeaderValue::from_static("header data"));
642        headers.insert(CACHE_CONTROL, HeaderValue::from_static("header data"));
643        headers.insert(CONTENT_ENCODING, HeaderValue::from_static("header data"));
644        headers.insert(CONTENT_LENGTH, HeaderValue::from_static("header data"));
645        headers.insert(CONTENT_RANGE, HeaderValue::from_static("header data"));
646        headers.insert(CONTENT_TYPE, HeaderValue::from_static("header data"));
647        headers.insert(HOST, HeaderValue::from_static("header data"));
648        headers.insert(MAX_FORWARDS, HeaderValue::from_static("header data"));
649        headers.insert(SET_COOKIE, HeaderValue::from_static("header data"));
650        headers.insert(TRAILER, HeaderValue::from_static("header data"));
651        headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("header data"));
652        headers.insert(TE, HeaderValue::from_static("header data"));
653
654        assert!(encoder.encode_trailers::<&[u8]>(headers, true).is_none());
655    }
656
657    #[test]
658    fn chunked_with_title_case_headers() {
659        let encoder = Encoder::chunked();
660        let trailers = vec![HeaderName::from_static("chunky-trailer")];
661        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
662
663        let headers = HeaderMap::from_iter(vec![(
664            HeaderName::from_static("chunky-trailer"),
665            HeaderValue::from_static("header data"),
666        )]);
667        let buf1 = encoder.encode_trailers::<&[u8]>(headers, true).unwrap();
668
669        let mut dst = Vec::new();
670        dst.put(buf1);
671        assert_eq!(dst, b"0\r\nChunky-Trailer: header data\r\n\r\n");
672    }
673
674    #[test]
675    fn chunked_trailers_case_insensitive_matching() {
676        // Regression test for issue #4010: HTTP/1.1 trailers are case-sensitive
677        //
678        // Previously, the Trailer header values were stored as HeaderValue (preserving case)
679        // and compared against HeaderName (which is always lowercase). This caused trailers
680        // declared as "Chunky-Trailer" to not match actual trailers sent as "chunky-trailer".
681        //
682        // The fix converts Trailer header values to HeaderName during parsing, which
683        // normalizes the case and enables proper case-insensitive matching.
684        //
685        // Note: HeaderName::from_static() requires lowercase input. In real usage,
686        // HeaderName::from_bytes() is used to parse the Trailer header value, which
687        // normalizes mixed-case input like "Chunky-Trailer" to "chunky-trailer".
688        let encoder = Encoder::chunked();
689        let trailers = vec![HeaderName::from_static("chunky-trailer")];
690        let encoder = encoder.into_chunked_with_trailing_fields(trailers);
691
692        // The actual trailer being sent
693        let headers = HeaderMap::from_iter(vec![(
694            HeaderName::from_static("chunky-trailer"),
695            HeaderValue::from_static("trailer value"),
696        )]);
697
698        let buf = encoder.encode_trailers::<&[u8]>(headers, false).unwrap();
699        let mut dst = Vec::new();
700        dst.put(buf);
701        assert_eq!(dst, b"0\r\nchunky-trailer: trailer value\r\n\r\n");
702    }
703}