Skip to main content

net_wire/http1/
framing.rs

1//! HTTP/1 message-body framing.
2
3use super::error::Http1ParseError;
4use super::fields::{Http1Fields, eq_ascii_case, is_token, trim_ows};
5
6/// HTTP/1 body framing selected by RFC 9112.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum Http1BodyFraming {
9    /// The message has no body.
10    None,
11    /// The body has the encoded fixed length.
12    ContentLength(usize),
13    /// The body uses chunked transfer coding.
14    Chunked,
15    /// A response body ends when the connection closes.
16    CloseDelimited,
17    /// A successful CONNECT response switches to tunnel data.
18    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}