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/command_line.h" |
| 10 #include "base/memory/ptr_util.h" |
| 11 #include "base/memory/ref_counted.h" |
| 12 #include "base/message_loop/message_loop.h" |
| 13 #include "base/strings/stringprintf.h" |
| 14 #include "base/test/scoped_command_line.h" |
| 15 #include "content/common/net/url_request_service_worker_data.h" |
10 #include "content/public/common/content_switches.h" | 16 #include "content/public/common/content_switches.h" |
| 17 #include "net/base/load_flags.h" |
| 18 #include "net/http/http_util.h" |
| 19 #include "net/url_request/redirect_info.h" |
| 20 #include "net/url_request/url_request_job.h" |
| 21 #include "net/url_request/url_request_test_util.h" |
11 #include "testing/gmock/include/gmock/gmock.h" | 22 #include "testing/gmock/include/gmock/gmock.h" |
12 #include "testing/gtest/include/gtest/gtest.h" | 23 #include "testing/gtest/include/gtest/gtest.h" |
13 | 24 |
| 25 using ::testing::_; |
| 26 |
14 namespace content { | 27 namespace content { |
15 | 28 |
16 class ClearSiteDataThrottleTest : public testing::Test { | 29 namespace { |
| 30 |
| 31 static const char* kClearCookiesHeader = |
| 32 "Clear-Site-Data: { \"types\": [ \"cookies\" ] }"; |
| 33 |
| 34 // Used to verify that resource throttle delegate calls are made. |
| 35 class MockResourceThrottleDelegate : public ResourceThrottle::Delegate { |
17 public: | 36 public: |
18 void SetUp() override { | 37 MOCK_METHOD0(Cancel, void()); |
19 base::CommandLine::ForCurrentProcess()->AppendSwitch( | 38 MOCK_METHOD0(CancelAndIgnore, void()); |
20 switches::kEnableExperimentalWebPlatformFeatures); | 39 MOCK_METHOD1(CancelWithError, void(int)); |
21 throttle_ = ClearSiteDataThrottle::CreateThrottleForNavigation(nullptr); | 40 MOCK_METHOD0(Resume, void()); |
| 41 }; |
| 42 |
| 43 // A testing ConsoleMessagesDelegate that does not require valid WebContents |
| 44 // or thread jumping. |
| 45 class TestConsoleMessagesDelegate |
| 46 : public ClearSiteDataThrottle::ConsoleMessagesDelegate { |
| 47 public: |
| 48 TestConsoleMessagesDelegate() {} |
| 49 ~TestConsoleMessagesDelegate() override {} |
| 50 |
| 51 void OutputMessages(const ResourceRequestInfo::WebContentsGetter& |
| 52 web_contents_getter) override { |
| 53 // No valid WebContents in this unittest. |
| 54 } |
| 55 }; |
| 56 |
| 57 // A slightly modified ClearSiteDataThrottle for testing with unconditional |
| 58 // construction, injectable response headers, and dummy clearing functionality. |
| 59 class TestThrottle : public ClearSiteDataThrottle { |
| 60 public: |
| 61 TestThrottle(net::URLRequest* request, |
| 62 std::unique_ptr<TestConsoleMessagesDelegate> delegate) |
| 63 : ClearSiteDataThrottle(request, std::move(delegate)) {} |
| 64 ~TestThrottle() override {} |
| 65 |
| 66 void SetResponseHeaders(std::string headers) { |
| 67 headers = "HTTP/1.1 200\n" + headers; |
| 68 headers_ = new net::HttpResponseHeaders( |
| 69 net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size())); |
22 } | 70 } |
23 | 71 |
24 ClearSiteDataThrottle* GetThrottle() { | 72 MOCK_METHOD4(ClearSiteData, |
25 return static_cast<ClearSiteDataThrottle*>(throttle_.get()); | 73 void(const url::Origin& origin, |
| 74 bool clear_cookies, |
| 75 bool clear_storage, |
| 76 bool clear_cache)); |
| 77 |
| 78 protected: |
| 79 const net::HttpResponseHeaders* GetResponseHeaders() const override { |
| 80 return headers_.get(); |
| 81 } |
| 82 |
| 83 void ExecuteClearingTask(const url::Origin& origin, |
| 84 bool clear_cookies, |
| 85 bool clear_storage, |
| 86 bool clear_cache, |
| 87 const base::Closure& callback) override { |
| 88 ClearSiteData(origin, clear_cookies, clear_storage, clear_cache); |
| 89 |
| 90 callback.Run(); |
26 } | 91 } |
27 | 92 |
28 private: | 93 private: |
29 std::unique_ptr<NavigationThrottle> throttle_; | 94 scoped_refptr<net::HttpResponseHeaders> headers_; |
30 }; | 95 }; |
31 | 96 |
| 97 } // namespace |
| 98 |
| 99 class ClearSiteDataThrottleTest : public testing::Test { |
| 100 private: |
| 101 base::MessageLoop message_loop_; |
| 102 }; |
| 103 |
| 104 TEST_F(ClearSiteDataThrottleTest, CreateThrottleForRequest) { |
| 105 // Enable experimental features. |
| 106 std::unique_ptr<base::test::ScopedCommandLine> command_line( |
| 107 new base::test::ScopedCommandLine()); |
| 108 command_line->GetProcessCommandLine()->AppendSwitch( |
| 109 switches::kEnableExperimentalWebPlatformFeatures); |
| 110 |
| 111 // Create a URL request. |
| 112 GURL url("https://www.example.com"); |
| 113 net::TestURLRequestContext context; |
| 114 std::unique_ptr<net::URLRequest> request( |
| 115 context.CreateRequest(url, net::DEFAULT_PRIORITY, nullptr)); |
| 116 |
| 117 // We will not create the throttle for an empty ResourceRequestInfo. |
| 118 EXPECT_FALSE(ClearSiteDataThrottle::CreateThrottleForRequest(request.get())); |
| 119 |
| 120 // We can create the throttle for a valid ResourceRequestInfo. |
| 121 ResourceRequestInfo::AllocateForTesting(request.get(), RESOURCE_TYPE_IMAGE, |
| 122 nullptr, 0, 0, 0, false, true, true, |
| 123 true, false); |
| 124 EXPECT_TRUE(ClearSiteDataThrottle::CreateThrottleForRequest(request.get())); |
| 125 |
| 126 // But not if experimental web features are disabled again. |
| 127 request->SetLoadFlags(net::LOAD_NORMAL); |
| 128 command_line.reset(); |
| 129 EXPECT_FALSE(ClearSiteDataThrottle::CreateThrottleForRequest(request.get())); |
| 130 } |
| 131 |
32 TEST_F(ClearSiteDataThrottleTest, ParseHeader) { | 132 TEST_F(ClearSiteDataThrottleTest, ParseHeader) { |
33 struct TestCase { | 133 struct TestCase { |
34 const char* header; | 134 const char* header; |
35 bool cookies; | 135 bool cookies; |
36 bool storage; | 136 bool storage; |
37 bool cache; | 137 bool cache; |
38 } test_cases[] = { | 138 } test_cases[] = { |
39 // One data type. | 139 // One data type. |
40 {"{ \"types\": [\"cookies\"] }", true, false, false}, | 140 {"{ \"types\": [\"cookies\"] }", true, false, false}, |
41 {"{ \"types\": [\"storage\"] }", false, true, false}, | 141 {"{ \"types\": [\"storage\"] }", false, true, false}, |
(...skipping 27 matching lines...) Expand all Loading... |
69 {"{ \"types\": [\"cache\", \"foo\"] }", false, false, true}, | 169 {"{ \"types\": [\"cache\", \"foo\"] }", false, false, true}, |
70 }; | 170 }; |
71 | 171 |
72 for (const TestCase& test_case : test_cases) { | 172 for (const TestCase& test_case : test_cases) { |
73 SCOPED_TRACE(test_case.header); | 173 SCOPED_TRACE(test_case.header); |
74 | 174 |
75 bool actual_cookies; | 175 bool actual_cookies; |
76 bool actual_storage; | 176 bool actual_storage; |
77 bool actual_cache; | 177 bool actual_cache; |
78 | 178 |
79 std::vector<ClearSiteDataThrottle::ConsoleMessage> messages; | 179 TestConsoleMessagesDelegate console_delegate; |
80 | 180 |
81 EXPECT_TRUE(GetThrottle()->ParseHeader(test_case.header, &actual_cookies, | 181 EXPECT_TRUE(ClearSiteDataThrottle::ParseHeader( |
82 &actual_storage, &actual_cache, | 182 test_case.header, &actual_cookies, &actual_storage, &actual_cache, |
83 &messages)); | 183 &console_delegate, GURL())); |
84 | 184 |
85 EXPECT_EQ(test_case.cookies, actual_cookies); | 185 EXPECT_EQ(test_case.cookies, actual_cookies); |
86 EXPECT_EQ(test_case.storage, actual_storage); | 186 EXPECT_EQ(test_case.storage, actual_storage); |
87 EXPECT_EQ(test_case.cache, actual_cache); | 187 EXPECT_EQ(test_case.cache, actual_cache); |
88 } | 188 } |
89 } | 189 } |
90 | 190 |
91 TEST_F(ClearSiteDataThrottleTest, InvalidHeader) { | 191 TEST_F(ClearSiteDataThrottleTest, InvalidHeader) { |
92 struct TestCase { | 192 struct TestCase { |
93 const char* header; | 193 const char* header; |
94 const char* console_message; | 194 const char* console_message; |
95 } test_cases[] = { | 195 } test_cases[] = { |
96 {"", "Not a valid JSON.\n"}, | 196 {"", "Not a valid JSON.\n"}, |
97 {"\"unclosed quote", "Not a valid JSON.\n"}, | 197 {"\"unclosed quote", "Not a valid JSON.\n"}, |
98 {"\"some text\"", "Expecting a JSON dictionary with a 'types' field.\n"}, | 198 {"\"some text\"", "Expecting a JSON dictionary with a 'types' field.\n"}, |
99 {"{ \"field\" : {} }", | 199 {"{ \"field\" : {} }", |
100 "Expecting a JSON dictionary with a 'types' field.\n"}, | 200 "Expecting a JSON dictionary with a 'types' field.\n"}, |
101 {"{ \"types\" : [ \"passwords\" ] }", | 201 {"{ \"types\" : [ \"passwords\" ] }", |
102 "Invalid type: \"passwords\".\n" | 202 "Unrecognized type: \"passwords\".\n" |
103 "No valid types specified in the 'types' field.\n"}, | 203 "No recognized types specified in the 'types' field.\n"}, |
104 {"{ \"types\" : [ [ \"list in a list\" ] ] }", | 204 {"{ \"types\" : [ [ \"list in a list\" ] ] }", |
105 "Invalid type: [\"list in a list\"].\n" | 205 "Unrecognized type: [\"list in a list\"].\n" |
106 "No valid types specified in the 'types' field.\n"}, | 206 "No recognized types specified in the 'types' field.\n"}, |
107 {"{ \"types\" : [ \"кукис\", \"сторидж\", \"кэш\" ]", | 207 {"{ \"types\" : [ \"кукис\", \"сторидж\", \"кэш\" ]", |
108 "Must only contain ASCII characters.\n"}}; | 208 "Must only contain ASCII characters.\n"}}; |
109 | 209 |
110 for (const TestCase& test_case : test_cases) { | 210 for (const TestCase& test_case : test_cases) { |
111 SCOPED_TRACE(test_case.header); | 211 SCOPED_TRACE(test_case.header); |
112 | 212 |
113 bool actual_cookies; | 213 bool actual_cookies; |
114 bool actual_storage; | 214 bool actual_storage; |
115 bool actual_cache; | 215 bool actual_cache; |
116 | 216 |
117 std::vector<ClearSiteDataThrottle::ConsoleMessage> messages; | 217 TestConsoleMessagesDelegate console_delegate; |
118 | 218 |
119 EXPECT_FALSE(GetThrottle()->ParseHeader(test_case.header, &actual_cookies, | 219 EXPECT_FALSE(ClearSiteDataThrottle::ParseHeader( |
120 &actual_storage, &actual_cache, | 220 test_case.header, &actual_cookies, &actual_storage, &actual_cache, |
121 &messages)); | 221 &console_delegate, GURL())); |
122 | 222 |
123 std::string multiline_message; | 223 std::string multiline_message; |
124 for (const auto& message : messages) { | 224 for (const auto& message : console_delegate.messages()) { |
125 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, message.level); | 225 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, message.level); |
126 multiline_message += message.text + "\n"; | 226 multiline_message += message.text + "\n"; |
127 } | 227 } |
128 | 228 |
129 EXPECT_EQ(test_case.console_message, multiline_message); | 229 EXPECT_EQ(test_case.console_message, multiline_message); |
130 } | 230 } |
131 } | 231 } |
132 | 232 |
| 233 TEST_F(ClearSiteDataThrottleTest, LoadDoNotSaveCookies) { |
| 234 net::TestURLRequestContext context; |
| 235 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 236 GURL("https://www.example.com"), net::DEFAULT_PRIORITY, nullptr)); |
| 237 std::unique_ptr<TestConsoleMessagesDelegate> scoped_console_delegate( |
| 238 new TestConsoleMessagesDelegate()); |
| 239 const TestConsoleMessagesDelegate* console_delegate = |
| 240 scoped_console_delegate.get(); |
| 241 TestThrottle throttle(request.get(), std::move(scoped_console_delegate)); |
| 242 MockResourceThrottleDelegate delegate; |
| 243 throttle.set_delegate_for_testing(&delegate); |
| 244 throttle.SetResponseHeaders(kClearCookiesHeader); |
| 245 |
| 246 bool defer; |
| 247 throttle.WillProcessResponse(&defer); |
| 248 EXPECT_TRUE(defer); |
| 249 EXPECT_EQ(1u, console_delegate->messages().size()); |
| 250 EXPECT_EQ("Clearing cookies.", console_delegate->messages().front().text); |
| 251 EXPECT_EQ(console_delegate->messages().front().level, |
| 252 CONSOLE_MESSAGE_LEVEL_INFO); |
| 253 |
| 254 request->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); |
| 255 throttle.WillProcessResponse(&defer); |
| 256 EXPECT_FALSE(defer); |
| 257 EXPECT_EQ(2u, console_delegate->messages().size()); |
| 258 EXPECT_EQ( |
| 259 "The request's credentials mode prohibits modifying cookies " |
| 260 "and other local data.", |
| 261 console_delegate->messages().rbegin()->text); |
| 262 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, |
| 263 console_delegate->messages().rbegin()->level); |
| 264 } |
| 265 |
| 266 TEST_F(ClearSiteDataThrottleTest, InvalidOrigin) { |
| 267 struct TestCase { |
| 268 const char* origin; |
| 269 std::string error_message; |
| 270 } kTestCases[] = { |
| 271 // The throttle only works on secure origins. |
| 272 {"https://secure-origin.com", ""}, |
| 273 {"filesystem:https://secure-origin.com/temporary/", ""}, |
| 274 |
| 275 // That includes localhost. |
| 276 {"http://localhost", ""}, |
| 277 |
| 278 // Not on insecure origins. |
| 279 {"http://insecure-origin.com", "Not supported for insecure origins."}, |
| 280 {"filesystem:http://insecure-origin.com/temporary/", |
| 281 "Not supported for insecure origins."}, |
| 282 |
| 283 // Not on unique origins. |
| 284 {"file:///foo/bar.txt", "Not supported for unique origins."}, |
| 285 }; |
| 286 |
| 287 net::TestURLRequestContext context; |
| 288 |
| 289 for (const TestCase& test_case : kTestCases) { |
| 290 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 291 GURL(test_case.origin), net::DEFAULT_PRIORITY, nullptr)); |
| 292 std::unique_ptr<TestConsoleMessagesDelegate> scoped_console_delegate( |
| 293 new TestConsoleMessagesDelegate()); |
| 294 const TestConsoleMessagesDelegate* console_delegate = |
| 295 scoped_console_delegate.get(); |
| 296 TestThrottle throttle(request.get(), std::move(scoped_console_delegate)); |
| 297 MockResourceThrottleDelegate delegate; |
| 298 throttle.set_delegate_for_testing(&delegate); |
| 299 throttle.SetResponseHeaders(kClearCookiesHeader); |
| 300 |
| 301 bool defer; |
| 302 throttle.WillProcessResponse(&defer); |
| 303 |
| 304 EXPECT_EQ(console_delegate->messages().size(), 1u); |
| 305 if (!defer) { |
| 306 EXPECT_EQ(test_case.error_message, |
| 307 console_delegate->messages().front().text); |
| 308 EXPECT_EQ(CONSOLE_MESSAGE_LEVEL_ERROR, |
| 309 console_delegate->messages().front().level); |
| 310 } |
| 311 } |
| 312 } |
| 313 |
| 314 TEST_F(ClearSiteDataThrottleTest, DeferAndResume) { |
| 315 enum Stage { START, REDIRECT, RESPONSE }; |
| 316 |
| 317 struct TestCase { |
| 318 Stage stage; |
| 319 std::string response_headers; |
| 320 bool should_defer; |
| 321 } kTestCases[] = { |
| 322 // The throttle never interferes while the request is starting. Response |
| 323 // headers are ignored, because URLRequest is not supposed to have any |
| 324 // at this stage in the first place. |
| 325 {START, "", false}, |
| 326 {START, kClearCookiesHeader, false}, |
| 327 |
| 328 // The throttle does not defer redirects if there are no interesting |
| 329 // response headers. |
| 330 {REDIRECT, "", false}, |
| 331 {REDIRECT, "Set-Cookie: abc=123;", false}, |
| 332 {REDIRECT, "Content-Type: image/png;", false}, |
| 333 |
| 334 // That includes malformed Clear-Site-Data headers or header values |
| 335 // that do not lead to deletion. |
| 336 {REDIRECT, "Clear-Site-Data: { types: cookies } ", false}, |
| 337 {REDIRECT, "Clear-Site-Data: { \"types\": [ \"unknown type\" ] }", false}, |
| 338 |
| 339 // However, redirects are deferred for valid Clear-Site-Data headers. |
| 340 {REDIRECT, |
| 341 "Clear-Site-Data: { \"types\": [ \"cookies\", \"unknown type\" ] }", |
| 342 true}, |
| 343 {REDIRECT, |
| 344 base::StringPrintf("Content-Type: image/png;\n%s", kClearCookiesHeader), |
| 345 true}, |
| 346 {REDIRECT, |
| 347 base::StringPrintf("%s\nContent-Type: image/png;", kClearCookiesHeader), |
| 348 true}, |
| 349 |
| 350 // We expect at most one instance of the header. Multiple instances |
| 351 // will not be parsed currently. This is not an inherent property of |
| 352 // Clear-Site-Data, just a documentation of the current behavior. |
| 353 {REDIRECT, |
| 354 base::StringPrintf("%s\n%s", kClearCookiesHeader, kClearCookiesHeader), |
| 355 false}, |
| 356 |
| 357 // Final response headers are treated the same way as in the case |
| 358 // of redirect. |
| 359 {REDIRECT, "Set-Cookie: abc=123;", false}, |
| 360 {REDIRECT, "Clear-Site-Data: { types: cookies } ", false}, |
| 361 {REDIRECT, kClearCookiesHeader, true}, |
| 362 }; |
| 363 |
| 364 struct TestOrigin { |
| 365 const char* origin; |
| 366 bool valid; |
| 367 } kTestOrigins[] = { |
| 368 // The throttle only works on secure origins. |
| 369 {"https://secure-origin.com", true}, |
| 370 {"filesystem:https://secure-origin.com/temporary/", true}, |
| 371 |
| 372 // That includes localhost. |
| 373 {"http://localhost", true}, |
| 374 |
| 375 // Not on insecure origins. |
| 376 {"http://insecure-origin.com", false}, |
| 377 {"filesystem:http://insecure-origin.com/temporary/", false}, |
| 378 |
| 379 // Not on unique origins. |
| 380 {"data:unique-origin;", false}, |
| 381 }; |
| 382 |
| 383 net::TestURLRequestContext context; |
| 384 |
| 385 for (const TestOrigin& test_origin : kTestOrigins) { |
| 386 for (const TestCase& test_case : kTestCases) { |
| 387 SCOPED_TRACE(base::StringPrintf("Origin=%s\nStage=%d\nHeaders:\n%s", |
| 388 test_origin.origin, test_case.stage, |
| 389 test_case.response_headers.c_str())); |
| 390 |
| 391 std::unique_ptr<net::URLRequest> request(context.CreateRequest( |
| 392 GURL(test_origin.origin), net::DEFAULT_PRIORITY, nullptr)); |
| 393 TestThrottle throttle(request.get(), |
| 394 base::MakeUnique<TestConsoleMessagesDelegate>()); |
| 395 throttle.SetResponseHeaders(test_case.response_headers); |
| 396 |
| 397 MockResourceThrottleDelegate delegate; |
| 398 throttle.set_delegate_for_testing(&delegate); |
| 399 |
| 400 // Whether we should defer is always conditional on the origin |
| 401 // being valid. |
| 402 bool expected_defer = test_case.should_defer && test_origin.valid; |
| 403 |
| 404 // If we expect loading to be deferred, then we also expect data to be |
| 405 // cleared and the load to eventually resume. |
| 406 if (expected_defer) { |
| 407 testing::Expectation e = EXPECT_CALL( |
| 408 throttle, |
| 409 ClearSiteData(url::Origin(GURL(test_origin.origin)), _, _, _)); |
| 410 EXPECT_CALL(delegate, Resume()).After(e); |
| 411 } else { |
| 412 EXPECT_CALL(throttle, ClearSiteData(_, _, _, _)).Times(0); |
| 413 EXPECT_CALL(delegate, Resume()).Times(0); |
| 414 } |
| 415 |
| 416 bool actual_defer = false; |
| 417 |
| 418 switch (test_case.stage) { |
| 419 case START: { |
| 420 throttle.WillStartRequest(&actual_defer); |
| 421 break; |
| 422 } |
| 423 case REDIRECT: { |
| 424 net::RedirectInfo redirect_info; |
| 425 throttle.WillRedirectRequest(redirect_info, &actual_defer); |
| 426 break; |
| 427 } |
| 428 case RESPONSE: { |
| 429 throttle.WillProcessResponse(&actual_defer); |
| 430 break; |
| 431 } |
| 432 } |
| 433 |
| 434 EXPECT_EQ(expected_defer, actual_defer); |
| 435 testing::Mock::VerifyAndClearExpectations(&delegate); |
| 436 } |
| 437 } |
| 438 } |
| 439 |
133 } // namespace content | 440 } // namespace content |
OLD | NEW |