diff options
Diffstat (limited to 'components/script/cors.rs')
-rw-r--r-- | components/script/cors.rs | 490 |
1 files changed, 0 insertions, 490 deletions
diff --git a/components/script/cors.rs b/components/script/cors.rs deleted file mode 100644 index f9d0bbbd264..00000000000 --- a/components/script/cors.rs +++ /dev/null @@ -1,490 +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 hyper::client::Request; -use hyper::header::{AccessControlAllowHeaders, AccessControlRequestHeaders}; -use hyper::header::{AccessControlAllowMethods, AccessControlRequestMethod}; -use hyper::header::{AccessControlAllowOrigin, AccessControlMaxAge}; -use hyper::header::{ContentType, Host}; -use hyper::header::{HeaderView, Headers}; -use hyper::method::Method; -use hyper::mime::{Mime, SubLevel, TopLevel}; -use hyper::status::StatusClass::Success; -use net_traits::{AsyncResponseListener, Metadata, NetworkError, ResponseAction}; -use network_listener::{NetworkListener, PreInvoke}; -use script_runtime::ScriptChan; -use std::ascii::AsciiExt; -use std::borrow::ToOwned; -use std::sync::{Arc, Mutex}; -use time::{self, Timespec, now}; -use unicase::UniCase; -use url::Url; -use util::thread::spawn_named; - -/// Interface for network listeners concerned with CORS checks. Proper network requests -/// should be initiated from this method, based on the response provided. -pub trait AsyncCORSResponseListener { - fn response_available(&self, response: CORSResponse); -} - -#[derive(Clone, HeapSizeOf)] -pub struct CORSRequest { - pub origin: Url, - pub destination: Url, - pub mode: RequestMode, - #[ignore_heap_size_of = "Defined in hyper"] - pub method: Method, - #[ignore_heap_size_of = "Defined in hyper"] - pub headers: Headers, - /// CORS preflight flag (https://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, -} - -/// https://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. -#[derive(PartialEq, Copy, Clone, HeapSizeOf)] -pub enum RequestMode { - CORS, // CORS - ForcedPreflight, // 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: Headers, - same_origin_data_url_flag: bool) - -> Result<Option<CORSRequest>, ()> { - if referer.origin() == destination.origin() { - return Ok(None); // Not cross-origin, proceed with a normal fetch - } - match destination.scheme() { - // As per (https://fetch.spec.whatwg.org/#main-fetch 5.1.9), about URLs can be fetched - // the same as a basic request. - "about" if destination.path() == "blank" => Ok(None), - // As per (https://fetch.spec.whatwg.org/#main-fetch 5.1.9), data URLs can be fetched - // the same as a basic request if the request's method is GET and the - // same-origin data-URL flag is set. - "data" if same_origin_data_url_flag && method == Method::Get => Ok(None), - "http" | "https" => { - let mut req = CORSRequest::new(referer, destination, mode, method, headers); - req.preflight_flag = !is_simple_method(&req.method) || - mode == RequestMode::ForcedPreflight; - if req.headers.iter().any(|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: Headers) - -> CORSRequest { - referer.set_fragment(None); - referer.set_query(None); - referer.set_path(""); - CORSRequest { - origin: referer, - destination: destination, - mode: mode, - method: method, - headers: headers, - preflight_flag: false, - } - } - - pub fn http_fetch_async(&self, - listener: Box<AsyncCORSResponseListener + Send>, - script_chan: Box<ScriptChan + Send>) { - struct CORSContext { - listener: Box<AsyncCORSResponseListener + Send>, - response: Option<CORSResponse>, - } - - // This is shoe-horning the CORSReponse stuff into the rest of the async network - // framework right now. It would be worth redesigning http_fetch to do this properly. - impl AsyncResponseListener for CORSContext { - fn headers_available(&mut self, _metadata: Result<Metadata, NetworkError>) { - } - - fn data_available(&mut self, _payload: Vec<u8>) { - } - - fn response_complete(&mut self, _status: Result<(), NetworkError>) { - let response = self.response.take().unwrap(); - self.listener.response_available(response); - } - } - impl PreInvoke for CORSContext {} - - let context = CORSContext { - listener: listener, - response: None, - }; - let listener = NetworkListener { - context: Arc::new(Mutex::new(context)), - script_chan: script_chan, - }; - - // TODO: this exists only to make preflight check non-blocking - // perhaps should be handled by the resource thread? - let req = self.clone(); - spawn_named("cors".to_owned(), move || { - let response = req.http_fetch(); - let mut context = listener.context.lock(); - let context = context.as_mut().unwrap(); - context.response = Some(response); - listener.notify(ResponseAction::ResponseComplete(Ok(()))); - }); - } - - /// 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 ForcedPreflight, 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.name())) && - (!is_simple_method(&self.method) || self.mode == RequestMode::ForcedPreflight) { - 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 - } - - /// https://fetch.spec.whatwg.org/#cors-preflight-fetch - fn preflight_fetch(&self) -> CORSResponse { - let error = CORSResponse::new_error(); - let mut cors_response = CORSResponse::new(); - - // Step 1 - let mut preflight = self.clone(); - preflight.method = Method::Options; - preflight.headers = Headers::new(); - // Step 2 - preflight.headers.set(AccessControlRequestMethod(self.method.clone())); - - // Steps 3-5 - let mut header_names = vec![]; - for header in self.headers.iter() { - header_names.push(header.name().to_owned()); - } - header_names.sort(); - preflight.headers - .set(AccessControlRequestHeaders(header_names.into_iter().map(UniCase).collect())); - - let preflight_request = Request::new(preflight.method, preflight.destination); - let mut req = match preflight_request { - Ok(req) => req, - Err(_) => return error, - }; - - 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, - }; - // Step 6 - let response = match stream.send() { - Ok(r) => r, - Err(_) => return error, - }; - - // Step 7: We don't perform a CORS check here - // FYI, fn allow_cross_origin_request() performs the CORS check - match response.status.class() { - Success => {} - _ => return error, - } - cors_response.headers = response.headers.clone(); - // Substeps 1-3 (parsing rules: https://fetch.spec.whatwg.org/#http-new-header-syntax) - let methods_substep4 = [self.method.clone()]; - let mut methods = match response.headers.get() { - Some(&AccessControlAllowMethods(ref v)) => &**v, - _ => return error, - }; - let headers = match response.headers.get() { - Some(&AccessControlAllowHeaders(ref h)) => h, - _ => return error, - }; - // Substep 4 - if methods.is_empty() && preflight.mode == RequestMode::ForcedPreflight { - methods = &methods_substep4; - } - // Substep 5 - if !is_simple_method(&self.method) && !methods.iter().any(|m| m == &self.method) { - return error; - } - // Substep 6 - for h in self.headers.iter() { - if is_simple_header(&h) { - continue; - } - if !headers.iter().any(|ref h2| h.name().eq_ignore_ascii_case(h2)) { - return error; - } - } - // Substeps 7-8 - 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 { - 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, - HeaderOrMethod::MethodData(m.clone()))); - } - } - // Substeps 13-14 - 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, - HeaderOrMethod::HeaderData(h.to_string()))); - } - } - // Substep 15 - cors_response - } -} - - -pub struct CORSResponse { - pub network_error: bool, - pub headers: Headers, -} - -impl CORSResponse { - fn new() -> CORSResponse { - CORSResponse { - network_error: false, - headers: Headers::new(), - } - } - - fn new_error() -> CORSResponse { - CORSResponse { - network_error: true, - headers: Headers::new(), - } - } -} - -// CORS Cache stuff - -/// A CORS cache object. Anchor it somewhere to the user agent. -#[derive(Clone)] -pub struct CORSCache(Vec<CORSCacheEntry>); - -/// Union type for CORS cache entries -/// Each entry might pertain to a header or method -#[derive(Clone)] -pub enum HeaderOrMethod { - HeaderData(String), - MethodData(Method), -} - -impl HeaderOrMethod { - fn match_header(&self, header_name: &str) -> bool { - match *self { - HeaderOrMethod::HeaderData(ref s) => (&**s).eq_ignore_ascii_case(header_name), - _ => false, - } - } - - fn match_method(&self, method: &Method) -> bool { - match *self { - HeaderOrMethod::MethodData(ref m) => m == method, - _ => false, - } - } -} - -// An entry in the CORS cache -#[derive(Clone)] -pub struct CORSCacheEntry { - pub origin: Url, - pub url: Url, - pub max_age: u32, - pub credentials: bool, - pub header_or_method: HeaderOrMethod, - created: Timespec, -} - -impl CORSCacheEntry { - fn new(origin: Url, - url: Url, - max_age: u32, - 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 { - /// https://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.into_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.into_iter() - .filter(|e| now.sec > e.created.sec + e.max_age as i64) - .collect(); - *self = CORSCache(new_buf); - } - - /// https://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(); - // Credentials are not yet implemented here - self.0.iter_mut().find(|e| { - e.origin.scheme() == request.origin.scheme() && - e.origin.host_str() == request.origin.host_str() && - e.origin.port() == request.origin.port() && - e.url == request.destination && - e.header_or_method.match_header(header_name) - }) - } - - 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: u32) - -> 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(); - // Credentials are not yet implemented here - self.0.iter_mut().find(|e| { - e.origin.scheme() == request.origin.scheme() && - e.origin.host_str() == request.origin.host_str() && - e.origin.port() == request.origin.port() && - e.url == request.destination && - e.header_or_method.match_method(method) - }) - } - - /// https://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: u32) - -> 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(); - self.0.push(entry); - } -} - -fn is_simple_header(h: &HeaderView) -> bool { - // FIXME: use h.is::<HeaderType>() when AcceptLanguage and - // ContentLanguage headers exist - match &*h.name().to_ascii_lowercase() { - "accept" | "accept-language" | "content-language" => true, - "content-type" => match h.value() { - Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) | - Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _))) | - Some(&ContentType(Mime(TopLevel::Multipart, SubLevel::FormData, _))) => true, - - _ => false, - - }, - _ => false, - } -} - -fn is_simple_method(m: &Method) -> bool { - match *m { - Method::Get | Method::Head | Method::Post => true, - _ => false, - } -} - -/// Perform a CORS check on a header list and CORS request -/// https://fetch.spec.whatwg.org/#cors-check -pub fn allow_cross_origin_request(req: &CORSRequest, headers: &Headers) -> bool { - match headers.get::<AccessControlAllowOrigin>() { - Some(&AccessControlAllowOrigin::Any) => true, // Not always true, depends on credentials mode - Some(&AccessControlAllowOrigin::Value(ref url)) => req.origin.as_str() == *url, - Some(&AccessControlAllowOrigin::Null) | - None => false, - } -} |