mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Basic implementation of CORS
This commit is contained in:
parent
9ab53ba44b
commit
a64dafd815
5 changed files with 509 additions and 11 deletions
|
@ -106,6 +106,20 @@ fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
|
||||||
if 3 == (response.status.code() / 100) {
|
if 3 == (response.status.code() / 100) {
|
||||||
match response.headers.location {
|
match response.headers.location {
|
||||||
Some(new_url) => {
|
Some(new_url) => {
|
||||||
|
// CORS (http://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10)
|
||||||
|
match load_data.cors {
|
||||||
|
Some(ref c) => {
|
||||||
|
if c.preflight {
|
||||||
|
// The preflight lied
|
||||||
|
send_error(url, "Preflight fetch inconsistent with main fetch".to_string(), start_chan);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// XXXManishearth There are some CORS-related steps here,
|
||||||
|
// but they don't seem necessary until credentials are implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
info!("redirecting to {:s}", new_url.serialize());
|
info!("redirecting to {:s}", new_url.serialize());
|
||||||
url = new_url;
|
url = new_url;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -32,7 +32,8 @@ pub struct LoadData {
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
pub headers: RequestHeaderCollection,
|
pub headers: RequestHeaderCollection,
|
||||||
pub data: Option<Vec<u8>>
|
pub data: Option<Vec<u8>>,
|
||||||
|
pub cors: Option<ResourceCORSData>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoadData {
|
impl LoadData {
|
||||||
|
@ -41,11 +42,20 @@ impl LoadData {
|
||||||
url: url,
|
url: url,
|
||||||
method: Get,
|
method: Get,
|
||||||
headers: RequestHeaderCollection::new(),
|
headers: RequestHeaderCollection::new(),
|
||||||
data: None
|
data: None,
|
||||||
|
cors: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
|
pub struct ResourceCORSData {
|
||||||
|
/// CORS Preflight flag
|
||||||
|
pub preflight: bool,
|
||||||
|
/// Origin of CORS Request
|
||||||
|
pub origin: Url
|
||||||
|
}
|
||||||
|
|
||||||
/// Metadata about a loaded resource, such as is obtained from HTTP headers.
|
/// Metadata about a loaded resource, such as is obtained from HTTP headers.
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
/// Final URL after redirects.
|
/// Final URL after redirects.
|
||||||
|
|
418
src/components/script/cors.rs
Normal file
418
src/components/script/cors.rs
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
/* 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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,8 @@ use js::jsval::{JSVal, NullValue, UndefinedValue};
|
||||||
use libc;
|
use libc;
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
|
|
||||||
use net::resource_task::{ResourceTask, Load, LoadData, Payload, Done};
|
use net::resource_task::{ResourceTask, ResourceCORSData, Load, LoadData, Payload, Done};
|
||||||
|
use cors::{allow_cross_origin_request, CORSRequest, CORSMode, ForcedPreflightMode};
|
||||||
use script_task::{ScriptChan, XHRProgressMsg};
|
use script_task::{ScriptChan, XHRProgressMsg};
|
||||||
use servo_util::str::DOMString;
|
use servo_util::str::DOMString;
|
||||||
use servo_util::task::spawn_named;
|
use servo_util::task::spawn_named;
|
||||||
|
@ -180,7 +181,8 @@ impl XMLHttpRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(fetch_type: &SyncOrAsync, resource_task: ResourceTask,
|
fn fetch(fetch_type: &SyncOrAsync, resource_task: ResourceTask,
|
||||||
load_data: LoadData, terminate_receiver: Receiver<Error>) -> ErrorResult {
|
mut load_data: LoadData, terminate_receiver: Receiver<Error>,
|
||||||
|
cors_request: Result<Option<CORSRequest>,()>) -> ErrorResult {
|
||||||
fn notify_partial_progress(fetch_type: &SyncOrAsync, msg: XHRProgress) {
|
fn notify_partial_progress(fetch_type: &SyncOrAsync, msg: XHRProgress) {
|
||||||
match *fetch_type {
|
match *fetch_type {
|
||||||
Sync(ref xhr) => {
|
Sync(ref xhr) => {
|
||||||
|
@ -193,6 +195,22 @@ impl XMLHttpRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match cors_request {
|
||||||
|
Err(_) => return Err(Network), // Happens in case of cross-origin non-http URIs
|
||||||
|
Ok(Some(ref req)) => {
|
||||||
|
let response = req.http_fetch();
|
||||||
|
if response.network_error {
|
||||||
|
return Err(Network)
|
||||||
|
} else {
|
||||||
|
load_data.cors = Some(ResourceCORSData {
|
||||||
|
preflight: req.preflight_flag,
|
||||||
|
origin: req.origin.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
// Step 10, 13
|
// Step 10, 13
|
||||||
let (start_chan, start_port) = channel();
|
let (start_chan, start_port) = channel();
|
||||||
resource_task.send(Load(load_data, start_chan));
|
resource_task.send(Load(load_data, start_chan));
|
||||||
|
@ -201,6 +219,17 @@ impl XMLHttpRequest {
|
||||||
Ok(e) => return Err(e),
|
Ok(e) => return Err(e),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
match cors_request {
|
||||||
|
Ok(Some(ref req)) => {
|
||||||
|
match response.metadata.headers {
|
||||||
|
Some(ref h) if allow_cross_origin_request(req, h) => {},
|
||||||
|
_ => return Err(Network)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// XXXManishearth Clear cache entries in case of a network error
|
||||||
|
|
||||||
notify_partial_progress(fetch_type, HeadersReceivedMsg(
|
notify_partial_progress(fetch_type, HeadersReceivedMsg(
|
||||||
response.metadata.headers.clone(), response.metadata.status.clone()));
|
response.metadata.headers.clone(), response.metadata.status.clone()));
|
||||||
let mut buf = vec!();
|
let mut buf = vec!();
|
||||||
|
@ -514,23 +543,48 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
|
||||||
request_headers.borrow_mut().accept = Some(String::from_str("*/*"))
|
request_headers.borrow_mut().accept = Some(String::from_str("*/*"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXXManishearth this is to be replaced with Origin for CORS (with no path)
|
|
||||||
let referer_url = self.global.root().root_ref().get_url();
|
|
||||||
self.request_headers.deref().borrow_mut().referer =
|
|
||||||
Some(referer_url.serialize_no_fragment());
|
|
||||||
|
|
||||||
load_data.headers = (*self.request_headers.deref().borrow()).clone();
|
load_data.headers = (*self.request_headers.deref().borrow()).clone();
|
||||||
load_data.method = (*self.request_method.deref().borrow()).clone();
|
load_data.method = (*self.request_method.deref().borrow()).clone();
|
||||||
let (terminate_sender, terminate_receiver) = channel();
|
let (terminate_sender, terminate_receiver) = channel();
|
||||||
*self.terminate_sender.deref().borrow_mut() = Some(terminate_sender);
|
*self.terminate_sender.deref().borrow_mut() = Some(terminate_sender);
|
||||||
|
|
||||||
|
// CORS stuff
|
||||||
|
let referer_url = self.global.root().root_ref().get_url();
|
||||||
|
let mode = if self.upload_events.deref().get() {
|
||||||
|
ForcedPreflightMode
|
||||||
|
} else {
|
||||||
|
CORSMode
|
||||||
|
};
|
||||||
|
let cors_request = CORSRequest::maybe_new(referer_url.clone(), load_data.url.clone(), mode,
|
||||||
|
load_data.method.clone(), load_data.headers.clone());
|
||||||
|
match cors_request {
|
||||||
|
Ok(None) => {
|
||||||
|
let mut buf = String::new();
|
||||||
|
buf.push_str(referer_url.scheme.as_slice());
|
||||||
|
buf.push_str("://".as_slice());
|
||||||
|
referer_url.serialize_host().map(|ref h| buf.push_str(h.as_slice()));
|
||||||
|
referer_url.port().as_ref().map(|&p| {
|
||||||
|
buf.push_str(":".as_slice());
|
||||||
|
buf.push_str(p);
|
||||||
|
});
|
||||||
|
referer_url.serialize_path().map(|ref h| buf.push_str(h.as_slice()));
|
||||||
|
self.request_headers.deref().borrow_mut().referer = Some(buf);
|
||||||
|
},
|
||||||
|
Ok(Some(ref req)) => self.insert_trusted_header("origin".to_string(),
|
||||||
|
format!("{}", req.origin)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if self.sync.deref().get() {
|
if self.sync.deref().get() {
|
||||||
return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data, terminate_receiver);
|
return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data,
|
||||||
|
terminate_receiver, cors_request);
|
||||||
} else {
|
} else {
|
||||||
let builder = TaskBuilder::new().named("XHRTask");
|
let builder = TaskBuilder::new().named("XHRTask");
|
||||||
self.fetch_time.deref().set(time::now().to_timespec().sec);
|
self.fetch_time.deref().set(time::now().to_timespec().sec);
|
||||||
let script_chan = global.root_ref().script_chan().clone();
|
let script_chan = global.root_ref().script_chan().clone();
|
||||||
builder.spawn(proc() {
|
builder.spawn(proc() {
|
||||||
let _ = XMLHttpRequest::fetch(&mut Async(addr.unwrap(), script_chan), resource_task, load_data, terminate_receiver);
|
let _ = XMLHttpRequest::fetch(&mut Async(addr.unwrap(), script_chan),
|
||||||
|
resource_task, load_data, terminate_receiver, cors_request);
|
||||||
});
|
});
|
||||||
let timeout = self.timeout.deref().get();
|
let timeout = self.timeout.deref().get();
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
|
|
|
@ -42,6 +42,8 @@ extern crate sync;
|
||||||
extern crate servo_msg = "msg";
|
extern crate servo_msg = "msg";
|
||||||
extern crate url = "url_";
|
extern crate url = "url_";
|
||||||
|
|
||||||
|
|
||||||
|
pub mod cors;
|
||||||
pub mod dom {
|
pub mod dom {
|
||||||
pub mod bindings {
|
pub mod bindings {
|
||||||
pub mod global;
|
pub mod global;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue