Skip to main content

net_wire/http1/
head.rs

1//! HTTP/1 message head views.
2
3use super::error::Http1ParseError;
4use super::fields::{Http1Fields, LineEndError, find_line_end, is_token};
5use super::framing::Http1BodyFraming;
6use super::framing::framing;
7
8/// A supported HTTP/1 protocol version.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum Http1Version {
11    /// HTTP/1.0.
12    Http10,
13    /// HTTP/1.1.
14    Http11,
15}
16
17impl Http1Version {
18    pub(crate) fn parse(value: &[u8]) -> Option<Self> {
19        match value {
20            b"HTTP/1.0" => Some(Self::Http10),
21            b"HTTP/1.1" => Some(Self::Http11),
22            _ => None,
23        }
24    }
25
26    /// Returns the exact wire spelling.
27    pub const fn as_bytes(self) -> &'static [u8] {
28        match self {
29            Self::Http10 => b"HTTP/1.0",
30            Self::Http11 => b"HTTP/1.1",
31        }
32    }
33}
34
35/// A parsed HTTP/1 request head.
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct Http1RequestHead<'a> {
38    bytes: &'a [u8],
39    method: &'a [u8],
40    target: &'a [u8],
41    version: Http1Version,
42    fields: Http1Fields<'a>,
43}
44
45impl<'a> Http1RequestHead<'a> {
46    /// Parses exactly one complete request head.
47    pub fn parse(input: &'a [u8]) -> Result<Self, Http1ParseError> {
48        let split = split_head(input)?;
49        let first_space = split
50            .start_line
51            .iter()
52            .position(|byte| *byte == b' ')
53            .ok_or(Http1ParseError::InvalidStartLine)?;
54        let second_space = split.start_line[first_space + 1..]
55            .iter()
56            .position(|byte| *byte == b' ')
57            .map(|offset| first_space + 1 + offset)
58            .ok_or(Http1ParseError::InvalidStartLine)?;
59        let method = &split.start_line[..first_space];
60        let target = &split.start_line[first_space + 1..second_space];
61        let version = &split.start_line[second_space + 1..];
62        if method.is_empty()
63            || !method.iter().copied().all(is_token)
64            || !valid_request_target(target)
65        {
66            return Err(Http1ParseError::InvalidStartLine);
67        }
68        let version = Http1Version::parse(version).ok_or(Http1ParseError::InvalidStartLine)?;
69        let fields = Http1Fields::parse(split.fields, false)?;
70        Ok(Self {
71            bytes: split.bytes,
72            method,
73            target,
74            version,
75            fields,
76        })
77    }
78
79    pub(crate) fn from_validated(
80        bytes: &'a [u8],
81        method_end: usize,
82        target_end: usize,
83        fields_start: usize,
84        version: Http1Version,
85    ) -> Self {
86        Self {
87            bytes,
88            method: &bytes[..method_end],
89            target: &bytes[method_end + 1..target_end],
90            version,
91            fields: Http1Fields::from_validated(&bytes[fields_start..bytes.len() - 2]),
92        }
93    }
94
95    /// Returns the complete encoded head, including the terminating blank line.
96    pub const fn as_bytes(self) -> &'a [u8] {
97        self.bytes
98    }
99
100    /// Returns the encoded head length.
101    pub const fn head_len(self) -> usize {
102        self.bytes.len()
103    }
104
105    /// Returns the raw method.
106    pub const fn method(self) -> &'a [u8] {
107        self.method
108    }
109
110    /// Returns the raw request target.
111    pub const fn target(self) -> &'a [u8] {
112        self.target
113    }
114
115    /// Returns the protocol version.
116    pub const fn version(self) -> Http1Version {
117        self.version
118    }
119
120    /// Returns ordered fields without combining duplicates.
121    pub const fn fields(self) -> Http1Fields<'a> {
122        self.fields
123    }
124
125    /// Determines request-body framing according to RFC 9112.
126    pub fn body_framing(self) -> Result<Http1BodyFraming, Http1ParseError> {
127        framing(self.fields, true, None, None)
128    }
129}
130
131/// A parsed HTTP/1 response head.
132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub struct Http1ResponseHead<'a> {
134    bytes: &'a [u8],
135    status: u16,
136    reason: &'a [u8],
137    version: Http1Version,
138    fields: Http1Fields<'a>,
139}
140
141impl<'a> Http1ResponseHead<'a> {
142    /// Parses exactly one complete response head.
143    pub fn parse(input: &'a [u8]) -> Result<Self, Http1ParseError> {
144        let split = split_head(input)?;
145        let line = split.start_line;
146        if line.len() < 13 || line.get(8) != Some(&b' ') || line.get(12) != Some(&b' ') {
147            return Err(Http1ParseError::InvalidStartLine);
148        }
149        let version = Http1Version::parse(&line[..8]).ok_or(Http1ParseError::InvalidStartLine)?;
150        let status_bytes = &line[9..12];
151        if !status_bytes.iter().all(u8::is_ascii_digit) || !valid_reason(&line[13..]) {
152            return Err(Http1ParseError::InvalidStartLine);
153        }
154        let status = u16::from(status_bytes[0] - b'0') * 100
155            + u16::from(status_bytes[1] - b'0') * 10
156            + u16::from(status_bytes[2] - b'0');
157        let fields = Http1Fields::parse(split.fields, false)?;
158        Ok(Self {
159            bytes: split.bytes,
160            status,
161            reason: &line[13..],
162            version,
163            fields,
164        })
165    }
166
167    pub(crate) fn from_validated(
168        bytes: &'a [u8],
169        status: u16,
170        reason_start: usize,
171        fields_start: usize,
172        version: Http1Version,
173    ) -> Self {
174        Self {
175            bytes,
176            status,
177            reason: &bytes[reason_start..fields_start - 2],
178            version,
179            fields: Http1Fields::from_validated(&bytes[fields_start..bytes.len() - 2]),
180        }
181    }
182
183    /// Returns the complete encoded head, including the terminating blank line.
184    pub const fn as_bytes(self) -> &'a [u8] {
185        self.bytes
186    }
187
188    /// Returns the encoded head length.
189    pub const fn head_len(self) -> usize {
190        self.bytes.len()
191    }
192
193    /// Returns the numeric status code.
194    pub const fn status(self) -> u16 {
195        self.status
196    }
197
198    /// Returns the raw reason phrase.
199    pub const fn reason(self) -> &'a [u8] {
200        self.reason
201    }
202
203    /// Returns the protocol version.
204    pub const fn version(self) -> Http1Version {
205        self.version
206    }
207
208    /// Returns ordered fields without combining duplicates.
209    pub const fn fields(self) -> Http1Fields<'a> {
210        self.fields
211    }
212
213    /// Determines response-body framing using the associated request method when known.
214    pub fn body_framing(
215        self,
216        request_method: Option<&[u8]>,
217    ) -> Result<Http1BodyFraming, Http1ParseError> {
218        framing(self.fields, false, Some(self.status), request_method)
219    }
220}
221
222struct SplitHead<'a> {
223    bytes: &'a [u8],
224    start_line: &'a [u8],
225    fields: &'a [u8],
226}
227
228fn split_head(input: &[u8]) -> Result<SplitHead<'_>, Http1ParseError> {
229    let first_end = map_line_end(input, 0, Http1ParseError::InvalidStartLine)?;
230    let fields_start = first_end + 2;
231    let mut offset = fields_start;
232    loop {
233        let end = map_line_end(input, offset, Http1ParseError::InvalidField)?;
234        if end == offset {
235            return Ok(SplitHead {
236                bytes: &input[..end + 2],
237                start_line: &input[..first_end],
238                fields: &input[fields_start..offset],
239            });
240        }
241        offset = end + 2;
242    }
243}
244
245fn map_line_end(
246    input: &[u8],
247    start: usize,
248    malformed: Http1ParseError,
249) -> Result<usize, Http1ParseError> {
250    match find_line_end(input, start) {
251        Ok(end) => Ok(end),
252        Err(LineEndError::Malformed) => Err(malformed),
253        Err(LineEndError::Incomplete) => Err(Http1ParseError::Incomplete {
254            required: input.len().saturating_add(1),
255            available: input.len(),
256        }),
257    }
258}
259
260pub(crate) fn valid_request_target(target: &[u8]) -> bool {
261    !target.is_empty() && !target.iter().any(|byte| *byte <= 0x20 || *byte == 0x7f)
262}
263
264pub(crate) fn valid_reason(reason: &[u8]) -> bool {
265    !reason
266        .iter()
267        .any(|byte| matches!(*byte, 0..=8 | 10..=31 | 127))
268}