use std::convert::From;
use std::fs::{create_dir, read_to_string};
use std::path::PathBuf;
use std::sync::RwLock;
use bytes::Bytes;
use reqwest::cookie::CookieStore;
use reqwest::header::HeaderValue;
use reqwest::Url;
#[derive(Debug, thiserror::Error)]
pub(crate) enum CookieCacheError {
#[error("No existing cookie cache found.")]
DoesNotExist,
#[error("Failed to read cookie cache from disk.")]
FileSystemError,
#[error("Failed to (de)serialize cookie cache: {error}")]
SerializationError {
#[from]
error: serde_json::Error,
},
}
impl From<std::io::Error> for CookieCacheError {
fn from(_: std::io::Error) -> Self {
Self::FileSystemError
}
}
fn get_cookie_cache_dir() -> Result<PathBuf, CookieCacheError> {
let home = dirs::home_dir().ok_or(CookieCacheError::FileSystemError)?;
Ok(home.join(".fedora"))
}
fn get_cookie_cache_path() -> Result<PathBuf, CookieCacheError> {
let home = dirs::home_dir().ok_or(CookieCacheError::FileSystemError)?;
Ok(home.join(".fedora/fedora-rs-cookie-jar.json"))
}
fn parse_cookie(value: &HeaderValue) -> Result<cookie::Cookie, cookie::ParseError> {
std::str::from_utf8(value.as_bytes())
.map_err(cookie::ParseError::from)
.and_then(cookie::Cookie::parse)
}
#[derive(Debug)]
pub(crate) struct CachingJar {
pub(crate) store: RwLock<cookie_store::CookieStore>,
}
impl CachingJar {
pub fn new(store: cookie_store::CookieStore) -> CachingJar {
CachingJar {
store: RwLock::new(store),
}
}
pub fn empty() -> CachingJar {
CachingJar {
store: RwLock::new(cookie_store::CookieStore::default()),
}
}
pub fn read_from_disk() -> Result<CachingJar, CookieCacheError> {
let path = get_cookie_cache_path()?;
let contents = match read_to_string(path) {
Ok(string) => Ok(string),
Err(error) => {
if let std::io::ErrorKind::NotFound = error.kind() {
Err(CookieCacheError::DoesNotExist)
} else {
Err(error.into())
}
},
}?;
let store: cookie_store::CookieStore = serde_json::from_str(&contents)?;
Ok(CachingJar::new(store))
}
pub fn write_to_disk(&self) -> Result<(), CookieCacheError> {
let cache_dir = get_cookie_cache_dir()?;
let cache_path = get_cookie_cache_path()?;
if !cache_dir.exists() {
create_dir(cache_dir)?;
}
let store = &*self.store.read().expect("Poisoned lock!");
let contents = serde_json::to_string_pretty(store)?;
std::fs::write(cache_path, contents)?;
Ok(())
}
}
impl CookieStore for CachingJar {
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &Url) {
let iter = cookie_headers.filter_map(|val| parse_cookie(val).map(|cookie| cookie.into_owned()).ok());
self.store
.write()
.expect("Poisoned RwLock! Something has gone wrong.")
.store_response_cookies(iter, url);
}
fn cookies(&self, url: &Url) -> Option<HeaderValue> {
let s = self
.store
.read()
.expect("Poisoned RwLock! Something has gone wrong.")
.get_request_values(url)
.map(|(name, value)| format!("{}={}", name, value))
.collect::<Vec<_>>()
.join("; ");
if s.is_empty() {
return None;
}
HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
}
}