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(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}