127 lines
3.5 KiB
Rust
127 lines
3.5 KiB
Rust
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
/// Hosts that are out of bounds for someone who didn't accept TOS
|
|
const GATED_DOMAINS: &[&str] = &["polyfrost.org", "sentry.io"];
|
|
|
|
/// Hosts that are allowed even if someone didn't accept TOS
|
|
const ALLOWED_HOSTS: &[&str] = &[
|
|
"data-v2.polyfrost.org",
|
|
"meta.polyfrost.org",
|
|
"status.polyfrost.org",
|
|
];
|
|
|
|
static DECLINED: AtomicBool = AtomicBool::new(false);
|
|
|
|
pub fn init(declined: bool) {
|
|
DECLINED.store(declined, Ordering::SeqCst);
|
|
}
|
|
|
|
pub fn decline() {
|
|
DECLINED.store(true, Ordering::SeqCst);
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn declined() -> bool {
|
|
DECLINED.load(Ordering::Relaxed)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn blocks_url(url: &str) -> bool {
|
|
declined() && is_gated_url(url)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn is_gated_url(url: &str) -> bool {
|
|
url::Url::parse(url)
|
|
.ok()
|
|
.and_then(|parsed| parsed.host_str().map(is_gated_host))
|
|
.unwrap_or(true)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn is_gated_host(host: &str) -> bool {
|
|
let host = host.trim_end_matches('.').to_ascii_lowercase();
|
|
|
|
if ALLOWED_HOSTS.contains(&host.as_str()) {
|
|
return false;
|
|
}
|
|
|
|
GATED_DOMAINS
|
|
.iter()
|
|
.any(|domain| host == *domain || host.ends_with(&format!(".{domain}")))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn gates_polyfrost_services_and_the_crash_reporter() {
|
|
for host in [
|
|
"polyfrost.org",
|
|
"plus.polyfrost.org",
|
|
"PLUS.POLYFROST.ORG",
|
|
"plus.polyfrost.org.",
|
|
"o4511714343124992.ingest.us.sentry.io",
|
|
] {
|
|
assert!(is_gated_host(host), "expected {host} to be gated");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn leaves_everything_else_alone() {
|
|
for host in [
|
|
"api.modrinth.com",
|
|
"api.curseforge.com",
|
|
"sessionserver.mojang.com",
|
|
"api.minecraftservices.com",
|
|
"www.gstatic.com",
|
|
"github.com",
|
|
"notpolyfrost.org",
|
|
"polyfrost.org.attacker.com",
|
|
"evil-sentry.io.attacker.com",
|
|
] {
|
|
assert!(!is_gated_host(host), "unexpected gate on {host}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn lets_the_exempt_hosts_through() {
|
|
for url in [
|
|
"https://data-v2.polyfrost.org/oneclient/bundles/metadata.json",
|
|
"https://data-v2.polyfrost.org/oneclient/versions/art/Tricky_Trials.jpg",
|
|
"https://data-v2.polyfrost.org/oneclient/tos.json",
|
|
"https://meta.polyfrost.org/fabric/v2/manifest.json",
|
|
"https://status.polyfrost.org/index.json",
|
|
"https://META.POLYFROST.ORG./minecraft/v1/manifest.json",
|
|
] {
|
|
assert!(!is_gated_url(url), "unexpected gate on {url}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn the_exemption_is_the_exact_host_and_nothing_near_it() {
|
|
for host in [
|
|
"plus.polyfrost.org",
|
|
"evil.data-v2.polyfrost.org",
|
|
"meta.polyfrost.org.evil.polyfrost.org",
|
|
"status.polyfrost.org.evil.polyfrost.org",
|
|
] {
|
|
assert!(is_gated_host(host), "expected {host} to stay gated");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn gates_urls_by_their_host() {
|
|
assert!(is_gated_url("https://plus.polyfrost.org/account/login"));
|
|
assert!(is_gated_url("wss://plus.polyfrost.org/websocket"));
|
|
assert!(!is_gated_url("https://api.modrinth.com/v2/search"));
|
|
}
|
|
|
|
#[test]
|
|
fn unparseable_urls_fail_closed() {
|
|
assert!(is_gated_url("not a url"));
|
|
assert!(is_gated_url(""));
|
|
assert!(is_gated_url("file:///etc/hosts"));
|
|
}
|
|
}
|