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