net_wire/http1/
framing.rs1use super::error::Http1ParseError;
4use super::fields::{Http1Fields, eq_ascii_case, is_token, trim_ows};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum Http1BodyFraming {
9 None,
11 ContentLength(usize),
13 Chunked,
15 CloseDelimited,
17 Tunnel,
19}
20
21pub(crate) fn framing(
22 fields: Http1Fields<'_>,
23 request: bool,
24 status: Option<u16>,
25 method: Option<&[u8]>,
26) -> Result<Http1BodyFraming, Http1ParseError> {
27 if !request
28 && (method == Some(b"HEAD" as &[u8]) || matches!(status, Some(100..=199 | 204 | 304)))
29 {
30 return Ok(Http1BodyFraming::None);
31 }
32 if !request && method == Some(b"CONNECT" as &[u8]) && matches!(status, Some(200..=299)) {
33 return Ok(Http1BodyFraming::Tunnel);
34 }
35
36 let has_content_length = fields.iter().any(|field| field.name_eq(b"content-length"));
37 let has_transfer_encoding = fields
38 .iter()
39 .any(|field| field.name_eq(b"transfer-encoding"));
40 if has_content_length && has_transfer_encoding {
41 return Err(Http1ParseError::TransferEncodingWithContentLength);
42 }
43
44 if has_transfer_encoding {
45 let final_chunked = parse_transfer_encoding(fields)?;
46 if final_chunked {
47 return Ok(Http1BodyFraming::Chunked);
48 }
49 if request {
50 return Err(Http1ParseError::InvalidTransferEncoding);
51 }
52 return Ok(Http1BodyFraming::CloseDelimited);
53 }
54
55 if has_content_length {
56 return Ok(Http1BodyFraming::ContentLength(parse_content_length(
57 fields,
58 )?));
59 }
60
61 Ok(if request {
62 Http1BodyFraming::None
63 } else {
64 Http1BodyFraming::CloseDelimited
65 })
66}
67
68fn parse_content_length(fields: Http1Fields<'_>) -> Result<usize, Http1ParseError> {
69 let mut selected = None;
70 for field in fields
71 .iter()
72 .filter(|field| field.name_eq(b"content-length"))
73 {
74 for member in field.raw_value().split(|byte| *byte == b',') {
75 let member = trim_ows(member);
76 if member.is_empty() || !member.iter().all(u8::is_ascii_digit) {
77 return Err(Http1ParseError::InvalidContentLength);
78 }
79 let mut value = 0usize;
80 for byte in member {
81 value = value
82 .checked_mul(10)
83 .and_then(|value| value.checked_add(usize::from(*byte - b'0')))
84 .ok_or(Http1ParseError::ContentLengthOverflow)?;
85 }
86 match selected {
87 Some(previous) if previous != value => {
88 return Err(Http1ParseError::ConflictingContentLength);
89 }
90 _ => selected = Some(value),
91 }
92 }
93 }
94 selected.ok_or(Http1ParseError::InvalidContentLength)
95}
96
97#[derive(Default)]
98struct TransferState {
99 seen: bool,
100 chunked_seen: bool,
101 final_chunked: bool,
102}
103
104fn parse_transfer_encoding(fields: Http1Fields<'_>) -> Result<bool, Http1ParseError> {
105 let mut state = TransferState::default();
106 for field in fields
107 .iter()
108 .filter(|field| field.name_eq(b"transfer-encoding"))
109 {
110 parse_transfer_value(field.raw_value(), &mut state)?;
111 }
112 if !state.seen {
113 return Err(Http1ParseError::InvalidTransferEncoding);
114 }
115 Ok(state.final_chunked)
116}
117
118fn parse_transfer_value(value: &[u8], state: &mut TransferState) -> Result<(), Http1ParseError> {
119 let mut offset = 0;
120 loop {
121 skip_ows(value, &mut offset);
122 if offset == value.len() {
123 return Err(Http1ParseError::InvalidTransferEncoding);
124 }
125 if state.chunked_seen {
126 return Err(Http1ParseError::InvalidChunkedTransferEncoding);
127 }
128
129 let coding_start = offset;
130 parse_token(value, &mut offset)?;
131 let is_chunked = eq_ascii_case(&value[coding_start..offset], b"chunked");
132 skip_ows(value, &mut offset);
133
134 let mut has_parameters = false;
135 while value.get(offset) == Some(&b';') {
136 has_parameters = true;
137 offset += 1;
138 skip_ows(value, &mut offset);
139 parse_token(value, &mut offset)?;
140 skip_ows(value, &mut offset);
141 if value.get(offset) != Some(&b'=') {
142 return Err(Http1ParseError::InvalidTransferEncoding);
143 }
144 offset += 1;
145 skip_ows(value, &mut offset);
146 parse_token_or_quoted(value, &mut offset)?;
147 skip_ows(value, &mut offset);
148 }
149 if is_chunked && has_parameters {
150 return Err(Http1ParseError::InvalidChunkedTransferEncoding);
151 }
152
153 state.seen = true;
154 state.chunked_seen |= is_chunked;
155 state.final_chunked = is_chunked;
156 if offset == value.len() {
157 return Ok(());
158 }
159 if value[offset] != b',' {
160 return Err(Http1ParseError::InvalidTransferEncoding);
161 }
162 offset += 1;
163 }
164}
165
166fn parse_token(value: &[u8], offset: &mut usize) -> Result<(), Http1ParseError> {
167 let start = *offset;
168 while value.get(*offset).is_some_and(|byte| is_token(*byte)) {
169 *offset += 1;
170 }
171 if *offset == start {
172 return Err(Http1ParseError::InvalidTransferEncoding);
173 }
174 Ok(())
175}
176
177fn parse_token_or_quoted(value: &[u8], offset: &mut usize) -> Result<(), Http1ParseError> {
178 if value.get(*offset) != Some(&b'"') {
179 return parse_token(value, offset);
180 }
181 *offset += 1;
182 loop {
183 let byte = *value
184 .get(*offset)
185 .ok_or(Http1ParseError::InvalidTransferEncoding)?;
186 match byte {
187 b'"' => {
188 *offset += 1;
189 return Ok(());
190 }
191 b'\\' => {
192 *offset += 1;
193 let escaped = *value
194 .get(*offset)
195 .ok_or(Http1ParseError::InvalidTransferEncoding)?;
196 if !matches!(escaped, b'\t' | b' ' | 0x21..=0x7e | 0x80..=0xff) {
197 return Err(Http1ParseError::InvalidTransferEncoding);
198 }
199 *offset += 1;
200 }
201 b'\t' | b' ' | 0x21 | 0x23..=0x5b | 0x5d..=0x7e | 0x80..=0xff => *offset += 1,
202 _ => return Err(Http1ParseError::InvalidTransferEncoding),
203 }
204 }
205}
206
207fn skip_ows(value: &[u8], offset: &mut usize) {
208 while matches!(value.get(*offset), Some(b' ' | b'\t')) {
209 *offset += 1;
210 }
211}