| // Copyright 2014 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/incident_reporting/last_download_finder.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/guid.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/mock_entropy_provider.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/history/chrome_history_client.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/history/web_history_service_factory.h" |
| #include "chrome/browser/prefs/browser_prefs.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "components/history/content/browser/content_visit_delegate.h" |
| #include "components/history/content/browser/download_conversions.h" |
| #include "components/history/content/browser/history_database_helper.h" |
| #include "components/history/core/browser/download_constants.h" |
| #include "components/history/core/browser/download_row.h" |
| #include "components/history/core/browser/history_constants.h" |
| #include "components/history/core/browser/history_database_params.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/safe_browsing/common/safe_browsing_prefs.h" |
| #include "components/safe_browsing/proto/csd.pb.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/public/test/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| // A testing factory that creates a HistoryService for a TestingProfile. |
| std::unique_ptr<KeyedService> BuildHistoryService( |
| content::BrowserContext* context) { |
| TestingProfile* profile = static_cast<TestingProfile*>(context); |
| |
| // Delete the file before creating the service. |
| base::FilePath history_path( |
| profile->GetPath().Append(history::kHistoryFilename)); |
| if (!base::DeleteFile(history_path, false) || |
| base::PathExists(history_path)) { |
| ADD_FAILURE() << "failed to delete history db file " |
| << history_path.value(); |
| return nullptr; |
| } |
| |
| std::unique_ptr<history::HistoryService> history_service( |
| new history::HistoryService( |
| std::make_unique<ChromeHistoryClient>( |
| BookmarkModelFactory::GetForBrowserContext(profile)), |
| std::unique_ptr<history::VisitDelegate>())); |
| if (history_service->Init( |
| history::HistoryDatabaseParamsForPath(profile->GetPath()))) { |
| return std::move(history_service); |
| } |
| |
| ADD_FAILURE() << "failed to initialize history service"; |
| return nullptr; |
| } |
| |
| #if defined(OS_WIN) |
| static const base::FilePath::CharType kBinaryFileName[] = |
| FILE_PATH_LITERAL("spam.exe"); |
| static const base::FilePath::CharType kBinaryFileNameForOtherOS[] = |
| FILE_PATH_LITERAL("spam.dmg"); |
| #elif defined(OS_MACOSX) |
| static const base::FilePath::CharType kBinaryFileName[] = |
| FILE_PATH_LITERAL("spam.dmg"); |
| static const base::FilePath::CharType kBinaryFileNameForOtherOS[] = |
| FILE_PATH_LITERAL("spam.apk"); |
| #elif defined(OS_ANDROID) |
| static const base::FilePath::CharType kBinaryFileName[] = |
| FILE_PATH_LITERAL("spam.apk"); |
| static const base::FilePath::CharType kBinaryFileNameForOtherOS[] = |
| FILE_PATH_LITERAL("spam.dmg"); |
| #else |
| static const base::FilePath::CharType kBinaryFileName[] = |
| FILE_PATH_LITERAL("spam.exe"); |
| #endif |
| |
| static const base::FilePath::CharType kTxtFileName[] = |
| FILE_PATH_LITERAL("download.txt"); |
| |
| } // namespace |
| |
| namespace safe_browsing { |
| |
| class LastDownloadFinderTest : public testing::Test { |
| public: |
| void NeverCalled( |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> download, |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| non_binary_download) { |
| FAIL(); |
| } |
| |
| // Creates a new profile that participates in safe browsing extended reporting |
| // and adds a download to its history. |
| void CreateProfileWithDownload() { |
| TestingProfile* profile = |
| CreateProfile(SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile( |
| profile, ServiceAccessType::EXPLICIT_ACCESS); |
| history_service->CreateDownload( |
| CreateTestDownloadRow(kBinaryFileName), |
| base::Bind(&LastDownloadFinderTest::OnDownloadCreated, |
| base::Unretained(this))); |
| } |
| |
| // LastDownloadFinder::LastDownloadCallback implementation that |
| // passes the found download to |result| and then runs a closure. |
| void OnLastDownload( |
| std::unique_ptr<ClientIncidentReport_DownloadDetails>* result, |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails>* |
| non_binary_result, |
| const base::Closure& quit_closure, |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> download, |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| non_binary_download) { |
| *result = std::move(download); |
| *non_binary_result = std::move(non_binary_download); |
| quit_closure.Run(); |
| } |
| |
| protected: |
| // A type for specifying whether a profile created by CreateProfile |
| // participates in safe browsing and safe browsing extended reporting. |
| enum SafeBrowsingDisposition { |
| OPT_OUT, |
| SAFE_BROWSING_ONLY, |
| EXTENDED_REPORTING_ONLY, |
| SAFE_BROWSING_AND_EXTENDED_REPORTING, |
| }; |
| |
| LastDownloadFinderTest() : profile_number_(), download_id_(1) {} |
| |
| void SetUp() override { |
| testing::Test::SetUp(); |
| profile_manager_.reset( |
| new TestingProfileManager(TestingBrowserProcess::GetGlobal())); |
| ASSERT_TRUE(profile_manager_->SetUp()); |
| } |
| |
| TestingProfile* CreateProfile(SafeBrowsingDisposition safe_browsing_opt_in) { |
| std::string profile_name("profile"); |
| profile_name.append(base::NumberToString(++profile_number_)); |
| |
| // Set up keyed service factories. |
| TestingProfile::TestingFactories factories; |
| // Build up a custom history service. |
| factories.emplace_back(HistoryServiceFactory::GetInstance(), |
| base::BindRepeating(&BuildHistoryService)); |
| // Suppress WebHistoryService since it makes network requests. |
| factories.emplace_back(WebHistoryServiceFactory::GetInstance(), |
| BrowserContextKeyedServiceFactory::TestingFactory()); |
| |
| // Create prefs for the profile with safe browsing enabled or not. |
| std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs( |
| new sync_preferences::TestingPrefServiceSyncable); |
| RegisterUserProfilePrefs(prefs->registry()); |
| prefs->SetBoolean( |
| prefs::kSafeBrowsingEnabled, |
| safe_browsing_opt_in == SAFE_BROWSING_ONLY || |
| safe_browsing_opt_in == SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| safe_browsing::SetExtendedReportingPref( |
| prefs.get(), |
| safe_browsing_opt_in == EXTENDED_REPORTING_ONLY || |
| safe_browsing_opt_in == SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| |
| TestingProfile* profile = profile_manager_->CreateTestingProfile( |
| profile_name, std::move(prefs), |
| base::UTF8ToUTF16(profile_name), // user_name |
| 0, // avatar_id |
| std::string(), // supervised_user_id |
| std::move(factories)); |
| |
| return profile; |
| } |
| |
| LastDownloadFinder::DownloadDetailsGetter GetDownloadDetailsGetter() { |
| return base::Bind(&LastDownloadFinderTest::GetDownloadDetails, |
| base::Unretained(this)); |
| } |
| |
| void AddDownload(Profile* profile, const history::DownloadRow& download) { |
| base::RunLoop run_loop; |
| |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile( |
| profile, ServiceAccessType::EXPLICIT_ACCESS); |
| history_service->CreateDownload( |
| download, |
| base::Bind(&LastDownloadFinderTest::ContinueOnDownloadCreated, |
| base::Unretained(this), |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // Runs the last download finder on all loaded profiles. |
| void RunLastDownloadFinder( |
| std::unique_ptr<ClientIncidentReport_DownloadDetails>* |
| last_binary_download, |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails>* |
| last_non_binary_download) { |
| base::RunLoop run_loop; |
| |
| std::unique_ptr<LastDownloadFinder> finder(LastDownloadFinder::Create( |
| GetDownloadDetailsGetter(), |
| base::Bind(&LastDownloadFinderTest::OnLastDownload, |
| base::Unretained(this), last_binary_download, |
| last_non_binary_download, run_loop.QuitClosure()))); |
| |
| if (finder) |
| run_loop.Run(); |
| } |
| |
| history::DownloadRow CreateTestDownloadRow( |
| const base::FilePath::CharType* file_path) { |
| base::Time now(base::Time::Now()); |
| |
| history::DownloadRow row; |
| row.current_path = base::FilePath(file_path); |
| row.target_path = base::FilePath(file_path); |
| row.url_chain.push_back(GURL("http://www.google.com/")); |
| row.referrer_url = GURL("http://referrer.example.com/"); |
| row.site_url = GURL("http://site-url.example.com/"); |
| row.tab_url = GURL("http://tab-url.example.com/"); |
| row.tab_referrer_url = GURL("http://tab-referrer.example.com/"); |
| row.mime_type = "application/octet-stream"; |
| row.original_mime_type = "application/octet-stream"; |
| row.start_time = now - base::TimeDelta::FromMinutes(10); |
| row.end_time = now - base::TimeDelta::FromMinutes(9); |
| row.received_bytes = 47; |
| row.total_bytes = 47; |
| row.state = history::DownloadState::COMPLETE; |
| row.danger_type = history::DownloadDangerType::NOT_DANGEROUS; |
| row.interrupt_reason = history::ToHistoryDownloadInterruptReason( |
| download::DOWNLOAD_INTERRUPT_REASON_NONE); |
| row.id = download_id_++; |
| row.guid = base::GenerateGUID(); |
| row.opened = false; |
| row.last_access_time = now - base::TimeDelta::FromMinutes(5); |
| row.transient = false; |
| |
| return row; |
| } |
| |
| private: |
| // A HistoryService::DownloadCreateCallback that asserts that the download was |
| // created and runs |closure|. |
| void ContinueOnDownloadCreated(const base::Closure& closure, bool created) { |
| ASSERT_TRUE(created); |
| closure.Run(); |
| } |
| |
| // A HistoryService::DownloadCreateCallback that asserts that the download was |
| // created. |
| void OnDownloadCreated(bool created) { ASSERT_TRUE(created); } |
| |
| void GetDownloadDetails( |
| content::BrowserContext* context, |
| const DownloadMetadataManager::GetDownloadDetailsCallback& callback) { |
| callback.Run(std::unique_ptr<ClientIncidentReport_DownloadDetails>()); |
| } |
| |
| content::TestBrowserThreadBundle browser_thread_bundle_; |
| std::unique_ptr<TestingProfileManager> profile_manager_; |
| int profile_number_; |
| |
| // Incremented on every download addition to avoid downloads with the same id. |
| int download_id_; |
| }; |
| |
| // Tests that nothing happens if there are no profiles at all. |
| TEST_F(LastDownloadFinderTest, NoProfiles) { |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| RunLastDownloadFinder(&last_binary_download, &last_non_binary_download); |
| EXPECT_FALSE(last_binary_download); |
| EXPECT_FALSE(last_non_binary_download); |
| } |
| |
| // Tests that nothing happens other than the callback being invoked if there are |
| // no profiles participating in safe browsing. |
| TEST_F(LastDownloadFinderTest, NoSafeBrowsingProfile) { |
| // Create a profile with a history service that is opted-out |
| TestingProfile* profile = CreateProfile(EXTENDED_REPORTING_ONLY); |
| |
| // Add a download. |
| AddDownload(profile, CreateTestDownloadRow(kBinaryFileName)); |
| |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| RunLastDownloadFinder(&last_binary_download, &last_non_binary_download); |
| EXPECT_FALSE(last_binary_download); |
| EXPECT_FALSE(last_non_binary_download); |
| } |
| |
| // Tests that nothing happens other than the callback being invoked if there are |
| // no profiles participating in safe browsing extended reporting. |
| TEST_F(LastDownloadFinderTest, NoExtendedReportingProfile) { |
| // Create a profile with a history service that is opted-out |
| TestingProfile* profile = CreateProfile(SAFE_BROWSING_ONLY); |
| |
| // Add a download. |
| AddDownload(profile, CreateTestDownloadRow(kBinaryFileName)); |
| |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| RunLastDownloadFinder(&last_binary_download, &last_non_binary_download); |
| EXPECT_FALSE(last_binary_download); |
| EXPECT_FALSE(last_non_binary_download); |
| } |
| |
| // Tests that a download is found from a single profile. |
| TEST_F(LastDownloadFinderTest, SimpleEndToEnd) { |
| // Create a profile with a history service that is opted-in. |
| TestingProfile* profile = CreateProfile(SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| |
| // Add a binary and non-binary download. |
| AddDownload(profile, CreateTestDownloadRow(kBinaryFileName)); |
| AddDownload(profile, CreateTestDownloadRow(kTxtFileName)); |
| |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| RunLastDownloadFinder(&last_binary_download, &last_non_binary_download); |
| EXPECT_TRUE(last_binary_download); |
| EXPECT_TRUE(last_non_binary_download); |
| } |
| |
| // Tests that a non-binary download is found |
| TEST_F(LastDownloadFinderTest, NonBinaryOnly) { |
| // Create a profile with a history service that is opted-in. |
| TestingProfile* profile = CreateProfile(SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| |
| // Add a non-binary download. |
| AddDownload(profile, CreateTestDownloadRow(kTxtFileName)); |
| |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| RunLastDownloadFinder(&last_binary_download, &last_non_binary_download); |
| EXPECT_FALSE(last_binary_download); |
| EXPECT_TRUE(last_non_binary_download); |
| } |
| |
| #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_ANDROID) |
| // Tests that nothing happens if the binary is an executable for a different OS. |
| TEST_F(LastDownloadFinderTest, DownloadForDifferentOs) { |
| // Create a profile with a history service that is opted-in. |
| TestingProfile* profile = CreateProfile(SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| |
| // Add a download. |
| AddDownload(profile, CreateTestDownloadRow(kBinaryFileNameForOtherOS)); |
| |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| RunLastDownloadFinder(&last_binary_download, &last_non_binary_download); |
| EXPECT_FALSE(last_binary_download); |
| EXPECT_FALSE(last_non_binary_download); |
| } |
| #endif |
| |
| // Tests that there is no crash if the finder is deleted before results arrive. |
| TEST_F(LastDownloadFinderTest, DeleteBeforeResults) { |
| // Create a profile with a history service that is opted-in. |
| TestingProfile* profile = CreateProfile(SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| |
| // Add a download. |
| AddDownload(profile, CreateTestDownloadRow(kBinaryFileName)); |
| |
| // Start a finder and kill it before the search completes. |
| LastDownloadFinder::Create(GetDownloadDetailsGetter(), |
| base::Bind(&LastDownloadFinderTest::NeverCalled, |
| base::Unretained(this))).reset(); |
| } |
| |
| // Tests that a download in profile added after the search is begun is found. |
| TEST_F(LastDownloadFinderTest, AddProfileAfterStarting) { |
| // Create a profile with a history service that is opted-in. |
| CreateProfile(SAFE_BROWSING_AND_EXTENDED_REPORTING); |
| |
| std::unique_ptr<ClientIncidentReport_DownloadDetails> last_binary_download; |
| std::unique_ptr<ClientIncidentReport_NonBinaryDownloadDetails> |
| last_non_binary_download; |
| base::RunLoop run_loop; |
| |
| // Post a task that will create a second profile once the main loop is run. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&LastDownloadFinderTest::CreateProfileWithDownload, |
| base::Unretained(this))); |
| |
| // Create a finder that we expect will find a download in the second profile. |
| std::unique_ptr<LastDownloadFinder> finder(LastDownloadFinder::Create( |
| GetDownloadDetailsGetter(), |
| base::Bind(&LastDownloadFinderTest::OnLastDownload, |
| base::Unretained(this), &last_binary_download, |
| &last_non_binary_download, run_loop.QuitClosure()))); |
| |
| run_loop.Run(); |
| |
| ASSERT_TRUE(last_binary_download); |
| } |
| |
| } // namespace safe_browsing |