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

Side by Side Diff: components/safe_browsing/base_resource_throttle.cc

Issue 2876473003: [ABANDONED] [WIP] Refactor SafeBrowsingResourceThrottle in preparation for WebSocket (Closed)
Patch Set: Minor fixes Created 3 years, 6 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
(Empty)
1 // Copyright (c) 2017 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 "components/safe_browsing/base_resource_throttle.h"
6
7 #include "base/metrics/histogram_macros.h"
8 #include "base/trace_event/trace_event.h"
9 #include "base/values.h"
10 #include "components/safe_browsing/base_ui_manager.h"
11 #include "components/safe_browsing_db/util.h"
12 #include "components/security_interstitials/content/unsafe_resource.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/resource_request_info.h"
15 #include "content/public/browser/web_contents.h"
16 #include "net/base/load_flags.h"
17 #include "net/log/net_log_capture_mode.h"
18 #include "net/log/net_log_source.h"
19 #include "net/log/net_log_source_type.h"
20 #include "net/url_request/redirect_info.h"
21 #include "net/url_request/url_request.h"
22
23 using net::NetLogEventType;
24 using net::NetLogSourceType;
25
26 namespace safe_browsing {
27
28 namespace {
29
30 // Maximum time in milliseconds to wait for the safe browsing service to
31 // verify a URL. After this amount of time the outstanding check will be
32 // aborted, and the URL will be treated as if it were safe.
33 const int kCheckUrlTimeoutMs = 5000;
34
35 // Return a dictionary with "url"=|url-spec| and optionally
36 // |name|=|value| (if not null), for netlogging.
37 // This will also add a reference to the original request's net_log ID.
38 std::unique_ptr<base::Value> NetLogUrlCallback(
39 const net::URLRequest* request,
40 const GURL& url,
41 const char* name,
42 const char* value,
43 net::NetLogCaptureMode /* capture_mode */) {
44 std::unique_ptr<base::DictionaryValue> event_params(
45 new base::DictionaryValue());
46 event_params->SetString("url", url.spec());
47 if (name && value)
48 event_params->SetString(name, value);
49 request->net_log().source().AddToEventParameters(event_params.get());
50 return std::move(event_params);
51 }
52
53 // Return a dictionary with |name|=|value|, for netlogging.
54 std::unique_ptr<base::Value> NetLogStringCallback(const char* name,
55 const char* value,
56 net::NetLogCaptureMode) {
57 std::unique_ptr<base::DictionaryValue> event_params(
58 new base::DictionaryValue());
59 if (name && value)
60 event_params->SetString(name, value);
61 return std::move(event_params);
62 }
63
64 } // namespace
65
66 // TODO(eroman): Downgrade these CHECK()s to DCHECKs once there is more
67 // unit test coverage.
68
69 BaseResourceThrottle::BaseResourceThrottle(
70 const net::URLRequest* request,
71 content::ResourceType resource_type,
72 scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
73 scoped_refptr<BaseUIManager> ui_manager)
74 : ui_manager_(ui_manager),
75 threat_type_(SB_THREAT_TYPE_SAFE),
76 database_manager_(database_manager),
77 request_(request),
78 state_(STATE_NONE),
79 defer_state_(DEFERRED_NONE),
80 resource_type_(resource_type),
81 net_log_with_source_(
82 net::NetLogWithSource::Make(request->net_log().net_log(),
83 NetLogSourceType::SAFE_BROWSING)) {}
84
85 // static
86 BaseResourceThrottle* BaseResourceThrottle::MaybeCreate(
87 net::URLRequest* request,
88 content::ResourceType resource_type,
89 scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
90 scoped_refptr<BaseUIManager> ui_manager) {
91 if (database_manager->IsSupported()) {
92 return new BaseResourceThrottle(request, resource_type,
93 database_manager, ui_manager);
94 }
95 return nullptr;
96 }
97
98 BaseResourceThrottle::~BaseResourceThrottle() {
99 if (defer_state_ != DEFERRED_NONE) {
100 EndNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, nullptr, nullptr);
101 }
102
103 if (state_ == STATE_CHECKING_URL) {
104 database_manager_->CancelCheck(this);
105 EndNetLogEvent(NetLogEventType::SAFE_BROWSING_CHECKING_URL, "result",
106 "request_canceled");
107 }
108 }
109
110 // Note on net_log calls: SAFE_BROWSING_DEFERRED events must be wholly
111 // nested within SAFE_BROWSING_CHECKING_URL events. Synchronous checks
112 // are not logged at all.
113 void BaseResourceThrottle::BeginNetLogEvent(NetLogEventType type,
114 const GURL& url,
115 const char* name,
116 const char* value) {
117 net_log_with_source_.BeginEvent(
118 type, base::Bind(&NetLogUrlCallback, request_, url, name, value));
119 request_->net_log().AddEvent(
120 type, net_log_with_source_.source().ToEventParametersCallback());
121 }
122
123 void BaseResourceThrottle::EndNetLogEvent(NetLogEventType type,
124 const char* name,
125 const char* value) {
126 net_log_with_source_.EndEvent(type,
127 base::Bind(&NetLogStringCallback, name, value));
128 request_->net_log().AddEvent(
129 type, net_log_with_source_.source().ToEventParametersCallback());
130 }
131
132 void BaseResourceThrottle::WillStartRequest(bool* defer) {
133 // We need to check the new URL before starting the request.
134 if (CheckUrl(request_->url()))
135 return;
136
137 // We let the check run in parallel with resource load only if this
138 // db_manager only supports asynchronous checks, like on mobile.
139 // Otherwise, we defer now.
140 if (database_manager_->ChecksAreAlwaysAsync())
141 return;
142
143 // If the URL couldn't be verified synchronously, defer starting the
144 // request until the check has completed.
145 defer_state_ = DEFERRED_START;
146 defer_start_time_ = base::TimeTicks::Now();
147 *defer = true;
148 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, request_->url(),
149 "defer_reason", "at_start");
150 }
151
152 void BaseResourceThrottle::WillProcessResponse(bool* defer) {
153 CHECK_EQ(defer_state_, DEFERRED_NONE);
154 // TODO(nparker): Maybe remove this check, since it should have no effect.
155 if (!database_manager_->ChecksAreAlwaysAsync())
156 return;
157
158 if (state_ == STATE_CHECKING_URL ||
159 state_ == STATE_DISPLAYING_BLOCKING_PAGE) {
160 defer_state_ = DEFERRED_PROCESSING;
161 defer_start_time_ = base::TimeTicks::Now();
162 *defer = true;
163 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, request_->url(),
164 "defer_reason", "at_response");
165 }
166 }
167
168 bool BaseResourceThrottle::MustProcessResponseBeforeReadingBody() {
169 // On Android, SafeBrowsing may only decide to cancel the request when the
170 // response has been received. Therefore, no part of it should be cached
171 // until this ResourceThrottle has been able to check the response. This
172 // prevents the following scenario:
173 // 1) A request is made for foo.com which has been hacked.
174 // 2) The request is only canceled at WillProcessResponse stage, but part of
175 // it has been cached.
176 // 3) foo.com is no longer hacked and removed from the SafeBrowsing list.
177 // 4) The user requests foo.com, which is not on the SafeBrowsing list. This
178 // is deemed safe. However, the resource is actually served from cache,
179 // using the version that was previously stored.
180 // 5) This results in the user accessing an unsafe resource without being
181 // notified that it's dangerous.
182 // TODO(clamy): Add a browser test that checks this specific scenario.
183 return true;
184 }
185
186 void BaseResourceThrottle::WillRedirectRequest(
187 const net::RedirectInfo& redirect_info,
188 bool* defer) {
189 CHECK_EQ(defer_state_, DEFERRED_NONE);
190
191 // Prev check completed and was safe.
192 if (state_ == STATE_NONE) {
193 // Save the redirect urls for possible malware detail reporting later.
194 redirect_urls_.push_back(redirect_info.new_url);
195
196 // We need to check the new URL before following the redirect.
197 if (CheckUrl(redirect_info.new_url))
198 return;
199 defer_state_ = DEFERRED_REDIRECT;
200 } else {
201 CHECK(state_ == STATE_CHECKING_URL ||
202 state_ == STATE_DISPLAYING_BLOCKING_PAGE);
203 // We can't check this new URL until we have finished checking
204 // the prev one, or resumed from the blocking page.
205 unchecked_redirect_url_ = redirect_info.new_url;
206 defer_state_ = DEFERRED_UNCHECKED_REDIRECT;
207 }
208
209 defer_start_time_ = base::TimeTicks::Now();
210 *defer = true;
211 BeginNetLogEvent(
212 NetLogEventType::SAFE_BROWSING_DEFERRED, redirect_info.new_url,
213 "defer_reason",
214 defer_state_ == DEFERRED_REDIRECT ? "redirect" : "unchecked_redirect");
215 }
216
217 const char* BaseResourceThrottle::GetNameForLogging() const {
218 return "BaseResourceThrottle";
219 }
220
221 void BaseResourceThrottle::MaybeDestroyPrerenderContents(
222 const content::ResourceRequestInfo* info) {}
223
224 // SafeBrowsingService::Client implementation, called on the IO thread once
225 // the URL has been classified.
226 void BaseResourceThrottle::OnCheckBrowseUrlResult(
227 const GURL& url,
228 SBThreatType threat_type,
229 const ThreatMetadata& metadata) {
230 CHECK_EQ(state_, STATE_CHECKING_URL);
231 CHECK(url.is_valid());
232 CHECK(url_being_checked_.is_valid());
233 CHECK_EQ(url, url_being_checked_);
234
235 timer_.Stop(); // Cancel the timeout timer.
236 threat_type_ = threat_type;
237 state_ = STATE_NONE;
238
239 if (defer_state_ != DEFERRED_NONE) {
240 EndNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, nullptr, nullptr);
241 }
242 EndNetLogEvent(
243 NetLogEventType::SAFE_BROWSING_CHECKING_URL, "result",
244 threat_type_ == SB_THREAT_TYPE_SAFE ? "safe" : "unsafe");
245
246 if (threat_type == SB_THREAT_TYPE_SAFE) {
247 if (defer_state_ != DEFERRED_NONE) {
248 // Log how much time the safe browsing check cost us.
249 ui_manager_->LogPauseDelay(base::TimeTicks::Now() - defer_start_time_);
250 ResumeRequest();
251 } else {
252 ui_manager_->LogPauseDelay(base::TimeDelta());
253 }
254 return;
255 }
256
257 const content::ResourceRequestInfo* info =
258 content::ResourceRequestInfo::ForRequest(request_);
259
260 if (request_->load_flags() & net::LOAD_PREFETCH) {
261 // Destroy the prefetch with FINAL_STATUS_SAFEBROSWING.
262 if (resource_type_ == content::RESOURCE_TYPE_MAIN_FRAME) {
263 MaybeDestroyPrerenderContents(info);
264 }
265 // Don't prefetch resources that fail safe browsing, disallow them.
266 Cancel();
267 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.UnsafePrefetchCanceled",
268 resource_type_, content::RESOURCE_TYPE_LAST_TYPE);
269 return;
270 }
271
272 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Unsafe", resource_type_,
273 content::RESOURCE_TYPE_LAST_TYPE);
274
275 security_interstitials::UnsafeResource resource;
276 resource.url = url;
277 resource.original_url = request_->original_url();
278 resource.redirect_urls = redirect_urls_;
279 resource.is_subresource = resource_type_ != content::RESOURCE_TYPE_MAIN_FRAME;
280 resource.is_subframe = resource_type_ == content::RESOURCE_TYPE_SUB_FRAME;
281 resource.threat_type = threat_type;
282 resource.threat_metadata = metadata;
283 resource.callback = base::Bind(
284 &BaseResourceThrottle::OnBlockingPageComplete, AsWeakPtr());
285 resource.callback_thread = content::BrowserThread::GetTaskRunnerForThread(
286 content::BrowserThread::IO);
287 resource.web_contents_getter = info->GetWebContentsGetterForRequest();
288 resource.threat_source = database_manager_->GetThreatSource();
289
290 state_ = STATE_DISPLAYING_BLOCKING_PAGE;
291
292 StartDisplayingBlockingPageHelper(resource);
293 }
294
295 void BaseResourceThrottle::StartDisplayingBlockingPageHelper(
296 security_interstitials::UnsafeResource resource) {
297 content::BrowserThread::PostTask(
298 content::BrowserThread::UI, FROM_HERE,
299 base::Bind(&BaseResourceThrottle::StartDisplayingBlockingPage,
300 AsWeakPtr(), ui_manager_, resource));
301 }
302
303 // Static
304 void BaseResourceThrottle::StartDisplayingBlockingPage(
305 const base::WeakPtr<BaseResourceThrottle>& throttle,
306 scoped_refptr<BaseUIManager> ui_manager,
307 const security_interstitials::UnsafeResource& resource) {
308 content::WebContents* web_contents = resource.web_contents_getter.Run();
309 if (web_contents) {
310 ui_manager->DisplayBlockingPage(resource);
311 return;
312 }
313
314 // Tab is gone or it's being prerendered.
315 content::BrowserThread::PostTask(
316 content::BrowserThread::IO, FROM_HERE,
317 base::Bind(&BaseResourceThrottle::Cancel, throttle));
318 }
319
320 void BaseResourceThrottle::OnBlockingPageComplete(bool proceed) {
321 CHECK_EQ(state_, STATE_DISPLAYING_BLOCKING_PAGE);
322 state_ = STATE_NONE;
323
324 if (proceed) {
325 threat_type_ = SB_THREAT_TYPE_SAFE;
326 if (defer_state_ != DEFERRED_NONE) {
327 ResumeRequest();
328 }
329 } else {
330 CancelResourceLoad();
331 }
332 }
333
334 void BaseResourceThrottle::CancelResourceLoad() {
335 Cancel();
336 }
337
338 scoped_refptr<BaseUIManager> BaseResourceThrottle::ui_manager() {
339 return ui_manager_;
340 }
341
342 bool BaseResourceThrottle::CheckUrl(const GURL& url) {
343 TRACE_EVENT1("loader", "BaseResourceThrottle::CheckUrl", "url",
344 url.spec());
345 CHECK_EQ(state_, STATE_NONE);
346 // To reduce aggregate latency on mobile, check only the most dangerous
347 // resource types.
348 if (!database_manager_->CanCheckResourceType(resource_type_)) {
349 // TODO(vakh): Consider changing this metric to SafeBrowsing.V4ResourceType
350 // to be consistent with the other PVer4 metrics.
351 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Skipped", resource_type_,
352 content::RESOURCE_TYPE_LAST_TYPE);
353 return true;
354 }
355
356 // TODO(vakh): Consider changing this metric to SafeBrowsing.V4ResourceType to
357 // be consistent with the other PVer4 metrics.
358 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Checked", resource_type_,
359 content::RESOURCE_TYPE_LAST_TYPE);
360
361 if (database_manager_->CheckBrowseUrl(url, this)) {
362 threat_type_ = SB_THREAT_TYPE_SAFE;
363 ui_manager_->LogPauseDelay(base::TimeDelta()); // No delay.
364 return true;
365 }
366
367 state_ = STATE_CHECKING_URL;
368 url_being_checked_ = url;
369 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_CHECKING_URL, url, nullptr,
370 nullptr);
371
372 // Start a timer to abort the check if it takes too long.
373 // TODO(nparker): Set this only when we defer, based on remaining time,
374 // so we don't cancel earlier than necessary.
375 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCheckUrlTimeoutMs),
376 this, &BaseResourceThrottle::OnCheckUrlTimeout);
377
378 return false;
379 }
380
381 void BaseResourceThrottle::OnCheckUrlTimeout() {
382 CHECK_EQ(state_, STATE_CHECKING_URL);
383
384 database_manager_->CancelCheck(this);
385
386 OnCheckBrowseUrlResult(url_being_checked_, safe_browsing::SB_THREAT_TYPE_SAFE,
387 ThreatMetadata());
388 }
389
390 void BaseResourceThrottle::ResumeRequest() {
391 CHECK_EQ(state_, STATE_NONE);
392 CHECK_NE(defer_state_, DEFERRED_NONE);
393
394 bool resume = true;
395 if (defer_state_ == DEFERRED_UNCHECKED_REDIRECT) {
396 // Save the redirect urls for possible malware detail reporting later.
397 redirect_urls_.push_back(unchecked_redirect_url_);
398 if (!CheckUrl(unchecked_redirect_url_)) {
399 // We're now waiting for the unchecked_redirect_url_.
400 defer_state_ = DEFERRED_REDIRECT;
401 resume = false;
402 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED,
403 unchecked_redirect_url_, "defer_reason",
404 "resumed_redirect");
405 }
406 }
407
408 if (resume) {
409 defer_state_ = DEFERRED_NONE;
410 Resume();
411 }
412 }
413
414 } // namespace safe_browsing
OLDNEW
« no previous file with comments | « components/safe_browsing/base_resource_throttle.h ('k') | components/safe_browsing/base_ui_manager.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698