Skip to main content

windows_webview/
cookie.rs

1use super::*;
2
3/// The `SameSite` policy of a [`Cookie`], controlling whether it is sent with
4/// cross-site requests.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum SameSite {
7    /// Sent with all requests, including cross-site (requires
8    /// [`is_secure`](Cookie::is_secure)).
9    None,
10    /// Sent with same-site requests and top-level cross-site navigations.
11    Lax,
12    /// Sent only with same-site requests.
13    Strict,
14}
15
16impl SameSite {
17    fn from_raw(value: COREWEBVIEW2_COOKIE_SAME_SITE_KIND) -> Self {
18        match value {
19            0 => Self::None,
20            2 => Self::Strict,
21            _ => Self::Lax,
22        }
23    }
24
25    fn to_raw(self) -> COREWEBVIEW2_COOKIE_SAME_SITE_KIND {
26        match self {
27            Self::None => 0,
28            Self::Lax => 1,
29            Self::Strict => 2,
30        }
31    }
32}
33
34/// A browser cookie. Read cookies with [`CookieManager::get_cookies`] and write
35/// them with [`CookieManager::add_or_update_cookie`].
36#[derive(Clone, Debug, PartialEq)]
37pub struct Cookie {
38    pub name: String,
39    pub value: String,
40    pub domain: String,
41    pub path: String,
42    /// Whether the cookie is sent only over HTTPS.
43    pub is_secure: bool,
44    /// Whether the cookie is hidden from script (`HttpOnly`).
45    pub is_http_only: bool,
46    pub same_site: SameSite,
47    /// When the cookie expires, as seconds since the Unix epoch, or `None` for a
48    /// session cookie that is cleared when the browser closes.
49    pub expires: Option<f64>,
50}
51
52impl Cookie {
53    /// Creates a cookie for the given `name`, `value`, `domain`, and `path` with
54    /// default attributes (a non-secure, `Lax`, session cookie). Adjust the
55    /// public fields before passing it to
56    /// [`CookieManager::add_or_update_cookie`].
57    pub fn new(name: &str, value: &str, domain: &str, path: &str) -> Self {
58        Self {
59            name: name.to_string(),
60            value: value.to_string(),
61            domain: domain.to_string(),
62            path: path.to_string(),
63            is_secure: false,
64            is_http_only: false,
65            same_site: SameSite::Lax,
66            expires: None,
67        }
68    }
69
70    fn from_com(cookie: &ICoreWebView2Cookie) -> Result<Self> {
71        unsafe {
72            let expires = if cookie.IsSession()?.as_bool() {
73                None
74            } else {
75                Some(cookie.Expires()?)
76            };
77            Ok(Self {
78                name: string::take(cookie.Name()?),
79                value: string::take(cookie.Value()?),
80                domain: string::take(cookie.Domain()?),
81                path: string::take(cookie.Path()?),
82                is_secure: cookie.IsSecure()?.as_bool(),
83                is_http_only: cookie.IsHttpOnly()?.as_bool(),
84                same_site: SameSite::from_raw(cookie.SameSite()?),
85                expires,
86            })
87        }
88    }
89}
90
91/// Reads, writes, and deletes the browser's cookies. Obtain it with
92/// [`WebView::cookie_manager`].
93pub struct CookieManager(pub(crate) ICoreWebView2CookieManager);
94
95impl CookieManager {
96    /// Asynchronously retrieves the cookies that apply to `uri` (or all cookies
97    /// when `uri` is empty). The `handler` closure receives them on the UI
98    /// thread.
99    pub fn get_cookies<F: FnOnce(Result<Vec<Cookie>>) + 'static>(
100        &self,
101        uri: &str,
102        handler: F,
103    ) -> Result<()> {
104        let uri = HSTRING::from(uri);
105        let handler = handler::GetCookiesCompleted::create(handler);
106        unsafe { self.0.GetCookies(&uri, &handler) }.ok()
107    }
108
109    /// Adds the cookie, or updates the existing cookie with the same name,
110    /// domain, and path.
111    pub fn add_or_update_cookie(&self, cookie: &Cookie) -> Result<()> {
112        let name = HSTRING::from(&cookie.name);
113        let value = HSTRING::from(&cookie.value);
114        let domain = HSTRING::from(&cookie.domain);
115        let path = HSTRING::from(&cookie.path);
116        unsafe {
117            let raw = self.0.CreateCookie(&name, &value, &domain, &path)?;
118            raw.SetIsSecure(cookie.is_secure).ok()?;
119            raw.SetIsHttpOnly(cookie.is_http_only).ok()?;
120            raw.SetSameSite(cookie.same_site.to_raw()).ok()?;
121            if let Some(expires) = cookie.expires {
122                raw.SetExpires(expires).ok()?;
123            }
124            self.0.AddOrUpdateCookie(&raw).ok()
125        }
126    }
127
128    /// Deletes cookies with the matching `name` that apply to `uri`.
129    pub fn delete_cookies(&self, name: &str, uri: &str) -> Result<()> {
130        let name = HSTRING::from(name);
131        let uri = HSTRING::from(uri);
132        unsafe { self.0.DeleteCookies(&name, &uri) }.ok()
133    }
134
135    /// Deletes cookies with the matching `name`, `domain`, and `path`.
136    pub fn delete_cookies_with_domain_and_path(
137        &self,
138        name: &str,
139        domain: &str,
140        path: &str,
141    ) -> Result<()> {
142        let name = HSTRING::from(name);
143        let domain = HSTRING::from(domain);
144        let path = HSTRING::from(path);
145        unsafe { self.0.DeleteCookiesWithDomainAndPath(&name, &domain, &path) }.ok()
146    }
147
148    /// Deletes all cookies.
149    pub fn delete_all_cookies(&self) -> Result<()> {
150        unsafe { self.0.DeleteAllCookies() }.ok()
151    }
152}
153
154pub(crate) fn collect(list: &ICoreWebView2CookieList) -> Result<Vec<Cookie>> {
155    let count = unsafe { list.Count()? };
156    let mut cookies = Vec::with_capacity(count as usize);
157    for index in 0..count {
158        let cookie = unsafe { list.GetValueAtIndex(index)? };
159        cookies.push(Cookie::from_com(&cookie)?);
160    }
161    Ok(cookies)
162}