blob: e9dc7b13599e0422abbc8c06c315958c3be1ed45 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/renderer_host/safe_browsing_resource_throttle.h"
#include "base/logging.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prerender/prerender_contents.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request.h"
// Maximum time in milliseconds to wait for the safe browsing service to
// verify a URL. After this amount of time the outstanding check will be
// aborted, and the URL will be treated as if it were safe.
static const int kCheckUrlTimeoutMs = 5000;
// TODO(eroman): Downgrade these CHECK()s to DCHECKs once there is more
// unit test coverage.
SafeBrowsingResourceThrottle::SafeBrowsingResourceThrottle(
const net::URLRequest* request,
bool is_subresource,
SafeBrowsingService* safe_browsing)
: state_(STATE_NONE),
defer_state_(DEFERRED_NONE),
threat_type_(SB_THREAT_TYPE_SAFE),
database_manager_(safe_browsing->database_manager()),
ui_manager_(safe_browsing->ui_manager()),
request_(request),
is_subresource_(is_subresource) {
}
SafeBrowsingResourceThrottle::~SafeBrowsingResourceThrottle() {
if (state_ == STATE_CHECKING_URL)
database_manager_->CancelCheck(this);
}
void SafeBrowsingResourceThrottle::WillStartRequest(bool* defer) {
// We need to check the new URL before starting the request.
if (CheckUrl(request_->url()))
return;
// If the URL couldn't be verified synchronously, defer starting the
// request until the check has completed.
defer_state_ = DEFERRED_START;
*defer = true;
}
void SafeBrowsingResourceThrottle::WillRedirectRequest(const GURL& new_url,
bool* defer) {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ == DEFERRED_NONE);
// Save the redirect urls for possible malware detail reporting later.
redirect_urls_.push_back(new_url);
// We need to check the new URL before following the redirect.
if (CheckUrl(new_url))
return;
// If the URL couldn't be verified synchronously, defer following the
// redirect until the SafeBrowsing check is complete. Store the redirect
// context so we can pass it on to other handlers once we have completed
// our check.
defer_state_ = DEFERRED_REDIRECT;
*defer = true;
}
const char* SafeBrowsingResourceThrottle::GetNameForLogging() const {
return "SafeBrowsingResourceThrottle";
}
// SafeBrowsingService::Client implementation, called on the IO thread once
// the URL has been classified.
void SafeBrowsingResourceThrottle::OnCheckBrowseUrlResult(
const GURL& url, SBThreatType threat_type) {
CHECK(state_ == STATE_CHECKING_URL);
CHECK(defer_state_ != DEFERRED_NONE);
CHECK(url == url_being_checked_) << "Was expecting: " << url_being_checked_
<< " but got: " << url;
timer_.Stop(); // Cancel the timeout timer.
threat_type_ = threat_type;
state_ = STATE_NONE;
if (threat_type == SB_THREAT_TYPE_SAFE) {
// Log how much time the safe browsing check cost us.
ui_manager_->LogPauseDelay(base::TimeTicks::Now() - url_check_start_time_);
// Continue the request.
ResumeRequest();
return;
}
if (request_->load_flags() & net::LOAD_PREFETCH) {
// Don't prefetch resources that fail safe browsing, disallow
// them.
controller()->Cancel();
return;
}
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_);
SafeBrowsingUIManager::UnsafeResource resource;
resource.url = url;
resource.original_url = request_->original_url();
resource.redirect_urls = redirect_urls_;
resource.is_subresource = is_subresource_;
resource.threat_type = threat_type;
resource.callback = base::Bind(
&SafeBrowsingResourceThrottle::OnBlockingPageComplete, AsWeakPtr());
resource.render_process_host_id = info->GetChildID();
resource.render_view_id = info->GetRouteID();
state_ = STATE_DISPLAYING_BLOCKING_PAGE;
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&SafeBrowsingResourceThrottle::StartDisplayingBlockingPage,
AsWeakPtr(), ui_manager_, resource));
}
void SafeBrowsingResourceThrottle::StartDisplayingBlockingPage(
const base::WeakPtr<SafeBrowsingResourceThrottle>& throttle,
scoped_refptr<SafeBrowsingUIManager> ui_manager,
const SafeBrowsingUIManager::UnsafeResource& resource) {
bool should_show_blocking_page = true;
content::RenderViewHost* rvh = content::RenderViewHost::FromID(
resource.render_process_host_id, resource.render_view_id);
if (rvh) {
content::WebContents* web_contents =
content::WebContents::FromRenderViewHost(rvh);
prerender::PrerenderContents* prerender_contents =
prerender::PrerenderContents::FromWebContents(web_contents);
if (prerender_contents) {
prerender_contents->Destroy(prerender::FINAL_STATUS_SAFE_BROWSING);
should_show_blocking_page = false;
}
if (should_show_blocking_page) {
ui_manager->DisplayBlockingPage(resource);
return;
}
}
// Tab is gone or it's being prerendered.
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&SafeBrowsingResourceThrottle::Cancel, throttle));
}
void SafeBrowsingResourceThrottle::Cancel() {
controller()->Cancel();
}
// SafeBrowsingService::UrlCheckCallback implementation, called on the IO
// thread when the user has decided to proceed with the current request, or
// go back.
void SafeBrowsingResourceThrottle::OnBlockingPageComplete(bool proceed) {
CHECK(state_ == STATE_DISPLAYING_BLOCKING_PAGE);
state_ = STATE_NONE;
if (proceed) {
threat_type_ = SB_THREAT_TYPE_SAFE;
ResumeRequest();
} else {
controller()->Cancel();
}
}
bool SafeBrowsingResourceThrottle::CheckUrl(const GURL& url) {
CHECK(state_ == STATE_NONE);
bool succeeded_synchronously = database_manager_->CheckBrowseUrl(url, this);
if (succeeded_synchronously) {
threat_type_ = SB_THREAT_TYPE_SAFE;
ui_manager_->LogPauseDelay(base::TimeDelta()); // No delay.
return true;
}
state_ = STATE_CHECKING_URL;
url_being_checked_ = url;
// Record the start time of the check.
url_check_start_time_ = base::TimeTicks::Now();
// Start a timer to abort the check if it takes too long.
timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kCheckUrlTimeoutMs),
this, &SafeBrowsingResourceThrottle::OnCheckUrlTimeout);
return false;
}
void SafeBrowsingResourceThrottle::OnCheckUrlTimeout() {
CHECK(state_ == STATE_CHECKING_URL);
CHECK(defer_state_ != DEFERRED_NONE);
database_manager_->CancelCheck(this);
OnCheckBrowseUrlResult(url_being_checked_, SB_THREAT_TYPE_SAFE);
}
void SafeBrowsingResourceThrottle::ResumeRequest() {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ != DEFERRED_NONE);
defer_state_ = DEFERRED_NONE;
controller()->Resume();
}