1use std::time::{Duration, Instant};
13
14use http::Method;
15use http::header::HeaderName;
16use net_traits::request::{CredentialsMode, NetworkPartitionKey, Origin, Request};
17use servo_url::ServoUrl;
18
19#[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#[derive(Clone, Debug)]
46pub struct CorsCacheEntry {
47 pub origin: Origin,
49 pub url: ServoUrl,
51 pub max_age: Duration,
53 pub credentials: bool,
55 pub header_or_method: HeaderOrMethod,
58 pub network_partition_key: NetworkPartitionKey,
61 created: Instant,
62}
63
64impl CorsCacheEntry {
65 fn new(
66 origin: Origin,
67 url: ServoUrl,
68 max_age: Duration,
69 credentials: bool,
70 header_or_method: HeaderOrMethod,
71 network_partition_key: NetworkPartitionKey,
72 ) -> CorsCacheEntry {
73 CorsCacheEntry {
74 origin,
75 url,
76 max_age,
77 credentials,
78 header_or_method,
79 network_partition_key,
80 created: Instant::now(),
81 }
82 }
83}
84
85fn network_partition_key_for_request(request: &Request) -> NetworkPartitionKey {
87 request
97 .client
98 .as_ref()
99 .and_then(|c| c.network_partition_key.clone())
100}
101
102fn match_headers(cors_cache: &CorsCacheEntry, cors_req: &Request) -> bool {
103 cors_cache.origin == cors_req.origin &&
104 cors_cache.url == cors_req.current_url() &&
105 (cors_cache.credentials || cors_req.credentials_mode != CredentialsMode::Include) &&
106 cors_cache.network_partition_key == network_partition_key_for_request(cors_req)
107}
108
109#[derive(Clone, Default)]
111pub struct CorsCache(Vec<CorsCacheEntry>);
112
113impl CorsCache {
114 fn find_entry_by_header<'a>(
115 &'a mut self,
116 request: &Request,
117 header_name: &HeaderName,
118 ) -> Option<&'a mut CorsCacheEntry> {
119 self.cleanup();
120 self.0
121 .iter_mut()
122 .find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name))
123 }
124
125 fn find_entry_by_method<'a>(
126 &'a mut self,
127 request: &Request,
128 method: &Method,
129 ) -> Option<&'a mut CorsCacheEntry> {
130 self.cleanup();
132 self.0
133 .iter_mut()
134 .find(|e| match_headers(e, request) && e.header_or_method.match_method(method))
135 }
136
137 pub fn cleanup(&mut self) {
139 let now = Instant::now();
140 self.0.retain(|entry| now < entry.created + entry.max_age);
141 }
142
143 pub fn match_header(&mut self, request: &Request, header_name: &HeaderName) -> bool {
146 self.find_entry_by_header(request, header_name).is_some()
147 }
148
149 pub fn match_header_and_update(
154 &mut self,
155 request: &Request,
156 header_name: &HeaderName,
157 new_max_age: Duration,
158 ) -> bool {
159 match self
160 .find_entry_by_header(request, header_name)
161 .map(|e| e.max_age = new_max_age)
162 {
163 Some(_) => true,
164 None => {
165 self.insert(CorsCacheEntry::new(
166 request.origin.clone(),
167 request.current_url(),
168 new_max_age,
169 request.credentials_mode == CredentialsMode::Include,
170 HeaderOrMethod::HeaderData(header_name.clone()),
171 network_partition_key_for_request(request),
172 ));
173 false
174 },
175 }
176 }
177
178 pub fn match_method(&mut self, request: &Request, method: &Method) -> bool {
181 self.find_entry_by_method(request, method).is_some()
182 }
183
184 pub fn match_method_and_update(
189 &mut self,
190 request: &Request,
191 method: &Method,
192 new_max_age: Duration,
193 ) -> bool {
194 match self
195 .find_entry_by_method(request, method)
196 .map(|e| e.max_age = new_max_age)
197 {
198 Some(_) => true,
199 None => {
200 self.insert(CorsCacheEntry::new(
201 request.origin.clone(),
202 request.current_url(),
203 new_max_age,
204 request.credentials_mode == CredentialsMode::Include,
205 HeaderOrMethod::MethodData(method.clone()),
206 network_partition_key_for_request(request),
207 ));
208 false
209 },
210 }
211 }
212
213 pub fn insert(&mut self, entry: CorsCacheEntry) {
215 self.cleanup();
216 self.0.push(entry);
217 }
218
219 pub(crate) fn clear_entries_for_origin_and_url(&mut self, origin: &Origin, url: &ServoUrl) {
221 self.cleanup();
227 self.0
228 .retain(|cache_entry| !(cache_entry.origin == *origin && cache_entry.url == *url));
229 }
230}