diff --git a/components/prism-csp.js b/components/prism-csp.js
index c7229e70a1..8030886a14 100644
--- a/components/prism-csp.js
+++ b/components/prism-csp.js
@@ -4,26 +4,73 @@
* Reference: https://scotthelme.co.uk/csp-cheat-sheet/
*
* Supports the following:
- * - CSP Level 1
- * - CSP Level 2
- * - CSP Level 3
+ * - https://www.w3.org/TR/CSP1/
+ * - https://www.w3.org/TR/CSP2/
+ * - https://www.w3.org/TR/CSP3/
*/
-Prism.languages.csp = {
- 'directive': {
- pattern: /(^|[^-\da-z])(?:base-uri|block-all-mixed-content|(?:child|connect|default|font|frame|img|manifest|media|object|prefetch|script|style|worker)-src|disown-opener|form-action|frame-(?:ancestors|options)|input-protection(?:-(?:clip|selectors))?|navigate-to|plugin-types|policy-uri|referrer|reflected-xss|report-(?:to|uri)|require-sri-for|sandbox|(?:script|style)-src-(?:attr|elem)|upgrade-insecure-requests)(?=[^-\da-z]|$)/i,
- lookbehind: true,
- alias: 'keyword'
- },
- 'safe': {
- // CSP2 hashes and nonces are base64 values. CSP3 accepts both base64 and base64url values.
- // See https://tools.ietf.org/html/rfc4648#section-4
- // See https://tools.ietf.org/html/rfc4648#section-5
- pattern: /'(?:deny|none|report-sample|self|strict-dynamic|top-only|(?:nonce|sha(?:256|384|512))-[-+/\w=]+)'/i,
- alias: 'selector'
- },
- 'unsafe': {
- pattern: /(?:'unsafe-(?:allow-redirects|dynamic|eval|hash-attributes|hashed-attributes|hashes|inline)'|\*)/i,
- alias: 'function'
+(function (Prism) {
+
+ /**
+ * @param {string} source
+ * @returns {RegExp}
+ */
+ function value(source) {
+ return RegExp(/([ \t])/.source + '(?:' + source + ')' + /(?=[\s;]|$)/.source, 'i');
}
-};
+
+ Prism.languages.csp = {
+ 'directive': {
+ pattern: /(^|[\s;])(?:base-uri|block-all-mixed-content|(?:child|connect|default|font|frame|img|manifest|media|object|prefetch|script|style|worker)-src|disown-opener|form-action|frame-(?:ancestors|options)|input-protection(?:-(?:clip|selectors))?|navigate-to|plugin-types|policy-uri|referrer|reflected-xss|report-(?:to|uri)|require-sri-for|sandbox|(?:script|style)-src-(?:attr|elem)|upgrade-insecure-requests)(?=[\s;]|$)/i,
+ lookbehind: true,
+ alias: 'property'
+ },
+ 'scheme': {
+ pattern: value(/[a-z][a-z0-9.+-]*:/.source),
+ lookbehind: true
+ },
+ 'none': {
+ pattern: value(/'none'/.source),
+ lookbehind: true,
+ alias: 'keyword'
+ },
+ 'nonce': {
+ pattern: value(/'nonce-[-+/\w=]+'/.source),
+ lookbehind: true,
+ alias: 'number'
+ },
+ 'hash': {
+ pattern: value(/'sha(?:256|384|512)-[-+/\w=]+'/.source),
+ lookbehind: true,
+ alias: 'number'
+ },
+ 'host': {
+ pattern: value(
+ /[a-z][a-z0-9.+-]*:\/\/[^\s;,']*/.source +
+ '|' +
+ /\*[^\s;,']*/.source +
+ '|' +
+ /[a-z0-9-]+(?:\.[a-z0-9-]+)+(?::[\d*]+)?(?:\/[^\s;,']*)?/.source
+ ),
+ lookbehind: true,
+ alias: 'url',
+ inside: {
+ 'important': /\*/
+ }
+ },
+ 'keyword': [
+ {
+ pattern: value(/'unsafe-[a-z-]+'/.source),
+ lookbehind: true,
+ alias: 'unsafe'
+ },
+ {
+ pattern: value(/'[a-z-]+'/.source),
+ lookbehind: true,
+ alias: 'safe'
+ },
+ ],
+ 'punctuation': /;/
+ };
+
+}(Prism));
diff --git a/components/prism-csp.min.js b/components/prism-csp.min.js
index 6dc0715cc6..c415e34bff 100644
--- a/components/prism-csp.min.js
+++ b/components/prism-csp.min.js
@@ -1 +1 @@
-Prism.languages.csp={directive:{pattern:/(^|[^-\da-z])(?:base-uri|block-all-mixed-content|(?:child|connect|default|font|frame|img|manifest|media|object|prefetch|script|style|worker)-src|disown-opener|form-action|frame-(?:ancestors|options)|input-protection(?:-(?:clip|selectors))?|navigate-to|plugin-types|policy-uri|referrer|reflected-xss|report-(?:to|uri)|require-sri-for|sandbox|(?:script|style)-src-(?:attr|elem)|upgrade-insecure-requests)(?=[^-\da-z]|$)/i,lookbehind:!0,alias:"keyword"},safe:{pattern:/'(?:deny|none|report-sample|self|strict-dynamic|top-only|(?:nonce|sha(?:256|384|512))-[-+/\w=]+)'/i,alias:"selector"},unsafe:{pattern:/(?:'unsafe-(?:allow-redirects|dynamic|eval|hash-attributes|hashed-attributes|hashes|inline)'|\*)/i,alias:"function"}};
\ No newline at end of file
+!function(e){function n(e){return RegExp("([ \t])(?:"+e+")(?=[\\s;]|$)","i")}Prism.languages.csp={directive:{pattern:/(^|[\s;])(?:base-uri|block-all-mixed-content|(?:child|connect|default|font|frame|img|manifest|media|object|prefetch|script|style|worker)-src|disown-opener|form-action|frame-(?:ancestors|options)|input-protection(?:-(?:clip|selectors))?|navigate-to|plugin-types|policy-uri|referrer|reflected-xss|report-(?:to|uri)|require-sri-for|sandbox|(?:script|style)-src-(?:attr|elem)|upgrade-insecure-requests)(?=[\s;]|$)/i,lookbehind:!0,alias:"property"},scheme:{pattern:n("[a-z][a-z0-9.+-]*:"),lookbehind:!0},none:{pattern:n("'none'"),lookbehind:!0,alias:"keyword"},nonce:{pattern:n("'nonce-[-+/\\w=]+'"),lookbehind:!0,alias:"number"},hash:{pattern:n("'sha(?:256|384|512)-[-+/\\w=]+'"),lookbehind:!0,alias:"number"},host:{pattern:n("[a-z][a-z0-9.+-]*://[^\\s;,']*|\\*[^\\s;,']*|[a-z0-9-]+(?:\\.[a-z0-9-]+)+(?::[\\d*]+)?(?:/[^\\s;,']*)?"),lookbehind:!0,alias:"url",inside:{important:/\*/}},keyword:[{pattern:n("'unsafe-[a-z-]+'"),lookbehind:!0,alias:"unsafe"},{pattern:n("'[a-z-]+'"),lookbehind:!0,alias:"safe"}],punctuation:/;/}}();
\ No newline at end of file
diff --git a/tests/languages/csp/directive_no_value_feature.test b/tests/languages/csp/directive_no_value_feature.test
index a45d608292..a9fce90009 100644
--- a/tests/languages/csp/directive_no_value_feature.test
+++ b/tests/languages/csp/directive_no_value_feature.test
@@ -4,7 +4,7 @@ upgrade-insecure-requests;
[
["directive", "upgrade-insecure-requests"],
- ";"
+ ["punctuation", ";"]
]
----------------------------------------------------
diff --git a/tests/languages/csp/directive_with_source_expression_feature.test b/tests/languages/csp/directive_with_source_expression_feature.test
index f618d290ad..747dcc6a3b 100644
--- a/tests/languages/csp/directive_with_source_expression_feature.test
+++ b/tests/languages/csp/directive_with_source_expression_feature.test
@@ -4,21 +4,26 @@ input-protection tolerance=50; input-protection-clip before=60; input-protection
[
["directive", "input-protection"],
- " tolerance=50; ",
+ " tolerance=50",
+ ["punctuation", ";"],
["directive", "input-protection-clip"],
- " before=60; ",
+ " before=60",
+ ["punctuation", ";"],
["directive", "input-protection-selectors"],
- " div; ",
+ " div",
+ ["punctuation", ";"],
["directive", "policy-uri"],
- " https://example.com; ",
+ ["host", ["https://example.com"]],
+ ["punctuation", ";"],
["directive", "script-src"],
- " example.com; ",
+ ["host", ["example.com"]],
+ ["punctuation", ";"],
["directive", "script-src-attr"],
- ["safe", "'none'"],
- "; ",
+ ["none", "'none'"],
+ ["punctuation", ";"],
["directive", "style-src-elem"],
- ["safe", "'none'"],
- ";"
+ ["none", "'none'"],
+ ["punctuation", ";"]
]
----------------------------------------------------
diff --git a/tests/languages/csp/hash_feature.test b/tests/languages/csp/hash_feature.test
new file mode 100644
index 0000000000..fd3b130cd2
--- /dev/null
+++ b/tests/languages/csp/hash_feature.test
@@ -0,0 +1,8 @@
+style-src 'sha256-EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4='
+
+----------------------------------------------------
+
+[
+ ["directive", "style-src"],
+ ["hash", "'sha256-EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4='"]
+]
diff --git a/tests/languages/csp/host_feature.test b/tests/languages/csp/host_feature.test
new file mode 100644
index 0000000000..9bc19e5c12
--- /dev/null
+++ b/tests/languages/csp/host_feature.test
@@ -0,0 +1,46 @@
+default-src trusted.com *.trusted.com;
+img-src *;
+media-src media1.com media2.com;
+script-src userscripts.example.com;
+frame-ancestors https://alice https://bob;
+frame-ancestors https://example.com/;
+
+sandbox allow-scripts;
+
+----------------------------------------------------
+
+[
+ ["directive", "default-src"],
+ ["host", ["trusted.com"]],
+ ["host", [
+ ["important", "*"],
+ ".trusted.com"
+ ]],
+ ["punctuation", ";"],
+
+ ["directive", "img-src"],
+ ["host", [
+ ["important", "*"]
+ ]],
+ ["punctuation", ";"],
+
+ ["directive", "media-src"],
+ ["host", ["media1.com"]],
+ ["host", ["media2.com"]],
+ ["punctuation", ";"],
+
+ ["directive", "script-src"],
+ ["host", ["userscripts.example.com"]],
+ ["punctuation", ";"],
+
+ ["directive", "frame-ancestors"],
+ ["host", ["https://alice"]],
+ ["host", ["https://bob"]],
+ ["punctuation", ";"],
+
+ ["directive", "frame-ancestors"],
+ ["host", ["https://example.com/"]],
+ ["punctuation", ";"],
+
+ ["directive", "sandbox"], " allow-scripts", ["punctuation", ";"]
+]
diff --git a/tests/languages/csp/issue2661.test b/tests/languages/csp/issue2661.test
index 1d25bd01f1..6eb7c5f519 100644
--- a/tests/languages/csp/issue2661.test
+++ b/tests/languages/csp/issue2661.test
@@ -3,7 +3,10 @@ default-src-is-a-fake; fake-default-src;
----------------------------------------------------
[
- "default-src-is-a-fake; fake-default-src;"
+ "default-src-is-a-fake",
+ ["punctuation", ";"],
+ " fake-default-src",
+ ["punctuation", ";"]
]
----------------------------------------------------
diff --git a/tests/languages/csp/keyword_safe_feature.html.test b/tests/languages/csp/keyword_safe_feature.html.test
new file mode 100644
index 0000000000..db3f36f6b2
--- /dev/null
+++ b/tests/languages/csp/keyword_safe_feature.html.test
@@ -0,0 +1,16 @@
+default-src 'report-sample';
+style-src 'self' 'strict-dynamic';
+
+----------------------------------------------------
+
+default-src
+'report-sample'
+;
+style-src
+'self'
+'strict-dynamic'
+;
+
+----------------------------------------------------
+
+Checks for source expressions classified as safe.
diff --git a/tests/languages/csp/keyword_unsafe_feature.html.test b/tests/languages/csp/keyword_unsafe_feature.html.test
new file mode 100644
index 0000000000..227bec6b5e
--- /dev/null
+++ b/tests/languages/csp/keyword_unsafe_feature.html.test
@@ -0,0 +1,20 @@
+navigate-to 'unsafe-allow-redirects';
+script-src 'unsafe-dynamic' 'unsafe-eval' 'unsafe-hash-attributes' 'unsafe-hashed-attributes' 'unsafe-hashes' 'unsafe-inline';
+
+----------------------------------------------------
+
+navigate-to
+'unsafe-allow-redirects'
+;
+script-src
+'unsafe-dynamic'
+'unsafe-eval'
+'unsafe-hash-attributes'
+'unsafe-hashed-attributes'
+'unsafe-hashes'
+'unsafe-inline'
+;
+
+----------------------------------------------------
+
+Checks for source expressions classified as unsafe.
diff --git a/tests/languages/csp/nonce_feature.test b/tests/languages/csp/nonce_feature.test
new file mode 100644
index 0000000000..dc0e4a33ee
--- /dev/null
+++ b/tests/languages/csp/nonce_feature.test
@@ -0,0 +1,9 @@
+style-src 'nonce-yeah';
+
+----------------------------------------------------
+
+[
+ ["directive", "style-src"],
+ ["nonce", "'nonce-yeah'"],
+ ["punctuation", ";"]
+]
diff --git a/tests/languages/csp/none_feature.test b/tests/languages/csp/none_feature.test
new file mode 100644
index 0000000000..7b4e837d31
--- /dev/null
+++ b/tests/languages/csp/none_feature.test
@@ -0,0 +1,8 @@
+sandbox 'none'
+
+----------------------------------------------------
+
+[
+ ["directive", "sandbox"],
+ ["none", "'none'"]
+]
diff --git a/tests/languages/csp/safe_feature.test b/tests/languages/csp/safe_feature.test
deleted file mode 100644
index f61cc32fdd..0000000000
--- a/tests/languages/csp/safe_feature.test
+++ /dev/null
@@ -1,20 +0,0 @@
-default-src 'none' 'report-sample'; style-src 'self' 'strict-dynamic' 'nonce-yeah' 'sha256-EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4=';
-
-----------------------------------------------------
-
-[
- ["directive", "default-src"],
- ["safe", "'none'"],
- ["safe", "'report-sample'"],
- "; ",
- ["directive", "style-src"],
- ["safe", "'self'"],
- ["safe", "'strict-dynamic'"],
- ["safe", "'nonce-yeah'"],
- ["safe", "'sha256-EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4='"],
- ";"
-]
-
-----------------------------------------------------
-
-Checks for source expressions classified as safe.
diff --git a/tests/languages/csp/scheme_feature.test b/tests/languages/csp/scheme_feature.test
new file mode 100644
index 0000000000..5807a8c5dd
--- /dev/null
+++ b/tests/languages/csp/scheme_feature.test
@@ -0,0 +1,10 @@
+default-src https: 'unsafe-inline' 'unsafe-eval'
+
+----------------------------------------------------
+
+[
+ ["directive", "default-src"],
+ ["scheme", "https:"],
+ ["keyword", "'unsafe-inline'"],
+ ["keyword", "'unsafe-eval'"]
+]
diff --git a/tests/languages/csp/unsafe_feature.test b/tests/languages/csp/unsafe_feature.test
deleted file mode 100644
index 758ab58fc0..0000000000
--- a/tests/languages/csp/unsafe_feature.test
+++ /dev/null
@@ -1,21 +0,0 @@
-navigate-to 'unsafe-allow-redirects'; script-src 'unsafe-dynamic' 'unsafe-eval' 'unsafe-hash-attributes' 'unsafe-hashed-attributes' 'unsafe-hashes' 'unsafe-inline';
-
-----------------------------------------------------
-
-[
- ["directive", "navigate-to"],
- ["unsafe", "'unsafe-allow-redirects'"],
- "; ",
- ["directive", "script-src"],
- ["unsafe", "'unsafe-dynamic'"],
- ["unsafe", "'unsafe-eval'"],
- ["unsafe", "'unsafe-hash-attributes'"],
- ["unsafe", "'unsafe-hashed-attributes'"],
- ["unsafe", "'unsafe-hashes'"],
- ["unsafe", "'unsafe-inline'"],
- ";"
-]
-
-----------------------------------------------------
-
-Checks for source expressions classified as unsafe.