blob: 0c0cc6eb3fb0a75571f39bd54a29a18f8a3a263b [file] [log] [blame]
kalman30a92f962015-09-03 18:08:201// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Yutaka Hirano54b166eb2019-12-05 20:59:525#include "base/bind.h"
kalman30a92f962015-09-03 18:08:206#include "base/files/file_path.h"
7#include "base/strings/stringprintf.h"
8#include "chrome/browser/extensions/extension_apitest.h"
thestige80821242015-09-30 23:46:089#include "chrome/browser/ui/browser_navigator_params.h"
kalman30a92f962015-09-03 18:08:2010#include "chrome/browser/ui/tabs/tab_strip_model.h"
11#include "chrome/test/base/ui_test_utils.h"
12#include "content/public/browser/web_contents.h"
13#include "content/public/test/browser_test_utils.h"
14#include "extensions/common/extension.h"
Devlin Cronin4f455a22018-01-25 01:36:4515#include "extensions/test/test_extension_dir.h"
kalman30a92f962015-09-03 18:08:2016#include "net/dns/mock_host_resolver.h"
17#include "net/test/embedded_test_server/embedded_test_server.h"
Yutaka Hirano54b166eb2019-12-05 20:59:5218#include "net/test/embedded_test_server/http_request.h"
19#include "net/test/embedded_test_server/http_response.h"
20#include "services/network/public/cpp/features.h"
kalman30a92f962015-09-03 18:08:2021
22namespace extensions {
23
24namespace {
25
Yutaka Hirano54b166eb2019-12-05 20:59:5226// Returns a response whose body is request's origin.
27std::unique_ptr<net::test_server::HttpResponse> HandleEchoOrigin(
28 const net::test_server::HttpRequest& request) {
29 if (request.relative_url != "/echo-origin")
30 return nullptr;
31
32 auto response = std::make_unique<net::test_server::BasicHttpResponse>();
33 response->set_code(net::HTTP_OK);
34 response->set_content_type("text/plain");
35 auto it = request.headers.find("origin");
36 if (it != request.headers.end()) {
37 response->set_content(it->second);
38 } else {
39 response->set_content("<no origin attached>");
40 }
41 response->AddCustomHeader("access-control-allow-origin", "*");
42
43 return response;
44}
45
kalman30a92f962015-09-03 18:08:2046// JavaScript snippet which performs a fetch given a URL expression to be
47// substituted as %s, then sends back the fetched content using the
48// domAutomationController.
49const char* kFetchScript =
50 "fetch(%s).then(function(result) {\n"
51 " return result.text();\n"
52 "}).then(function(text) {\n"
53 " window.domAutomationController.send(text);\n"
54 "}).catch(function(err) {\n"
55 " window.domAutomationController.send(String(err));\n"
56 "});\n";
57
Yutaka Hirano54b166eb2019-12-05 20:59:5258constexpr char kFetchPostScript[] = R"(
59 fetch($1, {method: 'POST'}).then((result) => {
60 return result.text();
61 }).then((text) => {
62 window.domAutomationController.send(text);
63 }).catch((error) => {
64 window.domAutomationController.send(String(err));
65 });
66)";
67
kalman30a92f962015-09-03 18:08:2068class ExtensionFetchTest : public ExtensionApiTest {
69 protected:
70 // Writes an empty background page and a text file called "text" with content
71 // "text content", then loads and returns the extension. |dir| must already
72 // have a manifest.
73 const Extension* WriteFilesAndLoadTestExtension(TestExtensionDir* dir) {
74 dir->WriteFile(FILE_PATH_LITERAL("text"), "text content");
75 dir->WriteFile(FILE_PATH_LITERAL("bg.js"), "");
vabr9142fe22016-09-08 13:19:2276 return LoadExtension(dir->UnpackedPath());
kalman30a92f962015-09-03 18:08:2077 }
78
79 // Returns |kFetchScript| with |url_expression| substituted as its test URL.
80 std::string GetFetchScript(const std::string& url_expression) {
81 return base::StringPrintf(kFetchScript, url_expression.c_str());
82 }
83
84 // Returns |url| as a string surrounded by single quotes, for passing to
85 // JavaScript as a string literal.
86 std::string GetQuotedURL(const GURL& url) {
87 return base::StringPrintf("'%s'", url.spec().c_str());
88 }
89
90 // Like GetQuotedURL(), but fetching the URL from the test server's |host|
91 // and |path|.
92 std::string GetQuotedTestServerURL(const std::string& host,
93 const std::string& path) {
94 return GetQuotedURL(embedded_test_server()->GetURL(host, path));
95 }
96
97 // Opens a tab, puts it in the foreground, navigates it to |url| then returns
98 // its WebContents.
99 content::WebContents* CreateAndNavigateTab(const GURL& url) {
cm.sanchi2522bc92017-12-04 08:04:13100 NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
nick3b04f322016-08-31 19:29:19101 params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
kalman30a92f962015-09-03 18:08:20102 ui_test_utils::NavigateToURL(&params);
103 return browser()->tab_strip_model()->GetActiveWebContents();
104 }
105
kalman30a92f962015-09-03 18:08:20106 void SetUpOnMainThread() override {
107 ExtensionApiTest::SetUpOnMainThread();
108 host_resolver()->AddRule("*", "127.0.0.1");
Yutaka Hirano54b166eb2019-12-05 20:59:52109
110 embedded_test_server()->RegisterRequestHandler(
111 base::BindRepeating(HandleEchoOrigin));
kalman30a92f962015-09-03 18:08:20112 ASSERT_TRUE(StartEmbeddedTestServer());
113 }
114};
115
116IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, ExtensionCanFetchExtensionResource) {
117 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51118 constexpr char kManifest[] =
119 R"({
120 "background": {"scripts": ["bg.js"]},
121 "manifest_version": 2,
122 "name": "ExtensionCanFetchExtensionResource",
123 "version": "1"
124 })";
125 dir.WriteManifest(kManifest);
kalman30a92f962015-09-03 18:08:20126 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
127 ASSERT_TRUE(extension);
128
129 EXPECT_EQ(
130 "text content",
131 ExecuteScriptInBackgroundPage(
132 extension->id(), GetFetchScript("chrome.runtime.getURL('text')")));
133}
134
135IN_PROC_BROWSER_TEST_F(ExtensionFetchTest,
136 ExtensionCanFetchHostedResourceWithHostPermissions) {
137 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51138 constexpr char kManifest[] =
139 R"({
140 "background": {"scripts": ["bg.js"]},
141 "manifest_version": 2,
142 "name": "ExtensionCanFetchHostedResourceWithHostPermissions",
143 "permissions": ["http://example.com/*"],
144 "version": "1"
145 })";
146 dir.WriteManifest(kManifest);
kalman30a92f962015-09-03 18:08:20147 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
148 ASSERT_TRUE(extension);
149
150 EXPECT_EQ("Hello!", ExecuteScriptInBackgroundPage(
151 extension->id(),
152 GetFetchScript(GetQuotedTestServerURL(
153 "example.com", "/extensions/test_file.txt"))));
154}
155
Xiaocheng Huc11718bb2019-12-28 00:34:40156// TODO(crbug.com/1038156): Test is flaky.
kalman30a92f962015-09-03 18:08:20157IN_PROC_BROWSER_TEST_F(
158 ExtensionFetchTest,
Xiaocheng Huc11718bb2019-12-28 00:34:40159 DISABLED_ExtensionCannotFetchHostedResourceWithoutHostPermissions) {
kalman30a92f962015-09-03 18:08:20160 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51161 constexpr char kManifest[] =
162 R"({
163 "background": {"scripts": ["bg.js"]},
164 "manifest_version": 2,
165 "name": "ExtensionCannotFetchHostedResourceWithoutHostPermissions",
166 "version": "1"
167 })";
168 dir.WriteManifest(kManifest);
kalman30a92f962015-09-03 18:08:20169 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
170 ASSERT_TRUE(extension);
171
172 // TODO(kalman): Another test would be to configure the test server to work
173 // with CORS, and test that the fetch succeeds.
174 EXPECT_EQ(
175 "TypeError: Failed to fetch",
176 ExecuteScriptInBackgroundPage(
177 extension->id(), GetFetchScript(GetQuotedTestServerURL(
178 "example.com", "/extensions/test_file.txt"))));
179}
180
181IN_PROC_BROWSER_TEST_F(ExtensionFetchTest,
182 HostCanFetchWebAccessibleExtensionResource) {
183 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51184 constexpr char kManifest[] =
185 R"({
186 "background": {"scripts": ["bg.js"]},
187 "manifest_version": 2,
188 "name": "HostCanFetchWebAccessibleExtensionResource",
189 "version": "1",
190 "web_accessible_resources": ["text"]
191 })";
192 dir.WriteManifest(kManifest);
kalman30a92f962015-09-03 18:08:20193 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
194 ASSERT_TRUE(extension);
195
196 content::WebContents* empty_tab = CreateAndNavigateTab(
197 embedded_test_server()->GetURL("example.com", "/empty.html"));
198
199 // TODO(kalman): Test this from a content script too.
200 std::string fetch_result;
201 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
202 empty_tab,
203 GetFetchScript(GetQuotedURL(extension->GetResourceURL("text"))),
204 &fetch_result));
205 EXPECT_EQ("text content", fetch_result);
206}
207
Makoto Shimazub8a7f58f2018-11-07 19:09:31208// Calling fetch() from a http(s) service worker context to a
209// chrome-extensions:// URL since the loading path in a service worker is
210// different from pages.
211// This is a regression test for https://crbug.com/901443.
212IN_PROC_BROWSER_TEST_F(
213 ExtensionFetchTest,
214 HostCanFetchWebAccessibleExtensionResource_FetchFromServiceWorker) {
215 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51216 constexpr char kManifest[] =
217 R"({
218 "background": {"scripts": ["bg.js"]},
219 "manifest_version": 2,
220 "name": "FetchFromServiceWorker",
221 "version": "1",
222 "web_accessible_resources": ["text"]
223 })";
224 dir.WriteManifest(kManifest);
Makoto Shimazub8a7f58f2018-11-07 19:09:31225 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
226 ASSERT_TRUE(extension);
227
228 content::WebContents* tab =
229 CreateAndNavigateTab(embedded_test_server()->GetURL(
230 "/workers/fetch_from_service_worker.html"));
231 EXPECT_EQ("ready", content::EvalJs(tab, "setup();"));
232 EXPECT_EQ("text content",
233 content::EvalJs(
234 tab, base::StringPrintf(
235 "fetch_from_service_worker('%s');",
236 extension->GetResourceURL("text").spec().c_str())));
237}
238
kalman30a92f962015-09-03 18:08:20239IN_PROC_BROWSER_TEST_F(ExtensionFetchTest,
240 HostCannotFetchNonWebAccessibleExtensionResource) {
241 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51242 constexpr char kManifest[] =
243 R"({
244 "background": {"scripts": ["bg.js"]},
245 "manifest_version": 2,
246 "name": "HostCannotFetchNonWebAccessibleExtensionResource",
247 "version": "1"
248 })";
249 dir.WriteManifest(kManifest);
kalman30a92f962015-09-03 18:08:20250 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
251 ASSERT_TRUE(extension);
252
253 content::WebContents* empty_tab = CreateAndNavigateTab(
254 embedded_test_server()->GetURL("example.com", "/empty.html"));
255
256 // TODO(kalman): Test this from a content script too.
257 std::string fetch_result;
258 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
259 empty_tab,
260 GetFetchScript(GetQuotedURL(extension->GetResourceURL("text"))),
261 &fetch_result));
262 EXPECT_EQ("TypeError: Failed to fetch", fetch_result);
263}
264
Yutaka Hirano7938c712019-10-15 06:00:42265IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, FetchResponseType) {
266 const std::string script = base::StringPrintf(
267 "fetch(%s).then(function(response) {\n"
268 " window.domAutomationController.send(response.type);\n"
269 "}).catch(function(err) {\n"
270 " window.domAutomationController.send(String(err));\n"
271 "});\n",
272 GetQuotedTestServerURL("example.com", "/extensions/test_file.txt")
273 .data());
274 TestExtensionDir dir;
Devlin Cronin57344652019-12-27 20:51:51275 constexpr char kManifest[] =
276 R"({
277 "background": {"scripts": ["bg.js"]},
278 "manifest_version": 2,
279 "name": "FetchResponseType",
280 "permissions": ["http://example.com/*"],
281 "version": "1"
282 })";
283 dir.WriteManifest(kManifest);
Yutaka Hirano7938c712019-10-15 06:00:42284 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
285 ASSERT_TRUE(extension);
286
287 EXPECT_EQ("basic", ExecuteScriptInBackgroundPage(extension->id(), script));
288}
289
Yutaka Hirano3c2a14b2020-01-14 07:08:23290class ExtensionFetchPostOriginTest : public ExtensionFetchTest,
291 public testing::WithParamInterface<bool> {
292 protected:
293 void SetUp() override {
294 if (GetParam()) {
295 scoped_feature_list_.InitAndEnableFeature(
296 network::features::
297 kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess);
298 } else {
299 scoped_feature_list_.InitAndDisableFeature(
300 network::features::
301 kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess);
302 }
303 ExtensionFetchTest::SetUp();
304 }
305
306 private:
307 base::test::ScopedFeatureList scoped_feature_list_;
308};
309
310IN_PROC_BROWSER_TEST_P(ExtensionFetchPostOriginTest,
311 OriginOnPostWithPermissions) {
Yutaka Hirano54b166eb2019-12-05 20:59:52312 TestExtensionDir dir;
313 dir.WriteManifest(R"JSON(
314 {
315 "background": {"scripts": ["bg.js"]},
316 "manifest_version": 2,
317 "name": "FetchResponseType",
318 "permissions": ["http://example.com/*"],
319 "version": "1"
320 })JSON");
321 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
322 ASSERT_TRUE(extension);
323
Yutaka Hirano54b166eb2019-12-05 20:59:52324 GURL destination_url =
325 embedded_test_server()->GetURL("example.com", "/echo-origin");
326 std::string script = content::JsReplace(kFetchPostScript, destination_url);
327 std::string origin_string =
Yutaka Hirano3c2a14b2020-01-14 07:08:23328 network::features::ShouldEnableOutOfBlinkCorsForTesting() && GetParam()
Yutaka Hirano54b166eb2019-12-05 20:59:52329 ? url::Origin::Create(destination_url).Serialize()
330 : url::Origin::Create(extension->url()).Serialize();
331 EXPECT_EQ(origin_string,
332 ExecuteScriptInBackgroundPage(extension->id(), script));
333}
334
Yutaka Hirano3c2a14b2020-01-14 07:08:23335IN_PROC_BROWSER_TEST_P(ExtensionFetchPostOriginTest,
336 OriginOnPostWithoutPermissions) {
Yutaka Hirano54b166eb2019-12-05 20:59:52337 TestExtensionDir dir;
338 dir.WriteManifest(R"JSON(
339 {
340 "background": {"scripts": ["bg.js"]},
341 "manifest_version": 2,
342 "name": "FetchResponseType",
343 "permissions": [],
344 "version": "1"
345 })JSON");
346 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
347 ASSERT_TRUE(extension);
348
349 const std::string script = content::JsReplace(
350 kFetchPostScript,
351 embedded_test_server()->GetURL("example.com", "/echo-origin"));
352 EXPECT_EQ(url::Origin::Create(extension->url()).Serialize(),
353 ExecuteScriptInBackgroundPage(extension->id(), script));
354}
355
Yutaka Hirano3c2a14b2020-01-14 07:08:23356INSTANTIATE_TEST_SUITE_P(UseExtensionOrigin,
357 ExtensionFetchPostOriginTest,
358 testing::Values(false));
359
360INSTANTIATE_TEST_SUITE_P(UseDestinationUrlOrigin,
361 ExtensionFetchPostOriginTest,
362 testing::Values(true));
363
Yutaka Hirano5a605872020-01-08 05:09:07364// An extension background script should be able to fetch resources contained in
365// the extension, and those resources should not be opaque.
366IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, ExtensionResourceShouldNotBeOpaque) {
367 // We use a script to test this feature. Ideally testing with fetch() and
368 // response type is better, but some logic in blink (see the manual
369 // response type handling in blink::FetchManager) would hide potential
370 // breakages, which is why we are using a script.
371 const std::string script = base::StringPrintf(R"(
372 const script = document.createElement('script');
373 window.onerror = (message) => {
374 window.domAutomationController.send('onerror: ' + message);
375 }
376 script.src = 'error.js'
377 document.body.appendChild(script);)");
378 TestExtensionDir dir;
379 dir.WriteManifest(R"JSON(
380 {
381 "background": {"scripts": ["bg.js"]},
382 "manifest_version": 2,
383 "name": "FetchResponseType",
384 "permissions": [],
385 "version": "1"
386 })JSON");
387 dir.WriteFile(FILE_PATH_LITERAL("error.js"), "throw TypeError('hi!')");
388 const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
389 ASSERT_TRUE(extension);
390
391 // We expect that we can read the content of the error here. Otherwise
392 // "onerror: Script error." will be seen.
393 EXPECT_EQ("onerror: Uncaught TypeError: hi!",
394 ExecuteScriptInBackgroundPage(extension->id(), script));
395}
396
kalman30a92f962015-09-03 18:08:20397} // namespace
398
399} // namespace extensions