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