sark_core/http/codec/decode/
headers.rs1use 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}