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