use crate::request::RequestExt;
use chrono::prelude::*;
use chrono::Duration;
use lazy_static::lazy_static;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use publicsuffix::List;
use std::error::Error;
lazy_static! {
static ref TTL: Duration = Duration::hours(24);
static ref CACHE: RwLock<ListCache> = Default::default();
}
struct ListCache {
list: List,
last_refreshed: Option<DateTime<Utc>>,
last_updated: Option<DateTime<Utc>>,
}
impl Default for ListCache {
fn default() -> Self {
Self {
list: List::from_str(include_str!("list/public_suffix_list.dat"))
.expect("could not parse bundled public suffix list"),
last_refreshed: None,
last_updated: None,
}
}
}
impl ListCache {
fn needs_refreshed(&self) -> bool {
match self.last_refreshed {
Some(last_refreshed) => Utc::now() - last_refreshed > *TTL,
None => true,
}
}
fn refresh(&mut self) -> Result<(), Box<dyn Error>> {
let result = self.try_refresh();
self.last_refreshed = Some(Utc::now());
result
}
fn try_refresh(&mut self) -> Result<(), Box<dyn Error>> {
let mut request = http::Request::get(publicsuffix::LIST_URL);
if let Some(last_updated) = self.last_updated {
request.header(http::header::IF_MODIFIED_SINCE, last_updated.to_rfc2822());
}
let mut response = request.body(())?.send()?;
match response.status() {
http::StatusCode::OK => {
self.list = List::from_reader(response.body_mut())?;
self.last_updated = Some(Utc::now());
log::debug!("public suffix list updated");
}
http::StatusCode::NOT_MODIFIED => {
self.last_updated = Some(Utc::now());
}
status => log::warn!(
"could not update public suffix list, got status code {}",
status,
),
}
Ok(())
}
}
pub(crate) fn is_public_suffix(domain: impl AsRef<str>) -> bool {
let domain = domain.as_ref();
with_cache(|cache| {
cache
.list
.parse_domain(domain)
.ok()
.and_then(|d| d.suffix().map(|d| d == domain))
.unwrap_or(false)
})
}
fn with_cache<T>(f: impl FnOnce(&ListCache) -> T) -> T {
let cache = CACHE.upgradable_read();
if cache.needs_refreshed() {
let mut cache = RwLockUpgradableReadGuard::upgrade(cache);
if cache.needs_refreshed() {
if let Err(e) = cache.refresh() {
log::warn!("could not refresh public suffix list: {}", e);
}
}
f(&*cache)
} else {
f(&*cache)
}
}