Skip to content

Commit 5c4664e

Browse files
committed
first checkin
1 parent b841f38 commit 5c4664e

28 files changed

Lines changed: 6520 additions & 0 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2023 Ronald Brill.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.csp;
16+
17+
import java.util.regex.Pattern;
18+
19+
public final class Constants {
20+
// https://tools.ietf.org/html/rfc3986#section-3.1
21+
public static final String schemePart = "[a-zA-Z][a-zA-Z0-9+\\-.]*";
22+
public static final Pattern schemePattern = Pattern.compile("^(?<scheme>" + Constants.schemePart + ":)");
23+
24+
// https://tools.ietf.org/html/rfc7230#section-3.2.6
25+
public static final Pattern rfc7230TokenPattern = Pattern.compile("^[!#$%&'*+\\-.^_`|~0-9a-zA-Z]+$");
26+
27+
// RFC 2045 appendix A: productions of type and subtype
28+
// https://tools.ietf.org/html/rfc2045#section-5.1
29+
public static final Pattern mediaTypePattern
30+
= Pattern.compile("^(?<type>[a-zA-Z0-9!#$%^&*\\-_+{}|'.`~]+)/(?<subtype>[a-zA-Z0-9!#$%^&*\\-_+{}|'.`~]+)$");
31+
public static final Pattern unquotedKeywordPattern
32+
= Pattern.compile("^(?:self|unsafe-inline|unsafe-eval|unsafe-redirect|none|strict-dynamic|unsafe-hashes|report-sample|unsafe-allow-redirects)$");
33+
34+
// port-part constants
35+
public static final int WILDCARD_PORT = -200;
36+
public static final int EMPTY_PORT = -1;
37+
38+
// https://w3c.github.io/webappsec-csp/#grammardef-host-part
39+
private static final String hostPart = "\\*|(?:\\*\\.)?[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*";
40+
41+
// https://w3c.github.io/webappsec-csp/#grammardef-port-part
42+
private static final String portPart = ":(?:[0-9]+|\\*)";
43+
private static final String unreserved = "[a-zA-Z0-9\\-._~]";
44+
private static final String pctEncoded = "%[a-fA-F0-9]{2}";
45+
private static final String subDelims = "[!$&'()*+,;=]";
46+
private static final String pchar = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|[:@])";
47+
48+
// https://w3c.github.io/webappsec-csp/#grammardef-path-part
49+
// XXX: divergence from spec; uses path-abempty from RFC3986 instead of path
50+
private static final String pathPart = "(?:/" + pchar + "*)+";
51+
52+
private static final String queryFragmentPart = "(?:\\?[^#]*)?(?:#.*)?";
53+
54+
public static final Pattern hostSourcePattern = Pattern.compile(
55+
"^(?<scheme>" + schemePart + "://)?(?<host>" + hostPart + ")(?<port>" + portPart + ")?(?<path>" + pathPart
56+
+ ")?" + queryFragmentPart + "$");
57+
// public static final Pattern relativeReportUriPattern =
58+
// Pattern.compile("^(?<path>" + pathPart + ")" + queryFragmentPart + "$");
59+
public static final Pattern IPv4address = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
60+
public static final Pattern IPV6loopback = Pattern.compile("^[0:]+:1$");
61+
public static final String IPv6address = "(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)";
62+
public static final Pattern IPv6addressWithOptionalBracket = Pattern.compile("^(?:\\[" + IPv6address + "\\]|" + IPv6address + ")$");
63+
64+
// https://infra.spec.whatwg.org/#ascii-whitespace
65+
public static final String WHITESPACE_CHARS = "\t\n\f\r ";
66+
67+
private Constants() {
68+
// Utility class
69+
}
70+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2023 Ronald Brill.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.csp;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.Locale;
21+
import java.util.function.Predicate;
22+
import java.util.regex.Pattern;
23+
24+
public class Directive {
25+
public static final Predicate<String> IS_DIRECTIVE_NAME = Pattern.compile("^[A-Za-z0-9\\-]+$").asPredicate();
26+
public static final Predicate<String> containsNonDirectiveCharacter = Pattern.compile("[" + Constants.WHITESPACE_CHARS + ",;]").asPredicate();
27+
private List<String> values_;
28+
29+
protected void addValue(final String value) {
30+
Policy.enforceAscii(value);
31+
if (containsNonDirectiveCharacter.test(value)) {
32+
throw new IllegalArgumentException("values must not contain whitespace, ',', or ';'");
33+
}
34+
if (value.isEmpty()) {
35+
throw new IllegalArgumentException("values must not be empty");
36+
}
37+
this.values_.add(value);
38+
}
39+
40+
public List<String> getValues() {
41+
return Collections.unmodifiableList(this.values_);
42+
}
43+
44+
protected Directive(final List<String> values) {
45+
this.values_ = new ArrayList<>();
46+
for (final String value : values) {
47+
// We use this API so we get the validity checks
48+
this.addValue(value);
49+
}
50+
}
51+
52+
protected void removeValueIgnoreCase(final String value) {
53+
final String lowcaseValue = value.toLowerCase(Locale.ENGLISH);
54+
// Could we use some fancy data structure to avoid the linear indexing here?
55+
// Yes, probably. But in practice these are short lists, and iterating them is not that expensive.
56+
final ArrayList<String> copy = new ArrayList<>(this.values_.size());
57+
for (final String existing : this.values_) {
58+
if (!existing.toLowerCase(Locale.ENGLISH).equals(lowcaseValue)) {
59+
copy.add(existing);
60+
}
61+
}
62+
this.values_ = copy;
63+
}
64+
65+
@FunctionalInterface
66+
public interface DirectiveErrorConsumer {
67+
void add(Policy.Severity severity, String message,
68+
int valueIndex); // index = -1 for errors not pertaining to a value
69+
70+
DirectiveErrorConsumer ignored = (severity, message, valueIndex) -> { };
71+
}
72+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright (c) 2023 Ronald Brill.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.csp;
16+
17+
public enum FetchDirectiveKind {
18+
ChildSrc("child-src"),
19+
ConnectSrc("connect-src"),
20+
DefaultSrc("default-src"),
21+
FontSrc("font-src"),
22+
FrameSrc("frame-src"),
23+
ImgSrc("img-src"),
24+
ManifestSrc("manifest-src"),
25+
MediaSrc("media-src"),
26+
ObjectSrc("object-src"),
27+
PrefetchSrc("prefetch-src"),
28+
ScriptSrcAttr("script-src-attr"),
29+
ScriptSrc("script-src"),
30+
ScriptSrcElem("script-src-elem"),
31+
StyleSrcAttr("style-src-attr"),
32+
StyleSrc("style-src"),
33+
StyleSrcElem("style-src-elem"),
34+
WorkerSrc("worker-src");
35+
36+
private final String repr_;
37+
38+
FetchDirectiveKind(final String repr) {
39+
this.repr_ = repr;
40+
}
41+
42+
public String getRepr() {
43+
return repr_;
44+
}
45+
46+
// returns null if not matched
47+
public static FetchDirectiveKind fromString(final String name) {
48+
switch (name) {
49+
case "child-src":
50+
return ChildSrc;
51+
case "connect-src":
52+
return ConnectSrc;
53+
case "default-src":
54+
return DefaultSrc;
55+
case "font-src":
56+
return FontSrc;
57+
case "frame-src":
58+
return FrameSrc;
59+
case "img-src":
60+
return ImgSrc;
61+
case "manifest-src":
62+
return ManifestSrc;
63+
case "media-src":
64+
return MediaSrc;
65+
case "object-src":
66+
return ObjectSrc;
67+
case "prefetch-src":
68+
return PrefetchSrc;
69+
case "script-src-attr":
70+
return ScriptSrcAttr;
71+
case "script-src":
72+
return ScriptSrc;
73+
case "script-src-elem":
74+
return ScriptSrcElem;
75+
case "style-src-attr":
76+
return StyleSrcAttr;
77+
case "style-src":
78+
return StyleSrc;
79+
case "style-src-elem":
80+
return StyleSrcElem;
81+
case "worker-src":
82+
return WorkerSrc;
83+
default:
84+
return null;
85+
}
86+
}
87+
88+
// https://w3c.github.io/webappsec-csp/#directive-fallback-list
89+
// Note the oddity that worker-src falls back to child-src then script-src
90+
// then directive-src, but frame-src falls back to child-src then directly default-src
91+
// Also note that `script-src` falls back to `default-src` for "unsafe-eval", but this
92+
// is done manually in prose rather than in this table
93+
// (in https://w3c.github.io/webappsec-csp/#can-compile-strings )
94+
// It is included here only for completeness
95+
private static final FetchDirectiveKind[] ScriptSrcFallback
96+
= new FetchDirectiveKind[] {ScriptSrc, DefaultSrc};
97+
private static final FetchDirectiveKind[] ScriptSrcElemFallback
98+
= new FetchDirectiveKind[] {ScriptSrcElem, ScriptSrc, DefaultSrc};
99+
private static final FetchDirectiveKind[] ScriptSrcAttrFallback
100+
= new FetchDirectiveKind[] {ScriptSrcAttr, ScriptSrc, DefaultSrc};
101+
private static final FetchDirectiveKind[] StyleSrcFallback
102+
= new FetchDirectiveKind[] {StyleSrc, DefaultSrc};
103+
private static final FetchDirectiveKind[] StyleSrcElemFallback
104+
= new FetchDirectiveKind[] {StyleSrcElem, StyleSrc, DefaultSrc};
105+
private static final FetchDirectiveKind[] StyleSrcAttrFallback
106+
= new FetchDirectiveKind[] {StyleSrcAttr, StyleSrc, DefaultSrc};
107+
private static final FetchDirectiveKind[] WorkerSrcFallback
108+
= new FetchDirectiveKind[] {WorkerSrc, ChildSrc, ScriptSrc, DefaultSrc};
109+
private static final FetchDirectiveKind[] ConnectSrcFallback
110+
= new FetchDirectiveKind[] {ConnectSrc, DefaultSrc};
111+
private static final FetchDirectiveKind[] ManifestSrcFallback
112+
= new FetchDirectiveKind[] {ManifestSrc, DefaultSrc};
113+
private static final FetchDirectiveKind[] PrefetchSrcFallback
114+
= new FetchDirectiveKind[] {PrefetchSrc, DefaultSrc};
115+
private static final FetchDirectiveKind[] ObjectSrcFallback
116+
= new FetchDirectiveKind[] {ObjectSrc, DefaultSrc};
117+
private static final FetchDirectiveKind[] FrameSrcFallback
118+
= new FetchDirectiveKind[] {FrameSrc, ChildSrc, DefaultSrc };
119+
private static final FetchDirectiveKind[] MediaSrcFallback
120+
= new FetchDirectiveKind[] {MediaSrc, DefaultSrc };
121+
private static final FetchDirectiveKind[] FontSrcFallback
122+
= new FetchDirectiveKind[] {FontSrc, DefaultSrc };
123+
private static final FetchDirectiveKind[] ImgSrcFallback
124+
= new FetchDirectiveKind[] {ImgSrc, DefaultSrc };
125+
126+
static FetchDirectiveKind[] getFetchDirectiveFallbackList(final FetchDirectiveKind directive) {
127+
switch (directive) {
128+
case ScriptSrc:
129+
return ScriptSrcFallback;
130+
case ScriptSrcElem:
131+
return ScriptSrcElemFallback;
132+
case ScriptSrcAttr:
133+
return ScriptSrcAttrFallback;
134+
case StyleSrc:
135+
return StyleSrcFallback;
136+
case StyleSrcElem:
137+
return StyleSrcElemFallback;
138+
case StyleSrcAttr:
139+
return StyleSrcAttrFallback;
140+
case WorkerSrc:
141+
return WorkerSrcFallback;
142+
case ConnectSrc:
143+
return ConnectSrcFallback;
144+
case ManifestSrc:
145+
return ManifestSrcFallback;
146+
case PrefetchSrc:
147+
return PrefetchSrcFallback;
148+
case ObjectSrc:
149+
return ObjectSrcFallback;
150+
case FrameSrc:
151+
return FrameSrcFallback;
152+
case MediaSrc:
153+
return MediaSrcFallback;
154+
case FontSrc:
155+
return FontSrcFallback;
156+
case ImgSrc:
157+
return ImgSrcFallback;
158+
default:
159+
throw new IllegalArgumentException("Unknown fetch directive " + directive);
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)