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