1// Copyright (c) 2011 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/memory/scoped_ptr.h"
15#include "base/memory/scoped_temp_dir.h"
16#include "base/message_loop.h"
17#include "base/platform_file.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/common/net/test_url_fetcher_factory.h"
22#include "chrome/common/net/url_fetcher.h"
23#include "chrome/common/safe_browsing/csd.pb.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                                       float score) {
68    ClientPhishingRequest* request = new ClientPhishingRequest();
69    request->set_url(phishing_url.spec());
70    request->set_client_score(score);
71    request->set_is_phishing(true);  // client thinks the URL is phishing.
72    csd_service_->SendClientReportPhishingRequest(
73        request,
74        NewCallback(this, &ClientSideDetectionServiceTest::SendRequestDone));
75    phishing_url_ = phishing_url;
76    msg_loop_.Run();  // Waits until callback is called.
77    return is_phishing_;
78  }
79
80  void SetModelFetchResponse(std::string response_data, bool success) {
81    factory_->SetFakeResponse(ClientSideDetectionService::kClientModelUrl,
82                              response_data, success);
83  }
84
85  void SetClientReportPhishingResponse(std::string response_data,
86                                       bool success) {
87    factory_->SetFakeResponse(
88        ClientSideDetectionService::kClientReportPhishingUrl,
89        response_data, success);
90  }
91
92  int GetNumReports() {
93    return csd_service_->GetNumReports();
94  }
95
96  std::queue<base::Time>& GetPhishingReportTimes() {
97    return csd_service_->phishing_report_times_;
98  }
99
100  void SetCache(const GURL& gurl, bool is_phishing, base::Time time) {
101    csd_service_->cache_[gurl] =
102        make_linked_ptr(new ClientSideDetectionService::CacheState(is_phishing,
103                                                                   time));
104  }
105
106  void TestCache() {
107    ClientSideDetectionService::PhishingCache& cache = csd_service_->cache_;
108    base::Time now = base::Time::Now();
109    base::Time time = now - ClientSideDetectionService::kNegativeCacheInterval +
110        base::TimeDelta::FromMinutes(5);
111    cache[GURL("http://first.url.com/")] =
112        make_linked_ptr(new ClientSideDetectionService::CacheState(false,
113                                                                   time));
114
115    time = now - ClientSideDetectionService::kNegativeCacheInterval -
116        base::TimeDelta::FromHours(1);
117    cache[GURL("http://second.url.com/")] =
118        make_linked_ptr(new ClientSideDetectionService::CacheState(false,
119                                                                   time));
120
121    time = now - ClientSideDetectionService::kPositiveCacheInterval -
122        base::TimeDelta::FromMinutes(5);
123    cache[GURL("http://third.url.com/")] =
124        make_linked_ptr(new ClientSideDetectionService::CacheState(true, time));
125
126    time = now - ClientSideDetectionService::kPositiveCacheInterval +
127        base::TimeDelta::FromMinutes(5);
128    cache[GURL("http://fourth.url.com/")] =
129        make_linked_ptr(new ClientSideDetectionService::CacheState(true, time));
130
131    csd_service_->UpdateCache();
132
133    // 3 elements should be in the cache, the first, third, and fourth.
134    EXPECT_EQ(3U, cache.size());
135    EXPECT_TRUE(cache.find(GURL("http://first.url.com/")) != cache.end());
136    EXPECT_TRUE(cache.find(GURL("http://third.url.com/")) != cache.end());
137    EXPECT_TRUE(cache.find(GURL("http://fourth.url.com/")) != cache.end());
138
139    // While 3 elements remain, only the first and the fourth are actually
140    // valid.
141    bool is_phishing;
142    EXPECT_TRUE(csd_service_->GetValidCachedResult(
143        GURL("http://first.url.com"), &is_phishing));
144    EXPECT_FALSE(is_phishing);
145    EXPECT_FALSE(csd_service_->GetValidCachedResult(
146        GURL("http://third.url.com"), &is_phishing));
147    EXPECT_TRUE(csd_service_->GetValidCachedResult(
148        GURL("http://fourth.url.com"), &is_phishing));
149    EXPECT_TRUE(is_phishing);
150  }
151
152 protected:
153  scoped_ptr<ClientSideDetectionService> csd_service_;
154  scoped_ptr<FakeURLFetcherFactory> factory_;
155  MessageLoop msg_loop_;
156
157 private:
158  void GetModelFileDone(base::PlatformFile model_file) {
159    model_file_ = model_file;
160    msg_loop_.Quit();
161  }
162
163  void SendRequestDone(GURL phishing_url, bool is_phishing) {
164    ASSERT_EQ(phishing_url, phishing_url_);
165    is_phishing_ = is_phishing;
166    msg_loop_.Quit();
167  }
168
169  scoped_ptr<BrowserThread> browser_thread_;
170  base::PlatformFile model_file_;
171  scoped_ptr<BrowserThread> file_thread_;
172
173  GURL phishing_url_;
174  bool is_phishing_;
175};
176
177TEST_F(ClientSideDetectionServiceTest, TestFetchingModel) {
178  ScopedTempDir tmp_dir;
179  ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
180  FilePath model_path = tmp_dir.path().AppendASCII("model");
181
182  // The first time we create the csd service the model file does not exist so
183  // we expect there to be a fetch.
184  SetModelFetchResponse("BOGUS MODEL", true);
185  csd_service_.reset(ClientSideDetectionService::Create(model_path, NULL));
186  base::PlatformFile model_file = GetModelFile();
187  EXPECT_NE(model_file, base::kInvalidPlatformFileValue);
188  EXPECT_EQ(ReadModelFile(model_file), "BOGUS MODEL");
189
190  // If you call GetModelFile() multiple times you always get the same platform
191  // file back.  We don't re-open the file.
192  EXPECT_EQ(GetModelFile(), model_file);
193
194  // The second time the model already exists on disk.  In this case there
195  // should not be any fetch.  To ensure that we clear the factory.
196  factory_->ClearFakeReponses();
197  csd_service_.reset(ClientSideDetectionService::Create(model_path, NULL));
198  model_file = GetModelFile();
199  EXPECT_NE(model_file, base::kInvalidPlatformFileValue);
200  EXPECT_EQ(ReadModelFile(model_file), "BOGUS MODEL");
201
202  // If the model does not exist and the fetch fails we should get an error.
203  model_path = tmp_dir.path().AppendASCII("another_model");
204  SetModelFetchResponse("", false /* success */);
205  csd_service_.reset(ClientSideDetectionService::Create(model_path, NULL));
206  EXPECT_EQ(GetModelFile(), base::kInvalidPlatformFileValue);
207}
208
209TEST_F(ClientSideDetectionServiceTest, ServiceObjectDeletedBeforeCallbackDone) {
210  SetModelFetchResponse("bogus model", true /* success */);
211  ScopedTempDir tmp_dir;
212  ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
213  csd_service_.reset(ClientSideDetectionService::Create(
214      tmp_dir.path().AppendASCII("model"), NULL));
215  EXPECT_TRUE(csd_service_.get() != NULL);
216  // We delete the client-side detection service class even though the callbacks
217  // haven't run yet.
218  csd_service_.reset();
219  // Waiting for the callbacks to run should not crash even if the service
220  // object is gone.
221  msg_loop_.RunAllPending();
222}
223
224TEST_F(ClientSideDetectionServiceTest, SendClientReportPhishingRequest) {
225  SetModelFetchResponse("bogus model", true /* success */);
226  ScopedTempDir tmp_dir;
227  ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
228  csd_service_.reset(ClientSideDetectionService::Create(
229      tmp_dir.path().AppendASCII("model"), NULL));
230
231  GURL url("http://a.com/");
232  float score = 0.4f;  // Some random client score.
233
234  base::Time before = base::Time::Now();
235
236  // Invalid response body from the server.
237  SetClientReportPhishingResponse("invalid proto response", true /* success */);
238  EXPECT_FALSE(SendClientReportPhishingRequest(url, score));
239
240  // Normal behavior.
241  ClientPhishingResponse response;
242  response.set_phishy(true);
243  SetClientReportPhishingResponse(response.SerializeAsString(),
244                                  true /* success */);
245  EXPECT_TRUE(SendClientReportPhishingRequest(url, score));
246
247  // This request will fail
248  GURL second_url("http://b.com/");
249  response.set_phishy(false);
250  SetClientReportPhishingResponse(response.SerializeAsString(),
251                                  false /* success*/);
252  EXPECT_FALSE(SendClientReportPhishingRequest(second_url, score));
253
254  base::Time after = base::Time::Now();
255
256  // Check that we have recorded all 3 requests within the correct time range.
257  std::queue<base::Time>& report_times = GetPhishingReportTimes();
258  EXPECT_EQ(3U, report_times.size());
259  while (!report_times.empty()) {
260    base::Time time = report_times.back();
261    report_times.pop();
262    EXPECT_LE(before, time);
263    EXPECT_GE(after, time);
264  }
265
266  // Only the first url should be in the cache.
267  bool is_phishing;
268  EXPECT_TRUE(csd_service_->IsInCache(url));
269  EXPECT_TRUE(csd_service_->GetValidCachedResult(url, &is_phishing));
270  EXPECT_TRUE(is_phishing);
271  EXPECT_FALSE(csd_service_->IsInCache(second_url));
272}
273
274TEST_F(ClientSideDetectionServiceTest, GetNumReportTest) {
275  SetModelFetchResponse("bogus model", true /* success */);
276  ScopedTempDir tmp_dir;
277  ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
278  csd_service_.reset(ClientSideDetectionService::Create(
279      tmp_dir.path().AppendASCII("model"), NULL));
280
281  std::queue<base::Time>& report_times = GetPhishingReportTimes();
282  base::Time now = base::Time::Now();
283  base::TimeDelta twenty_five_hours = base::TimeDelta::FromHours(25);
284  report_times.push(now - twenty_five_hours);
285  report_times.push(now - twenty_five_hours);
286  report_times.push(now);
287  report_times.push(now);
288
289  EXPECT_EQ(2, GetNumReports());
290}
291
292TEST_F(ClientSideDetectionServiceTest, CacheTest) {
293  SetModelFetchResponse("bogus model", true /* success */);
294  ScopedTempDir tmp_dir;
295  ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
296  csd_service_.reset(ClientSideDetectionService::Create(
297      tmp_dir.path().AppendASCII("model"), NULL));
298
299  TestCache();
300}
301
302TEST_F(ClientSideDetectionServiceTest, IsPrivateIPAddress) {
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  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("10.1.2.3"));
310  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("127.0.0.1"));
311  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("172.24.3.4"));
312  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("192.168.1.1"));
313  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fc00::"));
314  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fec0::"));
315  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fec0:1:2::3"));
316  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("::1"));
317
318  EXPECT_FALSE(csd_service_->IsPrivateIPAddress("1.2.3.4"));
319  EXPECT_FALSE(csd_service_->IsPrivateIPAddress("200.1.1.1"));
320  EXPECT_FALSE(csd_service_->IsPrivateIPAddress("2001:0db8:ac10:fe01::"));
321
322  // If the address can't be parsed, the default is true.
323  EXPECT_TRUE(csd_service_->IsPrivateIPAddress("blah"));
324}
325
326}  // namespace safe_browsing
327