net/fetch/
cors_cache.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! An implementation of the [CORS preflight cache](https://fetch.spec.whatwg.org/#cors-preflight-cache)
6//! For now this library is XHR-specific.
7//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what
8//! the request mode should be and compare with the fetch spec
9//! This library will eventually become the core of the Fetch crate
10//! with CORSRequest being expanded into FetchRequest (etc)
11
12use std::time::{Duration, Instant};
13
14use http::Method;
15use http::header::HeaderName;
16use net_traits::request::{CredentialsMode, Origin, Request};
17use servo_url::ServoUrl;
18
19/// Union type for CORS cache entries
20///
21/// Each entry might pertain to a header or method
22#[derive(Clone, Debug)]
23pub enum HeaderOrMethod {
24    HeaderData(HeaderName),
25    MethodData(Method),
26}
27
28impl HeaderOrMethod {
29    fn match_header(&self, header_name: &HeaderName) -> bool {
30        match *self {
31            HeaderOrMethod::HeaderData(ref n) => n == header_name,
32            _ => false,
33        }
34    }
35
36    fn match_method(&self, method: &Method) -> bool {
37        match *self {
38            HeaderOrMethod::MethodData(ref m) => m == method,
39            _ => false,
40        }
41    }
42}
43
44/// An entry in the CORS cache
45#[derive(Clone, Debug)]
46pub struct CorsCacheEntry {
47    pub origin: Origin,
48    pub url: ServoUrl,
49    pub max_age: Duration,
50    pub credentials: bool,
51    pub header_or_method: HeaderOrMethod,
52    created: Instant,
53}
54
55impl CorsCacheEntry {
56    fn new(
57        origin: Origin,
58        url: ServoUrl,
59        max_age: Duration,
60        credentials: bool,
61        header_or_method: HeaderOrMethod,
62    ) -> CorsCacheEntry {
63        CorsCacheEntry {
64            origin,
65            url,
66            max_age,
67            credentials,
68            header_or_method,
69            created: Instant::now(),
70        }
71    }
72}
73
74fn match_headers(cors_cache: &CorsCacheEntry, cors_req: &Request) -> bool {
75    cors_cache.origin == cors_req.origin &&
76        cors_cache.url == cors_req.current_url() &&
77        (cors_cache.credentials || cors_req.credentials_mode != CredentialsMode::Include)
78}
79
80/// A simple, vector-based CORS Cache
81#[derive(Clone, Default)]
82pub struct CorsCache(Vec<CorsCacheEntry>);
83
84impl CorsCache {
85    fn find_entry_by_header<'a>(
86        &'a mut self,
87        request: &Request,
88        header_name: &HeaderName,
89    ) -> Option<&'a mut CorsCacheEntry> {
90        self.cleanup();
91        self.0
92            .iter_mut()
93            .find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name))
94    }
95
96    fn find_entry_by_method<'a>(
97        &'a mut self,
98        request: &Request,
99        method: Method,
100    ) -> Option<&'a mut CorsCacheEntry> {
101        // we can take the method from CorSRequest itself
102        self.cleanup();
103        self.0
104            .iter_mut()
105            .find(|e| match_headers(e, request) && e.header_or_method.match_method(&method))
106    }
107
108    /// Remove old entries
109    pub fn cleanup(&mut self) {
110        let CorsCache(buf) = self.clone();
111        let now = Instant::now();
112        let new_buf: Vec<CorsCacheEntry> = buf
113            .into_iter()
114            .filter(|e| now < e.created + e.max_age)
115            .collect();
116        *self = CorsCache(new_buf);
117    }
118
119    /// Returns true if an entry with a
120    /// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found
121    pub fn match_header(&mut self, request: &Request, header_name: &HeaderName) -> bool {
122        self.find_entry_by_header(request, header_name).is_some()
123    }
124
125    /// Updates max age if an entry for a
126    /// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
127    ///
128    /// If not, it will insert an equivalent entry
129    pub fn match_header_and_update(
130        &mut self,
131        request: &Request,
132        header_name: &HeaderName,
133        new_max_age: Duration,
134    ) -> bool {
135        match self
136            .find_entry_by_header(request, header_name)
137            .map(|e| e.max_age = new_max_age)
138        {
139            Some(_) => true,
140            None => {
141                self.insert(CorsCacheEntry::new(
142                    request.origin.clone(),
143                    request.current_url(),
144                    new_max_age,
145                    request.credentials_mode == CredentialsMode::Include,
146                    HeaderOrMethod::HeaderData(header_name.clone()),
147                ));
148                false
149            },
150        }
151    }
152
153    /// Returns true if an entry with a
154    /// [matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found
155    pub fn match_method(&mut self, request: &Request, method: Method) -> bool {
156        self.find_entry_by_method(request, method).is_some()
157    }
158
159    /// Updates max age if an entry for
160    /// [a matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
161    ///
162    /// If not, it will insert an equivalent entry
163    pub fn match_method_and_update(
164        &mut self,
165        request: &Request,
166        method: Method,
167        new_max_age: Duration,
168    ) -> bool {
169        match self
170            .find_entry_by_method(request, method.clone())
171            .map(|e| e.max_age = new_max_age)
172        {
173            Some(_) => true,
174            None => {
175                self.insert(CorsCacheEntry::new(
176                    request.origin.clone(),
177                    request.current_url(),
178                    new_max_age,
179                    request.credentials_mode == CredentialsMode::Include,
180                    HeaderOrMethod::MethodData(method),
181                ));
182                false
183            },
184        }
185    }
186
187    /// Insert an entry
188    pub fn insert(&mut self, entry: CorsCacheEntry) {
189        self.cleanup();
190        self.0.push(entry);
191    }
192}