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