Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(208)

Side by Side Diff: content/browser/browsing_data/clear_site_data_throttle_unittest.cc

Issue 2368923003: Support the Clear-Site-Data header on resource requests (Closed)
Patch Set: Addressed comments, added some debug outputs. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 =
mmenke 2017/05/22 19:36:09 nit: static not needed, "const char kClearCookies
msramek 2017/05/24 22:59:52 Done. Changed here as well as in clear_site_data_t
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 {
mmenke 2017/05/22 19:36:09 Is this class needed? The production one already
msramek 2017/05/24 22:59:52 Removed. Thanks for noticing.
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) {
mmenke 2017/05/22 19:36:09 nit: const std::string&
msramek 2017/05/24 22:59:52 Done.
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_;
mmenke 2017/05/22 19:36:09 I think there's now a more task-runner friendly wa
msramek 2017/05/24 22:59:52 Done.
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
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 }
mmenke 2017/05/22 19:36:09 optional: Hrm...There's no testing of the output
msramek 2017/05/24 22:59:52 Do you mean OutputMessagesOnUIThread()? I must adm
mmenke 2017/05/25 15:19:01 Sorry, I did indeed mean OutputMessagesOnUIThread.
msramek 2017/05/30 21:58:44 Oh, that's what you mean. OK, I added a unittest f
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);
mmenke 2017/05/22 19:36:09 Why is the size 1 on secure origins, and not 0?
msramek 2017/05/24 22:59:52 The throttle also outputs a message on successful
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(
mmenke 2017/05/22 19:36:09 This name violates the google style guide - should
msramek 2017/05/24 22:59:52 Fixed. Sorry for that.
408 throttle,
409 ClearSiteData(url::Origin(GURL(test_origin.origin)), _, _, _));
mmenke 2017/05/22 19:36:09 I'm not going to block on this, but GMOCK syntax i
msramek 2017/05/24 22:59:52 In that case I'd prefer to keep it as is - as usua
mmenke 2017/05/25 15:19:01 Not going to advocate any more for getting rid of
410 EXPECT_CALL(delegate, Resume()).After(e);
411 } else {
412 EXPECT_CALL(throttle, ClearSiteData(_, _, _, _)).Times(0);
mmenke 2017/05/22 19:36:09 I don't think we ever check that ClearSiteData is
msramek 2017/05/24 22:59:52 I expanded the ParseHeader test, since that one te
mmenke 2017/05/25 15:19:01 True. My feeling is just that we should test indi
msramek 2017/05/30 21:58:44 Acknowledged. Fair enough.
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698