bench_scraper/
cookie.rs

1#![warn(missing_docs)]
2
3#[derive(Debug, PartialEq, Eq)]
4/// The [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy for a cookie.
5pub enum SameSite {
6    /// Cookies are not sent on normal cross-site subrequests, but are sent when a user is navigating to the origin site.
7    Lax,
8    /// Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party websites.
9    Strict,
10    /// Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-site requests.
11    None,
12}
13
14#[derive(Debug, PartialEq, Eq)]
15/// A single HTTP cookie.
16pub struct Cookie {
17    /// The host (domain) with which the cookie is associated.
18    pub host: String,
19    /// The path under which the cookie should be used when making requests.
20    pub path: String,
21    /// The name of the cookie.
22    pub name: String,
23    /// The contents of the cookie.
24    pub value: String,
25    /// Whether the cookie should only be sent over encrypted channels (https).
26    pub is_secure: bool,
27    /// Whether the cookie should be hidden from client-side scripting (javascript).
28    pub is_http_only: bool,
29    /// When the cookie was first registered with the browser.
30    pub creation_time: time::OffsetDateTime,
31    /// When the cookie will no longer be valid.
32    pub expiration_time: Option<time::OffsetDateTime>,
33    /// The SameSite setting for the cookie (which may not have been specified).
34    pub same_site: Option<SameSite>,
35    /// The last time the cookie was accessed by the browser (if this data is tracked).
36    pub last_accessed: Option<time::OffsetDateTime>,
37}
38
39impl Cookie {
40    /// Crafts a [`Set-Cookie` header value] corresponding to this cookie.
41    ///
42    /// [`Set-Cookie` header value]: https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie
43    pub fn get_set_cookie_header(&self) -> String {
44        let mut properties: Vec<String> = vec![
45            format!("{}={}", self.name, self.value),
46            format!("Path={}", self.path),
47        ];
48        // we're doing our best to guess whether domain is set or not
49        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
50        if !self.name.starts_with("__Host-") {
51            properties.push(format!("Domain={}", self.host));
52        }
53        if let Some(expiration) = self.expiration_time {
54            if let Ok(format) = expiration.format(&time::format_description::well_known::Rfc2822) {
55                properties.push(format!("Expires={}", format));
56            }
57        }
58        if self.is_secure {
59            properties.push("Secure".to_string())
60        }
61        if self.is_http_only {
62            properties.push("HttpOnly".to_string());
63        }
64        if let Some(ss) = &self.same_site {
65            properties.push(format!(
66                "SameSite={}",
67                match ss {
68                    SameSite::Lax => "Lax",
69                    SameSite::Strict => "Strict",
70                    SameSite::None => "None",
71                }
72            ));
73        }
74        properties.join("; ")
75    }
76
77    /// Creates a [URL] that could have feasibly responded with this cookie as a `Set-Cookie` header.
78    ///
79    /// This URL is a guess based on the cookie's domain and path;
80    /// there is no guarantee that calls to this URL will set this cookie,
81    /// or even that this URL will respond successfully.
82    ///
83    /// [URL]: https://developer.mozilla.org/docs/Glossary/URL
84    pub fn get_url(&self) -> String {
85        format!("https://{}{}", self.host.trim_matches('.'), self.path)
86    }
87}
88
89#[cfg(feature = "reqwest")]
90impl TryFrom<Cookie> for reqwest::header::HeaderValue {
91    type Error = reqwest::header::InvalidHeaderValue;
92
93    fn try_from(cookie: Cookie) -> Result<Self, Self::Error> {
94        let result = cookie.get_set_cookie_header();
95        reqwest::header::HeaderValue::from_str(&result)
96    }
97}
98
99#[cfg(feature = "reqwest")]
100impl FromIterator<Cookie> for reqwest::cookie::Jar {
101    fn from_iter<I: IntoIterator<Item = Cookie>>(iter: I) -> reqwest::cookie::Jar {
102        let jar = reqwest::cookie::Jar::default();
103        for cookie in iter {
104            let set_cookie = cookie.get_set_cookie_header();
105            if let Ok(url) = reqwest::Url::parse(&cookie.get_url()) {
106                jar.add_cookie_str(&set_cookie, &url);
107            }
108        }
109        jar
110    }
111}