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}