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