summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/reporting/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/reporting/resources')
-rw-r--r--testing/web-platform/tests/reporting/resources/README.md75
-rw-r--r--testing/web-platform/tests/reporting/resources/csp-error.https.sub.html19
-rw-r--r--testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/resources/fail.pngbin0 -> 759 bytes
-rw-r--r--testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html22
-rw-r--r--testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html4
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-report-once.py34
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-report.https.sub.html6
-rw-r--r--testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html15
-rw-r--r--testing/web-platform/tests/reporting/resources/report-helper.js38
-rw-r--r--testing/web-platform/tests/reporting/resources/report.py143
-rw-r--r--testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html15
-rw-r--r--testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html24
-rw-r--r--testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers2
17 files changed, 404 insertions, 0 deletions
diff --git a/testing/web-platform/tests/reporting/resources/README.md b/testing/web-platform/tests/reporting/resources/README.md
new file mode 100644
index 0000000000..b77d1f96b7
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/README.md
@@ -0,0 +1,75 @@
+# Using the common report collector
+
+To send reports to the collector, configure the reporting API to POST reports
+to the collector's URL. This can be same- or cross- origin with the reporting
+document, as the collector will follow the CORS protocol.
+
+The collector supports both CSP Level 2 (report-uri) reports as well as
+Reporting API reports.
+
+A GET request can be used to retrieve stored reports for analysis.
+
+A POST request can be used to clear reports stored in the server.
+
+Sent credentials are stored with the reports, and can be retrieved separately.
+
+CORS Notes:
+* Preflight requests originating from www2.web-platform.test will be rejected.
+ This allows tests to ensure that cross-origin report uploads are not sent when
+ the endpoint does not support CORS.
+
+## Supported GET parameters:
+ `op`: For GET requests, a string indicating the operation to perform (see
+ below for description of supported operations). Defaults to
+ `retrieve_report`.
+
+ `reportID`: A UUID to associate with the reports sent from this document. This
+ can be used to distinguish between reports from multiple documents, and to
+ provide multiple distinct endpoints for a single document. Either `reportID`
+ or `endpoint` must be provided.
+
+ `endpoint`: A string which will be used to generate a UUID to be used as the
+ reportID. Either `reportID` or `endpoint` must be provided.
+
+ `timeout`: The amount of time to wait, in seconds, before responding. Defaults
+ to 0.5s.
+
+ `min_count`: The minimum number of reports to return with the `retrieve_report`
+ operation. If there have been fewer than this many reports received, then an
+ empty report list will be returned instead.
+
+ `retain`: If present, reports will remain in the stash after being retrieved.
+ By default, reports are cleared once retrieved.
+
+### Operations:
+ `retrieve_report`: Returns all reports received so far for this reportID, as a
+ JSON-formatted list. If no reports have been received, an empty list will be
+ returned.
+
+ `retrieve_cookies`: Returns the cookies sent with the most recent reports for
+ this reportID, as a JSON-formatted object.
+
+ `retrieve_count`: Returns the number of POST requests for reports with this
+ reportID so far.
+
+## Supported POST JSON payload:
+
+ `op`: For POST requests, a string indicating the operation to perform (see
+ below for description of supported operations).
+
+ `reportIDs`: A list of `reportID`s, each one a UUID associated with reports stored in the server stash.
+
+### Operations
+`DELETE`: Clear all reports associated with `reportID` listed in `reportIDs` list.
+
+### Example usage:
+```
+# Clear reports on the server.
+fetch('/reporting/resources/report.py', {
+ method: "POST",
+ body: JSON.stringify({
+ op: "DELETE",
+ reportIDs: [...] # a list of reportID
+ })
+});
+```
diff --git a/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html
new file mode 100644
index 0000000000..c883051945
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Notify parent on load and generate a CSP error</title>
+</head>
+<body>
+ <script>
+ addEventListener('load', () => {
+ // Alert the parent frame that this frame has loaded.
+ parent.postMessage('Loaded','*');
+
+ // Trigger a CSP error, which should generate a report.
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ document.body.appendChild(img);
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers
new file mode 100644
index 0000000000..00b60a2d0c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="https://{{domains[www]}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/resources/fail.png b/testing/web-platform/tests/reporting/resources/fail.png
new file mode 100644
index 0000000000..b593380333
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/fail.png
Binary files differ
diff --git a/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html
new file mode 100644
index 0000000000..9887769128
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug test page 1</title>
+</head>
+<body>
+<h1>Bug test page 1</h1>
+<!-- This image will cause a CSP violation, which will trigger an immediate report -->
+<img src="missing1.png">
+<script>
+setTimeout(()=>{
+ var img = document.createElement('img');
+ img.src = "missing2.png";
+ // Appending this image will cause a second CSP violation, which will trigger
+ // a second report.
+ document.body.appendChild(img);
+ location.href = "second-csp-report.https.sub.html";
+}, 1);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..2f5eeb4d8c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp="/reporting/resources/report.py?pipe=trickle(d1)&endpoint=csp1"
+Content-Security-Policy: img-src 'none'; report-to csp
diff --git a/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html
new file mode 100644
index 0000000000..7adec0f309
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Generate CSP reports </title>
+<img src='/reporting/resources/fail.png'>
diff --git a/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..44242c3c89
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to default
diff --git a/testing/web-platform/tests/reporting/resources/generate-report-once.py b/testing/web-platform/tests/reporting/resources/generate-report-once.py
new file mode 100644
index 0000000000..163846a4b9
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-report-once.py
@@ -0,0 +1,34 @@
+def main(request, response):
+ # Handle CORS preflight requests
+ if request.method == u'OPTIONS':
+ # Always reject preflights for one subdomain
+ if b"www2" in request.headers[b"Origin"]:
+ return (400, [], u"CORS preflight rejected for www2")
+ return [
+ (b"Content-Type", b"text/plain"),
+ (b"Access-Control-Allow-Origin", b"*"),
+ (b"Access-Control-Allow-Methods", b"get"),
+ (b"Access-Control-Allow-Headers", b"Content-Type"),
+ ], u"CORS allowed"
+
+ if b"reportID" in request.GET:
+ key = request.GET.first(b"reportID")
+ else:
+ response.status = 400
+ return "reportID parameter is required."
+
+ with request.server.stash.lock:
+ visited = request.server.stash.take(key=key)
+ if visited is None:
+ response.headers.set("Reporting-Endpoints",
+ b"default=\"/reporting/resources/report.py?reportID=%s\"" % key)
+ request.server.stash.put(key=key, value=True)
+
+ response.content = b"""
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Generate deprecation report</title>
+<script>
+ webkitRequestAnimationFrame(() => {});
+</script>
+"""
diff --git a/testing/web-platform/tests/reporting/resources/generate-report.https.sub.html b/testing/web-platform/tests/reporting/resources/generate-report.https.sub.html
new file mode 100644
index 0000000000..f1f4e96300
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-report.https.sub.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Generate deprecation report</title>
+<script>
+ webkitRequestAnimationFrame(() => {});
+</script>
diff --git a/testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html b/testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html
new file mode 100644
index 0000000000..0dd26ecc2b
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Utility page which embeds a reporting page on the chosen host</title>
+</head>
+<body>
+ <script>
+ const searchParams = new URLSearchParams(window.location.search);
+ const host = searchParams.get('host') || "{{hosts[][]}}";
+ const frame = document.createElement('iframe');
+ frame.src=`https://${host}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html`;
+ document.body.appendChild(frame);
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/reporting/resources/report-helper.js b/testing/web-platform/tests/reporting/resources/report-helper.js
new file mode 100644
index 0000000000..213a635c49
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/report-helper.js
@@ -0,0 +1,38 @@
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+async function pollReports(endpoint, id, min_count) {
+ const res = await fetch(`${endpoint}?reportID=${id}${min_count ? `&min_count=${min_count}` : ''}`, { cache: 'no-store' });
+ const reports = [];
+ if (res.status === 200) {
+ for (const report of await res.json()) {
+ reports.push(report);
+ }
+ }
+ return reports;
+}
+
+async function pollCookies(endpoint, id) {
+ const res = await fetch(`${endpoint}?reportID=${id}&op=retrieve_cookies`, { cache: 'no-store' });
+ const dict = await res.json();
+ if (dict.reportCookies == 'None')
+ return {};
+ return dict.reportCookies;
+}
+
+async function pollNumResults(endpoint, id) {
+ const res = await fetch(`${endpoint}?reportID=${id}&op=retrieve_count`, { cache: 'no-store' });
+ const dict = await res.json();
+ if (dict.report_count == 'None')
+ return 0;
+ return dict.report_count;
+}
+
+function checkReportExists(reports, type, url) {
+ for (const report of reports) {
+ if (report.type !== type) continue;
+ if (report.body.documentURL == url || report.body.sourceFile === url) return true;
+ }
+ assert_unreached(`A report of ${type} from ${url} is not found.`);
+}
diff --git a/testing/web-platform/tests/reporting/resources/report.py b/testing/web-platform/tests/reporting/resources/report.py
new file mode 100644
index 0000000000..b5ee0c07d8
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/report.py
@@ -0,0 +1,143 @@
+import time
+import json
+import re
+import uuid
+
+from wptserve.utils import isomorphic_decode
+
+
+def retrieve_from_stash(request, key, timeout, default_value, min_count=None, retain=False):
+ """Retrieve the set of reports for a given report ID.
+
+ This will extract either the set of reports, credentials, or request count
+ from the stash (depending on the key passed in) and return it encoded as JSON.
+
+ When retrieving reports, this will not return any reports until min_count
+ reports have been received.
+
+ If timeout seconds elapse before the requested data can be found in the stash,
+ or before at least min_count reports are received, default_value will be
+ returned instead."""
+ t0 = time.time()
+ while time.time() - t0 < timeout:
+ time.sleep(0.5)
+ with request.server.stash.lock:
+ value = request.server.stash.take(key=key)
+ if value is not None:
+ have_sufficient_reports = (
+ min_count is None or len(value) >= min_count)
+ if retain or not have_sufficient_reports:
+ request.server.stash.put(key=key, value=value)
+ if have_sufficient_reports:
+ return json.dumps(value)
+
+ return default_value
+
+
+def main(request, response):
+ # Handle CORS preflight requests
+ if request.method == u'OPTIONS':
+ # Always reject preflights for one subdomain
+ if b"www2" in request.headers[b"Origin"]:
+ return (400, [], u"CORS preflight rejected for www2")
+ return [
+ (b"Content-Type", b"text/plain"),
+ (b"Access-Control-Allow-Origin", b"*"),
+ (b"Access-Control-Allow-Methods", b"post"),
+ (b"Access-Control-Allow-Headers", b"Content-Type"),
+ ], u"CORS allowed"
+
+ # Delete reports as requested
+ if request.method == u'POST':
+ body = json.loads(request.body)
+ if (isinstance(body, dict) and "op" in body):
+ if body["op"] == "DELETE" and "reportIDs" in body:
+ with request.server.stash.lock:
+ for key in body["reportIDs"]:
+ request.server.stash.take(key=key)
+ return "reports cleared"
+ response.status = 400
+ return "op parameter value not recognized"
+
+ if b"reportID" in request.GET:
+ key = request.GET.first(b"reportID")
+ elif b"endpoint" in request.GET:
+ key = uuid.uuid5(uuid.NAMESPACE_OID, isomorphic_decode(
+ request.GET[b'endpoint'])).urn.encode('ascii')[9:]
+ else:
+ response.status = 400
+ return "Either reportID or endpoint parameter is required."
+
+ # Cookie and count keys are derived from the report ID.
+ cookie_key = re.sub(b'^....', b'cccc', key)
+ count_key = re.sub(b'^....', b'dddd', key)
+
+ if request.method == u'GET':
+ try:
+ timeout = float(request.GET.first(b"timeout"))
+ except:
+ timeout = 0.5
+ try:
+ min_count = int(request.GET.first(b"min_count"))
+ except:
+ min_count = 1
+ retain = (b"retain" in request.GET)
+
+ op = request.GET.first(b"op", b"")
+ if op in (b"retrieve_report", b""):
+ return [(b"Content-Type", b"application/json")], retrieve_from_stash(request, key, timeout, u'[]', min_count, retain)
+
+ if op == b"retrieve_cookies":
+ return [(b"Content-Type", b"application/json")], u"{ \"reportCookies\" : " + str(retrieve_from_stash(request, cookie_key, timeout, u"\"None\"")) + u"}"
+
+ if op == b"retrieve_count":
+ return [(b"Content-Type", b"application/json")], u"{ \"report_count\": %s }" % retrieve_from_stash(request, count_key, timeout, 0)
+
+ response.status = 400
+ return "op parameter value not recognized."
+
+ # Save cookies.
+ if len(request.cookies.keys()) > 0:
+ # Convert everything into strings and dump it into a dict.
+ temp_cookies_dict = {}
+ for dict_key in request.cookies.keys():
+ temp_cookies_dict[isomorphic_decode(dict_key)] = str(
+ request.cookies.get_list(dict_key))
+ with request.server.stash.lock:
+ # Clear any existing cookie data for this request before storing new data.
+ request.server.stash.take(key=cookie_key)
+ request.server.stash.put(key=cookie_key, value=temp_cookies_dict)
+
+ # Append new report(s).
+ new_reports = json.loads(request.body)
+
+ # If the incoming report is a CSP report-uri report, then it will be a single
+ # dictionary rather than a list of reports. To handle this case, ensure that
+ # any non-list request bodies are wrapped in a list.
+ if not isinstance(new_reports, list):
+ new_reports = [new_reports]
+
+ for report in new_reports:
+ report[u"metadata"] = {
+ u"content_type": isomorphic_decode(request.headers[b"Content-Type"]),
+ }
+
+ with request.server.stash.lock:
+ reports = request.server.stash.take(key=key)
+ if reports is None:
+ reports = []
+ reports.extend(new_reports)
+ request.server.stash.put(key=key, value=reports)
+
+ # Increment report submission count. This tracks the number of times this
+ # reporting endpoint was contacted, rather than the total number of reports
+ # submitted, which can be seen from the length of the report list.
+ with request.server.stash.lock:
+ count = request.server.stash.take(key=count_key)
+ if count is None:
+ count = 0
+ count += 1
+ request.server.stash.put(key=count_key, value=count)
+
+ # Return acknowledgement report.
+ return [(b"Content-Type", b"text/plain")], b"Recorded report " + request.body
diff --git a/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html
new file mode 100644
index 0000000000..326a0fd0ed
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Generates a CSP violation report and sends it to a same-origin endpoint</title>
+</head>
+<body>
+ <script>
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ document.body.appendChild(img);
+ // Post back to the main frame that the report should have been queued.
+ top.postMessage("done", "*");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..8244fafb22
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html
new file mode 100644
index 0000000000..a34bc653f5
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug test page 2</title>
+</head>
+<body>
+<h1>Bug test page 2</h1>
+<script>
+var img = document.createElement('img');
+img.src = "missing3.png";
+// Appending this image will cause a third CSP violation. The report generated
+// here must not be batched with the reports from the previous page, regardless
+// of whether they have been sent or not.
+document.body.appendChild(img);
+// Give the report handler enough time to finish handling any reports from the
+// previous page (Reports there are delayed by 1 second because of the trickle
+// pipe in the headers in first-csp-report.https.sub.html.sub.headers) and then
+// inform the parent that reports may be checked.
+setTimeout(()=>{
+ parent.postMessage("ready", "*");
+},1250);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..b32c548fef
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp="/reporting/resources/report.py?endpoint=csp2"
+Content-Security-Policy: img-src 'none'; report-to csp