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
21 using ::testing::_;
22 using ::testing::Return;
23 using ::testing::SaveArg;
24
25 namespace safe_browsing {
26
27 namespace {
28
29 class FakeDownloadFeedback : public DownloadFeedback {
30 public:
FakeDownloadFeedback(net::URLRequestContextGetter * request_context_getter,base::TaskRunner * file_task_runner,const base::FilePath & file_path,const std::string & ping_request,const std::string & ping_response,base::Closure deletion_callback)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
~FakeDownloadFeedback()43 virtual ~FakeDownloadFeedback() {
44 deletion_callback_.Run();
45 }
46
Start(const base::Closure & finish_callback)47 virtual void Start(const base::Closure& finish_callback) OVERRIDE {
48 start_called_ = true;
49 finish_callback_ = finish_callback;
50 }
51
GetPingRequestForTesting() const52 virtual const std::string& GetPingRequestForTesting() const OVERRIDE {
53 return ping_request_;
54 }
55
GetPingResponseForTesting() const56 virtual const std::string& GetPingResponseForTesting() const OVERRIDE {
57 return ping_response_;
58 }
59
finish_callback() const60 base::Closure finish_callback() const {
61 return finish_callback_;
62 }
63
start_called() const64 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
80 class FakeDownloadFeedbackFactory : public DownloadFeedbackFactory {
81 public:
~FakeDownloadFeedbackFactory()82 virtual ~FakeDownloadFeedbackFactory() {}
83
CreateDownloadFeedback(net::URLRequestContextGetter * request_context_getter,base::TaskRunner * file_task_runner,const base::FilePath & file_path,const std::string & ping_request,const std::string & ping_response)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
DownloadFeedbackDeleted(size_t n)103 void DownloadFeedbackDeleted(size_t n) {
104 feedbacks_[n] = NULL;
105 }
106
feedback(size_t n) const107 FakeDownloadFeedback* feedback(size_t n) const {
108 return feedbacks_[n];
109 }
110
num_feedbacks() const111 size_t num_feedbacks() const {
112 return feedbacks_.size();
113 }
114
115 private:
116 std::vector<FakeDownloadFeedback*> feedbacks_;
117 };
118
WillStorePings(DownloadProtectionService::DownloadCheckResult result,int64 size)119 bool 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
131 class DownloadFeedbackServiceTest : public testing::Test {
132 public:
DownloadFeedbackServiceTest()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
SetUp()142 virtual void SetUp() OVERRIDE {
143 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
144 DownloadFeedback::RegisterFactory(&download_feedback_factory_);
145 }
146
TearDown()147 virtual void TearDown() OVERRIDE {
148 DownloadFeedback::RegisterFactory(NULL);
149 }
150
CreateTestFile(int n) const151 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
feedback(size_t n) const161 FakeDownloadFeedback* feedback(size_t n) const {
162 return download_feedback_factory_.feedback(n);
163 }
164
num_feedbacks() const165 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
TEST_F(DownloadFeedbackServiceTest,MaybeStorePingsForDownload)178 TEST_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
TEST_F(DownloadFeedbackServiceTest,SingleFeedbackComplete)195 TEST_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
TEST_F(DownloadFeedbackServiceTest,MultiplePendingFeedbackComplete)233 TEST_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
TEST_F(DownloadFeedbackServiceTest,MultiFeedbackWithIncomplete)302 TEST_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