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