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"
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"
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
[] =
44 int ModelLoader::GetModelNumber() {
45 std::string num_str
= variations::GetVariationParamValue(
46 kClientModelFinchExperiment
, kClientModelFinchParam
);
48 if (!base::StringToInt(num_str
, &model_number
)) {
49 model_number
= 0; // Default model
55 std::string
ModelLoader::FillInModelName(bool is_extended_reporting
,
57 return base::StringPrintf(kClientModelNamePattern
,
58 is_extended_reporting
? "_ext" : "", model_number
);
62 bool ModelLoader::ModelHasValidHashIds(const ClientSideModel
& model
) {
63 const int max_index
= model
.hashes_size() - 1;
64 for (int i
= 0; i
< model
.rule_size(); ++i
) {
65 for (int j
= 0; j
< model
.rule(i
).feature_size(); ++j
) {
66 if (model
.rule(i
).feature(j
) < 0 ||
67 model
.rule(i
).feature(j
) > max_index
) {
72 for (int i
= 0; i
< model
.page_term_size(); ++i
) {
73 if (model
.page_term(i
) < 0 || model
.page_term(i
) > max_index
) {
80 // Model name and URL are a function of is_extended_reporting and Finch.
81 ModelLoader::ModelLoader(base::Closure update_renderers_callback
,
82 net::URLRequestContextGetter
* request_context_getter
,
83 bool is_extended_reporting
)
84 : name_(FillInModelName(is_extended_reporting
, GetModelNumber())),
85 url_(kClientModelUrlPrefix
+ name_
),
86 update_renderers_callback_(update_renderers_callback
),
87 request_context_getter_(request_context_getter
),
89 DCHECK(url_
.is_valid());
93 ModelLoader::ModelLoader(base::Closure update_renderers_callback
,
94 const std::string model_name
)
96 url_(kClientModelUrlPrefix
+ name_
),
97 update_renderers_callback_(update_renderers_callback
),
98 request_context_getter_(NULL
),
100 DCHECK(url_
.is_valid());
103 ModelLoader::~ModelLoader() {
106 void ModelLoader::StartFetch() {
107 // Start fetching the model either from the cache or possibly from the
108 // network if the model isn't in the cache.
110 // TODO(nparker): If no profile needs this model, we shouldn't fetch it.
111 // Then only re-fetch when a profile setting changes to need it.
112 // This will save on the order of ~50KB/week/client of bandwidth.
113 fetcher_
= net::URLFetcher::Create(0 /* ID used for testing */, url_
,
114 net::URLFetcher::GET
, this);
115 fetcher_
->SetRequestContext(request_context_getter_
);
119 void ModelLoader::OnURLFetchComplete(const net::URLFetcher
* source
) {
120 DCHECK_EQ(fetcher_
, source
);
121 DCHECK_EQ(url_
, source
->GetURL());
124 source
->GetResponseAsString(&data
);
125 net::URLRequestStatus status
= source
->GetStatus();
126 const bool is_success
= status
.is_success();
127 const int response_code
= source
->GetResponseCode();
128 SafeBrowsingProtocolManager::RecordGetHashResponseOrErrorCode(
129 status
, response_code
);
131 // max_age is valid iff !0.
132 base::TimeDelta max_age
;
133 if (is_success
&& net::HTTP_OK
== response_code
&&
134 source
->GetResponseHeaders()) {
135 source
->GetResponseHeaders()->GetMaxAgeValue(&max_age
);
137 scoped_ptr
<ClientSideModel
> model(new ClientSideModel());
138 ClientModelStatus model_status
;
139 if (!is_success
|| net::HTTP_OK
!= response_code
) {
140 model_status
= MODEL_FETCH_FAILED
;
141 } else if (data
.empty()) {
142 model_status
= MODEL_EMPTY
;
143 } else if (data
.size() > kMaxModelSizeBytes
) {
144 model_status
= MODEL_TOO_LARGE
;
145 } else if (!model
->ParseFromString(data
)) {
146 model_status
= MODEL_PARSE_ERROR
;
147 } else if (!model
->IsInitialized() || !model
->has_version()) {
148 model_status
= MODEL_MISSING_FIELDS
;
149 } else if (!ModelHasValidHashIds(*model
)) {
150 model_status
= MODEL_BAD_HASH_IDS
;
151 } else if (model
->version() < 0 ||
152 (model_
.get() && model
->version() < model_
->version())) {
153 model_status
= MODEL_INVALID_VERSION_NUMBER
;
154 } else if (model_
.get() && model
->version() == model_
->version()) {
155 model_status
= MODEL_NOT_CHANGED
;
157 // The model is valid => replace the existing model with the new one.
158 model_str_
.assign(data
);
160 model_status
= MODEL_SUCCESS
;
162 EndFetch(model_status
, max_age
);
165 void ModelLoader::EndFetch(ClientModelStatus status
, base::TimeDelta max_age
) {
166 // We don't differentiate models in the UMA stats.
167 UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ClientModelStatus",
170 if (status
== MODEL_SUCCESS
) {
171 update_renderers_callback_
.Run();
173 int delay_ms
= kClientModelFetchIntervalMs
;
174 // If the most recently fetched model had a valid max-age and the model was
175 // valid we're scheduling the next model update for after the max-age expired.
176 if (!max_age
.is_zero() &&
177 (status
== MODEL_SUCCESS
|| status
== MODEL_NOT_CHANGED
)) {
178 // We're adding 60s of additional delay to make sure we're past
180 max_age
+= base::TimeDelta::FromMinutes(1);
181 delay_ms
= max_age
.InMilliseconds();
184 // Schedule the next model reload.
185 ScheduleFetch(delay_ms
);
188 void ModelLoader::ScheduleFetch(int64 delay_ms
) {
189 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
190 switches::kSbDisableAutoUpdate
))
192 base::MessageLoop::current()->PostDelayedTask(
194 base::Bind(&ModelLoader::StartFetch
, weak_factory_
.GetWeakPtr()),
195 base::TimeDelta::FromMilliseconds(delay_ms
));
198 void ModelLoader::CancelFetcher() {
199 // Invalidate any scheduled request.
200 weak_factory_
.InvalidateWeakPtrs();
201 // Cancel any request in progress.
205 } // namespace safe_browsing