root/chrome/browser/safe_browsing/malware_details_cache.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. has_started_
  2. StartCacheCollection
  3. HasStarted
  4. OpenEntry
  5. GetResource
  6. OnURLFetchComplete
  7. ReadResponse
  8. ReadData
  9. AdvanceEntry
  10. AllDone

// 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.
//
// Implementation of the MalwareDetails class.

#include "chrome/browser/safe_browsing/malware_details.h"

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/md5.h"
#include "base/strings/string_util.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "chrome/browser/safe_browsing/malware_details_cache.h"
#include "chrome/browser/safe_browsing/report.pb.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"

using content::BrowserThread;
using safe_browsing::ClientMalwareReportRequest;

// Only send small files for now, a better strategy would use the size
// of the whole report and the user's bandwidth.
static const uint32 kMaxBodySizeBytes = 1024;

MalwareDetailsCacheCollector::MalwareDetailsCacheCollector()
    : resources_(NULL), result_(NULL), has_started_(false) {}

void MalwareDetailsCacheCollector::StartCacheCollection(
    net::URLRequestContextGetter* request_context_getter,
    safe_browsing::ResourceMap* resources,
    bool* result,
    const base::Closure& callback) {
  // Start the data collection from the HTTP cache. We use a URLFetcher
  // and set the right flags so we only hit the cache.
  DVLOG(1) << "Getting cache data for all urls...";
  request_context_getter_ = request_context_getter;
  resources_ = resources;
  resources_it_ = resources_->begin();
  result_ = result;
  callback_ = callback;
  has_started_ = true;

  // Post a task in the message loop, so the callers don't need to
  // check if we call their callback immediately.
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&MalwareDetailsCacheCollector::OpenEntry, this));
}

bool MalwareDetailsCacheCollector::HasStarted() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  return has_started_;
}

MalwareDetailsCacheCollector::~MalwareDetailsCacheCollector() {}

// Fetch a URL and advance to the next one when done.
void MalwareDetailsCacheCollector::OpenEntry() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DVLOG(1) << "OpenEntry";

  if (resources_it_ == resources_->end()) { // We are done.
    AllDone(true);
    return;
  }

  if (!request_context_getter_.get()) {
    DVLOG(1) << "Missing request context getter";
    AllDone(false);
    return;
  }

  current_fetch_.reset(net::URLFetcher::Create(
      GURL(resources_it_->first), net::URLFetcher::GET, this));
  current_fetch_->SetRequestContext(request_context_getter_.get());
  // Only from cache, and don't save cookies.
  current_fetch_->SetLoadFlags(net::LOAD_ONLY_FROM_CACHE |
                               net::LOAD_DO_NOT_SAVE_COOKIES);
  current_fetch_->SetAutomaticallyRetryOn5xx(false);  // No retries.
  current_fetch_->Start();  // OnURLFetchComplete will be called when done.
}

ClientMalwareReportRequest::Resource* MalwareDetailsCacheCollector::GetResource(
    const GURL& url) {
  safe_browsing::ResourceMap::iterator it = resources_->find(url.spec());
  if (it != resources_->end()) {
    return it->second.get();
  }
  return NULL;
}

void MalwareDetailsCacheCollector::OnURLFetchComplete(
    const net::URLFetcher* source) {
  DVLOG(1) << "OnUrlFetchComplete";
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(current_fetch_.get());
  if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS &&
      source->GetStatus().error() == net::ERR_CACHE_MISS) {
    // Cache miss, skip this resource.
    DVLOG(1) << "Cache miss for url: " << source->GetURL();
    AdvanceEntry();
    return;
  }

  if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS) {
    // Some other error occurred, e.g. the request could have been cancelled.
    DVLOG(1) << "Unsuccessful fetch: " << source->GetURL();
    AdvanceEntry();
    return;
  }

  // Set the response headers and body to the right resource, which
  // might not be the same as the one we asked for.
  // For redirects, resources_it_->first != url.spec().
  ClientMalwareReportRequest::Resource* resource =
      GetResource(source->GetURL());
  if (!resource) {
    DVLOG(1) << "Cannot find resource for url:" << source->GetURL();
    AdvanceEntry();
    return;
  }

  ReadResponse(resource, source);
  std::string data;
  source->GetResponseAsString(&data);
  ReadData(resource, data);
  AdvanceEntry();
}

void MalwareDetailsCacheCollector::ReadResponse(
    ClientMalwareReportRequest::Resource* pb_resource,
    const net::URLFetcher* source) {
  DVLOG(1) << "ReadResponse";
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  net::HttpResponseHeaders* headers = source->GetResponseHeaders();
  if (!headers) {
    DVLOG(1) << "Missing response headers.";
    return;
  }

  ClientMalwareReportRequest::HTTPResponse* pb_response =
      pb_resource->mutable_response();
  pb_response->mutable_firstline()->set_code(headers->response_code());
  void* iter = NULL;
  std::string name, value;
  while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
    ClientMalwareReportRequest::HTTPHeader* pb_header =
        pb_response->add_headers();
    pb_header->set_name(name);
    // Strip any Set-Cookie headers.
    if (LowerCaseEqualsASCII(name, "set-cookie")) {
      pb_header->set_value("");
    } else {
      pb_header->set_value(value);
    }
  }

  if (!source->WasFetchedViaProxy()) {
    pb_response->set_remote_ip(source->GetSocketAddress().ToString());
  }
}

void MalwareDetailsCacheCollector::ReadData(
    ClientMalwareReportRequest::Resource* pb_resource,
    const std::string& data) {
  DVLOG(1) << "ReadData";
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  ClientMalwareReportRequest::HTTPResponse* pb_response =
      pb_resource->mutable_response();
  if (data.size() <= kMaxBodySizeBytes) {  // Only send small bodies for now.
    pb_response->set_body(data);
  }
  pb_response->set_bodylength(data.size());
  base::MD5Digest digest;
  base::MD5Sum(data.c_str(), data.size(), &digest);
  pb_response->set_bodydigest(base::MD5DigestToBase16(digest));
}

void MalwareDetailsCacheCollector::AdvanceEntry() {
  DVLOG(1) << "AdvanceEntry";
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  // Advance to the next resource.
  ++resources_it_;
  current_fetch_.reset(NULL);

  // Create a task so we don't take over the IO thread for too long.
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&MalwareDetailsCacheCollector::OpenEntry, this));
}

void MalwareDetailsCacheCollector::AllDone(bool success) {
  DVLOG(1) << "AllDone";
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  *result_ = success;
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback_);
  callback_.Reset();
}

/* [<][>][^][v][top][bottom][index][help] */