diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index ca6fd52b..6ec8e2a6 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -53,5 +53,5 @@ pub mod private { pub use crate::method::Method; pub use crate::status::{Status, StatusClass}; -pub use crate::raw_str::RawStr; +pub use crate::raw_str::{RawStr, RawStrBuf}; pub use crate::header::*; diff --git a/core/http/src/raw_str.rs b/core/http/src/raw_str.rs index 7e0a3166..8c3336b7 100644 --- a/core/http/src/raw_str.rs +++ b/core/http/src/raw_str.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::convert::AsRef; use std::cmp::Ordering; use std::str::Utf8Error; @@ -50,6 +50,26 @@ use crate::uncased::UncasedStr; #[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RawStr(str); +impl ToOwned for RawStr { + type Owned = RawStrBuf; + + fn to_owned(&self) -> Self::Owned { + RawStrBuf(self.to_string()) + } +} + +/// An owned version of [`RawStr`]. +#[repr(transparent)] +#[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RawStrBuf(String); + +impl RawStrBuf { + /// Cost-free conversion from `self` into a `String`. + pub fn into_string(self) -> String { + self.0 + } +} + impl RawStr { /// Constructs an `&RawStr` from a string-like type at no cost. /// @@ -68,6 +88,46 @@ impl RawStr { RawStr::ref_cast(string.as_ref()) } + /// Constructs an `&RawStr` from a string-like type at no cost. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::RawStr; + /// + /// let raw_str = RawStr::new("Hello, world!"); + /// + /// // `into` can also be used; note that the type must be specified + /// let raw_str: &RawStr = "Hello, world!".into(); + /// ``` + pub fn from_cow_str<'a>(cow: Cow<'a, str>) -> Cow<'a, RawStr> { + match cow { + Cow::Borrowed(b) => Cow::Borrowed(b.into()), + Cow::Owned(b) => Cow::Owned(b.into()), + } + } + + /// Constructs an `&RawStr` from a string-like type at no cost. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::RawStr; + /// + /// let raw_str = RawStr::new("Hello, world!"); + /// + /// // `into` can also be used; note that the type must be specified + /// let raw_str: &RawStr = "Hello, world!".into(); + /// ``` + pub fn into_cow_str<'a>(cow: Cow<'a, RawStr>) -> Cow<'a, str> { + match cow { + Cow::Borrowed(b) => Cow::Borrowed(b.as_str()), + Cow::Owned(b) => Cow::Owned(b.into_string()), + } + } + /// Performs percent decoding. fn _percent_decode(&self) -> percent_encoding::PercentDecode<'_> { percent_encoding::percent_decode(self.as_bytes()) @@ -822,6 +882,64 @@ impl fmt::Display for RawStr { } } +impl AsRef for RawStrBuf { + #[inline(always)] + fn as_ref(&self) -> &RawStr { + RawStr::new(self.0.as_str()) + } +} + +impl Borrow for RawStrBuf { + #[inline(always)] + fn borrow(&self) -> &RawStr { + self.as_ref() + } +} + +impl std::ops::Deref for RawStrBuf { + type Target = RawStr; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl fmt::Display for RawStrBuf { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Debug for RawStrBuf { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From for RawStrBuf { + #[inline(always)] + fn from(string: String) -> Self { + RawStrBuf(string) + } +} + +impl From<&str> for RawStrBuf { + #[inline(always)] + fn from(string: &str) -> Self { + string.to_string().into() + } +} + +impl From<&RawStr> for RawStrBuf { + #[inline(always)] + fn from(raw: &RawStr) -> Self { + raw.to_string().into() + } +} + #[cfg(test)] mod tests { use super::RawStr; diff --git a/core/http/src/uri/origin.rs b/core/http/src/uri/origin.rs index 4c9273c9..febdc8ed 100644 --- a/core/http/src/uri/origin.rs +++ b/core/http/src/uri/origin.rs @@ -3,10 +3,10 @@ use std::convert::TryFrom; use std::fmt::{self, Display}; use crate::ext::IntoOwned; -use crate::parse::{Indexed, Extent, IndexedStr}; +use crate::parse::{Indexed, Extent, IndexedStr, uri::tables::is_pchar}; use crate::uri::{self, UriPart, Query, Path}; use crate::uri::{Error, Segments, QuerySegments, as_utf8_unchecked}; -use crate::RawStr; +use crate::{RawStr, RawStrBuf}; use state::Storage; @@ -104,6 +104,19 @@ impl<'a, 'b> PartialEq> for Origin<'a> { } } +impl PartialEq for Origin<'_> { + fn eq(&self, other: &str) -> bool { + let (path, query) = RawStr::new(other).split_at_byte(b'?'); + self.path() == path && self.query().unwrap_or("".into()) == query + } +} + +impl PartialEq> for str { + fn eq(&self, other: &Origin<'_>) -> bool { + other.eq(self) + } +} + impl IntoOwned for Origin<'_> { type Owned = Origin<'static>; @@ -407,20 +420,27 @@ impl<'a> Origin<'a> { /// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri)); /// /// let old_uri = Origin::parse("/a/b/c/").unwrap(); + /// let expected = Origin::parse("/b/c/").unwrap(); + /// assert_eq!(old_uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), Some(expected)); + /// + /// let old_uri = Origin::parse("/a").unwrap(); + /// assert_eq!(old_uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), None); + /// + /// let old_uri = Origin::parse("/a/b/c/").unwrap(); /// assert_eq!(old_uri.map_path(|p| format!("hi/{}", p)), None); /// ``` #[inline] - pub fn map_path String>(&self, f: F) -> Option { - let path = f(self.path()); - if !path.starts_with('/') - || !path.bytes().all(|b| crate::parse::uri::tables::is_pchar(&b)) - { + pub fn map_path<'s, F, P>(&'s self, f: F) -> Option + where F: FnOnce(&'s RawStr) -> P, P: Into + 's + { + let path = f(self.path()).into(); + if !path.starts_with('/') || !path.as_bytes().iter().all(|b| is_pchar(&b)) { return None; } Some(Origin { source: self.source.clone(), - path: Cow::from(path).into(), + path: Cow::from(path.into_string()).into(), query: self.query.clone(), decoded_path_segs: Storage::new(), decoded_query_segs: Storage::new(), @@ -617,6 +637,15 @@ impl TryFrom for Origin<'static> { } } +// Because inference doesn't take `&String` to `&str`. +impl<'a> TryFrom<&'a String> for Origin<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a String) -> Result { + Origin::parse(value.as_str()) + } +} + impl<'a> TryFrom<&'a str> for Origin<'a> { type Error = Error<'a>;