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