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}