///|
fn directive_for_kind(kind : ResourceKind) -> Array[String] {
let names : Array[String] = []
match kind {
Script => {
names.push("script-src")
names.push("default-src")
}
Style => {
names.push("style-src")
names.push("default-src")
}
Image => {
names.push("img-src")
names.push("default-src")
}
Font => {
names.push("font-src")
names.push("default-src")
}
Connect => {
names.push("connect-src")
names.push("default-src")
}
Frame => {
names.push("frame-src")
names.push("child-src")
names.push("default-src")
}
Media => {
names.push("media-src")
names.push("default-src")
}
Worker => {
names.push("worker-src")
names.push("child-src")
names.push("script-src")
names.push("default-src")
}
Object => {
names.push("object-src")
names.push("default-src")
}
Manifest => {
names.push("manifest-src")
names.push("default-src")
}
Child => {
names.push("child-src")
names.push("default-src")
}
Base => names.push("base-uri")
Form => names.push("form-action")
Navigate => names.push("navigate-to")
Prefetch => {
names.push("prefetch-src")
names.push("default-src")
}
Other => names.push("default-src")
}
names
}
///|
fn first_effective_directive(
policy : Policy,
kind : ResourceKind,
) -> Directive? {
let names = directive_for_kind(kind)
for name in names {
match get_directive(policy, name) {
Some(directive) => return Some(directive)
None => ()
}
}
None
}
///|
fn source_matches_origin(source : Source, request : ResourceRequest) -> Bool {
let url = normalize_url(request.url)
let origin = lower_ascii(request.origin)
let request_origin = extract_origin(url)
let request_scheme = extract_scheme(url)
match source.kind {
Wildcard => true
Keyword => source.value == "self" && request_origin == lower_ascii(origin)
Scheme => request_scheme == source.value
Host => {
let pattern = extract_host_pattern(source.value)
let host = extract_host_pattern(request_origin)
if starts_with(pattern, "*.") {
ends_with(host, pattern[1:].to_owned())
} else {
host == pattern || request_origin == source.value
}
}
Nonce => false
Hash => false
UnknownSource => false
}
}
///|
fn source_matches_inline(source : Source, request : ResourceRequest) -> Bool {
match source.kind {
Keyword => source.value == "unsafe-inline"
Nonce => request.nonce.length() > 0 && request.nonce == source.value
Hash => false
_ => false
}
}
///|
/// Check whether a resource request is allowed by the policy.
pub fn allows(policy : Policy, request : ResourceRequest) -> Decision {
match first_effective_directive(policy, request.kind) {
None =>
{
allowed: true,
directive: "",
matched_source: "",
reason: "no effective directive controls " +
resource_kind_to_string(request.kind),
}
Some(directive) => {
if directive_has_source(directive, "'none'") {
return {
allowed: false,
directive: directive.name,
matched_source: "'none'",
reason: "directive explicitly contains 'none'",
}
}
for source in directive.sources {
let matched = if request.is_inline {
source_matches_inline(source, request)
} else {
source_matches_origin(source, request)
}
if matched {
return {
allowed: true,
directive: directive.name,
matched_source: source.raw,
reason: "matched source " + source.raw,
}
}
}
{
allowed: false,
directive: directive.name,
matched_source: "",
reason: "no source matched " + request.url,
}
}
}
}