OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/safe_browsing_db/v4_get_hash_protocol_manager.h" | 5 #include "components/safe_browsing_db/v4_get_hash_protocol_manager.h" |
6 | 6 |
7 #include <utility> | 7 #include <utility> |
8 | 8 |
9 #include "base/base64url.h" | 9 #include "base/base64url.h" |
10 #include "base/macros.h" | 10 #include "base/macros.h" |
11 #include "base/memory/ptr_util.h" | 11 #include "base/memory/ptr_util.h" |
12 #include "base/metrics/histogram_macros.h" | 12 #include "base/metrics/histogram_macros.h" |
13 #include "base/timer/timer.h" | 13 #include "base/timer/timer.h" |
14 #include "net/base/load_flags.h" | 14 #include "net/base/load_flags.h" |
15 #include "net/http/http_response_headers.h" | 15 #include "net/http/http_response_headers.h" |
16 #include "net/http/http_status_code.h" | 16 #include "net/http/http_status_code.h" |
17 #include "net/url_request/url_fetcher.h" | 17 #include "net/url_request/url_fetcher.h" |
18 #include "net/url_request/url_request_context_getter.h" | 18 #include "net/url_request/url_request_context_getter.h" |
19 | 19 |
20 using base::Time; | 20 using base::Time; |
21 using base::TimeDelta; | 21 using base::TimeDelta; |
22 | 22 |
23 namespace { | 23 namespace { |
24 | 24 |
| 25 // Record a GetHash result. |
| 26 void RecordGetHashResult(safe_browsing::V4OperationResult result) { |
| 27 UMA_HISTOGRAM_ENUMERATION( |
| 28 "SafeBrowsing.GetV4HashResult", result, |
| 29 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); |
| 30 } |
| 31 |
25 // Enumerate parsing failures for histogramming purposes. DO NOT CHANGE | 32 // Enumerate parsing failures for histogramming purposes. DO NOT CHANGE |
26 // THE ORDERING OF THESE VALUES. | 33 // THE ORDERING OF THESE VALUES. |
27 enum ParseResultType { | 34 enum ParseResultType { |
28 // Error parsing the protocol buffer from a string. | 35 // Error parsing the protocol buffer from a string. |
29 PARSE_FROM_STRING_ERROR = 0, | 36 PARSE_FROM_STRING_ERROR = 0, |
30 | 37 |
31 // A match in the response had an unexpected THREAT_ENTRY_TYPE. | 38 // A match in the response had an unexpected THREAT_ENTRY_TYPE. |
32 UNEXPECTED_THREAT_ENTRY_TYPE_ERROR = 1, | 39 UNEXPECTED_THREAT_ENTRY_TYPE_ERROR = 1, |
33 | 40 |
34 // A match in the response had an unexpected THREAT_TYPE. | 41 // A match in the response had an unexpected THREAT_TYPE. |
35 UNEXPECTED_THREAT_TYPE_ERROR = 2, | 42 UNEXPECTED_THREAT_TYPE_ERROR = 2, |
36 | 43 |
37 // A match in the response had an unexpected PLATFORM_TYPE. | 44 // A match in the response had an unexpected PLATFORM_TYPE. |
38 UNEXPECTED_PLATFORM_TYPE_ERROR = 3, | 45 UNEXPECTED_PLATFORM_TYPE_ERROR = 3, |
39 | 46 |
40 // A match in the response contained no metadata where metadata was | 47 // A match in the response contained no metadata where metadata was |
41 // expected. | 48 // expected. |
42 NO_METADATA_ERROR = 4, | 49 NO_METADATA_ERROR = 4, |
43 | 50 |
44 // A match in the response contained a ThreatType that was inconsistent | 51 // A match in the response contained a ThreatType that was inconsistent |
45 // with the other matches. | 52 // with the other matches. |
46 INCONSISTENT_THREAT_TYPE_ERROR = 5, | 53 INCONSISTENT_THREAT_TYPE_ERROR = 5, |
47 | 54 |
48 // A match in the response contained a metadata, but the metadata is invalid. | 55 // A match in the response contained a metadata, but the metadata is invalid. |
49 UNEXPECTED_METADATA_VALUE_ERROR = 6, | 56 UNEXPECTED_METADATA_VALUE_ERROR = 6, |
50 | 57 |
| 58 // A match in the response had no information in the threat field. |
| 59 NO_THREAT_ERROR = 7, |
| 60 |
51 // Memory space for histograms is determined by the max. ALWAYS | 61 // Memory space for histograms is determined by the max. ALWAYS |
52 // ADD NEW VALUES BEFORE THIS ONE. | 62 // ADD NEW VALUES BEFORE THIS ONE. |
53 PARSE_RESULT_TYPE_MAX = 7, | 63 PARSE_RESULT_TYPE_MAX = 8, |
54 }; | 64 }; |
55 | 65 |
56 // Record parsing errors of a GetHash result. | 66 // Record parsing errors of a GetHash result. |
57 void RecordParseGetHashResult(ParseResultType result_type) { | 67 void RecordParseGetHashResult(ParseResultType result_type) { |
58 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type, | 68 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type, |
59 PARSE_RESULT_TYPE_MAX); | 69 PARSE_RESULT_TYPE_MAX); |
60 } | 70 } |
61 | 71 |
62 // Record a GetHash result. | 72 // Enumerate full hash cache hits/misses for histogramming purposes. |
63 void RecordGetHashResult(safe_browsing::V4OperationResult result) { | 73 // DO NOT CHANGE THE ORDERING OF THESE VALUES. |
64 UMA_HISTOGRAM_ENUMERATION( | 74 enum V4FullHashCacheResultType { |
65 "SafeBrowsing.GetV4HashResult", result, | 75 // Full hashes for which there is no cache hit. |
66 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); | 76 FULL_HASH_CACHE_MISS = 0, |
| 77 |
| 78 // Full hashes with a cache hit. |
| 79 FULL_HASH_CACHE_HIT = 1, |
| 80 |
| 81 // Full hashes with a negative cache hit. |
| 82 FULL_HASH_NEGATIVE_CACHE_HIT = 2, |
| 83 |
| 84 // Memory space for histograms is determined by the max. ALWAYS |
| 85 // ADD NEW VALUES BEFORE THIS ONE. |
| 86 FULL_HASH_CACHE_RESULT_MAX |
| 87 }; |
| 88 |
| 89 // Record a full hash cache hit result. |
| 90 void RecordV4FullHashCacheResult(V4FullHashCacheResultType result_type) { |
| 91 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4FullHashCacheResult", result_type, |
| 92 FULL_HASH_CACHE_RESULT_MAX); |
67 } | 93 } |
68 | 94 |
| 95 // Enumerate GetHash hits/misses for histogramming purposes. DO NOT CHANGE THE |
| 96 // ORDERING OF THESE VALUES. |
| 97 enum V4GetHashCheckResultType { |
| 98 // Successful responses which returned no full hashes. |
| 99 GET_HASH_CHECK_EMPTY = 0, |
| 100 |
| 101 // Successful responses for which one or more of the full hashes matched. |
| 102 GET_HASH_CHECK_HIT = 1, |
| 103 |
| 104 // Successful responses which weren't empty but have no matches. |
| 105 GET_HASH_CHECK_MISS = 2, |
| 106 |
| 107 // Memory space for histograms is determined by the max. ALWAYS |
| 108 // ADD NEW VALUES BEFORE THIS ONE. |
| 109 GET_HASH_CHECK_RESULT_MAX |
| 110 }; |
| 111 |
| 112 // Record a GetHash hit result. |
| 113 void RecordV4GetHashCheckResult(V4GetHashCheckResultType result_type) { |
| 114 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4GetHashCheckResult", result_type, |
| 115 GET_HASH_CHECK_RESULT_MAX); |
| 116 } |
| 117 |
| 118 const char kPermission[] = "permission"; |
| 119 const char kPhaPatternType[] = "pha_pattern_type"; |
| 120 const char kMalwarePatternType[] = "malware_pattern_type"; |
| 121 const char kSePatternType[] = "se_pattern_type"; |
| 122 const char kLanding[] = "LANDING"; |
| 123 const char kDistribution[] = "DISTRIBUTION"; |
| 124 const char kSocialEngineeringAds[] = "SOCIAL_ENGINEERING_ADS"; |
| 125 const char kSocialEngineeringLanding[] = "SOCIAL_ENGINEERING_LANDING"; |
| 126 const char kPhishing[] = "PHISHING"; |
| 127 |
69 } // namespace | 128 } // namespace |
70 | 129 |
71 namespace safe_browsing { | 130 namespace safe_browsing { |
72 | 131 |
73 const char kUmaV4HashResponseMetricName[] = | 132 const char kUmaV4HashResponseMetricName[] = |
74 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode"; | 133 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode"; |
75 | 134 |
76 // The default V4GetHashProtocolManagerFactory. | 135 // The default V4GetHashProtocolManagerFactory. |
77 class V4GetHashProtocolManagerFactoryImpl | 136 class V4GetHashProtocolManagerFactoryImpl |
78 : public V4GetHashProtocolManagerFactory { | 137 : public V4GetHashProtocolManagerFactory { |
79 public: | 138 public: |
80 V4GetHashProtocolManagerFactoryImpl() {} | 139 V4GetHashProtocolManagerFactoryImpl() {} |
81 ~V4GetHashProtocolManagerFactoryImpl() override {} | 140 ~V4GetHashProtocolManagerFactoryImpl() override {} |
82 V4GetHashProtocolManager* CreateProtocolManager( | 141 std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager( |
83 net::URLRequestContextGetter* request_context_getter, | 142 net::URLRequestContextGetter* request_context_getter, |
| 143 const base::hash_set<UpdateListIdentifier>& stores_to_request, |
84 const V4ProtocolConfig& config) override { | 144 const V4ProtocolConfig& config) override { |
85 return new V4GetHashProtocolManager(request_context_getter, config); | 145 return base::WrapUnique(new V4GetHashProtocolManager( |
| 146 request_context_getter, stores_to_request, config)); |
86 } | 147 } |
87 | 148 |
88 private: | 149 private: |
89 DISALLOW_COPY_AND_ASSIGN(V4GetHashProtocolManagerFactoryImpl); | 150 DISALLOW_COPY_AND_ASSIGN(V4GetHashProtocolManagerFactoryImpl); |
90 }; | 151 }; |
91 | 152 |
| 153 // ---------------------------------------------------------------- |
| 154 |
| 155 CachedHashPrefixInfo::CachedHashPrefixInfo() {} |
| 156 |
| 157 CachedHashPrefixInfo::CachedHashPrefixInfo(const CachedHashPrefixInfo& other) = |
| 158 default; |
| 159 |
| 160 CachedHashPrefixInfo::~CachedHashPrefixInfo() {} |
| 161 |
| 162 // ---------------------------------------------------------------- |
| 163 |
| 164 FullHashCallbackInfo::FullHashCallbackInfo() {} |
| 165 |
| 166 FullHashCallbackInfo::FullHashCallbackInfo( |
| 167 const std::vector<FullHashInfo>& cached_full_hash_infos, |
| 168 const std::vector<HashPrefix>& prefixes_requested, |
| 169 std::unique_ptr<net::URLFetcher> fetcher, |
| 170 const FullHashToStoreAndHashPrefixesMap& |
| 171 full_hash_to_store_and_hash_prefixes, |
| 172 const FullHashCallback& callback) |
| 173 : cached_full_hash_infos(cached_full_hash_infos), |
| 174 callback(callback), |
| 175 fetcher(std::move(fetcher)), |
| 176 full_hash_to_store_and_hash_prefixes( |
| 177 full_hash_to_store_and_hash_prefixes), |
| 178 prefixes_requested(prefixes_requested) {} |
| 179 |
| 180 FullHashCallbackInfo::~FullHashCallbackInfo() {} |
| 181 |
| 182 // ---------------------------------------------------------------- |
| 183 |
| 184 FullHashInfo::FullHashInfo(const FullHash& full_hash, |
| 185 const UpdateListIdentifier& list_id, |
| 186 const base::Time& positive_expiry) |
| 187 : full_hash(full_hash), |
| 188 list_id(list_id), |
| 189 positive_expiry(positive_expiry) {} |
| 190 |
| 191 FullHashInfo::FullHashInfo(const FullHashInfo& other) = default; |
| 192 |
| 193 FullHashInfo::~FullHashInfo() {} |
| 194 |
| 195 bool FullHashInfo::operator==(const FullHashInfo& other) const { |
| 196 return full_hash == other.full_hash && list_id == other.list_id && |
| 197 positive_expiry == other.positive_expiry && metadata == other.metadata; |
| 198 } |
| 199 |
| 200 bool FullHashInfo::operator!=(const FullHashInfo& other) const { |
| 201 return !operator==(other); |
| 202 } |
| 203 |
92 // V4GetHashProtocolManager implementation -------------------------------- | 204 // V4GetHashProtocolManager implementation -------------------------------- |
93 | 205 |
94 // static | 206 // static |
95 V4GetHashProtocolManagerFactory* V4GetHashProtocolManager::factory_ = NULL; | 207 V4GetHashProtocolManagerFactory* V4GetHashProtocolManager::factory_ = NULL; |
96 | 208 |
97 // static | 209 // static |
98 V4GetHashProtocolManager* V4GetHashProtocolManager::Create( | 210 std::unique_ptr<V4GetHashProtocolManager> V4GetHashProtocolManager::Create( |
99 net::URLRequestContextGetter* request_context_getter, | 211 net::URLRequestContextGetter* request_context_getter, |
| 212 const base::hash_set<UpdateListIdentifier>& stores_to_request, |
100 const V4ProtocolConfig& config) { | 213 const V4ProtocolConfig& config) { |
101 if (!factory_) | 214 if (!factory_) |
102 factory_ = new V4GetHashProtocolManagerFactoryImpl(); | 215 factory_ = new V4GetHashProtocolManagerFactoryImpl(); |
103 return factory_->CreateProtocolManager(request_context_getter, config); | 216 return factory_->CreateProtocolManager(request_context_getter, |
| 217 stores_to_request, config); |
104 } | 218 } |
105 | 219 |
106 void V4GetHashProtocolManager::ResetGetHashErrors() { | 220 // static |
107 gethash_error_count_ = 0; | 221 void V4GetHashProtocolManager::RegisterFactory( |
108 gethash_back_off_mult_ = 1; | 222 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { |
| 223 if (factory_) |
| 224 delete factory_; |
| 225 factory_ = factory.release(); |
109 } | 226 } |
110 | 227 |
111 V4GetHashProtocolManager::V4GetHashProtocolManager( | 228 V4GetHashProtocolManager::V4GetHashProtocolManager( |
112 net::URLRequestContextGetter* request_context_getter, | 229 net::URLRequestContextGetter* request_context_getter, |
| 230 const base::hash_set<UpdateListIdentifier>& stores_to_request, |
113 const V4ProtocolConfig& config) | 231 const V4ProtocolConfig& config) |
114 : gethash_error_count_(0), | 232 : gethash_error_count_(0), |
115 gethash_back_off_mult_(1), | 233 gethash_back_off_mult_(1), |
116 next_gethash_time_(Time::FromDoubleT(0)), | 234 next_gethash_time_(Time::FromDoubleT(0)), |
117 config_(config), | 235 config_(config), |
118 request_context_getter_(request_context_getter), | 236 request_context_getter_(request_context_getter), |
119 url_fetcher_id_(0), | 237 url_fetcher_id_(0), |
120 clock_(new base::DefaultClock()) {} | 238 clock_(new base::DefaultClock()) { |
121 | 239 DCHECK(!stores_to_request.empty()); |
122 V4GetHashProtocolManager::~V4GetHashProtocolManager() { | 240 for (const UpdateListIdentifier& store : stores_to_request) { |
123 } | 241 platform_types_.insert(store.platform_type); |
124 | 242 threat_entry_types_.insert(store.threat_entry_type); |
125 // static | 243 threat_types_.insert(store.threat_type); |
126 void V4GetHashProtocolManager::RegisterFactory( | 244 } |
127 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { | 245 } |
128 if (factory_) | 246 |
129 delete factory_; | 247 V4GetHashProtocolManager::~V4GetHashProtocolManager() {} |
130 factory_ = factory.release(); | 248 |
| 249 void V4GetHashProtocolManager::ClearCache() { |
| 250 DCHECK(CalledOnValidThread()); |
| 251 full_hash_cache_.clear(); |
| 252 } |
| 253 |
| 254 void V4GetHashProtocolManager::GetFullHashes( |
| 255 const FullHashToStoreAndHashPrefixesMap& |
| 256 full_hash_to_store_and_hash_prefixes, |
| 257 FullHashCallback callback) { |
| 258 DCHECK(CalledOnValidThread()); |
| 259 DCHECK(!full_hash_to_store_and_hash_prefixes.empty()); |
| 260 |
| 261 std::vector<HashPrefix> prefixes_to_request; |
| 262 std::vector<FullHashInfo> cached_full_hash_infos; |
| 263 GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, Time::Now(), |
| 264 &prefixes_to_request, &cached_full_hash_infos); |
| 265 |
| 266 if (prefixes_to_request.empty()) { |
| 267 // 100% cache hits (positive or negative) so we can call the callback right |
| 268 // away. |
| 269 callback.Run(cached_full_hash_infos); |
| 270 return; |
| 271 } |
| 272 |
| 273 // We need to wait the minimum waiting duration, and if we are in backoff, |
| 274 // we need to check if we're past the next allowed time. If we are, we can |
| 275 // proceed with the request. If not, we are required to return empty results |
| 276 // (i.e. just use the results from cache and potentially report an unsafe |
| 277 // resource as safe). |
| 278 if (clock_->Now() <= next_gethash_time_) { |
| 279 if (gethash_error_count_) { |
| 280 RecordGetHashResult(V4OperationResult::BACKOFF_ERROR); |
| 281 } else { |
| 282 RecordGetHashResult(V4OperationResult::MIN_WAIT_DURATION_ERROR); |
| 283 } |
| 284 callback.Run(cached_full_hash_infos); |
| 285 return; |
| 286 } |
| 287 |
| 288 std::string req_base64 = GetHashRequest(prefixes_to_request); |
| 289 GURL gethash_url; |
| 290 net::HttpRequestHeaders headers; |
| 291 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); |
| 292 |
| 293 std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create( |
| 294 url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this); |
| 295 net::URLFetcher* fetcher = owned_fetcher.get(); |
| 296 pending_hash_requests_[fetcher].reset(new FullHashCallbackInfo( |
| 297 cached_full_hash_infos, prefixes_to_request, std::move(owned_fetcher), |
| 298 full_hash_to_store_and_hash_prefixes, callback)); |
| 299 |
| 300 fetcher->SetExtraRequestHeaders(headers.ToString()); |
| 301 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); |
| 302 fetcher->SetRequestContext(request_context_getter_.get()); |
| 303 fetcher->Start(); |
| 304 } |
| 305 |
| 306 void V4GetHashProtocolManager::GetFullHashesWithApis( |
| 307 const GURL& url, |
| 308 ThreatMetadataForApiCallback api_callback) { |
| 309 DCHECK(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme)); |
| 310 |
| 311 base::hash_set<FullHash> full_hashes; |
| 312 V4ProtocolManagerUtil::UrlToFullHashes(url, &full_hashes); |
| 313 |
| 314 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes; |
| 315 for (const FullHash& full_hash : full_hashes) { |
| 316 HashPrefix prefix; |
| 317 bool result = |
| 318 V4ProtocolManagerUtil::FullHashToSmallestHashPrefix(full_hash, &prefix); |
| 319 DCHECK(result); |
| 320 full_hash_to_store_and_hash_prefixes[full_hash].emplace_back( |
| 321 GetChromeUrlApiId(), prefix); |
| 322 } |
| 323 |
| 324 GetFullHashes(full_hash_to_store_and_hash_prefixes, |
| 325 base::Bind(&V4GetHashProtocolManager::OnFullHashForApi, |
| 326 base::Unretained(this), api_callback, full_hashes)); |
| 327 } |
| 328 |
| 329 void V4GetHashProtocolManager::GetFullHashCachedResults( |
| 330 const FullHashToStoreAndHashPrefixesMap& |
| 331 full_hash_to_store_and_hash_prefixes, |
| 332 const Time& now, |
| 333 std::vector<HashPrefix>* prefixes_to_request, |
| 334 std::vector<FullHashInfo>* cached_full_hash_infos) const { |
| 335 DCHECK(!full_hash_to_store_and_hash_prefixes.empty()); |
| 336 DCHECK(prefixes_to_request->empty()); |
| 337 DCHECK(cached_full_hash_infos->empty()); |
| 338 |
| 339 // Caching behavior is documented here: |
| 340 // https://developers.google.com/safe-browsing/v4/caching#about-caching |
| 341 // |
| 342 // The cache operates as follows: |
| 343 // Lookup: |
| 344 // Case 1: The prefix is in the cache. |
| 345 // Case a: The full hash is in the cache. |
| 346 // Case i : The positive full hash result has not expired. |
| 347 // The result is unsafe and we do not need to send a new |
| 348 // request. |
| 349 // Case ii: The positive full hash result has expired. |
| 350 // We need to send a request for full hashes. |
| 351 // Case b: The full hash is not in the cache. |
| 352 // Case i : The negative cache entry has not expired. |
| 353 // The result is still safe and we do not need to send a |
| 354 // new request. |
| 355 // Case ii: The negative cache entry has expired. |
| 356 // We need to send a request for full hashes. |
| 357 // Case 2: The prefix is not in the cache. |
| 358 // We need to send a request for full hashes. |
| 359 // |
| 360 // Note on eviction: |
| 361 // CachedHashPrefixInfo entries can be removed from the cache only when |
| 362 // the negative cache expire time and the cache expire time of all full |
| 363 // hash results for that prefix have expired. |
| 364 // Individual full hash results can be removed from the prefix's |
| 365 // cache entry if they expire AND their expire time is after the negative |
| 366 // cache expire time. |
| 367 |
| 368 base::hash_set<HashPrefix> unique_prefixes_to_request; |
| 369 for (const auto& it : full_hash_to_store_and_hash_prefixes) { |
| 370 const FullHash& full_hash = it.first; |
| 371 const StoreAndHashPrefixes& matched = it.second; |
| 372 for (const StoreAndHashPrefix& matched_it : matched) { |
| 373 const UpdateListIdentifier& list_id = matched_it.list_id; |
| 374 const HashPrefix& prefix = matched_it.hash_prefix; |
| 375 auto prefix_entry = full_hash_cache_.find(prefix); |
| 376 if (prefix_entry != full_hash_cache_.end()) { |
| 377 // Case 1. |
| 378 const CachedHashPrefixInfo& cached_prefix_info = prefix_entry->second; |
| 379 bool found_full_hash = false; |
| 380 for (const FullHashInfo& full_hash_info : |
| 381 cached_prefix_info.full_hash_infos) { |
| 382 if (full_hash_info.full_hash == full_hash && |
| 383 full_hash_info.list_id == list_id) { |
| 384 // Case a. |
| 385 found_full_hash = true; |
| 386 if (full_hash_info.positive_expiry > now) { |
| 387 // Case i. |
| 388 cached_full_hash_infos->push_back(full_hash_info); |
| 389 RecordV4FullHashCacheResult(FULL_HASH_CACHE_HIT); |
| 390 } else { |
| 391 // Case ii. |
| 392 unique_prefixes_to_request.insert(prefix); |
| 393 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); |
| 394 } |
| 395 break; |
| 396 } |
| 397 } |
| 398 |
| 399 if (!found_full_hash) { |
| 400 // Case b. |
| 401 if (cached_prefix_info.negative_expiry > now) { |
| 402 // Case i. |
| 403 RecordV4FullHashCacheResult(FULL_HASH_NEGATIVE_CACHE_HIT); |
| 404 } else { |
| 405 // Case ii. |
| 406 unique_prefixes_to_request.insert(prefix); |
| 407 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); |
| 408 } |
| 409 } |
| 410 } else { |
| 411 // Case 2. |
| 412 unique_prefixes_to_request.insert(prefix); |
| 413 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); |
| 414 } |
| 415 } |
| 416 } |
| 417 |
| 418 prefixes_to_request->insert(prefixes_to_request->begin(), |
| 419 unique_prefixes_to_request.begin(), |
| 420 unique_prefixes_to_request.end()); |
131 } | 421 } |
132 | 422 |
133 std::string V4GetHashProtocolManager::GetHashRequest( | 423 std::string V4GetHashProtocolManager::GetHashRequest( |
134 const std::vector<SBPrefix>& prefixes, | 424 const std::vector<HashPrefix>& prefixes_to_request) { |
135 const std::vector<PlatformType>& platforms, | 425 DCHECK(!prefixes_to_request.empty()); |
136 ThreatType threat_type) { | 426 |
137 // Build the request. Client info and client states are not added to the | |
138 // request protocol buffer. Client info is passed as params in the url. | |
139 FindFullHashesRequest req; | 427 FindFullHashesRequest req; |
140 ThreatInfo* info = req.mutable_threat_info(); | 428 ThreatInfo* info = req.mutable_threat_info(); |
141 info->add_threat_types(threat_type); | 429 for (const PlatformType p : platform_types_) { |
142 info->add_threat_entry_types(URL); | |
143 for (const PlatformType p : platforms) { | |
144 info->add_platform_types(p); | 430 info->add_platform_types(p); |
145 } | 431 } |
146 for (const SBPrefix& prefix : prefixes) { | 432 for (const ThreatEntryType tet : threat_entry_types_) { |
147 std::string hash(reinterpret_cast<const char*>(&prefix), sizeof(SBPrefix)); | 433 info->add_threat_entry_types(tet); |
148 info->add_threat_entries()->set_hash(hash); | 434 } |
| 435 for (const ThreatType tt : threat_types_) { |
| 436 info->add_threat_types(tt); |
| 437 } |
| 438 for (const HashPrefix& prefix : prefixes_to_request) { |
| 439 info->add_threat_entries()->set_hash(prefix); |
149 } | 440 } |
150 | 441 |
151 // Serialize and Base64 encode. | 442 // Serialize and Base64 encode. |
152 std::string req_data, req_base64; | 443 std::string req_data, req_base64; |
153 req.SerializeToString(&req_data); | 444 req.SerializeToString(&req_data); |
154 base::Base64UrlEncode(req_data, base::Base64UrlEncodePolicy::INCLUDE_PADDING, | 445 base::Base64UrlEncode(req_data, base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
155 &req_base64); | 446 &req_base64); |
156 return req_base64; | 447 return req_base64; |
157 } | 448 } |
158 | 449 |
| 450 void V4GetHashProtocolManager::GetHashUrlAndHeaders( |
| 451 const std::string& req_base64, |
| 452 GURL* gurl, |
| 453 net::HttpRequestHeaders* headers) const { |
| 454 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", |
| 455 config_, gurl, headers); |
| 456 } |
| 457 |
| 458 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { |
| 459 DCHECK(CalledOnValidThread()); |
| 460 TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( |
| 461 &gethash_error_count_, &gethash_back_off_mult_); |
| 462 next_gethash_time_ = now + next; |
| 463 } |
| 464 |
| 465 void V4GetHashProtocolManager::OnFullHashForApi( |
| 466 const ThreatMetadataForApiCallback& api_callback, |
| 467 const base::hash_set<FullHash>& full_hashes, |
| 468 const std::vector<FullHashInfo>& full_hash_infos) { |
| 469 ThreatMetadata md; |
| 470 for (const FullHashInfo& full_hash_info : full_hash_infos) { |
| 471 DCHECK_EQ(GetChromeUrlApiId(), full_hash_info.list_id); |
| 472 DCHECK(full_hashes.find(full_hash_info.full_hash) != full_hashes.end()); |
| 473 md.api_permissions.insert(full_hash_info.metadata.api_permissions.begin(), |
| 474 full_hash_info.metadata.api_permissions.end()); |
| 475 } |
| 476 |
| 477 api_callback.Run(md); |
| 478 } |
| 479 |
159 bool V4GetHashProtocolManager::ParseHashResponse( | 480 bool V4GetHashProtocolManager::ParseHashResponse( |
160 const std::string& data, | 481 const std::string& response_data, |
161 std::vector<SBFullHashResult>* full_hashes, | 482 std::vector<FullHashInfo>* full_hash_infos, |
162 base::Time* negative_cache_expire) { | 483 Time* negative_cache_expire) { |
163 FindFullHashesResponse response; | 484 FindFullHashesResponse response; |
164 | 485 |
165 if (!response.ParseFromString(data)) { | 486 if (!response.ParseFromString(response_data)) { |
166 RecordParseGetHashResult(PARSE_FROM_STRING_ERROR); | 487 RecordParseGetHashResult(PARSE_FROM_STRING_ERROR); |
167 return false; | 488 return false; |
168 } | 489 } |
169 | 490 |
170 // negative_cache_duration should always be set. | 491 // negative_cache_duration should always be set. |
171 DCHECK(response.has_negative_cache_duration()); | 492 DCHECK(response.has_negative_cache_duration()); |
| 493 |
172 // Seconds resolution is good enough so we ignore the nanos field. | 494 // Seconds resolution is good enough so we ignore the nanos field. |
173 *negative_cache_expire = | 495 *negative_cache_expire = |
174 clock_->Now() + base::TimeDelta::FromSeconds( | 496 clock_->Now() + |
175 response.negative_cache_duration().seconds()); | 497 TimeDelta::FromSeconds(response.negative_cache_duration().seconds()); |
176 | 498 |
177 if (response.has_minimum_wait_duration()) { | 499 if (response.has_minimum_wait_duration()) { |
178 // Seconds resolution is good enough so we ignore the nanos field. | 500 // Seconds resolution is good enough so we ignore the nanos field. |
179 next_gethash_time_ = | 501 next_gethash_time_ = |
180 clock_->Now() + base::TimeDelta::FromSeconds( | 502 clock_->Now() + |
181 response.minimum_wait_duration().seconds()); | 503 TimeDelta::FromSeconds(response.minimum_wait_duration().seconds()); |
182 } | 504 } |
183 | 505 |
184 // We only expect one threat type per request, so we make sure | |
185 // the threat types are consistent between matches. | |
186 ThreatType expected_threat_type = THREAT_TYPE_UNSPECIFIED; | |
187 | |
188 // Loop over the threat matches and fill in full_hashes. | |
189 for (const ThreatMatch& match : response.matches()) { | 506 for (const ThreatMatch& match : response.matches()) { |
190 // Make sure the platform and threat entry type match. | 507 if (!match.has_platform_type()) { |
191 if (!(match.has_threat_entry_type() && match.threat_entry_type() == URL && | 508 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); |
192 match.has_threat())) { | 509 return false; |
| 510 } |
| 511 if (!match.has_threat_entry_type()) { |
193 RecordParseGetHashResult(UNEXPECTED_THREAT_ENTRY_TYPE_ERROR); | 512 RecordParseGetHashResult(UNEXPECTED_THREAT_ENTRY_TYPE_ERROR); |
194 return false; | 513 return false; |
195 } | 514 } |
196 | |
197 if (!match.has_threat_type()) { | 515 if (!match.has_threat_type()) { |
198 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); | 516 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); |
199 return false; | 517 return false; |
200 } | 518 } |
201 | 519 if (!match.has_threat()) { |
202 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) { | 520 RecordParseGetHashResult(NO_THREAT_ERROR); |
203 expected_threat_type = match.threat_type(); | 521 return false; |
204 } else if (match.threat_type() != expected_threat_type) { | 522 } |
205 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR); | 523 |
206 return false; | 524 UpdateListIdentifier list_id( |
207 } | 525 match.platform_type(), match.threat_entry_type(), match.threat_type()); |
208 | 526 base::Time positive_expiry; |
209 // Fill in the full hash. | |
210 SBFullHashResult result; | |
211 result.hash = StringToSBFullHash(match.threat().hash()); | |
212 | |
213 if (match.has_cache_duration()) { | 527 if (match.has_cache_duration()) { |
214 // Seconds resolution is good enough so we ignore the nanos field. | 528 // Seconds resolution is good enough so we ignore the nanos field. |
215 result.cache_expire_after = | 529 positive_expiry = clock_->Now() + TimeDelta::FromSeconds( |
216 clock_->Now() + | 530 match.cache_duration().seconds()); |
217 base::TimeDelta::FromSeconds(match.cache_duration().seconds()); | |
218 } else { | 531 } else { |
219 result.cache_expire_after = clock_->Now(); | 532 positive_expiry = clock_->Now() - base::TimeDelta::FromSeconds(1); |
220 } | 533 } |
221 | 534 FullHashInfo full_hash_info(match.threat().hash(), list_id, |
222 // Different threat types will handle the metadata differently. | 535 positive_expiry); |
223 if (match.threat_type() == API_ABUSE) { | 536 if (!ParseMetadata(match, &full_hash_info.metadata)) { |
224 if (match.has_platform_type() && | 537 return false; |
225 match.platform_type() == CHROME_PLATFORM) { | 538 } |
226 if (match.has_threat_entry_metadata()) { | 539 |
227 // For API Abuse, store a list of the returned permissions. | 540 full_hash_infos->push_back(full_hash_info); |
228 for (const ThreatEntryMetadata::MetadataEntry& m : | 541 } |
229 match.threat_entry_metadata().entries()) { | 542 return true; |
230 if (m.key() == "permission") { | 543 } |
231 result.metadata.api_permissions.insert(m.value()); | 544 |
232 } else { | 545 bool V4GetHashProtocolManager::ParseMetadata(const ThreatMatch& match, |
233 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); | 546 ThreatMetadata* metadata) { |
234 return false; | 547 // Different threat types will handle the metadata differently. |
235 } | 548 if (match.threat_type() == API_ABUSE) { |
236 } | 549 if (!match.has_platform_type() || |
| 550 match.platform_type() != CHROME_PLATFORM) { |
| 551 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); |
| 552 return false; |
| 553 } |
| 554 |
| 555 if (!match.has_threat_entry_metadata()) { |
| 556 RecordParseGetHashResult(NO_METADATA_ERROR); |
| 557 return false; |
| 558 } |
| 559 // For API Abuse, store a list of the returned permissions. |
| 560 for (const ThreatEntryMetadata::MetadataEntry& m : |
| 561 match.threat_entry_metadata().entries()) { |
| 562 if (m.key() != kPermission) { |
| 563 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| 564 return false; |
| 565 } |
| 566 metadata->api_permissions.insert(m.value()); |
| 567 } |
| 568 } else if (match.threat_type() == MALWARE_THREAT || |
| 569 match.threat_type() == POTENTIALLY_HARMFUL_APPLICATION) { |
| 570 for (const ThreatEntryMetadata::MetadataEntry& m : |
| 571 match.threat_entry_metadata().entries()) { |
| 572 // TODO: Need to confirm the below key/value pairs with CSD backend. |
| 573 if (m.key() == kPhaPatternType || m.key() == kMalwarePatternType) { |
| 574 if (m.value() == kLanding) { |
| 575 metadata->threat_pattern_type = ThreatPatternType::MALWARE_LANDING; |
| 576 break; |
| 577 } else if (m.value() == kDistribution) { |
| 578 metadata->threat_pattern_type = |
| 579 ThreatPatternType::MALWARE_DISTRIBUTION; |
| 580 break; |
237 } else { | 581 } else { |
238 RecordParseGetHashResult(NO_METADATA_ERROR); | 582 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
239 return false; | 583 return false; |
240 } | 584 } |
241 } else { | 585 } |
242 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); | 586 } |
243 return false; | 587 } else if (match.threat_type() == SOCIAL_ENGINEERING_PUBLIC) { |
244 } | 588 for (const ThreatEntryMetadata::MetadataEntry& m : |
245 } else if (match.threat_type() == MALWARE_THREAT || | 589 match.threat_entry_metadata().entries()) { |
246 match.threat_type() == POTENTIALLY_HARMFUL_APPLICATION) { | 590 if (m.key() == kSePatternType) { |
247 for (const ThreatEntryMetadata::MetadataEntry& m : | 591 if (m.value() == kSocialEngineeringAds) { |
248 match.threat_entry_metadata().entries()) { | 592 metadata->threat_pattern_type = |
249 // TODO: Need to confirm the below key/value pairs with CSD backend. | 593 ThreatPatternType::SOCIAL_ENGINEERING_ADS; |
250 if (m.key() == "pha_pattern_type" || | 594 break; |
251 m.key() == "malware_pattern_type") { | 595 } else if (m.value() == kSocialEngineeringLanding) { |
252 if (m.value() == "LANDING") { | 596 metadata->threat_pattern_type = |
253 result.metadata.threat_pattern_type = | 597 ThreatPatternType::SOCIAL_ENGINEERING_LANDING; |
254 ThreatPatternType::MALWARE_LANDING; | 598 break; |
255 break; | 599 } else if (m.value() == kPhishing) { |
256 } else if (m.value() == "DISTRIBUTION") { | 600 metadata->threat_pattern_type = ThreatPatternType::PHISHING; |
257 result.metadata.threat_pattern_type = | 601 break; |
258 ThreatPatternType::MALWARE_DISTRIBUTION; | 602 } else { |
259 break; | 603 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
260 } else { | 604 return false; |
261 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); | |
262 return false; | |
263 } | |
264 } | 605 } |
265 } | 606 } |
266 } else if (match.threat_type() == SOCIAL_ENGINEERING_PUBLIC) { | 607 } |
267 for (const ThreatEntryMetadata::MetadataEntry& m : | 608 } else { |
268 match.threat_entry_metadata().entries()) { | 609 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); |
269 if (m.key() == "se_pattern_type") { | 610 return false; |
270 if (m.value() == "SOCIAL_ENGINEERING_ADS") { | 611 } |
271 result.metadata.threat_pattern_type = | 612 |
272 ThreatPatternType::SOCIAL_ENGINEERING_ADS; | |
273 break; | |
274 } else if (m.value() == "SOCIAL_ENGINEERING_LANDING") { | |
275 result.metadata.threat_pattern_type = | |
276 ThreatPatternType::SOCIAL_ENGINEERING_LANDING; | |
277 break; | |
278 } else if (m.value() == "PHISHING") { | |
279 result.metadata.threat_pattern_type = ThreatPatternType::PHISHING; | |
280 break; | |
281 } else { | |
282 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); | |
283 return false; | |
284 } | |
285 } | |
286 } | |
287 } else { | |
288 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); | |
289 return false; | |
290 } | |
291 | |
292 full_hashes->push_back(result); | |
293 } | |
294 return true; | 613 return true; |
295 } | 614 } |
296 | 615 |
297 void V4GetHashProtocolManager::GetFullHashes( | 616 void V4GetHashProtocolManager::ResetGetHashErrors() { |
298 const std::vector<SBPrefix>& prefixes, | 617 gethash_error_count_ = 0; |
299 const std::vector<PlatformType>& platforms, | 618 gethash_back_off_mult_ = 1; |
300 ThreatType threat_type, | |
301 FullHashCallback callback) { | |
302 DCHECK(CalledOnValidThread()); | |
303 // We need to wait the minimum waiting duration, and if we are in backoff, | |
304 // we need to check if we're past the next allowed time. If we are, we can | |
305 // proceed with the request. If not, we are required to return empty results | |
306 // (i.e. treat the page as safe). | |
307 if (clock_->Now() <= next_gethash_time_) { | |
308 if (gethash_error_count_) { | |
309 RecordGetHashResult(V4OperationResult::BACKOFF_ERROR); | |
310 } else { | |
311 RecordGetHashResult(V4OperationResult::MIN_WAIT_DURATION_ERROR); | |
312 } | |
313 std::vector<SBFullHashResult> full_hashes; | |
314 callback.Run(full_hashes, base::Time()); | |
315 return; | |
316 } | |
317 | |
318 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type); | |
319 GURL gethash_url; | |
320 net::HttpRequestHeaders headers; | |
321 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); | |
322 | |
323 std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create( | |
324 url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this); | |
325 net::URLFetcher* fetcher = owned_fetcher.get(); | |
326 fetcher->SetExtraRequestHeaders(headers.ToString()); | |
327 hash_requests_[fetcher] = std::make_pair(std::move(owned_fetcher), callback); | |
328 | |
329 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); | |
330 fetcher->SetRequestContext(request_context_getter_.get()); | |
331 fetcher->Start(); | |
332 } | |
333 | |
334 void V4GetHashProtocolManager::GetFullHashesWithApis( | |
335 const std::vector<SBPrefix>& prefixes, | |
336 FullHashCallback callback) { | |
337 std::vector<PlatformType> platform = {CHROME_PLATFORM}; | |
338 GetFullHashes(prefixes, platform, API_ABUSE, callback); | |
339 } | 619 } |
340 | 620 |
341 void V4GetHashProtocolManager::SetClockForTests( | 621 void V4GetHashProtocolManager::SetClockForTests( |
342 std::unique_ptr<base::Clock> clock) { | 622 std::unique_ptr<base::Clock> clock) { |
343 clock_ = std::move(clock); | 623 clock_ = std::move(clock); |
344 } | 624 } |
345 | 625 |
| 626 void V4GetHashProtocolManager::UpdateCache( |
| 627 const std::vector<HashPrefix>& prefixes_requested, |
| 628 const std::vector<FullHashInfo>& full_hash_infos, |
| 629 const Time& negative_cache_expire) { |
| 630 // If negative_cache_expire is null, don't cache the results since it's not |
| 631 // clear till what time they should be considered valid. |
| 632 if (negative_cache_expire.is_null()) { |
| 633 return; |
| 634 } |
| 635 |
| 636 for (const HashPrefix& prefix : prefixes_requested) { |
| 637 // Create or reset the cached result for this prefix. |
| 638 CachedHashPrefixInfo& chpi = full_hash_cache_[prefix]; |
| 639 chpi.full_hash_infos.clear(); |
| 640 chpi.negative_expiry = negative_cache_expire; |
| 641 |
| 642 for (const FullHashInfo& full_hash_info : full_hash_infos) { |
| 643 if (V4ProtocolManagerUtil::FullHashMatchesHashPrefix( |
| 644 full_hash_info.full_hash, prefix)) { |
| 645 chpi.full_hash_infos.push_back(full_hash_info); |
| 646 } |
| 647 } |
| 648 } |
| 649 } |
| 650 |
| 651 void V4GetHashProtocolManager::MergeResults( |
| 652 const FullHashToStoreAndHashPrefixesMap& |
| 653 full_hash_to_store_and_hash_prefixes, |
| 654 const std::vector<FullHashInfo>& full_hash_infos, |
| 655 std::vector<FullHashInfo>* merged_full_hash_infos) { |
| 656 bool get_hash_hit = false; |
| 657 for (const FullHashInfo& fhi : full_hash_infos) { |
| 658 auto it = full_hash_to_store_and_hash_prefixes.find(fhi.full_hash); |
| 659 if (full_hash_to_store_and_hash_prefixes.end() != it) { |
| 660 for (const StoreAndHashPrefix& sahp : it->second) { |
| 661 if (fhi.list_id == sahp.list_id) { |
| 662 merged_full_hash_infos->push_back(fhi); |
| 663 get_hash_hit = true; |
| 664 break; |
| 665 } |
| 666 } |
| 667 } |
| 668 } |
| 669 |
| 670 if (get_hash_hit) { |
| 671 RecordV4GetHashCheckResult(GET_HASH_CHECK_HIT); |
| 672 } else if (full_hash_infos.empty()) { |
| 673 RecordV4GetHashCheckResult(GET_HASH_CHECK_EMPTY); |
| 674 } else { |
| 675 RecordV4GetHashCheckResult(GET_HASH_CHECK_MISS); |
| 676 } |
| 677 } |
| 678 |
346 // net::URLFetcherDelegate implementation ---------------------------------- | 679 // net::URLFetcherDelegate implementation ---------------------------------- |
347 | 680 |
348 // SafeBrowsing request responses are handled here. | 681 // SafeBrowsing request responses are handled here. |
349 void V4GetHashProtocolManager::OnURLFetchComplete( | 682 void V4GetHashProtocolManager::OnURLFetchComplete( |
350 const net::URLFetcher* source) { | 683 const net::URLFetcher* source) { |
351 DCHECK(CalledOnValidThread()); | 684 DCHECK(CalledOnValidThread()); |
352 | 685 |
353 HashRequests::iterator it = hash_requests_.find(source); | 686 PendingHashRequests::iterator it = pending_hash_requests_.find(source); |
354 DCHECK(it != hash_requests_.end()) << "Request not found"; | 687 DCHECK(it != pending_hash_requests_.end()) << "Request not found"; |
355 | 688 |
356 int response_code = source->GetResponseCode(); | 689 int response_code = source->GetResponseCode(); |
357 net::URLRequestStatus status = source->GetStatus(); | 690 net::URLRequestStatus status = source->GetStatus(); |
358 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( | 691 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( |
359 kUmaV4HashResponseMetricName, status, response_code); | 692 kUmaV4HashResponseMetricName, status, response_code); |
360 | 693 |
361 const FullHashCallback& callback = it->second.second; | 694 std::vector<FullHashInfo> full_hash_infos; |
362 std::vector<SBFullHashResult> full_hashes; | 695 Time negative_cache_expire; |
363 base::Time negative_cache_expire; | |
364 if (status.is_success() && response_code == net::HTTP_OK) { | 696 if (status.is_success() && response_code == net::HTTP_OK) { |
365 RecordGetHashResult(V4OperationResult::STATUS_200); | 697 RecordGetHashResult(V4OperationResult::STATUS_200); |
366 ResetGetHashErrors(); | 698 ResetGetHashErrors(); |
367 std::string data; | 699 std::string data; |
368 source->GetResponseAsString(&data); | 700 source->GetResponseAsString(&data); |
369 if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) { | 701 if (!ParseHashResponse(data, &full_hash_infos, &negative_cache_expire)) { |
370 full_hashes.clear(); | 702 full_hash_infos.clear(); |
371 RecordGetHashResult(V4OperationResult::PARSE_ERROR); | 703 RecordGetHashResult(V4OperationResult::PARSE_ERROR); |
372 } | 704 } |
373 } else { | 705 } else { |
374 HandleGetHashError(clock_->Now()); | 706 HandleGetHashError(clock_->Now()); |
375 | 707 |
376 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: " | 708 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: " |
377 << source->GetURL() << " failed with error: " << status.error() | 709 << source->GetURL() << " failed with error: " << status.error() |
378 << " and response code: " << response_code; | 710 << " and response code: " << response_code; |
379 | 711 |
380 if (status.status() == net::URLRequestStatus::FAILED) { | 712 if (status.status() == net::URLRequestStatus::FAILED) { |
381 RecordGetHashResult(V4OperationResult::NETWORK_ERROR); | 713 RecordGetHashResult(V4OperationResult::NETWORK_ERROR); |
382 } else { | 714 } else { |
383 RecordGetHashResult(V4OperationResult::HTTP_ERROR); | 715 RecordGetHashResult(V4OperationResult::HTTP_ERROR); |
384 } | 716 } |
385 } | 717 } |
386 | 718 |
387 // Invoke the callback with full_hashes, even if there was a parse error or | 719 const std::unique_ptr<FullHashCallbackInfo>& fhci = it->second; |
388 // an error response code (in which case full_hashes will be empty). The | 720 UpdateCache(fhci->prefixes_requested, full_hash_infos, negative_cache_expire); |
389 // caller can't be blocked indefinitely. | 721 MergeResults(fhci->full_hash_to_store_and_hash_prefixes, full_hash_infos, |
390 callback.Run(full_hashes, negative_cache_expire); | 722 &fhci->cached_full_hash_infos); |
391 | 723 |
392 hash_requests_.erase(it); | 724 fhci->callback.Run(fhci->cached_full_hash_infos); |
| 725 |
| 726 pending_hash_requests_.erase(it); |
393 } | 727 } |
394 | 728 |
395 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { | 729 #ifndef DEBUG |
396 DCHECK(CalledOnValidThread()); | 730 std::ostream& operator<<(std::ostream& os, const FullHashInfo& fhi) { |
397 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( | 731 os << "{full_hash: " << fhi.full_hash << "; list_id: " << fhi.list_id |
398 &gethash_error_count_, &gethash_back_off_mult_); | 732 << "; positive_expiry: " << fhi.positive_expiry |
399 next_gethash_time_ = now + next; | 733 << "; metadata.api_permissions.size(): " |
| 734 << fhi.metadata.api_permissions.size() << "}"; |
| 735 return os; |
400 } | 736 } |
401 | 737 #endif |
402 void V4GetHashProtocolManager::GetHashUrlAndHeaders( | |
403 const std::string& req_base64, | |
404 GURL* gurl, | |
405 net::HttpRequestHeaders* headers) const { | |
406 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", | |
407 config_, gurl, headers); | |
408 } | |
409 | 738 |
410 } // namespace safe_browsing | 739 } // namespace safe_browsing |
OLD | NEW |