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 "content/browser/browsing_data/clear_site_data_handler.h"
6
7 #include <memory>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/run_loop.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/test/scoped_command_line.h"
15 #include "base/test/task_environment.h"
16 #include "content/public/common/content_switches.h"
17 #include "content/public/test/browser_task_environment.h"
18 #include "net/base/load_flags.h"
19 #include "net/http/http_util.h"
20 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
21 #include "net/url_request/redirect_info.h"
22 #include "net/url_request/url_request_job.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 using ::testing::_;
28
29 namespace content {
30
31 using ConsoleMessagesDelegate = ClearSiteDataHandler::ConsoleMessagesDelegate;
32 using Message = ClearSiteDataHandler::ConsoleMessagesDelegate::Message;
33
34 namespace {
35
36 const char kClearCookiesHeader[] = "\"cookies\"";
37
FakeBrowserContextGetter()38 BrowserContext* FakeBrowserContextGetter() {
39 return nullptr;
40 }
41
FakeWebContentsGetter()42 WebContents* FakeWebContentsGetter() {
43 return nullptr;
44 }
45
46 // A slightly modified ClearSiteDataHandler for testing with dummy clearing
47 // functionality.
48 class TestHandler : public ClearSiteDataHandler {
49 public:
TestHandler(base::RepeatingCallback<BrowserContext * ()> browser_context_getter,base::RepeatingCallback<WebContents * ()> web_contents_getter,const GURL & url,const std::string & header_value,int load_flags,base::OnceClosure callback,std::unique_ptr<ConsoleMessagesDelegate> delegate)50 TestHandler(base::RepeatingCallback<BrowserContext*()> browser_context_getter,
51 base::RepeatingCallback<WebContents*()> web_contents_getter,
52 const GURL& url,
53 const std::string& header_value,
54 int load_flags,
55 base::OnceClosure callback,
56 std::unique_ptr<ConsoleMessagesDelegate> delegate)
57 : ClearSiteDataHandler(browser_context_getter,
58 web_contents_getter,
59 url,
60 header_value,
61 load_flags,
62 std::move(callback),
63 std::move(delegate)) {}
64 ~TestHandler() override = default;
65
66 // |HandleHeaderAndOutputConsoleMessages()| is protected and not visible in
67 // test cases.
DoHandleHeader()68 bool DoHandleHeader() { return HandleHeaderAndOutputConsoleMessages(); }
69
70 MOCK_METHOD4(ClearSiteData,
71 void(const url::Origin& origin,
72 bool clear_cookies,
73 bool clear_storage,
74 bool clear_cache));
75
76 protected:
ExecuteClearingTask(const url::Origin & origin,bool clear_cookies,bool clear_storage,bool clear_cache,base::OnceClosure callback)77 void ExecuteClearingTask(const url::Origin& origin,
78 bool clear_cookies,
79 bool clear_storage,
80 bool clear_cache,
81 base::OnceClosure callback) override {
82 ClearSiteData(origin, clear_cookies, clear_storage, clear_cache);
83
84 // NOTE: ResourceThrottle expects Resume() to be called asynchronously.
85 // For the purposes of this test, synchronous call works correctly, and
86 // is preferable for simplicity, so that we don't have to synchronize
87 // between triggering Clear-Site-Data and verifying test expectations.
88 std::move(callback).Run();
89 }
90 };
91
92 // A ConsoleDelegate that copies message to a vector |message_buffer| owned by
93 // the caller instead of outputs to the console.
94 // We need this override because otherwise messages are outputted as soon as
95 // request finished, and we don't have a chance to check them.
96 class VectorConsoleMessagesDelegate : public ConsoleMessagesDelegate {
97 public:
VectorConsoleMessagesDelegate(std::vector<Message> * message_buffer)98 VectorConsoleMessagesDelegate(std::vector<Message>* message_buffer)
99 : message_buffer_(message_buffer) {}
100 ~VectorConsoleMessagesDelegate() override = default;
101
OutputMessages(const base::RepeatingCallback<WebContents * ()> & web_contents_getter)102 void OutputMessages(const base::RepeatingCallback<WebContents*()>&
103 web_contents_getter) override {
104 *message_buffer_ = GetMessagesForTesting();
105 }
106
107 private:
108 std::vector<Message>* message_buffer_;
109 };
110
111 // A ConsoleDelegate that outputs messages to a string |output_buffer| owned
112 // by the caller instead of to the console (losing the level information).
113 class StringConsoleMessagesDelegate : public ConsoleMessagesDelegate {
114 public:
StringConsoleMessagesDelegate(std::string * output_buffer)115 StringConsoleMessagesDelegate(std::string* output_buffer) {
116 SetOutputFormattedMessageFunctionForTesting(base::BindRepeating(
117 &StringConsoleMessagesDelegate::OutputFormattedMessage,
118 base::Unretained(output_buffer)));
119 }
120
~StringConsoleMessagesDelegate()121 ~StringConsoleMessagesDelegate() override {}
122
123 private:
OutputFormattedMessage(std::string * output_buffer,WebContents * web_contents,blink::mojom::ConsoleMessageLevel level,const std::string & formatted_text)124 static void OutputFormattedMessage(std::string* output_buffer,
125 WebContents* web_contents,
126 blink::mojom::ConsoleMessageLevel level,
127 const std::string& formatted_text) {
128 *output_buffer += formatted_text + "\n";
129 }
130 };
131
132 } // namespace
133
134 class ClearSiteDataHandlerTest : public testing::Test {
135 public:
ClearSiteDataHandlerTest()136 ClearSiteDataHandlerTest()
137 : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP) {}
138
139 private:
140 BrowserTaskEnvironment task_environment_;
141
142 DISALLOW_COPY_AND_ASSIGN(ClearSiteDataHandlerTest);
143 };
144
TEST_F(ClearSiteDataHandlerTest,ParseHeaderAndExecuteClearingTask)145 TEST_F(ClearSiteDataHandlerTest, ParseHeaderAndExecuteClearingTask) {
146 struct TestCase {
147 const char* header;
148 bool cookies;
149 bool storage;
150 bool cache;
151 };
152
153 std::vector<TestCase> standard_test_cases = {
154 // One data type.
155 {"\"cookies\"", true, false, false},
156 {"\"storage\"", false, true, false},
157 {"\"cache\"", false, false, true},
158
159 // Two data types.
160 {"\"cookies\", \"storage\"", true, true, false},
161 {"\"cookies\", \"cache\"", true, false, true},
162 {"\"storage\", \"cache\"", false, true, true},
163
164 // Three data types.
165 {"\"storage\", \"cache\", \"cookies\"", true, true, true},
166 {"\"cache\", \"cookies\", \"storage\"", true, true, true},
167 {"\"cookies\", \"storage\", \"cache\"", true, true, true},
168
169 // The wildcard datatype is not yet shipped.
170 {"\"*\", \"storage\"", false, true, false},
171 {"\"cookies\", \"*\", \"storage\"", true, true, false},
172 {"\"*\", \"cookies\", \"*\"", true, false, false},
173
174 // Different formatting.
175 {"\"cookies\"", true, false, false},
176
177 // Duplicates.
178 {"\"cookies\", \"cookies\"", true, false, false},
179
180 // Other JSON-formatted items in the list.
181 {"\"storage\", { \"other_params\": {} }", false, true, false},
182
183 // Unknown types are ignored, but we still proceed with the deletion for
184 // those that we recognize.
185 {"\"cache\", \"foo\"", false, false, true},
186 };
187
188 std::vector<TestCase> experimental_test_cases = {
189 // Wildcard.
190 {"\"*\"", true, true, true},
191 {"\"*\", \"storage\"", true, true, true},
192 {"\"cache\", \"*\", \"storage\"", true, true, true},
193 {"\"*\", \"cookies\", \"*\"", true, true, true},
194 };
195
196 const std::vector<TestCase>* test_case_sets[] = {&standard_test_cases,
197 &experimental_test_cases};
198
199 for (const std::vector<TestCase>* test_cases : test_case_sets) {
200 base::test::ScopedCommandLine scoped_command_line;
201 if (test_cases == &experimental_test_cases) {
202 scoped_command_line.GetProcessCommandLine()->AppendSwitch(
203 switches::kEnableExperimentalWebPlatformFeatures);
204 }
205
206 for (const TestCase& test_case : *test_cases) {
207 SCOPED_TRACE(test_case.header);
208
209 // Test that ParseHeader works correctly.
210 bool actual_cookies;
211 bool actual_storage;
212 bool actual_cache;
213
214 GURL url("https://example.com");
215 ConsoleMessagesDelegate console_delegate;
216
217 EXPECT_TRUE(ClearSiteDataHandler::ParseHeaderForTesting(
218 test_case.header, &actual_cookies, &actual_storage, &actual_cache,
219 &console_delegate, url));
220
221 EXPECT_EQ(test_case.cookies, actual_cookies);
222 EXPECT_EQ(test_case.storage, actual_storage);
223 EXPECT_EQ(test_case.cache, actual_cache);
224
225 // Test that a call with the above parameters actually reaches
226 // ExecuteClearingTask().
227 net::TestURLRequestContext context;
228 std::unique_ptr<net::URLRequest> request(context.CreateRequest(
229 url, net::DEFAULT_PRIORITY, nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
230 TestHandler handler(base::BindRepeating(&FakeBrowserContextGetter),
231 base::BindRepeating(&FakeWebContentsGetter),
232 request->url(), test_case.header,
233 request->load_flags(), base::DoNothing(),
234 std::make_unique<ConsoleMessagesDelegate>());
235
236 EXPECT_CALL(handler,
237 ClearSiteData(url::Origin::Create(url), test_case.cookies,
238 test_case.storage, test_case.cache));
239 bool defer = handler.DoHandleHeader();
240 EXPECT_TRUE(defer);
241
242 testing::Mock::VerifyAndClearExpectations(&handler);
243 }
244 }
245 }
246
TEST_F(ClearSiteDataHandlerTest,InvalidHeader)247 TEST_F(ClearSiteDataHandlerTest, InvalidHeader) {
248 struct TestCase {
249 const char* header;
250 const char* console_message;
251 } test_cases[] = {{"", "No recognized types specified.\n"},
252 {"\"unclosed",
253 "Unrecognized type: \"unclosed.\n"
254 "No recognized types specified.\n"},
255 {"\"passwords\"",
256 "Unrecognized type: \"passwords\".\n"
257 "No recognized types specified.\n"},
258 // The wildcard datatype is not yet shipped.
259 {"[ \"*\" ]",
260 "Unrecognized type: [ \"*\" ].\n"
261 "No recognized types specified.\n"},
262 {"[ \"list\" ]",
263 "Unrecognized type: [ \"list\" ].\n"
264 "No recognized types specified.\n"},
265 {"{ \"cookies\": [ \"a\" ] }",
266 "Unrecognized type: { \"cookies\": [ \"a\" ] }.\n"
267 "No recognized types specified.\n"},
268 {"\"кукис\", \"сторидж\", \"кэш\"",
269 "Must only contain ASCII characters.\n"}};
270
271 for (const TestCase& test_case : test_cases) {
272 SCOPED_TRACE(test_case.header);
273
274 bool actual_cookies;
275 bool actual_storage;
276 bool actual_cache;
277
278 ConsoleMessagesDelegate console_delegate;
279
280 EXPECT_FALSE(ClearSiteDataHandler::ParseHeaderForTesting(
281 test_case.header, &actual_cookies, &actual_storage, &actual_cache,
282 &console_delegate, GURL()));
283
284 std::string multiline_message;
285 for (const auto& message : console_delegate.GetMessagesForTesting()) {
286 EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, message.level);
287 multiline_message += message.text + "\n";
288 }
289
290 EXPECT_EQ(test_case.console_message, multiline_message);
291 }
292 }
293
TEST_F(ClearSiteDataHandlerTest,ClearCookieSuccess)294 TEST_F(ClearSiteDataHandlerTest, ClearCookieSuccess) {
295 net::TestURLRequestContext context;
296 std::unique_ptr<net::URLRequest> request(
297 context.CreateRequest(GURL("https://example.com"), net::DEFAULT_PRIORITY,
298 nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
299 std::vector<Message> message_buffer;
300 TestHandler handler(
301 base::BindRepeating(&FakeBrowserContextGetter),
302 base::BindRepeating(&FakeWebContentsGetter), request->url(),
303 kClearCookiesHeader, request->load_flags(), base::DoNothing(),
304 std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));
305
306 EXPECT_CALL(handler, ClearSiteData(_, _, _, _));
307 bool defer = handler.DoHandleHeader();
308 EXPECT_TRUE(defer);
309 EXPECT_EQ(1u, message_buffer.size());
310 EXPECT_EQ(
311 "Cleared data types: \"cookies\". "
312 "Clearing channel IDs and HTTP authentication cache is currently "
313 "not supported, as it breaks active network connections.",
314 message_buffer.front().text);
315 EXPECT_EQ(message_buffer.front().level,
316 blink::mojom::ConsoleMessageLevel::kInfo);
317 testing::Mock::VerifyAndClearExpectations(&handler);
318 }
319
TEST_F(ClearSiteDataHandlerTest,LoadDoNotSaveCookies)320 TEST_F(ClearSiteDataHandlerTest, LoadDoNotSaveCookies) {
321 net::TestURLRequestContext context;
322 std::unique_ptr<net::URLRequest> request(
323 context.CreateRequest(GURL("https://example.com"), net::DEFAULT_PRIORITY,
324 nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
325 request->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
326 std::vector<Message> message_buffer;
327 TestHandler handler(
328 base::BindRepeating(&FakeBrowserContextGetter),
329 base::BindRepeating(&FakeWebContentsGetter), request->url(),
330 kClearCookiesHeader, request->load_flags(), base::DoNothing(),
331 std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));
332
333 EXPECT_CALL(handler, ClearSiteData(_, _, _, _)).Times(0);
334 bool defer = handler.DoHandleHeader();
335 EXPECT_FALSE(defer);
336 EXPECT_EQ(1u, message_buffer.size());
337 EXPECT_EQ(
338 "The request's credentials mode prohibits modifying cookies "
339 "and other local data.",
340 message_buffer.front().text);
341 EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
342 message_buffer.front().level);
343 testing::Mock::VerifyAndClearExpectations(&handler);
344 }
345
TEST_F(ClearSiteDataHandlerTest,InvalidOrigin)346 TEST_F(ClearSiteDataHandlerTest, InvalidOrigin) {
347 struct TestCase {
348 const char* origin;
349 bool expect_success;
350 std::string error_message; // Tested only if |expect_success| = false.
351 } kTestCases[] = {
352 // The handler only works on secure origins.
353 {"https://secure-origin.com", true, ""},
354 {"filesystem:https://secure-origin.com/temporary/", true, ""},
355
356 // That includes localhost.
357 {"http://localhost", true, ""},
358
359 // Not on insecure origins.
360 {"http://insecure-origin.com", false,
361 "Not supported for insecure origins."},
362 {"filesystem:http://insecure-origin.com/temporary/", false,
363 "Not supported for insecure origins."},
364
365 // Not on unique origins.
366 {"data:unique-origin;", false, "Not supported for unique origins."},
367 };
368
369 net::TestURLRequestContext context;
370
371 for (const TestCase& test_case : kTestCases) {
372 std::unique_ptr<net::URLRequest> request(
373 context.CreateRequest(GURL(test_case.origin), net::DEFAULT_PRIORITY,
374 nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
375 std::vector<Message> message_buffer;
376 TestHandler handler(
377 base::BindRepeating(&FakeBrowserContextGetter),
378 base::BindRepeating(&FakeWebContentsGetter), request->url(),
379 kClearCookiesHeader, request->load_flags(), base::DoNothing(),
380 std::make_unique<VectorConsoleMessagesDelegate>(&message_buffer));
381
382 EXPECT_CALL(handler, ClearSiteData(_, _, _, _))
383 .Times(test_case.expect_success ? 1 : 0);
384
385 bool defer = handler.DoHandleHeader();
386
387 EXPECT_EQ(defer, test_case.expect_success);
388 EXPECT_EQ(message_buffer.size(), 1u);
389 EXPECT_EQ(test_case.expect_success
390 ? blink::mojom::ConsoleMessageLevel::kInfo
391 : blink::mojom::ConsoleMessageLevel::kError,
392 message_buffer.front().level);
393 if (!test_case.expect_success) {
394 EXPECT_EQ(test_case.error_message, message_buffer.front().text);
395 }
396 testing::Mock::VerifyAndClearExpectations(&handler);
397 }
398 }
399
400 // Verifies that console outputs from various actions on different URLs
401 // are correctly pretty-printed to the console.
TEST_F(ClearSiteDataHandlerTest,FormattedConsoleOutput)402 TEST_F(ClearSiteDataHandlerTest, FormattedConsoleOutput) {
403 struct TestCase {
404 const char* header;
405 const char* url;
406 const char* output;
407 } kTestCases[] = {
408 // Successful deletion outputs one line, and in case of cookies, also
409 // a disclaimer about omitted data (https://crbug.com/798760).
410 {"\"cookies\"", "https://origin1.com/foo",
411 "Clear-Site-Data header on 'https://origin1.com/foo': "
412 "Cleared data types: \"cookies\". "
413 "Clearing channel IDs and HTTP authentication cache is currently "
414 "not supported, as it breaks active network connections.\n"},
415
416 // Another successful deletion.
417 {"\"storage\"", "https://origin2.com/foo",
418 "Clear-Site-Data header on 'https://origin2.com/foo': "
419 "Cleared data types: \"storage\".\n"},
420
421 // Redirect to the same URL. Unsuccessful deletion outputs two lines.
422 {"\"foo\"", "https://origin2.com/foo",
423 "Clear-Site-Data header on 'https://origin2.com/foo': "
424 "Unrecognized type: \"foo\".\n"
425 "Clear-Site-Data header on 'https://origin2.com/foo': "
426 "No recognized types specified.\n"},
427
428 // Redirect to another URL. Another unsuccessful deletion.
429 {"\"some text\"", "https://origin3.com/bar",
430 "Clear-Site-Data header on 'https://origin3.com/bar': "
431 "Unrecognized type: \"some text\".\n"
432 "Clear-Site-Data header on 'https://origin3.com/bar': "
433 "No recognized types specified.\n"},
434
435 // Yet another on the same URL.
436 {"\"passwords\"", "https://origin3.com/bar",
437 "Clear-Site-Data header on 'https://origin3.com/bar': "
438 "Unrecognized type: \"passwords\".\n"
439 "Clear-Site-Data header on 'https://origin3.com/bar': "
440 "No recognized types specified.\n"},
441
442 // Successful deletion on the same URL.
443 {"\"cache\"", "https://origin3.com/bar",
444 "Clear-Site-Data header on 'https://origin3.com/bar': "
445 "Cleared data types: \"cache\".\n"},
446
447 // Redirect to the original URL.
448 // Successful deletion outputs one line.
449 {"", "https://origin1.com/foo",
450 "Clear-Site-Data header on 'https://origin1.com/foo': "
451 "No recognized types specified.\n"}};
452
453 // TODO(crbug.com/876931): Delay output until next frame for navigations.
454 bool kHandlerTypeIsNavigation[] = {false};
455
456 for (bool navigation : kHandlerTypeIsNavigation) {
457 SCOPED_TRACE(navigation ? "Navigation test." : "Subresource test.");
458
459 net::TestURLRequestContext context;
460 std::unique_ptr<net::URLRequest> request(
461 context.CreateRequest(GURL(kTestCases[0].url), net::DEFAULT_PRIORITY,
462 nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
463
464 std::string output_buffer;
465 std::string last_seen_console_output;
466
467 // |NetworkServiceClient| creates a new |ClearSiteDataHandler| for each
468 // navigation, redirect, or subresource header responses.
469 for (size_t i = 0; i < base::size(kTestCases); i++) {
470 TestHandler handler(
471 base::BindRepeating(&FakeBrowserContextGetter),
472 base::BindRepeating(&FakeWebContentsGetter), GURL(kTestCases[i].url),
473 kTestCases[i].header, request->load_flags(), base::DoNothing(),
474 std::make_unique<StringConsoleMessagesDelegate>(&output_buffer));
475 handler.DoHandleHeader();
476
477 // For navigations, the console should be still empty. For subresource
478 // requests, messages should be added progressively.
479 if (navigation) {
480 EXPECT_TRUE(output_buffer.empty());
481 } else {
482 EXPECT_EQ(last_seen_console_output + kTestCases[i].output,
483 output_buffer);
484 }
485
486 last_seen_console_output = output_buffer;
487 }
488
489 // At the end, the console must contain all messages regardless of whether
490 // it was a navigation or a subresource request.
491 std::string expected_output;
492 for (struct TestCase& test_case : kTestCases)
493 expected_output += test_case.output;
494 EXPECT_EQ(expected_output, output_buffer);
495 }
496 }
497
498 } // namespace content
499