Initial implementation of RawStr.

This commit is contained in:
Sergio Benitez 2017-03-30 17:52:02 -07:00
parent b49c89af7a
commit 709acf18a4
4 changed files with 238 additions and 1 deletions

View File

@ -18,6 +18,7 @@ mod content_type;
mod status;
mod header;
mod accept;
mod raw_str;
pub(crate) mod parse;
@ -32,6 +33,7 @@ pub use self::content_type::ContentType;
pub use self::accept::{Accept, WeightedMediaType};
pub use self::status::{Status, StatusClass};
pub use self::header::{Header, HeaderMap};
pub use self::raw_str::RawStr;
pub use self::media_type::MediaType;
pub use self::cookies::*;

234
lib/src/http/raw_str.rs Normal file
View File

@ -0,0 +1,234 @@
use std::ops::{Deref, DerefMut};
use std::borrow::Cow;
use std::convert::AsRef;
use std::cmp::Ordering;
use std::ascii::AsciiExt;
use std::str::Utf8Error;
use std::fmt;
use url;
use http::uncased::UncasedStr;
/// A reference to a raw HTTP string.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStr(str);
impl RawStr {
#[inline(always)]
pub fn from_str<'a>(string: &'a str) -> &'a RawStr {
string.into()
}
#[inline(always)]
pub fn as_str(&self) -> &str {
self
}
#[inline(always)]
pub fn as_uncased_str(&self) -> &UncasedStr {
self.as_str().into()
}
/// Returns a URL-decoded version of the string. If the percent encoded
/// values are not valid UTF-8, an `Err` is returned.
#[inline]
pub fn percent_decode(&self) -> Result<Cow<str>, Utf8Error> {
url::percent_encoding::percent_decode(self.as_bytes()).decode_utf8()
}
/// Returns a URL-decoded version of the path. Any invalid UTF-8
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
/// replacement character.
#[inline]
pub fn percent_decode_lossy(&self) -> Cow<str> {
url::percent_encoding::percent_decode(self.as_bytes()).decode_utf8_lossy()
}
/// Do some HTML escaping.
///
/// # Example
///
/// Strings with HTML sequences are escaped:
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str: &RawStr = "<b>Hi!</b>".into();
/// let escaped = raw_str.html_escape();
/// assert_eq!(escaped, "&lt;b&gt;Hi!&lt;&#x2F;b&gt;");
///
/// let raw_str: &RawStr = "Hello, <i>world!</i>".into();
/// let escaped = raw_str.html_escape();
/// assert_eq!(escaped, "Hello, &lt;i&gt;world!&lt;&#x2F;i&gt;");
/// ```
///
/// Strings without HTML sequences remain untouched:
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str: &RawStr = "Hello!".into();
/// let escaped = raw_str.html_escape();
/// assert_eq!(escaped, "Hello!");
///
/// let raw_str: &RawStr = "大阪".into();
/// let escaped = raw_str.html_escape();
/// assert_eq!(escaped, "大阪");
/// ```
pub fn html_escape(&self) -> Cow<str> {
let mut escaped = false;
let mut allocated = Vec::new(); // this is allocation free
for c in self.as_bytes() {
match *c {
b'&' | b'<' | b'>' | b'"' | b'\'' | b'/' | b'`' => {
if !escaped {
let i = (c as *const u8 as usize) - (self.as_ptr() as usize);
allocated = Vec::with_capacity(self.len() * 2);
allocated.extend_from_slice(&self.as_bytes()[..i]);
}
match *c {
b'&' => allocated.extend_from_slice(b"&amp;"),
b'<' => allocated.extend_from_slice(b"&lt;"),
b'>' => allocated.extend_from_slice(b"&gt;"),
b'"' => allocated.extend_from_slice(b"&quot;"),
b'\'' => allocated.extend_from_slice(b"&#x27;"),
b'/' => allocated.extend_from_slice(b"&#x2F;"),
// Old versions of IE treat a ` as a '.
b'`' => allocated.extend_from_slice(b"&#96;"),
_ => unreachable!()
}
escaped = true;
}
_ if escaped => allocated.push(*c),
_ => { }
}
}
if escaped {
unsafe { Cow::Owned(String::from_utf8_unchecked(allocated)) }
} else {
Cow::Borrowed(self.as_str())
}
}
}
impl<'a> From<&'a str> for &'a RawStr {
#[inline(always)]
fn from(string: &'a str) -> &'a RawStr {
unsafe { ::std::mem::transmute(string) }
}
}
impl PartialEq<str> for RawStr {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for RawStr {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl<'a> PartialEq<String> for &'a RawStr {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialOrd<str> for RawStr {
#[inline(always)]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
(self as &str).partial_cmp(other)
}
}
impl AsRef<str> for RawStr {
#[inline(always)]
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<[u8]> for RawStr {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ToString for RawStr {
#[inline(always)]
fn to_string(&self) -> String {
String::from(self.as_str())
}
}
impl AsciiExt for RawStr {
type Owned = String;
#[inline(always)]
fn is_ascii(&self) -> bool { (self as &str).is_ascii() }
#[inline(always)]
fn to_ascii_uppercase(&self) -> String { (self as &str).to_ascii_uppercase() }
#[inline(always)]
fn to_ascii_lowercase(&self) -> String { (self as &str).to_ascii_lowercase() }
#[inline(always)]
fn make_ascii_uppercase(&mut self) { (self as &mut str).make_ascii_uppercase() }
#[inline(always)]
fn make_ascii_lowercase(&mut self) { (self as &mut str).make_ascii_lowercase() }
#[inline(always)]
fn eq_ignore_ascii_case(&self, o: &RawStr) -> bool {
(self as &str).eq_ignore_ascii_case(o as &str)
}
}
impl Deref for RawStr {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
&self.0
}
}
impl DerefMut for RawStr {
#[inline(always)]
fn deref_mut(&mut self) -> &mut str {
&mut self.0
}
}
impl fmt::Display for RawStr {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::RawStr;
#[test]
fn can_compare() {
let raw_str = RawStr::from_str("abc");
assert_eq!(raw_str, "abc");
assert_eq!("abc", raw_str.as_str());
assert_eq!(raw_str, RawStr::from_str("abc"));
assert_eq!(raw_str, "abc".to_string());
assert_eq!("abc".to_string(), raw_str.as_str());
}
}

View File

@ -23,6 +23,7 @@ use std::fmt;
pub struct UncasedStr(str);
impl UncasedStr {
#[inline(always)]
pub fn as_str(&self) -> &str {
&self.0
}

View File

@ -216,7 +216,7 @@ impl<'a> URI<'a> {
/// let decoded_path = URI::percent_decode(uri.path().as_bytes()).expect("decoded");
/// assert_eq!(decoded_path, "/Hello, world!");
/// ```
pub fn percent_decode(string: &[u8]) -> Result<Cow<str>, Utf8Error> {
pub fn percent_decode(string: &[u8]) -> Result<Cow<str>, Utf8Error> {
let decoder = url::percent_encoding::percent_decode(string);
decoder.decode_utf8()
}