Hide Sec-Fetch-... headers from service worker `fetch` event.

This CL excludes Sec-Fetch-... request headers from the Request object
passes to the service worker's `fetch` event.  This CL also adds WPT
tests that verify that these request headers will be re-added when the
service worker forwards the request back into the network (via implicit
fallback, or via an explicit responseWith+fetch).

Bug: 949997
Change-Id: I04422a1630f62b497961ca1789007f2402148e76
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1611314
Commit-Queue: Ɓukasz Anforowicz <lukasza@chromium.org>
Reviewed-by: Matt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659937}
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index 22e1e2a..5b6b4d6 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -204,6 +204,24 @@
       map, std::forward<Args>(args)...);
 }
 
+bool IsExcludedHeaderForServiceWorkerFetchEvent(
+    const std::string& header_name) {
+  // Excluding Sec-Fetch-... headers as suggested in
+  // https://crbug.com/949997#c4.
+  if (base::StartsWith(header_name, "sec-fetch-",
+                       base::CompareCase::INSENSITIVE_ASCII)) {
+    return true;
+  }
+
+  if (GetContentClient()
+          ->renderer()
+          ->IsExcludedHeaderForServiceWorkerFetchEvent(header_name)) {
+    return true;
+  }
+
+  return false;
+}
+
 }  // namespace
 
 // Holds data that needs to be bound to the worker context on the
@@ -1297,11 +1315,11 @@
   web_request->SetURL(blink::WebURL(request->url));
   web_request->SetMethod(blink::WebString::FromUTF8(request->method));
   for (const auto& pair : request->headers) {
-    if (!GetContentClient()
-             ->renderer()
-             ->IsExcludedHeaderForServiceWorkerFetchEvent(pair.first)) {
-      web_request->SetHeader(blink::WebString::FromUTF8(pair.first),
-                             blink::WebString::FromUTF8(pair.second));
+    const std::string& header_name = pair.first;
+    const std::string& header_value = pair.second;
+    if (!IsExcludedHeaderForServiceWorkerFetchEvent(header_name)) {
+      web_request->SetHeader(blink::WebString::FromUTF8(header_name),
+                             blink::WebString::FromUTF8(header_value));
     }
   }
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch-via-serviceworker--fallback.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch-via-serviceworker--fallback.tentative.https.sub.html
new file mode 100644
index 0000000..e8ec11e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch-via-serviceworker--fallback.tentative.https.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+  This test verifies presence of Sec-Fetch-... request headers on a request
+  handled by a service worker - this test covers the scenario when the service
+  worker doesn't do anything and the request falls back to the network.
+-->
+<meta charset="utf-8"/>
+<link rel="author" href="lukasza@chromium.org" title="Lukasz Anforowicz">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script src=/common/utils.js></script>
+<script>
+    const nonce = token();
+    const key = "fetch-via-serviceworker--fallback--" + nonce;
+
+    promise_test(async function(t) {
+        const SCOPE = 'resources/fetch-via-serviceworker--fallback--frame.html';
+        const SCRIPT = 'resources/fetch-via-serviceworker--fallback--sw.js';
+        const URL = '/fetch/sec-metadata/resources/record-header.py?file=' + key;
+        const RETRIEVAL_URL = "/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key;
+
+        const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+        t.add_cleanup(async () => {
+          if (reg)
+            await reg.unregister();
+        });
+
+        await wait_for_state(t, reg.installing, 'activated');
+
+        const frame = await with_iframe(SCOPE);
+        t.add_cleanup(async () => {
+          if (frame)
+            frame.remove();
+        });
+
+        // Trigger a fetch that 1) will go through the service worker and 2) will
+        // fetch a special URL that records request headers.
+        await frame.contentWindow.fetch(URL, {mode:'no-cors'});
+
+        // Retrieve the request headers that have been recorded in the previous step.
+        const response = await fetch(RETRIEVAL_URL);
+        const text = await response.text();
+
+        // Verify presence of the expected Sec-Fetch-... request headers.
+        let expected = {"dest":"empty", "site":"same-origin", "user":"", "mode": "no-cors"};
+        assert_header_equals(text, expected);
+      }, 'Sec-Fetch headers after SW fallback');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch-via-serviceworker--respondWith.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch-via-serviceworker--respondWith.tentative.https.sub.html
new file mode 100644
index 0000000..24f3b27
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch-via-serviceworker--respondWith.tentative.https.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!--
+  This test verifies presence of Sec-Fetch-... request headers on a request
+  handled by a service worker - this test covers the scenario when the service
+  worker responds to the `fetch` event with:
+      event.respondWith(fetch(event.request));
+-->
+<meta charset="utf-8"/>
+<link rel="author" href="lukasza@chromium.org" title="Lukasz Anforowicz">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script src=/common/utils.js></script>
+<script>
+    const nonce = token();
+    const key = "fetch-via-serviceworker--respondWith--" + nonce;
+
+    promise_test(async function(t) {
+        const SCOPE = 'resources/fetch-via-serviceworker--respondWith--frame.html';
+        const SCRIPT = 'resources/fetch-via-serviceworker--respondWith--sw.js';
+        const URL = '/fetch/sec-metadata/resources/record-header.py?file=' + key;
+        const RETRIEVAL_URL = "/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key;
+
+        const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+        t.add_cleanup(async () => {
+          if (reg)
+            await reg.unregister();
+        });
+
+        await wait_for_state(t, reg.installing, 'activated');
+
+        const frame = await with_iframe(SCOPE);
+        t.add_cleanup(async () => {
+          if (frame)
+            frame.remove();
+        });
+
+        // Trigger a fetch that 1) will go through the service worker and 2) will
+        // fetch a special URL that records request headers.
+        await frame.contentWindow.fetch(URL, {mode:'no-cors'});
+
+        // Retrieve the request headers that have been recorder in the previous step.
+        const response = await fetch(RETRIEVAL_URL);
+        const text = await response.text();
+
+        // Verify presence of the expected Sec-Fetch-... request headers.
+        let expected = {"dest":"empty", "site":"same-origin", "user":"", "mode": "no-cors"};
+        assert_header_equals(text, expected);
+      }, 'Sec-Fetch headers after SW fallback');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--fallback--frame.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--fallback--frame.html
new file mode 100644
index 0000000..9879802
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--fallback--frame.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Page Title</title>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--fallback--sw.js b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--fallback--sw.js
new file mode 100644
index 0000000..09858b26
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--fallback--sw.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', function(event) {
+    // Empty event handler - will fallback to the network.
+});
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--respondWith--frame.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--respondWith--frame.html
new file mode 100644
index 0000000..9879802
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--respondWith--frame.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Page Title</title>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--respondWith--sw.js b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--respondWith--sw.js
new file mode 100644
index 0000000..8bf8d8f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/fetch-via-serviceworker--respondWith--sw.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', function(event) {
+    event.respondWith(fetch(event.request));
+});
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html
index ee436d9..c86198d 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html
@@ -1,4 +1,9 @@
 <!DOCTYPE html>
+<!--
+  This test verifies presence of Sec-Fetch-... request headers on a request
+  that fetches the service worker script itself (i.e. the script at the URL
+  passed as an argument to navigator.serviceWorker.register).
+-->
 <meta charset="utf-8"/>
 <link rel="author" href="mtrzos@google.com" title="Maciek Trzos">
 <script src=/resources/testharness.js></script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
index 76e0835..44c6a05 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
@@ -1,9 +1,9 @@
 This is a testharness.js-based test.
 PASS initialize global state
-FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 6"
-FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 8"
-FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 6"
-FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 8"
+FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 3"
+FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 5"
+FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 3"
+FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 5"
 PASS FetchEvent#request.body contains XHR request data (string)
 PASS FetchEvent#request.body contains XHR request data (blob)
 PASS FetchEvent#request.method is set to XHR method
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html b/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
index e98cbba..76f86e9 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
+++ b/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
@@ -31,7 +31,7 @@
           header_names.sort();
           assert_array_equals(
             header_names,
-            ["accept", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "upgrade-insecure-requests", "user-agent"],
+            ["accept", "upgrade-insecure-requests", "user-agent"],
             'event.request has the expected headers.');
 
           return service_worker_unregister_and_done(t, scope);