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 fmt::Debug for Cookie<'_> {
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(
108 headers: &hyper::HeaderMap,
109) -> impl Iterator<Item = &HeaderValue> + '_ {
110 headers.get_all(SET_COOKIE).iter()
111}
112
113pub(crate) fn extract_response_cookies(
114 headers: &hyper::HeaderMap,
115) -> impl Iterator<Item = Result<Cookie<'_>, CookieParseError>> + '_ {
116 headers.get_all(SET_COOKIE).iter().map(Cookie::parse)
117}
118
119pub(crate) struct CookieParseError(cookie_crate::ParseError);
121
122impl fmt::Debug for CookieParseError {
123 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124 self.0.fmt(f)
125 }
126}
127
128impl fmt::Display for CookieParseError {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 self.0.fmt(f)
131 }
132}
133
134impl std::error::Error for CookieParseError {}
135
136impl Jar {
139 pub fn add_cookie_str(&self, cookie: &str, url: &url::Url) {
155 let cookies = cookie_crate::Cookie::parse(cookie)
156 .ok()
157 .map(|c| c.into_owned())
158 .into_iter();
159 self.0.write().unwrap().store_response_cookies(cookies, url);
160 }
161}
162
163impl CookieStore for Jar {
164 fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url) {
165 let iter =
166 cookie_headers.filter_map(|val| Cookie::parse(val).map(|c| c.0.into_owned()).ok());
167
168 self.0.write().unwrap().store_response_cookies(iter, url);
169 }
170
171 fn cookies(&self, url: &url::Url) -> Option<HeaderValue> {
172 let s = self
173 .0
174 .read()
175 .unwrap()
176 .get_request_values(url)
177 .map(|(name, value)| format!("{name}={value}"))
178 .collect::<Vec<_>>()
179 .join("; ");
180
181 if s.is_empty() {
182 return None;
183 }
184
185 HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
186 }
187}
188
189pub(crate) mod service {
190 use crate::cookie;
191 use http::{Request, Response};
192 use http_body::Body;
193 use pin_project_lite::pin_project;
194 use std::future::Future;
195 use std::pin::Pin;
196 use std::sync::Arc;
197 use std::task::ready;
198 use std::task::Context;
199 use std::task::Poll;
200 use tower::Service;
201 use url::Url;
202
203 #[derive(Clone)]
205 pub struct CookieService<S> {
206 inner: S,
207 cookie_store: Option<Arc<dyn cookie::CookieStore>>,
208 }
209
210 impl<S> CookieService<S> {
211 pub fn new(inner: S, cookie_store: Option<Arc<dyn cookie::CookieStore>>) -> Self {
213 Self {
214 inner,
215 cookie_store,
216 }
217 }
218 }
219
220 impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for CookieService<S>
221 where
222 S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
223 ReqBody: Body + Default,
224 {
225 type Response = Response<ResBody>;
226 type Error = S::Error;
227 type Future = ResponseFuture<S, ReqBody>;
228
229 #[inline]
230 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
231 self.inner.poll_ready(cx)
232 }
233
234 fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
235 let clone = self.inner.clone();
236 let mut inner = std::mem::replace(&mut self.inner, clone);
237 let url = Url::parse(req.uri().to_string().as_str()).expect("invalid URL");
238 if let Some(cookie_store) = self.cookie_store.as_ref() {
239 if req.headers().get(crate::header::COOKIE).is_none() {
240 let headers = req.headers_mut();
241 crate::util::add_cookie_header(headers, &**cookie_store, &url);
242 }
243 }
244
245 let cookie_store = self.cookie_store.clone();
246 ResponseFuture {
247 future: inner.call(req),
248 cookie_store,
249 url,
250 }
251 }
252 }
253
254 pin_project! {
255 #[allow(missing_debug_implementations)]
256 #[derive(Clone)]
257 pub struct ResponseFuture<S, B>
259 where
260 S: Service<Request<B>>,
261 {
262 #[pin]
263 future: S::Future,
264 cookie_store: Option<Arc<dyn cookie::CookieStore>>,
265 url: Url,
266 }
267 }
268
269 impl<S, ReqBody, ResBody> Future for ResponseFuture<S, ReqBody>
270 where
271 S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
272 ReqBody: Body + Default,
273 {
274 type Output = Result<Response<ResBody>, S::Error>;
275
276 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
277 let cookie_store = self.cookie_store.clone();
278 let url = self.url.clone();
279 let res = ready!(self.project().future.as_mut().poll(cx)?);
280
281 if let Some(cookie_store) = cookie_store.as_ref() {
282 let mut cookies = cookie::extract_response_cookie_headers(res.headers()).peekable();
283 if cookies.peek().is_some() {
284 cookie_store.set_cookies(&mut cookies, &url);
285 }
286 }
287 Poll::Ready(Ok(res))
288 }
289 }
290}