mirror of https://github.com/rwf2/Rocket.git
Make Content-Type case-preserving; add 'params' method.
This commit is contained in:
parent
855d9b7b00
commit
2da08a975c
|
@ -24,10 +24,14 @@ fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
|
|||
|
||||
fn content_type_to_expr(ecx: &ExtCtxt, ct: Option<ContentType>) -> Option<P<Expr>> {
|
||||
ct.map(|ct| {
|
||||
let (ttype, subtype) = (ct.ttype, ct.subtype);
|
||||
let (top, sub) = (ct.ttype.as_str(), ct.subtype.as_str());
|
||||
quote_expr!(ecx, ::rocket::http::ContentType {
|
||||
ttype: ::std::borrow::Cow::Borrowed($ttype),
|
||||
subtype: ::std::borrow::Cow::Borrowed($subtype),
|
||||
ttype: ::rocket::http::ascii::UncasedAscii {
|
||||
string: ::std::borrow::Cow::Borrowed($top)
|
||||
},
|
||||
subtype: ::rocket::http::ascii::UncasedAscii {
|
||||
string: ::std::borrow::Cow::Borrowed($sub)
|
||||
},
|
||||
params: None
|
||||
})
|
||||
})
|
||||
|
|
|
@ -29,6 +29,34 @@ impl PartialEq for UncasedAsciiRef {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for UncasedAsciiRef {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0.eq_ignore_ascii_case(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<UncasedAsciiRef> for str {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &UncasedAsciiRef) -> bool {
|
||||
other.0.eq_ignore_ascii_case(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<&'a str> for UncasedAsciiRef {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: & &'a str) -> bool {
|
||||
self.0.eq_ignore_ascii_case(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<UncasedAsciiRef> for &'a str {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &UncasedAsciiRef) -> bool {
|
||||
other.0.eq_ignore_ascii_case(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for &'a UncasedAsciiRef {
|
||||
#[inline(always)]
|
||||
fn from(string: &'a str) -> &'a UncasedAsciiRef {
|
||||
|
@ -65,7 +93,7 @@ impl Ord for UncasedAsciiRef {
|
|||
/// An uncased (case-preserving) ASCII string.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UncasedAscii<'s> {
|
||||
string: Cow<'s, str>
|
||||
pub string: Cow<'s, str>
|
||||
}
|
||||
|
||||
impl<'s> UncasedAscii<'s> {
|
||||
|
@ -181,6 +209,34 @@ impl<'a, 'b> PartialEq<UncasedAscii<'b>> for UncasedAscii<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<str> for UncasedAscii<'a> {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_ref().eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> PartialEq<UncasedAscii<'b>> for str {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &UncasedAscii<'b>) -> bool {
|
||||
other.as_ref().eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> PartialEq<&'b str> for UncasedAscii<'a> {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: & &'b str) -> bool {
|
||||
self.as_ref().eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> PartialEq<UncasedAscii<'b>> for &'a str {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &UncasedAscii<'b>) -> bool {
|
||||
other.as_ref().eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Eq for UncasedAscii<'s> { }
|
||||
|
||||
impl<'s> Hash for UncasedAscii<'s> {
|
||||
|
@ -215,13 +271,19 @@ mod tests {
|
|||
macro_rules! assert_uncased_eq {
|
||||
($($string:expr),+) => ({
|
||||
let mut strings = Vec::new();
|
||||
$(strings.push(UncasedAscii::from($string));)+
|
||||
$(strings.push($string);)+
|
||||
|
||||
for i in 0..strings.len() {
|
||||
for j in i..strings.len() {
|
||||
let (a, b) = (&strings[i], &strings[j]);
|
||||
assert_eq!(a, b);
|
||||
assert_eq!(hash(&a), hash(&b));
|
||||
let (str_a, str_b) = (strings[i], strings[j]);
|
||||
let ascii_a = UncasedAscii::from(str_a);
|
||||
let ascii_b = UncasedAscii::from(str_b);
|
||||
assert_eq!(ascii_a, ascii_b);
|
||||
assert_eq!(hash(&ascii_a), hash(&ascii_b));
|
||||
assert_eq!(ascii_a, str_a);
|
||||
assert_eq!(ascii_b, str_b);
|
||||
assert_eq!(ascii_a, str_b);
|
||||
assert_eq!(ascii_b, str_a);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::fmt;
|
|||
|
||||
use http::Header;
|
||||
use http::hyper::mime::Mime;
|
||||
use http::ascii::{uncased_eq, UncasedAscii};
|
||||
use router::Collider;
|
||||
|
||||
/// Representation of HTTP Content-Types.
|
||||
|
@ -39,16 +40,16 @@ use router::Collider;
|
|||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct ContentType {
|
||||
/// The "type" component of the Content-Type.
|
||||
pub ttype: Cow<'static, str>,
|
||||
pub ttype: UncasedAscii<'static>,
|
||||
/// The "subtype" component of the Content-Type.
|
||||
pub subtype: Cow<'static, str>,
|
||||
pub subtype: UncasedAscii<'static>,
|
||||
/// Semicolon-seperated parameters associated with the Content-Type.
|
||||
pub params: Option<Cow<'static, str>>
|
||||
pub params: Option<UncasedAscii<'static>>
|
||||
}
|
||||
|
||||
macro_rules! ctr_params {
|
||||
() => (None);
|
||||
($param:expr) => (Some(Cow::Borrowed($param)));
|
||||
($param:expr) => (Some(UncasedAscii { string: Cow::Borrowed($param) }));
|
||||
}
|
||||
|
||||
macro_rules! ctrs {
|
||||
|
@ -65,8 +66,8 @@ macro_rules! ctrs {
|
|||
#[doc="</i>"]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const $name: ContentType = ContentType {
|
||||
ttype: Cow::Borrowed($top),
|
||||
subtype: Cow::Borrowed($sub),
|
||||
ttype: UncasedAscii { string: Cow::Borrowed($top) },
|
||||
subtype: UncasedAscii { string: Cow::Borrowed($sub) },
|
||||
params: ctr_params!($($param)*)
|
||||
};
|
||||
)+
|
||||
|
@ -74,12 +75,8 @@ macro_rules! ctrs {
|
|||
/// Returns `true` if this ContentType is known to Rocket, that is,
|
||||
/// there is an associated constant for `self`.
|
||||
pub fn is_known(&self) -> bool {
|
||||
match (&*self.ttype, &*self.subtype) {
|
||||
$(
|
||||
($top, $sub) => true,
|
||||
)+
|
||||
_ => false
|
||||
}
|
||||
$(if self.$check_name() { return true })+
|
||||
false
|
||||
}
|
||||
|
||||
$(
|
||||
|
@ -146,24 +143,26 @@ impl ContentType {
|
|||
/// ```
|
||||
pub fn from_extension(ext: &str) -> ContentType {
|
||||
match ext {
|
||||
"txt" => ContentType::Plain,
|
||||
"html" | "htm" => ContentType::HTML,
|
||||
"xml" => ContentType::XML,
|
||||
"csv" => ContentType::CSV,
|
||||
"js" => ContentType::JavaScript,
|
||||
"css" => ContentType::CSS,
|
||||
"json" => ContentType::JSON,
|
||||
"png" => ContentType::PNG,
|
||||
"gif" => ContentType::GIF,
|
||||
"bmp" => ContentType::BMP,
|
||||
"jpeg" | "jpg" => ContentType::JPEG,
|
||||
"pdf" => ContentType::PDF,
|
||||
x if uncased_eq(x, "txt") => ContentType::Plain,
|
||||
x if uncased_eq(x, "html") => ContentType::HTML,
|
||||
x if uncased_eq(x, "htm") => ContentType::HTML,
|
||||
x if uncased_eq(x, "xml") => ContentType::XML,
|
||||
x if uncased_eq(x, "csv") => ContentType::CSV,
|
||||
x if uncased_eq(x, "js") => ContentType::JavaScript,
|
||||
x if uncased_eq(x, "css") => ContentType::CSS,
|
||||
x if uncased_eq(x, "json") => ContentType::JSON,
|
||||
x if uncased_eq(x, "png") => ContentType::PNG,
|
||||
x if uncased_eq(x, "gif") => ContentType::GIF,
|
||||
x if uncased_eq(x, "bmp") => ContentType::BMP,
|
||||
x if uncased_eq(x, "jpeg") => ContentType::JPEG,
|
||||
x if uncased_eq(x, "jpg") => ContentType::JPEG,
|
||||
x if uncased_eq(x, "pdf") => ContentType::PDF,
|
||||
_ => ContentType::Any
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `ContentType` with type `ttype` and subtype `subtype`.
|
||||
/// This should be _only_ to construct uncommon Content-Types or custom
|
||||
/// This should _only_ be used to construct uncommon Content-Types or custom
|
||||
/// Content-Types. Use an associated constant for common Content-Types.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -180,11 +179,7 @@ impl ContentType {
|
|||
pub fn new<T, S>(ttype: T, subtype: S) -> ContentType
|
||||
where T: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>
|
||||
{
|
||||
ContentType {
|
||||
ttype: ttype.into(),
|
||||
subtype: subtype.into(),
|
||||
params: None
|
||||
}
|
||||
ContentType::with_params::<T, S, T>(ttype, subtype, None)
|
||||
}
|
||||
|
||||
/// Creates a new `ContentType` with type `ttype`, subtype `subtype`, and
|
||||
|
@ -210,11 +205,52 @@ impl ContentType {
|
|||
P: Into<Cow<'static, str>>
|
||||
{
|
||||
ContentType {
|
||||
ttype: ttype.into(),
|
||||
subtype: subtype.into(),
|
||||
params: params.map(|p| p.into())
|
||||
ttype: UncasedAscii::from(ttype),
|
||||
subtype: UncasedAscii::from(subtype),
|
||||
params: params.map(|p| UncasedAscii::from(p))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the (key, value) pairs of the Content-Type's
|
||||
/// parameter list. The iterator will be empty if the Content-Type has no
|
||||
/// parameters.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The `ContentType::Plain` type has one parameter: `charset=utf-8`:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::ContentType;
|
||||
///
|
||||
/// let plain = ContentType::Plain;
|
||||
/// let plain_params: Vec<_> = plain.params().collect();
|
||||
/// assert_eq!(plain_params, vec![("charset", "utf-8")]);
|
||||
/// ```
|
||||
///
|
||||
/// The `ContentType::PNG` type has no parameters:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::ContentType;
|
||||
///
|
||||
/// let png = ContentType::PNG;
|
||||
/// assert_eq!(png.params().count(), 0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn params<'a>(&'a self) -> impl Iterator<Item=(&'a str, &'a str)> + 'a {
|
||||
let params = match self.params {
|
||||
Some(ref params) => params.as_str(),
|
||||
None => ""
|
||||
};
|
||||
|
||||
params.split(";")
|
||||
.filter_map(|param| {
|
||||
let mut kv = param.split("=");
|
||||
match (kv.next(), kv.next()) {
|
||||
(Some(key), Some(val)) => Some((key.trim(), val.trim())),
|
||||
_ => None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContentType {
|
||||
|
@ -250,30 +286,18 @@ impl From<Mime> for ContentType {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_valid_first_char(c: char) -> bool {
|
||||
match c {
|
||||
'a'...'z' | 'A'...'Z' | '0'...'9' | '*' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_char(c: char) -> bool {
|
||||
is_valid_first_char(c) || match c {
|
||||
'!' | '#' | '$' | '&' | '-' | '^' | '.' | '+' | '_' => true,
|
||||
_ => false,
|
||||
match c {
|
||||
'0'...'9' | 'A'...'Z' | '^'...'~' | '#'...'\''
|
||||
| '!' | '*' | '+' | '-' | '.' => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_token(string: &str) -> bool {
|
||||
if string.len() < 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
string.chars().take(1).all(is_valid_first_char)
|
||||
&& string.chars().skip(1).all(is_valid_char)
|
||||
string.len() >= 1 && string.chars().all(is_valid_char)
|
||||
}
|
||||
|
||||
|
||||
impl FromStr for ContentType {
|
||||
type Err = &'static str;
|
||||
|
||||
|
@ -288,6 +312,7 @@ impl FromStr for ContentType {
|
|||
/// use rocket::http::ContentType;
|
||||
///
|
||||
/// let json = ContentType::from_str("application/json").unwrap();
|
||||
/// assert!(json.is_known());
|
||||
/// assert_eq!(json, ContentType::JSON);
|
||||
/// ```
|
||||
///
|
||||
|
@ -355,7 +380,7 @@ impl FromStr for ContentType {
|
|||
trimmed_params.push(param);
|
||||
}
|
||||
|
||||
let (ttype, subtype) = (top_s.to_lowercase(), sub_s.to_lowercase());
|
||||
let (ttype, subtype) = (top_s.to_string(), sub_s.to_string());
|
||||
let params = params.map(|_| trimmed_params.join(";"));
|
||||
Ok(ContentType::with_params(ttype, subtype, params))
|
||||
}
|
||||
|
@ -394,8 +419,8 @@ impl Into<Header<'static>> for ContentType {
|
|||
|
||||
impl Collider for ContentType {
|
||||
fn collides_with(&self, other: &ContentType) -> bool {
|
||||
(self.ttype == "*" || other.ttype == "*" || self.ttype == other.ttype) &&
|
||||
(self.subtype == "*" || other.subtype == "*" || self.subtype == other.subtype)
|
||||
let collide = |a, b| a == "*" || b == "*" || a == b;
|
||||
collide(&self.ttype, &other.ttype) && collide(&self.subtype, &other.subtype)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,6 +485,9 @@ mod test {
|
|||
ContentType::with_params("*", "*", Some("charset=utf-8;else=1")));
|
||||
assert_parse!("*/*; charset=\"utf-8\"; else=1",
|
||||
ContentType::with_params("*", "*", Some("charset=\"utf-8\";else=1")));
|
||||
assert_parse!("multipart/form-data; boundary=----WebKitFormBoundarypRshfItmvaC3aEuq",
|
||||
ContentType::with_params("multipart", "form-data",
|
||||
Some("boundary=----WebKitFormBoundarypRshfItmvaC3aEuq")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::str::FromStr;
|
|||
|
||||
use error::Error;
|
||||
use http::hyper;
|
||||
use http::ascii;
|
||||
use http::ascii::uncased_eq;
|
||||
|
||||
use self::Method::*;
|
||||
|
||||
|
@ -78,15 +78,15 @@ impl FromStr for Method {
|
|||
// clients don't follow this, so we just do a case-insensitive match here.
|
||||
fn from_str(s: &str) -> Result<Method, Error> {
|
||||
match s {
|
||||
x if ascii::uncased_eq(x, Get.as_str()) => Ok(Get),
|
||||
x if ascii::uncased_eq(x, Put.as_str()) => Ok(Put),
|
||||
x if ascii::uncased_eq(x, Post.as_str()) => Ok(Post),
|
||||
x if ascii::uncased_eq(x, Delete.as_str()) => Ok(Delete),
|
||||
x if ascii::uncased_eq(x, Options.as_str()) => Ok(Options),
|
||||
x if ascii::uncased_eq(x, Head.as_str()) => Ok(Head),
|
||||
x if ascii::uncased_eq(x, Trace.as_str()) => Ok(Trace),
|
||||
x if ascii::uncased_eq(x, Connect.as_str()) => Ok(Connect),
|
||||
x if ascii::uncased_eq(x, Patch.as_str()) => Ok(Patch),
|
||||
x if uncased_eq(x, Get.as_str()) => Ok(Get),
|
||||
x if uncased_eq(x, Put.as_str()) => Ok(Put),
|
||||
x if uncased_eq(x, Post.as_str()) => Ok(Post),
|
||||
x if uncased_eq(x, Delete.as_str()) => Ok(Delete),
|
||||
x if uncased_eq(x, Options.as_str()) => Ok(Options),
|
||||
x if uncased_eq(x, Head.as_str()) => Ok(Head),
|
||||
x if uncased_eq(x, Trace.as_str()) => Ok(Trace),
|
||||
x if uncased_eq(x, Connect.as_str()) => Ok(Connect),
|
||||
x if uncased_eq(x, Patch.as_str()) => Ok(Patch),
|
||||
_ => Err(Error::BadMethod),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@ mod method;
|
|||
mod content_type;
|
||||
mod status;
|
||||
mod header;
|
||||
mod ascii;
|
||||
|
||||
// We need to export this for codegen, but otherwise it's unnecessary.
|
||||
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
||||
#[doc(hidden)] pub mod ascii;
|
||||
|
||||
pub use self::method::Method;
|
||||
pub use self::content_type::ContentType;
|
||||
|
|
Loading…
Reference in New Issue