Attach URL's origin for neither-GET-nor-HEAD requests from extensions

With this change, fetch with neither GET nor HEAD methods made by
extension scripts (not including content scripts) will have destination
URL's origin as the origin request header, not the extension's origin,
if the extension has access to the destination URL's origin.

This is good for extension authors because some servers reject
requests with chrome-extensions:// origins. When the extension has
access to the destination URL, it is no problem to pretend the request
is a "same-origin" request.

Bug: 966223
Change-Id: I062f2371225c625a0d4ddd845727c4e4c5566b11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1780091
Reviewed-by: Devlin <[email protected]>
Reviewed-by: Takashi Toyoshima <[email protected]>
Reviewed-by: Ɓukasz Anforowicz <[email protected]>
Commit-Queue: Yutaka Hirano <[email protected]>
Auto-Submit: Yutaka Hirano <[email protected]>
Cr-Commit-Position: refs/heads/master@{#722188}
diff --git a/chrome/browser/extensions/fetch_apitest.cc b/chrome/browser/extensions/fetch_apitest.cc
index cdaf9712..2196c55 100644
--- a/chrome/browser/extensions/fetch_apitest.cc
+++ b/chrome/browser/extensions/fetch_apitest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/extension_apitest.h"
@@ -14,11 +15,34 @@
 #include "extensions/test/test_extension_dir.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "services/network/public/cpp/features.h"
 
 namespace extensions {
 
 namespace {
 
+// Returns a response whose body is request's origin.
+std::unique_ptr<net::test_server::HttpResponse> HandleEchoOrigin(
+    const net::test_server::HttpRequest& request) {
+  if (request.relative_url != "/echo-origin")
+    return nullptr;
+
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_code(net::HTTP_OK);
+  response->set_content_type("text/plain");
+  auto it = request.headers.find("origin");
+  if (it != request.headers.end()) {
+    response->set_content(it->second);
+  } else {
+    response->set_content("<no origin attached>");
+  }
+  response->AddCustomHeader("access-control-allow-origin", "*");
+
+  return response;
+}
+
 // JavaScript snippet which performs a fetch given a URL expression to be
 // substituted as %s, then sends back the fetched content using the
 // domAutomationController.
@@ -31,6 +55,16 @@
     "  window.domAutomationController.send(String(err));\n"
     "});\n";
 
+constexpr char kFetchPostScript[] = R"(
+  fetch($1, {method: 'POST'}).then((result) => {
+    return result.text();
+  }).then((text) => {
+    window.domAutomationController.send(text);
+  }).catch((error) => {
+    window.domAutomationController.send(String(err));
+  });
+)";
+
 class ExtensionFetchTest : public ExtensionApiTest {
  protected:
   // Writes an empty background page and a text file called "text" with content
@@ -73,6 +107,9 @@
   void SetUpOnMainThread() override {
     ExtensionApiTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
+
+    embedded_test_server()->RegisterRequestHandler(
+        base::BindRepeating(HandleEchoOrigin));
     ASSERT_TRUE(StartEmbeddedTestServer());
   }
 };
@@ -244,6 +281,52 @@
   EXPECT_EQ("basic", ExecuteScriptInBackgroundPage(extension->id(), script));
 }
 
+IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, OriginOnPostWithPermissions) {
+  TestExtensionDir dir;
+  dir.WriteManifest(R"JSON(
+     {
+      "background": {"scripts": ["bg.js"]},
+      "manifest_version": 2,
+      "name": "FetchResponseType",
+      "permissions": ["http://example.com/*"],
+      "version": "1"
+     })JSON");
+  const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
+  ASSERT_TRUE(extension);
+
+  // Extension scripts will have the origin of the destination URL only when
+  // OOR-CORS is enabled.
+  GURL destination_url =
+      embedded_test_server()->GetURL("example.com", "/echo-origin");
+  std::string script = content::JsReplace(kFetchPostScript, destination_url);
+  std::string origin_string =
+      network::features::ShouldEnableOutOfBlinkCorsForTesting()
+          ? url::Origin::Create(destination_url).Serialize()
+          : url::Origin::Create(extension->url()).Serialize();
+  EXPECT_EQ(origin_string,
+            ExecuteScriptInBackgroundPage(extension->id(), script));
+}
+
+IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, OriginOnPostWithoutPermissions) {
+  TestExtensionDir dir;
+  dir.WriteManifest(R"JSON(
+     {
+      "background": {"scripts": ["bg.js"]},
+      "manifest_version": 2,
+      "name": "FetchResponseType",
+      "permissions": [],
+      "version": "1"
+     })JSON");
+  const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
+  ASSERT_TRUE(extension);
+
+  const std::string script = content::JsReplace(
+      kFetchPostScript,
+      embedded_test_server()->GetURL("example.com", "/echo-origin"));
+  EXPECT_EQ(url::Origin::Create(extension->url()).Serialize(),
+            ExecuteScriptInBackgroundPage(extension->id(), script));
+}
+
 }  // namespace
 
 }  // namespace extensions