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
35namespace {
36
37// A BrowserContextKeyedServiceFactory::TestingFactoryFunction that creates a
38// HistoryService for a TestingProfile.
39KeyedService* 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
64class 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.
262TEST_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.
268TEST_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.
279TEST_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.
290TEST_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.
307TEST_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