Skip to main content

sark_http/h1/
target.rs

1//! HTTP/1 request-target and authority admission.
2
3use crate::{
4    field::{self, scan::Match},
5    request::{self, span},
6    uri::{self, path},
7};
8
9#[derive(Clone, Copy, PartialEq, Eq, Debug)]
10pub enum Error {
11    InvalidHost,
12    MissingHost,
13    EmptyAuthority,
14    InvalidTarget,
15    Path(path::Error),
16    Authority(uri::Error),
17    Span(Operation, span::Error),
18    Layout(request::Error),
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum HostError {
23    Authority(uri::Error),
24    Duplicate,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct Invalid;
29
30#[derive(Clone, Copy, PartialEq, Eq)]
31#[repr(u8)]
32#[derive(Debug)]
33pub enum Operation {
34    Path,
35    Query,
36    Authority,
37    PathShift,
38    QueryShift,
39    AuthorityShift,
40}
41
42impl Operation {
43    fn check(self, start: usize, len: usize) -> Result<span::Span, Error> {
44        match span::Span::checked(start, len) {
45            Ok(span) => Ok(span),
46            Err(cause) => Err(Error::Span(self, cause)),
47        }
48    }
49
50    fn shift(self, span: span::Span, base: usize) -> Result<span::Span, Error> {
51        match span.shifted(base) {
52            Ok(span) => Ok(span),
53            Err(cause) => Err(Error::Span(self, cause)),
54        }
55    }
56}
57
58#[derive(Clone, Copy, PartialEq, Eq)]
59#[repr(u8)]
60pub enum Host {
61    Empty,
62    Authority,
63}
64
65#[derive(Clone, Copy, Default, PartialEq, Eq)]
66#[repr(u8)]
67enum HostState {
68    #[default]
69    Missing,
70    Empty,
71    Authority,
72}
73
74#[derive(Clone, Copy, Default, PartialEq, Eq)]
75pub struct Request {
76    host: HostState,
77}
78
79#[derive(Clone, Copy, PartialEq, Eq)]
80#[repr(u8)]
81pub enum Form {
82    Origin,
83    Absolute,
84    Authority,
85    Asterisk,
86}
87
88#[derive(Clone, Copy, PartialEq, Eq)]
89#[repr(u8)]
90pub(crate) enum Route {
91    Borrowed,
92    Root,
93    None,
94}
95
96#[derive(Clone, Copy, PartialEq, Eq)]
97enum Part {
98    Missing,
99    Present(span::Span),
100}
101
102/// Validated target ranges, relative until [`Target::shifted`] binds an owner.
103#[derive(Clone, Copy, PartialEq, Eq)]
104pub struct Target {
105    path: span::Span,
106    query: Part,
107    authority: Part,
108    form: Form,
109    route: Route,
110}
111
112impl Target {
113    pub fn parse(method: &[u8], target: &[u8]) -> Result<Self, Error> {
114        if method.is_empty()
115            || !method.iter().copied().all(field::Error::is_name_byte)
116            || target.is_empty()
117        {
118            return Err(Error::InvalidTarget);
119        }
120        if target == b"*" {
121            return if method == b"OPTIONS" {
122                Ok(Self {
123                    path: span::Span::default(),
124                    query: Part::Missing,
125                    authority: Part::Missing,
126                    form: Form::Asterisk,
127                    route: Route::None,
128                })
129            } else {
130                Err(Error::InvalidTarget)
131            };
132        }
133        if method == b"CONNECT" {
134            return if is_connect(target) {
135                Ok(Self {
136                    path: span::Span::default(),
137                    query: Part::Missing,
138                    authority: Part::Present(Operation::Authority.check(0, target.len())?),
139                    form: Form::Authority,
140                    route: Route::None,
141                })
142            } else {
143                Err(Error::InvalidTarget)
144            };
145        }
146        if target[0] == b'/' {
147            let scan = path::Scan::new(target).complete().map_err(Error::Path)?;
148            return origin(scan, 0);
149        }
150
151        let rest = scheme(target).ok_or(Error::InvalidTarget)?;
152        let rest_at = target.len() - rest.len();
153        let (auth, path_at, suffix) = if let Some(rest) = rest.strip_prefix(b"//") {
154            let span_at = rest_at + 2;
155            let (value, suffix) = match Match::find2(b'/', b'?', rest) {
156                Some(found) => (found.prefix(), found.tail()),
157                None => (rest, &[][..]),
158            };
159            let auth = if value.is_empty() {
160                Part::Missing
161            } else {
162                uri::Authority::parse(value).map_err(Error::Authority)?;
163                Part::Present(Operation::Authority.check(span_at, value.len())?)
164            };
165            (auth, span_at + value.len(), suffix)
166        } else {
167            (Part::Missing, rest_at, rest)
168        };
169
170        let scan = path::Scan::new(suffix).complete().map_err(Error::Path)?;
171        let path_len = scan.path_end();
172        let route = if path_len == 0 {
173            Route::Root
174        } else if suffix.starts_with(b"/") {
175            Route::Borrowed
176        } else {
177            Route::None
178        };
179        Ok(Self {
180            path: Operation::Path.check(path_at, path_len)?,
181            query: match scan.query() {
182                Some(start) => Part::Present(
183                    Operation::Query.check(path_at + start.get(), scan.end() - start.get())?,
184                ),
185                None => Part::Missing,
186            },
187            authority: auth,
188            form: Form::Absolute,
189            route,
190        })
191    }
192
193    pub(crate) fn parse_prefix(
194        method: &[u8],
195        bytes: &[u8],
196        base: usize,
197    ) -> Result<Option<(usize, Self)>, Error> {
198        if bytes.first() == Some(&b'/') && method != b"CONNECT" {
199            return match path::Scan::new(bytes).prefix() {
200                path::Outcome::Complete(scan) => {
201                    origin(scan, base).map(|target| Some((scan.end(), target)))
202                }
203                path::Outcome::Incomplete => Ok(None),
204                path::Outcome::Invalid(error) => Err(Error::Path(error)),
205            };
206        }
207
208        let Some(end) = Match::find2(b' ', b'\r', bytes) else {
209            return Ok(None);
210        };
211        if end.start() == 0 || end.byte() == b'\r' {
212            return Err(Error::InvalidTarget);
213        }
214        let target = Self::parse(method, end.prefix())?.shifted(base)?;
215        Ok(Some((end.start(), target)))
216    }
217
218    pub fn shifted(self, base: usize) -> Result<Self, Error> {
219        Ok(Self {
220            path: Operation::PathShift.shift(self.path, base)?,
221            query: match self.query {
222                Part::Missing => Part::Missing,
223                Part::Present(query) => Part::Present(Operation::QueryShift.shift(query, base)?),
224            },
225            authority: match self.authority {
226                Part::Missing => Part::Missing,
227                Part::Present(auth) => Part::Present(Operation::AuthorityShift.shift(auth, base)?),
228            },
229            ..self
230        })
231    }
232
233    pub const fn form(self) -> Form {
234        self.form
235    }
236
237    /// The source range backing the normalized route path. An empty range is
238    /// intentional for an absolute URI whose omitted path routes as `/`.
239    pub const fn path_span(self) -> span::Span {
240        self.path
241    }
242
243    pub const fn query_span(self) -> Option<span::Span> {
244        match self.query {
245            Part::Missing => None,
246            Part::Present(query) => Some(query),
247        }
248    }
249
250    pub const fn authority(self) -> Option<span::Span> {
251        match self.authority {
252            Part::Missing => None,
253            Part::Present(auth) => Some(auth),
254        }
255    }
256
257    pub(crate) fn layout(
258        self,
259        target: span::Span,
260        span: span::Span,
261    ) -> Result<request::Layout, Error> {
262        let form = match self.form {
263            Form::Authority => request::Form::Authority,
264            Form::Asterisk => request::Form::Asterisk,
265            Form::Absolute if matches!(self.route, Route::Root) => request::Form::Root,
266            Form::Origin | Form::Absolute => request::Form::Path,
267        };
268        let route = match self.route {
269            Route::Borrowed | Route::Root => Some(self.path),
270            Route::None => None,
271        };
272        let selected = (!matches!(form, request::Form::Authority)).then_some(span);
273        request::Layout::try_new(form, target, selected, route).map_err(Error::Layout)
274    }
275
276    /// Projects the path used by an origin router without allocating or
277    /// rewriting the original request-target.
278    pub fn route_path(self, request: &[u8]) -> Option<&[u8]> {
279        match self.route {
280            Route::Borrowed => self.path.get(request),
281            Route::Root => Some(b"/"),
282            Route::None => match self.form {
283                Form::Authority | Form::Asterisk => Some(b"*"),
284                Form::Origin | Form::Absolute => None,
285            },
286        }
287    }
288
289    const fn source(self) -> Option<Source> {
290        match self.form {
291            Form::Absolute | Form::Authority => Some(match self.authority {
292                Part::Present(auth) if !auth.is_empty() => Source::Target,
293                Part::Missing | Part::Present(_) => Source::Empty,
294            }),
295            Form::Origin | Form::Asterisk => None,
296        }
297    }
298}
299
300#[derive(Clone, Copy, PartialEq, Eq)]
301#[repr(u8)]
302pub enum Source {
303    Empty,
304    Host,
305    Target,
306}
307
308#[derive(Clone, Copy, PartialEq, Eq)]
309#[repr(u8)]
310pub enum Policy {
311    AllowEmpty,
312    Require,
313}
314
315#[derive(Clone, Copy, PartialEq, Eq)]
316pub struct Plan {
317    target: Form,
318    source: Source,
319}
320
321impl Request {
322    pub fn try_host(self, value: &[u8]) -> Result<Self, HostError> {
323        let host = Host::parse(value).map_err(HostError::Authority)?;
324        self.try_push(host)
325    }
326
327    pub(crate) fn try_push(mut self, value: Host) -> Result<Self, HostError> {
328        if !matches!(self.host, HostState::Missing) {
329            return Err(HostError::Duplicate);
330        }
331        self.host = match value {
332            Host::Empty => HostState::Empty,
333            Host::Authority => HostState::Authority,
334        };
335        Ok(self)
336    }
337
338    pub const fn has_host(self) -> bool {
339        !matches!(self.host, HostState::Missing)
340    }
341
342    pub fn finish(self, method: &[u8], target: &[u8], version: &[u8]) -> Result<Plan, Error> {
343        let target = Target::parse(method, target)?;
344        self.finish_plan(target, version)
345    }
346
347    /// Finalizes authority semantics from a request-target that was already
348    /// validated by the request-line parser.
349    pub fn finish_plan(self, target: Target, version: &[u8]) -> Result<Plan, Error> {
350        if version == b"HTTP/1.1" && matches!(self.host, HostState::Missing) {
351            return Err(Error::MissingHost);
352        }
353
354        let source = match target.source() {
355            Some(source) => source,
356            None => match self.host {
357                HostState::Authority => Source::Host,
358                HostState::Missing | HostState::Empty => Source::Empty,
359            },
360        };
361        Ok(Plan {
362            target: target.form(),
363            source,
364        })
365    }
366}
367
368impl Plan {
369    pub const fn target_form(self) -> Form {
370        self.target
371    }
372
373    pub const fn source(self) -> Source {
374        self.source
375    }
376
377    /// Applies the endpoint policy used by HTTP(S) origins, whose target URI
378    /// cannot be reconstructed without a non-empty effective authority.
379    pub const fn require(self) -> Result<Self, Error> {
380        if matches!(self.source, Source::Empty) {
381            Err(Error::EmptyAuthority)
382        } else {
383            Ok(self)
384        }
385    }
386
387    /// Resolves the selected authority to a range in the retained request.
388    /// Both inputs must use the same request-relative coordinate space.
389    pub const fn span(self, target: Target, host: Option<span::Span>) -> Result<span::Span, Error> {
390        match self.source {
391            Source::Host => match host {
392                Some(span) => Ok(span),
393                None => Err(Error::InvalidHost),
394            },
395            Source::Target => match target.authority() {
396                Some(span) => Ok(span),
397                None => Err(Error::InvalidTarget),
398            },
399            Source::Empty => Err(Error::EmptyAuthority),
400        }
401    }
402}
403
404impl Host {
405    pub fn parse(value: &[u8]) -> Result<Self, uri::Error> {
406        let value = field::Error::trim_ows(value);
407        if value.is_empty() {
408            return Ok(Self::Empty);
409        }
410        uri::Authority::parse(value)?;
411        Ok(Self::Authority)
412    }
413}
414
415fn origin(scan: path::Plan, base: usize) -> Result<Target, Error> {
416    let path_end = scan.path_end();
417    Ok(Target {
418        path: Operation::Path.check(base, path_end)?,
419        query: match scan.query() {
420            Some(start) => {
421                Part::Present(Operation::Query.check(base + start.get(), scan.end() - start.get())?)
422            }
423            None => Part::Missing,
424        },
425        authority: Part::Missing,
426        form: Form::Origin,
427        route: Route::Borrowed,
428    })
429}
430
431fn scheme(value: &[u8]) -> Option<&[u8]> {
432    let mut bytes = value.iter();
433    if !bytes.next()?.is_ascii_alphabetic() {
434        return None;
435    }
436    while let Some(&byte) = bytes.next() {
437        if byte == b':' {
438            return Some(bytes.as_slice());
439        }
440        if !(byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')) {
441            return None;
442        }
443    }
444    None
445}
446
447fn is_connect(value: &[u8]) -> bool {
448    if value.first() == Some(&b'[') {
449        let Some(close) = value.iter().position(|byte| *byte == b']') else {
450            return false;
451        };
452        return close > 1
453            && uri::Authority::is_ip(&value[1..close])
454            && uri::Authority::is_port(&value[close + 1..]);
455    }
456
457    let Some(colon) = value.iter().rposition(|byte| *byte == b':') else {
458        return false;
459    };
460    !value[..colon].contains(&b':')
461        && uri::Authority::is_reg(&value[..colon])
462        && uri::Authority::is_port(&value[colon..])
463}
464
465const _: () = assert!(core::mem::size_of::<Target>() <= 4 * core::mem::size_of::<usize>());
466const _: () = assert!(core::mem::size_of::<Part>() == core::mem::size_of::<span::Span>());
467const _: () = assert!(core::mem::size_of::<Request>() == 1);
468const _: () = assert!(core::mem::size_of::<Plan>() <= 2);
469const _: () = assert!(core::mem::size_of::<Operation>() == 1);
470const _: () = assert!(core::mem::size_of::<Error>() == 5 * core::mem::size_of::<usize>());
471const _: () = assert!(core::mem::size_of::<Option<Error>>() == core::mem::size_of::<Error>());