Add basic CSP Early Hints WPTs
The test scenario is that:
- Navigate to the test page. The test page sends an Early Hints response
to perform a cross origin preload with a CSP.
- The page sends the final response with a CSP.
- CSPs are configured to allow or disallow cross origin fetches, or
may be absent.
- The test page checks whether the preload is allowed/disallowed.
Currently disallowed -> allowed cases are failing. Subsequent CLs would
resolve the failures.
Bug: 1305896
Change-Id: I3a9ca4a06fe0b1c48f5a93c8881750295510886c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3527873
Reviewed-by: Yutaka Hirano <yhirano@chromium.org>
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#982123}
diff --git a/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js b/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js
new file mode 100644
index 0000000..f61e268
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ const final_policy = "absent";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js b/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js
new file mode 100644
index 0000000..0e1762a
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ const final_policy = "allowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js b/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js
new file mode 100644
index 0000000..3fcd89c
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ const final_policy = "disallowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js b/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js
new file mode 100644
index 0000000..15128ce
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ const final_policy = "absent";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js b/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js
new file mode 100644
index 0000000..ee51e78
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ const final_policy = "allowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js b/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js
new file mode 100644
index 0000000..67b3933
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ const final_policy = "disallowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js b/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js
new file mode 100644
index 0000000..80b563d
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "disallowed";
+ const final_policy = "absent";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js b/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js
new file mode 100644
index 0000000..cfac9b0
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "disallowed";
+ const final_policy = "allowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js b/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js
new file mode 100644
index 0000000..c8a7ca3
--- /dev/null
+++ b/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "disallowed";
+ const final_policy = "disallowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/loading/early-hints/resources/csp-basic-loader.h2.py b/loading/early-hints/resources/csp-basic-loader.h2.py
new file mode 100644
index 0000000..080901e
--- /dev/null
+++ b/loading/early-hints/resources/csp-basic-loader.h2.py
@@ -0,0 +1,47 @@
+import os
+
+
+def _calculate_csp_value(policy, resource_origin):
+ if policy == "absent":
+ return None
+ elif policy == "allowed":
+ return "script-src 'self' 'unsafe-inline' {}".format(resource_origin)
+ elif policy == "disallowed":
+ return "script-src 'self' 'unsafe-inline'"
+ else:
+ return None
+
+
+def handle_headers(frame, request, response):
+ resource_origin = request.GET.first(b"resource-origin").decode()
+
+ # Send a 103 response.
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ early_hints_csp = _calculate_csp_value(
+ request.GET.first(b"early-hints-policy").decode(), resource_origin)
+ if early_hints_csp:
+ early_hints.append((b"content-security-policy", early_hints_csp))
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Send the final response header.
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ final_csp = _calculate_csp_value(
+ request.GET.first(b"final-policy").decode(), resource_origin)
+ if final_csp:
+ response.headers["content-security-policy"] = final_csp
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "csp-basic.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/loading/early-hints/resources/csp-basic.html b/loading/early-hints/resources/csp-basic.html
new file mode 100644
index 0000000..0086711
--- /dev/null
+++ b/loading/early-hints/resources/csp-basic.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+const SEARCH_PARAMS = new URLSearchParams(window.location.search);
+const EARLY_HINTS_POLICY = SEARCH_PARAMS.get("early-hints-policy");
+const FINAL_POLICY = SEARCH_PARAMS.get("final-policy");
+
+function isResourceAllowed() {
+ return FINAL_POLICY === "absent" || FINAL_POLICY === "allowed";
+}
+
+function shouldEarlyHintsPreloadResource() {
+ return EARLY_HINTS_POLICY === "absent" || EARLY_HINTS_POLICY == "allowed";
+}
+
+promise_test(async (t) => {
+ const resource_url = SEARCH_PARAMS.get("resource-url");
+ if (isResourceAllowed()) {
+ await fetchScript(resource_url);
+ const early_hints_preloaded = isPreloadedByEarlyHints(resource_url);
+ const should_early_hints_preload = shouldEarlyHintsPreloadResource();
+ const description = "Early Hints " +
+ (early_hints_preloaded ? "preloaded" : "didn't preload") +
+ " the resource, should " +
+ (should_early_hints_preload ? "" : "not ") + "preload.";
+ assert_equals(early_hints_preloaded, should_early_hints_preload,
+ description);
+ } else {
+ await promise_rejects_js(t, Error, fetchScript(resource_url));
+ }
+}, `Early Hints CSP: Early Hints policy = ${EARLY_HINTS_POLICY}, final response policy = ${FINAL_POLICY}.`);
+</script>
+</body>
diff --git a/loading/early-hints/resources/early-hints-helpers.sub.js b/loading/early-hints/resources/early-hints-helpers.sub.js
index 5c1b219..33f80bc 100644
--- a/loading/early-hints/resources/early-hints-helpers.sub.js
+++ b/loading/early-hints/resources/early-hints-helpers.sub.js
@@ -49,11 +49,12 @@
*
* @param {string} url
*/
- async function fetchScript(url) {
- return new Promise((resolve) => {
+async function fetchScript(url) {
+ return new Promise((resolve, reject) => {
const el = document.createElement("script");
el.src = url;
el.onload = resolve;
+ el.onerror = _ => reject(new Error("Failed to fetch script"));
document.body.appendChild(el);
});
}
@@ -66,6 +67,9 @@
*/
function isPreloadedByEarlyHints(url) {
const entries = performance.getEntriesByName(url);
+ if (entries.length === 0) {
+ return false;
+ }
assert_equals(entries.length, 1);
return entries[0].initiatorType === "early-hints";
}
@@ -87,3 +91,28 @@
const url = new URL(path, window.location);
window.location.replace(url);
}
+
+/**
+ * Navigate to the content security policy basic test. The test page sends an
+ * Early Hints response with a cross origin resource preload. CSP headers are
+ * configured based on the given policies. A policy should be one of the
+ * followings:
+ * "absent" - Do not send Content-Security-Policy header
+ * "allowed" - Set Content-Security-Policy to allow the cross origin preload
+ * "disallowed" - Set Content-Security-Policy to disallow the cross origin preload
+ *
+ * @param {string} early_hints_policy - The policy for the Early Hints response
+ * @param {string} final_policy - The policy for the final response
+ */
+function navigateToContentSecurityPolicyBasicTest(
+ early_hints_policy, final_policy) {
+ const params = new URLSearchParams();
+ params.set("resource-origin", CROSS_ORIGIN);
+ params.set("resource-url",
+ CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("early-hints-policy", early_hints_policy);
+ params.set("final-policy", final_policy);
+
+ const url = "resources/csp-basic-loader.h2.py?" + params.toString();
+ window.location.replace(new URL(url, window.location));
+}