Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(233)

Side by Side Diff: components/safe_browsing_db/v4_get_hash_protocol_manager_unittest.cc

Issue 2233103002: Move full hash caching logic to v4_get_hash_protocol_manager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: unique_ptr for V4GetHasProtocolManager. Fix the lone failing unit test. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 <memory> 7 #include <memory>
8 #include <vector> 8 #include <vector>
9 9
10 #include "base/base64.h" 10 #include "base/base64.h"
11 #include "base/memory/ptr_util.h" 11 #include "base/memory/ptr_util.h"
12 #include "base/run_loop.h"
12 #include "base/strings/stringprintf.h" 13 #include "base/strings/stringprintf.h"
13 #include "base/test/simple_test_clock.h" 14 #include "base/test/simple_test_clock.h"
14 #include "base/time/time.h" 15 #include "base/time/time.h"
15 #include "components/safe_browsing_db/safebrowsing.pb.h" 16 #include "components/safe_browsing_db/safebrowsing.pb.h"
16 #include "components/safe_browsing_db/testing_util.h" 17 #include "components/safe_browsing_db/testing_util.h"
17 #include "components/safe_browsing_db/util.h" 18 #include "components/safe_browsing_db/util.h"
18 #include "net/base/escape.h" 19 #include "net/base/escape.h"
19 #include "net/base/load_flags.h" 20 #include "net/base/load_flags.h"
20 #include "net/base/net_errors.h" 21 #include "net/base/net_errors.h"
21 #include "net/url_request/test_url_fetcher_factory.h" 22 #include "net/url_request/test_url_fetcher_factory.h"
22 #include "testing/gtest/include/gtest/gtest.h" 23 #include "testing/gtest/include/gtest/gtest.h"
23 24
24 using base::Time; 25 using base::Time;
25 using base::TimeDelta; 26 using base::TimeDelta;
26 27
27 namespace { 28 namespace {
28 29
29 const char kClient[] = "unittest"; 30 const char kClient[] = "unittest";
30 const char kAppVer[] = "1.0"; 31 const char kAppVer[] = "1.0";
31 const char kKeyParam[] = "test_key_param"; 32 const char kKeyParam[] = "test_key_param";
32 33
33 } // namespace 34 } // namespace
34 35
35 namespace safe_browsing { 36 namespace safe_browsing {
36 37
37 class SafeBrowsingV4GetHashProtocolManagerTest : public testing::Test { 38 class V4GetHashProtocolManagerTest : public testing::Test {
38 protected: 39 protected:
39 std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager() { 40 std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager() {
40 V4ProtocolConfig config; 41 V4ProtocolConfig config;
41 config.client_name = kClient; 42 config.client_name = kClient;
42 config.version = kAppVer; 43 config.version = kAppVer;
43 config.key_param = kKeyParam; 44 config.key_param = kKeyParam;
44 return std::unique_ptr<V4GetHashProtocolManager>( 45 base::hash_set<UpdateListIdentifier> stores_to_look(
45 V4GetHashProtocolManager::Create(NULL, config)); 46 {GetUrlMalwareId(), GetChromeUrlApiId()});
47 return V4GetHashProtocolManager::Create(NULL, stores_to_look, config);
48 }
49
50 void SetupFetcherToReturnStockOKResponse(
51 const net::TestURLFetcherFactory& factory) {
52 net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
53 DCHECK(fetcher);
54 fetcher->set_status(net::URLRequestStatus());
55 fetcher->set_response_code(200);
56 fetcher->SetResponseString(GetStockV4HashResponse());
57 fetcher->delegate()->OnURLFetchComplete(fetcher);
46 } 58 }
47 59
48 std::string GetStockV4HashResponse() { 60 std::string GetStockV4HashResponse() {
49 FindFullHashesResponse res; 61 FindFullHashesResponse res;
50 res.mutable_negative_cache_duration()->set_seconds(600); 62 res.mutable_negative_cache_duration()->set_seconds(600);
51 ThreatMatch* m = res.add_matches(); 63 ThreatMatch* m = res.add_matches();
52 m->set_threat_type(API_ABUSE); 64 m->set_threat_type(API_ABUSE);
53 m->set_platform_type(CHROME_PLATFORM); 65 m->set_platform_type(CHROME_PLATFORM);
54 m->set_threat_entry_type(URL); 66 m->set_threat_entry_type(URL);
55 m->mutable_cache_duration()->set_seconds(300); 67 m->mutable_cache_duration()->set_seconds(300);
56 m->mutable_threat()->set_hash( 68 m->mutable_threat()->set_hash(FullHash("Everything's shiny, Cap'n."));
57 SBFullHashToString(SBFullHashForString("Everything's shiny, Cap'n.")));
58 ThreatEntryMetadata::MetadataEntry* e = 69 ThreatEntryMetadata::MetadataEntry* e =
59 m->mutable_threat_entry_metadata()->add_entries(); 70 m->mutable_threat_entry_metadata()->add_entries();
60 e->set_key("permission"); 71 e->set_key("permission");
61 e->set_value("NOTIFICATIONS"); 72 e->set_value("NOTIFICATIONS");
62 73
63 // Serialize. 74 // Serialize.
64 std::string res_data; 75 std::string res_data;
65 res.SerializeToString(&res_data); 76 res.SerializeToString(&res_data);
66 77
67 return res_data; 78 return res_data;
68 } 79 }
69 80
70 void SetTestClock(base::Time now, V4GetHashProtocolManager* pm) { 81 void SetTestClock(base::Time now, V4GetHashProtocolManager* pm) {
71 base::SimpleTestClock* clock = new base::SimpleTestClock(); 82 base::SimpleTestClock* clock = new base::SimpleTestClock();
72 clock->SetNow(now); 83 clock->SetNow(now);
73 pm->SetClockForTests(base::WrapUnique(clock)); 84 pm->SetClockForTests(base::WrapUnique(clock));
74 } 85 }
75 }; 86 };
76 87
77 void ValidateGetV4HashResults( 88 void ValidateGetV4HashResults(const std::vector<FullHashInfo>& expected_results,
78 const std::vector<SBFullHashResult>& expected_full_hashes, 89 const std::vector<FullHashInfo>& actual_results) {
79 const base::Time& expected_cache_expire, 90 EXPECT_EQ(expected_results.size(), actual_results.size());
80 const std::vector<SBFullHashResult>& full_hashes, 91 for (size_t i = 0; i < actual_results.size(); i++) {
81 const base::Time& cache_expire) { 92 EXPECT_TRUE(expected_results[i] == actual_results[i]);
82 EXPECT_EQ(expected_cache_expire, cache_expire);
83 ASSERT_EQ(expected_full_hashes.size(), full_hashes.size());
84
85 for (unsigned int i = 0; i < expected_full_hashes.size(); ++i) {
86 const SBFullHashResult& expected = expected_full_hashes[i];
87 const SBFullHashResult& actual = full_hashes[i];
88 EXPECT_TRUE(SBFullHashEqual(expected.hash, actual.hash));
89 EXPECT_EQ(expected.metadata, actual.metadata);
90 EXPECT_EQ(expected.cache_expire_after, actual.cache_expire_after);
91 } 93 }
92 } 94 }
93 95
94 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, 96 TEST_F(V4GetHashProtocolManagerTest, TestGetHashErrorHandlingNetwork) {
95 TestGetHashErrorHandlingNetwork) {
96 net::TestURLFetcherFactory factory; 97 net::TestURLFetcherFactory factory;
97 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 98 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
98 99
99 std::vector<SBPrefix> prefixes; 100 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
100 std::vector<SBFullHashResult> expected_full_hashes; 101 full_hash_to_store_and_hash_prefixes[FullHash("AFullHash")].push_back(
101 base::Time expected_cache_expire; 102 StoreAndHashPrefix(GetUrlSocEngId(), HashPrefix("AHashPrefix")));
102 103 std::vector<FullHashInfo> expected_results;
103 pm->GetFullHashesWithApis( 104 pm->GetFullHashes(full_hash_to_store_and_hash_prefixes,
104 prefixes, base::Bind(&ValidateGetV4HashResults, expected_full_hashes, 105 base::Bind(&ValidateGetV4HashResults, expected_results));
105 expected_cache_expire));
106 106
107 net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); 107 net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
108 DCHECK(fetcher); 108 DCHECK(fetcher);
109 // Failed request status should result in error. 109 // Failed request status should result in error.
110 fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::FAILED, 110 fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::FAILED,
111 net::ERR_CONNECTION_RESET)); 111 net::ERR_CONNECTION_RESET));
112 fetcher->set_response_code(200); 112 fetcher->set_response_code(200);
113 fetcher->SetResponseString(GetStockV4HashResponse()); 113 fetcher->SetResponseString(GetStockV4HashResponse());
114 fetcher->delegate()->OnURLFetchComplete(fetcher); 114 fetcher->delegate()->OnURLFetchComplete(fetcher);
115 115
116 // Should have recorded one error, but back off multiplier is unchanged. 116 // Should have recorded one error, but back off multiplier is unchanged.
117 EXPECT_EQ(1ul, pm->gethash_error_count_); 117 EXPECT_EQ(1ul, pm->gethash_error_count_);
118 EXPECT_EQ(1ul, pm->gethash_back_off_mult_); 118 EXPECT_EQ(1ul, pm->gethash_back_off_mult_);
119 } 119 }
120 120
121 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, 121 TEST_F(V4GetHashProtocolManagerTest, TestGetHashErrorHandlingResponseCode) {
122 TestGetHashErrorHandlingResponseCode) {
123 net::TestURLFetcherFactory factory; 122 net::TestURLFetcherFactory factory;
124 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 123 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
125 124
126 std::vector<SBPrefix> prefixes; 125 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
127 std::vector<SBFullHashResult> expected_full_hashes; 126 full_hash_to_store_and_hash_prefixes[FullHash("AFullHash")].push_back(
128 base::Time expected_cache_expire; 127 StoreAndHashPrefix(GetUrlSocEngId(), HashPrefix("AHashPrefix")));
129 128 std::vector<FullHashInfo> expected_results;
130 pm->GetFullHashesWithApis( 129 pm->GetFullHashes(full_hash_to_store_and_hash_prefixes,
131 prefixes, base::Bind(&ValidateGetV4HashResults, expected_full_hashes, 130 base::Bind(&ValidateGetV4HashResults, expected_results));
132 expected_cache_expire));
133 131
134 net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); 132 net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
135 DCHECK(fetcher); 133 DCHECK(fetcher);
136 fetcher->set_status(net::URLRequestStatus()); 134 fetcher->set_status(net::URLRequestStatus());
137 // Response code of anything other than 200 should result in error. 135 // Response code of anything other than 200 should result in error.
138 fetcher->set_response_code(204); 136 fetcher->set_response_code(204);
139 fetcher->SetResponseString(GetStockV4HashResponse()); 137 fetcher->SetResponseString(GetStockV4HashResponse());
140 fetcher->delegate()->OnURLFetchComplete(fetcher); 138 fetcher->delegate()->OnURLFetchComplete(fetcher);
141 139
142 // Should have recorded one error, but back off multiplier is unchanged. 140 // Should have recorded one error, but back off multiplier is unchanged.
143 EXPECT_EQ(1ul, pm->gethash_error_count_); 141 EXPECT_EQ(1ul, pm->gethash_error_count_);
144 EXPECT_EQ(1ul, pm->gethash_back_off_mult_); 142 EXPECT_EQ(1ul, pm->gethash_back_off_mult_);
145 } 143 }
146 144
147 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, TestGetHashErrorHandlingOK) { 145 TEST_F(V4GetHashProtocolManagerTest, TestGetHashErrorHandlingOK) {
148 net::TestURLFetcherFactory factory; 146 net::TestURLFetcherFactory factory;
149 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 147 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
150 148
151 base::Time now = base::Time::UnixEpoch(); 149 base::Time now = base::Time::UnixEpoch();
152 SetTestClock(now, pm.get()); 150 SetTestClock(now, pm.get());
153 151
154 std::vector<SBPrefix> prefixes; 152 HashPrefix prefix("Everything");
155 std::vector<SBFullHashResult> expected_full_hashes; 153 FullHash full_hash("Everything's shiny, Cap'n.");
156 SBFullHashResult hash_result; 154 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
157 hash_result.hash = SBFullHashForString("Everything's shiny, Cap'n."); 155 full_hash_to_store_and_hash_prefixes[full_hash].push_back(
158 hash_result.metadata.api_permissions.insert("NOTIFICATIONS"); 156 StoreAndHashPrefix(GetChromeUrlApiId(), prefix));
159 hash_result.cache_expire_after = now + base::TimeDelta::FromSeconds(300); 157 std::vector<FullHashInfo> expected_results;
160 expected_full_hashes.push_back(hash_result); 158 FullHashInfo fhi(full_hash, GetChromeUrlApiId(),
161 base::Time expected_cache_expire = now + base::TimeDelta::FromSeconds(600); 159 now + base::TimeDelta::FromSeconds(300));
160 fhi.metadata.api_permissions.insert("NOTIFICATIONS");
161 expected_results.push_back(fhi);
162 162
163 pm->GetFullHashesWithApis( 163 pm->GetFullHashes(full_hash_to_store_and_hash_prefixes,
164 prefixes, base::Bind(&ValidateGetV4HashResults, expected_full_hashes, 164 base::Bind(&ValidateGetV4HashResults, expected_results));
165 expected_cache_expire));
166 165
167 net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); 166 SetupFetcherToReturnStockOKResponse(factory);
168 DCHECK(fetcher);
169 fetcher->set_status(net::URLRequestStatus());
170 fetcher->set_response_code(200);
171 fetcher->SetResponseString(GetStockV4HashResponse());
172 fetcher->delegate()->OnURLFetchComplete(fetcher);
173 167
174 // No error, back off multiplier is unchanged. 168 // No error, back off multiplier is unchanged.
175 EXPECT_EQ(0ul, pm->gethash_error_count_); 169 EXPECT_EQ(0ul, pm->gethash_error_count_);
176 EXPECT_EQ(1ul, pm->gethash_back_off_mult_); 170 EXPECT_EQ(1ul, pm->gethash_back_off_mult_);
171
172 // Verify the state of the cache.
173 const FullHashCache* cache = pm->full_hash_cache_for_tests();
174 // Check the cache.
175 ASSERT_EQ(1u, cache->size());
176 EXPECT_EQ(1u, cache->count(prefix));
177 const CachedHashPrefixInfo& cached_result = cache->at(prefix);
178 EXPECT_EQ(cached_result.negative_ttl,
179 now + base::TimeDelta::FromSeconds(600));
180 ASSERT_EQ(1u, cached_result.full_hash_infos.size());
181 EXPECT_EQ(FullHash("Everything's shiny, Cap'n."),
182 cached_result.full_hash_infos[0].full_hash);
177 } 183 }
178 184
179 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, TestGetHashRequest) { 185 TEST_F(V4GetHashProtocolManagerTest,
186 TestResultsNotCachedForNegativeCacheDuration) {
187 net::TestURLFetcherFactory factory;
180 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 188 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
181 189
190 HashPrefix prefix("Everything");
191 std::vector<HashPrefix> prefixes_requested({prefix});
192 base::Time negative_cache_expire;
193 FullHash full_hash("Everything's shiny, Cap'n.");
194 std::vector<FullHashInfo> fhis;
195 fhis.emplace_back(full_hash, GetChromeUrlApiId(), base::Time::UnixEpoch());
196
197 pm->UpdateCache(prefixes_requested, fhis, negative_cache_expire);
198
199 // Verify the state of the cache.
200 const FullHashCache* cache = pm->full_hash_cache_for_tests();
201 // Check the cache.
202 EXPECT_EQ(0u, cache->size());
203 }
204
205 TEST_F(V4GetHashProtocolManagerTest, TestGetHashRequest) {
206 HashPrefix one = "hashone";
207 HashPrefix two = "hashtwo";
208 std::vector<HashPrefix> prefixes_to_request = {one, two};
209
182 FindFullHashesRequest req; 210 FindFullHashesRequest req;
183 ThreatInfo* info = req.mutable_threat_info(); 211 ThreatInfo* info = req.mutable_threat_info();
212 info->add_threat_types(MALWARE_THREAT);
184 info->add_threat_types(API_ABUSE); 213 info->add_threat_types(API_ABUSE);
214
215 info->add_platform_types(GetCurrentPlatformType());
185 info->add_platform_types(CHROME_PLATFORM); 216 info->add_platform_types(CHROME_PLATFORM);
217
186 info->add_threat_entry_types(URL); 218 info->add_threat_entry_types(URL);
187 219
188 SBPrefix one = 1u; 220 info->add_threat_entries()->set_hash(one);
189 SBPrefix two = 2u; 221 info->add_threat_entries()->set_hash(two);
190 SBPrefix three = 3u;
191 std::string hash(reinterpret_cast<const char*>(&one), sizeof(SBPrefix));
192 info->add_threat_entries()->set_hash(hash);
193 hash.clear();
194 hash.append(reinterpret_cast<const char*>(&two), sizeof(SBPrefix));
195 info->add_threat_entries()->set_hash(hash);
196 hash.clear();
197 hash.append(reinterpret_cast<const char*>(&three), sizeof(SBPrefix));
198 info->add_threat_entries()->set_hash(hash);
199 222
200 // Serialize and Base64 encode. 223 // Serialize and Base64 encode.
201 std::string req_data, req_base64; 224 std::string req_data, req_base64;
202 req.SerializeToString(&req_data); 225 req.SerializeToString(&req_data);
203 base::Base64Encode(req_data, &req_base64); 226 base::Base64Encode(req_data, &req_base64);
204 227
205 std::vector<PlatformType> platform; 228 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
206 platform.push_back(CHROME_PLATFORM); 229 EXPECT_EQ(req_base64, pm->GetHashRequest(prefixes_to_request));
207 std::vector<SBPrefix> prefixes;
208 prefixes.push_back(one);
209 prefixes.push_back(two);
210 prefixes.push_back(three);
211 EXPECT_EQ(req_base64, pm->GetHashRequest(prefixes, platform, API_ABUSE));
212 } 230 }
213 231
214 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, TestParseHashResponse) { 232 TEST_F(V4GetHashProtocolManagerTest, TestParseHashResponse) {
215 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 233 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
216 234
217 base::Time now = base::Time::UnixEpoch(); 235 base::Time now = base::Time::UnixEpoch();
218 SetTestClock(now, pm.get()); 236 SetTestClock(now, pm.get());
219 237
238 FullHash full_hash("Everything's shiny, Cap'n.");
220 FindFullHashesResponse res; 239 FindFullHashesResponse res;
221 res.mutable_negative_cache_duration()->set_seconds(600); 240 res.mutable_negative_cache_duration()->set_seconds(600);
222 res.mutable_minimum_wait_duration()->set_seconds(400); 241 res.mutable_minimum_wait_duration()->set_seconds(400);
223 ThreatMatch* m = res.add_matches(); 242 ThreatMatch* m = res.add_matches();
224 m->set_threat_type(API_ABUSE); 243 m->set_threat_type(API_ABUSE);
225 m->set_platform_type(CHROME_PLATFORM); 244 m->set_platform_type(CHROME_PLATFORM);
226 m->set_threat_entry_type(URL); 245 m->set_threat_entry_type(URL);
227 m->mutable_cache_duration()->set_seconds(300); 246 m->mutable_cache_duration()->set_seconds(300);
228 m->mutable_threat()->set_hash( 247 m->mutable_threat()->set_hash(full_hash);
229 SBFullHashToString(SBFullHashForString("Everything's shiny, Cap'n.")));
230 ThreatEntryMetadata::MetadataEntry* e = 248 ThreatEntryMetadata::MetadataEntry* e =
231 m->mutable_threat_entry_metadata()->add_entries(); 249 m->mutable_threat_entry_metadata()->add_entries();
232 e->set_key("permission"); 250 e->set_key("permission");
233 e->set_value("NOTIFICATIONS"); 251 e->set_value("NOTIFICATIONS");
234 252
235 // Serialize. 253 // Serialize.
236 std::string res_data; 254 std::string res_data;
237 res.SerializeToString(&res_data); 255 res.SerializeToString(&res_data);
238 256
239 std::vector<SBFullHashResult> full_hashes; 257 std::vector<FullHashInfo> full_hash_infos;
240 base::Time cache_expire; 258 base::Time cache_expire;
241 EXPECT_TRUE(pm->ParseHashResponse(res_data, &full_hashes, &cache_expire)); 259 EXPECT_TRUE(pm->ParseHashResponse(res_data, &full_hash_infos, &cache_expire));
242 260
243 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire); 261 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire);
244 EXPECT_EQ(1ul, full_hashes.size()); 262 ASSERT_EQ(1ul, full_hash_infos.size());
245 EXPECT_TRUE(SBFullHashEqual(SBFullHashForString("Everything's shiny, Cap'n."), 263 const FullHashInfo& fhi = full_hash_infos[0];
246 full_hashes[0].hash)); 264 EXPECT_EQ(full_hash, fhi.full_hash);
247 EXPECT_EQ(1ul, full_hashes[0].metadata.api_permissions.size()); 265 EXPECT_EQ(GetChromeUrlApiId(), fhi.list_id);
248 EXPECT_EQ(1ul, 266 EXPECT_EQ(1ul, fhi.metadata.api_permissions.size());
249 full_hashes[0].metadata.api_permissions.count("NOTIFICATIONS")); 267 EXPECT_EQ(1ul, fhi.metadata.api_permissions.count("NOTIFICATIONS"));
250 EXPECT_EQ(now + 268 EXPECT_EQ(now + base::TimeDelta::FromSeconds(300), fhi.positive_ttl);
251 base::TimeDelta::FromSeconds(300), full_hashes[0].cache_expire_after);
252 EXPECT_EQ(now + base::TimeDelta::FromSeconds(400), pm->next_gethash_time_); 269 EXPECT_EQ(now + base::TimeDelta::FromSeconds(400), pm->next_gethash_time_);
253 } 270 }
254 271
255 // Adds an entry with an ignored ThreatEntryType. 272 // Adds an entry with an ignored ThreatEntryType.
256 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, 273 TEST_F(V4GetHashProtocolManagerTest,
257 TestParseHashResponseWrongThreatEntryType) { 274 TestParseHashResponseWrongThreatEntryType) {
258 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 275 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
259 276
260 base::Time now = base::Time::UnixEpoch(); 277 base::Time now = base::Time::UnixEpoch();
261 SetTestClock(now, pm.get()); 278 SetTestClock(now, pm.get());
262 279
263 FindFullHashesResponse res; 280 FindFullHashesResponse res;
264 res.mutable_negative_cache_duration()->set_seconds(600); 281 res.mutable_negative_cache_duration()->set_seconds(600);
265 res.add_matches()->set_threat_entry_type(EXECUTABLE); 282 res.add_matches()->set_threat_entry_type(EXECUTABLE);
266 283
267 // Serialize. 284 // Serialize.
268 std::string res_data; 285 std::string res_data;
269 res.SerializeToString(&res_data); 286 res.SerializeToString(&res_data);
270 287
271 std::vector<SBFullHashResult> full_hashes; 288 std::vector<FullHashInfo> full_hash_infos;
272 base::Time cache_expire; 289 base::Time cache_expire;
273 EXPECT_FALSE(pm->ParseHashResponse(res_data, &full_hashes, &cache_expire)); 290 EXPECT_FALSE(
291 pm->ParseHashResponse(res_data, &full_hash_infos, &cache_expire));
274 292
275 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire); 293 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire);
276 // There should be no hash results. 294 // There should be no hash results.
277 EXPECT_EQ(0ul, full_hashes.size()); 295 EXPECT_EQ(0ul, full_hash_infos.size());
278 } 296 }
279 297
280 // Adds entries with a ThreatPatternType metadata. 298 // Adds entries with a ThreatPatternType metadata.
281 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, 299 TEST_F(V4GetHashProtocolManagerTest, TestParseHashThreatPatternType) {
282 TestParseHashThreatPatternType) {
283 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 300 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
284 301
285 base::Time now = base::Time::UnixEpoch(); 302 base::Time now = base::Time::UnixEpoch();
286 SetTestClock(now, pm.get()); 303 SetTestClock(now, pm.get());
287 304
288 // Test social engineering pattern type. 305 {
289 FindFullHashesResponse se_res; 306 // Test social engineering pattern type.
290 se_res.mutable_negative_cache_duration()->set_seconds(600); 307 FindFullHashesResponse se_res;
291 ThreatMatch* se = se_res.add_matches(); 308 se_res.mutable_negative_cache_duration()->set_seconds(600);
292 se->set_threat_type(SOCIAL_ENGINEERING_PUBLIC); 309 ThreatMatch* se = se_res.add_matches();
293 se->set_platform_type(CHROME_PLATFORM); 310 se->set_threat_type(SOCIAL_ENGINEERING_PUBLIC);
294 se->set_threat_entry_type(URL); 311 se->set_platform_type(CHROME_PLATFORM);
295 SBFullHash hash_string = SBFullHashForString("Everything's shiny, Cap'n."); 312 se->set_threat_entry_type(URL);
296 se->mutable_threat()->set_hash(SBFullHashToString(hash_string)); 313 FullHash full_hash("Everything's shiny, Cap'n.");
297 ThreatEntryMetadata::MetadataEntry* se_meta = 314 se->mutable_threat()->set_hash(full_hash);
298 se->mutable_threat_entry_metadata()->add_entries(); 315 ThreatEntryMetadata::MetadataEntry* se_meta =
299 se_meta->set_key("se_pattern_type"); 316 se->mutable_threat_entry_metadata()->add_entries();
300 se_meta->set_value("SOCIAL_ENGINEERING_LANDING"); 317 se_meta->set_key("se_pattern_type");
318 se_meta->set_value("SOCIAL_ENGINEERING_LANDING");
301 319
302 std::string se_data; 320 std::string se_data;
303 se_res.SerializeToString(&se_data); 321 se_res.SerializeToString(&se_data);
304 322
305 std::vector<SBFullHashResult> full_hashes; 323 std::vector<FullHashInfo> full_hash_infos;
306 base::Time cache_expire; 324 base::Time cache_expire;
307 EXPECT_TRUE(pm->ParseHashResponse(se_data, &full_hashes, &cache_expire)); 325 EXPECT_TRUE(
326 pm->ParseHashResponse(se_data, &full_hash_infos, &cache_expire));
327 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire);
308 328
309 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire); 329 ASSERT_EQ(1ul, full_hash_infos.size());
310 EXPECT_EQ(1ul, full_hashes.size()); 330 const FullHashInfo& fhi = full_hash_infos[0];
311 EXPECT_TRUE(SBFullHashEqual(hash_string, full_hashes[0].hash)); 331 EXPECT_EQ(full_hash, fhi.full_hash);
312 EXPECT_EQ(ThreatPatternType::SOCIAL_ENGINEERING_LANDING, 332 const UpdateListIdentifier list_id(CHROME_PLATFORM, URL,
313 full_hashes[0].metadata.threat_pattern_type); 333 SOCIAL_ENGINEERING_PUBLIC);
334 EXPECT_EQ(list_id, fhi.list_id);
335 EXPECT_EQ(ThreatPatternType::SOCIAL_ENGINEERING_LANDING,
336 fhi.metadata.threat_pattern_type);
337 }
314 338
315 // Test potentially harmful application pattern type. 339 {
316 FindFullHashesResponse pha_res; 340 // Test potentially harmful application pattern type.
317 pha_res.mutable_negative_cache_duration()->set_seconds(600); 341 FindFullHashesResponse pha_res;
318 ThreatMatch* pha = pha_res.add_matches(); 342 pha_res.mutable_negative_cache_duration()->set_seconds(600);
319 pha->set_threat_type(POTENTIALLY_HARMFUL_APPLICATION); 343 ThreatMatch* pha = pha_res.add_matches();
320 pha->set_threat_entry_type(URL); 344 pha->set_threat_type(POTENTIALLY_HARMFUL_APPLICATION);
321 pha->set_platform_type(CHROME_PLATFORM); 345 pha->set_threat_entry_type(URL);
322 hash_string = SBFullHashForString("Not to fret."); 346 pha->set_platform_type(CHROME_PLATFORM);
323 pha->mutable_threat()->set_hash(SBFullHashToString(hash_string)); 347 FullHash full_hash("Not to fret.");
324 ThreatEntryMetadata::MetadataEntry* pha_meta = 348 pha->mutable_threat()->set_hash(full_hash);
325 pha->mutable_threat_entry_metadata()->add_entries(); 349 ThreatEntryMetadata::MetadataEntry* pha_meta =
326 pha_meta->set_key("pha_pattern_type"); 350 pha->mutable_threat_entry_metadata()->add_entries();
327 pha_meta->set_value("LANDING"); 351 pha_meta->set_key("pha_pattern_type");
352 pha_meta->set_value("LANDING");
328 353
329 std::string pha_data; 354 std::string pha_data;
330 pha_res.SerializeToString(&pha_data); 355 pha_res.SerializeToString(&pha_data);
331 full_hashes.clear(); 356 std::vector<FullHashInfo> full_hash_infos;
332 EXPECT_TRUE(pm->ParseHashResponse(pha_data, &full_hashes, &cache_expire)); 357 base::Time cache_expire;
333 EXPECT_EQ(1ul, full_hashes.size()); 358 EXPECT_TRUE(
334 EXPECT_TRUE(SBFullHashEqual(hash_string, full_hashes[0].hash)); 359 pm->ParseHashResponse(pha_data, &full_hash_infos, &cache_expire));
335 EXPECT_EQ(ThreatPatternType::MALWARE_LANDING, 360 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire);
336 full_hashes[0].metadata.threat_pattern_type);
337 361
338 // Test invalid pattern type. 362 ASSERT_EQ(1ul, full_hash_infos.size());
339 FindFullHashesResponse invalid_res; 363 const FullHashInfo& fhi = full_hash_infos[0];
340 invalid_res.mutable_negative_cache_duration()->set_seconds(600); 364 EXPECT_EQ(full_hash, fhi.full_hash);
341 ThreatMatch* invalid = invalid_res.add_matches(); 365 const UpdateListIdentifier list_id(CHROME_PLATFORM, URL,
342 invalid->set_threat_type(POTENTIALLY_HARMFUL_APPLICATION); 366 POTENTIALLY_HARMFUL_APPLICATION);
343 invalid->set_threat_entry_type(URL); 367 EXPECT_EQ(list_id, fhi.list_id);
344 invalid->set_platform_type(CHROME_PLATFORM); 368 EXPECT_EQ(ThreatPatternType::MALWARE_LANDING,
345 invalid->mutable_threat()->set_hash(SBFullHashToString(hash_string)); 369 fhi.metadata.threat_pattern_type);
346 ThreatEntryMetadata::MetadataEntry* invalid_meta = 370 }
347 invalid->mutable_threat_entry_metadata()->add_entries();
348 invalid_meta->set_key("pha_pattern_type");
349 invalid_meta->set_value("INVALIDE_VALUE");
350 371
351 std::string invalid_data; 372 {
352 invalid_res.SerializeToString(&invalid_data); 373 // Test invalid pattern type.
353 full_hashes.clear(); 374 FullHash full_hash("Not to fret.");
354 EXPECT_FALSE( 375 FindFullHashesResponse invalid_res;
355 pm->ParseHashResponse(invalid_data, &full_hashes, &cache_expire)); 376 invalid_res.mutable_negative_cache_duration()->set_seconds(600);
356 EXPECT_EQ(0ul, full_hashes.size()); 377 ThreatMatch* invalid = invalid_res.add_matches();
378 invalid->set_threat_type(POTENTIALLY_HARMFUL_APPLICATION);
379 invalid->set_threat_entry_type(URL);
380 invalid->set_platform_type(CHROME_PLATFORM);
381 invalid->mutable_threat()->set_hash(full_hash);
382 ThreatEntryMetadata::MetadataEntry* invalid_meta =
383 invalid->mutable_threat_entry_metadata()->add_entries();
384 invalid_meta->set_key("pha_pattern_type");
385 invalid_meta->set_value("INVALIDE_VALUE");
386
387 std::string invalid_data;
388 invalid_res.SerializeToString(&invalid_data);
389 std::vector<FullHashInfo> full_hash_infos;
390 base::Time cache_expire;
391 EXPECT_FALSE(
392 pm->ParseHashResponse(invalid_data, &full_hash_infos, &cache_expire));
393 EXPECT_EQ(0ul, full_hash_infos.size());
394 }
357 } 395 }
358 396
359 // Adds metadata with a key value that is not "permission". 397 // Adds metadata with a key value that is not "permission".
360 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, 398 TEST_F(V4GetHashProtocolManagerTest,
361 TestParseHashResponseNonPermissionMetadata) { 399 TestParseHashResponseNonPermissionMetadata) {
362 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 400 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
363 401
364 base::Time now = base::Time::UnixEpoch(); 402 base::Time now = base::Time::UnixEpoch();
365 SetTestClock(now, pm.get()); 403 SetTestClock(now, pm.get());
366 404
367 FindFullHashesResponse res; 405 FindFullHashesResponse res;
368 res.mutable_negative_cache_duration()->set_seconds(600); 406 res.mutable_negative_cache_duration()->set_seconds(600);
369 ThreatMatch* m = res.add_matches(); 407 ThreatMatch* m = res.add_matches();
370 m->set_threat_type(API_ABUSE); 408 m->set_threat_type(API_ABUSE);
371 m->set_platform_type(CHROME_PLATFORM); 409 m->set_platform_type(CHROME_PLATFORM);
372 m->set_threat_entry_type(URL); 410 m->set_threat_entry_type(URL);
373 m->mutable_threat()->set_hash( 411 m->mutable_threat()->set_hash(FullHash("Not to fret."));
374 SBFullHashToString(SBFullHashForString("Not to fret.")));
375 ThreatEntryMetadata::MetadataEntry* e = 412 ThreatEntryMetadata::MetadataEntry* e =
376 m->mutable_threat_entry_metadata()->add_entries(); 413 m->mutable_threat_entry_metadata()->add_entries();
377 e->set_key("notpermission"); 414 e->set_key("notpermission");
378 e->set_value("NOTGEOLOCATION"); 415 e->set_value("NOTGEOLOCATION");
379 416
380 // Serialize. 417 // Serialize.
381 std::string res_data; 418 std::string res_data;
382 res.SerializeToString(&res_data); 419 res.SerializeToString(&res_data);
383 420
384 std::vector<SBFullHashResult> full_hashes; 421 std::vector<FullHashInfo> full_hash_infos;
385 base::Time cache_expire; 422 base::Time cache_expire;
386 EXPECT_FALSE(pm->ParseHashResponse(res_data, &full_hashes, &cache_expire)); 423 EXPECT_FALSE(
424 pm->ParseHashResponse(res_data, &full_hash_infos, &cache_expire));
387 425
388 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire); 426 EXPECT_EQ(now + base::TimeDelta::FromSeconds(600), cache_expire);
389 EXPECT_EQ(0ul, full_hashes.size()); 427 EXPECT_EQ(0ul, full_hash_infos.size());
390 } 428 }
391 429
392 TEST_F(SafeBrowsingV4GetHashProtocolManagerTest, 430 TEST_F(V4GetHashProtocolManagerTest,
393 TestParseHashResponseInconsistentThreatTypes) { 431 TestParseHashResponseInconsistentThreatTypes) {
394 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager()); 432 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
395 433
396 FindFullHashesResponse res; 434 FindFullHashesResponse res;
397 res.mutable_negative_cache_duration()->set_seconds(600); 435 res.mutable_negative_cache_duration()->set_seconds(600);
398 ThreatMatch* m1 = res.add_matches(); 436 ThreatMatch* m1 = res.add_matches();
399 m1->set_threat_type(API_ABUSE); 437 m1->set_threat_type(API_ABUSE);
400 m1->set_platform_type(CHROME_PLATFORM); 438 m1->set_platform_type(CHROME_PLATFORM);
401 m1->set_threat_entry_type(URL); 439 m1->set_threat_entry_type(URL);
402 m1->mutable_threat()->set_hash( 440 m1->mutable_threat()->set_hash(FullHash("Everything's shiny, Cap'n."));
403 SBFullHashToString(SBFullHashForString("Everything's shiny, Cap'n.")));
404 m1->mutable_threat_entry_metadata()->add_entries(); 441 m1->mutable_threat_entry_metadata()->add_entries();
405 ThreatMatch* m2 = res.add_matches(); 442 ThreatMatch* m2 = res.add_matches();
406 m2->set_threat_type(MALWARE_THREAT); 443 m2->set_threat_type(MALWARE_THREAT);
407 m2->set_threat_entry_type(URL); 444 m2->set_threat_entry_type(URL);
408 m2->mutable_threat()->set_hash( 445 m2->mutable_threat()->set_hash(FullHash("Not to fret."));
409 SBFullHashToString(SBFullHashForString("Not to fret.")));
410 446
411 // Serialize. 447 // Serialize.
412 std::string res_data; 448 std::string res_data;
413 res.SerializeToString(&res_data); 449 res.SerializeToString(&res_data);
414 450
415 std::vector<SBFullHashResult> full_hashes; 451 std::vector<FullHashInfo> full_hash_infos;
416 base::Time cache_expire; 452 base::Time cache_expire;
417 EXPECT_FALSE(pm->ParseHashResponse(res_data, &full_hashes, &cache_expire)); 453 EXPECT_FALSE(
454 pm->ParseHashResponse(res_data, &full_hash_infos, &cache_expire));
455 }
456
457 // Checks that results are looked up correctly in the cache.
458 TEST_F(V4GetHashProtocolManagerTest, GetCachedResults) {
459 base::Time now = base::Time::UnixEpoch();
460 FullHash full_hash("example.com/");
461 HashPrefix prefix("exam");
462 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
463 std::vector<HashPrefix> prefixes_to_request;
464 std::vector<FullHashInfo> cached_full_hash_infos;
465 full_hash_to_store_and_hash_prefixes[full_hash].push_back(
466 StoreAndHashPrefix(GetUrlMalwareId(), prefix));
467 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
468 FullHashCache* cache = pm->full_hash_cache_for_tests();
469
470 // Test with an empty cache. (Case: 2)
471 pm->GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, now,
472 &prefixes_to_request, &cached_full_hash_infos);
473 EXPECT_TRUE(cache->empty());
474 ASSERT_EQ(1ul, prefixes_to_request.size());
475 EXPECT_EQ(prefix, prefixes_to_request[0]);
476 EXPECT_TRUE(cached_full_hash_infos.empty());
477
478 // Prefix has a cache entry but full hash is not there. (Case: 1-b-i)
479 CachedHashPrefixInfo* entry = &(*cache)[prefix];
480 entry->negative_ttl = now + base::TimeDelta::FromMinutes(5);
481 prefixes_to_request.clear();
482 cached_full_hash_infos.clear();
483 pm->GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, now,
484 &prefixes_to_request, &cached_full_hash_infos);
485 EXPECT_TRUE(prefixes_to_request.empty());
486 EXPECT_TRUE(cached_full_hash_infos.empty());
487
488 // Expired negative cache entry. (Case: 1-b-ii)
489 entry->negative_ttl = now - base::TimeDelta::FromMinutes(5);
490 prefixes_to_request.clear();
491 cached_full_hash_infos.clear();
492 pm->GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, now,
493 &prefixes_to_request, &cached_full_hash_infos);
494 ASSERT_EQ(1ul, prefixes_to_request.size());
495 EXPECT_EQ(prefix, prefixes_to_request[0]);
496 EXPECT_TRUE(cached_full_hash_infos.empty());
497
498 // Now put unexpired full hash in the cache. (Case: 1-a-i)
499 FullHashInfo fhi(full_hash, GetUrlMalwareId(),
500 now + base::TimeDelta::FromMinutes(3));
501 entry->full_hash_infos.push_back(fhi);
502 prefixes_to_request.clear();
503 cached_full_hash_infos.clear();
504 pm->GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, now,
505 &prefixes_to_request, &cached_full_hash_infos);
506 EXPECT_TRUE(prefixes_to_request.empty());
507 ASSERT_EQ(1ul, cached_full_hash_infos.size());
508 EXPECT_EQ(full_hash, cached_full_hash_infos[0].full_hash);
509
510 // Expire the full hash in the cache. (Case: 1-a-ii)
511 entry->full_hash_infos[0].positive_ttl =
512 now - base::TimeDelta::FromMinutes(3);
513 prefixes_to_request.clear();
514 cached_full_hash_infos.clear();
515 pm->GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, now,
516 &prefixes_to_request, &cached_full_hash_infos);
517 ASSERT_EQ(1ul, prefixes_to_request.size());
518 EXPECT_EQ(prefix, prefixes_to_request[0]);
519 EXPECT_TRUE(cached_full_hash_infos.empty());
520 }
521
522 TEST_F(V4GetHashProtocolManagerTest, TestUpdatesAreMerged) {
523 // We'll add one of the requested FullHashInfo objects into the cache, and
524 // inject the other one as a response from the server. The result should
525 // include both FullHashInfo objects.
526
527 net::TestURLFetcherFactory factory;
528 std::unique_ptr<V4GetHashProtocolManager> pm(CreateProtocolManager());
529 HashPrefix prefix_1("exam");
530 FullHash full_hash_1("example.com/test");
531 HashPrefix prefix_2("Everything");
532 FullHash full_hash_2("Everything's shiny, Cap'n.");
533
534 base::Time now = Time::Now();
535 SetTestClock(now, pm.get());
536
537 FullHashCache* cache = pm->full_hash_cache_for_tests();
538 CachedHashPrefixInfo* entry = &(*cache)[prefix_1];
539 entry->negative_ttl = now + base::TimeDelta::FromMinutes(100);
540 // Put one unexpired full hash in the cache for a store we'll look in.
541 entry->full_hash_infos.emplace_back(full_hash_1, GetUrlMalwareId(),
542 now + base::TimeDelta::FromSeconds(200));
543 // Put one unexpired full hash in the cache for a store we'll not look in.
544 entry->full_hash_infos.emplace_back(full_hash_1, GetUrlSocEngId(),
545 now + base::TimeDelta::FromSeconds(200));
546
547 // Request full hash information from two stores.
548 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
549 full_hash_to_store_and_hash_prefixes[full_hash_1].push_back(
550 StoreAndHashPrefix(GetUrlMalwareId(), prefix_1));
551 full_hash_to_store_and_hash_prefixes[full_hash_2].push_back(
552 StoreAndHashPrefix(GetChromeUrlApiId(), prefix_2));
553
554 // Expect full hash information from both stores.
555 std::vector<FullHashInfo> expected_results;
556 expected_results.emplace_back(full_hash_1, GetUrlMalwareId(),
557 now + base::TimeDelta::FromSeconds(200));
558 expected_results.emplace_back(full_hash_2, GetChromeUrlApiId(),
559 now + base::TimeDelta::FromSeconds(300));
560 expected_results[1].metadata.api_permissions.insert("NOTIFICATIONS");
561
562 pm->GetFullHashes(full_hash_to_store_and_hash_prefixes,
563 base::Bind(&ValidateGetV4HashResults, expected_results));
564
565 SetupFetcherToReturnStockOKResponse(factory);
566
567 // No error, back off multiplier is unchanged.
568 EXPECT_EQ(0ul, pm->gethash_error_count_);
569 EXPECT_EQ(1ul, pm->gethash_back_off_mult_);
570
571 // Verify the state of the cache.
572 ASSERT_EQ(2u, cache->size());
573 const CachedHashPrefixInfo& cached_result_1 = cache->at(prefix_1);
574 EXPECT_EQ(cached_result_1.negative_ttl,
575 now + base::TimeDelta::FromMinutes(100));
576 ASSERT_EQ(2u, cached_result_1.full_hash_infos.size());
577 EXPECT_EQ(full_hash_1, cached_result_1.full_hash_infos[0].full_hash);
578 EXPECT_EQ(GetUrlMalwareId(), cached_result_1.full_hash_infos[0].list_id);
579
580 const CachedHashPrefixInfo& cached_result_2 = cache->at(prefix_2);
581 EXPECT_EQ(cached_result_2.negative_ttl,
582 now + base::TimeDelta::FromSeconds(600));
583 ASSERT_EQ(1u, cached_result_2.full_hash_infos.size());
584 EXPECT_EQ(full_hash_2, cached_result_2.full_hash_infos[0].full_hash);
585 EXPECT_EQ(GetChromeUrlApiId(), cached_result_2.full_hash_infos[0].list_id);
418 } 586 }
419 587
420 } // namespace safe_browsing 588 } // namespace safe_browsing
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698