blob: 91bf26eb046517a6237e6c451518e7cc018b695d [file] [log] [blame]
// Copyright 2016 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 "components/safe_browsing_db/v4_update_protocol_manager.h"
#include <utility>
#include "base/base64.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/timer/timer.h"
#include "components/safe_browsing_db/safebrowsing.pb.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
using base::Time;
using base::TimeDelta;
namespace {
// Enumerate parsing failures for histogramming purposes. DO NOT CHANGE
// THE ORDERING OF THESE VALUES.
enum ParseResultType {
// Error parsing the protocol buffer from a string.
PARSE_FROM_STRING_ERROR = 0,
// No platform_type set in the response.
NO_PLATFORM_TYPE_ERROR = 1,
// No threat_entry_type set in the response.
NO_THREAT_ENTRY_TYPE_ERROR = 2,
// No threat_type set in the response.
NO_THREAT_TYPE_ERROR = 3,
// No state set in the response for one or more lists.
NO_STATE_ERROR = 4,
// Memory space for histograms is determined by the max. ALWAYS
// ADD NEW VALUES BEFORE THIS ONE.
PARSE_RESULT_TYPE_MAX = 5
};
// Record parsing errors of an update result.
void RecordParseUpdateResult(ParseResultType result_type) {
UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4UpdateResult", result_type,
PARSE_RESULT_TYPE_MAX);
}
void RecordUpdateResult(safe_browsing::V4OperationResult result) {
UMA_HISTOGRAM_ENUMERATION(
"SafeBrowsing.V4UpdateResult", result,
safe_browsing::V4OperationResult::OPERATION_RESULT_MAX);
}
} // namespace
namespace safe_browsing {
// Minimum time, in seconds, from start up before we must issue an update query.
static const int kV4TimerStartIntervalSecMin = 60;
// Maximum time, in seconds, from start up before we must issue an update query.
static const int kV4TimerStartIntervalSecMax = 300;
// The default V4UpdateProtocolManagerFactory.
class V4UpdateProtocolManagerFactoryImpl
: public V4UpdateProtocolManagerFactory {
public:
V4UpdateProtocolManagerFactoryImpl() {}
~V4UpdateProtocolManagerFactoryImpl() override {}
scoped_ptr<V4UpdateProtocolManager> CreateProtocolManager(
net::URLRequestContextGetter* request_context_getter,
const V4ProtocolConfig& config,
const base::hash_map<UpdateListIdentifier, std::string>&
current_list_states,
V4UpdateCallback callback) override {
return scoped_ptr<V4UpdateProtocolManager>(new V4UpdateProtocolManager(
request_context_getter, config, current_list_states, callback));
}
private:
DISALLOW_COPY_AND_ASSIGN(V4UpdateProtocolManagerFactoryImpl);
};
// V4UpdateProtocolManager implementation --------------------------------
// static
V4UpdateProtocolManagerFactory* V4UpdateProtocolManager::factory_ = NULL;
// static
scoped_ptr<V4UpdateProtocolManager> V4UpdateProtocolManager::Create(
net::URLRequestContextGetter* request_context_getter,
const V4ProtocolConfig& config,
const base::hash_map<UpdateListIdentifier, std::string>&
current_list_states,
V4UpdateCallback callback) {
if (!factory_)
factory_ = new V4UpdateProtocolManagerFactoryImpl();
return factory_->CreateProtocolManager(request_context_getter, config,
current_list_states, callback);
}
void V4UpdateProtocolManager::ResetUpdateErrors() {
update_error_count_ = 0;
update_back_off_mult_ = 1;
}
V4UpdateProtocolManager::V4UpdateProtocolManager(
net::URLRequestContextGetter* request_context_getter,
const V4ProtocolConfig& config,
const base::hash_map<UpdateListIdentifier, std::string>&
current_list_states,
V4UpdateCallback callback)
: current_list_states_(current_list_states),
update_error_count_(0),
update_back_off_mult_(1),
next_update_interval_(base::TimeDelta::FromSeconds(
base::RandInt(kV4TimerStartIntervalSecMin,
kV4TimerStartIntervalSecMax))),
config_(config),
request_context_getter_(request_context_getter),
url_fetcher_id_(0),
callback_(callback) {
ScheduleNextUpdate(false /* no back off */);
}
V4UpdateProtocolManager::~V4UpdateProtocolManager() {}
bool V4UpdateProtocolManager::IsUpdateScheduled() const {
return update_timer_.IsRunning();
}
void V4UpdateProtocolManager::ScheduleNextUpdate(bool back_off) {
// TODO(vakh): Set disable_auto_update correctly using the command line
// switch.
if (config_.disable_auto_update) {
DCHECK(!IsUpdateScheduled());
return;
}
// Reschedule with the new update.
base::TimeDelta next_update_interval = GetNextUpdateInterval(back_off);
ScheduleNextUpdateAfterInterval(next_update_interval);
}
// According to section 5 of the SafeBrowsing protocol specification, we must
// back off after a certain number of errors.
base::TimeDelta V4UpdateProtocolManager::GetNextUpdateInterval(bool back_off) {
DCHECK(CalledOnValidThread());
DCHECK(next_update_interval_ > base::TimeDelta());
base::TimeDelta next = next_update_interval_;
if (back_off) {
next = V4ProtocolManagerUtil::GetNextBackOffInterval(
&update_error_count_, &update_back_off_mult_);
}
return next;
}
void V4UpdateProtocolManager::ScheduleNextUpdateAfterInterval(
base::TimeDelta interval) {
DCHECK(CalledOnValidThread());
DCHECK(interval >= base::TimeDelta());
// Unschedule any current timer.
update_timer_.Stop();
update_timer_.Start(FROM_HERE, interval, this,
&V4UpdateProtocolManager::IssueUpdateRequest);
}
std::string V4UpdateProtocolManager::GetBase64SerializedUpdateRequestProto(
const base::hash_map<UpdateListIdentifier, std::string>&
current_list_states) {
// Build the request. Client info and client states are not added to the
// request protocol buffer. Client info is passed as params in the url.
FetchThreatListUpdatesRequest request;
for (const auto& entry : current_list_states) {
const auto& list_to_update = entry.first;
const auto& state = entry.second;
ListUpdateRequest* list_update_request = request.add_list_update_requests();
list_update_request->set_platform_type(list_to_update.platform_type);
list_update_request->set_threat_entry_type(
list_to_update.threat_entry_type);
list_update_request->set_threat_type(list_to_update.threat_type);
if (!state.empty()) {
list_update_request->set_state(state);
}
}
// Serialize and Base64 encode.
std::string req_data, req_base64;
request.SerializeToString(&req_data);
base::Base64Encode(req_data, &req_base64);
return req_base64;
}
bool V4UpdateProtocolManager::ParseUpdateResponse(
const std::string& data,
std::vector<ListUpdateResponse>* list_update_responses) {
FetchThreatListUpdatesResponse response;
if (!response.ParseFromString(data)) {
RecordParseUpdateResult(PARSE_FROM_STRING_ERROR);
return false;
}
if (response.has_minimum_wait_duration()) {
// Seconds resolution is good enough so we ignore the nanos field.
int64_t minimum_wait_duration_seconds =
response.minimum_wait_duration().seconds();
// Do not let the next_update_interval_ to be too low.
if (minimum_wait_duration_seconds < kV4TimerStartIntervalSecMin) {
minimum_wait_duration_seconds = kV4TimerStartIntervalSecMin;
}
next_update_interval_ =
base::TimeDelta::FromSeconds(minimum_wait_duration_seconds);
}
// TODO(vakh): Do something useful with this response.
for (const ListUpdateResponse& list_update_response :
response.list_update_responses()) {
if (!list_update_response.has_platform_type()) {
RecordParseUpdateResult(NO_PLATFORM_TYPE_ERROR);
} else if (!list_update_response.has_threat_entry_type()) {
RecordParseUpdateResult(NO_THREAT_ENTRY_TYPE_ERROR);
} else if (!list_update_response.has_threat_type()) {
RecordParseUpdateResult(NO_THREAT_TYPE_ERROR);
} else if (!list_update_response.has_new_client_state()) {
RecordParseUpdateResult(NO_STATE_ERROR);
} else {
list_update_responses->push_back(list_update_response);
}
}
return true;
}
void V4UpdateProtocolManager::IssueUpdateRequest() {
DCHECK(CalledOnValidThread());
// If an update request is already pending, record and return silently.
if (request_.get()) {
RecordUpdateResult(V4OperationResult::ALREADY_PENDING_ERROR);
return;
}
std::string req_base64 = GetBase64SerializedUpdateRequestProto(
current_list_states_);
GURL update_url = GetUpdateUrl(req_base64);
request_.reset(net::URLFetcher::Create(url_fetcher_id_++, update_url,
net::URLFetcher::GET, this)
.release());
request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
request_->SetRequestContext(request_context_getter_.get());
request_->Start();
// TODO(vakh): Handle request timeout.
}
// net::URLFetcherDelegate implementation ----------------------------------
// SafeBrowsing request responses are handled here.
void V4UpdateProtocolManager::OnURLFetchComplete(
const net::URLFetcher* source) {
DCHECK(CalledOnValidThread());
int response_code = source->GetResponseCode();
net::URLRequestStatus status = source->GetStatus();
V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode(
"SafeBrowsing.V4UpdateHttpResponseOrErrorCode", status, response_code);
std::vector<ListUpdateResponse> list_update_responses;
bool back_off;
if (status.is_success() && response_code == net::HTTP_OK) {
back_off = false;
RecordUpdateResult(V4OperationResult::STATUS_200);
ResetUpdateErrors();
std::string data;
source->GetResponseAsString(&data);
if (!ParseUpdateResponse(data, &list_update_responses)) {
list_update_responses.clear();
RecordUpdateResult(V4OperationResult::PARSE_ERROR);
}
// Invoke the callback with list_update_responses.
// The caller should update its state now, based on list_update_responses.
callback_.Run(list_update_responses);
} else {
back_off = true;
DVLOG(1) << "SafeBrowsing GetEncodedUpdates request for: "
<< source->GetURL() << " failed with error: " << status.error()
<< " and response code: " << response_code;
if (status.status() == net::URLRequestStatus::FAILED) {
RecordUpdateResult(V4OperationResult::NETWORK_ERROR);
} else {
RecordUpdateResult(V4OperationResult::HTTP_ERROR);
}
// TODO(vakh): Figure out whether it is just a network error vs backoff vs
// another condition and RecordUpdateResult more accurately.
}
request_.reset();
ScheduleNextUpdate(back_off);
}
GURL V4UpdateProtocolManager::GetUpdateUrl(
const std::string& req_base64) const {
return V4ProtocolManagerUtil::GetRequestUrl(req_base64, "encodedUpdates",
config_);
}
} // namespace safe_browsing