// Copyright (c) 2011 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_handler.h"
#include "base/logging.h"
#include "content/browser/renderer_host/global_request_id.h"
#include "content/browser/renderer_host/resource_dispatcher_host.h"
#include "content/browser/renderer_host/resource_message_filter.h"
#include "content/common/resource_response.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.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.
SafeBrowsingResourceHandler::SafeBrowsingResourceHandler(
ResourceHandler* handler,
int render_process_host_id,
int render_view_id,
ResourceType::Type resource_type,
SafeBrowsingService* safe_browsing,
ResourceDispatcherHost* resource_dispatcher_host)
: state_(STATE_NONE),
defer_state_(DEFERRED_NONE),
safe_browsing_result_(SafeBrowsingService::SAFE),
deferred_request_id_(-1),
next_handler_(handler),
render_process_host_id_(render_process_host_id),
render_view_id_(render_view_id),
safe_browsing_(safe_browsing),
rdh_(resource_dispatcher_host),
resource_type_(resource_type) {
}
SafeBrowsingResourceHandler::~SafeBrowsingResourceHandler() {
}
bool SafeBrowsingResourceHandler::OnUploadProgress(int request_id,
uint64 position,
uint64 size) {
return next_handler_->OnUploadProgress(request_id, position, size);
}
bool SafeBrowsingResourceHandler::OnRequestRedirected(
int request_id,
const GURL& new_url,
ResourceResponse* response,
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 next_handler_->OnRequestRedirected(
request_id, new_url, response, defer);
}
// 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;
deferred_request_id_ = request_id;
deferred_url_ = new_url;
deferred_redirect_response_ = response;
*defer = true;
return true;
}
bool SafeBrowsingResourceHandler::OnResponseStarted(
int request_id, ResourceResponse* response) {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ == DEFERRED_NONE);
return next_handler_->OnResponseStarted(request_id, response);
}
void SafeBrowsingResourceHandler::OnCheckUrlTimeout() {
CHECK(state_ == STATE_CHECKING_URL);
CHECK(defer_state_ != DEFERRED_NONE);
safe_browsing_->CancelCheck(this);
OnBrowseUrlCheckResult(deferred_url_, SafeBrowsingService::SAFE);
}
bool SafeBrowsingResourceHandler::OnWillStart(int request_id,
const GURL& url,
bool* defer) {
// We need to check the new URL before starting the request.
if (CheckUrl(url))
return next_handler_->OnWillStart(request_id, url, defer);
// If the URL couldn't be verified synchronously, defer starting the
// request until the check has completed.
defer_state_ = DEFERRED_START;
deferred_request_id_ = request_id;
deferred_url_ = url;
*defer = true;
return true;
}
bool SafeBrowsingResourceHandler::OnWillRead(int request_id,
net::IOBuffer** buf, int* buf_size,
int min_size) {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ == DEFERRED_NONE);
return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
}
bool SafeBrowsingResourceHandler::OnReadCompleted(int request_id,
int* bytes_read) {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ == DEFERRED_NONE);
return next_handler_->OnReadCompleted(request_id, bytes_read);
}
bool SafeBrowsingResourceHandler::OnResponseCompleted(
int request_id, const net::URLRequestStatus& status,
const std::string& security_info) {
Shutdown();
return next_handler_->OnResponseCompleted(request_id, status, security_info);
}
void SafeBrowsingResourceHandler::OnRequestClosed() {
Shutdown();
next_handler_->OnRequestClosed();
}
// SafeBrowsingService::Client implementation, called on the IO thread once
// the URL has been classified.
void SafeBrowsingResourceHandler::OnBrowseUrlCheckResult(
const GURL& url, SafeBrowsingService::UrlCheckResult result) {
CHECK(state_ == STATE_CHECKING_URL);
CHECK(defer_state_ != DEFERRED_NONE);
CHECK(url == deferred_url_) << "Was expecting: " << deferred_url_
<< " but got: " << url;
timer_.Stop(); // Cancel the timeout timer.
safe_browsing_result_ = result;
state_ = STATE_NONE;
if (result == SafeBrowsingService::SAFE) {
// Log how much time the safe browsing check cost us.
base::TimeDelta pause_delta;
pause_delta = base::TimeTicks::Now() - url_check_start_time_;
safe_browsing_->LogPauseDelay(pause_delta);
// Continue the request.
ResumeRequest();
} else {
const net::URLRequest* request = rdh_->GetURLRequest(
GlobalRequestID(render_process_host_id_, deferred_request_id_));
if (request->load_flags() & net::LOAD_PREFETCH) {
// Don't prefetch resources that fail safe browsing, disallow
// them.
rdh_->CancelRequest(render_process_host_id_, deferred_request_id_, false);
} else {
StartDisplayingBlockingPage(url, result);
}
}
Release(); // Balances the AddRef() in CheckingUrl().
}
void SafeBrowsingResourceHandler::StartDisplayingBlockingPage(
const GURL& url,
SafeBrowsingService::UrlCheckResult result) {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ != DEFERRED_NONE);
CHECK(deferred_request_id_ != -1);
state_ = STATE_DISPLAYING_BLOCKING_PAGE;
AddRef(); // Balanced in OnBlockingPageComplete().
// Grab the original url of this request as well.
GURL original_url;
net::URLRequest* request = rdh_->GetURLRequest(
GlobalRequestID(render_process_host_id_, deferred_request_id_));
if (request)
original_url = request->original_url();
else
original_url = url;
safe_browsing_->DisplayBlockingPage(
url, original_url, redirect_urls_, resource_type_,
result, this, render_process_host_id_, render_view_id_);
}
// SafeBrowsingService::Client implementation, called on the IO thread when
// the user has decided to proceed with the current request, or go back.
void SafeBrowsingResourceHandler::OnBlockingPageComplete(bool proceed) {
CHECK(state_ == STATE_DISPLAYING_BLOCKING_PAGE);
state_ = STATE_NONE;
if (proceed) {
safe_browsing_result_ = SafeBrowsingService::SAFE;
net::URLRequest* request = rdh_->GetURLRequest(
GlobalRequestID(render_process_host_id_, deferred_request_id_));
// The request could be canceled by renderer at this stage.
// As a result, click proceed will do nothing (crbug.com/76460).
if (request)
ResumeRequest();
} else {
rdh_->CancelRequest(render_process_host_id_, deferred_request_id_, false);
}
Release(); // Balances the AddRef() in StartDisplayingBlockingPage().
}
void SafeBrowsingResourceHandler::Shutdown() {
if (state_ == STATE_CHECKING_URL) {
timer_.Stop();
safe_browsing_->CancelCheck(this);
state_ = STATE_NONE;
// Balance the AddRef() from CheckUrl() which would ordinarily be
// balanced by OnUrlCheckResult().
Release();
}
}
bool SafeBrowsingResourceHandler::CheckUrl(const GURL& url) {
CHECK(state_ == STATE_NONE);
bool succeeded_synchronously = safe_browsing_->CheckBrowseUrl(url, this);
if (succeeded_synchronously) {
safe_browsing_result_ = SafeBrowsingService::SAFE;
safe_browsing_->LogPauseDelay(base::TimeDelta()); // No delay.
return true;
}
AddRef(); // Balanced in OnUrlCheckResult().
state_ = STATE_CHECKING_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(base::TimeDelta::FromMilliseconds(kCheckUrlTimeoutMs),
this, &SafeBrowsingResourceHandler::OnCheckUrlTimeout);
return false;
}
void SafeBrowsingResourceHandler::ResumeRequest() {
CHECK(state_ == STATE_NONE);
CHECK(defer_state_ != DEFERRED_NONE);
// Resume whatever stage got paused by the safe browsing check.
switch (defer_state_) {
case DEFERRED_START:
ResumeStart();
break;
case DEFERRED_REDIRECT:
ResumeRedirect();
break;
case DEFERRED_NONE:
NOTREACHED();
break;
}
}
void SafeBrowsingResourceHandler::ResumeStart() {
CHECK(defer_state_ == DEFERRED_START);
CHECK(deferred_request_id_ != -1);
defer_state_ = DEFERRED_NONE;
// Retrieve the details for the paused OnWillStart().
int request_id = deferred_request_id_;
GURL url = deferred_url_;
ClearDeferredRequestInfo();
// Give the other resource handlers a chance to defer starting.
bool defer = false;
// TODO(eroman): the return value is being lost here. Should
// use it to cancel the request.
next_handler_->OnWillStart(request_id, url, &defer);
if (!defer)
rdh_->StartDeferredRequest(render_process_host_id_, request_id);
}
void SafeBrowsingResourceHandler::ResumeRedirect() {
CHECK(defer_state_ == DEFERRED_REDIRECT);
defer_state_ = DEFERRED_NONE;
// Retrieve the details for the paused OnReceivedRedirect().
int request_id = deferred_request_id_;
GURL redirect_url = deferred_url_;
scoped_refptr<ResourceResponse> redirect_response =
deferred_redirect_response_;
ClearDeferredRequestInfo();
// Give the other resource handlers a chance to handle the redirect.
bool defer = false;
// TODO(eroman): the return value is being lost here. Should
// use it to cancel the request.
next_handler_->OnRequestRedirected(request_id, redirect_url,
redirect_response, &defer);
if (!defer) {
rdh_->FollowDeferredRedirect(render_process_host_id_, request_id,
false, GURL());
}
}
void SafeBrowsingResourceHandler::ClearDeferredRequestInfo() {
deferred_request_id_ = -1;
deferred_url_ = GURL();
deferred_redirect_response_ = NULL;
}