rocket_http_community/uri/
absolute.rs

1use std::borrow::Cow;
2
3use crate::ext::IntoOwned;
4use crate::parse::{Extent, IndexedStr};
5use crate::uri::{as_utf8_unchecked, fmt, Authority, Data, Error, Path, Query};
6
7/// A URI with a scheme, authority, path, and query.
8///
9/// # Structure
10///
11/// The following diagram illustrates the syntactic structure of an absolute
12/// URI with all optional parts:
13///
14/// ```text
15///  http://user:pass@domain.com:4444/foo/bar?some=query
16///  |--|  |------------------------||------| |--------|
17/// scheme          authority          path      query
18/// ```
19///
20/// Only the scheme part of the URI is required.
21///
22/// # Normalization
23///
24/// Rocket prefers _normalized_ absolute URIs, an absolute URI with the
25/// following properties:
26///
27///   * If there is an authority, the path is empty or absolute.
28///   * The path and query, if any, are normalized with no empty segments except
29///     optionally for one trailing slash.
30///
31/// The [`Absolute::is_normalized()`] method checks for normalization while
32/// [`Absolute::into_normalized()`] normalizes any absolute URI.
33///
34/// As an example, the following URIs are all valid, normalized URIs:
35///
36/// ```rust
37/// # extern crate rocket;
38/// # use rocket::http::uri::Absolute;
39/// # let valid_uris = [
40/// "http://rocket.rs",
41/// "http://rocket.rs/",
42/// "ftp:/a/b/",
43/// "ftp:/a/b/?",
44/// "scheme:/foo/bar",
45/// "scheme:/foo/bar/",
46/// "scheme:/foo/bar/?",
47/// "scheme:/foo/bar/?abc",
48/// # ];
49/// # for uri in &valid_uris {
50/// #     let uri = Absolute::parse(uri).unwrap();
51/// #     assert!(uri.is_normalized(), "{} non-normal?", uri);
52/// # }
53/// ```
54///
55/// By contrast, the following are valid but non-normal URIs:
56///
57/// ```rust
58/// # extern crate rocket;
59/// # use rocket::http::uri::Absolute;
60/// # let invalid = [
61/// "ftp:/a//c//d",         // two empty segments
62/// "ftp:/?foo&",           // trailing empty query segment
63/// "ftp:/?fooa&&b",        // empty query segment
64/// # ];
65/// # for uri in &invalid {
66/// #   assert!(!Absolute::parse(uri).unwrap().is_normalized());
67/// # }
68/// ```
69///
70/// # (De)serialization
71///
72/// `Absolute` is both `Serialize` and `Deserialize`:
73///
74/// ```rust
75/// # #[cfg(feature = "serde")] mod serde_impl {
76/// # use serde as serde;
77/// use serde::{Serialize, Deserialize};
78/// use rocket::http::uri::Absolute;
79///
80/// #[derive(Deserialize, Serialize)]
81/// # #[serde(crate = "serde")]
82/// struct UriOwned {
83///     uri: Absolute<'static>,
84/// }
85///
86/// #[derive(Deserialize, Serialize)]
87/// # #[serde(crate = "serde")]
88/// struct UriBorrowed<'a> {
89///     uri: Absolute<'a>,
90/// }
91/// # }
92/// ```
93#[derive(Debug, Clone)]
94pub struct Absolute<'a> {
95    pub(crate) source: Option<Cow<'a, str>>,
96    pub(crate) scheme: IndexedStr<'a>,
97    pub(crate) authority: Option<Authority<'a>>,
98    pub(crate) path: Data<'a, fmt::Path>,
99    pub(crate) query: Option<Data<'a, fmt::Query>>,
100}
101
102impl<'a> Absolute<'a> {
103    /// Parses the string `string` into an `Absolute`. Parsing will never
104    /// allocate. Returns an `Error` if `string` is not a valid absolute URI.
105    ///
106    /// # Example
107    ///
108    /// ```rust
109    /// # #[macro_use] extern crate rocket;
110    /// use rocket::http::uri::Absolute;
111    ///
112    /// // Parse a valid authority URI.
113    /// let uri = Absolute::parse("https://rocket.rs").expect("valid URI");
114    /// assert_eq!(uri.scheme(), "https");
115    /// assert_eq!(uri.authority().unwrap().host(), "rocket.rs");
116    /// assert_eq!(uri.path(), "");
117    /// assert!(uri.query().is_none());
118    ///
119    /// // Prefer to use `uri!()` when the input is statically known:
120    /// let uri = uri!("https://rocket.rs");
121    /// assert_eq!(uri.scheme(), "https");
122    /// assert_eq!(uri.authority().unwrap().host(), "rocket.rs");
123    /// assert_eq!(uri.path(), "");
124    /// assert!(uri.query().is_none());
125    /// ```
126    pub fn parse(string: &'a str) -> Result<Absolute<'a>, Error<'a>> {
127        crate::parse::uri::absolute_from_str(string)
128    }
129
130    /// Parses the string `string` into an `Absolute`. Allocates minimally on
131    /// success and error.
132    ///
133    /// This method should be used instead of [`Absolute::parse()`] when the
134    /// source URI is already a `String`. Returns an `Error` if `string` is not
135    /// a valid absolute URI.
136    ///
137    /// # Example
138    ///
139    /// ```rust
140    /// # extern crate rocket;
141    /// use rocket::http::uri::Absolute;
142    ///
143    /// let source = format!("https://rocket.rs/foo/{}/three", 2);
144    /// let uri = Absolute::parse_owned(source).expect("valid URI");
145    /// assert_eq!(uri.authority().unwrap().host(), "rocket.rs");
146    /// assert_eq!(uri.path(), "/foo/2/three");
147    /// assert!(uri.query().is_none());
148    /// ```
149    // TODO: Avoid all allocations.
150    pub fn parse_owned(string: String) -> Result<Absolute<'static>, Error<'static>> {
151        let absolute = Absolute::parse(&string).map_err(|e| e.into_owned())?;
152        debug_assert!(absolute.source.is_some(), "Absolute parsed w/o source");
153
154        let absolute = Absolute {
155            scheme: absolute.scheme.into_owned(),
156            authority: absolute.authority.into_owned(),
157            query: absolute.query.into_owned(),
158            path: absolute.path.into_owned(),
159            source: Some(Cow::Owned(string)),
160        };
161
162        Ok(absolute)
163    }
164
165    /// Returns the scheme part of the absolute URI.
166    ///
167    /// # Example
168    ///
169    /// ```rust
170    /// # #[macro_use] extern crate rocket;
171    /// let uri = uri!("ftp://127.0.0.1");
172    /// assert_eq!(uri.scheme(), "ftp");
173    /// ```
174    #[inline(always)]
175    pub fn scheme(&self) -> &str {
176        self.scheme.from_cow_source(&self.source)
177    }
178
179    /// Returns the authority part of the absolute URI, if there is one.
180    ///
181    /// # Example
182    ///
183    /// ```rust
184    /// # #[macro_use] extern crate rocket;
185    /// let uri = uri!("https://rocket.rs:80");
186    /// assert_eq!(uri.scheme(), "https");
187    /// let authority = uri.authority().unwrap();
188    /// assert_eq!(authority.host(), "rocket.rs");
189    /// assert_eq!(authority.port(), Some(80));
190    ///
191    /// let uri = uri!("file:/web/home");
192    /// assert_eq!(uri.authority(), None);
193    /// ```
194    #[inline(always)]
195    pub fn authority(&self) -> Option<&Authority<'a>> {
196        self.authority.as_ref()
197    }
198
199    /// Returns the path part. May be empty.
200    ///
201    /// # Example
202    ///
203    /// ```rust
204    /// # #[macro_use] extern crate rocket;
205    /// let uri = uri!("ftp://rocket.rs/foo/bar");
206    /// assert_eq!(uri.path(), "/foo/bar");
207    ///
208    /// let uri = uri!("ftp://rocket.rs");
209    /// assert!(uri.path().is_empty());
210    /// ```
211    #[inline(always)]
212    pub fn path(&self) -> Path<'_> {
213        Path {
214            source: &self.source,
215            data: &self.path,
216        }
217    }
218
219    /// Returns the query part with the leading `?`. May be empty.
220    ///
221    /// # Example
222    ///
223    /// ```rust
224    /// # #[macro_use] extern crate rocket;
225    /// let uri = uri!("ftp://rocket.rs/foo?bar");
226    /// assert_eq!(uri.query().unwrap(), "bar");
227    ///
228    /// let uri = uri!("ftp://rocket.rs");
229    /// assert!(uri.query().is_none());
230    /// ```
231    #[inline(always)]
232    pub fn query(&self) -> Option<Query<'_>> {
233        self.query.as_ref().map(|data| Query {
234            source: &self.source,
235            data,
236        })
237    }
238
239    /// Removes the query part of this URI, if there is any.
240    ///
241    /// # Example
242    ///
243    /// ```rust
244    /// # #[macro_use] extern crate rocket;
245    /// let mut uri = uri!("ftp://rocket.rs/foo?bar");
246    /// assert_eq!(uri.query().unwrap(), "bar");
247    ///
248    /// uri.clear_query();
249    /// assert!(uri.query().is_none());
250    /// ```
251    #[inline(always)]
252    pub fn clear_query(&mut self) {
253        self.set_query(None);
254    }
255
256    /// Returns `true` if `self` is normalized. Otherwise, returns `false`.
257    ///
258    /// See [Normalization](#normalization) for more information on what it
259    /// means for an absolute URI to be normalized. Note that `uri!()` always
260    /// returns a normalized version of its static input.
261    ///
262    /// # Example
263    ///
264    /// ```rust
265    /// # #[macro_use] extern crate rocket;
266    /// use rocket::http::uri::Absolute;
267    ///
268    /// assert!(uri!("http://rocket.rs").is_normalized());
269    /// assert!(uri!("http://rocket.rs///foo////bar").is_normalized());
270    ///
271    /// assert!(Absolute::parse("http:/").unwrap().is_normalized());
272    /// assert!(Absolute::parse("http://").unwrap().is_normalized());
273    /// assert!(Absolute::parse("http://foo.rs/foo/bar").unwrap().is_normalized());
274    /// assert!(Absolute::parse("foo:bar").unwrap().is_normalized());
275    /// assert!(Absolute::parse("git://rocket.rs/").unwrap().is_normalized());
276    ///
277    /// assert!(!Absolute::parse("http:/foo//bar").unwrap().is_normalized());
278    /// assert!(!Absolute::parse("foo:bar?baz&&bop").unwrap().is_normalized());
279    /// ```
280    pub fn is_normalized(&self) -> bool {
281        let normalized_query = self.query().is_none_or(|q| q.is_normalized());
282        if self.authority().is_some() && !self.path().is_empty() {
283            self.path().is_normalized(true) && normalized_query
284        } else {
285            self.path().is_normalized(false) && normalized_query
286        }
287    }
288
289    /// Normalizes `self` in-place. Does nothing if `self` is already
290    /// normalized.
291    ///
292    /// # Example
293    ///
294    /// ```rust
295    /// use rocket::http::uri::Absolute;
296    ///
297    /// let mut uri = Absolute::parse("git://rocket.rs").unwrap();
298    /// assert!(uri.is_normalized());
299    ///
300    /// let mut uri = Absolute::parse("git://rocket.rs/").unwrap();
301    /// assert!(uri.is_normalized());
302    ///
303    /// let mut uri = Absolute::parse("http:/foo//bar").unwrap();
304    /// assert!(!uri.is_normalized());
305    /// uri.normalize();
306    /// assert!(uri.is_normalized());
307    ///
308    /// let mut uri = Absolute::parse("foo:bar?baz&&bop").unwrap();
309    /// assert!(!uri.is_normalized());
310    /// uri.normalize();
311    /// assert!(uri.is_normalized());
312    /// ```
313    pub fn normalize(&mut self) {
314        if self.authority().is_some() && !self.path().is_empty() {
315            if !self.path().is_normalized(true) {
316                self.path = self.path().to_normalized(true, true);
317            }
318        } else if !self.path().is_normalized(false) {
319            self.path = self.path().to_normalized(false, true);
320        }
321
322        if let Some(query) = self.query() {
323            if !query.is_normalized() {
324                self.query = Some(query.to_normalized());
325            }
326        }
327    }
328
329    /// Normalizes `self`. This is a no-op if `self` is already normalized.
330    ///
331    /// # Example
332    ///
333    /// ```rust
334    /// use rocket::http::uri::Absolute;
335    ///
336    /// let mut uri = Absolute::parse("git://rocket.rs/").unwrap();
337    /// assert!(uri.is_normalized());
338    ///
339    /// let mut uri = Absolute::parse("http:/foo//bar").unwrap();
340    /// assert!(!uri.is_normalized());
341    /// assert!(uri.into_normalized().is_normalized());
342    ///
343    /// let mut uri = Absolute::parse("foo:bar?baz&&bop").unwrap();
344    /// assert!(!uri.is_normalized());
345    /// assert!(uri.into_normalized().is_normalized());
346    /// ```
347    pub fn into_normalized(mut self) -> Self {
348        self.normalize();
349        self
350    }
351
352    /// Sets the authority in `self` to `authority`.
353    ///
354    /// # Example
355    ///
356    /// ```rust
357    /// # #[macro_use] extern crate rocket;
358    /// let mut uri = uri!("https://rocket.rs:80");
359    /// let authority = uri.authority().unwrap();
360    /// assert_eq!(authority.host(), "rocket.rs");
361    /// assert_eq!(authority.port(), Some(80));
362    ///
363    /// let new_authority = uri!("rocket.rs:443");
364    /// uri.set_authority(new_authority);
365    /// let authority = uri.authority().unwrap();
366    /// assert_eq!(authority.host(), "rocket.rs");
367    /// assert_eq!(authority.port(), Some(443));
368    /// ```
369    #[inline(always)]
370    pub fn set_authority(&mut self, authority: Authority<'a>) {
371        self.authority = Some(authority);
372    }
373
374    /// Sets the authority in `self` to `authority` and returns `self`.
375    ///
376    /// # Example
377    ///
378    /// ```rust
379    /// # #[macro_use] extern crate rocket;
380    /// let uri = uri!("https://rocket.rs:80");
381    /// let authority = uri.authority().unwrap();
382    /// assert_eq!(authority.host(), "rocket.rs");
383    /// assert_eq!(authority.port(), Some(80));
384    ///
385    /// let new_authority = uri!("rocket.rs");
386    /// let uri = uri.with_authority(new_authority);
387    /// let authority = uri.authority().unwrap();
388    /// assert_eq!(authority.host(), "rocket.rs");
389    /// assert_eq!(authority.port(), None);
390    /// ```
391    #[inline(always)]
392    pub fn with_authority(mut self, authority: Authority<'a>) -> Self {
393        self.set_authority(authority);
394        self
395    }
396}
397
398/// PRIVATE API.
399#[doc(hidden)]
400impl<'a> Absolute<'a> {
401    /// PRIVATE. Used by parser.
402    ///
403    /// SAFETY: `source` must be valid UTF-8.
404    /// CORRECTNESS: `scheme` must be non-empty.
405    #[inline]
406    pub(crate) unsafe fn raw(
407        source: Cow<'a, [u8]>,
408        scheme: Extent<&'a [u8]>,
409        authority: Option<Authority<'a>>,
410        path: Extent<&'a [u8]>,
411        query: Option<Extent<&'a [u8]>>,
412    ) -> Absolute<'a> {
413        Absolute {
414            source: Some(as_utf8_unchecked(source)),
415            scheme: scheme.into(),
416            authority,
417            path: Data::raw(path),
418            query: query.map(Data::raw),
419        }
420    }
421
422    /// PRIVATE. Used by tests.
423    #[cfg(test)]
424    pub fn new(
425        scheme: &'a str,
426        authority: impl Into<Option<Authority<'a>>>,
427        path: &'a str,
428        query: impl Into<Option<&'a str>>,
429    ) -> Absolute<'a> {
430        assert!(!scheme.is_empty());
431        Absolute::const_new(scheme, authority.into(), path, query.into())
432    }
433
434    /// PRIVATE. Used by codegen and `Host`.
435    #[doc(hidden)]
436    pub const fn const_new(
437        scheme: &'a str,
438        authority: Option<Authority<'a>>,
439        path: &'a str,
440        query: Option<&'a str>,
441    ) -> Absolute<'a> {
442        Absolute {
443            source: None,
444            scheme: IndexedStr::Concrete(Cow::Borrowed(scheme)),
445            authority,
446            path: Data {
447                value: IndexedStr::Concrete(Cow::Borrowed(path)),
448                decoded_segments: state::InitCell::new(),
449            },
450            query: match query {
451                Some(query) => Some(Data {
452                    value: IndexedStr::Concrete(Cow::Borrowed(query)),
453                    decoded_segments: state::InitCell::new(),
454                }),
455                None => None,
456            },
457        }
458    }
459
460    // TODO: Have a way to get a validated `path` to do this. See `Path`?
461    pub(crate) fn set_path<P>(&mut self, path: P)
462    where
463        P: Into<Cow<'a, str>>,
464    {
465        self.path = Data::new(path.into());
466    }
467
468    // TODO: Have a way to get a validated `query` to do this. See `Query`?
469    pub(crate) fn set_query<Q: Into<Option<Cow<'a, str>>>>(&mut self, query: Q) {
470        self.query = query.into().map(Data::new);
471    }
472}
473
474impl_serde!(Absolute<'a>, "an absolute-form URI");
475
476impl_traits!(Absolute, scheme, authority, path, query);
477
478impl std::fmt::Display for Absolute<'_> {
479    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480        write!(f, "{}:", self.scheme())?;
481        if let Some(authority) = self.authority() {
482            write!(f, "//{}", authority)?;
483        }
484
485        write!(f, "{}", self.path())?;
486        if let Some(query) = self.query() {
487            write!(f, "?{}", query)?;
488        }
489
490        Ok(())
491    }
492}