OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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. | |
4 | |
5 #include "components/safe_browsing_db/v4_update_protocol_manager.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/base64.h" | |
10 #include "base/macros.h" | |
11 #include "base/metrics/histogram_macros.h" | |
12 #include "base/timer/timer.h" | |
13 #include "components/safe_browsing_db/safebrowsing.pb.h" | |
14 #include "net/base/load_flags.h" | |
15 #include "net/http/http_response_headers.h" | |
16 #include "net/http/http_status_code.h" | |
17 #include "net/url_request/url_fetcher.h" | |
18 #include "net/url_request/url_request_context_getter.h" | |
19 | |
20 using base::Time; | |
21 using base::TimeDelta; | |
22 | |
23 namespace { | |
24 | |
25 // Enumerate parsing failures for histogramming purposes. DO NOT CHANGE | |
26 // THE ORDERING OF THESE VALUES. | |
27 enum ParseResultType { | |
28 // Error parsing the protocol buffer from a string. | |
29 PARSE_FROM_STRING_ERROR = 0, | |
30 | |
31 // No platform_type set in the response. | |
32 NO_PLATFORM_TYPE_ERROR = 1, | |
33 | |
34 // No threat_entry_type set in the response. | |
35 NO_THREAT_ENTRY_TYPE_ERROR = 2, | |
36 | |
37 // No threat_type set in the response. | |
38 NO_THREAT_TYPE_ERROR = 3, | |
39 | |
40 // No state set in the response for one or more lists. | |
41 NO_STATE_ERROR = 4, | |
42 | |
43 // Memory space for histograms is determined by the max. ALWAYS | |
44 // ADD NEW VALUES BEFORE THIS ONE. | |
45 PARSE_RESULT_TYPE_MAX = 5 | |
46 }; | |
47 | |
48 // Record parsing errors of an update result. | |
49 void RecordParseUpdateResult(ParseResultType result_type) { | |
50 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4UpdateResult", result_type, | |
51 PARSE_RESULT_TYPE_MAX); | |
52 } | |
53 | |
54 } // namespace | |
55 | |
56 namespace safe_browsing { | |
57 | |
58 const char kUmaV4UpdateResponseMetricName[] = | |
Nathan Parker
2016/03/22 23:57:55
It's pretty standard to just put the metric name's
vakh (use Gerrit instead)
2016/03/24 22:34:24
Done.
| |
59 "SafeBrowsing.V4UpdateHttpResponseOrErrorCode"; | |
60 | |
61 // The default V4UpdateProtocolManagerFactory. | |
62 class V4UpdateProtocolManagerFactoryImpl | |
63 : public V4UpdateProtocolManagerFactory { | |
64 public: | |
65 V4UpdateProtocolManagerFactoryImpl() {} | |
66 ~V4UpdateProtocolManagerFactoryImpl() override {} | |
67 V4UpdateProtocolManager* CreateProtocolManager( | |
68 net::URLRequestContextGetter* request_context_getter, | |
69 const V4ProtocolConfig& config) override { | |
70 return new V4UpdateProtocolManager(request_context_getter, config); | |
71 } | |
72 | |
73 private: | |
74 DISALLOW_COPY_AND_ASSIGN(V4UpdateProtocolManagerFactoryImpl); | |
75 }; | |
76 | |
77 // V4UpdateProtocolManager implementation -------------------------------- | |
78 | |
79 // static | |
80 V4UpdateProtocolManagerFactory* V4UpdateProtocolManager::factory_ = NULL; | |
81 | |
82 // static | |
83 V4UpdateProtocolManager* V4UpdateProtocolManager::Create( | |
84 net::URLRequestContextGetter* request_context_getter, | |
85 const V4ProtocolConfig& config) { | |
86 if (!factory_) | |
87 factory_ = new V4UpdateProtocolManagerFactoryImpl(); | |
Nathan Parker
2016/03/22 23:57:55
Do you need this Impl class at all? Another way w
vakh (use Gerrit instead)
2016/03/24 22:34:24
This would be useful for testing using DI. The old
| |
88 return factory_->CreateProtocolManager(request_context_getter, config); | |
89 } | |
90 | |
91 void V4UpdateProtocolManager::ResetUpdateErrors() { | |
92 update_error_count_ = 0; | |
93 update_back_off_mult_ = 1; | |
94 } | |
95 | |
96 V4UpdateProtocolManager::V4UpdateProtocolManager( | |
97 net::URLRequestContextGetter* request_context_getter, | |
98 const V4ProtocolConfig& config) | |
99 : update_error_count_(0), | |
100 update_back_off_mult_(1), | |
101 next_update_time_(Time::FromDoubleT(0)), | |
102 config_(config), | |
103 request_context_getter_(request_context_getter), | |
104 url_fetcher_id_(0), | |
105 update_request_pending_(false) {} | |
106 | |
107 // static | |
108 void V4UpdateProtocolManager::RecordUpdateResult( | |
109 OperationResultType result_type) { | |
110 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4UpdateResult", result_type, | |
111 V4ProtocolManagerUtil::OPERATION_RESULT_TYPE_MAX); | |
112 } | |
113 | |
114 V4UpdateProtocolManager::~V4UpdateProtocolManager() { | |
115 } | |
116 | |
117 std::string V4UpdateProtocolManager::GetUpdateRequest( | |
118 const base::hash_set<const UpdateListIdentifier*>& lists_to_update, | |
119 const base::hash_map<const UpdateListIdentifier*, const std::string&>& | |
120 current_list_states) { | |
121 // Build the request. Client info and client states are not added to the | |
122 // request protocol buffer. Client info is passed as params in the url. | |
123 FetchThreatListUpdatesRequest request; | |
124 for (const UpdateListIdentifier* list_to_update : lists_to_update) { | |
125 ListUpdateRequest* list_update_request = request.add_list_update_requests(); | |
126 list_update_request->set_platform_type(list_to_update->platform_type); | |
127 list_update_request->set_threat_entry_type( | |
128 list_to_update->threat_entry_type); | |
129 list_update_request->set_threat_type(list_to_update->threat_type); | |
130 | |
131 // If the current state of the list is available, add that to the proto. | |
132 base::hash_map<const UpdateListIdentifier*, | |
133 const std::string&>::const_iterator list_iter = | |
134 current_list_states.find(list_to_update); | |
135 if (list_iter != current_list_states.end()) { | |
136 list_update_request->set_state(list_iter->second); | |
137 } | |
138 } | |
139 | |
140 // Serialize and Base64 encode. | |
141 std::string req_data, req_base64; | |
142 request.SerializeToString(&req_data); | |
143 base::Base64Encode(req_data, &req_base64); | |
144 | |
145 return req_base64; | |
146 } | |
147 | |
148 bool V4UpdateProtocolManager::ParseUpdateResponse( | |
149 const std::string& data, | |
150 std::vector<ListUpdateResponse>* list_update_responses) { | |
151 FetchThreatListUpdatesResponse response; | |
152 | |
153 if (!response.ParseFromString(data)) { | |
154 RecordParseUpdateResult(PARSE_FROM_STRING_ERROR); | |
155 return false; | |
156 } | |
157 | |
158 if (response.has_minimum_wait_duration()) { | |
159 // Seconds resolution is good enough so we ignore the nanos field. | |
160 next_update_time_ = | |
Nathan Parker
2016/03/22 23:57:55
If it doesn't have a min wait duration, does next_
vakh (use Gerrit instead)
2016/03/24 22:34:24
Yes, we don't update it if the response doesn't co
Nathan Parker
2016/03/24 22:48:05
ok, so then assumption is that the caller to GetUp
vakh (use Gerrit instead)
2016/03/25 02:42:37
Not really. The class stores next_update_time_ whi
| |
161 Time::Now() + base::TimeDelta::FromSeconds( | |
162 response.minimum_wait_duration().seconds()); | |
163 } | |
164 | |
165 // TODO(vakh): Do something useful with this response. | |
166 for (const ListUpdateResponse& list_update_response : | |
167 response.list_update_responses()) { | |
168 if (!list_update_response.has_platform_type()) { | |
169 RecordParseUpdateResult(NO_PLATFORM_TYPE_ERROR); | |
170 } else if (!list_update_response.has_threat_entry_type()) { | |
171 RecordParseUpdateResult(NO_THREAT_ENTRY_TYPE_ERROR); | |
172 } else if (!list_update_response.has_threat_type()) { | |
173 RecordParseUpdateResult(NO_THREAT_TYPE_ERROR); | |
174 } else if (!list_update_response.has_new_client_state()) { | |
175 RecordParseUpdateResult(NO_STATE_ERROR); | |
176 } else { | |
177 list_update_responses->push_back(list_update_response); | |
178 } | |
179 } | |
180 return true; | |
181 } | |
182 | |
183 void V4UpdateProtocolManager::GetUpdates( | |
184 const base::hash_set<const UpdateListIdentifier*>& lists_to_update, | |
185 const base::hash_map<const UpdateListIdentifier*, const std::string&>& | |
186 current_list_states, | |
187 UpdateCallback callback) { | |
188 DCHECK(CalledOnValidThread()); | |
189 | |
190 // If an update request is already pending, return an empty result. | |
191 if (update_request_pending_) { | |
192 RecordUpdateResult(V4ProtocolManagerUtil::ALREADY_PENDING_ERROR); | |
193 std::vector<ListUpdateResponse> list_update_responses; | |
194 callback.Run(list_update_responses); | |
195 return; | |
196 } | |
197 | |
198 // We need to wait the minimum waiting duration, and if we are in backoff, | |
199 // we need to check if we're past the next allowed time. If we are, we can | |
200 // proceed with the request. If not, we are required to return empty results. | |
201 if (Time::Now() <= next_update_time_) { | |
202 if (update_error_count_) { | |
203 RecordUpdateResult(V4ProtocolManagerUtil::BACKOFF_ERROR); | |
204 } else { | |
205 RecordUpdateResult(V4ProtocolManagerUtil::MIN_WAIT_DURATION_ERROR); | |
206 } | |
207 std::vector<ListUpdateResponse> list_update_responses; | |
208 callback.Run(list_update_responses); | |
209 return; | |
210 } | |
211 | |
212 std::string req_base64 = | |
213 GetUpdateRequest(lists_to_update, current_list_states); | |
214 GURL update_url = GetUpdateUrl(req_base64); | |
215 | |
216 net::URLFetcher* fetcher = | |
Nathan Parker
2016/03/22 23:57:55
nit: You could put this directly into fetcher_
vakh (use Gerrit instead)
2016/03/24 22:34:24
Done.
| |
217 net::URLFetcher::Create(url_fetcher_id_++, update_url, | |
218 net::URLFetcher::GET, this) | |
219 .release(); | |
220 update_request_pending_ = true; | |
221 callback_ = callback; | |
222 request_.reset(fetcher); | |
223 | |
224 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); | |
225 fetcher->SetRequestContext(request_context_getter_.get()); | |
226 fetcher->Start(); | |
227 } | |
228 | |
229 // net::URLFetcherDelegate implementation ---------------------------------- | |
230 | |
231 // SafeBrowsing request responses are handled here. | |
232 void V4UpdateProtocolManager::OnURLFetchComplete( | |
233 const net::URLFetcher* source) { | |
234 DCHECK(CalledOnValidThread()); | |
235 | |
236 int response_code = source->GetResponseCode(); | |
237 net::URLRequestStatus status = source->GetStatus(); | |
238 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( | |
239 kUmaV4UpdateResponseMetricName, status, response_code); | |
240 | |
241 std::vector<ListUpdateResponse> list_update_responses; | |
242 if (status.is_success() && response_code == net::HTTP_OK) { | |
243 RecordUpdateResult(V4ProtocolManagerUtil::STATUS_200); | |
244 ResetUpdateErrors(); | |
245 std::string data; | |
246 source->GetResponseAsString(&data); | |
247 if (!ParseUpdateResponse(data, &list_update_responses)) { | |
248 list_update_responses.clear(); | |
249 RecordUpdateResult(V4ProtocolManagerUtil::PARSE_ERROR); | |
250 } | |
251 } else { | |
252 HandleUpdateError(Time::Now()); | |
253 | |
254 DVLOG(1) << "SafeBrowsing GetEncodedUpdates request for: " | |
255 << source->GetURL() << " failed with error: " << status.error() | |
256 << " and response code: " << response_code; | |
257 | |
258 if (status.status() == net::URLRequestStatus::FAILED) { | |
259 RecordUpdateResult(V4ProtocolManagerUtil::NETWORK_ERROR); | |
260 } else { | |
261 RecordUpdateResult(V4ProtocolManagerUtil::HTTP_ERROR); | |
262 } | |
263 } | |
264 | |
265 // Invoke the callback with list_update_responses, even if there was a parse | |
266 // error or an error response code (in which case list_update_responses will | |
267 // be empty). The caller can't be blocked indefinitely. | |
268 callback_.Run(list_update_responses); | |
269 | |
270 update_request_pending_ = false; | |
271 } | |
272 | |
273 void V4UpdateProtocolManager::HandleUpdateError(const Time& now) { | |
274 DCHECK(CalledOnValidThread()); | |
275 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( | |
276 &update_error_count_, &update_back_off_mult_); | |
277 next_update_time_ = now + next; | |
278 } | |
279 | |
280 GURL V4UpdateProtocolManager::GetUpdateUrl( | |
281 const std::string& req_base64) const { | |
282 return V4ProtocolManagerUtil::GetRequestUrl(req_base64, "encodedUpdates", | |
283 config_); | |
284 } | |
285 | |
286 } // namespace safe_browsing | |
OLD | NEW |