1 // Copyright 2014 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/incident_reporting/last_download_finder.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/callback.h" 12 #include "base/files/file_util.h" 13 #include "base/run_loop.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "chrome/browser/history/chrome_history_client.h" 17 #include "chrome/browser/history/chrome_history_client_factory.h" 18 #include "chrome/browser/history/download_row.h" 19 #include "chrome/browser/history/history_service.h" 20 #include "chrome/browser/history/history_service_factory.h" 21 #include "chrome/browser/history/web_history_service_factory.h" 22 #include "chrome/browser/prefs/browser_prefs.h" 23 #include "chrome/browser/profiles/profile_manager.h" 24 #include "chrome/common/chrome_constants.h" 25 #include "chrome/common/pref_names.h" 26 #include "chrome/common/safe_browsing/csd.pb.h" 27 #include "chrome/test/base/testing_browser_process.h" 28 #include "chrome/test/base/testing_pref_service_syncable.h" 29 #include "chrome/test/base/testing_profile.h" 30 #include "chrome/test/base/testing_profile_manager.h" 31 #include "content/public/test/test_browser_thread_bundle.h" 32 #include "content/public/test/test_utils.h" 33 #include "testing/gtest/include/gtest/gtest.h" 34 35 namespace { 36 37 // A BrowserContextKeyedServiceFactory::TestingFactoryFunction that creates a 38 // HistoryService for a TestingProfile. 39 KeyedService* BuildHistoryService(content::BrowserContext* context) { 40 TestingProfile* profile = static_cast<TestingProfile*>(context); 41 42 // Delete the file before creating the service. 43 base::FilePath history_path( 44 profile->GetPath().Append(chrome::kHistoryFilename)); 45 if (!base::DeleteFile(history_path, false) || 46 base::PathExists(history_path)) { 47 ADD_FAILURE() << "failed to delete history db file " 48 << history_path.value(); 49 return NULL; 50 } 51 52 HistoryService* history_service = new HistoryService( 53 ChromeHistoryClientFactory::GetForProfile(profile), profile); 54 if (history_service->Init(profile->GetPath())) 55 return history_service; 56 57 ADD_FAILURE() << "failed to initialize history service"; 58 delete history_service; 59 return NULL; 60 } 61 62 } // namespace 63 64 class LastDownloadFinderTest : public testing::Test { 65 public: 66 void NeverCalled(scoped_ptr< 67 safe_browsing::ClientIncidentReport_DownloadDetails> download) { 68 FAIL(); 69 } 70 71 // Creates a new profile that participates in safe browsing and adds a 72 // download to its history. 73 void CreateProfileWithDownload() { 74 TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_IN); 75 HistoryService* history_service = 76 HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS); 77 history_service->CreateDownload( 78 CreateTestDownloadRow(), 79 base::Bind(&LastDownloadFinderTest::OnDownloadCreated, 80 base::Unretained(this))); 81 } 82 83 // safe_browsing::LastDownloadFinder::LastDownloadCallback implementation that 84 // passes the found download to |result| and then runs a closure. 85 void OnLastDownload( 86 scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails>* result, 87 const base::Closure& quit_closure, 88 scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails> 89 download) { 90 *result = download.Pass(); 91 quit_closure.Run(); 92 } 93 94 protected: 95 // A type for specifying whether or not a profile created by CreateProfile 96 // participates in safe browsing. 97 enum SafeBrowsingDisposition { 98 SAFE_BROWSING_OPT_OUT, 99 SAFE_BROWSING_OPT_IN, 100 }; 101 102 LastDownloadFinderTest() : profile_number_() {} 103 104 virtual void SetUp() OVERRIDE { 105 testing::Test::SetUp(); 106 profile_manager_.reset( 107 new TestingProfileManager(TestingBrowserProcess::GetGlobal())); 108 ASSERT_TRUE(profile_manager_->SetUp()); 109 } 110 111 virtual void TearDown() OVERRIDE { 112 // Shut down the history service on all profiles. 113 std::vector<Profile*> profiles( 114 profile_manager_->profile_manager()->GetLoadedProfiles()); 115 for (size_t i = 0; i < profiles.size(); ++i) { 116 profiles[0]->AsTestingProfile()->DestroyHistoryService(); 117 } 118 profile_manager_.reset(); 119 TestingBrowserProcess::DeleteInstance(); 120 testing::Test::TearDown(); 121 } 122 123 TestingProfile* CreateProfile(SafeBrowsingDisposition safe_browsing_opt_in) { 124 std::string profile_name("profile"); 125 profile_name.append(base::IntToString(++profile_number_)); 126 127 // Set up keyed service factories. 128 TestingProfile::TestingFactories factories; 129 // Build up a custom history service. 130 factories.push_back(std::make_pair(HistoryServiceFactory::GetInstance(), 131 &BuildHistoryService)); 132 // Suppress WebHistoryService since it makes network requests. 133 factories.push_back(std::make_pair( 134 WebHistoryServiceFactory::GetInstance(), 135 static_cast<BrowserContextKeyedServiceFactory::TestingFactoryFunction>( 136 NULL))); 137 138 // Create prefs for the profile with safe browsing enabled or not. 139 scoped_ptr<TestingPrefServiceSyncable> prefs( 140 new TestingPrefServiceSyncable); 141 chrome::RegisterUserProfilePrefs(prefs->registry()); 142 prefs->SetBoolean(prefs::kSafeBrowsingEnabled, 143 safe_browsing_opt_in == SAFE_BROWSING_OPT_IN); 144 145 TestingProfile* profile = profile_manager_->CreateTestingProfile( 146 profile_name, 147 prefs.PassAs<PrefServiceSyncable>(), 148 base::UTF8ToUTF16(profile_name), // user_name 149 0, // avatar_id 150 std::string(), // supervised_user_id 151 factories); 152 153 return profile; 154 } 155 156 void AddDownload(Profile* profile, const history::DownloadRow& download) { 157 base::RunLoop run_loop; 158 159 HistoryService* history_service = 160 HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS); 161 history_service->CreateDownload( 162 download, 163 base::Bind(&LastDownloadFinderTest::ContinueOnDownloadCreated, 164 base::Unretained(this), 165 run_loop.QuitClosure())); 166 run_loop.Run(); 167 } 168 169 // Wait for the history backend thread to process any outstanding tasks. 170 // This is needed because HistoryService::QueryDownloads uses PostTaskAndReply 171 // to do work on the backend thread and then invoke the caller's callback on 172 // the originating thread. The PostTaskAndReplyRelay holds a reference to the 173 // backend until its RunReplyAndSelfDestruct is called on the originating 174 // thread. This reference MUST be released (on the originating thread, 175 // remember) _before_ calling DestroyHistoryService in TearDown(). See the 176 // giant comment in HistoryService::Cleanup explaining where the backend's 177 // dtor must be run. 178 void FlushHistoryBackend(Profile* profile) { 179 base::RunLoop run_loop; 180 HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS) 181 ->FlushForTest(run_loop.QuitClosure()); 182 run_loop.Run(); 183 // Then make sure anything bounced back to the main thread has been handled. 184 base::RunLoop().RunUntilIdle(); 185 } 186 187 // Runs the last download finder on all loaded profiles, returning the found 188 // download or an empty pointer if none was found. 189 scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails> 190 RunLastDownloadFinder() { 191 base::RunLoop run_loop; 192 193 scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails> 194 last_download; 195 196 scoped_ptr<safe_browsing::LastDownloadFinder> finder( 197 safe_browsing::LastDownloadFinder::Create( 198 base::Bind(&LastDownloadFinderTest::OnLastDownload, 199 base::Unretained(this), 200 &last_download, 201 run_loop.QuitClosure()))); 202 203 if (finder) 204 run_loop.Run(); 205 206 return last_download.Pass(); 207 } 208 209 history::DownloadRow CreateTestDownloadRow() { 210 base::Time now(base::Time::Now()); 211 return history::DownloadRow( 212 base::FilePath(FILE_PATH_LITERAL("spam.exe")), 213 base::FilePath(FILE_PATH_LITERAL("spam.exe")), 214 std::vector<GURL>(1, GURL("http://www.google.com")), // url_chain 215 GURL(), // referrer 216 "application/octet-stream", // mime_type 217 "application/octet-stream", // original_mime_type 218 now - base::TimeDelta::FromMinutes(10), // start 219 now - base::TimeDelta::FromMinutes(9), // end 220 std::string(), // etag 221 std::string(), // last_modified 222 47LL, // received 223 47LL, // total 224 content::DownloadItem::COMPLETE, // download_state 225 content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, // danger_type 226 content::DOWNLOAD_INTERRUPT_REASON_NONE, // interrupt_reason, 227 1, // id 228 false, // download_opened 229 std::string(), // ext_id 230 std::string()); // ext_name 231 } 232 233 void ExpectNoDownloadFound(scoped_ptr< 234 safe_browsing::ClientIncidentReport_DownloadDetails> download) { 235 EXPECT_FALSE(download); 236 } 237 238 void ExpectFoundTestDownload(scoped_ptr< 239 safe_browsing::ClientIncidentReport_DownloadDetails> download) { 240 ASSERT_TRUE(download); 241 } 242 243 content::TestBrowserThreadBundle browser_thread_bundle_; 244 scoped_ptr<TestingProfileManager> profile_manager_; 245 246 private: 247 // A HistoryService::DownloadCreateCallback that asserts that the download was 248 // created and runs |closure|. 249 void ContinueOnDownloadCreated(const base::Closure& closure, bool created) { 250 ASSERT_TRUE(created); 251 closure.Run(); 252 } 253 254 // A HistoryService::DownloadCreateCallback that asserts that the download was 255 // created. 256 void OnDownloadCreated(bool created) { ASSERT_TRUE(created); } 257 258 int profile_number_; 259 }; 260 261 // Tests that nothing happens if there are no profiles at all. 262 TEST_F(LastDownloadFinderTest, NoProfiles) { 263 ExpectNoDownloadFound(RunLastDownloadFinder()); 264 } 265 266 // Tests that nothing happens other than the callback being invoked if there are 267 // no profiles participating in safe browsing. 268 TEST_F(LastDownloadFinderTest, NoParticipatingProfiles) { 269 // Create a profile with a history service that is opted-out 270 TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_OUT); 271 272 // Add a download. 273 AddDownload(profile, CreateTestDownloadRow()); 274 275 ExpectNoDownloadFound(RunLastDownloadFinder()); 276 } 277 278 // Tests that a download is found from a single profile. 279 TEST_F(LastDownloadFinderTest, SimpleEndToEnd) { 280 // Create a profile with a history service that is opted-in. 281 TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_IN); 282 283 // Add a download. 284 AddDownload(profile, CreateTestDownloadRow()); 285 286 ExpectFoundTestDownload(RunLastDownloadFinder()); 287 } 288 289 // Tests that there is no crash if the finder is deleted before results arrive. 290 TEST_F(LastDownloadFinderTest, DeleteBeforeResults) { 291 // Create a profile with a history service that is opted-in. 292 TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_IN); 293 294 // Add a download. 295 AddDownload(profile, CreateTestDownloadRow()); 296 297 // Start a finder and kill it before the search completes. 298 safe_browsing::LastDownloadFinder::Create( 299 base::Bind(&LastDownloadFinderTest::NeverCalled, base::Unretained(this))) 300 .reset(); 301 302 // Flush tasks on the history backend thread. 303 FlushHistoryBackend(profile); 304 } 305 306 // Tests that a download in profile added after the search is begun is found. 307 TEST_F(LastDownloadFinderTest, AddProfileAfterStarting) { 308 // Create a profile with a history service that is opted-in. 309 CreateProfile(SAFE_BROWSING_OPT_IN); 310 311 scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails> last_download; 312 base::RunLoop run_loop; 313 314 // Post a task that will create a second profile once the main loop is run. 315 base::MessageLoop::current()->PostTask( 316 FROM_HERE, 317 base::Bind(&LastDownloadFinderTest::CreateProfileWithDownload, 318 base::Unretained(this))); 319 320 // Create a finder that we expect will find a download in the second profile. 321 scoped_ptr<safe_browsing::LastDownloadFinder> finder( 322 safe_browsing::LastDownloadFinder::Create( 323 base::Bind(&LastDownloadFinderTest::OnLastDownload, 324 base::Unretained(this), 325 &last_download, 326 run_loop.QuitClosure()))); 327 328 run_loop.Run(); 329 330 ExpectFoundTestDownload(last_download.Pass()); 331 } 332