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}
69
70impl<'r> Request<'r> {
71    /// The method.
72    #[must_use]
73    pub fn method(&self) -> Method {
74        self.http.method()
75    }
76
77    /// The canonical path inside the site: the base path removed, percent
78    /// escapes in their one canonical form. `/` for the site root.
79    #[must_use]
80    pub fn path(&self) -> &'r str {
81        self.path
82    }
83
84    /// The query after `?`, as received (validated by the parser, not
85    /// decoded).
86    #[must_use]
87    pub fn query(&self) -> Option<&'r str> {
88        self.http.query()
89    }
90
91    /// The host the request named, as received (empty for an HTTP/1.0
92    /// request without one). It matched one of the site's hosts, unless the
93    /// site accepts any host.
94    #[must_use]
95    pub fn host(&self) -> &'r str {
96        self.host
97    }
98
99    /// The first value of a field.
100    #[must_use]
101    pub fn header(&self, name: &str) -> Option<&'r [u8]> {
102        self.http.header(name)
103    }
104
105    /// The first value of a field, if it is UTF-8.
106    #[must_use]
107    pub fn header_str(&self, name: &str) -> Option<&'r str> {
108        self.http
109            .header(name)
110            .and_then(|v| core::str::from_utf8(v).ok())
111    }
112
113    /// The value of the cookie `name`, read with cookie-net. `Ok(None)` when
114    /// there is no such cookie.
115    ///
116    /// # Errors
117    ///
118    /// [`Ambiguous`] when the name appears twice, or the request has more
119    /// than one `Cookie` field: a sign of cookie tossing, never resolved by
120    /// picking one.
121    pub fn cookie(&self, name: &str) -> Result<Option<&'r str>, Ambiguous> {
122        let mut fields = self.http.head().header_all("cookie");
123        let Some(first) = fields.next() else {
124            return Ok(None);
125        };
126        if fields.next().is_some() {
127            return Err(Ambiguous);
128        }
129        cookie_net::get_unique(first, name).map_err(|_| Ambiguous)
130    }
131
132    /// The client, as serve-net established it (the real address behind a
133    /// trusted proxy).
134    #[must_use]
135    pub fn client(&self) -> &'r ClientInfo {
136        self.env.client()
137    }
138
139    /// `https` when the request came over TLS (or from a trusted proxy that
140    /// said so).
141    #[must_use]
142    pub fn scheme(&self) -> Scheme {
143        self.env.client().scheme()
144    }
145
146    /// The pattern of the matched route (`/users/:id`), once routing ran.
147    #[must_use]
148    pub fn route(&self) -> Option<&'r str> {
149        self.route
150    }
151
152    /// The request's reference.
153    #[must_use]
154    pub fn id(&self) -> RequestId {
155        RequestId::of(self.env)
156    }
157
158    /// serve-net's facts about the request.
159    #[must_use]
160    pub fn envelope(&self) -> &'r Envelope {
161        self.env
162    }
163
164    /// The raw request: the target as received, every field, the body.
165    #[must_use]
166    pub fn raw(&self) -> &'r HttpRequest<'r> {
167        self.http
168    }
169}
170
171impl fmt::Debug for Request<'_> {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        f.debug_struct("Request")
174            .field("method", &self.method())
175            .field("path", &self.path)
176            .field("route", &self.route)
177            .field("id", &self.id())
178            .finish_non_exhaustive()
179    }
180}