Skip to main content

ullun/
request.rs

1//! Request types and extractors
2
3use crate::cookies::Cookies;
4use crate::error::{Error, Result};
5use serde::de::DeserializeOwned;
6use std::collections::HashMap;
7use std::sync::{Arc, OnceLock};
8
9/// Path parameters extracted from route
10#[derive(Debug, Clone)]
11pub struct Params {
12    inner: HashMap<String, String>,
13}
14
15impl Params {
16    pub fn new() -> Self {
17        Self {
18            inner: HashMap::new(),
19        }
20    }
21
22    pub fn insert(&mut self, key: String, value: String) {
23        self.inner.insert(key, value);
24    }
25
26    pub fn get(&self, key: &str) -> Result<&str> {
27        self.inner
28            .get(key)
29            .map(|s| s.as_str())
30            .ok_or_else(|| Error::bad_request(format!("Missing param: {}", key)))
31    }
32
33    pub fn get_opt(&self, key: &str) -> Option<&str> {
34        self.inner.get(key).map(|s| s.as_str())
35    }
36
37    /// Parse a path parameter into any `FromStr` type, e.g. `params.parse::<u64>("id")?`
38    ///
39    /// Answers `400` naming the parameter when it does not parse.
40    pub fn parse<T: std::str::FromStr>(&self, key: &str) -> Result<T> {
41        self.get(key)?
42            .parse()
43            .map_err(|_| Error::bad_request(format!("Invalid path parameter '{}'", key)))
44    }
45}
46
47impl Default for Params {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53/// Query parameters from URL (also used for `application/x-www-form-urlencoded` bodies)
54#[derive(Debug, Clone)]
55pub struct Query {
56    inner: HashMap<String, String>,
57    raw: String,
58}
59
60impl Query {
61    pub fn new() -> Self {
62        Self {
63            inner: HashMap::new(),
64            raw: String::new(),
65        }
66    }
67
68    /// Parse `a=1&b=two`. Keys and values are percent-decoded and `+` is a
69    /// space. Pairs without `=` are kept with an empty value; if a key repeats,
70    /// the last value wins.
71    pub fn parse_query_string(query: &str) -> Self {
72        let mut q = Query {
73            inner: HashMap::new(),
74            raw: query.to_string(),
75        };
76        for pair in query.split('&').filter(|p| !p.is_empty()) {
77            let (key, value) = pair.split_once('=').unwrap_or((pair, ""));
78            q.inner
79                .insert(decode_component(key), decode_component(value));
80        }
81        q
82    }
83
84    pub fn get(&self, key: &str) -> Option<&str> {
85        self.inner.get(key).map(|s| s.as_str())
86    }
87
88    pub fn contains(&self, key: &str) -> bool {
89        self.inner.contains_key(key)
90    }
91
92    pub fn is_empty(&self) -> bool {
93        self.inner.is_empty()
94    }
95
96    /// Deserialize into a struct; numbers, booleans and `Option` are converted
97    /// from their string form.
98    pub fn parse<T: DeserializeOwned>(&self) -> Result<T> {
99        serde_urlencoded::from_str(&self.raw)
100            .map_err(|e| Error::bad_request(format!("Invalid query params: {}", e)))
101    }
102}
103
104fn decode_component(s: &str) -> String {
105    let s = s.replace('+', " ");
106    urlencoding::decode(&s).map(|c| c.into_owned()).unwrap_or(s)
107}
108
109impl Default for Query {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115/// The verified client certificate of a TLS connection (DER)
116#[derive(Debug, Clone)]
117pub struct ClientCertificate(pub Vec<u8>);
118
119/// Type-keyed storage for shared application state
120#[derive(Default)]
121pub(crate) struct StateMap {
122    values: HashMap<std::any::TypeId, Arc<dyn std::any::Any + Send + Sync>>,
123}
124
125impl StateMap {
126    pub(crate) fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
127        self.values
128            .insert(std::any::TypeId::of::<T>(), Arc::new(value));
129    }
130
131    pub(crate) fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
132        self.values
133            .get(&std::any::TypeId::of::<T>())
134            .and_then(|v| v.downcast_ref::<T>())
135    }
136
137    pub(crate) fn get_arc<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
138        self.values
139            .get(&std::any::TypeId::of::<T>())
140            .and_then(|v| v.clone().downcast::<T>().ok())
141    }
142}
143
144/// HTTP Request wrapper
145pub struct Request {
146    pub method: http::Method,
147    pub uri: http::Uri,
148    pub headers: http::HeaderMap,
149    pub body: bytes::Bytes,
150    pub params: Params,
151    pub query: Query,
152    /// Typed per-request data; middleware can attach values for handlers
153    /// (authenticated user, request id, ...).
154    pub extensions: http::Extensions,
155    /// Address of the connected peer (the proxy when behind one)
156    pub remote_addr: Option<std::net::SocketAddr>,
157    /// The route pattern that matched, e.g. `/users/{id}`; `None` when no route
158    /// matched. Use it, not the raw path, as a metrics label or log field: it
159    /// stays the same for every id.
160    pub matched_path: Option<std::sync::Arc<str>>,
161    /// The request arrived over TLS (`App::tls`). Behind a TLS-terminating
162    /// proxy this is `false`; see `secure::RequireHttps::trust_forwarded_proto`.
163    pub secure: bool,
164    pub(crate) state: Option<std::sync::Arc<StateMap>>,
165    pub(crate) shutdown: Option<crate::shutdown::Shutdown>,
166    pub(crate) stream: Option<crate::upload::BodyStream>,
167    pub(crate) trusted_proxies: Option<std::sync::Arc<crate::proxy::TrustedProxies>>,
168    cookies: OnceLock<Cookies>,
169}
170
171impl Request {
172    pub fn new(
173        method: http::Method,
174        uri: http::Uri,
175        headers: http::HeaderMap,
176        body: bytes::Bytes,
177    ) -> Self {
178        let query = uri
179            .query()
180            .map(Query::parse_query_string)
181            .unwrap_or_default();
182
183        Self {
184            method,
185            uri,
186            headers,
187            body,
188            params: Params::new(),
189            query,
190            extensions: http::Extensions::new(),
191            remote_addr: None,
192            matched_path: None,
193            secure: false,
194            state: None,
195            shutdown: None,
196            stream: None,
197            trusted_proxies: None,
198            cookies: OnceLock::new(),
199        }
200    }
201
202    /// Which of `offered` media types the client prefers according to `Accept`
203    /// (`None` when it accepts none of them), see [`crate::negotiate`]
204    pub fn accepts<'a>(&self, offered: &[&'a str]) -> Option<&'a str> {
205        let header = self
206            .headers
207            .get(http::header::ACCEPT)
208            .and_then(|v| v.to_str().ok())
209            .unwrap_or("");
210        crate::negotiate::best(header, offered)
211    }
212
213    /// Which of `offered` languages the client prefers according to `Accept-Language`
214    pub fn accepts_language<'a>(&self, offered: &[&'a str]) -> Option<&'a str> {
215        let header = self
216            .headers
217            .get(http::header::ACCEPT_LANGUAGE)
218            .and_then(|v| v.to_str().ok())
219            .unwrap_or("");
220        crate::negotiate::best_language(header, offered)
221    }
222
223    /// The body as a stream, see [`crate::upload`]. On routes registered with
224    /// `App::stream_body` nothing was buffered; on other routes the already
225    /// buffered body is served as a single chunk, so a handler works either way.
226    /// The stream can be taken once.
227    pub fn body_stream(&mut self) -> Result<crate::upload::BodyStream> {
228        Ok(self.stream.take().unwrap_or_else(|| {
229            crate::upload::BodyStream::from_bytes(std::mem::take(&mut self.body), None)
230        }))
231    }
232
233    /// A copy for work that outlives the request (background refreshes)
234    pub(crate) fn duplicate(&self) -> Request {
235        let mut copy = Request::new(
236            self.method.clone(),
237            self.uri.clone(),
238            self.headers.clone(),
239            self.body.clone(),
240        );
241        copy.params = self.params.clone();
242        copy.extensions = self.extensions.clone();
243        copy.remote_addr = self.remote_addr;
244        copy.matched_path = self.matched_path.clone();
245        copy.secure = self.secure;
246        copy.state = self.state.clone();
247        copy.shutdown = self.shutdown.clone();
248        copy.trusted_proxies = self.trusted_proxies.clone();
249        copy
250    }
251
252    /// True when the request came in over TLS
253    pub fn is_secure(&self) -> bool {
254        self.secure || self.forwarded_https()
255    }
256
257    /// Nonce for inline scripts and styles, when the
258    /// [`SecurityHeaders`](crate::security_headers::SecurityHeaders) policy uses the
259    /// `{nonce}` placeholder
260    pub fn csp_nonce(&self) -> Option<&str> {
261        self.extensions
262            .get::<crate::security_headers::CspNonce>()
263            .map(|n| n.0.as_str())
264    }
265
266    /// DER of the leaf certificate the client presented during the TLS
267    /// handshake (mutual TLS, feature `tls`); `None` for anonymous clients and
268    /// plain HTTP. The certificate has already been verified against the CAs
269    /// given to `TlsConfig::client_auth`; check *which* verified client it is
270    /// (fingerprint or subject) before authorizing anything.
271    pub fn client_certificate(&self) -> Option<&[u8]> {
272        self.extensions
273            .get::<ClientCertificate>()
274            .map(|c| c.0.as_slice())
275    }
276
277    /// Shared application state registered with `App::state`
278    ///
279    /// Answers `500` when no value of type `T` was registered: that is a
280    /// programming error, not something the client can fix.
281    pub fn state<T: Send + Sync + 'static>(&self) -> Result<&T> {
282        self.try_state().ok_or_else(|| {
283            Error::internal(format!(
284                "State of type {} is not registered",
285                std::any::type_name::<T>()
286            ))
287        })
288    }
289
290    /// Signal that fires when the server begins shutting down
291    ///
292    /// Long-lived handlers (SSE streams, polling loops) select on it to end
293    /// promptly: `tokio::select! { _ = req.shutdown_signal().wait() => break, ... }`.
294    pub fn shutdown_signal(&self) -> crate::shutdown::Shutdown {
295        self.shutdown
296            .clone()
297            .unwrap_or_else(crate::shutdown::Shutdown::never)
298    }
299
300    /// An owned handle to the same state, for when the value must outlive a
301    /// borrow of the request (it is moved into middleware `next.run(req)`, a
302    /// spawned task, ...)
303    pub fn state_arc<T: Send + Sync + 'static>(&self) -> Result<Arc<T>> {
304        self.state
305            .as_ref()
306            .and_then(|s| s.get_arc::<T>())
307            .ok_or_else(|| {
308                Error::internal(format!(
309                    "State of type {} is not registered",
310                    std::any::type_name::<T>()
311                ))
312            })
313    }
314
315    /// Like [`Request::state`], `None` when the type is not registered
316    pub fn try_state<T: Send + Sync + 'static>(&self) -> Option<&T> {
317        self.state.as_ref()?.get::<T>()
318    }
319
320    /// Get cookies from the request
321    pub fn cookies(&self) -> &Cookies {
322        self.cookies.get_or_init(|| {
323            if let Some(cookie_header) = self.headers.get("cookie") {
324                if let Ok(cookie_str) = cookie_header.to_str() {
325                    Cookies::parse(cookie_str)
326                } else {
327                    Cookies::new()
328                }
329            } else {
330                Cookies::new()
331            }
332        })
333    }
334
335    /// Parse an `application/x-www-form-urlencoded` body into a struct
336    ///
337    /// Answers `415` for other content types and `400` for values that do not
338    /// fit the target type.
339    pub fn form<T: DeserializeOwned>(&self) -> Result<T> {
340        self.require_form_content_type()?;
341        serde_urlencoded::from_bytes(&self.body)
342            .map_err(|e| Error::bad_request(format!("Invalid form data: {}", e)))
343    }
344
345    /// The same body as decoded key/value access (`+` and `%XX` decoded)
346    pub fn form_data(&self) -> Result<Query> {
347        self.require_form_content_type()?;
348        let text = std::str::from_utf8(&self.body)
349            .map_err(|_| Error::bad_request("Form data is not valid UTF-8"))?;
350        Ok(Query::parse_query_string(text))
351    }
352
353    fn require_form_content_type(&self) -> Result<()> {
354        let is_form = self
355            .headers
356            .get(http::header::CONTENT_TYPE)
357            .and_then(|v| v.to_str().ok())
358            .map(|v| {
359                v.split(';')
360                    .next()
361                    .unwrap_or("")
362                    .trim()
363                    .eq_ignore_ascii_case("application/x-www-form-urlencoded")
364            })
365            .unwrap_or(false);
366        if is_form {
367            Ok(())
368        } else {
369            Err(Error::custom(
370                415,
371                "Expected content-type application/x-www-form-urlencoded",
372            ))
373        }
374    }
375
376    /// Parse JSON body
377    pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
378        serde_json::from_slice(&self.body)
379            .map_err(|e| Error::bad_request(format!("Invalid JSON: {}", e)))
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386
387    fn form_request(content_type: Option<&str>, body: &'static str) -> Request {
388        let mut headers = http::HeaderMap::new();
389        if let Some(ct) = content_type {
390            headers.insert(http::header::CONTENT_TYPE, ct.parse().unwrap());
391        }
392        Request::new(
393            http::Method::POST,
394            "/".parse().unwrap(),
395            headers,
396            bytes::Bytes::from_static(body.as_bytes()),
397        )
398    }
399
400    #[derive(Debug, serde::Deserialize, PartialEq)]
401    struct Signup {
402        name: String,
403        age: u8,
404        newsletter: Option<bool>,
405    }
406
407    #[test]
408    fn params_parse_typed_values() {
409        let mut p = Params::new();
410        p.insert("id".into(), "42".into());
411        p.insert("name".into(), "x".into());
412        assert_eq!(p.parse::<u64>("id").unwrap(), 42);
413        assert_eq!(p.parse::<String>("name").unwrap(), "x");
414        let bad = p.parse::<u64>("name").unwrap_err();
415        assert_eq!(bad.status_code(), 400);
416        assert_eq!(bad.message(), "Invalid path parameter 'name'");
417        assert_eq!(p.parse::<u64>("missing").unwrap_err().status_code(), 400);
418        assert!(p.parse::<u8>("id").is_ok());
419        p.insert("big".into(), "300".into());
420        assert!(p.parse::<u8>("big").is_err());
421    }
422
423    #[test]
424    fn query_decodes_keys_values_and_plus() {
425        let q = Query::parse_query_string("q=rust+web&na%20me=a%26b&flag&&x=1");
426        assert_eq!(q.get("q"), Some("rust web"));
427        assert_eq!(q.get("na me"), Some("a&b"));
428        assert_eq!(q.get("flag"), Some(""));
429        assert!(q.contains("x") && !q.is_empty());
430    }
431
432    #[test]
433    fn query_parse_converts_types() {
434        let q = Query::parse_query_string("name=Ann&age=31&newsletter=true");
435        assert_eq!(
436            q.parse::<Signup>().unwrap(),
437            Signup {
438                name: "Ann".into(),
439                age: 31,
440                newsletter: Some(true)
441            }
442        );
443        let bad = Query::parse_query_string("name=Ann&age=old");
444        assert_eq!(bad.parse::<Signup>().unwrap_err().status_code(), 400);
445    }
446
447    #[test]
448    fn form_typed_and_untyped() {
449        let req = form_request(
450            Some("application/x-www-form-urlencoded; charset=UTF-8"),
451            "name=Ann+Lee&age=31",
452        );
453        let signup: Signup = req.form().unwrap();
454        assert_eq!(
455            signup,
456            Signup {
457                name: "Ann Lee".into(),
458                age: 31,
459                newsletter: None
460            }
461        );
462        assert_eq!(req.form_data().unwrap().get("name"), Some("Ann Lee"));
463    }
464
465    #[test]
466    fn form_rejects_wrong_content_type_and_bad_values() {
467        let json = form_request(Some("application/json"), "name=Ann&age=1");
468        assert_eq!(json.form::<Signup>().unwrap_err().status_code(), 415);
469        let none = form_request(None, "name=Ann&age=1");
470        assert_eq!(none.form_data().unwrap_err().status_code(), 415);
471
472        let bad = form_request(
473            Some("application/x-www-form-urlencoded"),
474            "name=Ann&age=300",
475        );
476        assert_eq!(bad.form::<Signup>().unwrap_err().status_code(), 400);
477    }
478}