1use std::cmp::Ordering;
9use std::collections::HashMap;
10use std::collections::hash_map::Entry;
11use std::time::SystemTime;
12
13use cookie::Cookie;
14use itertools::Itertools;
15use log::info;
16use net_traits::pub_domains::reg_suffix;
17use net_traits::{CookieSource, SiteDescriptor};
18use serde::{Deserialize, Serialize};
19use servo_url::ServoUrl;
20
21use crate::cookie::ServoCookie;
22
23#[derive(Clone, Debug, Deserialize, Serialize)]
24pub struct CookieStorage {
25 version: u32,
26 cookies_map: HashMap<String, Vec<ServoCookie>>,
27 max_per_host: usize,
28}
29
30#[derive(Debug)]
31pub enum RemoveCookieError {
32 Overlapping,
33 NonHTTP,
34}
35
36impl CookieStorage {
37 pub fn new(max_cookies: usize) -> CookieStorage {
38 CookieStorage {
39 version: 1,
40 cookies_map: HashMap::new(),
41 max_per_host: max_cookies,
42 }
43 }
44
45 pub fn remove(
47 &mut self,
48 cookie: &ServoCookie,
49 url: &ServoUrl,
50 source: CookieSource,
51 ) -> Result<Option<ServoCookie>, RemoveCookieError> {
52 let domain = reg_host(cookie.cookie.domain().as_ref().unwrap_or(&""));
53 let cookies = self.cookies_map.entry(domain).or_default();
54
55 if !cookie.cookie.secure().unwrap_or(false) && !url.is_secure_scheme() {
57 let new_domain = cookie.cookie.domain().as_ref().unwrap().to_owned();
58 let new_path = cookie.cookie.path().as_ref().unwrap().to_owned();
59
60 let any_overlapping = cookies.iter().any(|c| {
61 let existing_domain = c.cookie.domain().as_ref().unwrap().to_owned();
62 let existing_path = c.cookie.path().as_ref().unwrap().to_owned();
63
64 c.cookie.name() == cookie.cookie.name() &&
65 c.cookie.secure().unwrap_or(false) &&
66 (ServoCookie::domain_match(new_domain, existing_domain) ||
67 ServoCookie::domain_match(existing_domain, new_domain)) &&
68 ServoCookie::path_match(new_path, existing_path)
69 });
70
71 if any_overlapping {
72 return Err(RemoveCookieError::Overlapping);
73 }
74 }
75
76 let position = cookies.iter().position(|c| {
78 c.cookie.domain() == cookie.cookie.domain() &&
79 c.cookie.path() == cookie.cookie.path() &&
80 c.cookie.name() == cookie.cookie.name()
81 });
82
83 if let Some(ind) = position {
84 let c = cookies.remove(ind);
86
87 if c.cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP {
89 cookies.push(c);
91 Err(RemoveCookieError::NonHTTP)
92 } else {
93 Ok(Some(c))
94 }
95 } else {
96 Ok(None)
97 }
98 }
99
100 pub fn delete_cookies_for_sites(&mut self, sites: &Vec<String>) {
101 for site in sites {
106 if let Some(cookies) = self.cookies_map.get_mut(site) {
112 for cookie in cookies.iter_mut() {
113 cookie.set_expiry_time_in_past();
114 }
115 }
116 }
117 }
118
119 pub fn clear_storage(&mut self, url: Option<&ServoUrl>) {
120 if let Some(url) = url {
121 let domain = reg_host(url.host_str().unwrap_or(""));
122 let cookies = self.cookies_map.entry(domain).or_default();
125 for cookie in cookies.iter_mut() {
126 cookie.set_expiry_time_in_past();
127 }
128 } else {
129 self.cookies_map.clear();
130 }
131 }
132
133 pub fn delete_cookie_with_name(&mut self, url: &ServoUrl, name: String) {
134 let domain = reg_host(url.host_str().unwrap_or(""));
135 let cookies = self.cookies_map.entry(domain).or_default();
138 for cookie in cookies.iter_mut() {
139 if cookie.cookie.name() == name {
140 cookie.set_expiry_time_in_past();
141 }
142 }
143 }
144
145 pub fn push(&mut self, mut cookie: ServoCookie, url: &ServoUrl, source: CookieSource) {
147 if cookie.cookie.secure().unwrap_or(false) && !url.is_secure_scheme() {
149 return;
150 }
151
152 let old_cookie = self.remove(&cookie, url, source);
153 if old_cookie.is_err() {
154 return;
156 }
157
158 if let Some(old_cookie) = old_cookie.unwrap() {
160 cookie.creation_time = old_cookie.creation_time;
162 }
163
164 let domain = reg_host(cookie.cookie.domain().as_ref().unwrap_or(&""));
166 let cookies = self.cookies_map.entry(domain).or_default();
167
168 if cookies.len() == self.max_per_host {
169 let old_len = cookies.len();
170 cookies.retain(|c| !is_cookie_expired(c));
171 let new_len = cookies.len();
172
173 if new_len == old_len &&
175 !evict_one_cookie(cookie.cookie.secure().unwrap_or(false), cookies)
176 {
177 return;
178 }
179 }
180 cookies.push(cookie);
181 }
182
183 pub fn cookie_comparator(a: &ServoCookie, b: &ServoCookie) -> Ordering {
184 let a_path_len = a.cookie.path().as_ref().map_or(0, |p| p.len());
185 let b_path_len = b.cookie.path().as_ref().map_or(0, |p| p.len());
186 match a_path_len.cmp(&b_path_len) {
187 Ordering::Equal => a.creation_time.cmp(&b.creation_time),
188 Ordering::Greater => Ordering::Less,
190 Ordering::Less => Ordering::Greater,
191 }
192 }
193
194 pub fn remove_expired_cookies_for_url(&mut self, url: &ServoUrl) {
195 let domain = reg_host(url.host_str().unwrap_or(""));
196 if let Entry::Occupied(mut entry) = self.cookies_map.entry(domain) {
197 let cookies = entry.get_mut();
198 cookies.retain(|c| !is_cookie_expired(c));
199 if cookies.is_empty() {
200 entry.remove_entry();
201 }
202 }
203 }
204
205 pub fn remove_all_expired_cookies(&mut self) {
206 self.cookies_map.retain(|_, cookies| {
207 cookies.retain(|c| !is_cookie_expired(c));
208 !cookies.is_empty()
209 });
210 }
211
212 pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> {
214 let cookie_list = self.cookies_data_for_url(url, source);
216
217 let reducer = |acc: String, cookie: Cookie<'static>| -> String {
218 (match acc.len() {
225 0 => acc,
226 _ => acc + "; ",
227 }) + cookie.name() +
228 "=" +
229 cookie.value()
230 };
231
232 let result = cookie_list.fold("".to_owned(), reducer);
234
235 info!(" === COOKIES SENT: {}", result);
236 match result.len() {
237 0 => None,
238 _ => Some(result),
239 }
240 }
241
242 pub fn query_cookies(&mut self, url: &ServoUrl, name: Option<String>) -> Vec<Cookie<'static>> {
244 let cookie_list = self.cookies_data_for_url(url, CookieSource::NonHTTP);
246
247 if let Some(name) = name {
250 cookie_list.filter(|cookie| cookie.name() == name).collect()
253 } else {
254 cookie_list.collect()
255 }
256
257 }
262
263 pub fn cookies_data_for_url<'a>(
264 &'a mut self,
265 url: &'a ServoUrl,
266 source: CookieSource,
267 ) -> impl Iterator<Item = cookie::Cookie<'static>> + 'a {
268 let domain = reg_host(url.host_str().unwrap_or(""));
269 let cookies = self.cookies_map.entry(domain).or_default();
270
271 cookies
272 .iter_mut()
273 .filter(move |c| c.appropriate_for_url(url, source))
274 .sorted_by(|a: &&mut ServoCookie, b: &&mut ServoCookie| {
275 CookieStorage::cookie_comparator(a, b)
277 })
278 .map(|c| {
279 c.touch();
281 c.cookie.clone()
282 })
283 }
284
285 pub fn cookie_site_descriptors(&self) -> Vec<SiteDescriptor> {
286 self.cookies_map
287 .keys()
288 .cloned()
289 .map(SiteDescriptor::new)
290 .collect()
291 }
292}
293
294fn reg_host(url: &str) -> String {
295 reg_suffix(url).to_lowercase()
296}
297
298fn is_cookie_expired(cookie: &ServoCookie) -> bool {
299 matches!(cookie.expiry_time, Some(date_time) if date_time <= SystemTime::now())
300}
301
302fn evict_one_cookie(is_secure_cookie: bool, cookies: &mut Vec<ServoCookie>) -> bool {
303 let oldest_accessed = get_oldest_accessed(false, cookies);
305
306 if let Some((index, _)) = oldest_accessed {
307 cookies.remove(index);
308 } else {
309 if !is_secure_cookie {
311 return false;
312 }
313 let oldest_accessed = get_oldest_accessed(true, cookies);
314 if let Some((index, _)) = oldest_accessed {
315 cookies.remove(index);
316 }
317 }
318 true
319}
320
321fn get_oldest_accessed(
322 is_secure_cookie: bool,
323 cookies: &mut [ServoCookie],
324) -> Option<(usize, SystemTime)> {
325 let mut oldest_accessed = None;
326 for (i, c) in cookies.iter().enumerate() {
327 if (c.cookie.secure().unwrap_or(false) == is_secure_cookie) &&
328 oldest_accessed
329 .as_ref()
330 .is_none_or(|(_, current_oldest_time)| c.last_access < *current_oldest_time)
331 {
332 oldest_accessed = Some((i, c.last_access));
333 }
334 }
335 oldest_accessed
336}