1use std::borrow::Cow;
2use std::fmt;
3use std::sync::{Mutex, MutexGuard};
4
5use cookie_store::CookieStore;
6use http::Uri;
7
8use crate::http;
9use crate::util::UriExt;
10use crate::Error;
11
12#[cfg(feature = "json")]
13use std::io;
14
15#[derive(Debug)]
16pub(crate) struct SharedCookieJar {
17 inner: Mutex<CookieStore>,
18}
19
20pub struct CookieJar<'a>(MutexGuard<'a, CookieStore>);
25
26pub struct Cookie<'a>(CookieInner<'a>);
44
45#[allow(clippy::large_enum_variant)]
46enum CookieInner<'a> {
47 Borrowed(&'a cookie_store::Cookie<'a>),
48 Owned(cookie_store::Cookie<'a>),
49}
50
51impl<'a> CookieInner<'a> {
52 fn into_static(self) -> cookie_store::Cookie<'static> {
53 match self {
54 CookieInner::Borrowed(v) => v.clone().into_owned(),
55 CookieInner::Owned(v) => v.into_owned(),
56 }
57 }
58}
59
60impl<'a> Cookie<'a> {
61 pub fn parse<S>(cookie_str: S, uri: &Uri) -> Result<Cookie<'a>, Error>
63 where
64 S: Into<Cow<'a, str>>,
65 {
66 let cookie = cookie_store::Cookie::parse(cookie_str, &uri.try_into_url()?)?;
67 Ok(Cookie(CookieInner::Owned(cookie)))
68 }
69
70 pub fn name(&self) -> &str {
72 match &self.0 {
73 CookieInner::Borrowed(v) => v.name(),
74 CookieInner::Owned(v) => v.name(),
75 }
76 }
77
78 pub fn value(&self) -> &str {
80 match &self.0 {
81 CookieInner::Borrowed(v) => v.value(),
82 CookieInner::Owned(v) => v.value(),
83 }
84 }
85
86 #[cfg(test)]
87 fn as_cookie_store(&self) -> &cookie_store::Cookie<'a> {
88 match &self.0 {
89 CookieInner::Borrowed(v) => v,
90 CookieInner::Owned(v) => v,
91 }
92 }
93}
94
95impl Cookie<'static> {
96 fn into_owned(self) -> cookie_store::Cookie<'static> {
97 match self.0 {
98 CookieInner::Owned(v) => v,
99 _ => unreachable!(),
100 }
101 }
102}
103
104impl<'a> CookieJar<'a> {
105 pub fn get(&self, domain: &str, path: &str, name: &str) -> Option<Cookie<'_>> {
108 self.0
109 .get(domain, path, name)
110 .map(|c| Cookie(CookieInner::Borrowed(c)))
111 }
112
113 pub fn remove(&mut self, domain: &str, path: &str, name: &str) -> Option<Cookie<'static>> {
115 self.0
116 .remove(domain, path, name)
117 .map(|c| Cookie(CookieInner::Owned(c)))
118 }
119
120 pub fn insert(&mut self, cookie: Cookie<'static>, uri: &Uri) -> Result<(), Error> {
123 let url = uri.try_into_url()?;
124 self.0.insert(cookie.into_owned(), &url)?;
125 Ok(())
126 }
127
128 pub fn clear(&mut self) {
130 self.0.clear()
131 }
132
133 pub fn iter(&self) -> impl Iterator<Item = Cookie<'_>> {
135 self.0
136 .iter_unexpired()
137 .map(|c| Cookie(CookieInner::Borrowed(c)))
138 }
139
140 #[cfg(feature = "json")]
143 pub fn save_json<W: io::Write>(&self, writer: &mut W) -> Result<(), Error> {
144 Ok(cookie_store::serde::json::save(&self.0, writer)?)
145 }
146
147 #[cfg(feature = "json")]
151 pub fn load_json<R: io::BufRead>(&mut self, reader: R) -> Result<(), Error> {
152 let store = cookie_store::serde::json::load(reader)?;
153 *self.0 = store;
154 Ok(())
155 }
156
157 pub(crate) fn store_response_cookies<'b>(
158 &mut self,
159 iter: impl Iterator<Item = Cookie<'b>>,
160 uri: &Uri,
161 ) {
162 let url = uri.try_into_url().expect("uri to be a url");
163 let raw_cookies = iter.map(|c| c.0.into_static().into());
164 self.0.store_response_cookies(raw_cookies, &url);
165 }
166
167 pub fn release(self) {}
169}
170
171impl SharedCookieJar {
172 pub(crate) fn new() -> Self {
173 SharedCookieJar {
174 inner: Mutex::new(CookieStore::new()),
175 }
176 }
177
178 pub(crate) fn lock(&self) -> CookieJar<'_> {
179 let lock = self.inner.lock().unwrap();
180 CookieJar(lock)
181 }
182
183 pub(crate) fn get_request_cookies(&self, uri: &Uri) -> String {
184 let mut cookies = String::new();
185
186 let url = match uri.try_into_url() {
187 Ok(v) => v,
188 Err(e) => {
189 debug!("Bad url for cookie: {:?}", e);
190 return cookies;
191 }
192 };
193
194 let store = self.inner.lock().unwrap();
195
196 for c in store.matches(&url) {
197 if !is_cookie_rfc_compliant(c) {
198 debug!("Do not send non compliant cookie: {:?}", c.name());
199 continue;
200 }
201
202 if !cookies.is_empty() {
203 cookies.push(';');
204 }
205
206 cookies.push_str(&c.to_string());
207 }
208
209 cookies
210 }
211}
212
213fn is_cookie_rfc_compliant(cookie: &cookie_store::Cookie) -> bool {
214 fn is_valid_name(b: &u8) -> bool {
239 is_tchar(b)
240 }
241
242 fn is_valid_value(b: &u8) -> bool {
243 b.is_ascii()
244 && !b.is_ascii_control()
245 && !b.is_ascii_whitespace()
246 && *b != b'"'
247 && *b != b','
248 && *b != b';'
249 && *b != b'\\'
250 }
251
252 let name = cookie.name().as_bytes();
253
254 let valid_name = name.iter().all(is_valid_name);
255
256 if !valid_name {
257 log::trace!("cookie name is not valid: {:?}", cookie.name());
258 return false;
259 }
260
261 let value = cookie.value().as_bytes();
262
263 let valid_value = value.iter().all(is_valid_value);
264
265 if !valid_value {
266 log::trace!("cookie value is not valid: {:?}", cookie.name());
268 return false;
269 }
270
271 true
272}
273
274#[inline]
275pub(crate) fn is_tchar(b: &u8) -> bool {
276 match b {
277 b'!' | b'#' | b'$' | b'%' | b'&' => true,
278 b'\'' | b'*' | b'+' | b'-' | b'.' => true,
279 b'^' | b'_' | b'`' | b'|' | b'~' => true,
280 b if b.is_ascii_alphanumeric() => true,
281 _ => false,
282 }
283}
284
285impl fmt::Display for Cookie<'_> {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 match &self.0 {
288 CookieInner::Borrowed(v) => v.fmt(f),
289 CookieInner::Owned(v) => v.fmt(f),
290 }
291 }
292}
293
294#[cfg(test)]
295mod test {
296
297 use std::convert::TryFrom;
298
299 use super::*;
300
301 fn uri() -> Uri {
302 Uri::try_from("https://example.test").unwrap()
303 }
304
305 #[test]
306 fn illegal_cookie_name() {
307 let cookie = Cookie::parse("borked/=value", &uri()).unwrap();
308 assert!(!is_cookie_rfc_compliant(cookie.as_cookie_store()));
309 }
310
311 #[test]
312 fn illegal_cookie_value() {
313 let cookie = Cookie::parse("name=borked,", &uri()).unwrap();
314 assert!(!is_cookie_rfc_compliant(cookie.as_cookie_store()));
315 }
316
317 #[test]
318 fn legal_cookie_name_value() {
319 let cookie = Cookie::parse("name=value", &uri()).unwrap();
320 assert!(is_cookie_rfc_compliant(cookie.as_cookie_store()));
321 }
322}