1use crate::header::{HeaderValue, SET_COOKIE};
4use bytes::Bytes;
5use std::convert::TryInto;
6use std::fmt;
7use std::sync::RwLock;
8use std::time::SystemTime;
9
10pub trait CookieStore: Send + Sync {
12 fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url);
14 fn cookies(&self, url: &url::Url) -> Option<HeaderValue>;
16}
17
18pub struct Cookie<'a>(cookie_crate::Cookie<'a>);
20
21#[derive(Debug, Default)]
31pub struct Jar(RwLock<cookie_store::CookieStore>);
32
33impl<'a> Cookie<'a> {
36 fn parse(value: &'a HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
37 std::str::from_utf8(value.as_bytes())
38 .map_err(cookie_crate::ParseError::from)
39 .and_then(cookie_crate::Cookie::parse)
40 .map_err(CookieParseError)
41 .map(Cookie)
42 }
43
44 pub fn name(&self) -> &str {
46 self.0.name()
47 }
48
49 pub fn value(&self) -> &str {
51 self.0.value()
52 }
53
54 pub fn http_only(&self) -> bool {
56 self.0.http_only().unwrap_or(false)
57 }
58
59 pub fn secure(&self) -> bool {
61 self.0.secure().unwrap_or(false)
62 }
63
64 pub fn same_site_lax(&self) -> bool {
66 self.0.same_site() == Some(cookie_crate::SameSite::Lax)
67 }
68
69 pub fn same_site_strict(&self) -> bool {
71 self.0.same_site() == Some(cookie_crate::SameSite::Strict)
72 }
73
74 pub fn path(&self) -> Option<&str> {
76 self.0.path()
77 }
78
79 pub fn domain(&self) -> Option<&str> {
81 self.0.domain()
82 }
83
84 pub fn max_age(&self) -> Option<std::time::Duration> {
86 self.0.max_age().map(|d| {
87 d.try_into()
88 .expect("time::Duration into std::time::Duration")
89 })
90 }
91
92 pub fn expires(&self) -> Option<SystemTime> {
94 match self.0.expires() {
95 Some(cookie_crate::Expiration::DateTime(offset)) => Some(SystemTime::from(offset)),
96 None | Some(cookie_crate::Expiration::Session) => None,
97 }
98 }
99}
100
101impl<'a> fmt::Debug for Cookie<'a> {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 self.0.fmt(f)
104 }
105}
106
107pub(crate) fn extract_response_cookie_headers<'a>(
108 headers: &'a hyper::HeaderMap,
109) -> impl Iterator<Item = &'a HeaderValue> + 'a {
110 headers.get_all(SET_COOKIE).iter()
111}
112
113pub(crate) fn extract_response_cookies<'a>(
114 headers: &'a hyper::HeaderMap,
115) -> impl Iterator<Item = Result<Cookie<'a>, CookieParseError>> + 'a {
116 headers
117 .get_all(SET_COOKIE)
118 .iter()
119 .map(|value| Cookie::parse(value))
120}
121
122pub(crate) struct CookieParseError(cookie_crate::ParseError);
124
125impl<'a> fmt::Debug for CookieParseError {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 self.0.fmt(f)
128 }
129}
130
131impl<'a> fmt::Display for CookieParseError {
132 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133 self.0.fmt(f)
134 }
135}
136
137impl std::error::Error for CookieParseError {}
138
139impl Jar {
142 pub fn add_cookie_str(&self, cookie: &str, url: &url::Url) {
158 let cookies = cookie_crate::Cookie::parse(cookie)
159 .ok()
160 .map(|c| c.into_owned())
161 .into_iter();
162 self.0.write().unwrap().store_response_cookies(cookies, url);
163 }
164}
165
166impl CookieStore for Jar {
167 fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url) {
168 let iter =
169 cookie_headers.filter_map(|val| Cookie::parse(val).map(|c| c.0.into_owned()).ok());
170
171 self.0.write().unwrap().store_response_cookies(iter, url);
172 }
173
174 fn cookies(&self, url: &url::Url) -> Option<HeaderValue> {
175 let s = self
176 .0
177 .read()
178 .unwrap()
179 .get_request_values(url)
180 .map(|(name, value)| format!("{name}={value}"))
181 .collect::<Vec<_>>()
182 .join("; ");
183
184 if s.is_empty() {
185 return None;
186 }
187
188 HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
189 }
190}
191
192pub(crate) mod service {
193 use crate::cookie;
194 use http::{Request, Response};
195 use http_body::Body;
196 use pin_project_lite::pin_project;
197 use std::future::Future;
198 use std::pin::Pin;
199 use std::sync::Arc;
200 use std::task::ready;
201 use std::task::Context;
202 use std::task::Poll;
203 use tower::Service;
204 use url::Url;
205
206 #[derive(Clone)]
208 pub struct CookieService<S> {
209 inner: S,
210 cookie_store: Option<Arc<dyn cookie::CookieStore>>,
211 }
212
213 impl<S> CookieService<S> {
214 pub fn new(inner: S, cookie_store: Option<Arc<dyn cookie::CookieStore>>) -> Self {
216 Self {
217 inner,
218 cookie_store,
219 }
220 }
221 }
222
223 impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for CookieService<S>
224 where
225 S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
226 ReqBody: Body + Default,
227 {
228 type Response = Response<ResBody>;
229 type Error = S::Error;
230 type Future = ResponseFuture<S, ReqBody>;
231
232 #[inline]
233 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
234 self.inner.poll_ready(cx)
235 }
236
237 fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
238 let clone = self.inner.clone();
239 let mut inner = std::mem::replace(&mut self.inner, clone);
240 let url = Url::parse(req.uri().to_string().as_str()).expect("invalid URL");
241 if let Some(cookie_store) = self.cookie_store.as_ref() {
242 if req.headers().get(crate::header::COOKIE).is_none() {
243 let headers = req.headers_mut();
244 crate::util::add_cookie_header(headers, &**cookie_store, &url);
245 }
246 }
247
248 let cookie_store = self.cookie_store.clone();
249 ResponseFuture {
250 future: inner.call(req),
251 cookie_store,
252 url,
253 }
254 }
255 }
256
257 pin_project! {
258 #[allow(missing_debug_implementations)]
259 #[derive(Clone)]
260 pub struct ResponseFuture<S, B>
262 where
263 S: Service<Request<B>>,
264 {
265 #[pin]
266 future: S::Future,
267 cookie_store: Option<Arc<dyn cookie::CookieStore>>,
268 url: Url,
269 }
270 }
271
272 impl<S, ReqBody, ResBody> Future for ResponseFuture<S, ReqBody>
273 where
274 S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
275 ReqBody: Body + Default,
276 {
277 type Output = Result<Response<ResBody>, S::Error>;
278
279 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
280 let cookie_store = self.cookie_store.clone();
281 let url = self.url.clone();
282 let res = ready!(self.project().future.as_mut().poll(cx)?);
283
284 if let Some(cookie_store) = cookie_store.as_ref() {
285 let mut cookies = cookie::extract_response_cookie_headers(res.headers()).peekable();
286 if cookies.peek().is_some() {
287 cookie_store.set_cookies(&mut cookies, &url);
288 }
289 }
290 Poll::Ready(Ok(res))
291 }
292 }
293}