Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ The implementation targets [W3C CSP Level 3](https://www.w3.org/TR/CSP3/). Key s
```
src/main/java/org/htmlunit/csp/
├── Policy.java # Central class: parsing and high-level querying
├── PolicyList.java # List of policies (from comma-separated CSP headers)
├── PolicyList.java # List of policies (AND-query via CspQueries)
├── CspQueries.java # Shared enforcement query API (Policy + PolicyList)
├── CspQueriesInOrigin.java # Origin-bound sugar over CspQueries
├── PolicyInOrigin.java # Policy bound to a specific origin for querying
├── PolicyListInOrigin.java # PolicyList + origin (extends CspQueriesInOrigin)
├── Directive.java # Base class for all directive types
├── FetchDirectiveKind.java # Enum of fetch directives with fallback chains
├── Constants.java # Regex patterns, port constants
Expand Down
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,47 @@ Policy policy = Policy.parseSerializedCSP(policyText, (severity, message, direct
});
```

For a policy from a `<meta http-equiv="Content-Security-Policy" content="...">` element, pass `true` as the third argument so that `frame-ancestors`, `sandbox`, and `report-uri` produce warnings (those directives are ignored when delivered via meta):
For a policy from a [`<meta http-equiv="Content-Security-Policy">`](https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy) element, pass `true` as the third argument so that `frame-ancestors`, `sandbox`, and `report-uri` produce warnings (HTML requires that those directives must not appear in meta `content`, and browsers ignore them):

```java
Policy metaPolicy = Policy.parseSerializedCSP(metaContent, consumer, true);
```

Those three directives remain available via getters for analysis, but `allows*` methods ignore `frame-ancestors` and `sandbox` on meta-delivered policies (matching browser enforce after HTML strips them). See also [CSP3 meta element](https://w3c.github.io/webappsec-csp/#meta-element).

### Multiple Policies

Browsers do not conceptually merge multiple policies into a single policy. They independently evaluate each policy and allow an action only if every policy allows it ([CSP3 multiple policies](https://w3c.github.io/webappsec-csp/#multiple-policies)). Accordingly, this library preserves policies separately and evaluates queries across the collection (`PolicyList`, stopping at the first denying policy). An empty `PolicyList` is unrestricted: all `allows*` methods return `true`.

`Policy` and `PolicyList` share the enforcement query surface via `CspQueries`. Origin-bound helpers (`PolicyInOrigin` / `PolicyListInOrigin`) extend `CspQueriesInOrigin`, so new query methods belong on the interface (and one origin wrapper), not four copy-pasted classes.

```java
// Several Content-Security-Policy header values (each may itself be a comma-separated list)
PolicyList list = PolicyList.ofSerialized(List.of(
"img-src img.example.com",
"img-src more-images.example.com"
), Policy.PolicyListErrorConsumer.ignored);

PolicyListInOrigin bound = new PolicyListInOrigin(list, URI.parseURI("https://example.com").orElseThrow());

boolean allowed = bound.allowsImageFromSource(URI.parseURI("https://img.example.com/a.png").orElseThrow());
// allowed == false — second policy blocks even though first allows

// Or a single comma-separated header value:
PolicyList fromOneHeader = Policy.parseSerializedCSPList(
"img-src 'self', script-src 'none'", Policy.PolicyListErrorConsumer.ignored);
```

Keep Report-Only policies in a separate `PolicyList`. Directive inspection and parse warnings stay per member policy via `getPolicies()`.

`ofSerialized` always parses as header delivery (`deliveredViaMeta = false`). For mixed header + meta, parse each `Policy` with the correct flag, then build the list:

```java
Policy headerPolicy = Policy.parseSerializedCSP(headerValue, consumer, false);
Policy metaPolicy = Policy.parseSerializedCSP(metaContent, consumer, true);
PolicyList enforce = new PolicyList(List.of(headerPolicy, metaPolicy));
```

### Query a Policy

The high-level querying methods allow you to specify whatever relevant information you have. The missing information will be assumed to be worst-case - that is, these methods will return `true` only if any object which matches the provided characteristics would be allowed, regardless of its other characteristics.
Expand Down
281 changes: 281 additions & 0 deletions src/main/java/org/htmlunit/csp/CspQueries.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*
* Copyright (c) 2023-2026 Ronald Brill.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.csp;

import java.util.Optional;

import org.htmlunit.csp.url.URLWithScheme;
import org.htmlunit.csp.value.MediaType;

/**
* Enforcement query API shared by {@link Policy} and {@link PolicyList}.
* <p>
* For a single {@link Policy}, each method applies that policy's rules.
* For a {@link PolicyList}, each method returns {@code true} only if every
* member policy allows the resource (an empty list is unrestricted).
* Semantics of individual checks are documented on {@link Policy}.
* </p>
*
* @author Ronald Brill
* @see Policy
* @see PolicyList
* @see CspQueriesInOrigin
* @since 5.4.0
*/
public interface CspQueries {

/**
* Returns whether an external script is allowed.
*
* @param nonce script nonce, if any
* @param integrity SRI metadata, if any
* @param scriptUrl script URL, if known
* @param parserInserted whether the script is parser-inserted
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsExternalScript
*/
boolean allowsExternalScript(
Optional<String> nonce,
Optional<String> integrity,
Optional<? extends URLWithScheme> scriptUrl,
Optional<Boolean> parserInserted,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether an inline script is allowed.
*
* @param nonce script nonce, if any
* @param source inline script text, if known
* @param parserInserted whether the script is parser-inserted
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsInlineScript
*/
boolean allowsInlineScript(Optional<String> nonce,
Optional<String> source, Optional<Boolean> parserInserted);

/**
* Returns whether a script event-handler attribute is allowed.
*
* @param source attribute text, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsScriptAsAttribute
*/
boolean allowsScriptAsAttribute(Optional<String> source);

/**
* Returns whether eval is allowed.
*
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsEval
*/
boolean allowsEval();

/**
* Returns whether navigation is allowed.
*
* @param to navigation target URL, if known
* @param redirected whether the navigation is a redirect
* @param redirectedTo final URL after redirect, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsNavigation
*/
boolean allowsNavigation(
Optional<? extends URLWithScheme> to,
Optional<Boolean> redirected,
Optional<? extends URLWithScheme> redirectedTo,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a form action is allowed.
*
* @param to form action URL, if known
* @param redirected whether the submission redirects
* @param redirectedTo final URL after redirect, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsFormAction
*/
boolean allowsFormAction(
Optional<? extends URLWithScheme> to,
Optional<Boolean> redirected,
Optional<? extends URLWithScheme> redirectedTo,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a {@code javascript:} URL navigation is allowed.
*
* @param source JavaScript after the {@code javascript:} prefix, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsJavascriptUrlNavigation
*/
boolean allowsJavascriptUrlNavigation(
Optional<String> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether an external stylesheet is allowed.
*
* @param nonce style nonce, if any
* @param styleUrl stylesheet URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsExternalStyle
*/
boolean allowsExternalStyle(
Optional<String> nonce,
Optional<? extends URLWithScheme> styleUrl,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether an inline style is allowed.
*
* @param nonce style nonce, if any
* @param source inline style text, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsInlineStyle
*/
boolean allowsInlineStyle(Optional<String> nonce, Optional<String> source);

/**
* Returns whether a style attribute is allowed.
*
* @param source attribute text, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsStyleAsAttribute
*/
boolean allowsStyleAsAttribute(Optional<String> source);

/**
* Returns whether a frame is allowed.
*
* @param source framed resource URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsFrame
*/
boolean allowsFrame(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a frame ancestor is allowed.
*
* @param source ancestor frame URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsFrameAncestor
*/
boolean allowsFrameAncestor(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a connection is allowed.
*
* @param source connection URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsConnection
*/
boolean allowsConnection(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a font is allowed.
*
* @param source font URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsFont
*/
boolean allowsFont(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether an image is allowed.
*
* @param source image URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsImage
*/
boolean allowsImage(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether an application manifest is allowed.
*
* @param source manifest URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsApplicationManifest
*/
boolean allowsApplicationManifest(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether media is allowed.
*
* @param source media URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsMedia
*/
boolean allowsMedia(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether an object is allowed.
*
* @param source object URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsObject
*/
boolean allowsObject(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a prefetch is allowed.
*
* @param source prefetch URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsPrefetch
*/
boolean allowsPrefetch(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a worker is allowed.
*
* @param source worker URL, if known
* @param origin protected-resource origin, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsWorker
*/
boolean allowsWorker(Optional<? extends URLWithScheme> source,
Optional<? extends URLWithScheme> origin);

/**
* Returns whether a plugin type is allowed.
*
* @param mediaType plugin media type, if known
* @return {@code true} if allowed under this query target's rules
* @see Policy#allowsPlugin
*/
boolean allowsPlugin(Optional<? extends MediaType> mediaType);
}
Loading