1// Copyright (c) 2011 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 <string>
6#include <set>
7
8#include "base/file_util.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/stl_util-inl.h"
11#include "base/string_util.h"
12#include "build/build_config.h"
13#include "chrome/browser/download/download_file.h"
14#include "chrome/browser/download/download_file_manager.h"
15#include "chrome/browser/download/download_item.h"
16#include "chrome/browser/download/download_manager.h"
17#include "chrome/browser/download/download_prefs.h"
18#include "chrome/browser/download/download_status_updater.h"
19#include "chrome/browser/download/download_util.h"
20#include "chrome/browser/download/mock_download_manager.h"
21#include "chrome/browser/history/download_create_info.h"
22#include "chrome/browser/prefs/pref_service.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/test/testing_profile.h"
25#include "content/browser/browser_thread.h"
26#include "testing/gmock/include/gmock/gmock.h"
27#include "testing/gmock_mutant.h"
28#include "testing/gtest/include/gtest/gtest.h"
29
30class DownloadManagerTest : public testing::Test {
31 public:
32  static const char* kTestData;
33  static const size_t kTestDataLen;
34
35  DownloadManagerTest()
36      : profile_(new TestingProfile()),
37        download_manager_(new MockDownloadManager(&download_status_updater_)),
38        ui_thread_(BrowserThread::UI, &message_loop_),
39        file_thread_(BrowserThread::FILE, &message_loop_) {
40    download_manager_->Init(profile_.get());
41  }
42
43  ~DownloadManagerTest() {
44    download_manager_->Shutdown();
45    // profile_ must outlive download_manager_, so we explicitly delete
46    // download_manager_ first.
47    download_manager_ = NULL;
48    profile_.reset(NULL);
49    message_loop_.RunAllPending();
50  }
51
52  void AddDownloadToFileManager(int id, DownloadFile* download_file) {
53    file_manager()->downloads_[id] = download_file;
54  }
55
56  void OnAllDataSaved(int32 download_id, int64 size, const std::string& hash) {
57    download_manager_->OnAllDataSaved(download_id, size, hash);
58  }
59
60  void FileSelected(const FilePath& path, int index, void* params) {
61    download_manager_->FileSelected(path, index, params);
62  }
63
64  void AttachDownloadItem(DownloadCreateInfo* info) {
65    download_manager_->AttachDownloadItem(info);
66  }
67
68  void OnDownloadError(int32 download_id, int64 size, int os_error) {
69    download_manager_->OnDownloadError(download_id, size, os_error);
70  }
71
72  // Get the download item with ID |id|.
73  DownloadItem* GetActiveDownloadItem(int32 id) {
74    if (ContainsKey(download_manager_->active_downloads_, id))
75      return download_manager_->active_downloads_[id];
76    return NULL;
77  }
78
79 protected:
80  DownloadStatusUpdater download_status_updater_;
81  scoped_ptr<TestingProfile> profile_;
82  scoped_refptr<DownloadManager> download_manager_;
83  scoped_refptr<DownloadFileManager> file_manager_;
84  MessageLoopForUI message_loop_;
85  BrowserThread ui_thread_;
86  BrowserThread file_thread_;
87
88  DownloadFileManager* file_manager() {
89    if (!file_manager_) {
90      file_manager_ = new DownloadFileManager(NULL);
91      download_manager_->file_manager_ = file_manager_;
92    }
93    return file_manager_;
94  }
95
96  // Make sure download item |id| was set with correct safety state for
97  // given |is_dangerous_file| and |is_dangerous_url|.
98  bool VerifySafetyState(bool is_dangerous_file,
99                         bool is_dangerous_url,
100                         int id) {
101    DownloadItem::SafetyState safety_state =
102        download_manager_->GetDownloadItem(id)->safety_state();
103    return (is_dangerous_file || is_dangerous_url) ?
104        safety_state != DownloadItem::SAFE : safety_state == DownloadItem::SAFE;
105  }
106
107  DISALLOW_COPY_AND_ASSIGN(DownloadManagerTest);
108};
109
110const char* DownloadManagerTest::kTestData = "a;sdlfalsdfjalsdkfjad";
111const size_t DownloadManagerTest::kTestDataLen =
112    strlen(DownloadManagerTest::kTestData);
113
114namespace {
115
116const struct {
117  const char* url;
118  const char* mime_type;
119  bool save_as;
120  bool prompt_for_download;
121  bool expected_save_as;
122} kStartDownloadCases[] = {
123  { "http://www.foo.com/dont-open.html",
124    "text/html",
125    false,
126    false,
127    false, },
128  { "http://www.foo.com/save-as.html",
129    "text/html",
130    true,
131    false,
132    true, },
133  { "http://www.foo.com/always-prompt.html",
134    "text/html",
135    false,
136    true,
137    true, },
138  { "http://www.foo.com/user-script-text-html-mimetype.user.js",
139    "text/html",
140    false,
141    false,
142    false, },
143  { "http://www.foo.com/extensionless-extension",
144    "application/x-chrome-extension",
145    true,
146    false,
147    true, },
148  { "http://www.foo.com/save-as.pdf",
149    "application/pdf",
150    true,
151    false,
152    true, },
153  { "http://www.foo.com/sometimes_prompt.pdf",
154    "application/pdf",
155    false,
156    true,
157    false, },
158  { "http://www.foo.com/always_prompt.jar",
159    "application/jar",
160    false,
161    true,
162    true, },
163};
164
165const struct {
166  FilePath::StringType suggested_path;
167  bool is_dangerous_file;
168  bool is_dangerous_url;
169  bool finish_before_rename;
170  int expected_rename_count;
171} kDownloadRenameCases[] = {
172  // Safe download, download finishes BEFORE file name determined.
173  // Renamed twice (linear path through UI).  Crdownload file does not need
174  // to be deleted.
175  { FILE_PATH_LITERAL("foo.zip"),
176    false, false, true, 2, },
177  // Dangerous download (file is dangerous or download URL is not safe or both),
178  // download finishes BEFORE file name determined. Needs to be renamed only
179  // once.
180  { FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
181    true, false, true, 1, },
182  { FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
183    false, true, true, 1, },
184  { FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
185    true, true, true, 1, },
186  // Safe download, download finishes AFTER file name determined.
187  // Needs to be renamed twice.
188  { FILE_PATH_LITERAL("foo.zip"),
189    false, false, false, 2, },
190  // Dangerous download, download finishes AFTER file name determined.
191  // Needs to be renamed only once.
192  { FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
193    true, false, false, 1, },
194  { FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
195    false, true, false, 1, },
196  { FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
197    true, true, false, 1, },
198};
199
200class MockDownloadFile : public DownloadFile {
201 public:
202  MockDownloadFile(DownloadCreateInfo* info, DownloadManager* manager)
203      : DownloadFile(info, manager), renamed_count_(0) { }
204  virtual ~MockDownloadFile() { Destructed(); }
205  MOCK_METHOD1(Rename, bool(const FilePath&));
206  MOCK_METHOD0(Destructed, void());
207
208  bool TestMultipleRename(
209      int expected_count, const FilePath& expected,
210      const FilePath& path) {
211    ++renamed_count_;
212    EXPECT_EQ(expected_count, renamed_count_);
213    EXPECT_EQ(expected.value(), path.value());
214    return true;
215  }
216
217 private:
218  int renamed_count_;
219};
220
221// This is an observer that records what download IDs have opened a select
222// file dialog.
223class SelectFileObserver : public DownloadManager::Observer {
224 public:
225  explicit SelectFileObserver(DownloadManager* download_manager)
226      : download_manager_(download_manager) {
227    DCHECK(download_manager_.get());
228    download_manager_->AddObserver(this);
229  }
230
231  ~SelectFileObserver() {
232    download_manager_->RemoveObserver(this);
233  }
234
235  // Downloadmanager::Observer functions.
236  virtual void ModelChanged() {}
237  virtual void ManagerGoingDown() {}
238  virtual void SelectFileDialogDisplayed(int32 id) {
239    file_dialog_ids_.insert(id);
240  }
241
242  bool ShowedFileDialogForId(int32 id) {
243    return file_dialog_ids_.find(id) != file_dialog_ids_.end();
244  }
245
246 private:
247  std::set<int32> file_dialog_ids_;
248  scoped_refptr<DownloadManager> download_manager_;
249};
250
251// This observer tracks the progress of |DownloadItem|s.
252class ItemObserver : public DownloadItem::Observer {
253 public:
254  explicit ItemObserver(DownloadItem* tracked)
255      : tracked_(tracked), states_hit_(0),
256        was_updated_(false), was_opened_(false) {
257    DCHECK(tracked_);
258    tracked_->AddObserver(this);
259    // Record the initial state.
260    OnDownloadUpdated(tracked_);
261  }
262  ~ItemObserver() {
263    tracked_->RemoveObserver(this);
264  }
265
266  bool hit_state(int state) const {
267    return (1 << state) & states_hit_;
268  }
269  bool was_updated() const { return was_updated_; }
270  bool was_opened() const { return was_opened_; }
271
272 private:
273  // DownloadItem::Observer methods
274  virtual void OnDownloadUpdated(DownloadItem* download) {
275    DCHECK_EQ(tracked_, download);
276    states_hit_ |= (1 << download->state());
277    was_updated_ = true;
278  }
279  virtual void OnDownloadOpened(DownloadItem* download) {
280    DCHECK_EQ(tracked_, download);
281    states_hit_ |= (1 << download->state());
282    was_opened_ = true;
283  }
284
285  DownloadItem* tracked_;
286  int states_hit_;
287  bool was_updated_;
288  bool was_opened_;
289};
290
291}  // namespace
292
293#if !defined(OS_CHROMEOS)
294
295TEST_F(DownloadManagerTest, StartDownload) {
296  BrowserThread io_thread(BrowserThread::IO, &message_loop_);
297  PrefService* prefs = profile_->GetPrefs();
298  prefs->SetFilePath(prefs::kDownloadDefaultDirectory, FilePath());
299  download_manager_->download_prefs()->EnableAutoOpenBasedOnExtension(
300      FilePath(FILE_PATH_LITERAL("example.pdf")));
301
302  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kStartDownloadCases); ++i) {
303    prefs->SetBoolean(prefs::kPromptForDownload,
304                      kStartDownloadCases[i].prompt_for_download);
305
306    SelectFileObserver observer(download_manager_);
307    DownloadCreateInfo* info = new DownloadCreateInfo;
308    info->download_id = static_cast<int>(i);
309    info->prompt_user_for_save_location = kStartDownloadCases[i].save_as;
310    info->url_chain.push_back(GURL(kStartDownloadCases[i].url));
311    info->mime_type = kStartDownloadCases[i].mime_type;
312    download_manager_->CreateDownloadItem(info);
313
314    DownloadFile* download_file(new DownloadFile(info, download_manager_));
315    AddDownloadToFileManager(info->download_id, download_file);
316    download_file->Initialize(false);
317    download_manager_->StartDownload(info);
318    message_loop_.RunAllPending();
319
320    // NOTE: At this point, |AttachDownloadItem| will have been run if we don't
321    // need to prompt the user, so |info| could have been destructed.
322    // This means that we can't check any of its values.
323    // However, SelectFileObserver will have recorded any attempt to open the
324    // select file dialog.
325    EXPECT_EQ(kStartDownloadCases[i].expected_save_as,
326              observer.ShowedFileDialogForId(i));
327
328    // If the Save As dialog pops up, it never reached
329    // DownloadManager::AttachDownloadItem(), and never deleted info or
330    // completed.  This cleans up info.
331    // Note that DownloadManager::FileSelectionCanceled() is never called.
332    if (observer.ShowedFileDialogForId(i)) {
333      delete info;
334    }
335  }
336}
337
338#endif // !defined(OS_CHROMEOS)
339
340TEST_F(DownloadManagerTest, DownloadRenameTest) {
341  using ::testing::_;
342  using ::testing::CreateFunctor;
343  using ::testing::Invoke;
344  using ::testing::Return;
345
346  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDownloadRenameCases); ++i) {
347    // |info| will be destroyed in download_manager_.
348    DownloadCreateInfo* info(new DownloadCreateInfo);
349    info->download_id = static_cast<int>(i);
350    info->prompt_user_for_save_location = false;
351    info->url_chain.push_back(GURL());
352    info->is_dangerous_file = kDownloadRenameCases[i].is_dangerous_file;
353    info->is_dangerous_url = kDownloadRenameCases[i].is_dangerous_url;
354    FilePath new_path(kDownloadRenameCases[i].suggested_path);
355
356    MockDownloadFile* download_file(
357        new MockDownloadFile(info, download_manager_));
358    AddDownloadToFileManager(info->download_id, download_file);
359
360    // |download_file| is owned by DownloadFileManager.
361    ::testing::Mock::AllowLeak(download_file);
362    EXPECT_CALL(*download_file, Destructed()).Times(1);
363
364    if (kDownloadRenameCases[i].expected_rename_count == 1) {
365      EXPECT_CALL(*download_file, Rename(new_path)).WillOnce(Return(true));
366    } else {
367      ASSERT_EQ(2, kDownloadRenameCases[i].expected_rename_count);
368      FilePath crdownload(download_util::GetCrDownloadPath(new_path));
369      EXPECT_CALL(*download_file, Rename(_))
370          .WillOnce(testing::WithArgs<0>(Invoke(CreateFunctor(
371              download_file, &MockDownloadFile::TestMultipleRename,
372              1, crdownload))))
373          .WillOnce(testing::WithArgs<0>(Invoke(CreateFunctor(
374              download_file, &MockDownloadFile::TestMultipleRename,
375              2, new_path))));
376    }
377    download_manager_->CreateDownloadItem(info);
378
379    if (kDownloadRenameCases[i].finish_before_rename) {
380      OnAllDataSaved(i, 1024, std::string("fake_hash"));
381      message_loop_.RunAllPending();
382      FileSelected(new_path, i, info);
383    } else {
384      FileSelected(new_path, i, info);
385      message_loop_.RunAllPending();
386      OnAllDataSaved(i, 1024, std::string("fake_hash"));
387    }
388
389    message_loop_.RunAllPending();
390    EXPECT_TRUE(VerifySafetyState(kDownloadRenameCases[i].is_dangerous_file,
391                                  kDownloadRenameCases[i].is_dangerous_url,
392                                  i));
393  }
394}
395
396TEST_F(DownloadManagerTest, DownloadInterruptTest) {
397  using ::testing::_;
398  using ::testing::CreateFunctor;
399  using ::testing::Invoke;
400  using ::testing::Return;
401
402  // |info| will be destroyed in download_manager_.
403  DownloadCreateInfo* info(new DownloadCreateInfo);
404  info->download_id = static_cast<int>(0);
405  info->prompt_user_for_save_location = false;
406  info->url_chain.push_back(GURL());
407  info->is_dangerous_file = false;
408  info->is_dangerous_url = false;
409  const FilePath new_path(FILE_PATH_LITERAL("foo.zip"));
410  const FilePath cr_path(download_util::GetCrDownloadPath(new_path));
411
412  MockDownloadFile* download_file(
413      new MockDownloadFile(info, download_manager_));
414  AddDownloadToFileManager(info->download_id, download_file);
415
416  // |download_file| is owned by DownloadFileManager.
417  ::testing::Mock::AllowLeak(download_file);
418  EXPECT_CALL(*download_file, Destructed()).Times(1);
419
420  EXPECT_CALL(*download_file, Rename(cr_path)).WillOnce(Return(true));
421
422  download_manager_->CreateDownloadItem(info);
423
424  DownloadItem* download = GetActiveDownloadItem(0);
425  ASSERT_TRUE(download != NULL);
426
427  EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state());
428  scoped_ptr<ItemObserver> observer(new ItemObserver(download));
429
430  download_file->AppendDataToFile(kTestData, kTestDataLen);
431
432  info->path = new_path;
433  AttachDownloadItem(info);
434  message_loop_.RunAllPending();
435  EXPECT_TRUE(GetActiveDownloadItem(0) != NULL);
436
437  OnDownloadError(0, 1024, -6);
438  message_loop_.RunAllPending();
439
440  EXPECT_TRUE(GetActiveDownloadItem(0) == NULL);
441  EXPECT_EQ(DownloadItem::INTERRUPTED, download->state());
442  EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS));
443  EXPECT_TRUE(observer->hit_state(DownloadItem::INTERRUPTED));
444  EXPECT_FALSE(observer->hit_state(DownloadItem::COMPLETE));
445  EXPECT_FALSE(observer->hit_state(DownloadItem::CANCELLED));
446  EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING));
447  EXPECT_TRUE(observer->was_updated());
448  EXPECT_FALSE(observer->was_opened());
449
450  download->Cancel(true);
451
452  EXPECT_EQ(DownloadItem::INTERRUPTED, download->state());
453  EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS));
454  EXPECT_TRUE(observer->hit_state(DownloadItem::INTERRUPTED));
455  EXPECT_FALSE(observer->hit_state(DownloadItem::COMPLETE));
456  EXPECT_FALSE(observer->hit_state(DownloadItem::CANCELLED));
457  EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING));
458  EXPECT_TRUE(observer->was_updated());
459  EXPECT_FALSE(observer->was_opened());
460}
461
462TEST_F(DownloadManagerTest, DownloadCancelTest) {
463  using ::testing::_;
464  using ::testing::CreateFunctor;
465  using ::testing::Invoke;
466  using ::testing::Return;
467
468  // |info| will be destroyed in download_manager_.
469  DownloadCreateInfo* info(new DownloadCreateInfo);
470  info->download_id = static_cast<int>(0);
471  info->prompt_user_for_save_location = false;
472  info->url_chain.push_back(GURL());
473  info->is_dangerous_file = false;
474  info->is_dangerous_url = false;
475  const FilePath new_path(FILE_PATH_LITERAL("foo.zip"));
476  const FilePath cr_path(download_util::GetCrDownloadPath(new_path));
477
478  MockDownloadFile* download_file(
479      new MockDownloadFile(info, download_manager_));
480  AddDownloadToFileManager(info->download_id, download_file);
481
482  // |download_file| is owned by DownloadFileManager.
483  ::testing::Mock::AllowLeak(download_file);
484  EXPECT_CALL(*download_file, Destructed()).Times(1);
485
486  EXPECT_CALL(*download_file, Rename(cr_path)).WillOnce(Return(true));
487
488  download_manager_->CreateDownloadItem(info);
489
490  DownloadItem* download = GetActiveDownloadItem(0);
491  ASSERT_TRUE(download != NULL);
492
493  EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state());
494  scoped_ptr<ItemObserver> observer(new ItemObserver(download));
495
496  info->path = new_path;
497  AttachDownloadItem(info);
498  message_loop_.RunAllPending();
499  EXPECT_TRUE(GetActiveDownloadItem(0) != NULL);
500
501  download_file->AppendDataToFile(kTestData, kTestDataLen);
502
503  download->Cancel(false);
504  message_loop_.RunAllPending();
505
506  EXPECT_TRUE(GetActiveDownloadItem(0) != NULL);
507  EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS));
508  EXPECT_TRUE(observer->hit_state(DownloadItem::CANCELLED));
509  EXPECT_FALSE(observer->hit_state(DownloadItem::INTERRUPTED));
510  EXPECT_FALSE(observer->hit_state(DownloadItem::COMPLETE));
511  EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING));
512  EXPECT_TRUE(observer->was_updated());
513  EXPECT_FALSE(observer->was_opened());
514
515  EXPECT_FALSE(file_util::PathExists(new_path));
516  EXPECT_FALSE(file_util::PathExists(cr_path));
517}
518
519TEST_F(DownloadManagerTest, DownloadOverwriteTest) {
520  using ::testing::_;
521  using ::testing::CreateFunctor;
522  using ::testing::Invoke;
523  using ::testing::Return;
524
525  // Create a temporary directory.
526  ScopedTempDir temp_dir_;
527  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
528
529  // File names we're using.
530  const FilePath new_path(temp_dir_.path().AppendASCII("foo.txt"));
531  const FilePath cr_path(download_util::GetCrDownloadPath(new_path));
532  EXPECT_FALSE(file_util::PathExists(new_path));
533
534  // Create the file that we will overwrite.  Will be automatically cleaned
535  // up when temp_dir_ is destroyed.
536  FILE* fp = file_util::OpenFile(new_path, "w");
537  file_util::CloseFile(fp);
538  EXPECT_TRUE(file_util::PathExists(new_path));
539
540  // Construct the unique file name that normally would be created, but
541  // which we will override.
542  int uniquifier = download_util::GetUniquePathNumber(new_path);
543  FilePath unique_new_path = new_path;
544  EXPECT_NE(0, uniquifier);
545  download_util::AppendNumberToPath(&unique_new_path, uniquifier);
546
547  // |info| will be destroyed in download_manager_.
548  DownloadCreateInfo* info(new DownloadCreateInfo);
549  info->download_id = static_cast<int>(0);
550  info->prompt_user_for_save_location = true;
551  info->url_chain.push_back(GURL());
552  info->is_dangerous_file = false;
553  info->is_dangerous_url = false;
554
555  download_manager_->CreateDownloadItem(info);
556
557  DownloadItem* download = GetActiveDownloadItem(0);
558  ASSERT_TRUE(download != NULL);
559
560  EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state());
561  scoped_ptr<ItemObserver> observer(new ItemObserver(download));
562
563  // Create and initialize the download file.  We're bypassing the first part
564  // of the download process and skipping to the part after the final file
565  // name has been chosen, so we need to initialize the download file
566  // properly.
567  DownloadFile* download_file(
568      new DownloadFile(info, download_manager_));
569  download_file->Rename(cr_path);
570  // This creates the .crdownload version of the file.
571  download_file->Initialize(false);
572  // |download_file| is owned by DownloadFileManager.
573  AddDownloadToFileManager(info->download_id, download_file);
574
575  info->path = new_path;
576  AttachDownloadItem(info);
577  message_loop_.RunAllPending();
578  EXPECT_TRUE(GetActiveDownloadItem(0) != NULL);
579
580  download_file->AppendDataToFile(kTestData, kTestDataLen);
581
582  // Finish the download.
583  OnAllDataSaved(0, kTestDataLen, "");
584  message_loop_.RunAllPending();
585
586  // Download is complete.
587  EXPECT_TRUE(GetActiveDownloadItem(0) == NULL);
588  EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS));
589  EXPECT_FALSE(observer->hit_state(DownloadItem::CANCELLED));
590  EXPECT_FALSE(observer->hit_state(DownloadItem::INTERRUPTED));
591  EXPECT_TRUE(observer->hit_state(DownloadItem::COMPLETE));
592  EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING));
593  EXPECT_TRUE(observer->was_updated());
594  EXPECT_FALSE(observer->was_opened());
595  EXPECT_EQ(DownloadItem::COMPLETE, download->state());
596
597  EXPECT_TRUE(file_util::PathExists(new_path));
598  EXPECT_FALSE(file_util::PathExists(cr_path));
599  EXPECT_FALSE(file_util::PathExists(unique_new_path));
600  std::string file_contents;
601  EXPECT_TRUE(file_util::ReadFileToString(new_path, &file_contents));
602  EXPECT_EQ(std::string(kTestData), file_contents);
603}
604