1 // Copyright 2018 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
5 #include "base/bind.h"
6 #include "base/bind_helpers.h"
7 #include "base/command_line.h"
8 #include "base/files/file_path.h"
9 #include "base/macros.h"
10 #include "base/path_service.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/test/scoped_command_line.h"
16 #include "build/build_config.h"
17 #include "content/public/browser/render_view_host.h"
18 #include "content/public/browser/web_contents.h"
19 #include "content/public/common/content_paths.h"
20 #include "content/public/common/content_switches.h"
21 #include "content/public/common/web_preferences.h"
22 #include "content/public/test/browser_test_utils.h"
23 #include "content/public/test/content_browser_test.h"
24 #include "content/public/test/content_browser_test_utils.h"
25 #include "content/shell/browser/shell.h"
26 #include "net/http/http_request_headers.h"
27 #include "net/http/http_status_code.h"
28 #include "net/test/embedded_test_server/embedded_test_server.h"
29 #include "net/test/embedded_test_server/http_request.h"
30 #include "net/test/embedded_test_server/http_response.h"
31 #include "net/test/embedded_test_server/request_handler_util.h"
32 #include "services/network/public/cpp/cors/cors.h"
33 #include "testing/gmock/include/gmock/gmock.h"
34 #include "testing/gtest/include/gtest/gtest.h"
35 #include "url/gurl.h"
36 #include "url/url_constants.h"
37
38 namespace content {
39
40 namespace {
41
42 using net::test_server::BasicHttpResponse;
43 using net::test_server::HttpRequest;
44 using net::test_server::HttpResponse;
45
46 // Tests end to end Origin header and CORS check behaviors without
47 // --allow-file-access-from-files flag.
48 class CorsFileOriginBrowserTest : public ContentBrowserTest {
49 public:
CorsFileOriginBrowserTest()50 CorsFileOriginBrowserTest()
51 : pass_string_(base::ASCIIToUTF16("PASS")),
52 fail_string_(base::ASCIIToUTF16("FAIL")) {}
53 ~CorsFileOriginBrowserTest() override = default;
54
55 protected:
CreateTestDataURL(const std::string & relative_path)56 GURL CreateTestDataURL(const std::string& relative_path) {
57 base::AutoLock lock(lock_);
58 base::FilePath path(test_data_loader_path_);
59 path = path.AppendASCII(relative_path);
60 // This is wrong if developers checkout the source code in a path that
61 // contains non-ASCII characters to run the browser tests.
62 std::string url = "file://" + path.MaybeAsASCII();
63 return GURL(url);
64 }
65
CreateWatcher()66 std::unique_ptr<TitleWatcher> CreateWatcher() {
67 // Register all possible result strings here.
68 std::unique_ptr<TitleWatcher> watcher =
69 std::make_unique<TitleWatcher>(shell()->web_contents(), pass_string());
70 watcher->AlsoWaitForTitle(fail_string());
71
72 // Does not appear in the expectations, but the title can be on unexpected
73 // failures.
74 base::string16 wrong_origin_string =
75 base::ASCIIToUTF16("FAIL: response text does not match");
76 watcher->AlsoWaitForTitle(wrong_origin_string);
77 return watcher;
78 }
79
target_http_url()80 std::string target_http_url() {
81 return base::StringPrintf("http://127.0.0.1:%d/test", port());
82 }
target_file_url() const83 std::string target_file_url() const { return "get.txt"; }
target_self_file_url() const84 std::string target_self_file_url() const {
85 return "cors_file_origin_test.html";
86 }
87
pass_string() const88 const base::string16& pass_string() const { return pass_string_; }
fail_string() const89 const base::string16& fail_string() const { return fail_string_; }
90
port()91 uint16_t port() {
92 base::AutoLock lock(lock_);
93 return port_;
94 }
95
is_preflight_requested()96 bool is_preflight_requested() {
97 base::AutoLock lock(lock_);
98 return is_preflight_requested_;
99 }
100
101 private:
AllowFileAccessFromFiles()102 bool AllowFileAccessFromFiles() override { return false; }
IsWebSecurityEnabled() const103 virtual bool IsWebSecurityEnabled() const { return true; }
104
SetUpCommandLine(base::CommandLine * command_line)105 void SetUpCommandLine(base::CommandLine* command_line) override {
106 if (!IsWebSecurityEnabled()) {
107 command_line->AppendSwitch(switches::kDisableWebSecurity);
108 }
109
110 ContentBrowserTest::SetUpCommandLine(command_line);
111 }
SetUpOnMainThread()112 void SetUpOnMainThread() override {
113 base::AutoLock lock(lock_);
114
115 // Need to obtain the path on the main thread.
116 ASSERT_TRUE(base::PathService::Get(DIR_TEST_DATA, &test_data_loader_path_));
117 test_data_loader_path_ =
118 test_data_loader_path_.Append(FILE_PATH_LITERAL("loader"));
119
120 embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
121 &CorsFileOriginBrowserTest::HandleWithAccessControlAllowOrigin,
122 base::Unretained(this)));
123
124 ASSERT_TRUE(embedded_test_server()->Start());
125
126 port_ = embedded_test_server()->port();
127 }
128
HandleWithAccessControlAllowOrigin(const HttpRequest & request)129 std::unique_ptr<HttpResponse> HandleWithAccessControlAllowOrigin(
130 const HttpRequest& request) {
131 std::unique_ptr<BasicHttpResponse> response;
132
133 if (net::test_server::ShouldHandle(request, "/test")) {
134 // Accept XHR CORS requests.
135 response = std::make_unique<BasicHttpResponse>();
136 response->set_code(net::HTTP_OK);
137 auto query = net::test_server::ParseQuery(request.GetURL());
138 response->AddCustomHeader(
139 network::cors::header_names::kAccessControlAllowOrigin,
140 query["allow"][0]);
141 if (request.method == net::test_server::METHOD_OPTIONS) {
142 // For CORS-preflight request.
143 response->AddCustomHeader(
144 network::cors::header_names::kAccessControlAllowMethods,
145 "GET, OPTIONS");
146 response->AddCustomHeader(
147 network::cors::header_names::kAccessControlAllowHeaders,
148 "X-NotSimple");
149 response->AddCustomHeader(
150 network::cors::header_names::kAccessControlMaxAge, "0");
151 response->AddCustomHeader(net::HttpRequestHeaders::kCacheControl,
152 "no-store");
153 base::AutoLock lock(lock_);
154 is_preflight_requested_ = true;
155 }
156
157 // Return the request origin header as the body so that JavaScript can
158 // check if it sent the expected origin header.
159 auto origin = request.headers.find(net::HttpRequestHeaders::kOrigin);
160 if (origin != request.headers.end())
161 response->set_content(origin->second);
162 }
163 return response;
164 }
165
166 base::Lock lock_;
167 base::FilePath test_data_loader_path_;
168 uint16_t port_;
169 bool is_preflight_requested_ = false;
170
171 const base::string16 pass_string_;
172 const base::string16 fail_string_;
173
174 DISALLOW_COPY_AND_ASSIGN(CorsFileOriginBrowserTest);
175 };
176
177 // Tests end to end Origin header and CORS check behaviors with
178 // --allow-file-access-from-files flag.
179 class CorsFileOriginBrowserTestWithAllowFileAccessFromFiles
180 : public CorsFileOriginBrowserTest {
181 private:
AllowFileAccessFromFiles()182 bool AllowFileAccessFromFiles() override { return true; }
183 };
184
185 // Tests end to end Origin header and CORS check behaviors with
186 // --disable-web-security flag.
187 class CorsFileOriginBrowserTestWithDisableWebSecurity
188 : public CorsFileOriginBrowserTest {
189 private:
AllowFileAccessFromFiles()190 bool AllowFileAccessFromFiles() override { return false; }
IsWebSecurityEnabled() const191 bool IsWebSecurityEnabled() const override { return false; }
192 };
193
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,AccessControlAllowOriginIsNull)194 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,
195 AccessControlAllowOriginIsNull) {
196 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
197 EXPECT_TRUE(NavigateToURL(
198 shell(),
199 CreateTestDataURL(base::StringPrintf(
200 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
201 target_http_url().c_str(), "null", "null"))));
202
203 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
204 EXPECT_TRUE(is_preflight_requested());
205 }
206
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,AccessControlAllowOriginIsFile)207 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,
208 AccessControlAllowOriginIsFile) {
209 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
210 EXPECT_TRUE(NavigateToURL(
211 shell(),
212 CreateTestDataURL(base::StringPrintf(
213 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
214 target_http_url().c_str(), "file://", "null"))));
215
216 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle());
217 EXPECT_TRUE(is_preflight_requested());
218 }
219
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,AccessToSelfFileUrl)220 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest, AccessToSelfFileUrl) {
221 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
222 EXPECT_TRUE(NavigateToURL(
223 shell(),
224 CreateTestDataURL(base::StringPrintf(
225 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
226 target_self_file_url().c_str(), "unused", "unused"))));
227
228 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle());
229 }
230
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,AccessToAnotherFileUrl)231 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest, AccessToAnotherFileUrl) {
232 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
233 EXPECT_TRUE(NavigateToURL(
234 shell(),
235 CreateTestDataURL(base::StringPrintf(
236 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
237 target_file_url().c_str(), "unused", "unused"))));
238
239 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle());
240 }
241
242 // TODO(lukasza, nasko): https://crbug.com/981018: Enable this test on Macs
243 // after understanding what makes it flakily fail on the mac-rel trybot.
244 #if defined(OS_MACOSX)
245 #define MAYBE_UniversalAccessFromFileUrls DISABLED_UniversalAccessFromFileUrls
246 #else
247 #define MAYBE_UniversalAccessFromFileUrls UniversalAccessFromFileUrls
248 #endif
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,MAYBE_UniversalAccessFromFileUrls)249 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,
250 MAYBE_UniversalAccessFromFileUrls) {
251 const char* kScript = R"(
252 fetch($1)
253 .then(response => response.text())
254 .then(text => "SUCCESS: " + text)
255 .catch(error => "ERROR: " + error)
256 )";
257 std::string script =
258 JsReplace(kScript, embedded_test_server()->GetURL("/title2.html"));
259
260 // Activate the preference to allow universal access from file URLs.
261 RenderViewHost* rvh = shell()->web_contents()->GetRenderViewHost();
262 WebPreferences prefs = rvh->GetWebkitPreferences();
263 prefs.allow_universal_access_from_file_urls = true;
264 rvh->UpdateWebkitPreferences(prefs);
265
266 // Navigate to a file: test page.
267 GURL page_url = GetTestUrl(nullptr, "title1.html");
268 EXPECT_EQ(url::kFileScheme, page_url.scheme());
269 EXPECT_TRUE(NavigateToURL(shell(), page_url));
270
271 // Fetching http resources should be allowed by CORS when
272 // universal access from file URLs is requested.
273 std::string fetch_result = EvalJs(shell(), script).ExtractString();
274 fetch_result = TrimWhitespaceASCII(fetch_result, base::TRIM_ALL).as_string();
275 EXPECT_THAT(fetch_result, ::testing::HasSubstr("SUCCESS:"));
276 EXPECT_THAT(fetch_result, ::testing::HasSubstr("This page has a title"));
277 }
278
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,AccessControlAllowOriginIsNull)279 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,
280 AccessControlAllowOriginIsNull) {
281 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
282 EXPECT_TRUE(NavigateToURL(
283 shell(),
284 CreateTestDataURL(base::StringPrintf(
285 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
286 target_http_url().c_str(), "null", "file://"))));
287
288 EXPECT_EQ(fail_string(), watcher->WaitAndGetTitle());
289 EXPECT_TRUE(is_preflight_requested());
290 }
291
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,AccessControlAllowOriginIsFile)292 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,
293 AccessControlAllowOriginIsFile) {
294 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
295 EXPECT_TRUE(NavigateToURL(
296 shell(),
297 CreateTestDataURL(base::StringPrintf(
298 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
299 target_http_url().c_str(), "file://", "file://"))));
300
301 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
302 EXPECT_TRUE(is_preflight_requested());
303 }
304
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,AccessToSelfFileUrl)305 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,
306 AccessToSelfFileUrl) {
307 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
308 EXPECT_TRUE(NavigateToURL(
309 shell(),
310 CreateTestDataURL(base::StringPrintf(
311 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
312 target_self_file_url().c_str(), "unused", "unused"))));
313
314 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
315 }
316
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,AccessToAnotherFileUrl)317 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,
318 AccessToAnotherFileUrl) {
319 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
320 EXPECT_TRUE(NavigateToURL(
321 shell(),
322 CreateTestDataURL(base::StringPrintf(
323 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
324 target_file_url().c_str(), "unused", "unused"))));
325
326 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
327 }
328
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,AccessControlAllowOriginIsNull)329 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,
330 AccessControlAllowOriginIsNull) {
331 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
332 EXPECT_TRUE(NavigateToURL(
333 shell(),
334 CreateTestDataURL(base::StringPrintf(
335 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
336 target_http_url().c_str(), "unused", ""))));
337
338 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
339 EXPECT_FALSE(is_preflight_requested());
340 }
341
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,AccessControlAllowOriginIsFile)342 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,
343 AccessControlAllowOriginIsFile) {
344 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
345 EXPECT_TRUE(NavigateToURL(
346 shell(),
347 CreateTestDataURL(base::StringPrintf(
348 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
349 target_http_url().c_str(), "unused", ""))));
350
351 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
352 EXPECT_FALSE(is_preflight_requested());
353 }
354
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,AccessToSelfFileUrl)355 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,
356 AccessToSelfFileUrl) {
357 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
358 EXPECT_TRUE(NavigateToURL(
359 shell(),
360 CreateTestDataURL(base::StringPrintf(
361 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
362 target_self_file_url().c_str(), "unused", "unused"))));
363
364 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
365 }
366
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,AccessToAnotherFileUrl)367 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,
368 AccessToAnotherFileUrl) {
369 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
370 EXPECT_TRUE(NavigateToURL(
371 shell(),
372 CreateTestDataURL(base::StringPrintf(
373 "cors_file_origin_test.html?url=%s&allow=%s&response_text=%s",
374 target_file_url().c_str(), "unused", "unused"))));
375
376 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
377 }
378
379 // Test if local image files can be protected by canvas tainting.
380 // We can not have following test cases in web_tests because web_tests run with
381 // --run-web-tests flag that internally specifies --allow-file-access-from-files
382 // that changes this specific behavior.
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,NoCorsImagefileTaint)383 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest, NoCorsImagefileTaint) {
384 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
385 EXPECT_TRUE(NavigateToURL(
386 shell(), CreateTestDataURL("image-taint.html?test=no_cors")));
387 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
388 }
389
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest,CorsImagefileTaint)390 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTest, CorsImagefileTaint) {
391 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
392 EXPECT_TRUE(
393 NavigateToURL(shell(), CreateTestDataURL("image-taint.html?test=cors")));
394 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
395 }
396
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,NoCorsImagefileTaint)397 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,
398 NoCorsImagefileTaint) {
399 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
400 EXPECT_TRUE(NavigateToURL(
401 shell(),
402 CreateTestDataURL("image-taint.html?test=no_cors_with_file_access")));
403 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
404 }
405
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,CorsImagefileTaint)406 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithAllowFileAccessFromFiles,
407 CorsImagefileTaint) {
408 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
409 EXPECT_TRUE(NavigateToURL(
410 shell(),
411 CreateTestDataURL("image-taint.html?test=cors_with_file_access")));
412 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
413 }
414
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,NoCorsImagefileTaint)415 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,
416 NoCorsImagefileTaint) {
417 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
418 EXPECT_TRUE(NavigateToURL(
419 shell(), CreateTestDataURL(
420 "image-taint.html?test=no_cors_with_disable_web_security")));
421 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
422 }
423
IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,CorsImagefileTaint)424 IN_PROC_BROWSER_TEST_F(CorsFileOriginBrowserTestWithDisableWebSecurity,
425 CorsImagefileTaint) {
426 std::unique_ptr<TitleWatcher> watcher = CreateWatcher();
427 EXPECT_TRUE(NavigateToURL(
428 shell(), CreateTestDataURL(
429 "image-taint.html?test=cors_with_disable_web_security")));
430 EXPECT_EQ(pass_string(), watcher->WaitAndGetTitle());
431 }
432
433 } // namespace
434
435 } // namespace content
436