mirror of
https://github.com/servo/servo.git
synced 2025-06-20 15:18:58 +01:00
Auto merge of #10867 - danlrobertson:sandbox, r=KiChjang
Fix logic for cors cache match The current logic for a cors cache match does not consider "credentials is false and request's credentials mode is not "include" or credentials is true." I could have missed something, but `CacheRequestDetails::credentials` is set to true if credentials mode is "include", and false otherwise. So `(!cors_cache.credentials && !cors_req.credentials) || cors_cache.credentials` would be directly following the spec, but unless I'm mistaken `cors_cache.credentials || !cors_req.credentials` is logically the same. Fixes: #10525 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10867) <!-- Reviewable:end -->
This commit is contained in:
commit
3d38a60cee
4 changed files with 119 additions and 214 deletions
|
@ -12,7 +12,6 @@
|
||||||
use hyper::method::Method;
|
use hyper::method::Method;
|
||||||
use net_traits::request::Origin;
|
use net_traits::request::Origin;
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::sync::mpsc::{Sender, Receiver, channel};
|
|
||||||
use time;
|
use time;
|
||||||
use time::{now, Timespec};
|
use time::{now, Timespec};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -20,7 +19,7 @@ use url::Url;
|
||||||
/// Union type for CORS cache entries
|
/// Union type for CORS cache entries
|
||||||
///
|
///
|
||||||
/// Each entry might pertain to a header or method
|
/// Each entry might pertain to a header or method
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum HeaderOrMethod {
|
pub enum HeaderOrMethod {
|
||||||
HeaderData(String),
|
HeaderData(String),
|
||||||
MethodData(Method)
|
MethodData(Method)
|
||||||
|
@ -43,7 +42,7 @@ impl HeaderOrMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entry in the CORS cache
|
/// An entry in the CORS cache
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CORSCacheEntry {
|
pub struct CORSCacheEntry {
|
||||||
pub origin: Origin,
|
pub origin: Origin,
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
|
@ -74,51 +73,20 @@ pub struct CacheRequestDetails {
|
||||||
pub credentials: bool
|
pub credentials: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for a generic CORS Cache
|
fn match_headers(cors_cache: &CORSCacheEntry, cors_req: &CacheRequestDetails) -> bool {
|
||||||
pub trait CORSCache {
|
cors_cache.origin == cors_req.origin &&
|
||||||
/// [Clear the cache](https://fetch.spec.whatwg.org/#concept-cache-clear)
|
cors_cache.url == cors_req.destination &&
|
||||||
fn clear (&mut self, request: CacheRequestDetails);
|
(cors_cache.credentials || !cors_req.credentials)
|
||||||
|
|
||||||
/// Remove old entries
|
|
||||||
fn cleanup(&mut self);
|
|
||||||
|
|
||||||
/// Returns true if an entry with a
|
|
||||||
/// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found
|
|
||||||
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool;
|
|
||||||
|
|
||||||
/// Updates max age if an entry for a
|
|
||||||
/// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
|
|
||||||
///
|
|
||||||
/// If not, it will insert an equivalent entry
|
|
||||||
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: u32) -> bool;
|
|
||||||
|
|
||||||
/// Returns true if an entry with a
|
|
||||||
/// [matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found
|
|
||||||
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool;
|
|
||||||
|
|
||||||
/// Updates max age if an entry for
|
|
||||||
/// [a matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
|
|
||||||
///
|
|
||||||
/// If not, it will insert an equivalent entry
|
|
||||||
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: u32) -> bool;
|
|
||||||
/// Insert an entry
|
|
||||||
fn insert(&mut self, entry: CORSCacheEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple, vector-based CORS Cache
|
/// A simple, vector-based CORS Cache
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BasicCORSCache(Vec<CORSCacheEntry>);
|
pub struct CORSCache(Vec<CORSCacheEntry>);
|
||||||
|
|
||||||
fn match_headers(cors_cache: &CORSCacheEntry, cors_req: &CacheRequestDetails) -> bool {
|
impl CORSCache {
|
||||||
cors_cache.origin == cors_req.origin &&
|
|
||||||
cors_cache.url == cors_req.destination &&
|
|
||||||
cors_cache.credentials == cors_req.credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasicCORSCache {
|
pub fn new() -> CORSCache {
|
||||||
|
CORSCache(vec![])
|
||||||
pub fn new() -> BasicCORSCache {
|
|
||||||
BasicCORSCache(vec![])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_entry_by_header<'a>(&'a mut self, request: &CacheRequestDetails,
|
fn find_entry_by_header<'a>(&'a mut self, request: &CacheRequestDetails,
|
||||||
|
@ -133,33 +101,37 @@ impl BasicCORSCache {
|
||||||
self.cleanup();
|
self.cleanup();
|
||||||
self.0.iter_mut().find(|e| match_headers(e, request) && e.header_or_method.match_method(&method))
|
self.0.iter_mut().find(|e| match_headers(e, request) && e.header_or_method.match_method(&method))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl CORSCache for BasicCORSCache {
|
/// [Clear the cache](https://fetch.spec.whatwg.org/#concept-cache-clear)
|
||||||
/// https://fetch.spec.whatwg.org/#concept-cache-clear
|
pub fn clear (&mut self, request: CacheRequestDetails) {
|
||||||
#[allow(dead_code)]
|
let CORSCache(buf) = self.clone();
|
||||||
fn clear (&mut self, request: CacheRequestDetails) {
|
|
||||||
let BasicCORSCache(buf) = self.clone();
|
|
||||||
let new_buf: Vec<CORSCacheEntry> =
|
let new_buf: Vec<CORSCacheEntry> =
|
||||||
buf.into_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
|
buf.into_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
|
||||||
*self = BasicCORSCache(new_buf);
|
*self = CORSCache(new_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old entries
|
/// Remove old entries
|
||||||
fn cleanup(&mut self) {
|
pub fn cleanup(&mut self) {
|
||||||
let BasicCORSCache(buf) = self.clone();
|
let CORSCache(buf) = self.clone();
|
||||||
let now = time::now().to_timespec();
|
let now = time::now().to_timespec();
|
||||||
let new_buf: Vec<CORSCacheEntry> = buf.into_iter()
|
let new_buf: Vec<CORSCacheEntry> = buf.into_iter()
|
||||||
.filter(|e| now.sec > e.created.sec + e.max_age as i64)
|
.filter(|e| now.sec < e.created.sec + e.max_age as i64)
|
||||||
.collect();
|
.collect();
|
||||||
*self = BasicCORSCache(new_buf);
|
*self = CORSCache(new_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
|
/// Returns true if an entry with a
|
||||||
|
/// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found
|
||||||
|
pub fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
|
||||||
self.find_entry_by_header(&request, header_name).is_some()
|
self.find_entry_by_header(&request, header_name).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: u32) -> bool {
|
/// Updates max age if an entry for a
|
||||||
|
/// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
|
||||||
|
///
|
||||||
|
/// If not, it will insert an equivalent entry
|
||||||
|
pub fn match_header_and_update(&mut self, request: CacheRequestDetails,
|
||||||
|
header_name: &str, new_max_age: u32) -> bool {
|
||||||
match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) {
|
match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) {
|
||||||
Some(_) => true,
|
Some(_) => true,
|
||||||
None => {
|
None => {
|
||||||
|
@ -171,11 +143,17 @@ impl CORSCache for BasicCORSCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
|
/// Returns true if an entry with a
|
||||||
|
/// [matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found
|
||||||
|
pub fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
|
||||||
self.find_entry_by_method(&request, method).is_some()
|
self.find_entry_by_method(&request, method).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: u32) -> bool {
|
/// Updates max age if an entry for
|
||||||
|
/// [a matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
|
||||||
|
///
|
||||||
|
/// If not, it will insert an equivalent entry
|
||||||
|
pub fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: u32) -> bool {
|
||||||
match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
|
match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
|
||||||
Some(_) => true,
|
Some(_) => true,
|
||||||
None => {
|
None => {
|
||||||
|
@ -186,138 +164,9 @@ impl CORSCache for BasicCORSCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, entry: CORSCacheEntry) {
|
/// Insert an entry
|
||||||
|
pub fn insert(&mut self, entry: CORSCacheEntry) {
|
||||||
self.cleanup();
|
self.cleanup();
|
||||||
self.0.push(entry);
|
self.0.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Various messages that can be sent to a CORSCacheThread
|
|
||||||
pub enum CORSCacheThreadMsg {
|
|
||||||
Clear(CacheRequestDetails, Sender<()>),
|
|
||||||
Cleanup(Sender<()>),
|
|
||||||
MatchHeader(CacheRequestDetails, String, Sender<bool>),
|
|
||||||
MatchHeaderUpdate(CacheRequestDetails, String, u32, Sender<bool>),
|
|
||||||
MatchMethod(CacheRequestDetails, Method, Sender<bool>),
|
|
||||||
MatchMethodUpdate(CacheRequestDetails, Method, u32, Sender<bool>),
|
|
||||||
Insert(CORSCacheEntry, Sender<()>),
|
|
||||||
ExitMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Sender to a CORSCacheThread
|
|
||||||
///
|
|
||||||
/// This can be used as a CORS Cache.
|
|
||||||
/// The methods on this type block until they can run, and it behaves similar to a mutex
|
|
||||||
pub type CORSCacheSender = Sender<CORSCacheThreadMsg>;
|
|
||||||
|
|
||||||
impl CORSCache for CORSCacheSender {
|
|
||||||
fn clear (&mut self, request: CacheRequestDetails) {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::Clear(request, tx));
|
|
||||||
let _ = rx.recv();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cleanup(&mut self) {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::Cleanup(tx));
|
|
||||||
let _ = rx.recv();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::MatchHeader(request, header_name.to_owned(), tx));
|
|
||||||
rx.recv().unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: u32) -> bool {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::MatchHeaderUpdate(request, header_name.to_owned(), new_max_age, tx));
|
|
||||||
rx.recv().unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::MatchMethod(request, method, tx));
|
|
||||||
rx.recv().unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: u32) -> bool {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::MatchMethodUpdate(request, method, new_max_age, tx));
|
|
||||||
rx.recv().unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(&mut self, entry: CORSCacheEntry) {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let _ = self.send(CORSCacheThreadMsg::Insert(entry, tx));
|
|
||||||
let _ = rx.recv();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simple thread-based CORS Cache that can be sent messages
|
|
||||||
///
|
|
||||||
/// #Example
|
|
||||||
/// ```ignore
|
|
||||||
/// let thread = CORSCacheThread::new();
|
|
||||||
/// let builder = ThreadBuilder::new().named("XHRThread");
|
|
||||||
/// let mut sender = thread.sender();
|
|
||||||
/// builder.spawn(move || { thread.run() });
|
|
||||||
/// sender.insert(CORSCacheEntry::new(/* parameters here */));
|
|
||||||
/// ```
|
|
||||||
pub struct CORSCacheThread {
|
|
||||||
receiver: Receiver<CORSCacheThreadMsg>,
|
|
||||||
cache: BasicCORSCache,
|
|
||||||
sender: CORSCacheSender
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CORSCacheThread {
|
|
||||||
pub fn new() -> CORSCacheThread {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
CORSCacheThread {
|
|
||||||
receiver: rx,
|
|
||||||
cache: BasicCORSCache(vec![]),
|
|
||||||
sender: tx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides a sender to the cache thread
|
|
||||||
pub fn sender(&self) -> CORSCacheSender {
|
|
||||||
self.sender.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the cache thread
|
|
||||||
/// This blocks the current thread, so it is advised
|
|
||||||
/// to spawn a new thread for this
|
|
||||||
/// Send ExitMsg to the associated Sender to exit
|
|
||||||
pub fn run(&mut self) {
|
|
||||||
loop {
|
|
||||||
match self.receiver.recv().unwrap() {
|
|
||||||
CORSCacheThreadMsg::Clear(request, tx) => {
|
|
||||||
self.cache.clear(request);
|
|
||||||
let _ = tx.send(());
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::Cleanup(tx) => {
|
|
||||||
self.cache.cleanup();
|
|
||||||
let _ = tx.send(());
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::MatchHeader(request, header, tx) => {
|
|
||||||
let _ = tx.send(self.cache.match_header(request, &header));
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::MatchHeaderUpdate(request, header, new_max_age, tx) => {
|
|
||||||
let _ = tx.send(self.cache.match_header_and_update(request, &header, new_max_age));
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::MatchMethod(request, method, tx) => {
|
|
||||||
let _ = tx.send(self.cache.match_method(request, method));
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::MatchMethodUpdate(request, method, new_max_age, tx) => {
|
|
||||||
let _ = tx.send(self.cache.match_method_and_update(request, method, new_max_age));
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::Insert(entry, tx) => {
|
|
||||||
self.cache.insert(entry);
|
|
||||||
let _ = tx.send(());
|
|
||||||
},
|
|
||||||
CORSCacheThreadMsg::ExitMsg => break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use data_loader::decode;
|
use data_loader::decode;
|
||||||
use fetch::cors_cache::{BasicCORSCache, CORSCache, CacheRequestDetails};
|
use fetch::cors_cache::{CORSCache, CacheRequestDetails};
|
||||||
use http_loader::{NetworkHttpRequestFactory, create_http_connector, obtain_response};
|
use http_loader::{NetworkHttpRequestFactory, create_http_connector, obtain_response};
|
||||||
use hyper::header::{Accept, AcceptLanguage, Authorization, AccessControlAllowCredentials};
|
use hyper::header::{Accept, AcceptLanguage, Authorization, AccessControlAllowCredentials};
|
||||||
use hyper::header::{AccessControlAllowOrigin, AccessControlAllowHeaders, AccessControlAllowMethods};
|
use hyper::header::{AccessControlAllowOrigin, AccessControlAllowHeaders, AccessControlAllowMethods};
|
||||||
|
@ -40,6 +40,10 @@ pub fn fetch_async(request: Request, listener: Box<AsyncFetchListener + Send>) {
|
||||||
|
|
||||||
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
|
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
|
||||||
pub fn fetch(request: Rc<Request>) -> Response {
|
pub fn fetch(request: Rc<Request>) -> Response {
|
||||||
|
fetch_with_cors_cache(request, &mut CORSCache::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_with_cors_cache(request: Rc<Request>, cache: &mut CORSCache) -> Response {
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
if request.window.get() == Window::Client {
|
if request.window.get() == Window::Client {
|
||||||
|
@ -102,11 +106,11 @@ pub fn fetch(request: Rc<Request>) -> Response {
|
||||||
// TODO: create a fetch record and append it to request's client's fetch group list
|
// TODO: create a fetch record and append it to request's client's fetch group list
|
||||||
}
|
}
|
||||||
// Step 7
|
// Step 7
|
||||||
main_fetch(request, false, false)
|
main_fetch(request, cache, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
|
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
|
||||||
fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Response {
|
fn main_fetch(request: Rc<Request>, cache: &mut CORSCache, cors_flag: bool, recursive_flag: bool) -> Response {
|
||||||
// TODO: Implement main fetch spec
|
// TODO: Implement main fetch spec
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
|
@ -156,14 +160,14 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
|
||||||
current_url.scheme() == "about" ||
|
current_url.scheme() == "about" ||
|
||||||
request.mode == RequestMode::Navigate {
|
request.mode == RequestMode::Navigate {
|
||||||
|
|
||||||
basic_fetch(request.clone())
|
basic_fetch(request.clone(), cache)
|
||||||
|
|
||||||
} else if request.mode == RequestMode::SameOrigin {
|
} else if request.mode == RequestMode::SameOrigin {
|
||||||
Response::network_error()
|
Response::network_error()
|
||||||
|
|
||||||
} else if request.mode == RequestMode::NoCORS {
|
} else if request.mode == RequestMode::NoCORS {
|
||||||
request.response_tainting.set(ResponseTainting::Opaque);
|
request.response_tainting.set(ResponseTainting::Opaque);
|
||||||
basic_fetch(request.clone())
|
basic_fetch(request.clone(), cache)
|
||||||
|
|
||||||
} else if !matches!(current_url.scheme(), "http" | "https") {
|
} else if !matches!(current_url.scheme(), "http" | "https") {
|
||||||
Response::network_error()
|
Response::network_error()
|
||||||
|
@ -175,7 +179,7 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
|
||||||
|
|
||||||
request.response_tainting.set(ResponseTainting::CORSTainting);
|
request.response_tainting.set(ResponseTainting::CORSTainting);
|
||||||
request.redirect_mode.set(RedirectMode::Error);
|
request.redirect_mode.set(RedirectMode::Error);
|
||||||
let response = http_fetch(request.clone(), BasicCORSCache::new(), true, true, false);
|
let response = http_fetch(request.clone(), cache, true, true, false);
|
||||||
if response.is_network_error() {
|
if response.is_network_error() {
|
||||||
// TODO clear cache entries using request
|
// TODO clear cache entries using request
|
||||||
}
|
}
|
||||||
|
@ -183,7 +187,7 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
request.response_tainting.set(ResponseTainting::CORSTainting);
|
request.response_tainting.set(ResponseTainting::CORSTainting);
|
||||||
http_fetch(request.clone(), BasicCORSCache::new(), true, false, false)
|
http_fetch(request.clone(), cache, true, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -280,7 +284,7 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
|
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
|
||||||
fn basic_fetch(request: Rc<Request>) -> Response {
|
fn basic_fetch(request: Rc<Request>, cache: &mut CORSCache) -> Response {
|
||||||
|
|
||||||
let url = request.current_url();
|
let url = request.current_url();
|
||||||
|
|
||||||
|
@ -294,7 +298,7 @@ fn basic_fetch(request: Rc<Request>) -> Response {
|
||||||
},
|
},
|
||||||
|
|
||||||
"http" | "https" => {
|
"http" | "https" => {
|
||||||
http_fetch(request.clone(), BasicCORSCache::new(), false, false, false)
|
http_fetch(request.clone(), cache, false, false, false)
|
||||||
},
|
},
|
||||||
|
|
||||||
"data" => {
|
"data" => {
|
||||||
|
@ -324,7 +328,7 @@ fn basic_fetch(request: Rc<Request>) -> Response {
|
||||||
|
|
||||||
/// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
|
/// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
|
||||||
fn http_fetch(request: Rc<Request>,
|
fn http_fetch(request: Rc<Request>,
|
||||||
mut cache: BasicCORSCache,
|
cache: &mut CORSCache,
|
||||||
cors_flag: bool,
|
cors_flag: bool,
|
||||||
cors_preflight_flag: bool,
|
cors_preflight_flag: bool,
|
||||||
authentication_fetch_flag: bool) -> Response {
|
authentication_fetch_flag: bool) -> Response {
|
||||||
|
@ -394,7 +398,7 @@ fn http_fetch(request: Rc<Request>,
|
||||||
|
|
||||||
// Sub-substep 1
|
// Sub-substep 1
|
||||||
if method_mismatch || header_mismatch {
|
if method_mismatch || header_mismatch {
|
||||||
let preflight_result = cors_preflight_fetch(request.clone(), Some(cache));
|
let preflight_result = cors_preflight_fetch(request.clone(), cache);
|
||||||
// Sub-substep 2
|
// Sub-substep 2
|
||||||
if preflight_result.response_type == ResponseType::Error {
|
if preflight_result.response_type == ResponseType::Error {
|
||||||
return Response::network_error();
|
return Response::network_error();
|
||||||
|
@ -443,7 +447,7 @@ fn http_fetch(request: Rc<Request>,
|
||||||
RedirectMode::Follow => {
|
RedirectMode::Follow => {
|
||||||
// set back to default
|
// set back to default
|
||||||
response.return_internal.set(true);
|
response.return_internal.set(true);
|
||||||
http_redirect_fetch(request, Rc::new(response), cors_flag)
|
http_redirect_fetch(request, cache, Rc::new(response), cors_flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -466,7 +470,7 @@ fn http_fetch(request: Rc<Request>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4
|
// Step 4
|
||||||
return http_fetch(request, BasicCORSCache::new(), cors_flag, cors_preflight_flag, true);
|
return http_fetch(request, cache, cors_flag, cors_preflight_flag, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code 407
|
// Code 407
|
||||||
|
@ -482,7 +486,7 @@ fn http_fetch(request: Rc<Request>,
|
||||||
// TODO: Prompt the user for proxy authentication credentials
|
// TODO: Prompt the user for proxy authentication credentials
|
||||||
|
|
||||||
// Step 4
|
// Step 4
|
||||||
return http_fetch(request, BasicCORSCache::new(),
|
return http_fetch(request, cache,
|
||||||
cors_flag, cors_preflight_flag,
|
cors_flag, cors_preflight_flag,
|
||||||
authentication_fetch_flag);
|
authentication_fetch_flag);
|
||||||
}
|
}
|
||||||
|
@ -503,6 +507,7 @@ fn http_fetch(request: Rc<Request>,
|
||||||
|
|
||||||
/// [HTTP redirect fetch](https://fetch.spec.whatwg.org#http-redirect-fetch)
|
/// [HTTP redirect fetch](https://fetch.spec.whatwg.org#http-redirect-fetch)
|
||||||
fn http_redirect_fetch(request: Rc<Request>,
|
fn http_redirect_fetch(request: Rc<Request>,
|
||||||
|
cache: &mut CORSCache,
|
||||||
response: Rc<Response>,
|
response: Rc<Response>,
|
||||||
cors_flag: bool) -> Response {
|
cors_flag: bool) -> Response {
|
||||||
|
|
||||||
|
@ -580,7 +585,7 @@ fn http_redirect_fetch(request: Rc<Request>,
|
||||||
request.url_list.borrow_mut().push(location_url);
|
request.url_list.borrow_mut().push(location_url);
|
||||||
|
|
||||||
// Step 15
|
// Step 15
|
||||||
main_fetch(request, cors_flag, true)
|
main_fetch(request, cache, cors_flag, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch)
|
/// [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch)
|
||||||
|
@ -917,7 +922,7 @@ fn http_network_fetch(request: Rc<Request>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
|
/// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
|
||||||
fn cors_preflight_fetch(request: Rc<Request>, cache: Option<BasicCORSCache>) -> Response {
|
fn cors_preflight_fetch(request: Rc<Request>, cache: &mut CORSCache) -> Response {
|
||||||
// Step 1
|
// Step 1
|
||||||
let mut preflight = Request::new(request.current_url(), Some(request.origin.borrow().clone()), false);
|
let mut preflight = Request::new(request.current_url(), Some(request.origin.borrow().clone()), false);
|
||||||
*preflight.method.borrow_mut() = Method::Options;
|
*preflight.method.borrow_mut() = Method::Options;
|
||||||
|
@ -995,12 +1000,6 @@ fn cors_preflight_fetch(request: Rc<Request>, cache: Option<BasicCORSCache>) ->
|
||||||
|
|
||||||
// TODO: Substep 9 - Need to define what an imposed limit on max-age is
|
// TODO: Substep 9 - Need to define what an imposed limit on max-age is
|
||||||
|
|
||||||
// Substep 10
|
|
||||||
let mut cache = match cache {
|
|
||||||
Some(c) => c,
|
|
||||||
None => return response
|
|
||||||
};
|
|
||||||
|
|
||||||
// Substep 11, 12
|
// Substep 11, 12
|
||||||
for method in &methods {
|
for method in &methods {
|
||||||
cache.match_method_and_update(CacheRequestDetails {
|
cache.match_method_and_update(CacheRequestDetails {
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub enum Destination {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
|
/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum Origin {
|
pub enum Origin {
|
||||||
Client,
|
Client,
|
||||||
Origin(UrlOrigin)
|
Origin(UrlOrigin)
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use hyper::header::{AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowOrigin};
|
use hyper::header::{AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowOrigin};
|
||||||
use hyper::header::{AccessControlAllowMethods, AccessControlRequestHeaders, AccessControlRequestMethod};
|
use hyper::header::{AccessControlAllowMethods, AccessControlMaxAge};
|
||||||
|
use hyper::header::{AccessControlRequestHeaders, AccessControlRequestMethod};
|
||||||
use hyper::header::{CacheControl, ContentLanguage, ContentType, Expires, LastModified};
|
use hyper::header::{CacheControl, ContentLanguage, ContentType, Expires, LastModified};
|
||||||
use hyper::header::{Headers, HttpDate, Location, SetCookie, Pragma};
|
use hyper::header::{Headers, HttpDate, Location, SetCookie, Pragma};
|
||||||
use hyper::method::Method;
|
use hyper::method::Method;
|
||||||
|
@ -12,7 +13,8 @@ use hyper::server::{Handler, Listening, Server};
|
||||||
use hyper::server::{Request as HyperRequest, Response as HyperResponse};
|
use hyper::server::{Request as HyperRequest, Response as HyperResponse};
|
||||||
use hyper::status::StatusCode;
|
use hyper::status::StatusCode;
|
||||||
use hyper::uri::RequestUri;
|
use hyper::uri::RequestUri;
|
||||||
use net::fetch::methods::{fetch, fetch_async};
|
use net::fetch::cors_cache::{CacheRequestDetails, CORSCache};
|
||||||
|
use net::fetch::methods::{fetch, fetch_async, fetch_with_cors_cache};
|
||||||
use net_traits::AsyncFetchListener;
|
use net_traits::AsyncFetchListener;
|
||||||
use net_traits::request::{Origin, RedirectMode, Referer, Request, RequestMode};
|
use net_traits::request::{Origin, RedirectMode, Referer, Request, RequestMode};
|
||||||
use net_traits::response::{CacheState, Response, ResponseBody, ResponseType};
|
use net_traits::response::{CacheState, Response, ResponseBody, ResponseType};
|
||||||
|
@ -174,6 +176,61 @@ fn test_cors_preflight_fetch() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cors_preflight_cache_fetch() {
|
||||||
|
static ACK: &'static [u8] = b"ACK";
|
||||||
|
let state = Arc::new(AtomicUsize::new(0));
|
||||||
|
let counter = state.clone();
|
||||||
|
let mut cache = CORSCache::new();
|
||||||
|
let handler = move |request: HyperRequest, mut response: HyperResponse| {
|
||||||
|
if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
|
||||||
|
assert!(request.headers.has::<AccessControlRequestMethod>());
|
||||||
|
assert!(request.headers.has::<AccessControlRequestHeaders>());
|
||||||
|
response.headers_mut().set(AccessControlAllowOrigin::Any);
|
||||||
|
response.headers_mut().set(AccessControlAllowCredentials);
|
||||||
|
response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get]));
|
||||||
|
response.headers_mut().set(AccessControlMaxAge(6000));
|
||||||
|
} else {
|
||||||
|
response.headers_mut().set(AccessControlAllowOrigin::Any);
|
||||||
|
response.send(ACK).unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (mut server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let origin = Origin::Origin(UrlOrigin::new_opaque());
|
||||||
|
let mut request = Request::new(url.clone(), Some(origin.clone()), false);
|
||||||
|
request.referer = Referer::NoReferer;
|
||||||
|
request.use_cors_preflight = true;
|
||||||
|
request.mode = RequestMode::CORSMode;
|
||||||
|
let wrapped_request0 = Rc::new(request.clone());
|
||||||
|
let wrapped_request1 = Rc::new(request);
|
||||||
|
|
||||||
|
let fetch_response0 = fetch_with_cors_cache(wrapped_request0, &mut cache);
|
||||||
|
let fetch_response1 = fetch_with_cors_cache(wrapped_request1, &mut cache);
|
||||||
|
let _ = server.close();
|
||||||
|
|
||||||
|
assert!(!fetch_response0.is_network_error() && !fetch_response1.is_network_error());
|
||||||
|
|
||||||
|
// The response from the CORS-preflight cache was used
|
||||||
|
assert_eq!(1, counter.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
// The entry exists in the CORS-preflight cache
|
||||||
|
assert_eq!(true, cache.match_method(CacheRequestDetails {
|
||||||
|
origin: origin,
|
||||||
|
destination: url,
|
||||||
|
credentials: false
|
||||||
|
}, Method::Get));
|
||||||
|
|
||||||
|
match *fetch_response0.body.lock().unwrap() {
|
||||||
|
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
||||||
|
_ => panic!()
|
||||||
|
};
|
||||||
|
match *fetch_response1.body.lock().unwrap() {
|
||||||
|
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
||||||
|
_ => panic!()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cors_preflight_fetch_network_error() {
|
fn test_cors_preflight_fetch_network_error() {
|
||||||
static ACK: &'static [u8] = b"ACK";
|
static ACK: &'static [u8] = b"ACK";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue