blob: 6122e0f9d417c338bb236f336b446a9ddf97004a [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/safe_browsing/download_feedback_service.h"
#include <vector>
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/safe_browsing/download_feedback.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/mock_download_item.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Return;
using ::testing::SaveArg;
namespace safe_browsing {
namespace {
class FakeDownloadFeedback : public DownloadFeedback {
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)
: ping_request_(ping_request),
ping_response_(ping_response),
deletion_callback_(deletion_callback),
start_called_(false) {
}
virtual ~FakeDownloadFeedback() {
deletion_callback_.Run();
}
virtual void Start(const base::Closure& finish_callback) OVERRIDE {
start_called_ = true;
finish_callback_ = finish_callback;
}
virtual const std::string& GetPingRequestForTesting() const OVERRIDE {
return ping_request_;
}
virtual const std::string& GetPingResponseForTesting() const OVERRIDE {
return ping_response_;
}
base::Closure finish_callback() const {
return finish_callback_;
}
bool start_called() const {
return start_called_;
}
private:
scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
scoped_refptr<base::TaskRunner> file_task_runner_;
base::FilePath file_path_;
std::string ping_request_;
std::string ping_response_;
base::Closure finish_callback_;
base::Closure deletion_callback_;
bool start_called_;
};
class FakeDownloadFeedbackFactory : public DownloadFeedbackFactory {
public:
virtual ~FakeDownloadFeedbackFactory() {}
virtual DownloadFeedback* 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) OVERRIDE {
FakeDownloadFeedback* feedback = new FakeDownloadFeedback(
request_context_getter,
file_task_runner,
file_path,
ping_request,
ping_response,
base::Bind(&FakeDownloadFeedbackFactory::DownloadFeedbackDeleted,
base::Unretained(this),
feedbacks_.size()));
feedbacks_.push_back(feedback);
return feedback;
}
void DownloadFeedbackDeleted(size_t n) {
feedbacks_[n] = NULL;
}
FakeDownloadFeedback* feedback(size_t n) const {
return feedbacks_[n];
}
size_t num_feedbacks() const {
return feedbacks_.size();
}
private:
std::vector<FakeDownloadFeedback*> feedbacks_;
};
bool WillStorePings(DownloadProtectionService::DownloadCheckResult result,
int64 size) {
content::MockDownloadItem item;
EXPECT_CALL(item, GetReceivedBytes()).WillRepeatedly(Return(size));
EXPECT_FALSE(DownloadFeedbackService::IsEnabledForDownload(item));
DownloadFeedbackService::MaybeStorePingsForDownload(result, &item, "a", "b");
return DownloadFeedbackService::IsEnabledForDownload(item);
}
} // namespace
class DownloadFeedbackServiceTest : public testing::Test {
public:
DownloadFeedbackServiceTest()
: file_task_runner_(content::BrowserThread::GetMessageLoopProxyForThread(
content::BrowserThread::FILE)),
io_task_runner_(content::BrowserThread::GetMessageLoopProxyForThread(
content::BrowserThread::IO)),
request_context_getter_(
new net::TestURLRequestContextGetter(io_task_runner_)) {
}
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
DownloadFeedback::RegisterFactory(&download_feedback_factory_);
}
virtual void TearDown() OVERRIDE {
DownloadFeedback::RegisterFactory(NULL);
}
base::FilePath CreateTestFile(int n) const {
base::FilePath upload_file_path(
temp_dir_.path().AppendASCII("test file " + base::IntToString(n)));
const std::string upload_file_data = "data";
int wrote = base::WriteFile(
upload_file_path, upload_file_data.data(), upload_file_data.size());
EXPECT_EQ(static_cast<int>(upload_file_data.size()), wrote);
return upload_file_path;
}
FakeDownloadFeedback* feedback(size_t n) const {
return download_feedback_factory_.feedback(n);
}
size_t num_feedbacks() const {
return download_feedback_factory_.num_feedbacks();
}
protected:
base::ScopedTempDir temp_dir_;
content::TestBrowserThreadBundle thread_bundle_;
scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
FakeDownloadFeedbackFactory download_feedback_factory_;
};
TEST_F(DownloadFeedbackServiceTest, MaybeStorePingsForDownload) {
const int64 ok_size = DownloadFeedback::kMaxUploadSize;
const int64 bad_size = DownloadFeedback::kMaxUploadSize + 1;
EXPECT_FALSE(WillStorePings(DownloadProtectionService::SAFE, ok_size));
EXPECT_FALSE(WillStorePings(DownloadProtectionService::DANGEROUS, ok_size));
EXPECT_TRUE(WillStorePings(DownloadProtectionService::UNCOMMON, ok_size));
EXPECT_TRUE(
WillStorePings(DownloadProtectionService::DANGEROUS_HOST, ok_size));
EXPECT_FALSE(WillStorePings(DownloadProtectionService::SAFE, bad_size));
EXPECT_FALSE(WillStorePings(DownloadProtectionService::DANGEROUS, bad_size));
EXPECT_FALSE(WillStorePings(DownloadProtectionService::UNCOMMON, bad_size));
EXPECT_FALSE(
WillStorePings(DownloadProtectionService::DANGEROUS_HOST, bad_size));
}
TEST_F(DownloadFeedbackServiceTest, SingleFeedbackComplete) {
const base::FilePath file_path(CreateTestFile(0));
const std::string ping_request = "ping";
const std::string ping_response = "resp";
content::DownloadItem::AcquireFileCallback download_discarded_callback;
content::MockDownloadItem item;
EXPECT_CALL(item, GetDangerType())
.WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT));
EXPECT_CALL(item, GetReceivedBytes()).WillRepeatedly(Return(1000));
EXPECT_CALL(item, StealDangerousDownload(_))
.WillOnce(SaveArg<0>(&download_discarded_callback));
DownloadFeedbackService service(request_context_getter_.get(),
file_task_runner_.get());
service.MaybeStorePingsForDownload(
DownloadProtectionService::UNCOMMON, &item, ping_request, ping_response);
ASSERT_TRUE(DownloadFeedbackService::IsEnabledForDownload(item));
service.BeginFeedbackForDownload(&item);
ASSERT_FALSE(download_discarded_callback.is_null());
EXPECT_EQ(0U, num_feedbacks());
download_discarded_callback.Run(file_path);
ASSERT_EQ(1U, num_feedbacks());
ASSERT_TRUE(feedback(0));
EXPECT_TRUE(feedback(0)->start_called());
EXPECT_EQ(ping_request, feedback(0)->GetPingRequestForTesting());
EXPECT_EQ(ping_response, feedback(0)->GetPingResponseForTesting());
feedback(0)->finish_callback().Run();
EXPECT_FALSE(feedback(0));
// File should still exist since our FakeDownloadFeedback does not delete it.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(base::PathExists(file_path));
}
TEST_F(DownloadFeedbackServiceTest, MultiplePendingFeedbackComplete) {
const std::string ping_request = "ping";
const std::string ping_response = "resp";
const size_t num_downloads = 3;
content::DownloadItem::AcquireFileCallback
download_discarded_callback[num_downloads];
base::FilePath file_path[num_downloads];
content::MockDownloadItem item[num_downloads];
for (size_t i = 0; i < num_downloads; ++i) {
file_path[i] = CreateTestFile(i);
EXPECT_CALL(item[i], GetDangerType())
.WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT));
EXPECT_CALL(item[i], GetReceivedBytes()).WillRepeatedly(Return(1000));
EXPECT_CALL(item[i], StealDangerousDownload(_))
.WillOnce(SaveArg<0>(&download_discarded_callback[i]));
DownloadFeedbackService::MaybeStorePingsForDownload(
DownloadProtectionService::UNCOMMON, &item[i], ping_request,
ping_response);
ASSERT_TRUE(DownloadFeedbackService::IsEnabledForDownload(item[i]));
}
{
DownloadFeedbackService service(request_context_getter_.get(),
file_task_runner_.get());
for (size_t i = 0; i < num_downloads; ++i) {
SCOPED_TRACE(i);
service.BeginFeedbackForDownload(&item[i]);
ASSERT_FALSE(download_discarded_callback[i].is_null());
}
EXPECT_EQ(0U, num_feedbacks());
for (size_t i = 0; i < num_downloads; ++i) {
download_discarded_callback[i].Run(file_path[i]);
}
ASSERT_EQ(3U, num_feedbacks());
EXPECT_TRUE(feedback(0)->start_called());
EXPECT_FALSE(feedback(1)->start_called());
EXPECT_FALSE(feedback(2)->start_called());
feedback(0)->finish_callback().Run();
EXPECT_FALSE(feedback(0));
EXPECT_TRUE(feedback(1)->start_called());
EXPECT_FALSE(feedback(2)->start_called());
feedback(1)->finish_callback().Run();
EXPECT_FALSE(feedback(0));
EXPECT_FALSE(feedback(1));
EXPECT_TRUE(feedback(2)->start_called());
feedback(2)->finish_callback().Run();
EXPECT_FALSE(feedback(0));
EXPECT_FALSE(feedback(1));
EXPECT_FALSE(feedback(2));
}
base::RunLoop().RunUntilIdle();
// These files should still exist since the FakeDownloadFeedback does not
// delete them.
EXPECT_TRUE(base::PathExists(file_path[0]));
EXPECT_TRUE(base::PathExists(file_path[1]));
EXPECT_TRUE(base::PathExists(file_path[2]));
}
TEST_F(DownloadFeedbackServiceTest, MultiFeedbackWithIncomplete) {
const std::string ping_request = "ping";
const std::string ping_response = "resp";
const size_t num_downloads = 3;
content::DownloadItem::AcquireFileCallback
download_discarded_callback[num_downloads];
base::FilePath file_path[num_downloads];
content::MockDownloadItem item[num_downloads];
for (size_t i = 0; i < num_downloads; ++i) {
file_path[i] = CreateTestFile(i);
EXPECT_CALL(item[i], GetDangerType())
.WillRepeatedly(Return(content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT));
EXPECT_CALL(item[i], GetReceivedBytes()).WillRepeatedly(Return(1000));
EXPECT_CALL(item[i], StealDangerousDownload(_))
.WillOnce(SaveArg<0>(&download_discarded_callback[i]));
DownloadFeedbackService::MaybeStorePingsForDownload(
DownloadProtectionService::UNCOMMON, &item[i], ping_request,
ping_response);
ASSERT_TRUE(DownloadFeedbackService::IsEnabledForDownload(item[i]));
}
{
DownloadFeedbackService service(request_context_getter_.get(),
file_task_runner_.get());
for (size_t i = 0; i < num_downloads; ++i) {
SCOPED_TRACE(i);
service.BeginFeedbackForDownload(&item[i]);
ASSERT_FALSE(download_discarded_callback[i].is_null());
}
EXPECT_EQ(0U, num_feedbacks());
download_discarded_callback[0].Run(file_path[0]);
ASSERT_EQ(1U, num_feedbacks());
ASSERT_TRUE(feedback(0));
EXPECT_TRUE(feedback(0)->start_called());
download_discarded_callback[1].Run(file_path[1]);
ASSERT_EQ(2U, num_feedbacks());
ASSERT_TRUE(feedback(1));
EXPECT_FALSE(feedback(1)->start_called());
feedback(0)->finish_callback().Run();
EXPECT_FALSE(feedback(0));
EXPECT_TRUE(feedback(1)->start_called());
}
EXPECT_EQ(2U, num_feedbacks());
for (size_t i = 0; i < num_feedbacks(); ++i) {
SCOPED_TRACE(i);
EXPECT_FALSE(feedback(i));
}
// Running a download acquired callback after the DownloadFeedbackService is
// destroyed should delete the file.
download_discarded_callback[2].Run(file_path[2]);
EXPECT_EQ(2U, num_feedbacks());
// File should still exist since the FileUtilProxy task hasn't run yet.
EXPECT_TRUE(base::PathExists(file_path[2]));
base::RunLoop().RunUntilIdle();
// File should be deleted since the AcquireFileCallback ran after the service
// was deleted.
EXPECT_FALSE(base::PathExists(file_path[2]));
// These files should still exist since the FakeDownloadFeedback does not
// delete them.
EXPECT_TRUE(base::PathExists(file_path[0]));
EXPECT_TRUE(base::PathExists(file_path[1]));
}
} // namespace safe_browsing