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 void RecordUpdateResult(safe_browsing::V4OperationResult result) { | |
55 UMA_HISTOGRAM_ENUMERATION( | |
56 "SafeBrowsing.V4UpdateResult", result, | |
57 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); | |
58 } | |
59 | |
60 } // namespace | |
61 | |
62 namespace safe_browsing { | |
63 | |
64 // The default V4UpdateProtocolManagerFactory. | |
65 class V4UpdateProtocolManagerFactoryImpl | |
66 : public V4UpdateProtocolManagerFactory { | |
67 public: | |
68 V4UpdateProtocolManagerFactoryImpl() {} | |
69 ~V4UpdateProtocolManagerFactoryImpl() override {} | |
70 V4UpdateProtocolManager* CreateProtocolManager( | |
71 net::URLRequestContextGetter* request_context_getter, | |
72 const V4ProtocolConfig& config) override { | |
73 return new V4UpdateProtocolManager(request_context_getter, config); | |
74 } | |
75 | |
76 private: | |
77 DISALLOW_COPY_AND_ASSIGN(V4UpdateProtocolManagerFactoryImpl); | |
78 }; | |
79 | |
80 // V4UpdateProtocolManager implementation -------------------------------- | |
81 | |
82 // static | |
83 V4UpdateProtocolManagerFactory* V4UpdateProtocolManager::factory_ = NULL; | |
84 | |
85 // static | |
86 V4UpdateProtocolManager* V4UpdateProtocolManager::Create( | |
87 net::URLRequestContextGetter* request_context_getter, | |
88 const V4ProtocolConfig& config) { | |
89 if (!factory_) | |
90 factory_ = new V4UpdateProtocolManagerFactoryImpl(); | |
91 return factory_->CreateProtocolManager(request_context_getter, config); | |
92 } | |
93 | |
94 void V4UpdateProtocolManager::ResetUpdateErrors() { | |
95 update_error_count_ = 0; | |
96 update_back_off_mult_ = 1; | |
97 } | |
98 | |
99 V4UpdateProtocolManager::V4UpdateProtocolManager( | |
100 net::URLRequestContextGetter* request_context_getter, | |
101 const V4ProtocolConfig& config) | |
102 : update_error_count_(0), | |
103 update_back_off_mult_(1), | |
104 next_update_time_(Time::Now()), | |
105 config_(config), | |
106 request_context_getter_(request_context_getter), | |
107 url_fetcher_id_(0) {} | |
108 | |
109 V4UpdateProtocolManager::~V4UpdateProtocolManager() { | |
110 } | |
111 | |
112 std::string V4UpdateProtocolManager::GetUpdateRequest( | |
113 const base::hash_set<UpdateListIdentifier>& lists_to_update, | |
114 const base::hash_map<UpdateListIdentifier, const std::string&>& | |
Nathan Parker
2016/03/25 23:51:01
Using a reference for the string does require some
vakh (use Gerrit instead)
2016/03/26 00:06:56
Done.
| |
115 current_list_states) { | |
116 // Build the request. Client info and client states are not added to the | |
117 // request protocol buffer. Client info is passed as params in the url. | |
118 FetchThreatListUpdatesRequest request; | |
119 for (const auto& list_to_update : lists_to_update) { | |
120 ListUpdateRequest* list_update_request = request.add_list_update_requests(); | |
121 list_update_request->set_platform_type(list_to_update.platform_type); | |
122 list_update_request->set_threat_entry_type( | |
123 list_to_update.threat_entry_type); | |
124 list_update_request->set_threat_type(list_to_update.threat_type); | |
125 | |
126 // If the current state of the list is available, add that to the proto. | |
127 base::hash_map<UpdateListIdentifier, | |
128 const std::string&>::const_iterator list_iter = | |
129 current_list_states.find(list_to_update); | |
130 if (list_iter != current_list_states.end()) { | |
131 list_update_request->set_state(list_iter->second); | |
132 } | |
133 } | |
134 | |
135 // Serialize and Base64 encode. | |
136 std::string req_data, req_base64; | |
137 request.SerializeToString(&req_data); | |
138 base::Base64Encode(req_data, &req_base64); | |
139 | |
140 return req_base64; | |
141 } | |
142 | |
143 bool V4UpdateProtocolManager::ParseUpdateResponse( | |
144 const std::string& data, | |
145 std::vector<ListUpdateResponse>* list_update_responses) { | |
146 FetchThreatListUpdatesResponse response; | |
147 | |
148 if (!response.ParseFromString(data)) { | |
149 RecordParseUpdateResult(PARSE_FROM_STRING_ERROR); | |
150 return false; | |
151 } | |
152 | |
153 if (response.has_minimum_wait_duration()) { | |
154 // Seconds resolution is good enough so we ignore the nanos field. | |
155 base::TimeDelta next_update_interval = base::TimeDelta::FromSeconds( | |
156 response.minimum_wait_duration().seconds()); | |
157 next_update_time_ = Time::Now() + next_update_interval; | |
158 } | |
159 | |
160 // TODO(vakh): Do something useful with this response. | |
161 for (const ListUpdateResponse& list_update_response : | |
162 response.list_update_responses()) { | |
163 if (!list_update_response.has_platform_type()) { | |
164 RecordParseUpdateResult(NO_PLATFORM_TYPE_ERROR); | |
165 } else if (!list_update_response.has_threat_entry_type()) { | |
166 RecordParseUpdateResult(NO_THREAT_ENTRY_TYPE_ERROR); | |
167 } else if (!list_update_response.has_threat_type()) { | |
168 RecordParseUpdateResult(NO_THREAT_TYPE_ERROR); | |
169 } else if (!list_update_response.has_new_client_state()) { | |
170 RecordParseUpdateResult(NO_STATE_ERROR); | |
171 } else { | |
172 list_update_responses->push_back(list_update_response); | |
173 } | |
174 } | |
175 return true; | |
176 } | |
177 | |
178 void V4UpdateProtocolManager::GetUpdates( | |
179 const base::hash_set<UpdateListIdentifier>& lists_to_update, | |
180 const base::hash_map<UpdateListIdentifier, const std::string&>& | |
181 current_list_states, | |
182 UpdateCallback callback) { | |
183 DCHECK(CalledOnValidThread()); | |
184 | |
185 // If an update request is already pending, return an empty result. | |
186 if (request_) { | |
187 RecordUpdateResult(V4OperationResult::ALREADY_PENDING_ERROR); | |
188 std::vector<ListUpdateResponse> list_update_responses; | |
189 callback.Run(list_update_responses); | |
190 return; | |
191 } | |
192 | |
193 // We need to wait the minimum waiting duration, and if we are in backoff, | |
194 // we need to check if we're past the next allowed time. If we are, we can | |
195 // proceed with the request. If not, we are required to return empty results. | |
196 if (Time::Now() <= next_update_time_) { | |
197 if (update_error_count_) { | |
198 RecordUpdateResult(V4OperationResult::BACKOFF_ERROR); | |
199 } else { | |
200 RecordUpdateResult(V4OperationResult::MIN_WAIT_DURATION_ERROR); | |
201 } | |
202 std::vector<ListUpdateResponse> list_update_responses; | |
203 callback.Run(list_update_responses); | |
204 return; | |
205 } | |
206 | |
207 std::string req_base64 = | |
208 GetUpdateRequest(lists_to_update, current_list_states); | |
209 GURL update_url = GetUpdateUrl(req_base64); | |
210 | |
211 request_.reset(net::URLFetcher::Create(url_fetcher_id_++, update_url, | |
212 net::URLFetcher::GET, this) | |
213 .release()); | |
214 callback_ = callback; | |
215 | |
216 request_->SetLoadFlags(net::LOAD_DISABLE_CACHE); | |
217 request_->SetRequestContext(request_context_getter_.get()); | |
218 request_->Start(); | |
219 //TODO(vakh): Handle request timeout. | |
220 } | |
221 | |
222 // net::URLFetcherDelegate implementation ---------------------------------- | |
223 | |
224 // SafeBrowsing request responses are handled here. | |
225 void V4UpdateProtocolManager::OnURLFetchComplete( | |
226 const net::URLFetcher* source) { | |
227 DCHECK(CalledOnValidThread()); | |
228 | |
229 int response_code = source->GetResponseCode(); | |
230 net::URLRequestStatus status = source->GetStatus(); | |
231 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( | |
232 "SafeBrowsing.V4UpdateHttpResponseOrErrorCode", status, response_code); | |
233 | |
234 std::vector<ListUpdateResponse> list_update_responses; | |
235 if (status.is_success() && response_code == net::HTTP_OK) { | |
236 RecordUpdateResult(V4OperationResult::STATUS_200); | |
237 ResetUpdateErrors(); | |
238 std::string data; | |
239 source->GetResponseAsString(&data); | |
240 if (!ParseUpdateResponse(data, &list_update_responses)) { | |
241 list_update_responses.clear(); | |
242 RecordUpdateResult(V4OperationResult::PARSE_ERROR); | |
243 } | |
244 } else { | |
245 HandleUpdateError(Time::Now()); | |
246 | |
247 DVLOG(1) << "SafeBrowsing GetEncodedUpdates request for: " | |
248 << source->GetURL() << " failed with error: " << status.error() | |
249 << " and response code: " << response_code; | |
250 | |
251 if (status.status() == net::URLRequestStatus::FAILED) { | |
252 RecordUpdateResult(V4OperationResult::NETWORK_ERROR); | |
253 } else { | |
254 RecordUpdateResult(V4OperationResult::HTTP_ERROR); | |
255 } | |
256 } | |
257 | |
258 // Invoke the callback with list_update_responses, even if there was a parse | |
259 // error or an error response code (in which case list_update_responses will | |
260 // be empty). The caller can't be blocked indefinitely. | |
261 callback_.Run(list_update_responses); | |
262 request_.reset(); | |
263 } | |
264 | |
265 void V4UpdateProtocolManager::HandleUpdateError(const Time& now) { | |
266 DCHECK(CalledOnValidThread()); | |
267 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( | |
268 &update_error_count_, &update_back_off_mult_); | |
269 next_update_time_ = now + next; | |
270 } | |
271 | |
272 GURL V4UpdateProtocolManager::GetUpdateUrl( | |
273 const std::string& req_base64) const { | |
274 return V4ProtocolManagerUtil::GetRequestUrl(req_base64, "encodedUpdates", | |
275 config_); | |
276 } | |
277 | |
278 } // namespace safe_browsing | |
OLD | NEW |