1use super::error::Http1ParseError;
4use super::fields::{Http1Fields, LineEndError, find_line_end, is_token};
5use super::framing::Http1BodyFraming;
6use super::framing::framing;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum Http1Version {
11 Http10,
13 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 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#[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 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 pub const fn as_bytes(self) -> &'a [u8] {
97 self.bytes
98 }
99
100 pub const fn head_len(self) -> usize {
102 self.bytes.len()
103 }
104
105 pub const fn method(self) -> &'a [u8] {
107 self.method
108 }
109
110 pub const fn target(self) -> &'a [u8] {
112 self.target
113 }
114
115 pub const fn version(self) -> Http1Version {
117 self.version
118 }
119
120 pub const fn fields(self) -> Http1Fields<'a> {
122 self.fields
123 }
124
125 pub fn body_framing(self) -> Result<Http1BodyFraming, Http1ParseError> {
127 framing(self.fields, true, None, None)
128 }
129}
130
131#[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 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 pub const fn as_bytes(self) -> &'a [u8] {
185 self.bytes
186 }
187
188 pub const fn head_len(self) -> usize {
190 self.bytes.len()
191 }
192
193 pub const fn status(self) -> u16 {
195 self.status
196 }
197
198 pub const fn reason(self) -> &'a [u8] {
200 self.reason
201 }
202
203 pub const fn version(self) -> Http1Version {
205 self.version
206 }
207
208 pub const fn fields(self) -> Http1Fields<'a> {
210 self.fields
211 }
212
213 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}