普通文本  |  323行  |  10.8 KB

// 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;
}