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