use crate::Headers;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum Source {
SameOrigin,
Src,
None,
UnsafeInline,
Data,
Mediastream,
Https,
Blob,
Filesystem,
StrictDynamic,
UnsafeEval,
Wildcard,
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Source::SameOrigin => write!(f, "'self'"),
Source::Src => write!(f, "'src'"),
Source::None => write!(f, "'none'"),
Source::UnsafeInline => write!(f, "'unsafe-inline'"),
Source::Data => write!(f, "data:"),
Source::Mediastream => write!(f, "mediastream:"),
Source::Https => write!(f, "https:"),
Source::Blob => write!(f, "blob:"),
Source::Filesystem => write!(f, "filesystem:"),
Source::StrictDynamic => write!(f, "'strict-dynamic'"),
Source::UnsafeEval => write!(f, "'unsafe-eval'"),
Source::Wildcard => write!(f, "*"),
}
}
}
impl AsRef<str> for Source {
fn as_ref(&self) -> &str {
match *self {
Source::SameOrigin => "'self'",
Source::Src => "'src'",
Source::None => "'none'",
Source::UnsafeInline => "'unsafe-inline'",
Source::Data => "data:",
Source::Mediastream => "mediastream:",
Source::Https => "https:",
Source::Blob => "blob:",
Source::Filesystem => "filesystem:",
Source::StrictDynamic => "'strict-dynamic'",
Source::UnsafeEval => "'unsafe-eval'",
Source::Wildcard => "*",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ReportTo {
#[serde(skip_serializing_if = "Option::is_none")]
group: Option<String>,
max_age: i32,
endpoints: Vec<ReportToEndpoint>,
#[serde(skip_serializing_if = "Option::is_none")]
include_subdomains: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ReportToEndpoint {
url: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentSecurityPolicy {
policy: Vec<String>,
report_only_flag: bool,
directives: HashMap<String, Vec<String>>,
}
impl Default for ContentSecurityPolicy {
fn default() -> Self {
let policy = String::from("script-src 'self'; object-src 'self'");
ContentSecurityPolicy {
policy: vec![policy],
report_only_flag: false,
directives: HashMap::new(),
}
}
}
impl ContentSecurityPolicy {
pub fn new() -> Self {
Self {
policy: Vec::new(),
report_only_flag: false,
directives: HashMap::new(),
}
}
fn insert_directive<T: AsRef<str>>(&mut self, directive: &str, source: T) {
let directive = String::from(directive);
let directives = self.directives.entry(directive).or_insert_with(Vec::new);
let source: String = source.as_ref().to_string();
directives.push(source);
}
pub fn base_uri<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("base-uri", source);
self
}
pub fn block_all_mixed_content(&mut self) -> &mut Self {
let policy = String::from("block-all-mixed-content");
self.policy.push(policy);
self
}
pub fn connect_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("connect-src", source);
self
}
pub fn default_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("default-src", source);
self
}
pub fn font_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("font-src", source);
self
}
pub fn form_action<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("form-action", source);
self
}
pub fn frame_ancestors<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("frame-ancestors", source);
self
}
pub fn frame_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("frame-src", source);
self
}
pub fn img_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("img-src", source);
self
}
pub fn media_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("media-src", source);
self
}
pub fn object_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("object-src", source);
self
}
pub fn plugin_types<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("plugin-types", source);
self
}
pub fn require_sri_for<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("require-sri-for", source);
self
}
pub fn report_uri<T: AsRef<str>>(&mut self, uri: T) -> &mut Self {
self.insert_directive("report-uri", uri);
self
}
pub fn report_to(&mut self, endpoints: Vec<ReportTo>) -> &mut Self {
for endpoint in endpoints.iter() {
match serde_json::to_string(&endpoint) {
Ok(json) => {
let policy = format!("report-to {}", json);
self.policy.push(policy);
}
Err(error) => {
println!("{:?}", error);
}
}
}
self
}
pub fn sandbox<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("sandbox", source);
self
}
pub fn script_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("script-src", source);
self
}
pub fn style_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("style-src", source);
self
}
pub fn upgrade_insecure_requests(&mut self) -> &mut Self {
let policy = String::from("upgrade-insecure-requests");
self.policy.push(policy);
self
}
pub fn worker_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
self.insert_directive("worker-src", source);
self
}
pub fn report_only(&mut self) -> &mut Self {
self.report_only_flag = true;
self
}
fn value(&mut self) -> String {
for (directive, sources) in &self.directives {
let policy = format!("{} {}", directive, sources.join(" "));
self.policy.push(policy);
self.policy.sort();
}
self.policy.join("; ")
}
pub fn apply(&mut self, mut headers: impl AsMut<Headers>) {
let name = if self.report_only_flag {
"Content-Security-Policy-Report-Only"
} else {
"Content-Security-Policy"
};
headers
.as_mut()
.insert(name, self.value().to_owned())
.unwrap();
}
}