1// Copyright 2013 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 "chrome/browser/safe_browsing/download_feedback_service.h"
6
7#include <vector>
8
9#include "base/files/file_util.h"
10#include "base/files/scoped_temp_dir.h"
11#include "base/run_loop.h"
12#include "base/strings/string_number_conversions.h"
13#include "chrome/browser/safe_browsing/download_feedback.h"
14#include "content/public/browser/browser_thread.h"
15#include "content/public/test/mock_download_item.h"
16#include "content/public/test/test_browser_thread_bundle.h"
17#include "net/url_request/url_request_test_util.h"
18#include "testing/gmock/include/gmock/gmock.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21using ::testing::_;
22using ::testing::Return;
23using ::testing::SaveArg;
24
25namespace safe_browsing {
26
27namespace {
28
29class FakeDownloadFeedback : public DownloadFeedback {
30 public:
31  FakeDownloadFeedback(net::URLRequestContextGetter* request_context_getter,
32                       base::TaskRunner* file_task_runner,
33                       const base::FilePath& file_path,
34                       const std::string& ping_request,
35                       const std::string& ping_response,
36                       base::Closure deletion_callback)
37      : ping_request_(ping_request),
38        ping_response_(ping_response),
39        deletion_callback_(deletion_callback),
40        start_called_(false) {
41  }
42
43  virtual ~FakeDownloadFeedback() {
44    deletion_callback_.Run();
45  }
46
47  virtual void Start(const base::Closure& finish_callback) OVERRIDE {
48    start_called_ = true;
49    finish_callback_ = finish_callback;
50  }
51
52  virtual const std::string& GetPingRequestForTesting() const OVERRIDE {
53    return ping_request_;
54  }
55
56  virtual const std::string& GetPingResponseForTesting() const OVERRIDE {
57    return ping_response_;
58  }
59
60  base::Closure finish_callback() const {
61    return finish_callback_;
62  }
63
64  bool start_called() const {
65    return start_called_;
66  }
67
68 private:
69  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
70  scoped_refptr<base::TaskRunner> file_task_runner_;
71  base::FilePath file_path_;
72  std::string ping_request_;
73  std::string ping_response_;
74
75  base::Closure finish_callback_;
76  base::Closure deletion_callback_;
77  bool start_called_;
78};
79
80class FakeDownloadFeedbackFactory : public DownloadFeedbackFactory {
81 public:
82  virtual ~FakeDownloadFeedbackFactory() {}
83
84  virtual DownloadFeedback* CreateDownloadFeedback(
85      net::URLRequestContextGetter* request_context_getter,
86      base::TaskRunner* file_task_runner,
87      const base::FilePath& file_path,
88      const std::string& ping_request,
89      const std::string& ping_response) OVERRIDE {
90    FakeDownloadFeedback* feedback = new FakeDownloadFeedback(
91        request_context_getter,
92        file_task_runner,
93        file_path,
94        ping_request,
95        ping_response,
96        base::Bind(&FakeDownloadFeedbackFactory::DownloadFeedbackDeleted,
97                   base::Unretained(this),
98                   feedbacks_.size()));
99    feedbacks_.push_back(feedback);
100    return feedback;
101  }
102
103  void DownloadFeedbackDeleted(size_t n) {
104    feedbacks_[n] = NULL;
105  }
106
107  FakeDownloadFeedback* feedback(size_t n) const {
108    return feedbacks_[n];
109  }
110
111  size_t num_feedbacks() const {
112    return feedbacks_.size();
113  }
114
115 private:
116  std::vector<FakeDownloadFeedback*> feedbacks_;
117};
118
119bool WillStorePings(DownloadProtectionService::DownloadCheckResult result,
120                     int64 size) {
121  content::MockDownloadItem item;
122  EXPECT_CALL(item, GetReceivedBytes()).WillRepeatedly(Return(size));
123
124  EXPECT_FALSE(DownloadFeedbackService::IsEnabledForDownload(item));
125  DownloadFeedbackService::MaybeStorePingsForDownload(result, &item, "a", "b");
126  return DownloadFeedbackService::IsEnabledForDownload(item);
127}
128
129}  // namespace
130
131class DownloadFeedbackServiceTest : public testing::Test {
132 public:
133  DownloadFeedbackServiceTest()
134      : file_task_runner_(content::BrowserThread::GetMessageLoopProxyForThread(
135            content::BrowserThread::FILE)),
136        io_task_runner_(content::BrowserThread::GetMessageLoopProxyForThread(
137            content::BrowserThread::IO)),
138        request_context_getter_(
139            new net::TestURLRequestContextGetter(io_task_runner_)) {
140  }
141
142  virtual void SetUp() OVERRIDE {
143    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
144    DownloadFeedback::RegisterFactory(&download_feedback_factory_);
145  }
146
147  virtual void TearDown() OVERRIDE {
148    DownloadFeedback::RegisterFactory(NULL);
149  }
150
151  base::FilePath CreateTestFile(int n) const {
152    base::FilePath upload_file_path(
153        temp_dir_.path().AppendASCII("test file " + base::IntToString(n)));
154    const std::string upload_file_data = "data";
155    int wrote = base::WriteFile(
156        upload_file_path, upload_file_data.data(), upload_file_data.size());
157    EXPECT_EQ(static_cast<int>(upload_file_data.size()), wrote);
158    return upload_file_path;
159  }
160
161  FakeDownloadFeedback* feedback(size_t n) const {
162    return download_feedback_factory_.feedback(n);
163  }
164
165  size_t num_feedbacks() const {
166    return download_feedback_factory_.num_feedbacks();
167  }
168
169 protected:
170  base::ScopedTempDir temp_dir_;
171  content::TestBrowserThreadBundle thread_bundle_;
172  scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
173  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
174  scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
175  FakeDownloadFeedbackFactory download_feedback_factory_;
176};
177
178TEST_F(DownloadFeedbackServiceTest, MaybeStorePingsForDownload) {
179  const int64 ok_size = DownloadFeedback::kMaxUploadSize;
180  const int64 bad_size = DownloadFeedback::kMaxUploadSize + 1;
181
182  EXPECT_FALSE(WillStorePings(DownloadProtectionService::SAFE, ok_size));
183  EXPECT_FALSE(WillStorePings(DownloadProtectionService::DANGEROUS, ok_size));
184  EXPECT_TRUE(WillStorePings(DownloadProtectionService::UNCOMMON, ok_size));
185  EXPECT_TRUE(
186      WillStorePings(DownloadProtectionService::DANGEROUS_HOST, ok_size));
187
188  EXPECT_FALSE(WillStorePings(DownloadProtectionService::SAFE, bad_size));
189  EXPECT_FALSE(WillStorePings(DownloadProtectionService::DANGEROUS, bad_size));
190  EXPECT_FALSE(WillStorePings(DownloadProtectionService::UNCOMMON, bad_size));
191  EXPECT_FALSE(
192      WillStorePings(DownloadProtectionService::DANGEROUS_HOST, bad_size));
193}
194
195TEST_F(DownloadFeedbackServiceTest, SingleFeedbackComplete) {
196  const base::FilePath file_path(CreateTestFile(0));
197  const std::string ping_request = "ping";
198  const std::string ping_response = "resp";
199
200  content::DownloadItem::AcquireFileCallback download_discarded_callback;
201
202  content::MockDownloadItem item;
203  EXPECT_CALL(item, GetDangerType())
204      .WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT));
205  EXPECT_CALL(item, GetReceivedBytes()).WillRepeatedly(Return(1000));
206  EXPECT_CALL(item, StealDangerousDownload(_))
207      .WillOnce(SaveArg<0>(&download_discarded_callback));
208
209  DownloadFeedbackService service(request_context_getter_.get(),
210                                  file_task_runner_.get());
211  service.MaybeStorePingsForDownload(
212      DownloadProtectionService::UNCOMMON, &item, ping_request, ping_response);
213  ASSERT_TRUE(DownloadFeedbackService::IsEnabledForDownload(item));
214  service.BeginFeedbackForDownload(&item);
215  ASSERT_FALSE(download_discarded_callback.is_null());
216  EXPECT_EQ(0U, num_feedbacks());
217
218  download_discarded_callback.Run(file_path);
219  ASSERT_EQ(1U, num_feedbacks());
220  ASSERT_TRUE(feedback(0));
221  EXPECT_TRUE(feedback(0)->start_called());
222  EXPECT_EQ(ping_request, feedback(0)->GetPingRequestForTesting());
223  EXPECT_EQ(ping_response, feedback(0)->GetPingResponseForTesting());
224
225  feedback(0)->finish_callback().Run();
226  EXPECT_FALSE(feedback(0));
227
228  // File should still exist since our FakeDownloadFeedback does not delete it.
229  base::RunLoop().RunUntilIdle();
230  EXPECT_TRUE(base::PathExists(file_path));
231}
232
233TEST_F(DownloadFeedbackServiceTest, MultiplePendingFeedbackComplete) {
234  const std::string ping_request = "ping";
235  const std::string ping_response = "resp";
236  const size_t num_downloads = 3;
237
238  content::DownloadItem::AcquireFileCallback
239      download_discarded_callback[num_downloads];
240
241  base::FilePath file_path[num_downloads];
242  content::MockDownloadItem item[num_downloads];
243  for (size_t i = 0; i < num_downloads; ++i) {
244    file_path[i] = CreateTestFile(i);
245    EXPECT_CALL(item[i], GetDangerType())
246        .WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT));
247    EXPECT_CALL(item[i], GetReceivedBytes()).WillRepeatedly(Return(1000));
248    EXPECT_CALL(item[i], StealDangerousDownload(_))
249        .WillOnce(SaveArg<0>(&download_discarded_callback[i]));
250    DownloadFeedbackService::MaybeStorePingsForDownload(
251        DownloadProtectionService::UNCOMMON, &item[i], ping_request,
252        ping_response);
253    ASSERT_TRUE(DownloadFeedbackService::IsEnabledForDownload(item[i]));
254  }
255
256  {
257    DownloadFeedbackService service(request_context_getter_.get(),
258                                    file_task_runner_.get());
259    for (size_t i = 0; i < num_downloads; ++i) {
260      SCOPED_TRACE(i);
261      service.BeginFeedbackForDownload(&item[i]);
262      ASSERT_FALSE(download_discarded_callback[i].is_null());
263    }
264    EXPECT_EQ(0U, num_feedbacks());
265
266    for (size_t i = 0; i < num_downloads; ++i) {
267      download_discarded_callback[i].Run(file_path[i]);
268    }
269
270    ASSERT_EQ(3U, num_feedbacks());
271    EXPECT_TRUE(feedback(0)->start_called());
272    EXPECT_FALSE(feedback(1)->start_called());
273    EXPECT_FALSE(feedback(2)->start_called());
274
275    feedback(0)->finish_callback().Run();
276
277    EXPECT_FALSE(feedback(0));
278    EXPECT_TRUE(feedback(1)->start_called());
279    EXPECT_FALSE(feedback(2)->start_called());
280
281    feedback(1)->finish_callback().Run();
282
283    EXPECT_FALSE(feedback(0));
284    EXPECT_FALSE(feedback(1));
285    EXPECT_TRUE(feedback(2)->start_called());
286
287    feedback(2)->finish_callback().Run();
288
289    EXPECT_FALSE(feedback(0));
290    EXPECT_FALSE(feedback(1));
291    EXPECT_FALSE(feedback(2));
292  }
293
294  base::RunLoop().RunUntilIdle();
295  // These files should still exist since the FakeDownloadFeedback does not
296  // delete them.
297  EXPECT_TRUE(base::PathExists(file_path[0]));
298  EXPECT_TRUE(base::PathExists(file_path[1]));
299  EXPECT_TRUE(base::PathExists(file_path[2]));
300}
301
302TEST_F(DownloadFeedbackServiceTest, MultiFeedbackWithIncomplete) {
303  const std::string ping_request = "ping";
304  const std::string ping_response = "resp";
305  const size_t num_downloads = 3;
306
307  content::DownloadItem::AcquireFileCallback
308      download_discarded_callback[num_downloads];
309
310  base::FilePath file_path[num_downloads];
311  content::MockDownloadItem item[num_downloads];
312  for (size_t i = 0; i < num_downloads; ++i) {
313    file_path[i] = CreateTestFile(i);
314    EXPECT_CALL(item[i], GetDangerType())
315        .WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT));
316    EXPECT_CALL(item[i], GetReceivedBytes()).WillRepeatedly(Return(1000));
317    EXPECT_CALL(item[i], StealDangerousDownload(_))
318        .WillOnce(SaveArg<0>(&download_discarded_callback[i]));
319    DownloadFeedbackService::MaybeStorePingsForDownload(
320        DownloadProtectionService::UNCOMMON, &item[i], ping_request,
321        ping_response);
322    ASSERT_TRUE(DownloadFeedbackService::IsEnabledForDownload(item[i]));
323  }
324
325  {
326    DownloadFeedbackService service(request_context_getter_.get(),
327                                    file_task_runner_.get());
328    for (size_t i = 0; i < num_downloads; ++i) {
329      SCOPED_TRACE(i);
330      service.BeginFeedbackForDownload(&item[i]);
331      ASSERT_FALSE(download_discarded_callback[i].is_null());
332    }
333    EXPECT_EQ(0U, num_feedbacks());
334
335    download_discarded_callback[0].Run(file_path[0]);
336    ASSERT_EQ(1U, num_feedbacks());
337    ASSERT_TRUE(feedback(0));
338    EXPECT_TRUE(feedback(0)->start_called());
339
340    download_discarded_callback[1].Run(file_path[1]);
341    ASSERT_EQ(2U, num_feedbacks());
342    ASSERT_TRUE(feedback(1));
343    EXPECT_FALSE(feedback(1)->start_called());
344
345    feedback(0)->finish_callback().Run();
346    EXPECT_FALSE(feedback(0));
347    EXPECT_TRUE(feedback(1)->start_called());
348  }
349
350  EXPECT_EQ(2U, num_feedbacks());
351  for (size_t i = 0; i < num_feedbacks(); ++i) {
352    SCOPED_TRACE(i);
353    EXPECT_FALSE(feedback(i));
354  }
355
356  // Running a download acquired callback after the DownloadFeedbackService is
357  // destroyed should delete the file.
358  download_discarded_callback[2].Run(file_path[2]);
359  EXPECT_EQ(2U, num_feedbacks());
360
361  // File should still exist since the FileUtilProxy task hasn't run yet.
362  EXPECT_TRUE(base::PathExists(file_path[2]));
363
364  base::RunLoop().RunUntilIdle();
365  // File should be deleted since the AcquireFileCallback ran after the service
366  // was deleted.
367  EXPECT_FALSE(base::PathExists(file_path[2]));
368
369  // These files should still exist since the FakeDownloadFeedback does not
370  // delete them.
371  EXPECT_TRUE(base::PathExists(file_path[0]));
372  EXPECT_TRUE(base::PathExists(file_path[1]));
373}
374
375}  // namespace safe_browsing
376