#![warn(clippy::complexity)]
#![warn(clippy::cargo)]
#![warn(clippy::perf)]
#![deny(unsafe_code)]
#![allow(clippy::multiple_crate_versions)]
use http::HeaderValue;
use regex::RegexSet;
use std::fmt::Debug;
macro_rules! enum_str {
($name:ident { $($variant:ident($str:expr), )* }) => {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum $name {
$($variant,)*
}
impl From<$name> for String {
fn from(input: $name) -> String {
match input {
$( $name::$variant => $str.to_string(), )*
}
}
}
}
}
enum_str!(
CspDirectiveType {
ChildSrc("child-src"),
ConnectSrc("connect-src"),
DefaultSrc("default-src"),
FontSrc("font-src"),
ImgSrc("img-src"),
ManifestSrc("manifest-src"),
MediaSrc("media-src"),
ObjectSrc("object-src"),
PrefetchSrc("prefetch-src"),
ScriptSource("script-src"),
ScriptSourceElem("script-src-elem"),
StyleSource("style-src"),
StyleSourceElem("style-src-elem"),
WorkerSource("worker-src"),
BaseUri("base-uri"),
Sandbox("sandbox"),
FormAction("form-action"),
FrameAncestors("frame-ancestors"),
NavigateTo("navigate-to"),
ReportUri("report-uri"),
ReportTo("report-to"),
RequireTrustedTypesFor("require-trusted-types-for"),
TrustedTypes("trusted-types"),
UpgradeInsecureRequests("upgrade-insecure-requests"),
});
#[derive(Debug, Clone)]
pub struct CspDirective {
pub directive_type: CspDirectiveType,
pub values: Vec<CspValue>,
}
impl CspDirective {
#[must_use]
pub fn from(directive_type: CspDirectiveType, values: Vec<CspValue>) -> Self {
Self {
directive_type,
values,
}
}
}
#[derive(Clone, Debug)]
pub struct CspUrlMatcher {
pub matcher: RegexSet,
pub directives: Vec<CspDirective>,
}
impl CspUrlMatcher {
#[must_use]
pub fn new(matcher: RegexSet) -> Self {
Self {
matcher,
directives: vec![],
}
}
pub fn with_directive(&mut self, directive: CspDirective) -> &mut Self {
self.directives.push(directive);
self
}
}
impl From<CspUrlMatcher> for HeaderValue {
fn from(input: CspUrlMatcher) -> HeaderValue {
let mut res = String::new();
for directive in input.directives {
res.push_str(&format!(" {} ", String::from(directive.directive_type)));
for val in directive.values {
res.push_str(&format!(" {}", String::from(val)));
}
res.push(';');
}
HeaderValue::from_str(&res).unwrap()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CspValue {
None,
SelfSite,
StrictDynamic,
ReportSample,
UnsafeInline,
UnsafeEval,
UnsafeHashes,
UnsafeAllowRedirects,
Host {
value: &'static str,
},
SchemeHttps,
SchemeHttp,
SchemeData,
SchemeOther {
value: &'static str,
},
Nonce {
value: &'static str,
},
Sha256 {
value: &'static str,
},
Sha384 {
value: &'static str,
},
Sha512 {
value: &'static str,
},
}
impl From<CspValue> for String {
fn from(input: CspValue) -> String {
match input {
CspValue::None => "'none'".to_string(),
CspValue::SelfSite => "'self'".to_string(),
CspValue::StrictDynamic => "'strict-dynamic'".to_string(),
CspValue::ReportSample => "'report-sample'".to_string(),
CspValue::UnsafeInline => "'unsafe-inline'".to_string(),
CspValue::UnsafeEval => "'unsafe-eval'".to_string(),
CspValue::UnsafeHashes => "'unsafe-hashes'".to_string(),
CspValue::UnsafeAllowRedirects => "'unsafe-allow-redirects'".to_string(),
CspValue::SchemeHttps => "https:".to_string(),
CspValue::SchemeHttp => "http:".to_string(),
CspValue::SchemeData => "data:".to_string(),
CspValue::Host { value } | CspValue::SchemeOther { value } => value.to_string(),
CspValue::Nonce { value } => format!("nonce-{value}"),
CspValue::Sha256 { value } => format!("sha256-{value}"),
CspValue::Sha384 { value } => format!("sha384-{value}"),
CspValue::Sha512 { value } => format!("sha512-{value}"),
}
}
}