rocket_http_community/uri/
reference.rs

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