This source file includes following definitions.
- RecordUpdateResult
- CreateProtocolManager
- Create
- url_fetcher_id_
- RecordGetHashResult
- IsUpdateScheduled
- GetFullHash
- GetNextUpdate
- OnURLFetchComplete
- HandleServiceResponse
- Initialize
- ScheduleNextUpdate
- ForceScheduleNextUpdate
- GetNextUpdateInterval
- GetNextBackOffInterval
- IssueUpdateRequest
- IssueBackupUpdateRequest
- IssueChunkRequest
- OnGetChunksComplete
- UpdateResponseTimeout
- OnAddChunksComplete
- FormatList
- HandleGetHashError
- UpdateFinished
- UpdateFinished
- UpdateUrl
- BackupUpdateUrl
- GetHashUrl
- NextChunkUrl
- is_download
- is_download
#include "chrome/browser/safe_browsing/protocol_manager.h"
#ifndef NDEBUG
#include "base/base64.h"
#endif
#include "base/environment.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/timer/timer.h"
#include "chrome/browser/safe_browsing/protocol_parser.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/env_vars.h"
#include "google_apis/google_api_keys.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.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 base::Time;
using base::TimeDelta;
namespace {
enum UpdateResult {
UPDATE_RESULT_FAIL,
UPDATE_RESULT_SUCCESS,
UPDATE_RESULT_BACKUP_CONNECT_FAIL,
UPDATE_RESULT_BACKUP_CONNECT_SUCCESS,
UPDATE_RESULT_BACKUP_HTTP_FAIL,
UPDATE_RESULT_BACKUP_HTTP_SUCCESS,
UPDATE_RESULT_BACKUP_NETWORK_FAIL,
UPDATE_RESULT_BACKUP_NETWORK_SUCCESS,
UPDATE_RESULT_MAX,
UPDATE_RESULT_BACKUP_START = UPDATE_RESULT_BACKUP_CONNECT_FAIL,
};
void RecordUpdateResult(UpdateResult result) {
DCHECK(result >= 0 && result < UPDATE_RESULT_MAX);
UMA_HISTOGRAM_ENUMERATION("SB2.UpdateResult", result, UPDATE_RESULT_MAX);
}
}
static const int kSbTimerStartIntervalSecMin = 60;
static const int kSbTimerStartIntervalSecMax = 300;
static const int kSbMaxUpdateWaitSec = 30;
static const int kSbMaxBackOff = 8;
class SBProtocolManagerFactoryImpl : public SBProtocolManagerFactory {
public:
SBProtocolManagerFactoryImpl() { }
virtual ~SBProtocolManagerFactoryImpl() { }
virtual SafeBrowsingProtocolManager* CreateProtocolManager(
SafeBrowsingProtocolManagerDelegate* delegate,
net::URLRequestContextGetter* request_context_getter,
const SafeBrowsingProtocolConfig& config) OVERRIDE {
return new SafeBrowsingProtocolManager(
delegate, request_context_getter, config);
}
private:
DISALLOW_COPY_AND_ASSIGN(SBProtocolManagerFactoryImpl);
};
SBProtocolManagerFactory* SafeBrowsingProtocolManager::factory_ = NULL;
SafeBrowsingProtocolManager* SafeBrowsingProtocolManager::Create(
SafeBrowsingProtocolManagerDelegate* delegate,
net::URLRequestContextGetter* request_context_getter,
const SafeBrowsingProtocolConfig& config) {
if (!factory_)
factory_ = new SBProtocolManagerFactoryImpl();
return factory_->CreateProtocolManager(
delegate, request_context_getter, config);
}
SafeBrowsingProtocolManager::SafeBrowsingProtocolManager(
SafeBrowsingProtocolManagerDelegate* delegate,
net::URLRequestContextGetter* request_context_getter,
const SafeBrowsingProtocolConfig& config)
: delegate_(delegate),
request_type_(NO_REQUEST),
update_error_count_(0),
gethash_error_count_(0),
update_back_off_mult_(1),
gethash_back_off_mult_(1),
next_update_interval_(base::TimeDelta::FromSeconds(
base::RandInt(kSbTimerStartIntervalSecMin,
kSbTimerStartIntervalSecMax))),
update_state_(FIRST_REQUEST),
chunk_pending_to_write_(false),
version_(config.version),
update_size_(0),
client_name_(config.client_name),
request_context_getter_(request_context_getter),
url_prefix_(config.url_prefix),
backup_update_reason_(BACKUP_UPDATE_REASON_MAX),
disable_auto_update_(config.disable_auto_update),
url_fetcher_id_(0) {
DCHECK(!url_prefix_.empty());
backup_url_prefixes_[BACKUP_UPDATE_REASON_CONNECT] =
config.backup_connect_error_url_prefix;
backup_url_prefixes_[BACKUP_UPDATE_REASON_HTTP] =
config.backup_http_error_url_prefix;
backup_url_prefixes_[BACKUP_UPDATE_REASON_NETWORK] =
config.backup_network_error_url_prefix;
back_off_fuzz_ = static_cast<float>(base::RandDouble());
if (version_.empty())
version_ = SafeBrowsingProtocolManagerHelper::Version();
}
void SafeBrowsingProtocolManager::RecordGetHashResult(
bool is_download, ResultType result_type) {
if (is_download) {
UMA_HISTOGRAM_ENUMERATION("SB2.GetHashResultDownload", result_type,
GET_HASH_RESULT_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION("SB2.GetHashResult", result_type,
GET_HASH_RESULT_MAX);
}
}
bool SafeBrowsingProtocolManager::IsUpdateScheduled() const {
return update_timer_.IsRunning();
}
SafeBrowsingProtocolManager::~SafeBrowsingProtocolManager() {
STLDeleteContainerPairFirstPointers(hash_requests_.begin(),
hash_requests_.end());
hash_requests_.clear();
}
void SafeBrowsingProtocolManager::GetFullHash(
const std::vector<SBPrefix>& prefixes,
FullHashCallback callback,
bool is_download) {
DCHECK(CalledOnValidThread());
if (gethash_error_count_ && Time::Now() <= next_gethash_time_) {
std::vector<SBFullHashResult> full_hashes;
callback.Run(full_hashes, false);
return;
}
GURL gethash_url = GetHashUrl();
net::URLFetcher* fetcher = net::URLFetcher::Create(
url_fetcher_id_++, gethash_url, net::URLFetcher::POST, this);
hash_requests_[fetcher] = FullHashDetails(callback, is_download);
std::string get_hash;
SafeBrowsingProtocolParser parser;
parser.FormatGetHash(prefixes, &get_hash);
fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
fetcher->SetRequestContext(request_context_getter_.get());
fetcher->SetUploadData("text/plain", get_hash);
fetcher->Start();
}
void SafeBrowsingProtocolManager::GetNextUpdate() {
DCHECK(CalledOnValidThread());
if (!request_.get() && request_type_ == NO_REQUEST)
IssueUpdateRequest();
}
void SafeBrowsingProtocolManager::OnURLFetchComplete(
const net::URLFetcher* source) {
DCHECK(CalledOnValidThread());
scoped_ptr<const net::URLFetcher> fetcher;
bool parsed_ok = true;
HashRequests::iterator it = hash_requests_.find(source);
if (it != hash_requests_.end()) {
fetcher.reset(it->first);
const FullHashDetails& details = it->second;
std::vector<SBFullHashResult> full_hashes;
bool can_cache = false;
if (source->GetStatus().is_success() &&
(source->GetResponseCode() == 200 ||
source->GetResponseCode() == 204)) {
if (source->GetResponseCode() == 200)
RecordGetHashResult(details.is_download, GET_HASH_STATUS_200);
else
RecordGetHashResult(details.is_download, GET_HASH_STATUS_204);
can_cache = true;
gethash_error_count_ = 0;
gethash_back_off_mult_ = 1;
SafeBrowsingProtocolParser parser;
std::string data;
source->GetResponseAsString(&data);
parsed_ok = parser.ParseGetHash(
data.data(),
static_cast<int>(data.length()),
&full_hashes);
if (!parsed_ok) {
full_hashes.clear();
}
} else {
HandleGetHashError(Time::Now());
if (source->GetStatus().status() == net::URLRequestStatus::FAILED) {
VLOG(1) << "SafeBrowsing GetHash request for: " << source->GetURL()
<< " failed with error: " << source->GetStatus().error();
} else {
VLOG(1) << "SafeBrowsing GetHash request for: " << source->GetURL()
<< " failed with error: " << source->GetResponseCode();
}
}
details.callback.Run(full_hashes, can_cache);
hash_requests_.erase(it);
} else {
fetcher.reset(request_.release());
if (request_type_ == UPDATE_REQUEST ||
request_type_ == BACKUP_UPDATE_REQUEST) {
if (!fetcher.get()) {
return;
}
timeout_timer_.Stop();
}
net::URLRequestStatus status = source->GetStatus();
if (status.is_success() && source->GetResponseCode() == 200) {
std::string data;
source->GetResponseAsString(&data);
parsed_ok = HandleServiceResponse(
source->GetURL(), data.data(), static_cast<int>(data.length()));
if (!parsed_ok) {
VLOG(1) << "SafeBrowsing request for: " << source->GetURL()
<< " failed parse.";
chunk_request_urls_.clear();
if (request_type_ == UPDATE_REQUEST &&
IssueBackupUpdateRequest(BACKUP_UPDATE_REASON_HTTP)) {
return;
}
UpdateFinished(false);
}
switch (request_type_) {
case CHUNK_REQUEST:
if (parsed_ok) {
chunk_request_urls_.pop_front();
if (chunk_request_urls_.empty() && !chunk_pending_to_write_)
UpdateFinished(true);
}
break;
case UPDATE_REQUEST:
case BACKUP_UPDATE_REQUEST:
if (chunk_request_urls_.empty() && parsed_ok) {
UpdateFinished(true);
}
break;
case NO_REQUEST:
break;
default:
NOTREACHED();
break;
}
} else {
if (status.status() == net::URLRequestStatus::FAILED) {
VLOG(1) << "SafeBrowsing request for: " << source->GetURL()
<< " failed with error: " << source->GetStatus().error();
} else {
VLOG(1) << "SafeBrowsing request for: " << source->GetURL()
<< " failed with error: " << source->GetResponseCode();
}
if (request_type_ == CHUNK_REQUEST) {
chunk_request_urls_.clear();
} else if (request_type_ == UPDATE_REQUEST) {
BackupUpdateReason backup_update_reason = BACKUP_UPDATE_REASON_MAX;
if (status.is_success()) {
backup_update_reason = BACKUP_UPDATE_REASON_HTTP;
} else {
switch (status.error()) {
case net::ERR_INTERNET_DISCONNECTED:
case net::ERR_NETWORK_CHANGED:
backup_update_reason = BACKUP_UPDATE_REASON_NETWORK;
break;
default:
backup_update_reason = BACKUP_UPDATE_REASON_CONNECT;
break;
}
}
if (backup_update_reason != BACKUP_UPDATE_REASON_MAX &&
IssueBackupUpdateRequest(backup_update_reason)) {
return;
}
}
UpdateFinished(false);
}
}
IssueChunkRequest();
}
bool SafeBrowsingProtocolManager::HandleServiceResponse(const GURL& url,
const char* data,
int length) {
DCHECK(CalledOnValidThread());
SafeBrowsingProtocolParser parser;
switch (request_type_) {
case UPDATE_REQUEST:
case BACKUP_UPDATE_REQUEST: {
int next_update_sec = -1;
bool reset = false;
scoped_ptr<std::vector<SBChunkDelete> > chunk_deletes(
new std::vector<SBChunkDelete>);
std::vector<ChunkUrl> chunk_urls;
if (!parser.ParseUpdate(data, length, &next_update_sec,
&reset, chunk_deletes.get(), &chunk_urls)) {
return false;
}
base::TimeDelta next_update_interval =
base::TimeDelta::FromSeconds(next_update_sec);
last_update_ = Time::Now();
if (update_state_ == FIRST_REQUEST)
update_state_ = SECOND_REQUEST;
else if (update_state_ == SECOND_REQUEST)
update_state_ = NORMAL_REQUEST;
if (next_update_interval > base::TimeDelta()) {
next_update_interval_ = next_update_interval;
} else if (update_state_ == SECOND_REQUEST) {
next_update_interval_ = base::TimeDelta::FromSeconds(
base::RandInt(15, 45));
}
if (!chunk_urls.empty()) {
UMA_HISTOGRAM_COUNTS("SB2.UpdateUrls", chunk_urls.size());
for (size_t i = 0; i < chunk_urls.size(); ++i)
chunk_request_urls_.push_back(chunk_urls[i]);
}
if (reset) {
delegate_->ResetDatabase();
return true;
}
if (!chunk_deletes->empty())
delegate_->DeleteChunks(chunk_deletes.release());
break;
}
case CHUNK_REQUEST: {
UMA_HISTOGRAM_TIMES("SB2.ChunkRequest",
base::Time::Now() - chunk_request_start_);
const ChunkUrl chunk_url = chunk_request_urls_.front();
scoped_ptr<SBChunkList> chunks(new SBChunkList);
UMA_HISTOGRAM_COUNTS("SB2.ChunkSize", length);
update_size_ += length;
if (!parser.ParseChunk(chunk_url.list_name, data, length,
chunks.get())) {
#ifndef NDEBUG
std::string data_str;
data_str.assign(data, length);
std::string encoded_chunk;
base::Base64Encode(data_str, &encoded_chunk);
VLOG(1) << "ParseChunk error for chunk: " << chunk_url.url
<< ", Base64Encode(data): " << encoded_chunk
<< ", length: " << length;
#endif
return false;
}
if (!chunks->empty()) {
chunk_pending_to_write_ = true;
delegate_->AddChunks(
chunk_url.list_name, chunks.release(),
base::Bind(&SafeBrowsingProtocolManager::OnAddChunksComplete,
base::Unretained(this)));
}
break;
}
default:
return false;
}
return true;
}
void SafeBrowsingProtocolManager::Initialize() {
DCHECK(CalledOnValidThread());
scoped_ptr<base::Environment> env(base::Environment::Create());
if (env->HasVar(env_vars::kHeadless))
return;
ScheduleNextUpdate(false );
}
void SafeBrowsingProtocolManager::ScheduleNextUpdate(bool back_off) {
DCHECK(CalledOnValidThread());
if (disable_auto_update_) {
update_timer_.Stop();
return;
}
base::TimeDelta next_update_interval = GetNextUpdateInterval(back_off);
ForceScheduleNextUpdate(next_update_interval);
}
void SafeBrowsingProtocolManager::ForceScheduleNextUpdate(
base::TimeDelta interval) {
DCHECK(CalledOnValidThread());
DCHECK(interval >= base::TimeDelta());
update_timer_.Stop();
update_timer_.Start(FROM_HERE, interval, this,
&SafeBrowsingProtocolManager::GetNextUpdate);
}
base::TimeDelta SafeBrowsingProtocolManager::GetNextUpdateInterval(
bool back_off) {
DCHECK(CalledOnValidThread());
DCHECK(next_update_interval_ > base::TimeDelta());
base::TimeDelta next = next_update_interval_;
if (back_off) {
next = GetNextBackOffInterval(&update_error_count_, &update_back_off_mult_);
} else {
update_error_count_ = 0;
update_back_off_mult_ = 1;
}
return next;
}
base::TimeDelta SafeBrowsingProtocolManager::GetNextBackOffInterval(
int* error_count, int* multiplier) const {
DCHECK(CalledOnValidThread());
DCHECK(multiplier && error_count);
(*error_count)++;
if (*error_count > 1 && *error_count < 6) {
base::TimeDelta next = base::TimeDelta::FromMinutes(
*multiplier * (1 + back_off_fuzz_) * 30);
*multiplier *= 2;
if (*multiplier > kSbMaxBackOff)
*multiplier = kSbMaxBackOff;
return next;
}
if (*error_count >= 6)
return base::TimeDelta::FromHours(8);
return base::TimeDelta::FromMinutes(1);
}
void SafeBrowsingProtocolManager::IssueUpdateRequest() {
DCHECK(CalledOnValidThread());
request_type_ = UPDATE_REQUEST;
delegate_->UpdateStarted();
delegate_->GetChunks(
base::Bind(&SafeBrowsingProtocolManager::OnGetChunksComplete,
base::Unretained(this)));
}
bool SafeBrowsingProtocolManager::IssueBackupUpdateRequest(
BackupUpdateReason backup_update_reason) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(request_type_, UPDATE_REQUEST);
DCHECK(backup_update_reason >= 0 &&
backup_update_reason < BACKUP_UPDATE_REASON_MAX);
if (backup_url_prefixes_[backup_update_reason].empty())
return false;
request_type_ = BACKUP_UPDATE_REQUEST;
backup_update_reason_ = backup_update_reason;
GURL backup_update_url = BackupUpdateUrl(backup_update_reason);
request_.reset(net::URLFetcher::Create(
url_fetcher_id_++, backup_update_url, net::URLFetcher::POST, this));
request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
request_->SetRequestContext(request_context_getter_.get());
request_->SetUploadData("text/plain", update_list_data_);
request_->Start();
timeout_timer_.Start(FROM_HERE, TimeDelta::FromSeconds(kSbMaxUpdateWaitSec),
this,
&SafeBrowsingProtocolManager::UpdateResponseTimeout);
return true;
}
void SafeBrowsingProtocolManager::IssueChunkRequest() {
DCHECK(CalledOnValidThread());
if (request_.get() || chunk_request_urls_.empty() || chunk_pending_to_write_)
return;
ChunkUrl next_chunk = chunk_request_urls_.front();
DCHECK(!next_chunk.url.empty());
GURL chunk_url = NextChunkUrl(next_chunk.url);
request_type_ = CHUNK_REQUEST;
request_.reset(net::URLFetcher::Create(
url_fetcher_id_++, chunk_url, net::URLFetcher::GET, this));
request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
request_->SetRequestContext(request_context_getter_.get());
chunk_request_start_ = base::Time::Now();
request_->Start();
}
void SafeBrowsingProtocolManager::OnGetChunksComplete(
const std::vector<SBListChunkRanges>& lists, bool database_error) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(request_type_, UPDATE_REQUEST);
DCHECK(update_list_data_.empty());
if (database_error) {
UpdateFinished(false, false);
return;
}
bool found_malware = false;
bool found_phishing = false;
for (size_t i = 0; i < lists.size(); ++i) {
update_list_data_.append(FormatList(lists[i]));
if (lists[i].name == safe_browsing_util::kPhishingList)
found_phishing = true;
if (lists[i].name == safe_browsing_util::kMalwareList)
found_malware = true;
}
if (!found_phishing)
update_list_data_.append(FormatList(
SBListChunkRanges(safe_browsing_util::kPhishingList)));
if (!found_malware)
update_list_data_.append(FormatList(
SBListChunkRanges(safe_browsing_util::kMalwareList)));
UMA_HISTOGRAM_COUNTS("SB2.UpdateRequestSize", update_list_data_.size());
GURL update_url = UpdateUrl();
request_.reset(net::URLFetcher::Create(
url_fetcher_id_++, update_url, net::URLFetcher::POST, this));
request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
request_->SetRequestContext(request_context_getter_.get());
request_->SetUploadData("text/plain", update_list_data_);
request_->Start();
timeout_timer_.Start(FROM_HERE, TimeDelta::FromSeconds(kSbMaxUpdateWaitSec),
this,
&SafeBrowsingProtocolManager::UpdateResponseTimeout);
}
void SafeBrowsingProtocolManager::UpdateResponseTimeout() {
DCHECK(CalledOnValidThread());
DCHECK(request_type_ == UPDATE_REQUEST ||
request_type_ == BACKUP_UPDATE_REQUEST);
request_.reset();
if (request_type_ == UPDATE_REQUEST &&
IssueBackupUpdateRequest(BACKUP_UPDATE_REASON_CONNECT)) {
return;
}
UpdateFinished(false);
}
void SafeBrowsingProtocolManager::OnAddChunksComplete() {
DCHECK(CalledOnValidThread());
chunk_pending_to_write_ = false;
if (chunk_request_urls_.empty()) {
UMA_HISTOGRAM_LONG_TIMES("SB2.Update", Time::Now() - last_update_);
UpdateFinished(true);
} else {
IssueChunkRequest();
}
}
std::string SafeBrowsingProtocolManager::FormatList(
const SBListChunkRanges& list) {
std::string formatted_results;
formatted_results.append(list.name);
formatted_results.append(";");
if (!list.adds.empty()) {
formatted_results.append("a:" + list.adds);
if (!list.subs.empty())
formatted_results.append(":");
}
if (!list.subs.empty()) {
formatted_results.append("s:" + list.subs);
}
formatted_results.append("\n");
return formatted_results;
}
void SafeBrowsingProtocolManager::HandleGetHashError(const Time& now) {
DCHECK(CalledOnValidThread());
base::TimeDelta next = GetNextBackOffInterval(
&gethash_error_count_, &gethash_back_off_mult_);
next_gethash_time_ = now + next;
}
void SafeBrowsingProtocolManager::UpdateFinished(bool success) {
UpdateFinished(success, !success);
}
void SafeBrowsingProtocolManager::UpdateFinished(bool success, bool back_off) {
DCHECK(CalledOnValidThread());
UMA_HISTOGRAM_COUNTS("SB2.UpdateSize", update_size_);
update_size_ = 0;
bool update_success = success || request_type_ == CHUNK_REQUEST;
if (backup_update_reason_ == BACKUP_UPDATE_REASON_MAX) {
RecordUpdateResult(
update_success ? UPDATE_RESULT_SUCCESS : UPDATE_RESULT_FAIL);
} else {
UpdateResult update_result = static_cast<UpdateResult>(
UPDATE_RESULT_BACKUP_START +
(static_cast<int>(backup_update_reason_) * 2) +
update_success);
RecordUpdateResult(update_result);
}
backup_update_reason_ = BACKUP_UPDATE_REASON_MAX;
request_type_ = NO_REQUEST;
update_list_data_.clear();
delegate_->UpdateFinished(success);
ScheduleNextUpdate(back_off);
}
GURL SafeBrowsingProtocolManager::UpdateUrl() const {
std::string url = SafeBrowsingProtocolManagerHelper::ComposeUrl(
url_prefix_, "downloads", client_name_, version_, additional_query_);
return GURL(url);
}
GURL SafeBrowsingProtocolManager::BackupUpdateUrl(
BackupUpdateReason backup_update_reason) const {
DCHECK(backup_update_reason >= 0 &&
backup_update_reason < BACKUP_UPDATE_REASON_MAX);
DCHECK(!backup_url_prefixes_[backup_update_reason].empty());
std::string url = SafeBrowsingProtocolManagerHelper::ComposeUrl(
backup_url_prefixes_[backup_update_reason], "downloads", client_name_,
version_, additional_query_);
return GURL(url);
}
GURL SafeBrowsingProtocolManager::GetHashUrl() const {
std::string url = SafeBrowsingProtocolManagerHelper::ComposeUrl(
url_prefix_, "gethash", client_name_, version_, additional_query_);
return GURL(url);
}
GURL SafeBrowsingProtocolManager::NextChunkUrl(const std::string& url) const {
DCHECK(CalledOnValidThread());
std::string next_url;
if (!StartsWithASCII(url, "http://", false) &&
!StartsWithASCII(url, "https://", false)) {
if (StartsWithASCII(url_prefix_, "https://", false))
next_url.append("https://");
else
next_url.append("http://");
next_url.append(url);
} else {
next_url = url;
}
if (!additional_query_.empty()) {
if (next_url.find("?") != std::string::npos) {
next_url.append("&");
} else {
next_url.append("?");
}
next_url.append(additional_query_);
}
return GURL(next_url);
}
SafeBrowsingProtocolManager::FullHashDetails::FullHashDetails()
: callback(),
is_download(false) {
}
SafeBrowsingProtocolManager::FullHashDetails::FullHashDetails(
FullHashCallback callback, bool is_download)
: callback(callback),
is_download(is_download) {
}
SafeBrowsingProtocolManager::FullHashDetails::~FullHashDetails() {
}
SafeBrowsingProtocolManagerDelegate::~SafeBrowsingProtocolManagerDelegate() {
}