Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / safe_browsing / client_side_model_loader.cc
blobaaa95ba3a46d4591c3834edc291c6cd9165f6b7a
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/safe_browsing/client_side_model_loader.h"
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/safe_browsing/protocol_manager.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/common/safe_browsing/client_model.pb.h"
18 #include "chrome/common/safe_browsing/csd.pb.h"
19 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
20 #include "components/variations/variations_associated_data.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/http/http_status_code.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "net/url_request/url_request_context_getter.h"
25 #include "net/url_request/url_request_status.h"
26 #include "url/gurl.h"
28 namespace safe_browsing {
30 // Model Loader strings
31 const size_t ModelLoader::kMaxModelSizeBytes = 150 * 1024;
32 const int ModelLoader::kClientModelFetchIntervalMs = 3600 * 1000;
33 const char ModelLoader::kClientModelUrlPrefix[] =
34 "https://ssl.gstatic.com/safebrowsing/csd/";
35 const char ModelLoader::kClientModelNamePattern[] =
36 "client_model_v5%s_variation_%d.pb";
37 const char ModelLoader::kClientModelFinchExperiment[] =
38 "ClientSideDetectionModel";
39 const char ModelLoader::kClientModelFinchParam[] =
40 "ModelNum";
41 const char kUmaModelDownloadResponseMetricName[] =
42 "SBClientPhishing.ClientModelDownloadResponseOrErrorCode";
45 // static
46 int ModelLoader::GetModelNumber() {
47 std::string num_str = variations::GetVariationParamValue(
48 kClientModelFinchExperiment, kClientModelFinchParam);
49 int model_number = 0;
50 if (!base::StringToInt(num_str, &model_number)) {
51 model_number = 0; // Default model
53 return model_number;
56 // static
57 std::string ModelLoader::FillInModelName(bool is_extended_reporting,
58 int model_number) {
59 return base::StringPrintf(kClientModelNamePattern,
60 is_extended_reporting ? "_ext" : "", model_number);
63 // static
64 bool ModelLoader::ModelHasValidHashIds(const ClientSideModel& model) {
65 const int max_index = model.hashes_size() - 1;
66 for (int i = 0; i < model.rule_size(); ++i) {
67 for (int j = 0; j < model.rule(i).feature_size(); ++j) {
68 if (model.rule(i).feature(j) < 0 ||
69 model.rule(i).feature(j) > max_index) {
70 return false;
74 for (int i = 0; i < model.page_term_size(); ++i) {
75 if (model.page_term(i) < 0 || model.page_term(i) > max_index) {
76 return false;
79 return true;
82 // Model name and URL are a function of is_extended_reporting and Finch.
83 ModelLoader::ModelLoader(base::Closure update_renderers_callback,
84 net::URLRequestContextGetter* request_context_getter,
85 bool is_extended_reporting)
86 : name_(FillInModelName(is_extended_reporting, GetModelNumber())),
87 url_(kClientModelUrlPrefix + name_),
88 update_renderers_callback_(update_renderers_callback),
89 request_context_getter_(request_context_getter),
90 weak_factory_(this) {
91 DCHECK(url_.is_valid());
94 // For testing only
95 ModelLoader::ModelLoader(base::Closure update_renderers_callback,
96 const std::string model_name)
97 : name_(model_name),
98 url_(kClientModelUrlPrefix + name_),
99 update_renderers_callback_(update_renderers_callback),
100 request_context_getter_(NULL),
101 weak_factory_(this) {
102 DCHECK(url_.is_valid());
105 ModelLoader::~ModelLoader() {
108 void ModelLoader::StartFetch() {
109 // Start fetching the model either from the cache or possibly from the
110 // network if the model isn't in the cache.
112 // TODO(nparker): If no profile needs this model, we shouldn't fetch it.
113 // Then only re-fetch when a profile setting changes to need it.
114 // This will save on the order of ~50KB/week/client of bandwidth.
115 fetcher_ = net::URLFetcher::Create(0 /* ID used for testing */, url_,
116 net::URLFetcher::GET, this);
117 fetcher_->SetRequestContext(request_context_getter_);
118 fetcher_->Start();
121 void ModelLoader::OnURLFetchComplete(const net::URLFetcher* source) {
122 DCHECK_EQ(fetcher_, source);
123 DCHECK_EQ(url_, source->GetURL());
125 std::string data;
126 source->GetResponseAsString(&data);
127 net::URLRequestStatus status = source->GetStatus();
128 const bool is_success = status.is_success();
129 const int response_code = source->GetResponseCode();
130 SafeBrowsingProtocolManager::RecordHttpResponseOrErrorCode(
131 kUmaModelDownloadResponseMetricName, status, response_code);
133 // max_age is valid iff !0.
134 base::TimeDelta max_age;
135 if (is_success && net::HTTP_OK == response_code &&
136 source->GetResponseHeaders()) {
137 source->GetResponseHeaders()->GetMaxAgeValue(&max_age);
139 scoped_ptr<ClientSideModel> model(new ClientSideModel());
140 ClientModelStatus model_status;
141 if (!is_success || net::HTTP_OK != response_code) {
142 model_status = MODEL_FETCH_FAILED;
143 } else if (data.empty()) {
144 model_status = MODEL_EMPTY;
145 } else if (data.size() > kMaxModelSizeBytes) {
146 model_status = MODEL_TOO_LARGE;
147 } else if (!model->ParseFromString(data)) {
148 model_status = MODEL_PARSE_ERROR;
149 } else if (!model->IsInitialized() || !model->has_version()) {
150 model_status = MODEL_MISSING_FIELDS;
151 } else if (!ModelHasValidHashIds(*model)) {
152 model_status = MODEL_BAD_HASH_IDS;
153 } else if (model->version() < 0 ||
154 (model_.get() && model->version() < model_->version())) {
155 model_status = MODEL_INVALID_VERSION_NUMBER;
156 } else if (model_.get() && model->version() == model_->version()) {
157 model_status = MODEL_NOT_CHANGED;
158 } else {
159 // The model is valid => replace the existing model with the new one.
160 model_str_.assign(data);
161 model_.swap(model);
162 model_status = MODEL_SUCCESS;
164 EndFetch(model_status, max_age);
167 void ModelLoader::EndFetch(ClientModelStatus status, base::TimeDelta max_age) {
168 // We don't differentiate models in the UMA stats.
169 UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ClientModelStatus",
170 status,
171 MODEL_STATUS_MAX);
172 if (status == MODEL_SUCCESS) {
173 update_renderers_callback_.Run();
175 int delay_ms = kClientModelFetchIntervalMs;
176 // If the most recently fetched model had a valid max-age and the model was
177 // valid we're scheduling the next model update for after the max-age expired.
178 if (!max_age.is_zero() &&
179 (status == MODEL_SUCCESS || status == MODEL_NOT_CHANGED)) {
180 // We're adding 60s of additional delay to make sure we're past
181 // the model's age.
182 max_age += base::TimeDelta::FromMinutes(1);
183 delay_ms = max_age.InMilliseconds();
186 // Schedule the next model reload.
187 ScheduleFetch(delay_ms);
190 void ModelLoader::ScheduleFetch(int64 delay_ms) {
191 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
192 switches::kSbDisableAutoUpdate))
193 return;
194 base::MessageLoop::current()->PostDelayedTask(
195 FROM_HERE,
196 base::Bind(&ModelLoader::StartFetch, weak_factory_.GetWeakPtr()),
197 base::TimeDelta::FromMilliseconds(delay_ms));
200 void ModelLoader::CancelFetcher() {
201 // Invalidate any scheduled request.
202 weak_factory_.InvalidateWeakPtrs();
203 // Cancel any request in progress.
204 fetcher_.reset();
207 } // namespace safe_browsing