rocket_http/uri/
host.rs

1use std::fmt::{self, Display};
2
3use crate::uncased::UncasedStr;
4use crate::uri::error::Error;
5use crate::uri::{Absolute, Authority};
6
7/// A domain and port identified by a client as the server being messaged.
8///
9/// For requests made via HTTP/1.1, a host is identified via the `HOST` header.
10/// In HTTP/2 and HTTP/3, this information is instead communicated via the
11/// `:authority` and `:port` pseudo-header request fields. It is a
12/// client-controlled value via which the client communicates to the server the
13/// domain name and port it is attempting to communicate with. The following
14/// diagram illustrates the syntactic structure of a `Host`:
15///
16/// ```text
17/// some.domain.foo:8088
18/// |-------------| |--|
19///      domain     port
20/// ```
21///
22/// Only the domain part is required. Its value is case-insensitive.
23///
24/// # URI Construction
25///
26/// A `Host` is _not_ a [`Uri`](crate::uri::Uri), and none of Rocket's APIs will
27/// accept a `Host` value as such. This is because doing so would facilitate the
28/// construction of URIs to internal routes in a manner controllable by an
29/// attacker, inevitably leading to "HTTP Host header attacks".
30///
31/// Instead, a `Host` must be checked before being converted to a [`Uri`]
32/// value. The [`Host::to_authority`] and [`Host::to_absolute`] methods provide
33/// these mechanisms:
34///
35/// ```rust
36/// # #[macro_use] extern crate rocket;
37/// # type Token = String;
38/// use rocket::http::uri::Host;
39///
40/// // A sensitive URI we want to prefix with safe hosts.
41/// #[get("/token?<secret>")]
42/// fn token(secret: Token) { /* .. */ }
43///
44/// // Whitelist of known hosts. In a real setting, you might retrieve this
45/// // list from config at ignite-time using tools like `AdHoc::config()`.
46/// const WHITELIST: [Host<'static>; 4] = [
47///     Host::new(uri!("rocket.rs")),
48///     Host::new(uri!("rocket.rs:443")),
49///     Host::new(uri!("guide.rocket.rs")),
50///     Host::new(uri!("guide.rocket.rs:443")),
51/// ];
52///
53/// // Use `Host::to_absolute()` to case-insensitively check a host against a
54/// // whitelist, returning an `Absolute` usable as a `uri!()` prefix.
55/// let host = Host::new(uri!("guide.ROCKET.rs"));
56/// let prefix = host.to_absolute("https", &WHITELIST);
57///
58/// // Since `guide.rocket.rs` is in the whitelist, `prefix` is `Some`.
59/// assert!(prefix.is_some());
60/// if let Some(prefix) = prefix {
61///     // We can use this prefix to safely construct URIs.
62///     let uri = uri!(prefix, token("some-secret-token"));
63///     assert_eq!(uri, "https://guide.ROCKET.rs/token?secret=some-secret-token");
64/// }
65/// ```
66///
67/// # (De)serialization
68///
69/// `Host` is both `Serialize` and `Deserialize`:
70///
71/// ```rust
72/// # #[cfg(feature = "serde")] mod serde {
73/// # use serde_ as serde;
74/// use serde::{Serialize, Deserialize};
75/// use rocket::http::uri::Host;
76///
77/// #[derive(Deserialize, Serialize)]
78/// # #[serde(crate = "serde_")]
79/// struct UriOwned {
80///     uri: Host<'static>,
81/// }
82///
83/// #[derive(Deserialize, Serialize)]
84/// # #[serde(crate = "serde_")]
85/// struct UriBorrowed<'a> {
86///     uri: Host<'a>,
87/// }
88/// # }
89/// ```
90#[derive(Debug, Clone)]
91pub struct Host<'a>(Authority<'a>);
92
93impl<'a> Host<'a> {
94    /// Create a new `Host` from an `Authority`. Only the `host` and `port`
95    /// parts are preserved.
96    ///
97    /// ```rust
98    /// # #[macro_use] extern crate rocket;
99    /// use rocket::http::uri::Host;
100    ///
101    /// let host = Host::new(uri!("developer.mozilla.org"));
102    /// assert_eq!(host.to_string(), "developer.mozilla.org");
103    ///
104    /// let host = Host::new(uri!("foo:bar@developer.mozilla.org:1234"));
105    /// assert_eq!(host.to_string(), "developer.mozilla.org:1234");
106    ///
107    /// let host = Host::new(uri!("rocket.rs:443"));
108    /// assert_eq!(host.to_string(), "rocket.rs:443");
109    /// ```
110    pub const fn new(authority: Authority<'a>) -> Self {
111        Host(authority)
112    }
113
114    /// Parses the string `string` into a `Host`. Parsing will never allocate.
115    /// Returns an `Error` if `string` is not a valid authority URI, meaning
116    /// that this parser accepts a `user_info` part for compatibility but
117    /// discards it.
118    ///
119    /// # Example
120    ///
121    /// ```rust
122    /// # #[macro_use] extern crate rocket;
123    /// use rocket::http::uri::Host;
124    ///
125    /// // Parse from a valid authority URI.
126    /// let host = Host::parse("user:pass@domain").expect("valid host");
127    /// assert_eq!(host.domain(), "domain");
128    /// assert_eq!(host.port(), None);
129    ///
130    /// // Parse from a valid host.
131    /// let host = Host::parse("domain:311").expect("valid host");
132    /// assert_eq!(host.domain(), "doMaIN");
133    /// assert_eq!(host.port(), Some(311));
134    ///
135    /// // Invalid hosts fail to parse.
136    /// Host::parse("https://rocket.rs").expect_err("invalid host");
137    ///
138    /// // Prefer to use `uri!()` when the input is statically known:
139    /// let host = Host::new(uri!("domain"));
140    /// assert_eq!(host.domain(), "domain");
141    /// assert_eq!(host.port(), None);
142    /// ```
143    pub fn parse(string: &'a str) -> Result<Host<'a>, Error<'a>> {
144        Host::parse_bytes(string.as_bytes())
145    }
146
147    /// PRIVATE: Used by core.
148    #[doc(hidden)]
149    pub fn parse_bytes(bytes: &'a [u8]) -> Result<Host<'a>, Error<'a>> {
150        crate::parse::uri::authority_from_bytes(bytes).map(Host::new)
151    }
152
153    /// Parses the string `string` into an `Host`. Parsing never allocates
154    /// on success. May allocate on error.
155    ///
156    /// This method should be used instead of [`Host::parse()`] when the source
157    /// is already a `String`. Returns an `Error` if `string` is not a valid
158    /// authority URI, meaning that this parser accepts a `user_info` part for
159    /// compatibility but discards it.
160    ///
161    /// # Example
162    ///
163    /// ```rust
164    /// # extern crate rocket;
165    /// use rocket::http::uri::Host;
166    ///
167    /// let source = format!("rocket.rs:8000");
168    /// let host = Host::parse_owned(source).expect("valid host");
169    /// assert_eq!(host.domain(), "rocket.rs");
170    /// assert_eq!(host.port(), Some(8000));
171    /// ```
172    pub fn parse_owned(string: String) -> Result<Host<'static>, Error<'static>> {
173        Authority::parse_owned(string).map(Host::new)
174    }
175
176    /// Returns the case-insensitive domain part of the host.
177    ///
178    /// # Example
179    ///
180    /// ```rust
181    /// # #[macro_use] extern crate rocket;
182    /// use rocket::http::uri::Host;
183    ///
184    /// let host = Host::new(uri!("domain.com:123"));
185    /// assert_eq!(host.domain(), "domain.com");
186    ///
187    /// let host = Host::new(uri!("username:password@domain:123"));
188    /// assert_eq!(host.domain(), "domain");
189    ///
190    /// let host = Host::new(uri!("[1::2]:123"));
191    /// assert_eq!(host.domain(), "[1::2]");
192    /// ```
193    #[inline]
194    pub fn domain(&self) -> &UncasedStr {
195        self.0.host().into()
196    }
197
198    /// Returns the port part of the host, if there is one.
199    ///
200    /// # Example
201    ///
202    /// ```rust
203    /// # #[macro_use] extern crate rocket;
204    /// use rocket::http::uri::Host;
205    ///
206    /// // With a port.
207    /// let host = Host::new(uri!("domain:123"));
208    /// assert_eq!(host.port(), Some(123));
209    ///
210    /// let host = Host::new(uri!("domain.com:8181"));
211    /// assert_eq!(host.port(), Some(8181));
212    ///
213    /// // Without a port.
214    /// let host = Host::new(uri!("domain.foo.bar.tld"));
215    /// assert_eq!(host.port(), None);
216    /// ```
217    #[inline(always)]
218    pub fn port(&self) -> Option<u16> {
219        self.0.port()
220    }
221
222    /// Checks `self` against `whitelist`. If `self` is in `whitelist`, returns
223    /// an [`Authority`] URI representing self. Otherwise, returns `None`.
224    /// Domain comparison is case-insensitive.
225    ///
226    /// See [URI construction](Self#uri-construction) for more.
227    ///
228    /// # Example
229    ///
230    /// ```rust
231    /// # #[macro_use] extern crate rocket;
232    /// use rocket::http::uri::Host;
233    ///
234    /// let whitelist = &[Host::new(uri!("domain.tld"))];
235    ///
236    /// // A host in the whitelist returns `Some`.
237    /// let host = Host::new(uri!("domain.tld"));
238    /// let uri = host.to_authority(whitelist);
239    /// assert!(uri.is_some());
240    /// assert_eq!(uri.unwrap().to_string(), "domain.tld");
241    ///
242    /// let host = Host::new(uri!("foo:bar@doMaIN.tLd"));
243    /// let uri = host.to_authority(whitelist);
244    /// assert!(uri.is_some());
245    /// assert_eq!(uri.unwrap().to_string(), "doMaIN.tLd");
246    ///
247    /// // A host _not_ in the whitelist returns `None`.
248    /// let host = Host::new(uri!("domain.tld:1234"));
249    /// let uri = host.to_authority(whitelist);
250    /// assert!(uri.is_none());
251    /// ```
252    pub fn to_authority<'h, W>(&self, whitelist: W) -> Option<Authority<'a>>
253        where W: IntoIterator<Item = &'h Host<'h>>
254    {
255        let mut auth = whitelist.into_iter().any(|h| h == self).then(|| self.0.clone())?;
256        auth.user_info = None;
257        Some(auth)
258    }
259
260    /// Checks `self` against `whitelist`. If `self` is in `whitelist`, returns
261    /// an [`Absolute`] URI representing `self` with scheme `scheme`. Otherwise,
262    /// returns `None`. Domain comparison is case-insensitive.
263    ///
264    /// See [URI construction](Self#uri-construction) for more.
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// # #[macro_use] extern crate rocket;
270    /// use rocket::http::uri::Host;
271    ///
272    /// let whitelist = &[Host::new(uri!("domain.tld:443"))];
273    ///
274    /// // A host in the whitelist returns `Some`.
275    /// let host = Host::new(uri!("user@domain.tld:443"));
276    /// let uri = host.to_absolute("http", whitelist);
277    /// assert!(uri.is_some());
278    /// assert_eq!(uri.unwrap().to_string(), "http://domain.tld:443");
279    ///
280    /// let host = Host::new(uri!("domain.TLD:443"));
281    /// let uri = host.to_absolute("https", whitelist);
282    /// assert!(uri.is_some());
283    /// assert_eq!(uri.unwrap().to_string(), "https://domain.TLD:443");
284    ///
285    /// // A host _not_ in the whitelist returns `None`.
286    /// let host = Host::new(uri!("domain.tld"));
287    /// let uri = host.to_absolute("http", whitelist);
288    /// assert!(uri.is_none());
289    /// ```
290    pub fn to_absolute<'h, W>(&self, scheme: &'a str, whitelist: W) -> Option<Absolute<'a>>
291        where W: IntoIterator<Item = &'h Host<'h>>
292    {
293        let scheme = crate::parse::uri::scheme_from_str(scheme).ok()?;
294        let authority = self.to_authority(whitelist)?;
295        Some(Absolute::const_new(scheme, Some(authority), "", None))
296    }
297}
298
299impl_serde!(Host<'a>, "an HTTP host");
300
301impl_base_traits!(Host, domain, port);
302
303impl crate::ext::IntoOwned for Host<'_> {
304    type Owned = Host<'static>;
305
306    fn into_owned(self) -> Host<'static> {
307        Host(self.0.into_owned())
308    }
309}
310
311impl<'a> From<Authority<'a>> for Host<'a> {
312    fn from(auth: Authority<'a>) -> Self {
313        Host::new(auth)
314    }
315}
316
317impl Display for Host<'_> {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        self.domain().fmt(f)?;
320        if let Some(port) = self.port() {
321            write!(f, ":{}", port)?;
322        }
323
324        Ok(())
325    }
326}