OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/browsing_data/clear_site_data_throttle.h" | 5 #include "content/browser/browsing_data/clear_site_data_throttle.h" |
6 | 6 |
7 #include <memory> | 7 #include <memory> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/memory/ptr_util.h" |
10 #include "content/public/common/content_switches.h" | 10 #include "base/memory/ref_counted.h" |
| 11 #include "base/run_loop.h" |
| 12 #include "base/strings/stringprintf.h" |
| 13 #include "base/test/scoped_command_line.h" |
| 14 #include "base/test/scoped_task_environment.h" |
| 15 #include "content/public/browser/resource_request_info.h" |
| 16 #include "content/public/test/test_browser_thread.h" |
| 17 #include "content/public/test/test_browser_thread_bundle.h" |
| 18 #include "net/base/load_flags.h" |
| 19 #include "net/http/http_util.h" |
| 20 #include "net/url_request/redirect_info.h" |
| 21 #include "net/url_request/url_request_job.h" |
| 22 #include "net/url_request/url_request_test_util.h" |
11 #include "testing/gmock/include/gmock/gmock.h" | 23 #include "testing/gmock/include/gmock/gmock.h" |
12 #include "testing/gtest/include/gtest/gtest.h" | 24 #include "testing/gtest/include/gtest/gtest.h" |
13 | 25 |
| 26 using ::testing::_; |
| 27 |
14 namespace content { | 28 namespace content { |
15 | 29 |
| 30 using ConsoleMessagesDelegate = ClearSiteDataThrottle::ConsoleMessagesDelegate; |
| 31 |
| 32 namespace { |
| 33 |
| 34 const char kClearSiteDataHeaderPrefix[] = "Clear-Site-Data: "; |
| 35 |
| 36 const char kClearCookiesHeader[] = |
| 37 "Clear-Site-Data: { \"types\": [ \"cookies\" ] }"; |
| 38 |
| 39 void WaitForUIThread() { |
| 40 base::RunLoop run_loop; |
| 41 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, run_loop.QuitClosure()); |
| 42 run_loop.Run(); |
| 43 } |
| 44 |
| 45 // Used to verify that resource throttle delegate calls are made. |
| 46 class MockResourceThrottleDelegate : public ResourceThrottle::Delegate { |
| 47 public: |
| 48 MOCK_METHOD0(Cancel, void()); |
| 49 MOCK_METHOD0(CancelAndIgnore, void()); |
| 50 MOCK_METHOD1(CancelWithError, void(int)); |
| 51 MOCK_METHOD0(Resume, void()); |
| 52 }; |
| 53 |
| 54 // A slightly modified ClearSiteDataThrottle for testing with unconditional |
| 55 // construction, injectable response headers, and dummy clearing functionality. |
| 56 class TestThrottle : public ClearSiteDataThrottle { |
| 57 public: |
| 58 TestThrottle(net::URLRequest* request, |
| 59 std::unique_ptr<ConsoleMessagesDelegate> delegate) |
| 60 : ClearSiteDataThrottle(request, std::move(delegate)) {} |
| 61 ~TestThrottle() override {} |
| 62 |
| 63 void SetResponseHeaders(const std::string& headers) { |
| 64 std::string headers_with_status_code = "HTTP/1.1 200\n" + headers; |
| 65 headers_ = new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( |
| 66 headers_with_status_code.c_str(), headers_with_status_code.size())); |
| 67 } |
| 68 |
| 69 MOCK_METHOD4(ClearSiteData, |
| 70 void(const url::Origin& origin, |
| 71 bool clear_cookies, |
| 72 bool clear_storage, |
| 73 bool clear_cache)); |
| 74 |
| 75 protected: |
| 76 const net::HttpResponseHeaders* GetResponseHeaders() const override { |
| 77 return headers_.get(); |
| 78 } |
| 79 |
| 80 void ExecuteClearingTask(const url::Origin& origin, |
| 81 bool clear_cookies, |
| 82 bool clear_storage, |
| 83 bool clear_cache, |
| 84 base::OnceClosure callback) override { |
| 85 ClearSiteData(origin, clear_cookies, clear_storage, clear_cache); |
| 86 |
| 87 // NOTE: ResourceThrottle expects Resume() to be called asynchronously. |
| 88 // For the purposes of this test, synchronous call works correctly, and |
| 89 // is preferable for simplicity, so that we don't have to synchronize |
| 90 // between triggering Clear-Site-Data and verifying test expectations. |
| 91 std::move(callback).Run(); |
| 92 } |
| 93 |
| 94 private: |
| 95 scoped_refptr<net::HttpResponseHeaders> headers_; |
| 96 }; |
| 97 |
| 98 // A TestThrottle with modifiable current url. |
| 99 class RedirectableTestThrottle : public TestThrottle { |
| 100 public: |
| 101 RedirectableTestThrottle(net::URLRequest* request, |
| 102 std::unique_ptr<ConsoleMessagesDelegate> delegate) |
| 103 : TestThrottle(request, std::move(delegate)) {} |
| 104 |
| 105 const GURL& GetCurrentURL() const override { |
| 106 return current_url_.is_valid() ? current_url_ |
| 107 : TestThrottle::GetCurrentURL(); |
| 108 } |
| 109 |
| 110 void SetCurrentURLForTesting(const GURL& url) { current_url_ = url; } |
| 111 |
| 112 private: |
| 113 GURL current_url_; |
| 114 }; |
| 115 |
| 116 // A ConsoleDelegate that outputs messages to a string |output_buffer| owned |
| 117 // by the caller instead of to the console (losing the level information). |
| 118 class StringConsoleMessagesDelegate : public ConsoleMessagesDelegate { |
| 119 public: |
| 120 StringConsoleMessagesDelegate(std::string* output_buffer) { |
| 121 SetOutputFormattedMessageFunctionForTesting( |
| 122 base::Bind(&StringConsoleMessagesDelegate::OutputFormattedMessage, |
| 123 base::Unretained(output_buffer))); |
| 124 } |
| 125 |
| 126 ~StringConsoleMessagesDelegate() override {} |
| 127 |
| 128 private: |
| 129 static void OutputFormattedMessage(std::string* output_buffer, |
| 130 WebContents* web_contents, |
| 131 ConsoleMessageLevel level, |
| 132 const std::string& formatted_text) { |
| 133 *output_buffer += formatted_text + "\n"; |
| 134 } |
| 135 }; |
| 136 |
| 137 } // namespace |
| 138 |
16 class ClearSiteDataThrottleTest : public testing::Test { | 139 class ClearSiteDataThrottleTest : public testing::Test { |
17 public: | 140 public: |
18 void SetUp() override { | 141 ClearSiteDataThrottleTest() |
19 base::CommandLine::ForCurrentProcess()->AppendSwitch( | 142 : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} |
20 switches::kEnableExperimentalWebPlatformFeatures); | |
21 throttle_ = ClearSiteDataThrottle::CreateThrottleForNavigation(nullptr); | |
22 } | |
23 | |
24 ClearSiteDataThrottle* GetThrottle() { | |
25 return static_cast<ClearSiteDataThrottle*>(throttle_.get()); | |
26 } | |
27 | 143 |
28 private: | 144 private: |
29 std::unique_ptr<NavigationThrottle> throttle_; | 145 TestBrowserThreadBundle thread_bundle_; |
| 146 |
| 147 DISALLOW_COPY_AND_ASSIGN(ClearSiteDataThrottleTest); |
30 }; | 148 }; |
31 | 149 |
32 TEST_F(ClearSiteDataThrottleTest, ParseHeader) { | 150 TEST_F(ClearSiteDataThrottleTest, MaybeCreateThrottleForRequest) { |
| 151 // Create a URL request. |
| 152 GURL url("https://www.example.com"); |
| 153 net::TestURLRequestContext context; |
| 154 std::unique_ptr<net::URLRequest> request( |
| 155 context.CreateRequest(url, net::DEFAULT_PRIORITY, nullptr)); |
| 156 |
| 157 // We will not create the throttle for an empty ResourceRequestInfo. |
| 158 EXPECT_FALSE( |
| 159 ClearSiteDataThrottle::MaybeCreateThrottleForRequest(request.get())); |
| 160 |
| 161 // We can create the throttle for a valid ResourceRequestInfo. |
| 162 ResourceRequestInfo::AllocateForTesting(request.get(), RESOURCE_TYPE_IMAGE, |
| 163 nullptr, 0, 0, 0, false, true, true, |
| 164 true, false); |
| 165 EXPECT_TRUE( |
| 166 ClearSiteDataThrottle::MaybeCreateThrottleForRequest(request.get())); |
| 167 } |
| 168 |
| 169 TEST_F(ClearSiteDataThrottleTest, ParseHeaderAndExecuteClearingTask) { |
33 struct TestCase { | 170 struct TestCase { |
34 const char* header; | 171 const char* header; |
35 bool cookies; | 172 bool cookies; |
36 bool storage; | 173 bool storage; |
37 bool cache; | 174 bool cache; |
38 } test_cases[] = { | 175 } test_cases[] = { |
39 // One data type. | 176 // One data type. |
40 {"{ \"types\": [\"cookies\"] }", true, false, false}, | 177 {"{ \"types\": [\"cookies\"] }", true, false, false}, |
41 {"{ \"types\": [\"storage\"] }", false, true, false}, | 178 {"{ \"types\": [\"storage\"] }", false, true, false}, |
42 {"{ \"types\": [\"cache\"] }", false, false, true}, | 179 {"{ \"types\": [\"cache\"] }", false, false, true}, |
(...skipping 22 matching lines...) Expand all Loading... |
65 false}, | 202 false}, |
66 | 203 |
67 // Unknown types are ignored, but we still proceed with the deletion for | 204 // Unknown types are ignored, but we still proceed with the deletion for |
68 // those that we recognize. | 205 // those that we recognize. |
69 {"{ \"types\": [\"cache\", \"foo\"] }", false, false, true}, | 206 {"{ \"types\": [\"cache\", \"foo\"] }", false, false, true}, |
70 }; | 207 }; |
71 | 208 |
72 for (const TestCase& test_case : test_cases) { | 209 for (const TestCase& test_case : test_cases) { |
73 SCOPED_TRACE(test_case.header); | 210 SCOPED_TRACE(test_case.header); |
74 | 211 |
| 212 // Test that ParseHeader works correctly. |
75 bool actual_cookies; | 213 bool actual_cookies; |
76 bool actual_storage; | 214 bool actual_storage; |
77 bool actual_cache; | 215 bool actual_cache; |
78 | 216 |
79 std::vector<ClearSiteDataThrottle::ConsoleMessage> messages; | 217 GURL url("https://example.com"); |
| 218 ConsoleMessagesDelegate console_delegate; |
80 | 219 |
81 EXPECT_TRUE(GetThrottle()->ParseHeader(test_case.header, &actual_cookies, | 220 EXPECT_TRUE(ClearSiteDataThrottle::ParseHeaderForTesting( |
82 &actual_storage, &actual_cache, | 221 test_case.header, &actual_cookies, &actual_storage, &actual_cache, |
83 &messages)); | 222 &console_delegate, url)); |
84 | 223 |
85 EXPECT_EQ(test_case.cookies, actual_cookies); | 224 EXPECT_EQ(test_case.cookies, actual_cookies); |
86 EXPECT_EQ(test_case.storage, actual_storage); | 225 EXPECT_EQ(test_case.storage, actual_storage); |
87 EXPECT_EQ(test_case.cache, actual_cache); | 226 EXPECT_EQ(test_case.cache, actual_cache); |
| 227 |
| 228 // Test that a call with the above parameters actually reaches |
| 229 // ExecuteClearingTask(). |
| 230 net::TestURLRequestContext context; |
| 231 std::unique_ptr<net::URLRequest> request( |
| 232 context.CreateRequest(url, net::DEFAULT_PRIORITY, nullptr)); |
| 233 TestThrottle throttle(request.get(), |
| 234 base::MakeUnique<ConsoleMessagesDelegate>()); |
| 235 MockResourceThrottleDelegate delegate; |
| 236 throttle.set_delegate_for_testing(&delegate); |
| 237 throttle.SetResponseHeaders(std::string(kClearSiteDataHeaderPrefix) + |
| 238 test_case.header); |
| 239 |
| 240 EXPECT_CALL(throttle, ClearSiteData(url::Origin(url), test_case.cookies, |
| 241 test_case.storage, test_case.cache)); |
| 242 bool defer; |
| 243 throttle.WillProcessResponse(&defer); |
| 244 EXPECT_TRUE(defer); |
| 245 |
| 246 testing::Mock::VerifyAndClearExpectations(&throttle); |
88 } | 247 } |
89 } | 248 } |
90 | 249 |
91 TEST_F(ClearSiteDataThrottleTest, InvalidHeader) { | 250 TEST_F(ClearSiteDataThrottleTest, InvalidHeader) { |
92 struct TestCase { | 251 struct TestCase { |
93 const char* header; | 252 const char* header; |
94 const char* console_message; | 253 const char* console_message; |
95 } test_cases[] = { | 254 } test_cases[] = { |
96 {"", "Not a valid JSON.\n"}, | 255 {"", "Expected valid JSON.\n"}, |
97 {"\"unclosed quote", "Not a valid JSON.\n"}, | 256 {"\"unclosed quote", "Expected valid JSON.\n"}, |
98 {"\"some text\"", "Expecting a JSON dictionary with a 'types' field.\n"}, | 257 {"\"some text\"", "Expected a JSON dictionary with a 'types' field.\n"}, |
99 {"{ \"field\" : {} }", | 258 {"{ \"field\" : {} }", |
100 "Expecting a JSON dictionary with a 'types' field.\n"}, | 259 "Expected a JSON dictionary with a 'types' field.\n"}, |
101 {"{ \"types\" : [ \"passwords\" ] }", | 260 {"{ \"types\" : [ \"passwords\" ] }", |
102 "Invalid type: \"passwords\".\n" | 261 "Unrecognized type: \"passwords\".\n" |
103 "No valid types specified in the 'types' field.\n"}, | 262 "No recognized types specified in the 'types' field.\n"}, |
104 {"{ \"types\" : [ [ \"list in a list\" ] ] }", | 263 {"{ \"types\" : [ [ \"list in a list\" ] ] }", |
105 "Invalid type: [\"list in a list\"].\n" | 264 "Unrecognized type: [\"list in a list\"].\n" |
106 "No valid types specified in the 'types' field.\n"}, | 265 "No recognized types specified in the 'types' field.\n"}, |
107 {"{ \"types\" : [ \"кукис\", \"сторидж\", \"кэш\" ]", | 266 {"{ \"types\" : [ \"кукис\", \"сторидж\", \"кэш\" ]", |
108 "Must only contain ASCII characters.\n"}}; | 267 "Must only contain ASCII characters.\n"}}; |
109 | 268 |
110 for (const TestCase& test_case : test_cases) { | 269 for (const TestCase& test_case : test_cases) { |
111 SCOPED_TRACE(test_case.header); | 270 SCOPED_TRACE(test_case.header); |
112 | 271 |
113 bool actual_cookies; | 272 bool actual_cookies; |
114 bool actual_storage; | 273 bool actual_storage; |
115 bool actual_cache; | 274 bool actual_cache; |
116 | 275 |
117 std::vector<ClearSiteDataThrottle::ConsoleMessage> messages; | 276 ConsoleMessagesDelegate console_delegate; |
118 | 277 |
119 EXPECT_FALSE(GetThrottle()->ParseHeader(test_case.header, &actual_cookies, | 278 EXPECT_FALSE(ClearSiteDataThrottle::ParseHeaderForTesting( |
120 &actual_storage, &actual_cache, | 279 test_case.header, &actual_cookies, &actual_storage, &actual_cache, |
121 &messages)); | 280 &console_delegate, GURL())); |
122 | 281 |
123 std::string multiline_message; | 282 std::string multiline_message; |
124 for (const auto& message : messages) { | 283 for (const auto& message : console_delegate.messages()) { |
125 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, message.level); | 284 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, message.level); |
126 multiline_message += message.text + "\n"; | 285 multiline_message += message.text + "\n"; |
127 } | 286 } |
128 | 287 |
129 EXPECT_EQ(test_case.console_message, multiline_message); | 288 EXPECT_EQ(test_case.console_message, multiline_message); |
130 } | 289 } |
131 } | 290 } |
132 | 291 |
| 292 TEST_F(ClearSiteDataThrottleTest, LoadDoNotSaveCookies) { |
| 293 net::TestURLRequestContext context; |
| 294 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 295 GURL("https://www.example.com"), net::DEFAULT_PRIORITY, nullptr)); |
| 296 std::unique_ptr<ConsoleMessagesDelegate> scoped_console_delegate( |
| 297 new ConsoleMessagesDelegate()); |
| 298 const ConsoleMessagesDelegate* console_delegate = |
| 299 scoped_console_delegate.get(); |
| 300 TestThrottle throttle(request.get(), std::move(scoped_console_delegate)); |
| 301 MockResourceThrottleDelegate delegate; |
| 302 throttle.set_delegate_for_testing(&delegate); |
| 303 throttle.SetResponseHeaders(kClearCookiesHeader); |
| 304 |
| 305 EXPECT_CALL(throttle, ClearSiteData(_, _, _, _)); |
| 306 bool defer; |
| 307 throttle.WillProcessResponse(&defer); |
| 308 EXPECT_TRUE(defer); |
| 309 EXPECT_EQ(1u, console_delegate->messages().size()); |
| 310 EXPECT_EQ("Cleared data types: cookies.", |
| 311 console_delegate->messages().front().text); |
| 312 EXPECT_EQ(console_delegate->messages().front().level, |
| 313 CONSOLE_MESSAGE_LEVEL_INFO); |
| 314 testing::Mock::VerifyAndClearExpectations(&throttle); |
| 315 |
| 316 request->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); |
| 317 EXPECT_CALL(throttle, ClearSiteData(_, _, _, _)).Times(0); |
| 318 throttle.WillProcessResponse(&defer); |
| 319 EXPECT_FALSE(defer); |
| 320 EXPECT_EQ(2u, console_delegate->messages().size()); |
| 321 EXPECT_EQ( |
| 322 "The request's credentials mode prohibits modifying cookies " |
| 323 "and other local data.", |
| 324 console_delegate->messages().rbegin()->text); |
| 325 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, |
| 326 console_delegate->messages().rbegin()->level); |
| 327 testing::Mock::VerifyAndClearExpectations(&throttle); |
| 328 } |
| 329 |
| 330 TEST_F(ClearSiteDataThrottleTest, InvalidOrigin) { |
| 331 struct TestCase { |
| 332 const char* origin; |
| 333 bool expect_success; |
| 334 std::string error_message; // Tested only if |expect_success| = false. |
| 335 } kTestCases[] = { |
| 336 // The throttle only works on secure origins. |
| 337 {"https://secure-origin.com", true, ""}, |
| 338 {"filesystem:https://secure-origin.com/temporary/", true, ""}, |
| 339 |
| 340 // That includes localhost. |
| 341 {"http://localhost", true, ""}, |
| 342 |
| 343 // Not on insecure origins. |
| 344 {"http://insecure-origin.com", false, |
| 345 "Not supported for insecure origins."}, |
| 346 {"filesystem:http://insecure-origin.com/temporary/", false, |
| 347 "Not supported for insecure origins."}, |
| 348 |
| 349 // Not on unique origins. |
| 350 {"data:unique-origin;", false, "Not supported for unique origins."}, |
| 351 }; |
| 352 |
| 353 net::TestURLRequestContext context; |
| 354 |
| 355 for (const TestCase& test_case : kTestCases) { |
| 356 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 357 GURL(test_case.origin), net::DEFAULT_PRIORITY, nullptr)); |
| 358 std::unique_ptr<ConsoleMessagesDelegate> scoped_console_delegate( |
| 359 new ConsoleMessagesDelegate()); |
| 360 const ConsoleMessagesDelegate* console_delegate = |
| 361 scoped_console_delegate.get(); |
| 362 TestThrottle throttle(request.get(), std::move(scoped_console_delegate)); |
| 363 MockResourceThrottleDelegate delegate; |
| 364 throttle.set_delegate_for_testing(&delegate); |
| 365 throttle.SetResponseHeaders(kClearCookiesHeader); |
| 366 |
| 367 EXPECT_CALL(throttle, ClearSiteData(_, _, _, _)) |
| 368 .Times(test_case.expect_success ? 1 : 0); |
| 369 |
| 370 bool defer; |
| 371 throttle.WillProcessResponse(&defer); |
| 372 |
| 373 EXPECT_EQ(defer, test_case.expect_success); |
| 374 EXPECT_EQ(console_delegate->messages().size(), 1u); |
| 375 EXPECT_EQ(test_case.expect_success ? CONSOLE_MESSAGE_LEVEL_INFO |
| 376 : CONSOLE_MESSAGE_LEVEL_ERROR, |
| 377 console_delegate->messages().front().level); |
| 378 if (!test_case.expect_success) { |
| 379 EXPECT_EQ(test_case.error_message, |
| 380 console_delegate->messages().front().text); |
| 381 } |
| 382 testing::Mock::VerifyAndClearExpectations(&throttle); |
| 383 } |
| 384 } |
| 385 |
| 386 TEST_F(ClearSiteDataThrottleTest, DeferAndResume) { |
| 387 enum Stage { START, REDIRECT, RESPONSE }; |
| 388 |
| 389 struct TestCase { |
| 390 Stage stage; |
| 391 std::string response_headers; |
| 392 bool should_defer; |
| 393 } kTestCases[] = { |
| 394 // The throttle never interferes while the request is starting. Response |
| 395 // headers are ignored, because URLRequest is not supposed to have any |
| 396 // at this stage in the first place. |
| 397 {START, "", false}, |
| 398 {START, kClearCookiesHeader, false}, |
| 399 |
| 400 // The throttle does not defer redirects if there are no interesting |
| 401 // response headers. |
| 402 {REDIRECT, "", false}, |
| 403 {REDIRECT, "Set-Cookie: abc=123;", false}, |
| 404 {REDIRECT, "Content-Type: image/png;", false}, |
| 405 |
| 406 // That includes malformed Clear-Site-Data headers or header values |
| 407 // that do not lead to deletion. |
| 408 {REDIRECT, "Clear-Site-Data: { types: cookies } ", false}, |
| 409 {REDIRECT, "Clear-Site-Data: { \"types\": [ \"unknown type\" ] }", false}, |
| 410 |
| 411 // However, redirects are deferred for valid Clear-Site-Data headers. |
| 412 {REDIRECT, |
| 413 "Clear-Site-Data: { \"types\": [ \"cookies\", \"unknown type\" ] }", |
| 414 true}, |
| 415 {REDIRECT, |
| 416 base::StringPrintf("Content-Type: image/png;\n%s", kClearCookiesHeader), |
| 417 true}, |
| 418 {REDIRECT, |
| 419 base::StringPrintf("%s\nContent-Type: image/png;", kClearCookiesHeader), |
| 420 true}, |
| 421 |
| 422 // We expect at most one instance of the header. Multiple instances |
| 423 // will not be parsed currently. This is not an inherent property of |
| 424 // Clear-Site-Data, just a documentation of the current behavior. |
| 425 {REDIRECT, |
| 426 base::StringPrintf("%s\n%s", kClearCookiesHeader, kClearCookiesHeader), |
| 427 false}, |
| 428 |
| 429 // Final response headers are treated the same way as in the case |
| 430 // of redirect. |
| 431 {REDIRECT, "Set-Cookie: abc=123;", false}, |
| 432 {REDIRECT, "Clear-Site-Data: { types: cookies } ", false}, |
| 433 {REDIRECT, kClearCookiesHeader, true}, |
| 434 }; |
| 435 |
| 436 struct TestOrigin { |
| 437 const char* origin; |
| 438 bool valid; |
| 439 } kTestOrigins[] = { |
| 440 // The throttle only works on secure origins. |
| 441 {"https://secure-origin.com", true}, |
| 442 {"filesystem:https://secure-origin.com/temporary/", true}, |
| 443 |
| 444 // That includes localhost. |
| 445 {"http://localhost", true}, |
| 446 |
| 447 // Not on insecure origins. |
| 448 {"http://insecure-origin.com", false}, |
| 449 {"filesystem:http://insecure-origin.com/temporary/", false}, |
| 450 |
| 451 // Not on unique origins. |
| 452 {"data:unique-origin;", false}, |
| 453 }; |
| 454 |
| 455 net::TestURLRequestContext context; |
| 456 |
| 457 for (const TestOrigin& test_origin : kTestOrigins) { |
| 458 for (const TestCase& test_case : kTestCases) { |
| 459 SCOPED_TRACE(base::StringPrintf("Origin=%s\nStage=%d\nHeaders:\n%s", |
| 460 test_origin.origin, test_case.stage, |
| 461 test_case.response_headers.c_str())); |
| 462 |
| 463 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 464 GURL(test_origin.origin), net::DEFAULT_PRIORITY, nullptr)); |
| 465 TestThrottle throttle(request.get(), |
| 466 base::MakeUnique<ConsoleMessagesDelegate>()); |
| 467 throttle.SetResponseHeaders(test_case.response_headers); |
| 468 |
| 469 MockResourceThrottleDelegate delegate; |
| 470 throttle.set_delegate_for_testing(&delegate); |
| 471 |
| 472 // Whether we should defer is always conditional on the origin |
| 473 // being valid. |
| 474 bool expected_defer = test_case.should_defer && test_origin.valid; |
| 475 |
| 476 // If we expect loading to be deferred, then we also expect data to be |
| 477 // cleared and the load to eventually resume. |
| 478 if (expected_defer) { |
| 479 testing::Expectation expectation = EXPECT_CALL( |
| 480 throttle, |
| 481 ClearSiteData(url::Origin(GURL(test_origin.origin)), _, _, _)); |
| 482 EXPECT_CALL(delegate, Resume()).After(expectation); |
| 483 } else { |
| 484 EXPECT_CALL(throttle, ClearSiteData(_, _, _, _)).Times(0); |
| 485 EXPECT_CALL(delegate, Resume()).Times(0); |
| 486 } |
| 487 |
| 488 bool actual_defer = false; |
| 489 |
| 490 switch (test_case.stage) { |
| 491 case START: { |
| 492 throttle.WillStartRequest(&actual_defer); |
| 493 break; |
| 494 } |
| 495 case REDIRECT: { |
| 496 net::RedirectInfo redirect_info; |
| 497 throttle.WillRedirectRequest(redirect_info, &actual_defer); |
| 498 break; |
| 499 } |
| 500 case RESPONSE: { |
| 501 throttle.WillProcessResponse(&actual_defer); |
| 502 break; |
| 503 } |
| 504 } |
| 505 |
| 506 EXPECT_EQ(expected_defer, actual_defer); |
| 507 testing::Mock::VerifyAndClearExpectations(&delegate); |
| 508 } |
| 509 } |
| 510 } |
| 511 |
| 512 // Verifies that console outputs from various actions on different URLs |
| 513 // are correctly pretty-printed to the console. |
| 514 TEST_F(ClearSiteDataThrottleTest, FormattedConsoleOutput) { |
| 515 struct TestCase { |
| 516 const char* header; |
| 517 const char* url; |
| 518 const char* output; |
| 519 } kTestCases[] = { |
| 520 // Successful deletion outputs one line. |
| 521 {"{ \"types\": [ \"cookies\" ] }", "https://origin1.com/foo", |
| 522 "Clear-Site-Data header on 'https://origin1.com/foo': " |
| 523 "Cleared data types: cookies.\n"}, |
| 524 |
| 525 // Another successful deletion. |
| 526 {"{ \"types\": [ \"storage\" ] }", "https://origin2.com/foo", |
| 527 "Clear-Site-Data header on 'https://origin2.com/foo': " |
| 528 "Cleared data types: storage.\n"}, |
| 529 |
| 530 // Redirect to the same URL. Unsuccessful deletion outputs two lines. |
| 531 {"{ \"foo\": \"bar\" }", "https://origin2.com/foo", |
| 532 "Clear-Site-Data header on 'https://origin2.com/foo': " |
| 533 "Expected a JSON dictionary with a 'types' field.\n"}, |
| 534 |
| 535 // Redirect to another URL. Another unsuccessful deletion. |
| 536 {"\"some text\"", "https://origin3.com/bar", |
| 537 "Clear-Site-Data header on 'https://origin3.com/bar': " |
| 538 "Expected a JSON dictionary with a 'types' field.\n"}, |
| 539 |
| 540 // Yet another on the same URL. |
| 541 {"{ \"types\" : [ \"passwords\" ] }", "https://origin3.com/bar", |
| 542 "Clear-Site-Data header on 'https://origin3.com/bar': " |
| 543 "Unrecognized type: \"passwords\".\n" |
| 544 "Clear-Site-Data header on 'https://origin3.com/bar': " |
| 545 "No recognized types specified in the 'types' field.\n"}, |
| 546 |
| 547 // Successful deletion on the same URL. |
| 548 {"{ \"types\": [ \"cache\" ] }", "https://origin3.com/bar", |
| 549 "Clear-Site-Data header on 'https://origin3.com/bar': " |
| 550 "Cleared data types: cache.\n"}, |
| 551 |
| 552 // Redirect to the original URL. |
| 553 // Successful deletion outputs one line. |
| 554 {"", "https://origin1.com/foo", |
| 555 "Clear-Site-Data header on 'https://origin1.com/foo': " |
| 556 "Expected valid JSON.\n"}}; |
| 557 |
| 558 bool kThrottleTypeIsNavigation[] = {true, false}; |
| 559 |
| 560 for (bool navigation : kThrottleTypeIsNavigation) { |
| 561 SCOPED_TRACE(navigation ? "Navigation test." : "Subresource test."); |
| 562 |
| 563 net::TestURLRequestContext context; |
| 564 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 565 GURL(kTestCases[0].url), net::DEFAULT_PRIORITY, nullptr)); |
| 566 ResourceRequestInfo::AllocateForTesting( |
| 567 request.get(), |
| 568 navigation ? RESOURCE_TYPE_SUB_FRAME : RESOURCE_TYPE_IMAGE, nullptr, 0, |
| 569 0, 0, false, true, true, true, false); |
| 570 |
| 571 std::string output_buffer; |
| 572 std::unique_ptr<RedirectableTestThrottle> throttle = |
| 573 base::MakeUnique<RedirectableTestThrottle>( |
| 574 request.get(), |
| 575 base::MakeUnique<StringConsoleMessagesDelegate>(&output_buffer)); |
| 576 |
| 577 MockResourceThrottleDelegate delegate; |
| 578 throttle->set_delegate_for_testing(&delegate); |
| 579 |
| 580 std::string last_seen_console_output; |
| 581 |
| 582 // Simulate redirecting the throttle through the above origins with the |
| 583 // corresponding response headers. |
| 584 bool defer; |
| 585 throttle->WillStartRequest(&defer); |
| 586 |
| 587 for (size_t i = 0; i < arraysize(kTestCases); i++) { |
| 588 throttle->SetResponseHeaders(std::string(kClearSiteDataHeaderPrefix) + |
| 589 kTestCases[i].header); |
| 590 |
| 591 // TODO(msramek): There is probably a better way to do this inside |
| 592 // URLRequest. |
| 593 throttle->SetCurrentURLForTesting(GURL(kTestCases[i].url)); |
| 594 |
| 595 net::RedirectInfo redirect_info; |
| 596 if (i < arraysize(kTestCases) - 1) |
| 597 throttle->WillRedirectRequest(redirect_info, &defer); |
| 598 else |
| 599 throttle->WillProcessResponse(&defer); |
| 600 |
| 601 // Wait for any messages to be output. |
| 602 WaitForUIThread(); |
| 603 |
| 604 // For navigations, the console should be still empty. For subresource |
| 605 // requests, messages should be added progressively. |
| 606 if (navigation) { |
| 607 EXPECT_TRUE(output_buffer.empty()); |
| 608 } else { |
| 609 EXPECT_EQ(last_seen_console_output + kTestCases[i].output, |
| 610 output_buffer); |
| 611 } |
| 612 |
| 613 last_seen_console_output = output_buffer; |
| 614 } |
| 615 |
| 616 throttle.reset(); |
| 617 WaitForUIThread(); |
| 618 |
| 619 // At the end, the console must contain all messages regardless of whether |
| 620 // it was a navigation or a subresource request. |
| 621 std::string expected_output; |
| 622 for (struct TestCase& test_case : kTestCases) |
| 623 expected_output += test_case.output; |
| 624 EXPECT_EQ(expected_output, output_buffer); |
| 625 } |
| 626 } |
| 627 |
133 } // namespace content | 628 } // namespace content |
OLD | NEW |