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
24#[derive(Clone, Copy, PartialEq, Eq, Debug)]
25pub enum BodyFraming {
26    Length(usize),
27    Chunked,
28}
29
30impl HeaderScan {
31    pub(crate) fn validate_request_framing_pair(&self) -> Result<()> {
32        if self.has_transfer_encoding && self.content_length.is_some() {
33            return Err(Error::BadRequest(
34                "Content-Length with Transfer-Encoding is not allowed".into(),
35            ));
36        }
37        Ok(())
38    }
39
40    pub(super) fn transfer_encoding(value: &[u8]) -> Result<TransferEncodingInfo> {
41        let mut saw_any = false;
42        let mut saw_chunked = false;
43        let mut last_is_chunked = false;
44        let mut invalid_encoding = false;
45        let mut invalid_ordering = false;
46
47        Header::scan_csv_tokens(
48            value,
49            CsvScanMode::Strict,
50            |token: &[u8]| -> ControlFlow<()> {
51                if !token.is_ascii() {
52                    invalid_encoding = true;
53                    return ControlFlow::Break(());
54                }
55                saw_any = true;
56                let is_chunked = token.eq_ignore_ascii_case(b"chunked");
57                if saw_chunked && !last_is_chunked {
58                    invalid_ordering = true;
59                    return ControlFlow::Break(());
60                }
61                if saw_chunked && !is_chunked {
62                    invalid_ordering = true;
63                    return ControlFlow::Break(());
64                }
65                if is_chunked {
66                    saw_chunked = true;
67                }
68                last_is_chunked = is_chunked;
69                ControlFlow::Continue(())
70            },
71        )
72        .and_then(|_| {
73            if invalid_encoding {
74                return Err(Error::BadRequest("Invalid Transfer-Encoding".into()));
75            }
76            if invalid_ordering {
77                return Err(Error::BadRequest(
78                    "Invalid Transfer-Encoding ordering".into(),
79                ));
80            }
81            if saw_any {
82                Ok(())
83            } else {
84                Err(Error::BadRequest("Invalid Transfer-Encoding".into()))
85            }
86        })?;
87        if saw_chunked && !last_is_chunked {
88            return Err(Error::BadRequest(
89                "Invalid Transfer-Encoding ordering".into(),
90            ));
91        }
92
93        Ok(TransferEncodingInfo {
94            has_transfer_encoding: saw_any,
95            is_chunked: saw_chunked && last_is_chunked,
96        })
97    }
98
99    pub fn parse(headers: &[httparse::Header<'_>]) -> Result<Self> {
100        let mut content_length: Option<usize> = None;
101        let mut saw_content_length = false;
102        let mut has_transfer_encoding = false;
103        let mut is_chunked_transfer = false;
104        let mut has_expect = false;
105        let mut expect_continue = false;
106        let mut duplicate_content_length = false;
107
108        for h in headers.iter().filter(|h| !h.name.is_empty()) {
109            if h.name.eq_ignore_ascii_case("content-length") {
110                let len = Header::content_length(h.value)?;
111                if saw_content_length {
112                    if content_length != Some(len) {
113                        return Err(Error::BadRequest(
114                            "Multiple Content-Length headers are not allowed".into(),
115                        ));
116                    }
117                    duplicate_content_length = true;
118                }
119                saw_content_length = true;
120                content_length = Some(len);
121                continue;
122            }
123
124            if h.name.eq_ignore_ascii_case("transfer-encoding") {
125                let te = Self::transfer_encoding(h.value)?;
126                has_transfer_encoding = has_transfer_encoding || te.has_transfer_encoding;
127                if te.is_chunked {
128                    is_chunked_transfer = true;
129                }
130                continue;
131            }
132
133            if h.name.eq_ignore_ascii_case("expect") {
134                has_expect = true;
135                if Header::trimmed_eq_ascii_case(h.value, b"100-continue") {
136                    expect_continue = true;
137                }
138            }
139        }
140
141        Ok(HeaderScan {
142            content_length,
143            has_transfer_encoding,
144            is_chunked_transfer,
145            has_expect,
146            expect_continue,
147            duplicate_content_length,
148            accept_encoding_gzip: false,
149        })
150    }
151
152    pub fn content_length(headers: &[httparse::Header<'_>]) -> Result<Option<usize>> {
153        for h in headers {
154            if h.name.eq_ignore_ascii_case("content-length") {
155                return Ok(Some(Header::content_length(h.value)?));
156            }
157        }
158        Ok(None)
159    }
160
161    pub fn is_chunked(headers: &[httparse::Header<'_>]) -> bool {
162        Header::has_name(headers, "transfer-encoding")
163            && headers.iter().any(|h| {
164                h.name.eq_ignore_ascii_case("transfer-encoding")
165                    && Header::has_token(h.value, b"chunked")
166            })
167    }
168
169    pub fn validate_for_request(&self) -> Result<BodyFraming> {
170        if self.duplicate_content_length {
171            return Err(Error::BadRequest(
172                "Multiple Content-Length headers are not allowed".into(),
173            ));
174        }
175        self.validate_request_framing_pair()?;
176        if self.has_transfer_encoding {
177            if self.is_chunked_transfer {
178                return Ok(BodyFraming::Chunked);
179            }
180            return Err(Error::BadRequest(
181                "Transfer-Encoding is not supported".into(),
182            ));
183        }
184        Ok(BodyFraming::Length(self.content_length.unwrap_or(0)))
185    }
186}