download_item.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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 "chrome/browser/download/download_item.h"
6
7#include "base/basictypes.h"
8#include "base/file_util.h"
9#include "base/format_macros.h"
10#include "base/logging.h"
11#include "base/metrics/histogram.h"
12#include "base/stringprintf.h"
13#include "base/timer.h"
14#include "base/utf_string_conversions.h"
15#include "net/base/net_util.h"
16#include "chrome/browser/download/download_extensions.h"
17#include "chrome/browser/download/download_file_manager.h"
18#include "chrome/browser/download/download_history.h"
19#include "chrome/browser/download/download_manager.h"
20#include "chrome/browser/download/download_prefs.h"
21#include "chrome/browser/download/download_util.h"
22#include "chrome/browser/history/download_create_info.h"
23#include "chrome/browser/platform_util.h"
24#include "chrome/browser/prefs/pref_service.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/common/extensions/extension.h"
27#include "chrome/common/pref_names.h"
28#include "content/browser/browser_thread.h"
29#include "ui/base/l10n/l10n_util.h"
30
31// A DownloadItem normally goes through the following states:
32//      * Created (when download starts)
33//      * Made visible to consumers (e.g. Javascript) after the
34//        destination file has been determined.
35//      * Entered into the history database.
36//      * Made visible in the download shelf.
37//      * All data is saved.  Note that the actual data download occurs
38//        in parallel with the above steps, but until those steps are
39//        complete, completion of the data download will be ignored.
40//      * Download file is renamed to its final name, and possibly
41//        auto-opened.
42// TODO(rdsmith): This progress should be reflected in
43// DownloadItem::DownloadState and a state transition table/state diagram.
44//
45// TODO(rdsmith): This description should be updated to reflect the cancel
46// pathways.
47
48namespace {
49
50// Update frequency (milliseconds).
51const int kUpdateTimeMs = 1000;
52
53void DeleteDownloadedFile(const FilePath& path) {
54  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
55
56  // Make sure we only delete files.
57  if (!file_util::DirectoryExists(path))
58    file_util::Delete(path, false);
59}
60
61const char* DebugSafetyStateString(DownloadItem::SafetyState state) {
62  switch (state) {
63    case DownloadItem::SAFE:
64      return "SAFE";
65    case DownloadItem::DANGEROUS:
66      return "DANGEROUS";
67    case DownloadItem::DANGEROUS_BUT_VALIDATED:
68      return "DANGEROUS_BUT_VALIDATED";
69    default:
70      NOTREACHED() << "Unknown safety state " << state;
71      return "unknown";
72  };
73}
74
75const char* DebugDownloadStateString(DownloadItem::DownloadState state) {
76  switch (state) {
77    case DownloadItem::IN_PROGRESS:
78      return "IN_PROGRESS";
79    case DownloadItem::COMPLETE:
80      return "COMPLETE";
81    case DownloadItem::CANCELLED:
82      return "CANCELLED";
83    case DownloadItem::REMOVING:
84      return "REMOVING";
85    case DownloadItem::INTERRUPTED:
86      return "INTERRUPTED";
87    default:
88      NOTREACHED() << "Unknown download state " << state;
89      return "unknown";
90  };
91}
92
93DownloadItem::SafetyState GetSafetyState(bool dangerous_file,
94                                         bool dangerous_url) {
95  return (dangerous_url || dangerous_file) ?
96      DownloadItem::DANGEROUS : DownloadItem::SAFE;
97}
98
99// Note: When a download has both |dangerous_file| and |dangerous_url| set,
100// danger type is set to DANGEROUS_URL since the risk of dangerous URL
101// overweights that of dangerous file type.
102DownloadItem::DangerType GetDangerType(bool dangerous_file,
103                                       bool dangerous_url) {
104  if (dangerous_url) {
105    // dangerous URL overweights dangerous file. We check dangerous URL first.
106    return DownloadItem::DANGEROUS_URL;
107  } else if (dangerous_file) {
108    return DownloadItem::DANGEROUS_FILE;
109  }
110  return DownloadItem::NOT_DANGEROUS;
111}
112
113}  // namespace
114
115// Constructor for reading from the history service.
116DownloadItem::DownloadItem(DownloadManager* download_manager,
117                           const DownloadCreateInfo& info)
118    : id_(-1),
119      full_path_(info.path),
120      path_uniquifier_(0),
121      url_chain_(info.url_chain),
122      referrer_url_(info.referrer_url),
123      mime_type_(info.mime_type),
124      original_mime_type_(info.original_mime_type),
125      total_bytes_(info.total_bytes),
126      received_bytes_(info.received_bytes),
127      start_tick_(base::TimeTicks()),
128      state_(static_cast<DownloadState>(info.state)),
129      start_time_(info.start_time),
130      db_handle_(info.db_handle),
131      download_manager_(download_manager),
132      is_paused_(false),
133      open_when_complete_(false),
134      safety_state_(SAFE),
135      danger_type_(NOT_DANGEROUS),
136      auto_opened_(false),
137      target_name_(info.original_name),
138      render_process_id_(-1),
139      request_id_(-1),
140      save_as_(false),
141      is_otr_(false),
142      is_extension_install_(info.is_extension_install),
143      name_finalized_(false),
144      is_temporary_(false),
145      all_data_saved_(false),
146      opened_(false) {
147  if (IsInProgress())
148    state_ = CANCELLED;
149  if (IsComplete())
150    all_data_saved_ = true;
151  Init(false /* don't start progress timer */);
152}
153
154// Constructing for a regular download:
155DownloadItem::DownloadItem(DownloadManager* download_manager,
156                           const DownloadCreateInfo& info,
157                           bool is_otr)
158    : id_(info.download_id),
159      full_path_(info.path),
160      path_uniquifier_(info.path_uniquifier),
161      url_chain_(info.url_chain),
162      referrer_url_(info.referrer_url),
163      mime_type_(info.mime_type),
164      original_mime_type_(info.original_mime_type),
165      total_bytes_(info.total_bytes),
166      received_bytes_(0),
167      last_os_error_(0),
168      start_tick_(base::TimeTicks::Now()),
169      state_(IN_PROGRESS),
170      start_time_(info.start_time),
171      db_handle_(DownloadHistory::kUninitializedHandle),
172      download_manager_(download_manager),
173      is_paused_(false),
174      open_when_complete_(false),
175      safety_state_(GetSafetyState(info.is_dangerous_file,
176                                   info.is_dangerous_url)),
177      danger_type_(GetDangerType(info.is_dangerous_file,
178                                 info.is_dangerous_url)),
179      auto_opened_(false),
180      target_name_(info.original_name),
181      render_process_id_(info.child_id),
182      request_id_(info.request_id),
183      save_as_(info.prompt_user_for_save_location),
184      is_otr_(is_otr),
185      is_extension_install_(info.is_extension_install),
186      name_finalized_(false),
187      is_temporary_(!info.save_info.file_path.empty()),
188      all_data_saved_(false),
189      opened_(false) {
190  Init(true /* start progress timer */);
191}
192
193// Constructing for the "Save Page As..." feature:
194DownloadItem::DownloadItem(DownloadManager* download_manager,
195                           const FilePath& path,
196                           const GURL& url,
197                           bool is_otr)
198    : id_(1),
199      full_path_(path),
200      path_uniquifier_(0),
201      url_chain_(1, url),
202      referrer_url_(GURL()),
203      mime_type_(std::string()),
204      original_mime_type_(std::string()),
205      total_bytes_(0),
206      received_bytes_(0),
207      last_os_error_(0),
208      start_tick_(base::TimeTicks::Now()),
209      state_(IN_PROGRESS),
210      start_time_(base::Time::Now()),
211      db_handle_(DownloadHistory::kUninitializedHandle),
212      download_manager_(download_manager),
213      is_paused_(false),
214      open_when_complete_(false),
215      safety_state_(SAFE),
216      danger_type_(NOT_DANGEROUS),
217      auto_opened_(false),
218      render_process_id_(-1),
219      request_id_(-1),
220      save_as_(false),
221      is_otr_(is_otr),
222      is_extension_install_(false),
223      name_finalized_(false),
224      is_temporary_(false),
225      all_data_saved_(false),
226      opened_(false) {
227  Init(true /* start progress timer */);
228}
229
230DownloadItem::~DownloadItem() {
231  state_ = REMOVING;
232  UpdateObservers();
233}
234
235void DownloadItem::AddObserver(Observer* observer) {
236  observers_.AddObserver(observer);
237}
238
239void DownloadItem::RemoveObserver(Observer* observer) {
240  observers_.RemoveObserver(observer);
241}
242
243void DownloadItem::UpdateObservers() {
244  FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this));
245}
246
247bool DownloadItem::CanOpenDownload() {
248  return !Extension::IsExtension(target_name_);
249}
250
251bool DownloadItem::ShouldOpenFileBasedOnExtension() {
252  return download_manager_->ShouldOpenFileBasedOnExtension(
253      GetUserVerifiedFilePath());
254}
255
256void DownloadItem::OpenFilesBasedOnExtension(bool open) {
257  DownloadPrefs* prefs = download_manager_->download_prefs();
258  if (open)
259    prefs->EnableAutoOpenBasedOnExtension(GetUserVerifiedFilePath());
260  else
261    prefs->DisableAutoOpenBasedOnExtension(GetUserVerifiedFilePath());
262}
263
264void DownloadItem::OpenDownload() {
265  if (IsPartialDownload()) {
266    open_when_complete_ = !open_when_complete_;
267  } else if (IsComplete()) {
268    opened_ = true;
269    FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this));
270    if (is_extension_install()) {
271      download_util::OpenChromeExtension(download_manager_->profile(),
272                                         download_manager_,
273                                         *this);
274      return;
275    }
276#if defined(OS_MACOSX)
277    // Mac OS X requires opening downloads on the UI thread.
278    platform_util::OpenItem(full_path());
279#else
280    BrowserThread::PostTask(
281        BrowserThread::FILE, FROM_HERE,
282        NewRunnableFunction(&platform_util::OpenItem, full_path()));
283#endif
284  }
285}
286
287void DownloadItem::ShowDownloadInShell() {
288#if defined(OS_MACOSX)
289  // Mac needs to run this operation on the UI thread.
290  platform_util::ShowItemInFolder(full_path());
291#else
292  BrowserThread::PostTask(
293      BrowserThread::FILE, FROM_HERE,
294      NewRunnableFunction(&platform_util::ShowItemInFolder,
295                          full_path()));
296#endif
297}
298
299void DownloadItem::DangerousDownloadValidated() {
300  UMA_HISTOGRAM_ENUMERATION("Download.DangerousDownloadValidated",
301                            danger_type_,
302                            DANGEROUS_TYPE_MAX);
303  download_manager_->DangerousDownloadValidated(this);
304}
305
306void DownloadItem::UpdateSize(int64 bytes_so_far) {
307  received_bytes_ = bytes_so_far;
308
309  // If we've received more data than we were expecting (bad server info?),
310  // revert to 'unknown size mode'.
311  if (received_bytes_ > total_bytes_)
312    total_bytes_ = 0;
313}
314
315void DownloadItem::StartProgressTimer() {
316  update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdateTimeMs), this,
317                      &DownloadItem::UpdateObservers);
318}
319
320void DownloadItem::StopProgressTimer() {
321  update_timer_.Stop();
322}
323
324// Updates from the download thread may have been posted while this download
325// was being cancelled in the UI thread, so we'll accept them unless we're
326// complete.
327void DownloadItem::Update(int64 bytes_so_far) {
328  if (!IsInProgress()) {
329    NOTREACHED();
330    return;
331  }
332  UpdateSize(bytes_so_far);
333  UpdateObservers();
334}
335
336// Triggered by a user action.
337void DownloadItem::Cancel(bool update_history) {
338  VLOG(20) << __FUNCTION__ << "()" << " download = " << DebugString(true);
339  if (!IsPartialDownload()) {
340    // Small downloads might be complete before this method has
341    // a chance to run.
342    return;
343  }
344
345  download_util::RecordDownloadCount(download_util::CANCELLED_COUNT);
346
347  state_ = CANCELLED;
348  UpdateObservers();
349  StopProgressTimer();
350  if (update_history)
351    download_manager_->DownloadCancelled(id_);
352}
353
354void DownloadItem::MarkAsComplete() {
355  DCHECK(all_data_saved_);
356  state_ = COMPLETE;
357  UpdateObservers();
358}
359
360void DownloadItem::OnAllDataSaved(int64 size) {
361  DCHECK(!all_data_saved_);
362  all_data_saved_ = true;
363  UpdateSize(size);
364  StopProgressTimer();
365}
366
367void DownloadItem::Completed() {
368  VLOG(20) << " " << __FUNCTION__ << "() "
369           << DebugString(false);
370
371  download_util::RecordDownloadCount(download_util::COMPLETED_COUNT);
372
373  // Handle chrome extensions explicitly and skip the shell execute.
374  if (is_extension_install()) {
375    download_util::OpenChromeExtension(download_manager_->profile(),
376                                       download_manager_,
377                                       *this);
378    auto_opened_ = true;
379  } else if (open_when_complete() ||
380             download_manager_->ShouldOpenFileBasedOnExtension(
381                 GetUserVerifiedFilePath()) ||
382             is_temporary()) {
383    // If the download is temporary, like in drag-and-drop, do not open it but
384    // we still need to set it auto-opened so that it can be removed from the
385    // download shelf.
386    if (!is_temporary())
387      OpenDownload();
388    auto_opened_ = true;
389  }
390
391  DCHECK(all_data_saved_);
392  state_ = COMPLETE;
393  UpdateObservers();
394  download_manager_->DownloadCompleted(id());
395}
396
397void DownloadItem::Interrupted(int64 size, int os_error) {
398  if (!IsInProgress())
399    return;
400  state_ = INTERRUPTED;
401  last_os_error_ = os_error;
402  UpdateSize(size);
403  StopProgressTimer();
404  UpdateObservers();
405}
406
407void DownloadItem::Delete(DeleteReason reason) {
408  switch (reason) {
409    case DELETE_DUE_TO_USER_DISCARD:
410      UMA_HISTOGRAM_ENUMERATION("Download.UserDiscard",
411                                danger_type_,
412                                DANGEROUS_TYPE_MAX);
413      break;
414    case DELETE_DUE_TO_BROWSER_SHUTDOWN:
415      UMA_HISTOGRAM_ENUMERATION("Download.Discard",
416                                danger_type_,
417                                DANGEROUS_TYPE_MAX);
418      break;
419    default:
420      NOTREACHED();
421  }
422
423  BrowserThread::PostTask(
424      BrowserThread::FILE, FROM_HERE,
425      NewRunnableFunction(&DeleteDownloadedFile, full_path_));
426  Remove();
427  // We have now been deleted.
428}
429
430void DownloadItem::Remove() {
431  Cancel(true);
432  state_ = REMOVING;
433  download_manager_->RemoveDownload(db_handle_);
434  // We have now been deleted.
435}
436
437bool DownloadItem::TimeRemaining(base::TimeDelta* remaining) const {
438  if (total_bytes_ <= 0)
439    return false;  // We never received the content_length for this download.
440
441  int64 speed = CurrentSpeed();
442  if (speed == 0)
443    return false;
444
445  *remaining =
446      base::TimeDelta::FromSeconds((total_bytes_ - received_bytes_) / speed);
447  return true;
448}
449
450int64 DownloadItem::CurrentSpeed() const {
451  if (is_paused_)
452    return 0;
453  base::TimeDelta diff = base::TimeTicks::Now() - start_tick_;
454  int64 diff_ms = diff.InMilliseconds();
455  return diff_ms == 0 ? 0 : received_bytes_ * 1000 / diff_ms;
456}
457
458int DownloadItem::PercentComplete() const {
459  int percent = -1;
460  if (total_bytes_ > 0)
461    percent = static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
462  return percent;
463}
464
465void DownloadItem::Rename(const FilePath& full_path) {
466  VLOG(20) << " " << __FUNCTION__ << "()"
467           << " full_path = \"" << full_path.value() << "\""
468           << DebugString(true);
469  DCHECK(!full_path.empty());
470  full_path_ = full_path;
471}
472
473void DownloadItem::TogglePause() {
474  DCHECK(IsInProgress());
475  download_manager_->PauseDownload(id_, !is_paused_);
476  is_paused_ = !is_paused_;
477  UpdateObservers();
478}
479
480void DownloadItem::OnNameFinalized() {
481  VLOG(20) << " " << __FUNCTION__ << "() "
482           << DebugString(true);
483  name_finalized_ = true;
484
485  // We can't reach this point in the code without having received all the
486  // data, so it's safe to move to the COMPLETE state.
487  DCHECK(all_data_saved_);
488  state_ = COMPLETE;
489  UpdateObservers();
490  download_manager_->DownloadCompleted(id());
491}
492
493void DownloadItem::OnDownloadCompleting(DownloadFileManager* file_manager) {
494  VLOG(20) << " " << __FUNCTION__ << "() "
495           << " needs rename = " << NeedsRename()
496           << " " << DebugString(true);
497  DCHECK_NE(DANGEROUS, safety_state());
498  DCHECK(file_manager);
499
500  if (NeedsRename()) {
501    BrowserThread::PostTask(
502        BrowserThread::FILE, FROM_HERE,
503        NewRunnableMethod(
504            file_manager, &DownloadFileManager::RenameCompletingDownloadFile,
505            id(), GetTargetFilePath(), safety_state() == SAFE));
506    return;
507  } else {
508    name_finalized_ = true;
509  }
510
511  Completed();
512
513  BrowserThread::PostTask(
514      BrowserThread::FILE, FROM_HERE,
515      NewRunnableMethod(
516          file_manager, &DownloadFileManager::CompleteDownload, id()));
517}
518
519void DownloadItem::OnDownloadRenamedToFinalName(const FilePath& full_path) {
520  VLOG(20) << " " << __FUNCTION__ << "()"
521           << " full_path = " << full_path.value()
522           << " needed rename = " << NeedsRename()
523           << " " << DebugString(false);
524  DCHECK(NeedsRename());
525
526  Rename(full_path);
527  OnNameFinalized();
528
529  Completed();
530}
531
532bool DownloadItem::MatchesQuery(const string16& query) const {
533  if (query.empty())
534    return true;
535
536  DCHECK_EQ(query, l10n_util::ToLower(query));
537
538  string16 url_raw(l10n_util::ToLower(UTF8ToUTF16(url().spec())));
539  if (url_raw.find(query) != string16::npos)
540    return true;
541
542  // TODO(phajdan.jr): write a test case for the following code.
543  // A good test case would be:
544  //   "/\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xbd\xa0\xe5\xa5\xbd",
545  //   L"/\x4f60\x597d\x4f60\x597d",
546  //   "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD"
547  PrefService* prefs = download_manager_->profile()->GetPrefs();
548  std::string languages(prefs->GetString(prefs::kAcceptLanguages));
549  string16 url_formatted(l10n_util::ToLower(net::FormatUrl(url(), languages)));
550  if (url_formatted.find(query) != string16::npos)
551    return true;
552
553  string16 path(l10n_util::ToLower(full_path().LossyDisplayName()));
554  // This shouldn't just do a substring match; it is wrong for Unicode
555  // due to normalization and we have a fancier search-query system
556  // used elsewhere.
557  // http://code.google.com/p/chromium/issues/detail?id=71982
558  if (path.find(query) != string16::npos)
559    return true;
560
561  return false;
562}
563
564void DownloadItem::SetFileCheckResults(const FilePath& path,
565                                       bool is_dangerous_file,
566                                       bool is_dangerous_url,
567                                       int path_uniquifier,
568                                       bool prompt,
569                                       bool is_extension_install,
570                                       const FilePath& original_name) {
571  VLOG(20) << " " << __FUNCTION__ << "()"
572           << " path = \"" << path.value() << "\""
573           << " is_dangerous_file = " << is_dangerous_file
574           << " is_dangerous_url = " << is_dangerous_url
575           << " path_uniquifier = " << path_uniquifier
576           << " prompt = " << prompt
577           << " is_extension_install = " << is_extension_install
578           << " path = \"" << path.value() << "\""
579           << " original_name = \"" << original_name.value() << "\""
580           << " " << DebugString(true);
581  // Make sure the initial file name is set only once.
582  DCHECK(full_path_.empty());
583  DCHECK(!path.empty());
584
585  full_path_ = path;
586  safety_state_ = GetSafetyState(is_dangerous_file, is_dangerous_url);
587  danger_type_ = GetDangerType(is_dangerous_file, is_dangerous_url);
588  path_uniquifier_ = path_uniquifier;
589  save_as_ = prompt;
590  is_extension_install_ = is_extension_install;
591  target_name_ = original_name;
592
593  if (target_name_.value().empty())
594    target_name_ = full_path_.BaseName();
595}
596
597FilePath DownloadItem::GetTargetFilePath() const {
598  return full_path_.DirName().Append(target_name_);
599}
600
601FilePath DownloadItem::GetFileNameToReportUser() const {
602  if (path_uniquifier_ > 0) {
603    FilePath name(target_name_);
604    download_util::AppendNumberToPath(&name, path_uniquifier_);
605    return name;
606  }
607  return target_name_;
608}
609
610FilePath DownloadItem::GetUserVerifiedFilePath() const {
611  if (safety_state_ == DownloadItem::SAFE)
612    return GetTargetFilePath();
613  return full_path_;
614}
615
616void DownloadItem::Init(bool start_timer) {
617  if (target_name_.value().empty())
618    target_name_ = full_path_.BaseName();
619  if (start_timer)
620    StartProgressTimer();
621  VLOG(20) << " " << __FUNCTION__ << "() " << DebugString(true);
622}
623
624// TODO(ahendrickson) -- Move |INTERRUPTED| from |IsCancelled()| to
625// |IsPartialDownload()|, when resuming interrupted downloads is implemented.
626bool DownloadItem::IsPartialDownload() const {
627  return (state_ == IN_PROGRESS);
628}
629
630bool DownloadItem::IsInProgress() const {
631  return (state_ == IN_PROGRESS);
632}
633
634bool DownloadItem::IsCancelled() const {
635  return (state_ == CANCELLED) || (state_ == INTERRUPTED);
636}
637
638bool DownloadItem::IsInterrupted() const {
639  return (state_ == INTERRUPTED);
640}
641
642bool DownloadItem::IsComplete() const {
643  return state() == COMPLETE;
644}
645
646std::string DownloadItem::DebugString(bool verbose) const {
647  std::string description =
648      base::StringPrintf("{ id_ = %d"
649                         " state = %s",
650                         id_,
651                         DebugDownloadStateString(state()));
652
653  if (verbose) {
654    description += base::StringPrintf(
655        " db_handle = %" PRId64
656        " total_bytes = %" PRId64
657        " is_paused = " "%c"
658        " is_extension_install = " "%c"
659        " is_otr = " "%c"
660        " safety_state = " "%s"
661        " url = " "\"%s\""
662        " target_name_ = \"%" PRFilePath "\""
663        " full_path = \"%" PRFilePath "\"",
664        db_handle(),
665        total_bytes(),
666        is_paused() ? 'T' : 'F',
667        is_extension_install() ? 'T' : 'F',
668        is_otr() ? 'T' : 'F',
669        DebugSafetyStateString(safety_state()),
670        url().spec().c_str(),
671        target_name_.value().c_str(),
672        full_path().value().c_str());
673  } else {
674    description += base::StringPrintf(" url = \"%s\"", url().spec().c_str());
675  }
676
677  description += " }";
678
679  return description;
680}
681