aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/cors.rs
diff options
context:
space:
mode:
authorSean McArthur <sean.monstar@gmail.com>2014-11-20 17:39:09 -0800
committerManish Goregaokar <manishsmail@gmail.com>2014-12-04 10:06:41 -0800
commit4ee8c3a60b554609b7adbd262c7543f141b09224 (patch)
tree2322fe9d4f8df709c5ebccd207e81cc67679f272 /components/script/cors.rs
parent1dc67f0c761debabdadbf81d195a6954812c0d1f (diff)
downloadservo-4ee8c3a60b554609b7adbd262c7543f141b09224.tar.gz
servo-4ee8c3a60b554609b7adbd262c7543f141b09224.zip
convert script crate to use hyper
Diffstat (limited to 'components/script/cors.rs')
-rw-r--r--components/script/cors.rs321
1 files changed, 222 insertions, 99 deletions
diff --git a/components/script/cors.rs b/components/script/cors.rs
index 50e69ca333f..abd99af36d2 100644
--- a/components/script/cors.rs
+++ b/components/script/cors.rs
@@ -9,23 +9,21 @@
//! This library will eventually become the core of the Fetch crate
//! with CORSRequest being expanded into FetchRequest (etc)
-use std::ascii::{AsciiExt, OwnedAsciiExt};
-use std::from_str::FromStr;
-use std::io::BufReader;
+use std::ascii::AsciiExt;
+use std::fmt::{mod, Show};
+use std::str::from_utf8;
use time;
use time::{now, Timespec};
-use http::headers::response::HeaderCollection as ResponseHeaderCollection;
-use http::headers::request::HeaderCollection as RequestHeaderCollection;
-use http::headers::request::Header as RequestHeader;
+use hyper::header::{Headers, Header, HeaderFormat, HeaderView};
+use hyper::header::common::util as header_util;
+use hyper::client::Request;
+use hyper::mime::{mod, Mime};
+use hyper::header::common::{ContentType, Host};
+use hyper::method::{Method, Get, Head, Post, Options};
+use hyper::status::Success;
-use http::client::{RequestWriter, NetworkStream};
-use http::headers::{HeaderConvertible, HeaderEnum, HeaderValueByteIterator};
-use http::headers::content_type::MediaType;
-use http::headers::request::{Accept, AcceptLanguage, ContentLanguage, ContentType};
-use http::method::{Method, Get, Head, Post, Options};
-
-use url::{RelativeSchemeData, Url, UrlParser};
+use url::{RelativeSchemeData, Url};
#[deriving(Clone)]
pub struct CORSRequest {
@@ -33,7 +31,7 @@ pub struct CORSRequest {
pub destination: Url,
pub mode: RequestMode,
pub method: Method,
- pub headers: RequestHeaderCollection,
+ pub headers: Headers,
/// CORS preflight flag (http://fetch.spec.whatwg.org/#concept-http-fetch)
/// Indicates that a CORS preflight request and/or cache check is to be performed
pub preflight_flag: bool
@@ -51,7 +49,7 @@ pub enum RequestMode {
impl CORSRequest {
/// Creates a CORS request if necessary. Will return an error when fetching is forbidden
pub fn maybe_new(referer: Url, destination: Url, mode: RequestMode,
- method: Method, headers: RequestHeaderCollection) -> Result<Option<CORSRequest>, ()> {
+ method: Method, headers: Headers) -> Result<Option<CORSRequest>, ()> {
if referer.scheme == destination.scheme &&
referer.host() == destination.host() &&
referer.port() == destination.port() {
@@ -73,7 +71,7 @@ impl CORSRequest {
}
fn new(mut referer: Url, destination: Url, mode: RequestMode, method: Method,
- headers: RequestHeaderCollection) -> CORSRequest {
+ headers: Headers) -> CORSRequest {
match referer.scheme_data {
RelativeSchemeData(ref mut data) => data.path = vec!(),
_ => {}
@@ -104,7 +102,7 @@ impl CORSRequest {
let cache = &mut CORSCache(vec!()); // XXXManishearth Should come from user agent
if self.preflight_flag &&
!cache.match_method(self, &self.method) &&
- !self.headers.iter().all(|h| is_simple_header(&h) && cache.match_header(self, h.header_name().as_slice())) {
+ !self.headers.iter().all(|h| is_simple_header(&h) && cache.match_header(self, h.name())) {
if !is_simple_method(&self.method) || self.mode == ForcedPreflightMode {
return self.preflight_fetch();
// Everything after this is part of XHR::fetch()
@@ -121,69 +119,63 @@ impl CORSRequest {
let mut preflight = self.clone(); // Step 1
preflight.method = Options; // Step 2
- preflight.headers = RequestHeaderCollection::new(); // Step 3
+ preflight.headers = Headers::new(); // Step 3
// Step 4
- preflight.insert_string_header("Access-Control-Request-Method".to_string(), self.method.http_value());
+ preflight.headers.set(AccessControlRequestMethod(self.method.clone()));
// Step 5 - 7
let mut header_names = vec!();
for header in self.headers.iter() {
- header_names.push(header.header_name().into_ascii_lower());
+ header_names.push(header.name().to_ascii_lower());
}
header_names.sort();
- let header_list = header_names.connect(", "); // 0x2C 0x20
- preflight.insert_string_header("Access-Control-Request-Headers".to_string(), header_list);
+ preflight.headers.set(AccessControlRequestHeaders(header_names));
// Step 8 unnecessary, we don't use the request body
// Step 9, 10 unnecessary, we're writing our own fetch code
// Step 11
- let preflight_request = RequestWriter::<NetworkStream>::new(preflight.method, preflight.destination);
- let mut writer = match preflight_request {
- Ok(w) => box w,
+ let preflight_request = Request::new(preflight.method, preflight.destination);
+ let mut req = match preflight_request {
+ Ok(req) => req,
Err(_) => return error
};
- let host = writer.headers.host.clone();
- writer.headers = preflight.headers.clone();
- writer.headers.host = host;
- let response = match writer.read_response() {
+ let host = req.headers().get::<Host>().unwrap().clone();
+ *req.headers_mut() = preflight.headers.clone();
+ req.headers_mut().set(host);
+ let stream = match req.start() {
+ Ok(s) => s,
+ Err(_) => return error
+ };
+ let response = match stream.send() {
Ok(r) => r,
Err(_) => return error
};
// Step 12
- match response.status.code() {
- 200 ... 299 => {}
- _ => return error
+ match response.status.class() {
+ Success => {}
+ _ => return error
}
cors_response.headers = response.headers.clone();
// Substeps 1-3 (parsing rules: http://fetch.spec.whatwg.org/#http-new-header-syntax)
- fn find_header(headers: &ResponseHeaderCollection, name: &str) -> Option<String> {
- headers.iter().find(|h| h.header_name().as_slice()
- .eq_ignore_ascii_case(name))
- .map(|h| h.header_value())
- }
- let methods_string = match find_header(&response.headers, "Access-Control-Allow-Methods") {
- Some(s) => s,
+ let mut methods = match response.headers.get() {
+ Some(&AccessControlAllowMethods(ref v)) => v.as_slice(),
_ => return error
};
- let methods = methods_string.as_slice().split(',');
- let headers_string = match find_header(&response.headers, "Access-Control-Allow-Headers") {
- Some(s) => s,
+ let headers = match response.headers.get() {
+ Some(&AccessControlAllowHeaders(ref h)) => h,
_ => return error
};
- let headers = headers_string.as_slice().split(0x2Cu8 as char);
- // The ABNF # rule will consider consecutive delimeters as a single delimeter
- let mut methods: Vec<String> = methods.filter(|s| s.len() > 0).map(|s| s.to_string()).collect();
- let headers: Vec<String> = headers.filter(|s| s.len() > 0).map(|s| s.to_string()).collect();
// Substep 4
+ let methods_substep4 = [self.method.clone()];
if methods.len() == 0 || preflight.mode == ForcedPreflightMode {
- methods = vec!(self.method.http_value());
+ methods = methods_substep4.as_slice();
}
// Substep 5
if !is_simple_method(&self.method) &&
- !methods.iter().any(|ref m| self.method.http_value().as_slice().eq_ignore_ascii_case(m.as_slice())) {
+ !methods.iter().any(|m| m == &self.method) {
return error;
}
// Substep 6
@@ -191,30 +183,29 @@ impl CORSRequest {
if is_simple_header(&h) {
continue;
}
- if !headers.iter().any(|ref h2| h.header_name().as_slice().eq_ignore_ascii_case(h2.as_slice())) {
+ if !headers.iter().any(|ref h2| h.name().eq_ignore_ascii_case(h2.as_slice())) {
return error;
}
}
// Substep 7, 8
- let max_age: uint = find_header(&response.headers, "Access-Control-Max-Age")
- .and_then(|h| FromStr::from_str(h.as_slice())).unwrap_or(0);
+ let max_age = match response.headers.get() {
+ Some(&AccessControlMaxAge(num)) => num,
+ None => 0
+ };
// Substep 9: Impose restrictions on max-age, if any (unimplemented)
// Substeps 10-12: Add a cache (partially implemented, XXXManishearth)
// This cache should come from the user agent, creating a new one here to check
// for compile time errors
let cache = &mut CORSCache(vec!());
for m in methods.iter() {
- let maybe_method: Option<Method> = FromStr::from_str(m.as_slice());
- maybe_method.map(|ref m| {
- let cache_match = cache.match_method_and_update(self, m, max_age);
- if !cache_match {
- cache.insert(CORSCacheEntry::new(self.origin.clone(), self.destination.clone(),
- max_age, false, MethodData(m.clone())));
- }
- });
+ let cache_match = cache.match_method_and_update(self, m, max_age);
+ if !cache_match {
+ cache.insert(CORSCacheEntry::new(self.origin.clone(), self.destination.clone(),
+ max_age, false, MethodData(m.clone())));
+ }
}
- for h in headers.iter() {
- let cache_match = cache.match_header_and_update(self, h.as_slice(), max_age);
+ for h in response.headers.iter() {
+ let cache_match = cache.match_header_and_update(self, h.name(), max_age);
if !cache_match {
cache.insert(CORSCacheEntry::new(self.origin.clone(), self.destination.clone(),
max_age, false, HeaderData(h.to_string())));
@@ -222,35 +213,26 @@ impl CORSRequest {
}
cors_response
}
-
- fn insert_string_header(&mut self, name: String, value: String) {
- let value_bytes = value.into_bytes();
- let mut reader = BufReader::new(value_bytes.as_slice());
- let maybe_header: Option<RequestHeader> = HeaderEnum::value_from_stream(
- String::from_str(name.as_slice()),
- &mut HeaderValueByteIterator::new(&mut reader));
- self.headers.insert(maybe_header.unwrap());
- }
}
pub struct CORSResponse {
pub network_error: bool,
- pub headers: ResponseHeaderCollection
+ pub headers: Headers
}
impl CORSResponse {
fn new() -> CORSResponse {
CORSResponse {
network_error: false,
- headers: ResponseHeaderCollection::new()
+ headers: Headers::new()
}
}
fn new_error() -> CORSResponse {
CORSResponse {
network_error: true,
- headers: ResponseHeaderCollection::new()
+ headers: Headers::new()
}
}
}
@@ -290,14 +272,14 @@ impl HeaderOrMethod {
pub struct CORSCacheEntry {
pub origin: Url,
pub url: Url,
- pub max_age: uint,
+ pub max_age: u32,
pub credentials: bool,
pub header_or_method: HeaderOrMethod,
created: Timespec
}
impl CORSCacheEntry {
- fn new (origin:Url, url: Url, max_age: uint, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
+ fn new (origin:Url, url: Url, max_age: u32, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
CORSCacheEntry {
origin: origin,
url: url,
@@ -343,7 +325,7 @@ impl CORSCache {
self.find_entry_by_header(request, header_name).is_some()
}
- fn match_header_and_update(&mut self, request: &CORSRequest, header_name: &str, new_max_age: uint) -> bool {
+ fn match_header_and_update(&mut self, request: &CORSRequest, header_name: &str, new_max_age: u32) -> bool {
self.find_entry_by_header(request, header_name).map(|e| e.max_age = new_max_age).is_some()
}
@@ -365,7 +347,7 @@ impl CORSCache {
self.find_entry_by_method(request, method).is_some()
}
- fn match_method_and_update(&mut self, request: &CORSRequest, method: &Method, new_max_age: uint) -> bool {
+ fn match_method_and_update(&mut self, request: &CORSRequest, method: &Method, new_max_age: u32) -> bool {
self.find_entry_by_method(request, method).map(|e| e.max_age = new_max_age).is_some()
}
@@ -376,12 +358,18 @@ impl CORSCache {
}
}
-fn is_simple_header(h: &RequestHeader) -> bool {
- match *h {
- Accept(_) | AcceptLanguage(_) | ContentLanguage(_) => true,
- ContentType(MediaType {type_: ref t, subtype: ref s, ..}) => match (t.as_slice(), s.as_slice()) {
- ("text", "plain") | ("application", "x-www-form-urlencoded") | ("multipart", "form-data") => true,
+fn is_simple_header(h: &HeaderView) -> bool {
+ //FIXME: use h.is::<HeaderType>() when AcceptLanguage and
+ //ContentLanguage headers exist
+ match h.name().to_ascii_lower().as_slice() {
+ "accept" | "accept-language" | "content-language" => true,
+ "content-type" => match h.value() {
+ Some(&ContentType(Mime(mime::Text, mime::Plain, _))) |
+ Some(&ContentType(Mime(mime::Application, mime::WwwFormUrlEncoded, _))) |
+ Some(&ContentType(Mime(mime::Multipart, mime::FormData, _))) => true,
+
_ => false
+
},
_ => false
}
@@ -394,25 +382,160 @@ fn is_simple_method(m: &Method) -> bool {
}
}
+//XXX(seanmonstar): worth uplifting to Hyper?
+#[deriving(Clone)]
+struct AccessControlRequestMethod(pub Method);
+
+impl Header for AccessControlRequestMethod {
+ #[inline]
+ fn header_name(_: Option<AccessControlRequestMethod>) -> &'static str {
+ "Access-Control-Request-Method"
+ }
+
+ fn parse_header(raw: &[Vec<u8>]) -> Option<AccessControlRequestMethod> {
+ header_util::from_one_raw_str(raw).map(AccessControlRequestMethod)
+ }
+}
+
+impl HeaderFormat for AccessControlRequestMethod {
+ fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let AccessControlRequestMethod(ref method) = *self;
+ method.fmt(f)
+ }
+}
+
+#[deriving(Clone)]
+struct AccessControlRequestHeaders(pub Vec<String>);
+
+impl Header for AccessControlRequestHeaders {
+ #[inline]
+ fn header_name(_: Option<AccessControlRequestHeaders>) -> &'static str {
+ "Access-Control-Request-Headers"
+ }
+
+ fn parse_header(raw: &[Vec<u8>]) -> Option<AccessControlRequestHeaders> {
+ header_util::from_comma_delimited(raw).map(AccessControlRequestHeaders)
+ }
+}
+
+impl HeaderFormat for AccessControlRequestHeaders {
+ fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let AccessControlRequestHeaders(ref parts) = *self;
+ header_util::fmt_comma_delimited(f, parts.as_slice())
+ }
+}
+
+#[deriving(Clone)]
+struct AccessControlAllowMethods(pub Vec<Method>);
+
+impl Header for AccessControlAllowMethods {
+ #[inline]
+ fn header_name(_: Option<AccessControlAllowMethods>) -> &'static str {
+ "Access-Control-Allow-Methods"
+ }
+
+ fn parse_header(raw: &[Vec<u8>]) -> Option<AccessControlAllowMethods> {
+ header_util::from_comma_delimited(raw).map(AccessControlAllowMethods)
+ }
+}
+
+impl HeaderFormat for AccessControlAllowMethods {
+ fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let AccessControlAllowMethods(ref parts) = *self;
+ header_util::fmt_comma_delimited(f, parts.as_slice())
+ }
+}
+
+#[deriving(Clone)]
+struct AccessControlAllowHeaders(pub Vec<String>);
+
+impl Header for AccessControlAllowHeaders {
+ #[inline]
+ fn header_name(_: Option<AccessControlAllowHeaders>) -> &'static str {
+ "Access-Control-Allow-Headers"
+ }
+
+ fn parse_header(raw: &[Vec<u8>]) -> Option<AccessControlAllowHeaders> {
+ header_util::from_comma_delimited(raw).map(AccessControlAllowHeaders)
+ }
+}
+
+impl HeaderFormat for AccessControlAllowHeaders {
+ fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let AccessControlAllowHeaders(ref parts) = *self;
+ header_util::fmt_comma_delimited(f, parts.as_slice())
+ }
+}
+
+#[deriving(Clone)]
+enum AccessControlAllowOrigin {
+ AllowStar,
+ AllowOrigin(Url),
+}
+
+
+impl Header for AccessControlAllowOrigin {
+ #[inline]
+ fn header_name(_: Option<AccessControlAllowOrigin>) -> &'static str {
+ "Access-Control-Allow-Origin"
+ }
+
+ fn parse_header(raw: &[Vec<u8>]) -> Option<AccessControlAllowOrigin> {
+ if raw.len() == 1 {
+ from_utf8(raw[0].as_slice()).and_then(|s| {
+ if s == "*" {
+ Some(AllowStar)
+ } else {
+ Url::parse(s).ok().map(|url| AllowOrigin(url))
+ }
+ })
+ } else {
+ None
+ }
+ }
+}
+
+impl HeaderFormat for AccessControlAllowOrigin {
+ fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ AllowStar => "*".fmt(f),
+ AllowOrigin(ref url) => url.fmt(f)
+ }
+ }
+}
+
+#[deriving(Clone)]
+struct AccessControlMaxAge(pub u32);
+
+impl Header for AccessControlMaxAge {
+ #[inline]
+ fn header_name(_: Option<AccessControlMaxAge>) -> &'static str {
+ "Access-Control-Max-Age"
+ }
+
+ fn parse_header(raw: &[Vec<u8>]) -> Option<AccessControlMaxAge> {
+ header_util::from_one_raw_str(raw).map(AccessControlMaxAge)
+ }
+}
+
+impl HeaderFormat for AccessControlMaxAge {
+ fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let AccessControlMaxAge(ref num) = *self;
+ num.fmt(f)
+ }
+}
+
+
/// Perform a CORS check on a header list and CORS request
/// http://fetch.spec.whatwg.org/#cors-check
-pub fn allow_cross_origin_request(req: &CORSRequest, headers: &ResponseHeaderCollection) -> bool {
- let allow_cross_origin_request = headers.iter().find(|h| h.header_name()
- .as_slice()
- .eq_ignore_ascii_case("Access-Control-Allow-Origin"));
- match allow_cross_origin_request {
- Some(h) => {
- let origin_str = h.header_value();
- if origin_str.as_slice() == "*" {
- return true; // Not always true, depends on credentials mode
- }
- match UrlParser::new().parse(origin_str.as_slice()) {
- Ok(parsed) => parsed.scheme == req.origin.scheme &&
- parsed.host() == req.origin.host() &&
- parsed.port() == req.origin.port(),
- _ => false
- }
- },
+pub fn allow_cross_origin_request(req: &CORSRequest, headers: &Headers) -> bool {
+ //FIXME(seanmonstar): use req.headers.get::<AccessControlAllowOrigin>()
+ match headers.get() {
+ Some(&AllowStar) => true, // Not always true, depends on credentials mode
+ Some(&AllowOrigin(ref url)) =>
+ url.scheme == req.origin.scheme &&
+ url.host() == req.origin.host() &&
+ url.port() == req.origin.port(),
None => false
}
}