1use 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#[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,
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#[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 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 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 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 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 pub fn match_method(&mut self, request: &Request, method: Method) -> bool {
156 self.find_entry_by_method(request, method).is_some()
157 }
158
159 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 pub fn insert(&mut self, entry: CorsCacheEntry) {
189 self.cleanup();
190 self.0.push(entry);
191 }
192}