use crate::{
Error, Result,
cookies::{Cookie, CookieAcceptPolicy},
};
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::runtime::ProtocolObject;
use objc2_foundation::{
NSCopying, NSDictionary, NSHTTPCookie, NSHTTPCookieDomain, NSHTTPCookieName, NSHTTPCookiePath,
NSHTTPCookieSecure, NSHTTPCookieStorage, NSHTTPCookieValue, NSMutableDictionary, NSString,
NSURL,
};
#[derive(Debug, Clone)]
pub struct FoundationCookieStorage {
storage: Retained<NSHTTPCookieStorage>,
}
impl Cookie {
pub(crate) fn to_ns_cookie(&self) -> Result<Retained<NSHTTPCookie>> {
unsafe {
let dict: Retained<NSMutableDictionary<ProtocolObject<dyn NSCopying>, AnyObject>> =
NSMutableDictionary::new();
dict.setObject_forKey(
&*NSString::from_str(&self.name) as &AnyObject,
ProtocolObject::from_ref(NSHTTPCookieName),
);
dict.setObject_forKey(
&*NSString::from_str(&self.value) as &AnyObject,
ProtocolObject::from_ref(NSHTTPCookieValue),
);
dict.setObject_forKey(
&*NSString::from_str(&self.domain) as &AnyObject,
ProtocolObject::from_ref(NSHTTPCookieDomain),
);
dict.setObject_forKey(
&*NSString::from_str(&self.path) as &AnyObject,
ProtocolObject::from_ref(NSHTTPCookiePath),
);
if self.secure {
dict.setObject_forKey(
&*NSString::from_str("TRUE") as &AnyObject,
ProtocolObject::from_ref(NSHTTPCookieSecure),
);
}
let cookie_dict: Retained<NSDictionary<NSString, AnyObject>> =
objc2::rc::Retained::cast_unchecked(dict);
NSHTTPCookie::cookieWithProperties(&*cookie_dict)
.ok_or_else(|| Error::Internal("Failed to create NSHTTPCookie".to_string()))
}
}
pub(crate) fn from_ns_cookie(ns_cookie: &NSHTTPCookie) -> Option<Self> {
unsafe {
let name = ns_cookie.name().to_string();
let value = ns_cookie.value().to_string();
let domain = ns_cookie.domain().to_string();
let path = ns_cookie.path().to_string();
let secure = ns_cookie.isSecure();
let http_only = ns_cookie.isHTTPOnly();
let expires = ns_cookie
.expiresDate()
.map(|date| date.description().to_string());
Some(Self {
name,
value,
domain,
path,
secure,
http_only,
expires,
})
}
}
}
impl FoundationCookieStorage {
pub fn new() -> Self {
Self {
storage: unsafe { NSHTTPCookieStorage::sharedHTTPCookieStorage() },
}
}
pub fn for_group_container(identifier: &str) -> Self {
let storage = unsafe {
NSHTTPCookieStorage::sharedCookieStorageForGroupContainerIdentifier(
&NSString::from_str(identifier),
)
};
Self { storage }
}
pub fn all_cookies(&self) -> Vec<Cookie> {
let cookies = unsafe { self.storage.cookies() };
if let Some(cookies) = cookies {
(0..cookies.len())
.map(|i| cookies.objectAtIndex(i))
.filter_map(|cookie| Cookie::from_ns_cookie(&cookie))
.collect()
} else {
Vec::new()
}
}
pub fn cookies_for_url(&self, url: &str) -> Result<Vec<Cookie>> {
let nsurl =
unsafe { NSURL::URLWithString(&NSString::from_str(url)).ok_or(Error::InvalidUrl)? };
let cookies = unsafe { self.storage.cookiesForURL(&nsurl) };
if let Some(cookies) = cookies {
Ok((0..cookies.len())
.map(|i| cookies.objectAtIndex(i))
.filter_map(|cookie| Cookie::from_ns_cookie(&cookie))
.collect())
} else {
Ok(Vec::new())
}
}
pub fn add_cookie(&self, cookie: Cookie) -> Result<()> {
let ns_cookie = cookie.to_ns_cookie()?;
unsafe {
self.storage.setCookie(&ns_cookie);
}
Ok(())
}
pub fn remove_cookie(&self, cookie: Cookie) -> Result<()> {
let ns_cookie = cookie.to_ns_cookie()?;
unsafe {
self.storage.deleteCookie(&ns_cookie);
}
Ok(())
}
pub fn clear(&self) {
let cookies = self.all_cookies();
for cookie in cookies {
let _ = self.remove_cookie(cookie);
}
}
pub fn set_cookie_accept_policy(&self, policy: CookieAcceptPolicy) {
use objc2_foundation::NSHTTPCookieAcceptPolicy;
let ns_policy = match policy {
CookieAcceptPolicy::Always => NSHTTPCookieAcceptPolicy::Always,
CookieAcceptPolicy::Never => NSHTTPCookieAcceptPolicy::Never,
CookieAcceptPolicy::OnlyFromMainDocumentDomain => {
NSHTTPCookieAcceptPolicy::OnlyFromMainDocumentDomain
}
};
unsafe {
self.storage.setCookieAcceptPolicy(ns_policy);
}
}
}