diff options
author | Jack Moffitt <jack@metajack.im> | 2014-08-28 09:34:23 -0600 |
---|---|---|
committer | Jack Moffitt <jack@metajack.im> | 2014-09-08 20:21:42 -0600 |
commit | c6ab60dbfc6da7b4f800c9e40893c8b58413960c (patch) | |
tree | d1d74076cf7fa20e4f77ec7cb82cae98b67362cb /src/components/script/cors.rs | |
parent | db2f642c32fc5bed445bb6f2e45b0f6f0b4342cf (diff) | |
download | servo-c6ab60dbfc6da7b4f800c9e40893c8b58413960c.tar.gz servo-c6ab60dbfc6da7b4f800c9e40893c8b58413960c.zip |
Cargoify servo
Diffstat (limited to 'src/components/script/cors.rs')
-rw-r--r-- | src/components/script/cors.rs | 419 |
1 files changed, 0 insertions, 419 deletions
diff --git a/src/components/script/cors.rs b/src/components/script/cors.rs deleted file mode 100644 index 3a3fd98ee90..00000000000 --- a/src/components/script/cors.rs +++ /dev/null @@ -1,419 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -//! A partial implementation of CORS -//! For now this library is XHR-specific. -//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what -//! the request mode should be and compare with the fetch spec -//! This library will eventually become the core of the Fetch crate -//! with CORSRequest being expanded into FetchRequest (etc) - -use std::ascii::{StrAsciiExt, OwnedStrAsciiExt}; -use std::from_str::FromStr; -use std::io::BufReader; -use std::str::StrSlice; -use time; -use time::{now, Timespec}; - -use ResponseHeaderCollection = http::headers::response::HeaderCollection; -use RequestHeaderCollection = http::headers::request::HeaderCollection; -use RequestHeader = http::headers::request::Header; - -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}; - -#[deriving(Clone)] -pub struct CORSRequest { - pub origin: Url, - pub destination: Url, - pub mode: RequestMode, - pub method: Method, - pub headers: RequestHeaderCollection, - /// 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 -} - -/// http://fetch.spec.whatwg.org/#concept-request-mode -/// This only covers some of the request modes. The -/// `same-origin` and `no CORS` modes are unnecessary for XHR. -#[deriving(PartialEq, Clone)] -pub enum RequestMode { - CORSMode, // CORS - ForcedPreflightMode // CORS-with-forced-preflight -} - -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>, ()> { - if referer.scheme == destination.scheme && - referer.host() == destination.host() && - referer.port() == destination.port() { - return Ok(None); // Not cross-origin, proceed with a normal fetch - } - match destination.scheme.as_slice() { - // Todo: If the request's same origin data url flag is set (which isn't the case for XHR) - // we can fetch a data URL normally. about:blank can also be fetched by XHR - "http" | "https" => { - let mut req = CORSRequest::new(referer, destination, mode, method, headers); - req.preflight_flag = !is_simple_method(&req.method) || mode == ForcedPreflightMode; - if req.headers.iter().all(|h| is_simple_header(&h)) { - req.preflight_flag = true; - } - Ok(Some(req)) - }, - _ => Err(()), - } - } - - fn new(mut referer: Url, destination: Url, mode: RequestMode, method: Method, - headers: RequestHeaderCollection) -> CORSRequest { - match referer.scheme_data { - RelativeSchemeData(ref mut data) => data.path = vec!(), - _ => {} - }; - referer.fragment = None; - referer.query = None; - CORSRequest { - origin: referer, - destination: destination, - mode: mode, - method: method, - headers: headers, - preflight_flag: false - } - } - - /// http://fetch.spec.whatwg.org/#concept-http-fetch - /// This method assumes that the CORS flag is set - /// This does not perform the full HTTP fetch, rather it handles part of the CORS filtering - /// if self.mode is ForcedPreflightMode, then the CORS-with-forced-preflight - /// fetch flag is set as well - pub fn http_fetch(&self) -> CORSResponse { - let response = CORSResponse::new(); - // Step 2: Handle service workers (unimplemented) - // Step 3 - // Substep 1: Service workers (unimplemented ) - // Substep 2 - 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())) { - if !is_simple_method(&self.method) || self.mode == ForcedPreflightMode { - return self.preflight_fetch(); - // Everything after this is part of XHR::fetch() - // Expect the organization of code to improve once we have a fetch crate - } - } - response - } - - /// http://fetch.spec.whatwg.org/#cors-preflight-fetch - fn preflight_fetch(&self) -> CORSResponse { - let error = CORSResponse::new_error(); - let mut cors_response = CORSResponse::new(); - - let mut preflight = self.clone(); // Step 1 - preflight.method = Options; // Step 2 - preflight.headers = RequestHeaderCollection::new(); // Step 3 - // Step 4 - preflight.insert_string_header("Access-Control-Request-Method".to_string(), self.method.http_value()); - - // 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.sort(); - let header_list = header_names.connect(", "); // 0x2C 0x20 - preflight.insert_string_header("Access-Control-Request-Headers".to_string(), header_list); - - // 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, - Err(_) => return error - }; - - let host = writer.headers.host.clone(); - writer.headers = box preflight.headers.clone(); - writer.headers.host = host; - let response = match writer.read_response() { - Ok(r) => r, - Err(_) => return error - }; - - // Step 12 - match response.status.code() { - 200 .. 299 => {} - _ => 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, - _ => return error - }; - let methods = methods_string.as_slice().split(','); - let headers_string = match find_header(&*response.headers, "Access-Control-Allow-Headers") { - Some(s) => s, - _ => 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 - if methods.len() == 0 || preflight.mode == ForcedPreflightMode { - methods = vec!(self.method.http_value()); - } - // 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())) { - return error; - } - // Substep 6 - for h in self.headers.iter() { - if is_simple_header(&h) { - continue; - } - if !headers.iter().any(|ref h2| h.header_name().as_slice().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); - // 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()))); - } - }); - } - for h in headers.iter() { - let cache_match = cache.match_header_and_update(self, h.as_slice(), max_age); - if !cache_match { - cache.insert(CORSCacheEntry::new(self.origin.clone(), self.destination.clone(), - max_age, false, HeaderData(h.to_string()))); - } - } - 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 -} - -impl CORSResponse { - fn new() -> CORSResponse { - CORSResponse { - network_error: false, - headers: ResponseHeaderCollection::new() - } - } - - fn new_error() -> CORSResponse { - CORSResponse { - network_error: true, - headers: ResponseHeaderCollection::new() - } - } -} - -// CORS Cache stuff - -/// A CORS cache object. Anchor it somewhere to the user agent. -#[deriving(Clone)] -pub struct CORSCache(Vec<CORSCacheEntry>); - -/// Union type for CORS cache entries -/// Each entry might pertain to a header or method -#[deriving(Clone)] -pub enum HeaderOrMethod { - HeaderData(String), - MethodData(Method) -} - -impl HeaderOrMethod { - fn match_header(&self, header_name: &str) -> bool { - match *self { - HeaderData(ref s) => s.as_slice().eq_ignore_ascii_case(header_name), - _ => false - } - } - - fn match_method(&self, method: &Method) -> bool { - match *self { - MethodData(ref m) => m == method, - _ => false - } - } -} - -// An entry in the CORS cache -#[deriving(Clone)] -pub struct CORSCacheEntry { - pub origin: Url, - pub url: Url, - pub max_age: uint, - 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 { - CORSCacheEntry { - origin: origin, - url: url, - max_age: max_age, - credentials: credentials, - header_or_method: header_or_method, - created: time::now().to_timespec() - } - } -} - -impl CORSCache { - /// http://fetch.spec.whatwg.org/#concept-cache-clear - #[allow(dead_code)] - fn clear (&mut self, request: &CORSRequest) { - let CORSCache(buf) = self.clone(); - let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect(); - *self = CORSCache(new_buf); - } - - // Remove old entries - fn cleanup(&mut self) { - let CORSCache(buf) = self.clone(); - let now = time::now().to_timespec(); - let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| now.sec > e.created.sec + e.max_age as i64).collect(); - *self = CORSCache(new_buf); - } - - /// http://fetch.spec.whatwg.org/#concept-cache-match-header - fn find_entry_by_header<'a>(&'a mut self, request: &CORSRequest, header_name: &str) -> Option<&'a mut CORSCacheEntry> { - self.cleanup(); - let CORSCache(ref mut buf) = *self; - // Credentials are not yet implemented here - let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme && - e.origin.host() == request.origin.host() && - e.origin.port() == request.origin.port() && - e.url == request.destination && - e.header_or_method.match_header(header_name)); - entry - } - - fn match_header(&mut self, request: &CORSRequest, header_name: &str) -> bool { - 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 { - self.find_entry_by_header(request, header_name).map(|e| e.max_age = new_max_age).is_some() - } - - fn find_entry_by_method<'a>(&'a mut self, request: &CORSRequest, method: &Method) -> Option<&'a mut CORSCacheEntry> { - // we can take the method from CORSRequest itself - self.cleanup(); - let CORSCache(ref mut buf) = *self; - // Credentials are not yet implemented here - let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme && - e.origin.host() == request.origin.host() && - e.origin.port() == request.origin.port() && - e.url == request.destination && - e.header_or_method.match_method(method)); - entry - } - - /// http://fetch.spec.whatwg.org/#concept-cache-match-method - fn match_method(&mut self, request: &CORSRequest, method: &Method) -> bool { - 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 { - self.find_entry_by_method(request, method).map(|e| e.max_age = new_max_age).is_some() - } - - fn insert(&mut self, entry: CORSCacheEntry) { - self.cleanup(); - let CORSCache(ref mut buf) = *self; - buf.push(entry); - } -} - -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, - _ => false - }, - _ => false - } -} - -fn is_simple_method(m: &Method) -> bool { - match *m { - Get | Head | Post => true, - _ => false - } -} - -/// 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 == "*".to_string() { - 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 - } - }, - None => false - } -} |