Skip to main content

web_net/
request.rs

1//! The request as gates and handlers read it.
2
3use std::fmt;
4
5use serve_net::{ClientInfo, Envelope, HttpRequest, Method, Scheme};
6
7/// A request's reference: the connection id and the request's number on it.
8/// Shown on error pages and carried by every event, so a visitor's report
9/// finds the log line. Not a security token.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct RequestId {
12    connection: u64,
13    seq: u32,
14}
15
16impl RequestId {
17    pub(crate) fn of(env: &Envelope) -> Self {
18        Self {
19            connection: env.connection().get(),
20            seq: env.seq().get(),
21        }
22    }
23
24    /// The connection id.
25    #[must_use]
26    pub const fn connection(&self) -> u64 {
27        self.connection
28    }
29
30    /// The request's number on its connection.
31    #[must_use]
32    pub const fn seq(&self) -> u32 {
33        self.seq
34    }
35}
36
37impl fmt::Display for RequestId {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "{:x}-{}", self.connection, self.seq)
40    }
41}
42
43/// A `Cookie` field held the name more than once, or the request carried
44/// more than one `Cookie` field. A gate treats this as no credential, or as
45/// an attack; it never picks one of the values.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct Ambiguous;
48
49impl fmt::Display for Ambiguous {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.write_str("the cookie appears more than once")
52    }
53}
54
55impl std::error::Error for Ambiguous {}
56
57/// The request as gates and handlers read it: the parts web-net has checked
58/// (the host matched a site, the path is canonical) and the raw request
59/// underneath.
60#[derive(Clone, Copy)]
61pub struct Request<'r> {
62    pub(crate) http: &'r HttpRequest<'r>,
63    pub(crate) env: &'r Envelope,
64    pub(crate) path: &'r str,
65    pub(crate) host: &'r str,
66    pub(crate) route: Option<&'r str>,
67    pub(crate) tls: bool,
68    pub(crate) json: bool,
69}
70
71impl<'r> Request<'r> {
72    /// The method.
73    #[must_use]
74    pub fn method(&self) -> Method {
75        self.http.method()
76    }
77
78    /// The canonical path inside the site: the base path removed, percent
79    /// escapes in their one canonical form. `/` for the site root.
80    #[must_use]
81    pub fn path(&self) -> &'r str {
82        self.path
83    }
84
85    /// The query after `?`, as received (validated by the parser, not
86    /// decoded).
87    #[must_use]
88    pub fn query(&self) -> Option<&'r str> {
89        self.http.query()
90    }
91
92    /// The host the request named, as received (empty for an HTTP/1.0
93    /// request without one). It matched one of the site's hosts, unless the
94    /// site accepts any host.
95    #[must_use]
96    pub fn host(&self) -> &'r str {
97        self.host
98    }
99
100    /// The first value of a field.
101    #[must_use]
102    pub fn header(&self, name: &str) -> Option<&'r [u8]> {
103        self.http.header(name)
104    }
105
106    /// The first value of a field, if it is UTF-8.
107    #[must_use]
108    pub fn header_str(&self, name: &str) -> Option<&'r str> {
109        self.http
110            .header(name)
111            .and_then(|v| core::str::from_utf8(v).ok())
112    }
113
114    /// The value of the cookie `name`, read with cookie-net. `Ok(None)` when
115    /// there is no such cookie.
116    ///
117    /// # Errors
118    ///
119    /// [`Ambiguous`] when the name appears twice, or the request has more
120    /// than one `Cookie` field: a sign of cookie tossing, never resolved by
121    /// picking one.
122    pub fn cookie(&self, name: &str) -> Result<Option<&'r str>, Ambiguous> {
123        let mut fields = self.http.head().header_all("cookie");
124        let Some(first) = fields.next() else {
125            return Ok(None);
126        };
127        if fields.next().is_some() {
128            return Err(Ambiguous);
129        }
130        cookie_net::get_unique(first, name).map_err(|_| Ambiguous)
131    }
132
133    /// The client, as serve-net established it (the real address behind a
134    /// trusted proxy).
135    #[must_use]
136    pub fn client(&self) -> &'r ClientInfo {
137        self.env.client()
138    }
139
140    /// `https` when the request came over TLS (or from a trusted proxy that
141    /// said so).
142    #[must_use]
143    pub fn scheme(&self) -> Scheme {
144        self.env.client().scheme()
145    }
146
147    /// The pattern of the matched route (`/users/:id`), once routing ran.
148    #[must_use]
149    pub fn route(&self) -> Option<&'r str> {
150        self.route
151    }
152
153    /// The request's reference.
154    #[must_use]
155    pub fn id(&self) -> RequestId {
156        RequestId::of(self.env)
157    }
158
159    /// serve-net's facts about the request.
160    #[must_use]
161    pub fn envelope(&self) -> &'r Envelope {
162        self.env
163    }
164
165    /// The raw request: the target as received, every field, the body.
166    #[must_use]
167    pub fn raw(&self) -> &'r HttpRequest<'r> {
168        self.http
169    }
170}
171
172impl fmt::Debug for Request<'_> {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        f.debug_struct("Request")
175            .field("method", &self.method())
176            .field("path", &self.path)
177            .field("route", &self.route)
178            .field("id", &self.id())
179            .finish_non_exhaustive()
180    }
181}