Skip to main content

sark_core/http/codec/decode/
headers.rs

1use std::ops::ControlFlow;
2
3use crate::error::{Error, Result};
4use crate::http::codec::Header;
5use crate::http::codec::header_utils::CsvScanMode;
6
7#[derive(Clone, Copy)]
8pub(super) struct TransferEncodingInfo {
9    pub(super) has_transfer_encoding: bool,
10    pub(super) is_chunked: bool,
11}
12
13#[derive(Clone, Copy, Debug, Default)]
14pub struct HeaderScan {
15    pub content_length: Option<usize>,
16    pub has_transfer_encoding: bool,
17    pub is_chunked_transfer: bool,
18    pub has_expect: bool,
19    pub expect_continue: bool,
20    pub duplicate_content_length: bool,
21    pub accept_encoding_gzip: bool,
22}
23
24impl crate::http::codec::Parse {
25    pub(super) fn parse_transfer_encoding_value(value: &[u8]) -> Result<TransferEncodingInfo> {
26        let mut saw_any = false;
27        let mut saw_chunked = false;
28        let mut last_is_chunked = false;
29        let mut invalid_encoding = false;
30        let mut invalid_ordering = false;
31
32        Header::scan_csv_tokens(
33            value,
34            CsvScanMode::Strict,
35            |token: &[u8]| -> ControlFlow<()> {
36                if !token.is_ascii() {
37                    invalid_encoding = true;
38                    return ControlFlow::Break(());
39                }
40                saw_any = true;
41                let is_chunked = token.eq_ignore_ascii_case(b"chunked");
42                if saw_chunked && !last_is_chunked {
43                    invalid_ordering = true;
44                    return ControlFlow::Break(());
45                }
46                if saw_chunked && !is_chunked {
47                    invalid_ordering = true;
48                    return ControlFlow::Break(());
49                }
50                if is_chunked {
51                    saw_chunked = true;
52                }
53                last_is_chunked = is_chunked;
54                ControlFlow::Continue(())
55            },
56        )
57        .and_then(|_| {
58            if invalid_encoding {
59                return Err(Error::BadRequest("Invalid Transfer-Encoding".into()));
60            }
61            if invalid_ordering {
62                return Err(Error::BadRequest(
63                    "Invalid Transfer-Encoding ordering".into(),
64                ));
65            }
66            if saw_any {
67                Ok(())
68            } else {
69                Err(Error::BadRequest("Invalid Transfer-Encoding".into()))
70            }
71        })?;
72        if saw_chunked && !last_is_chunked {
73            return Err(Error::BadRequest(
74                "Invalid Transfer-Encoding ordering".into(),
75            ));
76        }
77
78        Ok(TransferEncodingInfo {
79            has_transfer_encoding: saw_any,
80            is_chunked: saw_chunked && last_is_chunked,
81        })
82    }
83
84    pub fn header_scan(headers: &[httparse::Header<'_>]) -> Result<HeaderScan> {
85        let mut content_length: Option<usize> = None;
86        let mut saw_content_length = false;
87        let mut has_transfer_encoding = false;
88        let mut is_chunked_transfer = false;
89        let mut has_expect = false;
90        let mut expect_continue = false;
91        let mut duplicate_content_length = false;
92
93        for h in headers.iter().filter(|h| !h.name.is_empty()) {
94            if h.name.eq_ignore_ascii_case("content-length") {
95                let len = Header::content_length(h.value)?;
96                if saw_content_length {
97                    if content_length != Some(len) {
98                        return Err(Error::BadRequest(
99                            "Multiple Content-Length headers are not allowed".into(),
100                        ));
101                    }
102                    duplicate_content_length = true;
103                }
104                saw_content_length = true;
105                content_length = Some(len);
106                continue;
107            }
108
109            if h.name.eq_ignore_ascii_case("transfer-encoding") {
110                let te = Self::parse_transfer_encoding_value(h.value)?;
111                has_transfer_encoding = has_transfer_encoding || te.has_transfer_encoding;
112                if te.is_chunked {
113                    is_chunked_transfer = true;
114                }
115                continue;
116            }
117
118            if h.name.eq_ignore_ascii_case("expect") {
119                has_expect = true;
120                if Header::trimmed_eq_ascii_case(h.value, b"100-continue") {
121                    expect_continue = true;
122                }
123            }
124        }
125
126        Ok(HeaderScan {
127            content_length,
128            has_transfer_encoding,
129            is_chunked_transfer,
130            has_expect,
131            expect_continue,
132            duplicate_content_length,
133            accept_encoding_gzip: false,
134        })
135    }
136
137    pub fn content_length(headers: &[httparse::Header<'_>]) -> Result<Option<usize>> {
138        for h in headers {
139            if h.name.eq_ignore_ascii_case("content-length") {
140                return Ok(Some(Header::content_length(h.value)?));
141            }
142        }
143        Ok(None)
144    }
145
146    pub fn is_chunked(headers: &[httparse::Header<'_>]) -> bool {
147        Header::has_name(headers, "transfer-encoding")
148            && headers.iter().any(|h| {
149                h.name.eq_ignore_ascii_case("transfer-encoding")
150                    && Header::has_token(h.value, b"chunked")
151            })
152    }
153}