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