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}