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 "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/scoped_observer.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "content/public/browser/browser_context.h"
15 #include "content/public/browser/browsing_data_filter_builder.h"
16 #include "content/public/browser/browsing_data_remover.h"
17 #include "content/public/browser/clear_site_data_utils.h"
18 #include "content/public/browser/web_contents.h"
19 #include "content/public/common/content_switches.h"
20 #include "content/public/common/origin_util.h"
21 #include "net/base/load_flags.h"
22 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
23 #include "net/http/http_response_headers.h"
24 
25 namespace content {
26 
27 namespace {
28 
29 // Datatypes.
30 const char kDatatypeWildcard[] = "\"*\"";
31 const char kDatatypeCookies[] = "\"cookies\"";
32 const char kDatatypeStorage[] = "\"storage\"";
33 const char kDatatypeCache[] = "\"cache\"";
34 
35 // Pretty-printed log output.
36 const char kConsoleMessageTemplate[] = "Clear-Site-Data header on '%s': %s";
37 const char kConsoleMessageCleared[] = "Cleared data types: %s.";
38 const char kConsoleMessageDatatypeSeparator[] = ", ";
39 
AreExperimentalFeaturesEnabled()40 bool AreExperimentalFeaturesEnabled() {
41   return base::CommandLine::ForCurrentProcess()->HasSwitch(
42       switches::kEnableExperimentalWebPlatformFeatures);
43 }
44 
45 // Represents the parameters as a single number to be recorded in a histogram.
ParametersMask(bool clear_cookies,bool clear_storage,bool clear_cache)46 int ParametersMask(bool clear_cookies, bool clear_storage, bool clear_cache) {
47   return static_cast<int>(clear_cookies) * (1 << 0) +
48          static_cast<int>(clear_storage) * (1 << 1) +
49          static_cast<int>(clear_cache) * (1 << 2);
50 }
51 
52 // Outputs a single |formatted_message| on the UI thread.
OutputFormattedMessage(WebContents * web_contents,blink::mojom::ConsoleMessageLevel level,const std::string & formatted_text)53 void OutputFormattedMessage(WebContents* web_contents,
54                             blink::mojom::ConsoleMessageLevel level,
55                             const std::string& formatted_text) {
56   if (web_contents)
57     web_contents->GetMainFrame()->AddMessageToConsole(level, formatted_text);
58 }
59 
60 }  // namespace
61 
62 ////////////////////////////////////////////////////////////////////////////////
63 // ConsoleMessagesDelegate
64 
ConsoleMessagesDelegate()65 ClearSiteDataHandler::ConsoleMessagesDelegate::ConsoleMessagesDelegate()
66     : output_formatted_message_function_(
67           base::BindRepeating(&OutputFormattedMessage)) {}
68 
~ConsoleMessagesDelegate()69 ClearSiteDataHandler::ConsoleMessagesDelegate::~ConsoleMessagesDelegate() {}
70 
AddMessage(const GURL & url,const std::string & text,blink::mojom::ConsoleMessageLevel level)71 void ClearSiteDataHandler::ConsoleMessagesDelegate::AddMessage(
72     const GURL& url,
73     const std::string& text,
74     blink::mojom::ConsoleMessageLevel level) {
75   messages_.push_back({url, text, level});
76 }
77 
OutputMessages(const base::RepeatingCallback<WebContents * ()> & web_contents_getter)78 void ClearSiteDataHandler::ConsoleMessagesDelegate::OutputMessages(
79     const base::RepeatingCallback<WebContents*()>& web_contents_getter) {
80   if (messages_.empty())
81     return;
82 
83   WebContents* web_contents = web_contents_getter.Run();
84 
85   for (const auto& message : messages_) {
86     // Prefix each message with |kConsoleMessageTemplate|.
87     output_formatted_message_function_.Run(
88         web_contents, message.level,
89         base::StringPrintf(kConsoleMessageTemplate, message.url.spec().c_str(),
90                            message.text.c_str()));
91   }
92 
93   messages_.clear();
94 }
95 
96 void ClearSiteDataHandler::ConsoleMessagesDelegate::
SetOutputFormattedMessageFunctionForTesting(const OutputFormattedMessageFunction & function)97     SetOutputFormattedMessageFunctionForTesting(
98         const OutputFormattedMessageFunction& function) {
99   output_formatted_message_function_ = function;
100 }
101 
102 ////////////////////////////////////////////////////////////////////////////////
103 // ClearSiteDataHandler
104 
105 // static
HandleHeader(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)106 void ClearSiteDataHandler::HandleHeader(
107     base::RepeatingCallback<BrowserContext*()> browser_context_getter,
108     base::RepeatingCallback<WebContents*()> web_contents_getter,
109     const GURL& url,
110     const std::string& header_value,
111     int load_flags,
112     base::OnceClosure callback) {
113   ClearSiteDataHandler handler(browser_context_getter, web_contents_getter, url,
114                                header_value, load_flags, std::move(callback),
115                                std::make_unique<ConsoleMessagesDelegate>());
116   handler.HandleHeaderAndOutputConsoleMessages();
117 }
118 
119 // static
ParseHeaderForTesting(const std::string & header,bool * clear_cookies,bool * clear_storage,bool * clear_cache,ConsoleMessagesDelegate * delegate,const GURL & current_url)120 bool ClearSiteDataHandler::ParseHeaderForTesting(
121     const std::string& header,
122     bool* clear_cookies,
123     bool* clear_storage,
124     bool* clear_cache,
125     ConsoleMessagesDelegate* delegate,
126     const GURL& current_url) {
127   return ClearSiteDataHandler::ParseHeader(header, clear_cookies, clear_storage,
128                                            clear_cache, delegate, current_url);
129 }
130 
ClearSiteDataHandler(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)131 ClearSiteDataHandler::ClearSiteDataHandler(
132     base::RepeatingCallback<BrowserContext*()> browser_context_getter,
133     base::RepeatingCallback<WebContents*()> web_contents_getter,
134     const GURL& url,
135     const std::string& header_value,
136     int load_flags,
137     base::OnceClosure callback,
138     std::unique_ptr<ConsoleMessagesDelegate> delegate)
139     : browser_context_getter_(browser_context_getter),
140       web_contents_getter_(web_contents_getter),
141       url_(url),
142       header_value_(header_value),
143       load_flags_(load_flags),
144       callback_(std::move(callback)),
145       delegate_(std::move(delegate)) {
146   DCHECK(browser_context_getter_);
147   DCHECK(web_contents_getter_);
148   DCHECK(delegate_);
149 }
150 
151 ClearSiteDataHandler::~ClearSiteDataHandler() = default;
152 
HandleHeaderAndOutputConsoleMessages()153 bool ClearSiteDataHandler::HandleHeaderAndOutputConsoleMessages() {
154   bool deferred = Run();
155 
156   // If the redirect is deferred, wait until it is resumed.
157   // TODO(crbug.com/876931): Delay output until next frame for navigations.
158   if (!deferred) {
159     OutputConsoleMessages();
160     RunCallbackNotDeferred();
161   }
162 
163   return deferred;
164 }
165 
Run()166 bool ClearSiteDataHandler::Run() {
167   // Only accept the header on secure non-unique origins.
168   if (!IsOriginSecure(url_)) {
169     delegate_->AddMessage(url_, "Not supported for insecure origins.",
170                           blink::mojom::ConsoleMessageLevel::kError);
171     return false;
172   }
173 
174   url::Origin origin = url::Origin::Create(url_);
175   if (origin.opaque()) {
176     delegate_->AddMessage(url_, "Not supported for unique origins.",
177                           blink::mojom::ConsoleMessageLevel::kError);
178     return false;
179   }
180 
181   // The LOAD_DO_NOT_SAVE_COOKIES flag prohibits the request from doing any
182   // modification to cookies. Clear-Site-Data applies this restriction to other
183   // data types as well.
184   // TODO(msramek): Consider showing a blocked icon via
185   // TabSpecificContentSettings and reporting the action in the "Blocked"
186   // section of the cookies dialog in OIB.
187   if (load_flags_ & net::LOAD_DO_NOT_SAVE_COOKIES) {
188     delegate_->AddMessage(
189         url_,
190         "The request's credentials mode prohibits modifying cookies "
191         "and other local data.",
192         blink::mojom::ConsoleMessageLevel::kError);
193     return false;
194   }
195 
196   bool clear_cookies;
197   bool clear_storage;
198   bool clear_cache;
199 
200   if (!ClearSiteDataHandler::ParseHeader(header_value_, &clear_cookies,
201                                          &clear_storage, &clear_cache,
202                                          delegate_.get(), url_)) {
203     return false;
204   }
205 
206   // Record the call parameters.
207   UMA_HISTOGRAM_ENUMERATION(
208       "Navigation.ClearSiteData.Parameters",
209       ParametersMask(clear_cookies, clear_storage, clear_cache), (1 << 3));
210 
211   ExecuteClearingTask(
212       origin, clear_cookies, clear_storage, clear_cache,
213       base::BindOnce(&ClearSiteDataHandler::TaskFinished,
214                      base::TimeTicks::Now(), std::move(delegate_),
215                      web_contents_getter_, std::move(callback_)));
216 
217   return true;
218 }
219 
220 // static
ParseHeader(const std::string & header,bool * clear_cookies,bool * clear_storage,bool * clear_cache,ConsoleMessagesDelegate * delegate,const GURL & current_url)221 bool ClearSiteDataHandler::ParseHeader(const std::string& header,
222                                        bool* clear_cookies,
223                                        bool* clear_storage,
224                                        bool* clear_cache,
225                                        ConsoleMessagesDelegate* delegate,
226                                        const GURL& current_url) {
227   if (!base::IsStringASCII(header)) {
228     delegate->AddMessage(current_url, "Must only contain ASCII characters.",
229                          blink::mojom::ConsoleMessageLevel::kError);
230     return false;
231   }
232 
233   *clear_cookies = false;
234   *clear_storage = false;
235   *clear_cache = false;
236 
237   std::vector<std::string> input_types = base::SplitString(
238       header, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
239   std::string output_types;
240 
241   for (unsigned i = 0; i < input_types.size(); i++) {
242     bool* data_type = nullptr;
243 
244     if (AreExperimentalFeaturesEnabled() &&
245         input_types[i] == kDatatypeWildcard) {
246       input_types.push_back(kDatatypeCookies);
247       input_types.push_back(kDatatypeStorage);
248       input_types.push_back(kDatatypeCache);
249       continue;
250     } else if (input_types[i] == kDatatypeCookies) {
251       data_type = clear_cookies;
252     } else if (input_types[i] == kDatatypeStorage) {
253       data_type = clear_storage;
254     } else if (input_types[i] == kDatatypeCache) {
255       data_type = clear_cache;
256     } else {
257       delegate->AddMessage(
258           current_url,
259           base::StringPrintf("Unrecognized type: %s.", input_types[i].c_str()),
260           blink::mojom::ConsoleMessageLevel::kError);
261       continue;
262     }
263 
264     DCHECK(data_type);
265 
266     if (*data_type)
267       continue;
268 
269     *data_type = true;
270     if (!output_types.empty())
271       output_types += kConsoleMessageDatatypeSeparator;
272     output_types += input_types[i];
273   }
274 
275   if (!*clear_cookies && !*clear_storage && !*clear_cache) {
276     delegate->AddMessage(current_url, "No recognized types specified.",
277                          blink::mojom::ConsoleMessageLevel::kError);
278     return false;
279   }
280 
281   // Pretty-print which types are to be cleared.
282   // TODO(crbug.com/798760): Remove the disclaimer about cookies.
283   std::string console_output =
284       base::StringPrintf(kConsoleMessageCleared, output_types.c_str());
285   if (*clear_cookies) {
286     console_output +=
287         " Clearing channel IDs and HTTP authentication cache is currently not"
288         " supported, as it breaks active network connections.";
289   }
290   delegate->AddMessage(current_url, console_output,
291                        blink::mojom::ConsoleMessageLevel::kInfo);
292 
293   return true;
294 }
295 
ExecuteClearingTask(const url::Origin & origin,bool clear_cookies,bool clear_storage,bool clear_cache,base::OnceClosure callback)296 void ClearSiteDataHandler::ExecuteClearingTask(const url::Origin& origin,
297                                                bool clear_cookies,
298                                                bool clear_storage,
299                                                bool clear_cache,
300                                                base::OnceClosure callback) {
301   ClearSiteData(browser_context_getter_, origin, clear_cookies, clear_storage,
302                 clear_cache, true /*avoid_closing_connections*/,
303                 std::move(callback));
304 }
305 
306 // static
TaskFinished(base::TimeTicks clearing_started,std::unique_ptr<ConsoleMessagesDelegate> delegate,base::RepeatingCallback<WebContents * ()> web_contents_getter,base::OnceClosure callback)307 void ClearSiteDataHandler::TaskFinished(
308     base::TimeTicks clearing_started,
309     std::unique_ptr<ConsoleMessagesDelegate> delegate,
310     base::RepeatingCallback<WebContents*()> web_contents_getter,
311     base::OnceClosure callback) {
312   DCHECK(!clearing_started.is_null());
313 
314   UMA_HISTOGRAM_CUSTOM_TIMES("Navigation.ClearSiteData.Duration",
315                              base::TimeTicks::Now() - clearing_started,
316                              base::TimeDelta::FromMilliseconds(1),
317                              base::TimeDelta::FromSeconds(1), 50);
318 
319   // TODO(crbug.com/876931): Delay output until next frame for navigations.
320   delegate->OutputMessages(web_contents_getter);
321 
322   std::move(callback).Run();
323 }
324 
OutputConsoleMessages()325 void ClearSiteDataHandler::OutputConsoleMessages() {
326   delegate_->OutputMessages(web_contents_getter_);
327 }
328 
RunCallbackNotDeferred()329 void ClearSiteDataHandler::RunCallbackNotDeferred() {
330   std::move(callback_).Run();
331 }
332 
333 }  // namespace content
334