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 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 |