Chromium Code Reviews| 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 |