/* 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 https://mozilla.org/MPL/2.0/. */ //! An implementation of the [CORS preflight cache](https://fetch.spec.whatwg.org/#cors-preflight-cache) //! 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 http::header::HeaderName; use hyper::Method; use net_traits::request::{CredentialsMode, Origin, Request}; use servo_url::ServoUrl; use time::{self, Timespec}; /// Union type for CORS cache entries /// /// Each entry might pertain to a header or method #[derive(Clone, Debug)] pub enum HeaderOrMethod { HeaderData(HeaderName), MethodData(Method), } impl HeaderOrMethod { fn match_header(&self, header_name: &HeaderName) -> bool { match *self { HeaderOrMethod::HeaderData(ref n) => n == 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, Debug)] pub struct CorsCacheEntry { pub origin: Origin, pub url: ServoUrl, pub max_age: u32, pub credentials: bool, pub header_or_method: HeaderOrMethod, created: Timespec, } impl CorsCacheEntry { fn new( origin: Origin, url: ServoUrl, 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(), } } } fn match_headers(cors_cache: &CorsCacheEntry, cors_req: &Request) -> bool { cors_cache.origin == cors_req.origin && cors_cache.url == cors_req.current_url() && (cors_cache.credentials || cors_req.credentials_mode != CredentialsMode::Include) } /// A simple, vector-based CORS Cache #[derive(Clone)] pub struct CorsCache(Vec<CorsCacheEntry>); impl CorsCache { pub fn new() -> CorsCache { CorsCache(vec![]) } fn find_entry_by_header<'a>( &'a mut self, request: &Request, header_name: &HeaderName, ) -> Option<&'a mut CorsCacheEntry> { self.cleanup(); self.0 .iter_mut() .find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name)) } fn find_entry_by_method<'a>( &'a mut self, request: &Request, method: Method, ) -> Option<&'a mut CorsCacheEntry> { // we can take the method from CorSRequest itself self.cleanup(); self.0 .iter_mut() .find(|e| match_headers(e, request) && e.header_or_method.match_method(&method)) } /// [Clear the cache](https://fetch.spec.whatwg.org/#concept-cache-clear) pub fn clear(&mut self, request: &Request) { let CorsCache(buf) = self.clone(); let new_buf: Vec<CorsCacheEntry> = buf .into_iter() .filter(|e| e.origin == request.origin && request.current_url() == e.url) .collect(); *self = CorsCache(new_buf); } /// Remove old entries pub 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); } /// 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: &Request, header_name: &HeaderName) -> bool { self.find_entry_by_header(&request, header_name).is_some() } /// 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: &Request, header_name: &HeaderName, new_max_age: u32, ) -> bool { match self .find_entry_by_header(&request, header_name) .map(|e| e.max_age = new_max_age) { Some(_) => true, None => { self.insert(CorsCacheEntry::new( request.origin.clone(), request.current_url(), new_max_age, request.credentials_mode == CredentialsMode::Include, HeaderOrMethod::HeaderData(header_name.clone()), )); false }, } } /// 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: &Request, method: Method) -> bool { self.find_entry_by_method(&request, method).is_some() } /// 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: &Request, method: Method, new_max_age: u32, ) -> bool { match self .find_entry_by_method(&request, method.clone()) .map(|e| e.max_age = new_max_age) { Some(_) => true, None => { self.insert(CorsCacheEntry::new( request.origin.clone(), request.current_url(), new_max_age, request.credentials_mode == CredentialsMode::Include, HeaderOrMethod::MethodData(method), )); false }, } } /// Insert an entry pub fn insert(&mut self, entry: CorsCacheEntry) { self.cleanup(); self.0.push(entry); } }