client_side_detection_service_unittest.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <map> 6#include <queue> 7#include <string> 8 9#include "base/callback.h" 10#include "base/file_path.h" 11#include "base/file_util.h" 12#include "base/file_util_proxy.h" 13#include "base/logging.h" 14#include "base/message_loop.h" 15#include "base/platform_file.h" 16#include "base/scoped_ptr.h" 17#include "base/scoped_temp_dir.h" 18#include "base/task.h" 19#include "base/time.h" 20#include "chrome/browser/safe_browsing/client_side_detection_service.h" 21#include "chrome/browser/safe_browsing/csd.pb.h" 22#include "chrome/common/net/test_url_fetcher_factory.h" 23#include "chrome/common/net/url_fetcher.h" 24#include "content/browser/browser_thread.h" 25#include "googleurl/src/gurl.h" 26#include "net/url_request/url_request_status.h" 27#include "testing/gtest/include/gtest/gtest.h" 28 29namespace safe_browsing { 30 31class ClientSideDetectionServiceTest : public testing::Test { 32 protected: 33 virtual void SetUp() { 34 file_thread_.reset(new BrowserThread(BrowserThread::FILE, &msg_loop_)); 35 36 factory_.reset(new FakeURLFetcherFactory()); 37 URLFetcher::set_factory(factory_.get()); 38 39 browser_thread_.reset(new BrowserThread(BrowserThread::UI, &msg_loop_)); 40 } 41 42 virtual void TearDown() { 43 msg_loop_.RunAllPending(); 44 csd_service_.reset(); 45 URLFetcher::set_factory(NULL); 46 file_thread_.reset(); 47 browser_thread_.reset(); 48 } 49 50 base::PlatformFile GetModelFile() { 51 model_file_ = base::kInvalidPlatformFileValue; 52 csd_service_->GetModelFile(NewCallback( 53 this, &ClientSideDetectionServiceTest::GetModelFileDone)); 54 // This method will block this thread until GetModelFileDone is called. 55 msg_loop_.Run(); 56 return model_file_; 57 } 58 59 std::string ReadModelFile(base::PlatformFile model_file) { 60 char buf[1024]; 61 int n = base::ReadPlatformFile(model_file, 0, buf, 1024); 62 EXPECT_LE(0, n); 63 return (n < 0) ? "" : std::string(buf, n); 64 } 65 66 bool SendClientReportPhishingRequest(const GURL& phishing_url, 67 double score) { 68 csd_service_->SendClientReportPhishingRequest( 69 phishing_url, 70 score, 71 NewCallback(this, &ClientSideDetectionServiceTest::SendRequestDone)); 72 phishing_url_ = phishing_url; 73 msg_loop_.Run(); // Waits until callback is called. 74 return is_phishing_; 75 } 76 77 void SetModelFetchResponse(std::string response_data, bool success) { 78 factory_->SetFakeResponse(ClientSideDetectionService::kClientModelUrl, 79 response_data, success); 80 } 81 82 void SetClientReportPhishingResponse(std::string response_data, 83 bool success) { 84 factory_->SetFakeResponse( 85 ClientSideDetectionService::kClientReportPhishingUrl, 86 response_data, success); 87 } 88 89 int GetNumReports() { 90 return csd_service_->GetNumReports(); 91 } 92 93 std::queue<base::Time>& GetPhishingReportTimes() { 94 return csd_service_->phishing_report_times_; 95 } 96 97 void SetCache(const GURL& gurl, bool is_phishing, base::Time time) { 98 csd_service_->cache_[gurl] = 99 make_linked_ptr(new ClientSideDetectionService::CacheState(is_phishing, 100 time)); 101 } 102 103 void TestCache() { 104 ClientSideDetectionService::PhishingCache& cache = csd_service_->cache_; 105 base::Time now = base::Time::Now(); 106 base::Time time = now - ClientSideDetectionService::kNegativeCacheInterval + 107 base::TimeDelta::FromMinutes(5); 108 cache[GURL("http://first.url.com/")] = 109 make_linked_ptr(new ClientSideDetectionService::CacheState(false, 110 time)); 111 112 time = now - ClientSideDetectionService::kNegativeCacheInterval - 113 base::TimeDelta::FromHours(1); 114 cache[GURL("http://second.url.com/")] = 115 make_linked_ptr(new ClientSideDetectionService::CacheState(false, 116 time)); 117 118 time = now - ClientSideDetectionService::kPositiveCacheInterval - 119 base::TimeDelta::FromMinutes(5); 120 cache[GURL("http://third.url.com/")] = 121 make_linked_ptr(new ClientSideDetectionService::CacheState(true, time)); 122 123 time = now - ClientSideDetectionService::kPositiveCacheInterval + 124 base::TimeDelta::FromMinutes(5); 125 cache[GURL("http://fourth.url.com/")] = 126 make_linked_ptr(new ClientSideDetectionService::CacheState(true, time)); 127 128 csd_service_->UpdateCache(); 129 130 // 3 elements should be in the cache, the first, third, and fourth. 131 EXPECT_EQ(3U, cache.size()); 132 EXPECT_TRUE(cache.find(GURL("http://first.url.com/")) != cache.end()); 133 EXPECT_TRUE(cache.find(GURL("http://third.url.com/")) != cache.end()); 134 EXPECT_TRUE(cache.find(GURL("http://fourth.url.com/")) != cache.end()); 135 136 // While 3 elements remain, only the first and the fourth are actually 137 // valid. 138 bool is_phishing; 139 EXPECT_TRUE(csd_service_->GetCachedResult(GURL("http://first.url.com"), 140 &is_phishing)); 141 EXPECT_FALSE(is_phishing); 142 EXPECT_FALSE(csd_service_->GetCachedResult(GURL("http://third.url.com"), 143 &is_phishing)); 144 EXPECT_TRUE(csd_service_->GetCachedResult(GURL("http://fourth.url.com"), 145 &is_phishing)); 146 EXPECT_TRUE(is_phishing); 147 } 148 149 protected: 150 scoped_ptr<ClientSideDetectionService> csd_service_; 151 scoped_ptr<FakeURLFetcherFactory> factory_; 152 MessageLoop msg_loop_; 153 154 private: 155 void GetModelFileDone(base::PlatformFile model_file) { 156 model_file_ = model_file; 157 msg_loop_.Quit(); 158 } 159 160 void SendRequestDone(GURL phishing_url, bool is_phishing) { 161 ASSERT_EQ(phishing_url, phishing_url_); 162 is_phishing_ = is_phishing; 163 msg_loop_.Quit(); 164 } 165 166 scoped_ptr<BrowserThread> browser_thread_; 167 base::PlatformFile model_file_; 168 scoped_ptr<BrowserThread> file_thread_; 169 170 GURL phishing_url_; 171 bool is_phishing_; 172}; 173 174TEST_F(ClientSideDetectionServiceTest, TestFetchingModel) { 175 ScopedTempDir tmp_dir; 176 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); 177 FilePath model_path = tmp_dir.path().AppendASCII("model"); 178 179 // The first time we create the csd service the model file does not exist so 180 // we expect there to be a fetch. 181 SetModelFetchResponse("BOGUS MODEL", true); 182 csd_service_.reset(ClientSideDetectionService::Create(model_path, NULL)); 183 base::PlatformFile model_file = GetModelFile(); 184 EXPECT_NE(model_file, base::kInvalidPlatformFileValue); 185 EXPECT_EQ(ReadModelFile(model_file), "BOGUS MODEL"); 186 187 // If you call GetModelFile() multiple times you always get the same platform 188 // file back. We don't re-open the file. 189 EXPECT_EQ(GetModelFile(), model_file); 190 191 // The second time the model already exists on disk. In this case there 192 // should not be any fetch. To ensure that we clear the factory. 193 factory_->ClearFakeReponses(); 194 csd_service_.reset(ClientSideDetectionService::Create(model_path, NULL)); 195 model_file = GetModelFile(); 196 EXPECT_NE(model_file, base::kInvalidPlatformFileValue); 197 EXPECT_EQ(ReadModelFile(model_file), "BOGUS MODEL"); 198 199 // If the model does not exist and the fetch fails we should get an error. 200 model_path = tmp_dir.path().AppendASCII("another_model"); 201 SetModelFetchResponse("", false /* success */); 202 csd_service_.reset(ClientSideDetectionService::Create(model_path, NULL)); 203 EXPECT_EQ(GetModelFile(), base::kInvalidPlatformFileValue); 204} 205 206TEST_F(ClientSideDetectionServiceTest, ServiceObjectDeletedBeforeCallbackDone) { 207 SetModelFetchResponse("bogus model", true /* success */); 208 ScopedTempDir tmp_dir; 209 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); 210 csd_service_.reset(ClientSideDetectionService::Create( 211 tmp_dir.path().AppendASCII("model"), NULL)); 212 EXPECT_TRUE(csd_service_.get() != NULL); 213 // We delete the client-side detection service class even though the callbacks 214 // haven't run yet. 215 csd_service_.reset(); 216 // Waiting for the callbacks to run should not crash even if the service 217 // object is gone. 218 msg_loop_.RunAllPending(); 219} 220 221TEST_F(ClientSideDetectionServiceTest, SendClientReportPhishingRequest) { 222 SetModelFetchResponse("bogus model", true /* success */); 223 ScopedTempDir tmp_dir; 224 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); 225 csd_service_.reset(ClientSideDetectionService::Create( 226 tmp_dir.path().AppendASCII("model"), NULL)); 227 228 GURL url("http://a.com/"); 229 double score = 0.4; // Some random client score. 230 231 base::Time before = base::Time::Now(); 232 233 // Invalid response body from the server. 234 SetClientReportPhishingResponse("invalid proto response", true /* success */); 235 EXPECT_FALSE(SendClientReportPhishingRequest(url, score)); 236 237 // Normal behavior. 238 ClientPhishingResponse response; 239 response.set_phishy(true); 240 SetClientReportPhishingResponse(response.SerializeAsString(), 241 true /* success */); 242 EXPECT_TRUE(SendClientReportPhishingRequest(url, score)); 243 244 // Caching causes this to still count as phishy. 245 response.set_phishy(false); 246 SetClientReportPhishingResponse(response.SerializeAsString(), 247 true /* success */); 248 EXPECT_TRUE(SendClientReportPhishingRequest(url, score)); 249 250 // This request will fail and should not be cached. 251 GURL second_url("http://b.com/"); 252 response.set_phishy(false); 253 SetClientReportPhishingResponse(response.SerializeAsString(), 254 false /* success*/); 255 EXPECT_FALSE(SendClientReportPhishingRequest(second_url, score)); 256 257 // Verify that the previous request was not cached. 258 response.set_phishy(true); 259 SetClientReportPhishingResponse(response.SerializeAsString(), 260 true /* success */); 261 EXPECT_TRUE(SendClientReportPhishingRequest(second_url, score)); 262 263 // This request is blocked because it's not in the cache and we have more 264 // than 3 requests. 265 GURL third_url("http://c.com"); 266 response.set_phishy(true); 267 SetClientReportPhishingResponse(response.SerializeAsString(), 268 true /* success */); 269 EXPECT_FALSE(SendClientReportPhishingRequest(third_url, score)); 270 271 // Verify that caching still works even when new requests are blocked. 272 response.set_phishy(true); 273 SetClientReportPhishingResponse(response.SerializeAsString(), 274 true /* success */); 275 EXPECT_TRUE(SendClientReportPhishingRequest(url, score)); 276 277 // Verify that we allow cache refreshing even when requests are blocked. 278 base::Time cache_time = base::Time::Now() - base::TimeDelta::FromHours(1); 279 SetCache(second_url, true, cache_time); 280 281 // Even though this element is in the cache, it's not currently valid so 282 // we make request and return that value instead. 283 response.set_phishy(false); 284 SetClientReportPhishingResponse(response.SerializeAsString(), 285 true /* success */); 286 EXPECT_FALSE(SendClientReportPhishingRequest(second_url, score)); 287 288 base::Time after = base::Time::Now(); 289 290 // Check that we have recorded 5 requests, all within the correct time range. 291 // The blocked request and the cached requests should not be present. 292 std::queue<base::Time>& report_times = GetPhishingReportTimes(); 293 EXPECT_EQ(5U, report_times.size()); 294 while (!report_times.empty()) { 295 base::Time time = report_times.back(); 296 report_times.pop(); 297 EXPECT_LE(before, time); 298 EXPECT_GE(after, time); 299 } 300} 301 302TEST_F(ClientSideDetectionServiceTest, GetNumReportTest) { 303 SetModelFetchResponse("bogus model", true /* success */); 304 ScopedTempDir tmp_dir; 305 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); 306 csd_service_.reset(ClientSideDetectionService::Create( 307 tmp_dir.path().AppendASCII("model"), NULL)); 308 309 std::queue<base::Time>& report_times = GetPhishingReportTimes(); 310 base::Time now = base::Time::Now(); 311 base::TimeDelta twenty_five_hours = base::TimeDelta::FromHours(25); 312 report_times.push(now - twenty_five_hours); 313 report_times.push(now - twenty_five_hours); 314 report_times.push(now); 315 report_times.push(now); 316 317 EXPECT_EQ(2, GetNumReports()); 318} 319 320TEST_F(ClientSideDetectionServiceTest, CacheTest) { 321 SetModelFetchResponse("bogus model", true /* success */); 322 ScopedTempDir tmp_dir; 323 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); 324 csd_service_.reset(ClientSideDetectionService::Create( 325 tmp_dir.path().AppendASCII("model"), NULL)); 326 327 TestCache(); 328} 329 330TEST_F(ClientSideDetectionServiceTest, IsPrivateIPAddress) { 331 SetModelFetchResponse("bogus model", true /* success */); 332 ScopedTempDir tmp_dir; 333 ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); 334 csd_service_.reset(ClientSideDetectionService::Create( 335 tmp_dir.path().AppendASCII("model"), NULL)); 336 337 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("10.1.2.3")); 338 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("127.0.0.1")); 339 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("172.24.3.4")); 340 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("192.168.1.1")); 341 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fc00::")); 342 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fec0::")); 343 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fec0:1:2::3")); 344 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("::1")); 345 346 EXPECT_FALSE(csd_service_->IsPrivateIPAddress("1.2.3.4")); 347 EXPECT_FALSE(csd_service_->IsPrivateIPAddress("200.1.1.1")); 348 EXPECT_FALSE(csd_service_->IsPrivateIPAddress("2001:0db8:ac10:fe01::")); 349 350 // If the address can't be parsed, the default is true. 351 EXPECT_TRUE(csd_service_->IsPrivateIPAddress("blah")); 352} 353 354} // namespace safe_browsing 355