Chromium Code Reviews| Index: components/safe_browsing_db/v4_get_hash_protocol_manager.cc |
| diff --git a/components/safe_browsing_db/v4_get_hash_protocol_manager.cc b/components/safe_browsing_db/v4_get_hash_protocol_manager.cc |
| index 8db01aca8cb180753c1e9941d74a2c4e090a013f..4e13c035ef875fe4f09fe4ddbe9aadfc2030c9ff 100644 |
| --- a/components/safe_browsing_db/v4_get_hash_protocol_manager.cc |
| +++ b/components/safe_browsing_db/v4_get_hash_protocol_manager.cc |
| @@ -48,9 +48,29 @@ enum ParseResultType { |
| // A match in the response contained a metadata, but the metadata is invalid. |
| UNEXPECTED_METADATA_VALUE_ERROR = 6, |
| + // A match in the response had no information in the threat field. |
| + NO_THREAT_ERROR = 7, |
| + |
| // Memory space for histograms is determined by the max. ALWAYS |
| // ADD NEW VALUES BEFORE THIS ONE. |
| - PARSE_RESULT_TYPE_MAX = 7, |
| + PARSE_RESULT_TYPE_MAX = 8, |
| +}; |
| + |
| +// Enumerate full hash cache hits/misses for histogramming purposes. |
| +// DO NOT CHANGE THE ORDERING OF THESE VALUES. |
| +enum V4FullHashCacheResultType { |
| + // Full hashes for which there is no cache hit. |
| + FULL_HASH_CACHE_MISS = 0, |
| + |
| + // Full hashes with a cache hit. |
| + FULL_HASH_CACHE_HIT = 1, |
| + |
| + // Full hashes with a negative cache hit. |
| + FULL_HASH_NEGATIVE_CACHE_HIT = 2, |
| + |
| + // Memory space for histograms is determined by the max. ALWAYS |
| + // ADD NEW VALUES BEFORE THIS ONE. |
| + FULL_HASH_CACHE_RESULT_MAX |
| }; |
| // Record parsing errors of a GetHash result. |
| @@ -66,6 +86,12 @@ void RecordGetHashResult(safe_browsing::V4OperationResult result) { |
| safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); |
| } |
| +// Record a full hash cache hit result. |
| +void RecordV4FullHashCacheResult(V4FullHashCacheResultType result_type) { |
| + UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4FullHashCacheResult", result_type, |
| + FULL_HASH_CACHE_RESULT_MAX); |
| +} |
| + |
| } // namespace |
| namespace safe_browsing { |
| @@ -79,37 +105,94 @@ class V4GetHashProtocolManagerFactoryImpl |
| public: |
| V4GetHashProtocolManagerFactoryImpl() {} |
| ~V4GetHashProtocolManagerFactoryImpl() override {} |
| - V4GetHashProtocolManager* CreateProtocolManager( |
| + std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager( |
| net::URLRequestContextGetter* request_context_getter, |
| + const base::hash_set<UpdateListIdentifier>& stores_to_request, |
| const V4ProtocolConfig& config) override { |
| - return new V4GetHashProtocolManager(request_context_getter, config); |
| + return base::WrapUnique(new V4GetHashProtocolManager( |
|
Nathan Parker
2016/09/09 21:26:21
The new recommended way, from ptr_util.h:
// Make
vakh (use Gerrit instead)
2016/09/09 23:25:17
I used that at first, but it requires the construc
|
| + request_context_getter, stores_to_request, config)); |
| } |
| private: |
| DISALLOW_COPY_AND_ASSIGN(V4GetHashProtocolManagerFactoryImpl); |
| }; |
| +// ---------------------------------------------------------------- |
| + |
| +CachedHashPrefixInfo::CachedHashPrefixInfo() {} |
| + |
| +CachedHashPrefixInfo::CachedHashPrefixInfo(const CachedHashPrefixInfo& other) = |
| + default; |
| + |
| +CachedHashPrefixInfo::~CachedHashPrefixInfo() {} |
| + |
| +// ---------------------------------------------------------------- |
| + |
| +FullHashCallbackInfo::FullHashCallbackInfo() {} |
| + |
| +FullHashCallbackInfo::FullHashCallbackInfo( |
| + const std::vector<FullHashInfo>& cached_full_hash_infos, |
| + const std::vector<HashPrefix>& prefixes_requested, |
| + std::unique_ptr<net::URLFetcher> fetcher, |
| + const FullHashToStoreAndHashPrefixesMap& |
| + full_hash_to_store_and_hash_prefixes, |
| + FullHashCallback callback) |
| + : cached_full_hash_infos(cached_full_hash_infos), |
| + callback(callback), |
| + fetcher(std::move(fetcher)), |
| + full_hash_to_store_and_hash_prefixes( |
| + full_hash_to_store_and_hash_prefixes), |
| + prefixes_requested(prefixes_requested) {} |
| + |
| +FullHashCallbackInfo::~FullHashCallbackInfo() {} |
| + |
| +// ---------------------------------------------------------------- |
| + |
| +FullHashInfo::FullHashInfo(const FullHash& full_hash, |
| + const UpdateListIdentifier& list_id, |
| + const base::Time& positive_ttl) |
| + : full_hash(full_hash), list_id(list_id), positive_ttl(positive_ttl) {} |
| + |
| +FullHashInfo::FullHashInfo(const FullHashInfo& other) = default; |
| + |
| +FullHashInfo::~FullHashInfo() {} |
| + |
| +bool FullHashInfo::operator==(const FullHashInfo& other) const { |
| + return full_hash == other.full_hash && list_id == other.list_id && |
| + positive_ttl == other.positive_ttl && metadata == other.metadata; |
| +} |
| + |
| +bool FullHashInfo::operator!=(const FullHashInfo& other) const { |
| + return !operator==(other); |
| +} |
| + |
| // V4GetHashProtocolManager implementation -------------------------------- |
| // static |
| V4GetHashProtocolManagerFactory* V4GetHashProtocolManager::factory_ = NULL; |
| // static |
| -V4GetHashProtocolManager* V4GetHashProtocolManager::Create( |
| +std::unique_ptr<V4GetHashProtocolManager> V4GetHashProtocolManager::Create( |
| net::URLRequestContextGetter* request_context_getter, |
| + const base::hash_set<UpdateListIdentifier>& stores_to_request, |
| const V4ProtocolConfig& config) { |
| if (!factory_) |
| factory_ = new V4GetHashProtocolManagerFactoryImpl(); |
| - return factory_->CreateProtocolManager(request_context_getter, config); |
| + return factory_->CreateProtocolManager(request_context_getter, |
| + stores_to_request, config); |
| } |
| -void V4GetHashProtocolManager::ResetGetHashErrors() { |
| - gethash_error_count_ = 0; |
| - gethash_back_off_mult_ = 1; |
| +// static |
| +void V4GetHashProtocolManager::RegisterFactory( |
| + std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { |
| + if (factory_) |
| + delete factory_; |
| + factory_ = factory.release(); |
| } |
| V4GetHashProtocolManager::V4GetHashProtocolManager( |
| net::URLRequestContextGetter* request_context_getter, |
| + const base::hash_set<UpdateListIdentifier>& stores_to_request, |
| const V4ProtocolConfig& config) |
| : gethash_error_count_(0), |
| gethash_back_off_mult_(1), |
| @@ -117,35 +200,213 @@ V4GetHashProtocolManager::V4GetHashProtocolManager( |
| config_(config), |
| request_context_getter_(request_context_getter), |
| url_fetcher_id_(0), |
| - clock_(new base::DefaultClock()) {} |
| + clock_(new base::DefaultClock()) { |
| + for (const UpdateListIdentifier& store : stores_to_request) { |
| + platform_types_.insert(store.platform_type); |
| + threat_entry_types_.insert(store.threat_entry_type); |
| + threat_types_.insert(store.threat_type); |
| + } |
|
Nathan Parker
2016/09/09 21:26:21
nit: Could just DCHECK(!stores_to_request.empty())
vakh (use Gerrit instead)
2016/09/09 23:25:17
Done.
|
| + DCHECK(!platform_types_.empty()); |
| + DCHECK(!threat_entry_types_.empty()); |
| + DCHECK(!threat_types_.empty()); |
| +} |
| -V4GetHashProtocolManager::~V4GetHashProtocolManager() { |
| +V4GetHashProtocolManager::~V4GetHashProtocolManager() {} |
| + |
| +void V4GetHashProtocolManager::ClearCache() { |
| + DCHECK(CalledOnValidThread()); |
| + full_hash_cache_.clear(); |
| } |
| -// static |
| -void V4GetHashProtocolManager::RegisterFactory( |
| - std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { |
| - if (factory_) |
| - delete factory_; |
| - factory_ = factory.release(); |
| +void V4GetHashProtocolManager::GetFullHashes( |
| + const FullHashToStoreAndHashPrefixesMap& |
| + full_hash_to_store_and_hash_prefixes, |
| + FullHashCallback callback) { |
| + DCHECK(CalledOnValidThread()); |
| + DCHECK(!full_hash_to_store_and_hash_prefixes.empty()); |
| + |
| + std::vector<HashPrefix> prefixes_to_request; |
| + std::vector<FullHashInfo> cached_full_hash_infos; |
| + GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, Time::Now(), |
| + &prefixes_to_request, &cached_full_hash_infos); |
| + |
| + if (prefixes_to_request.empty()) { |
| + // 100% cache hits (positive or negative) so we can call the callback right |
| + // away. |
| + callback.Run(cached_full_hash_infos); |
| + return; |
| + } |
| + |
| + // We need to wait the minimum waiting duration, and if we are in backoff, |
| + // we need to check if we're past the next allowed time. If we are, we can |
| + // proceed with the request. If not, we are required to return empty results |
| + // (i.e. treat the page as safe). |
|
Nathan Parker
2016/09/09 21:26:21
nit: "i.e. just use the cache and potentially trea
vakh (use Gerrit instead)
2016/09/09 23:25:17
Done.
|
| + if (clock_->Now() <= next_gethash_time_) { |
| + if (gethash_error_count_) { |
| + RecordGetHashResult(V4OperationResult::BACKOFF_ERROR); |
| + } else { |
| + RecordGetHashResult(V4OperationResult::MIN_WAIT_DURATION_ERROR); |
| + } |
| + callback.Run(cached_full_hash_infos); |
| + return; |
| + } |
| + |
| + std::string req_base64 = GetHashRequest(prefixes_to_request); |
| + GURL gethash_url; |
| + net::HttpRequestHeaders headers; |
| + GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); |
| + |
| + std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create( |
| + url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this); |
| + net::URLFetcher* fetcher = owned_fetcher.get(); |
| + pending_hash_requests_[fetcher].reset(new FullHashCallbackInfo( |
| + cached_full_hash_infos, prefixes_to_request, std::move(owned_fetcher), |
| + full_hash_to_store_and_hash_prefixes, callback)); |
| + |
| + fetcher->SetExtraRequestHeaders(headers.ToString()); |
| + fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); |
| + fetcher->SetRequestContext(request_context_getter_.get()); |
| + fetcher->Start(); |
| +} |
| + |
| +void V4GetHashProtocolManager::GetFullHashesWithApis( |
| + const GURL& url, |
| + ThreatMetadataForApiCallback api_callback) { |
| + DCHECK(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme)); |
| + |
| + base::hash_set<FullHash> full_hashes; |
| + V4ProtocolManagerUtil::UrlToFullHashes(url, &full_hashes); |
| + |
| + FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes; |
| + for (const FullHash& full_hash : full_hashes) { |
| + HashPrefix prefix; |
| + bool result = |
| + V4ProtocolManagerUtil::FullHashToSmallestHashPrefix(full_hash, &prefix); |
| + DCHECK(result); |
| + full_hash_to_store_and_hash_prefixes[full_hash].push_back( |
|
Nathan Parker
2016/09/09 21:26:21
.emplace_back(GetChromeUrlApiId(), prefix), if tha
vakh (use Gerrit instead)
2016/09/09 23:25:18
Done.
|
| + StoreAndHashPrefix(GetChromeUrlApiId(), prefix)); |
| + } |
| + |
| + GetFullHashes(full_hash_to_store_and_hash_prefixes, |
| + base::Bind(&V4GetHashProtocolManager::OnFullHashForApi, |
| + base::Unretained(this), api_callback, full_hashes)); |
| +} |
| + |
| +void V4GetHashProtocolManager::GetFullHashCachedResults( |
| + const FullHashToStoreAndHashPrefixesMap& |
| + full_hash_to_store_and_hash_prefixes, |
| + const Time& now, |
| + std::vector<HashPrefix>* prefixes_to_request, |
| + std::vector<FullHashInfo>* cached_full_hash_infos) const { |
| + DCHECK(!full_hash_to_store_and_hash_prefixes.empty()); |
| + DCHECK(prefixes_to_request); |
|
Nathan Parker
2016/09/09 21:26:21
nit: can skip checking the ptr if you're going to
vakh (use Gerrit instead)
2016/09/09 23:25:17
Done.
|
| + DCHECK(prefixes_to_request->empty()); |
| + DCHECK(cached_full_hash_infos); |
| + DCHECK(cached_full_hash_infos->empty()); |
| + |
| + // Caching behavior is documented here: |
| + // https://developers.google.com/safe-browsing/v4/caching#about-caching |
| + // |
| + // The cache operates as follows: |
| + // Lookup: |
| + // Case 1: The prefix is in the cache. |
| + // Case a: The full hash is in the cache. |
| + // Case i : The positive full hash result has not expired. |
| + // The result is unsafe and we do not need to send a new |
| + // request. |
| + // Case ii: The positive full hash result has expired. |
| + // We need to send a request for full hashes. |
| + // Case b: The full hash is not in the cache. |
| + // Case i : The negative cache entry has not expired. |
| + // The result is still safe and we do not need to send a |
| + // new request. |
| + // Case ii: The negative cache entry has expired. |
| + // We need to send a request for full hashes. |
| + // Case 2: The prefix is not in the cache. |
| + // We need to send a request for full hashes. |
| + // |
| + // Note on eviction: |
| + // CachedHashPrefixInfo entries can be removed from the cache only when |
| + // the negative cache expire time and the cache expire time of all full |
| + // hash results for that prefix have expired. |
| + // Individual full hash results can be removed from the prefix's |
| + // cache entry if they expire AND their expire time is after the negative |
| + // cache expire time. |
| + |
| + // TODO(vakh): Perform cache cleanup. |
| + base::hash_set<HashPrefix> unique_prefixes_to_request; |
| + for (const auto& it : full_hash_to_store_and_hash_prefixes) { |
|
Nathan Parker
2016/09/09 21:26:21
I'm assuming this code is not modified, ya? If th
vakh (use Gerrit instead)
2016/09/09 23:25:17
Most of it has been re-written. Please do take a l
Nathan Parker
2016/09/10 00:26:25
Seems ok to me. I see you aren't removing anythin
vakh (use Gerrit instead)
2016/09/10 00:29:29
Acknowledged.
|
| + const FullHash& full_hash = it.first; |
| + const StoreAndHashPrefixes& matched = it.second; |
| + for (const StoreAndHashPrefix& matched_it : matched) { |
| + const UpdateListIdentifier& list_id = matched_it.list_id; |
| + const HashPrefix& prefix = matched_it.hash_prefix; |
| + const auto& prefix_entry = full_hash_cache_.find(prefix); |
|
Nathan Parker
2016/09/10 00:26:25
I think this shouldn't be a reference, since find
vakh (use Gerrit instead)
2016/09/10 00:29:29
It's a common pattern:
https://cs.chromium.org/sea
Nathan Parker
2016/09/12 17:02:07
But this is a more common pattern:
https://cs.chro
Scott Hess - ex-Googler
2016/09/12 23:31:34
In general, the temporary lives for the scope of t
vakh (use Gerrit instead)
2016/09/13 00:12:06
Done. Changed to "auto it = "
Scott Hess - ex-Googler
2016/09/13 00:31:34
Compiler is standing to the side saying "I'm going
vakh (use Gerrit instead)
2016/09/13 01:18:56
Acknowledged. :)
|
| + if (prefix_entry != full_hash_cache_.end()) { |
| + // Case 1. |
| + const CachedHashPrefixInfo& cached_prefix_info = prefix_entry->second; |
| + bool found_full_hash = false; |
| + for (const FullHashInfo& full_hash_info : |
| + cached_prefix_info.full_hash_infos) { |
| + if (full_hash_info.full_hash == full_hash && |
| + full_hash_info.list_id == list_id) { |
| + // Case a. |
| + found_full_hash = true; |
| + bool positive_ttl_unexpired = full_hash_info.positive_ttl > now; |
| + if (positive_ttl_unexpired) { |
| + // Case i. |
| + cached_full_hash_infos->push_back(full_hash_info); |
| + RecordV4FullHashCacheResult(FULL_HASH_CACHE_HIT); |
| + } else { |
| + // Case ii. |
| + unique_prefixes_to_request.insert(prefix); |
| + RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); |
| + } |
| + break; |
| + } |
| + } |
| + |
| + if (!found_full_hash) { |
| + // Case b. |
| + if (cached_prefix_info.negative_ttl > now) { |
| + // Case i. |
| + RecordV4FullHashCacheResult(FULL_HASH_NEGATIVE_CACHE_HIT); |
| + } else { |
| + // Case ii. |
| + unique_prefixes_to_request.insert(prefix); |
| + RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); |
| + } |
| + } |
| + } else { |
| + // Case 2. |
| + unique_prefixes_to_request.insert(prefix); |
| + RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); |
| + } |
| + } |
| + } |
| + |
| + prefixes_to_request->insert(prefixes_to_request->begin(), |
| + unique_prefixes_to_request.begin(), |
| + unique_prefixes_to_request.end()); |
| } |
| std::string V4GetHashProtocolManager::GetHashRequest( |
| - const std::vector<SBPrefix>& prefixes, |
| - const std::vector<PlatformType>& platforms, |
| - ThreatType threat_type) { |
| - // Build the request. Client info and client states are not added to the |
| - // request protocol buffer. Client info is passed as params in the url. |
| + const std::vector<HashPrefix>& prefixes_to_request) { |
| + DCHECK(!prefixes_to_request.empty()); |
| + |
| FindFullHashesRequest req; |
| ThreatInfo* info = req.mutable_threat_info(); |
| - info->add_threat_types(threat_type); |
| - info->add_threat_entry_types(URL); |
| - for (const PlatformType p : platforms) { |
| + for (const PlatformType p : platform_types_) { |
| info->add_platform_types(p); |
| } |
| - for (const SBPrefix& prefix : prefixes) { |
| - std::string hash(reinterpret_cast<const char*>(&prefix), sizeof(SBPrefix)); |
| - info->add_threat_entries()->set_hash(hash); |
| + for (const ThreatEntryType tet : threat_entry_types_) { |
| + info->add_threat_entry_types(tet); |
| + } |
| + for (const ThreatType tt : threat_types_) { |
| + info->add_threat_types(tt); |
| + } |
| + for (const HashPrefix& prefix : prefixes_to_request) { |
| + info->add_threat_entries()->set_hash(prefix); |
| } |
| // Serialize and Base64 encode. |
| @@ -156,10 +417,43 @@ std::string V4GetHashProtocolManager::GetHashRequest( |
| return req_base64; |
| } |
| +void V4GetHashProtocolManager::GetHashUrlAndHeaders( |
| + const std::string& req_base64, |
| + GURL* gurl, |
| + net::HttpRequestHeaders* headers) const { |
| + V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", |
| + config_, gurl, headers); |
| +} |
| + |
| +void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { |
| + DCHECK(CalledOnValidThread()); |
| + TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( |
| + &gethash_error_count_, &gethash_back_off_mult_); |
| + next_gethash_time_ = now + next; |
| +} |
| + |
| +void V4GetHashProtocolManager::OnFullHashForApi( |
| + ThreatMetadataForApiCallback api_callback, |
| + const base::hash_set<FullHash>& full_hashes, |
| + const std::vector<FullHashInfo>& full_hash_infos) { |
| + ThreatMetadata md; |
| + for (const FullHashInfo& full_hash_info : full_hash_infos) { |
| + DCHECK_EQ(GetChromeUrlApiId(), full_hash_info.list_id); |
| + DCHECK(full_hashes.find(full_hash_info.full_hash) != full_hashes.end()); |
| + md.api_permissions.insert(full_hash_info.metadata.api_permissions.begin(), |
| + full_hash_info.metadata.api_permissions.end()); |
| + } |
| + |
| + // TODO(vakh): Figure out what UMA metrics to report. This code was previously |
| + // calling RecordV4GetHashCheckResult with appropriate values but that's not |
| + // applicable anymore. |
| + api_callback.Run(md); |
| +} |
| + |
| bool V4GetHashProtocolManager::ParseHashResponse( |
| const std::string& data, |
| - std::vector<SBFullHashResult>* full_hashes, |
| - base::Time* negative_cache_expire) { |
| + std::vector<FullHashInfo>* full_hash_infos, |
| + Time* negative_cache_expire) { |
| FindFullHashesResponse response; |
| if (!response.ParseFromString(data)) { |
| @@ -169,178 +463,183 @@ bool V4GetHashProtocolManager::ParseHashResponse( |
| // negative_cache_duration should always be set. |
| DCHECK(response.has_negative_cache_duration()); |
| + |
| // Seconds resolution is good enough so we ignore the nanos field. |
| *negative_cache_expire = |
| - clock_->Now() + base::TimeDelta::FromSeconds( |
| - response.negative_cache_duration().seconds()); |
| + clock_->Now() + |
| + TimeDelta::FromSeconds(response.negative_cache_duration().seconds()); |
| if (response.has_minimum_wait_duration()) { |
| // Seconds resolution is good enough so we ignore the nanos field. |
| next_gethash_time_ = |
| - clock_->Now() + base::TimeDelta::FromSeconds( |
| - response.minimum_wait_duration().seconds()); |
| + clock_->Now() + |
| + TimeDelta::FromSeconds(response.minimum_wait_duration().seconds()); |
| } |
| - // We only expect one threat type per request, so we make sure |
| - // the threat types are consistent between matches. |
| - ThreatType expected_threat_type = THREAT_TYPE_UNSPECIFIED; |
| - |
| - // Loop over the threat matches and fill in full_hashes. |
| for (const ThreatMatch& match : response.matches()) { |
| - // Make sure the platform and threat entry type match. |
| - if (!(match.has_threat_entry_type() && match.threat_entry_type() == URL && |
| - match.has_threat())) { |
| + if (!match.has_platform_type()) { |
| + RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); |
| + return false; |
| + } |
| + if (!match.has_threat_entry_type()) { |
| RecordParseGetHashResult(UNEXPECTED_THREAT_ENTRY_TYPE_ERROR); |
| return false; |
| } |
| - |
| if (!match.has_threat_type()) { |
| RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); |
| return false; |
| } |
| - |
| - if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) { |
| - expected_threat_type = match.threat_type(); |
| - } else if (match.threat_type() != expected_threat_type) { |
| - RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR); |
| + if (!match.has_threat()) { |
| + RecordParseGetHashResult(NO_THREAT_ERROR); |
| return false; |
| } |
| - // Fill in the full hash. |
| - SBFullHashResult result; |
| - result.hash = StringToSBFullHash(match.threat().hash()); |
| - |
| + UpdateListIdentifier list_id( |
| + match.platform_type(), match.threat_entry_type(), match.threat_type()); |
| + base::Time positive_ttl; |
| if (match.has_cache_duration()) { |
| // Seconds resolution is good enough so we ignore the nanos field. |
| - result.cache_expire_after = |
| - clock_->Now() + |
| - base::TimeDelta::FromSeconds(match.cache_duration().seconds()); |
| + positive_ttl = clock_->Now() + |
| + TimeDelta::FromSeconds(match.cache_duration().seconds()); |
| } else { |
| - result.cache_expire_after = clock_->Now(); |
| + positive_ttl = clock_->Now(); |
| + } |
| + FullHashInfo full_hash_info(match.threat().hash(), list_id, positive_ttl); |
| + if (!ParseMetadata(match, &full_hash_info.metadata)) { |
| + return false; |
| } |
| - // Different threat types will handle the metadata differently. |
| - if (match.threat_type() == API_ABUSE) { |
| - if (match.has_platform_type() && |
| - match.platform_type() == CHROME_PLATFORM) { |
| - if (match.has_threat_entry_metadata()) { |
| - // For API Abuse, store a list of the returned permissions. |
| - for (const ThreatEntryMetadata::MetadataEntry& m : |
| - match.threat_entry_metadata().entries()) { |
| - if (m.key() == "permission") { |
| - result.metadata.api_permissions.insert(m.value()); |
| - } else { |
| - RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| - return false; |
| - } |
| + full_hash_infos->push_back(full_hash_info); |
| + } |
| + return true; |
| +} |
| + |
| +bool V4GetHashProtocolManager::ParseMetadata(const ThreatMatch& match, |
| + ThreatMetadata* metadata) { |
| + DCHECK(metadata); |
| + // Different threat types will handle the metadata differently. |
| + if (match.threat_type() == API_ABUSE) { |
| + if (match.has_platform_type() && match.platform_type() == CHROME_PLATFORM) { |
| + if (match.has_threat_entry_metadata()) { |
|
Nathan Parker
2016/09/09 21:26:20
Can you reduce the nested if's by negating them an
vakh (use Gerrit instead)
2016/09/09 23:25:17
Done.
|
| + // For API Abuse, store a list of the returned permissions. |
| + for (const ThreatEntryMetadata::MetadataEntry& m : |
| + match.threat_entry_metadata().entries()) { |
| + if (m.key() == "permission") { |
| + metadata->api_permissions.insert(m.value()); |
| + } else { |
| + RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| + return false; |
| } |
| - } else { |
| - RecordParseGetHashResult(NO_METADATA_ERROR); |
| - return false; |
| } |
| } else { |
| - RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); |
| + RecordParseGetHashResult(NO_METADATA_ERROR); |
| return false; |
| } |
| - } else if (match.threat_type() == MALWARE_THREAT || |
| - match.threat_type() == POTENTIALLY_HARMFUL_APPLICATION) { |
| - for (const ThreatEntryMetadata::MetadataEntry& m : |
| - match.threat_entry_metadata().entries()) { |
| - // TODO: Need to confirm the below key/value pairs with CSD backend. |
| - if (m.key() == "pha_pattern_type" || |
| - m.key() == "malware_pattern_type") { |
| - if (m.value() == "LANDING") { |
| - result.metadata.threat_pattern_type = |
| - ThreatPatternType::MALWARE_LANDING; |
| - break; |
| - } else if (m.value() == "DISTRIBUTION") { |
| - result.metadata.threat_pattern_type = |
| - ThreatPatternType::MALWARE_DISTRIBUTION; |
| - break; |
| - } else { |
| - RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| - return false; |
| - } |
| + } else { |
| + RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); |
| + return false; |
| + } |
| + } else if (match.threat_type() == MALWARE_THREAT || |
| + match.threat_type() == POTENTIALLY_HARMFUL_APPLICATION) { |
| + for (const ThreatEntryMetadata::MetadataEntry& m : |
| + match.threat_entry_metadata().entries()) { |
| + // TODO: Need to confirm the below key/value pairs with CSD backend. |
| + if (m.key() == "pha_pattern_type" || m.key() == "malware_pattern_type") { |
| + if (m.value() == "LANDING") { |
| + metadata->threat_pattern_type = ThreatPatternType::MALWARE_LANDING; |
| + break; |
| + } else if (m.value() == "DISTRIBUTION") { |
| + metadata->threat_pattern_type = |
| + ThreatPatternType::MALWARE_DISTRIBUTION; |
| + break; |
| + } else { |
| + RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| + return false; |
| } |
| } |
| - } else if (match.threat_type() == SOCIAL_ENGINEERING_PUBLIC) { |
| - for (const ThreatEntryMetadata::MetadataEntry& m : |
| - match.threat_entry_metadata().entries()) { |
| - if (m.key() == "se_pattern_type") { |
| - if (m.value() == "SOCIAL_ENGINEERING_ADS") { |
| - result.metadata.threat_pattern_type = |
| - ThreatPatternType::SOCIAL_ENGINEERING_ADS; |
| - break; |
| - } else if (m.value() == "SOCIAL_ENGINEERING_LANDING") { |
| - result.metadata.threat_pattern_type = |
| - ThreatPatternType::SOCIAL_ENGINEERING_LANDING; |
| - break; |
| - } else if (m.value() == "PHISHING") { |
| - result.metadata.threat_pattern_type = ThreatPatternType::PHISHING; |
| - break; |
| - } else { |
| - RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| - return false; |
| - } |
| + } |
| + } else if (match.threat_type() == SOCIAL_ENGINEERING_PUBLIC) { |
| + for (const ThreatEntryMetadata::MetadataEntry& m : |
| + match.threat_entry_metadata().entries()) { |
| + if (m.key() == "se_pattern_type") { |
| + if (m.value() == "SOCIAL_ENGINEERING_ADS") { |
| + metadata->threat_pattern_type = |
| + ThreatPatternType::SOCIAL_ENGINEERING_ADS; |
| + break; |
| + } else if (m.value() == "SOCIAL_ENGINEERING_LANDING") { |
| + metadata->threat_pattern_type = |
| + ThreatPatternType::SOCIAL_ENGINEERING_LANDING; |
| + break; |
| + } else if (m.value() == "PHISHING") { |
| + metadata->threat_pattern_type = ThreatPatternType::PHISHING; |
| + break; |
| + } else { |
| + RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); |
| + return false; |
| } |
| } |
| - } else { |
| - RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); |
| - return false; |
| } |
| - |
| - full_hashes->push_back(result); |
| + } else { |
| + RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); |
| + return false; |
| } |
| + |
| return true; |
| } |
| -void V4GetHashProtocolManager::GetFullHashes( |
| - const std::vector<SBPrefix>& prefixes, |
| - const std::vector<PlatformType>& platforms, |
| - ThreatType threat_type, |
| - FullHashCallback callback) { |
| - DCHECK(CalledOnValidThread()); |
| - // We need to wait the minimum waiting duration, and if we are in backoff, |
| - // we need to check if we're past the next allowed time. If we are, we can |
| - // proceed with the request. If not, we are required to return empty results |
| - // (i.e. treat the page as safe). |
| - if (clock_->Now() <= next_gethash_time_) { |
| - if (gethash_error_count_) { |
| - RecordGetHashResult(V4OperationResult::BACKOFF_ERROR); |
| - } else { |
| - RecordGetHashResult(V4OperationResult::MIN_WAIT_DURATION_ERROR); |
| - } |
| - std::vector<SBFullHashResult> full_hashes; |
| - callback.Run(full_hashes, base::Time()); |
| - return; |
| - } |
| +void V4GetHashProtocolManager::ResetGetHashErrors() { |
| + gethash_error_count_ = 0; |
| + gethash_back_off_mult_ = 1; |
| +} |
| - std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type); |
| - GURL gethash_url; |
| - net::HttpRequestHeaders headers; |
| - GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); |
| +void V4GetHashProtocolManager::SetClockForTests( |
| + std::unique_ptr<base::Clock> clock) { |
| + clock_ = std::move(clock); |
| +} |
| - std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create( |
| - url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this); |
| - net::URLFetcher* fetcher = owned_fetcher.get(); |
| - fetcher->SetExtraRequestHeaders(headers.ToString()); |
| - hash_requests_[fetcher] = std::make_pair(std::move(owned_fetcher), callback); |
| +void V4GetHashProtocolManager::UpdateCache( |
| + const std::vector<HashPrefix>& prefixes_requested, |
| + const std::vector<FullHashInfo>& full_hash_infos, |
| + const Time& negative_cache_expire) { |
| + // If negative_cache_expire is null, don't cache the results it's not clear |
|
Nathan Parker
2016/09/09 21:26:21
nit: s/it's/since it's/
vakh (use Gerrit instead)
2016/09/09 23:25:18
Done.
|
| + // till what time they should be considered valid. |
| + if (negative_cache_expire.is_null()) { |
| + return; |
| + } |
| - fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); |
| - fetcher->SetRequestContext(request_context_getter_.get()); |
| - fetcher->Start(); |
| -} |
| + for (const HashPrefix& prefix : prefixes_requested) { |
| + // Create or reset the cached result for this prefix. |
| + full_hash_cache_[prefix].full_hash_infos.clear(); |
|
Nathan Parker
2016/09/09 21:26:21
This looks up prefix multiple times. You could us
vakh (use Gerrit instead)
2016/09/09 23:25:17
Changed to get a reference to it first (or add it
|
| + full_hash_cache_[prefix].negative_ttl = negative_cache_expire; |
| -void V4GetHashProtocolManager::GetFullHashesWithApis( |
| - const std::vector<SBPrefix>& prefixes, |
| - FullHashCallback callback) { |
| - std::vector<PlatformType> platform = {CHROME_PLATFORM}; |
| - GetFullHashes(prefixes, platform, API_ABUSE, callback); |
| + for (const FullHashInfo& full_hash_info : full_hash_infos) { |
| + if (V4ProtocolManagerUtil::FullHashMatchesHashPrefix( |
| + full_hash_info.full_hash, prefix)) { |
| + full_hash_cache_[prefix].full_hash_infos.push_back(full_hash_info); |
| + } |
| + } |
| + } |
| } |
| -void V4GetHashProtocolManager::SetClockForTests( |
| - std::unique_ptr<base::Clock> clock) { |
| - clock_ = std::move(clock); |
| +void V4GetHashProtocolManager::MergeResults( |
| + const FullHashToStoreAndHashPrefixesMap& |
| + full_hash_to_store_and_hash_prefixes, |
| + const std::vector<FullHashInfo>& full_hash_infos, |
| + std::vector<FullHashInfo>* merged_full_hash_infos) { |
| + for (const FullHashInfo& fhi : full_hash_infos) { |
| + bool matched_full_hash = |
| + full_hash_to_store_and_hash_prefixes.end() != |
| + full_hash_to_store_and_hash_prefixes.find(fhi.full_hash); |
| + if (matched_full_hash) { |
| + for (const StoreAndHashPrefix& sahp : |
| + full_hash_to_store_and_hash_prefixes.at(fhi.full_hash)) { |
|
Nathan Parker
2016/09/09 21:26:21
rather than looking up full_hash again, save the i
vakh (use Gerrit instead)
2016/09/09 23:25:17
Done.
|
| + if (fhi.list_id == sahp.list_id) { |
| + merged_full_hash_infos->push_back(fhi); |
| + break; |
| + } |
| + } |
| + } |
| + } |
| } |
| // net::URLFetcherDelegate implementation ---------------------------------- |
| @@ -350,24 +649,23 @@ void V4GetHashProtocolManager::OnURLFetchComplete( |
| const net::URLFetcher* source) { |
| DCHECK(CalledOnValidThread()); |
| - HashRequests::iterator it = hash_requests_.find(source); |
| - DCHECK(it != hash_requests_.end()) << "Request not found"; |
| + PendingHashRequests::iterator it = pending_hash_requests_.find(source); |
| + DCHECK(it != pending_hash_requests_.end()) << "Request not found"; |
| int response_code = source->GetResponseCode(); |
| net::URLRequestStatus status = source->GetStatus(); |
| V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( |
| kUmaV4HashResponseMetricName, status, response_code); |
| - const FullHashCallback& callback = it->second.second; |
| - std::vector<SBFullHashResult> full_hashes; |
| - base::Time negative_cache_expire; |
| + std::vector<FullHashInfo> full_hash_infos; |
| + Time negative_cache_expire; |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| RecordGetHashResult(V4OperationResult::STATUS_200); |
| ResetGetHashErrors(); |
| std::string data; |
| source->GetResponseAsString(&data); |
| - if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) { |
| - full_hashes.clear(); |
| + if (!ParseHashResponse(data, &full_hash_infos, &negative_cache_expire)) { |
| + full_hash_infos.clear(); |
| RecordGetHashResult(V4OperationResult::PARSE_ERROR); |
| } |
| } else { |
| @@ -384,27 +682,25 @@ void V4GetHashProtocolManager::OnURLFetchComplete( |
| } |
| } |
| - // Invoke the callback with full_hashes, even if there was a parse error or |
| - // an error response code (in which case full_hashes will be empty). The |
| - // caller can't be blocked indefinitely. |
| - callback.Run(full_hashes, negative_cache_expire); |
| + const std::unique_ptr<FullHashCallbackInfo>& fhci = it->second; |
| + UpdateCache(fhci->prefixes_requested, full_hash_infos, negative_cache_expire); |
| + MergeResults(fhci->full_hash_to_store_and_hash_prefixes, full_hash_infos, |
| + &fhci->cached_full_hash_infos); |
| - hash_requests_.erase(it); |
| -} |
| + const FullHashCallback& callback = fhci->callback; |
|
Nathan Parker
2016/09/09 21:26:21
just call fhci->callback.Run(..)
vakh (use Gerrit instead)
2016/09/09 23:25:17
Done.
|
| + callback.Run(fhci->cached_full_hash_infos); |
| -void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { |
| - DCHECK(CalledOnValidThread()); |
| - base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( |
| - &gethash_error_count_, &gethash_back_off_mult_); |
| - next_gethash_time_ = now + next; |
| + pending_hash_requests_.erase(it); |
| } |
| -void V4GetHashProtocolManager::GetHashUrlAndHeaders( |
| - const std::string& req_base64, |
| - GURL* gurl, |
| - net::HttpRequestHeaders* headers) const { |
| - V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", |
| - config_, gurl, headers); |
| +#ifndef DEBUG |
| +std::ostream& operator<<(std::ostream& os, const FullHashInfo& fhi) { |
| + os << "{full_hash: " << fhi.full_hash << "; list_id: " << fhi.list_id |
| + << "; positive_ttl: " << fhi.positive_ttl |
| + << "; metadata.api_permissions.size(): " |
| + << fhi.metadata.api_permissions.size() << "}"; |
| + return os; |
| } |
| +#endif |
| } // namespace safe_browsing |