///|
/// CSP directive families used by the catalog and reports.
pub(all) enum DirectiveCategory {
DirectiveFetch
DirectiveDocument
DirectiveNavigation
DirectiveReporting
DirectiveSandbox
DirectiveTrustedTypes
DirectiveUpgrade
DirectiveDeprecated
DirectiveExperimental
DirectiveOther
} derive(Eq, Debug)
///|
/// Metadata for a known CSP directive.
pub(all) struct DirectiveSpec {
name : String
category : DirectiveCategory
fallback : String?
repeatable : Bool
level : String
description : String
} derive(Eq, Debug)
///|
fn make_directive_spec(
name : String,
category : DirectiveCategory,
fallback : String?,
repeatable : Bool,
level : String,
description : String,
) -> DirectiveSpec {
{ name, category, fallback, repeatable, level, description }
}
///|
pub fn DirectiveCategory::name(self : DirectiveCategory) -> String {
match self {
DirectiveFetch => "fetch"
DirectiveDocument => "document"
DirectiveNavigation => "navigation"
DirectiveReporting => "reporting"
DirectiveSandbox => "sandbox"
DirectiveTrustedTypes => "trusted-types"
DirectiveUpgrade => "upgrade"
DirectiveDeprecated => "deprecated"
DirectiveExperimental => "experimental"
DirectiveOther => "other"
}
}
///|
pub fn DirectiveCategory::is_security_core(self : DirectiveCategory) -> Bool {
self == DirectiveFetch ||
self == DirectiveDocument ||
self == DirectiveNavigation ||
self == DirectiveSandbox ||
self == DirectiveTrustedTypes
}
///|
pub fn DirectiveSpec::summary(self : DirectiveSpec) -> String {
let fallback = match self.fallback {
Some(name) => name
None => "-"
}
self.name +
" category=" +
self.category.name() +
" level=" +
self.level +
" fallback=" +
fallback
}
///|
pub fn DirectiveSpec::is_fetch(self : DirectiveSpec) -> Bool {
self.category == DirectiveFetch
}
///|
pub fn DirectiveSpec::has_fallback(self : DirectiveSpec) -> Bool {
self.fallback is Some(_)
}
///|
pub fn DirectiveSpec::is_deprecated(self : DirectiveSpec) -> Bool {
self.category == DirectiveDeprecated
}
///|
pub fn DirectiveSpec::is_experimental(self : DirectiveSpec) -> Bool {
self.category == DirectiveExperimental
}
///|
pub fn directive_spec(name : StringView) -> DirectiveSpec? {
let key = lower_ascii(name)
if key == "base-uri" {
Some(
make_directive_spec(
"base-uri",
DirectiveDocument,
None,
false,
"CSP2",
"Restricts URLs that may be used in a document base element.",
),
)
} else if key == "child-src" {
Some(
make_directive_spec(
"child-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP2",
"Controls nested browsing contexts and workers in older policies.",
),
)
} else if key == "connect-src" {
Some(
make_directive_spec(
"connect-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls fetch, websocket, EventSource, beacon, and XHR endpoints.",
),
)
} else if key == "default-src" {
Some(
make_directive_spec(
"default-src",
DirectiveFetch,
None,
false,
"CSP1",
"Provides fallback sources for fetch directives.",
),
)
} else if key == "font-src" {
Some(
make_directive_spec(
"font-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls font loading sources.",
),
)
} else if key == "form-action" {
Some(
make_directive_spec(
"form-action",
DirectiveNavigation,
None,
false,
"CSP2",
"Restricts form submission targets.",
),
)
} else if key == "frame-ancestors" {
Some(
make_directive_spec(
"frame-ancestors",
DirectiveNavigation,
None,
false,
"CSP2",
"Restricts which pages may embed the protected resource.",
),
)
} else if key == "frame-src" {
Some(
make_directive_spec(
"frame-src",
DirectiveFetch,
Some("child-src"),
false,
"CSP1/CSP3",
"Controls frame and iframe sources.",
),
)
} else if key == "img-src" {
Some(
make_directive_spec(
"img-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls image and favicon sources.",
),
)
} else if key == "manifest-src" {
Some(
make_directive_spec(
"manifest-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP3",
"Controls web app manifest loading.",
),
)
} else if key == "media-src" {
Some(
make_directive_spec(
"media-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls audio, video, and text track sources.",
),
)
} else if key == "object-src" {
Some(
make_directive_spec(
"object-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls plugin-like object, embed, and applet sources.",
),
)
} else if key == "prefetch-src" {
Some(
make_directive_spec(
"prefetch-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP3",
"Controls prefetch and prerender hints.",
),
)
} else if key == "script-src" {
Some(
make_directive_spec(
"script-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls script execution and loading.",
),
)
} else if key == "script-src-elem" {
Some(
make_directive_spec(
"script-src-elem",
DirectiveFetch,
Some("script-src"),
false,
"CSP3",
"Controls script elements.",
),
)
} else if key == "script-src-attr" {
Some(
make_directive_spec(
"script-src-attr",
DirectiveFetch,
Some("script-src"),
false,
"CSP3",
"Controls inline event handler attributes.",
),
)
} else if key == "style-src" {
Some(
make_directive_spec(
"style-src",
DirectiveFetch,
Some("default-src"),
false,
"CSP1",
"Controls stylesheet sources and inline style behavior.",
),
)
} else if key == "style-src-elem" {
Some(
make_directive_spec(
"style-src-elem",
DirectiveFetch,
Some("style-src"),
false,
"CSP3",
"Controls stylesheet elements.",
),
)
} else if key == "style-src-attr" {
Some(
make_directive_spec(
"style-src-attr",
DirectiveFetch,
Some("style-src"),
false,
"CSP3",
"Controls inline style attributes.",
),
)
} else if key == "worker-src" {
Some(
make_directive_spec(
"worker-src",
DirectiveFetch,
Some("child-src"),
false,
"CSP3",
"Controls worker, shared worker, and service worker sources.",
),
)
} else if key == "navigate-to" {
Some(
make_directive_spec(
"navigate-to",
DirectiveNavigation,
None,
false,
"CSP3",
"Restricts top-level navigations initiated by the protected page.",
),
)
} else if key == "report-uri" {
Some(
make_directive_spec(
"report-uri",
DirectiveReporting,
None,
true,
"CSP1",
"Defines legacy violation reporting endpoints.",
),
)
} else if key == "report-to" {
Some(
make_directive_spec(
"report-to",
DirectiveReporting,
None,
false,
"CSP3",
"Defines a Reporting API endpoint group for violations.",
),
)
} else if key == "sandbox" {
Some(
make_directive_spec(
"sandbox",
DirectiveSandbox,
None,
false,
"CSP1",
"Applies sandbox flags to the protected resource.",
),
)
} else if key == "upgrade-insecure-requests" {
Some(
make_directive_spec(
"upgrade-insecure-requests",
DirectiveUpgrade,
None,
false,
"CSP2",
"Asks user agents to upgrade HTTP subresource requests to HTTPS.",
),
)
} else if key == "block-all-mixed-content" {
Some(
make_directive_spec(
"block-all-mixed-content",
DirectiveDeprecated,
None,
false,
"CSP2",
"Deprecated mixed-content blocking directive.",
),
)
} else if key == "require-sri-for" {
Some(
make_directive_spec(
"require-sri-for",
DirectiveDeprecated,
None,
false,
"CSP3 removed",
"Removed directive for requiring subresource integrity.",
),
)
} else if key == "plugin-types" {
Some(
make_directive_spec(
"plugin-types",
DirectiveDeprecated,
None,
false,
"CSP2",
"Deprecated plugin MIME type restriction.",
),
)
} else if key == "referrer" {
Some(
make_directive_spec(
"referrer",
DirectiveDeprecated,
None,
false,
"CSP2",
"Deprecated referrer directive replaced by Referrer-Policy.",
),
)
} else if key == "reflected-xss" {
Some(
make_directive_spec(
"reflected-xss",
DirectiveDeprecated,
None,
false,
"CSP1",
"Deprecated reflected XSS filter directive.",
),
)
} else if key == "require-trusted-types-for" {
Some(
make_directive_spec(
"require-trusted-types-for",
DirectiveTrustedTypes,
None,
false,
"CSP3",
"Requires Trusted Types for selected DOM injection sinks.",
),
)
} else if key == "trusted-types" {
Some(
make_directive_spec(
"trusted-types",
DirectiveTrustedTypes,
None,
false,
"CSP3",
"Restricts Trusted Types policy names.",
),
)
} else if key == "webrtc" {
Some(
make_directive_spec(
"webrtc",
DirectiveExperimental,
None,
false,
"CSP experimental",
"Experimental control for WebRTC IP handling behavior.",
),
)
} else if key == "fenced-frame-src" {
Some(
make_directive_spec(
"fenced-frame-src",
DirectiveExperimental,
Some("child-src"),
false,
"CSP experimental",
"Experimental fenced frame source control.",
),
)
} else {
None
}
}
///|
pub fn is_known_directive_name(name : StringView) -> Bool {
directive_spec(name) is Some(_)
}
///|
pub fn directive_category(name : StringView) -> DirectiveCategory {
match directive_spec(name) {
Some(spec) => spec.category
None => DirectiveOther
}
}
///|
pub fn directive_fallback_parent(name : StringView) -> String? {
match directive_spec(name) {
Some(spec) => spec.fallback
None => None
}
}
///|
pub fn known_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("base-uri")
names.push("child-src")
names.push("connect-src")
names.push("default-src")
names.push("font-src")
names.push("form-action")
names.push("frame-ancestors")
names.push("frame-src")
names.push("img-src")
names.push("manifest-src")
names.push("media-src")
names.push("object-src")
names.push("prefetch-src")
names.push("script-src")
names.push("script-src-elem")
names.push("script-src-attr")
names.push("style-src")
names.push("style-src-elem")
names.push("style-src-attr")
names.push("worker-src")
names.push("navigate-to")
names.push("report-uri")
names.push("report-to")
names.push("sandbox")
names.push("upgrade-insecure-requests")
names.push("block-all-mixed-content")
names.push("require-sri-for")
names.push("plugin-types")
names.push("referrer")
names.push("reflected-xss")
names.push("require-trusted-types-for")
names.push("trusted-types")
names.push("webrtc")
names.push("fenced-frame-src")
names
}
///|
pub fn fetch_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("child-src")
names.push("connect-src")
names.push("default-src")
names.push("font-src")
names.push("frame-src")
names.push("img-src")
names.push("manifest-src")
names.push("media-src")
names.push("object-src")
names.push("prefetch-src")
names.push("script-src")
names.push("script-src-elem")
names.push("script-src-attr")
names.push("style-src")
names.push("style-src-elem")
names.push("style-src-attr")
names.push("worker-src")
names.push("fenced-frame-src")
names
}
///|
pub fn document_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("base-uri")
names.push("sandbox")
names.push("trusted-types")
names.push("require-trusted-types-for")
names
}
///|
pub fn navigation_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("form-action")
names.push("frame-ancestors")
names.push("navigate-to")
names
}
///|
pub fn reporting_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("report-uri")
names.push("report-to")
names
}
///|
pub fn deprecated_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("block-all-mixed-content")
names.push("require-sri-for")
names.push("plugin-types")
names.push("referrer")
names.push("reflected-xss")
names
}
///|
pub fn hardening_directive_names() -> Array[String] {
let names : Array[String] = []
names.push("default-src")
names.push("object-src")
names.push("base-uri")
names.push("frame-ancestors")
names.push("form-action")
names.push("upgrade-insecure-requests")
names
}
///|
pub fn known_directive_specs() -> Array[DirectiveSpec] {
let specs : Array[DirectiveSpec] = []
for name in known_directive_names() {
match directive_spec(name[:]) {
Some(spec) => specs.push(spec)
None => ()
}
}
specs
}
///|
pub fn known_directives_by_category(
category : DirectiveCategory,
) -> Array[DirectiveSpec] {
known_directive_specs().filter(spec => spec.category == category)
}
///|
pub fn known_directive_catalog_report() -> String {
let lines : Array[String] = []
for spec in known_directive_specs() {
lines.push(spec.summary())
}
lines.join("\n")
}
///|
pub fn Policy::unknown_directives(self : Policy) -> Array[Directive] {
self.directives.filter(directive => {
!is_known_directive_name(directive.name[:])
})
}
///|
pub fn Policy::deprecated_directives(self : Policy) -> Array[Directive] {
self.directives.filter(directive => {
match directive_spec(directive.name[:]) {
Some(spec) => spec.is_deprecated()
None => false
}
})
}
///|
pub fn Policy::experimental_directives(self : Policy) -> Array[Directive] {
self.directives.filter(directive => {
match directive_spec(directive.name[:]) {
Some(spec) => spec.is_experimental()
None => false
}
})
}
///|
pub fn Policy::directives_in_category(
self : Policy,
category : DirectiveCategory,
) -> Array[Directive] {
self.directives.filter(directive => {
directive_category(directive.name[:]) == category
})
}
///|
pub fn Policy::fetch_directives(self : Policy) -> Array[Directive] {
self.directives_in_category(DirectiveFetch)
}
///|
pub fn Policy::document_directives(self : Policy) -> Array[Directive] {
self.directives_in_category(DirectiveDocument)
}
///|
pub fn Policy::navigation_directives(self : Policy) -> Array[Directive] {
self.directives_in_category(DirectiveNavigation)
}
///|
pub fn Policy::reporting_directives(self : Policy) -> Array[Directive] {
self.directives_in_category(DirectiveReporting)
}
///|
pub fn Policy::directive_catalog_summary(self : Policy) -> String {
let lines : Array[String] = []
lines.push(
"known=\{self.directives.length() - self.unknown_directives().length()}",
)
lines.push("unknown=\{self.unknown_directives().length()}")
lines.push("deprecated=\{self.deprecated_directives().length()}")
lines.push("experimental=\{self.experimental_directives().length()}")
lines.push("fetch=\{self.fetch_directives().length()}")
lines.push("document=\{self.document_directives().length()}")
lines.push("navigation=\{self.navigation_directives().length()}")
lines.push("reporting=\{self.reporting_directives().length()}")
lines.join("\n")
}