safe_browsing_resource_throttle.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright (c) 2012 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 "chrome/browser/renderer_host/safe_browsing_resource_throttle.h"
6
7#include "base/logging.h"
8#include "chrome/browser/browser_process.h"
9#include "chrome/browser/prerender/prerender_contents.h"
10#include "chrome/browser/safe_browsing/safe_browsing_service.h"
11#include "content/public/browser/browser_thread.h"
12#include "content/public/browser/render_view_host.h"
13#include "content/public/browser/resource_controller.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/url_request/url_request.h"
18
19// Maximum time in milliseconds to wait for the safe browsing service to
20// verify a URL. After this amount of time the outstanding check will be
21// aborted, and the URL will be treated as if it were safe.
22static const int kCheckUrlTimeoutMs = 5000;
23
24// TODO(eroman): Downgrade these CHECK()s to DCHECKs once there is more
25//               unit test coverage.
26
27SafeBrowsingResourceThrottle::SafeBrowsingResourceThrottle(
28    const net::URLRequest* request,
29    bool is_subresource,
30    SafeBrowsingService* safe_browsing)
31    : state_(STATE_NONE),
32      defer_state_(DEFERRED_NONE),
33      threat_type_(SB_THREAT_TYPE_SAFE),
34      database_manager_(safe_browsing->database_manager()),
35      ui_manager_(safe_browsing->ui_manager()),
36      request_(request),
37      is_subresource_(is_subresource) {
38}
39
40SafeBrowsingResourceThrottle::~SafeBrowsingResourceThrottle() {
41  if (state_ == STATE_CHECKING_URL)
42    database_manager_->CancelCheck(this);
43}
44
45void SafeBrowsingResourceThrottle::WillStartRequest(bool* defer) {
46  // We need to check the new URL before starting the request.
47  if (CheckUrl(request_->url()))
48    return;
49
50  // If the URL couldn't be verified synchronously, defer starting the
51  // request until the check has completed.
52  defer_state_ = DEFERRED_START;
53  *defer = true;
54}
55
56void SafeBrowsingResourceThrottle::WillRedirectRequest(const GURL& new_url,
57                                                       bool* defer) {
58  CHECK(state_ == STATE_NONE);
59  CHECK(defer_state_ == DEFERRED_NONE);
60
61  // Save the redirect urls for possible malware detail reporting later.
62  redirect_urls_.push_back(new_url);
63
64  // We need to check the new URL before following the redirect.
65  if (CheckUrl(new_url))
66    return;
67
68  // If the URL couldn't be verified synchronously, defer following the
69  // redirect until the SafeBrowsing check is complete. Store the redirect
70  // context so we can pass it on to other handlers once we have completed
71  // our check.
72  defer_state_ = DEFERRED_REDIRECT;
73  *defer = true;
74}
75
76const char* SafeBrowsingResourceThrottle::GetNameForLogging() const {
77  return "SafeBrowsingResourceThrottle";
78}
79
80// SafeBrowsingService::Client implementation, called on the IO thread once
81// the URL has been classified.
82void SafeBrowsingResourceThrottle::OnCheckBrowseUrlResult(
83    const GURL& url, SBThreatType threat_type) {
84  CHECK(state_ == STATE_CHECKING_URL);
85  CHECK(defer_state_ != DEFERRED_NONE);
86  CHECK(url == url_being_checked_) << "Was expecting: " << url_being_checked_
87                                   << " but got: " << url;
88
89#if defined(OS_ANDROID)
90  // Temporarily disable SB interstitial during Finch experiment.
91  // The database check is still exercised, but the interstitial never shown.
92  threat_type = SB_THREAT_TYPE_SAFE;
93#endif
94
95  timer_.Stop();  // Cancel the timeout timer.
96  threat_type_ = threat_type;
97  state_ = STATE_NONE;
98
99  if (threat_type == SB_THREAT_TYPE_SAFE) {
100    // Log how much time the safe browsing check cost us.
101    ui_manager_->LogPauseDelay(base::TimeTicks::Now() - url_check_start_time_);
102
103    // Continue the request.
104    ResumeRequest();
105    return;
106  }
107
108  if (request_->load_flags() & net::LOAD_PREFETCH) {
109    // Don't prefetch resources that fail safe browsing, disallow
110    // them.
111    controller()->Cancel();
112    return;
113  }
114
115  const content::ResourceRequestInfo* info =
116      content::ResourceRequestInfo::ForRequest(request_);
117
118  SafeBrowsingUIManager::UnsafeResource resource;
119  resource.url = url;
120  resource.original_url = request_->original_url();
121  resource.redirect_urls = redirect_urls_;
122  resource.is_subresource = is_subresource_;
123  resource.threat_type = threat_type;
124  resource.callback = base::Bind(
125      &SafeBrowsingResourceThrottle::OnBlockingPageComplete, AsWeakPtr());
126  resource.render_process_host_id = info->GetChildID();
127  resource.render_view_id =  info->GetRouteID();
128
129  state_ = STATE_DISPLAYING_BLOCKING_PAGE;
130
131  content::BrowserThread::PostTask(
132      content::BrowserThread::UI,
133      FROM_HERE,
134      base::Bind(&SafeBrowsingResourceThrottle::StartDisplayingBlockingPage,
135                 AsWeakPtr(), ui_manager_, resource));
136}
137
138void SafeBrowsingResourceThrottle::StartDisplayingBlockingPage(
139    const base::WeakPtr<SafeBrowsingResourceThrottle>& throttle,
140    scoped_refptr<SafeBrowsingUIManager> ui_manager,
141    const SafeBrowsingUIManager::UnsafeResource& resource) {
142  bool should_show_blocking_page = true;
143
144  content::RenderViewHost* rvh = content::RenderViewHost::FromID(
145      resource.render_process_host_id, resource.render_view_id);
146  if (rvh) {
147    content::WebContents* web_contents =
148        content::WebContents::FromRenderViewHost(rvh);
149    prerender::PrerenderContents* prerender_contents =
150        prerender::PrerenderContents::FromWebContents(web_contents);
151    if (prerender_contents) {
152      prerender_contents->Destroy(prerender::FINAL_STATUS_SAFE_BROWSING);
153      should_show_blocking_page = false;
154    }
155
156    if (should_show_blocking_page)  {
157      ui_manager->DisplayBlockingPage(resource);
158      return;
159    }
160  }
161
162  // Tab is gone or it's being prerendered.
163  content::BrowserThread::PostTask(
164      content::BrowserThread::IO,
165      FROM_HERE,
166      base::Bind(&SafeBrowsingResourceThrottle::Cancel, throttle));
167}
168
169void SafeBrowsingResourceThrottle::Cancel() {
170  controller()->Cancel();
171}
172
173// SafeBrowsingService::UrlCheckCallback implementation, called on the IO
174// thread when the user has decided to proceed with the current request, or
175// go back.
176void SafeBrowsingResourceThrottle::OnBlockingPageComplete(bool proceed) {
177  CHECK(state_ == STATE_DISPLAYING_BLOCKING_PAGE);
178  state_ = STATE_NONE;
179
180  if (proceed) {
181    threat_type_ = SB_THREAT_TYPE_SAFE;
182    ResumeRequest();
183  } else {
184    controller()->Cancel();
185  }
186}
187
188bool SafeBrowsingResourceThrottle::CheckUrl(const GURL& url) {
189  CHECK(state_ == STATE_NONE);
190  bool succeeded_synchronously = database_manager_->CheckBrowseUrl(url, this);
191  if (succeeded_synchronously) {
192    threat_type_ = SB_THREAT_TYPE_SAFE;
193    ui_manager_->LogPauseDelay(base::TimeDelta());  // No delay.
194    return true;
195  }
196
197  state_ = STATE_CHECKING_URL;
198  url_being_checked_ = url;
199
200  // Record the start time of the check.
201  url_check_start_time_ = base::TimeTicks::Now();
202
203  // Start a timer to abort the check if it takes too long.
204  timer_.Start(FROM_HERE,
205               base::TimeDelta::FromMilliseconds(kCheckUrlTimeoutMs),
206               this, &SafeBrowsingResourceThrottle::OnCheckUrlTimeout);
207
208  return false;
209}
210
211void SafeBrowsingResourceThrottle::OnCheckUrlTimeout() {
212  CHECK(state_ == STATE_CHECKING_URL);
213  CHECK(defer_state_ != DEFERRED_NONE);
214
215  database_manager_->CancelCheck(this);
216  OnCheckBrowseUrlResult(url_being_checked_, SB_THREAT_TYPE_SAFE);
217}
218
219void SafeBrowsingResourceThrottle::ResumeRequest() {
220  CHECK(state_ == STATE_NONE);
221  CHECK(defer_state_ != DEFERRED_NONE);
222
223  defer_state_ = DEFERRED_NONE;
224  controller()->Resume();
225}
226