[trusted types] Support reporting and report-only mode.

Bug: 739170
Change-Id: I7c1e4db4f22166692d9fdd90c60d2ef61635033b
Reviewed-on: https://chromium-review.googlesource.com/c/1456012
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#634145}
diff --git a/trusted-types/trusted-types-report-only.tentative.https.html b/trusted-types/trusted-types-report-only.tentative.https.html
new file mode 100644
index 0000000..f33183b
--- /dev/null
+++ b/trusted-types/trusted-types-report-only.tentative.https.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/content-security-policy/support/testharness-helper.js"></script>
+</head>
+<body>
+
+  <!-- Some elements for the tests to act on. -->
+  <a id="anchor" href="#">anchor</a>
+  <div id="div"></div>
+  <script id="script-src" src=""></script>
+  <script id="script"></script>
+
+  <script>
+  // CSP insists the "trusted-types: ..." directives are deliverd as headers
+  // (rather than as "<meta http-equiv" tags). This test assumes the following
+  // headers are set in the .headers file:
+  //
+  //   Content-Security-Policy-Report-Only: trusted-types ...; report-uri ...
+
+  // Return function that returns a promise that resolves on the given
+  // violation report.
+  function expect_violation(filter) {
+    return new Promise((resolve, reject) => {
+      function handler(e) {
+        if (e.originalPolicy.includes(filter)) {
+          document.removeEventListener("securitypolicyviolation", handler);
+          e.stopPropagation();
+          resolve(e);
+        }
+      }
+      document.addEventListener("securitypolicyviolation", handler);
+    });
+  }
+
+  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  const id = x => x;
+  const policy = TrustedTypes.createPolicy("two", {
+    createHTML: id,
+    createScriptURL: id,
+    createURL: id,
+    createScript: id,
+  });
+
+
+  promise_test(t => {
+    let p = expect_violation("trusted-types two");
+    document.getElementById("anchor").href = "#abc";
+    assert_true(document.getElementById("anchor").href.endsWith("#abc"));
+    return p;
+  }, "Trusted Type violation report-only: assign string to url");
+
+  promise_test(t => {
+    let p = expect_violation("trusted-types two");
+    document.getElementById("div").innerHTML = "abc";
+    assert_equals(document.getElementById("div").textContent, "abc");
+    return p;
+  }, "Trusted Type violation report-only: assign string to html");
+
+  promise_test(t => {
+    let p = expect_violation("trusted-types two");
+    document.getElementById("script-src").src = "#";
+    assert_true(document.getElementById("script-src").src.endsWith("#"));
+    return p;
+  }, "Trusted Type violation report-only: assign string to script.src");
+
+  promise_test(t => {
+    let p = expect_violation("trusted-types two");
+    document.getElementById("script").innerHTML = "con" + "sole.log('Hello');";
+    assert_true(document.getElementById("script").textContent.startsWith("consol"));
+    return p;
+  }, "Trusted Type violation report-only: assign string to script content");
+
+  promise_test(t => {
+    let p = expect_violation("trusted-types two");
+    document.getElementById("anchor").href = "#def";
+    return p.then(report => {
+      assert_equals(report.documentURI, "" + window.location);
+      assert_equals(report.disposition, "report");
+      assert_equals(report.effectiveDirective, "trusted-types");
+      assert_equals(report.violatedDirective, "trusted-types");
+      assert_true(report.originalPolicy.startsWith("trusted-types two;"));
+    });
+  }, "Trusted Type violation report: check report contents");
+
+  </script>
+
+</body>
diff --git a/trusted-types/trusted-types-report-only.tentative.https.html.headers b/trusted-types/trusted-types-report-only.tentative.https.html.headers
new file mode 100644
index 0000000..b38cfae
--- /dev/null
+++ b/trusted-types/trusted-types-report-only.tentative.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy-Report-Only: trusted-types two; report-uri /content-security-policy/resources/dummy-report.php
diff --git a/trusted-types/trusted-types-reporting-check-report.https.html b/trusted-types/trusted-types-reporting-check-report.https.html
new file mode 100644
index 0000000..1119077
--- /dev/null
+++ b/trusted-types/trusted-types-reporting-check-report.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <title>Check Trusted Type violation reports</title>
+
+<!-- We assume these HTTP headers are set on the request:
+
+  Set-Cookie: trusted-types-reporting-check-report={{$id:uuid()}}; Path=/trusted-types/
+  Content-Security-Policy-Report-Only: \
+     trusted-types one two; \
+     report-uri ../content-security-policy/support/report.py?op=put&reportID={{$id}}
+-->
+</head>
+<body>
+  <script>
+    TrustedTypes.createPolicy("three", {});
+  </script>
+  <script async defer src='../content-security-policy/support/checkReport.sub.js?reportField=violated-directive&reportValue=trusted-types'></script>
+</body>
+</html>
diff --git a/trusted-types/trusted-types-reporting-check-report.https.html.sub.headers b/trusted-types/trusted-types-reporting-check-report.https.html.sub.headers
new file mode 100644
index 0000000..5830239
--- /dev/null
+++ b/trusted-types/trusted-types-reporting-check-report.https.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: trusted-types-reporting-check-report={{$id:uuid()}}; Path=/trusted-types/
+Content-Security-Policy-Report-Only: trusted-types one two; report-uri ../content-security-policy/support/report.py?op=put&reportID={{$id}}
diff --git a/trusted-types/trusted-types-reporting.tentative.https.html b/trusted-types/trusted-types-reporting.tentative.https.html
new file mode 100644
index 0000000..3074895
--- /dev/null
+++ b/trusted-types/trusted-types-reporting.tentative.https.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/content-security-policy/support/testharness-helper.js"></script>
+</head>
+<body>
+  <script>
+  // CSP insists the "trusted-types: ..." directives are deliverd as headers
+  // (rather than as "<meta http-equiv" tags). This test assumes the following
+  // headers are set in the .headers file:
+  //
+  //   Content-Security-Policy: trusted-types one
+  //   Content-Security-Policy-Report-Only: trusted-types two; report-uri ...
+  //   Content-Security-Policy: plugin-types bla/blubb
+  //
+  // The last rule is there so we can provoke a CSP violation report at will.
+  // The intent is that in order to test that a violation has *not* been thrown
+  // (and without resorting to abominations like timeouts), we force a *another*
+  // CSP violation (by violating the img-src rule) and when that event is
+  // processed we can we sure that an earlier event - if it indeed occurred -
+  // must have already been processed.
+
+  const url = "" + document.location;
+
+  // Return function that returns a promise that resolves on the given
+  // violation report.
+  //
+  // filter_arg - iff function, call it with the event object.
+  //              Else, string-ify and compare against event.originalPolicy.
+  function promise_violation(filter_arg) {
+    return _ => new Promise((resolve, reject) => {
+      function handler(e) {
+        let matches = (filter_arg instanceof Function)
+            ? filter_arg(e)
+            : (e.originalPolicy.includes(filter_arg));
+        if (matches) {
+          document.removeEventListener("securitypolicyviolation", handler);
+          e.stopPropagation();
+          resolve(e);
+        }
+      }
+      document.addEventListener("securitypolicyviolation", handler);
+    });
+  }
+
+  // Like assert_throws, but we don't care about the exact error. We just want
+  // to run the code and continue.
+  function expect_throws(fn) {
+    try { fn(); assert_unreached(); } catch (err) { /* ignore */ }
+  }
+
+  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  const id = x => x;
+  const a_policy = {
+    createHTML: id,
+    createScriptURL: id,
+    createURL: id,
+    createScript: id,
+  };
+
+  // Provoke/wait for a CSP violation, in order to be sure that all previous
+  // CSP violations have been delivered.
+  function promise_flush() {
+    return promise_violation("plugin-types bla/blubb");
+  }
+  function flush() {
+   expect_throws(_ => {
+      var o = document.createElement('object');
+      o.type = "application/x-shockwave-flash";
+      document.body.appendChild(o);
+    });
+  }
+
+
+  promise_test(t => {
+    let p = Promise.resolve()
+        .then(promise_violation("trusted-types one"))
+        .then(promise_violation("trusted-types two"))
+        .then(promise_flush());
+    expect_throws(_ => TrustedTypes.createPolicy("three", a_policy));
+    flush();
+    return p;
+  }, "Trusted Type violation report: creating a forbidden policy.");
+
+  promise_test(t => {
+    let p = promise_flush()();
+    expect_throws(_ => TrustedTypes.createPolicy("two", a_policy));
+    flush();
+    return p;
+  }, "Trusted Type violation report: creating a report-only-forbidden policy.");
+
+  promise_test(t => {
+    let p = Promise.resolve()
+        .then(promise_violation("trusted-types two"))
+        .then(promise_flush());
+    TrustedTypes.createPolicy("one", a_policy, {exposed: true});
+    flush();
+    return p;
+  }, "Trusted Type violation report: creating a forbidden-but-not-reported policy.");
+
+  promise_test(t => {
+    let p = promise_violation("trusted-types two")();
+    expect_throws(_ => document.getElementById("anchor").href = url);
+    return p;
+  }, "Trusted Type violation report: assign string to url");
+
+  promise_test(t => {
+    let p = promise_violation("trusted-types two")();
+    expect_throws(_ => document.getElementById("div").innerHTML = "abc");
+    return p;
+  }, "Trusted Type violation report: assign string to html");
+
+  // In the following tests, we rely on the previous tests to have successfully
+  // created an exposed, no-op policy "one". Let's briefly test that.
+  promise_test(t => {
+    assert_true(!!TrustedTypes.getExposedPolicy("one"));
+    return Promise.resolve();
+  }, "By now, the \"one\" policy should have been created.");
+
+  promise_test(t => {
+    let p = promise_flush()();
+    document.getElementById("anchor").href =
+        TrustedTypes.getExposedPolicy("one").createURL("#");
+    flush();
+    return p;
+  }, "Trusted Type violation report: assign trusted URL to url; no report");
+
+  promise_test(t => {
+    let p = promise_flush()();
+    document.getElementById("div").innerHTML =
+        TrustedTypes.getExposedPolicy("one").createHTML("abc");
+    flush();
+    return p;
+  }, "Trusted Type violation report: assign trusted HTML to html; no report");
+
+  </script>
+
+  <!-- Some elements for the tests to act on. -->
+  <a id="anchor" href="">anchor</a>
+  <div id="div"></div>
+
+</body>
diff --git a/trusted-types/trusted-types-reporting.tentative.https.html.headers b/trusted-types/trusted-types-reporting.tentative.https.html.headers
new file mode 100644
index 0000000..8093b84
--- /dev/null
+++ b/trusted-types/trusted-types-reporting.tentative.https.html.headers
@@ -0,0 +1,4 @@
+Content-Security-Policy: trusted-types one
+Content-Security-Policy-Report-Only: trusted-types two; report-uri /content-security-policy/resources/dummy-report.php
+Content-Security-Policy: plugin-types bla/blubb
+