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