blob: 028e6d5531ca7d85481b992b5e5990dadedc3f50 [file] [log] [blame]
// Copyright (c) 2012 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.
// File method ordering: Methods in this file are in the same order as
// in download_item_impl.h, with the following exception: The public
// interface Start is placed in chronological order with the other
// (private) routines that together define a DownloadItem's state
// transitions as the download progresses. See "Download progression
// cascade" later in this file.
// A regular DownloadItem (created for a download in this session of the
// browser) normally goes through the following states:
// * Created (when download starts)
// * Destination filename determined
// * Entered into the history database.
// * Made visible in the download shelf.
// * All the data is saved. Note that the actual data download occurs
// in parallel with the above steps, but until those steps are
// complete, the state of the data save will be ignored.
// * Download file is renamed to its final name, and possibly
// auto-opened.
#include "content/browser/download/download_item_impl.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/download/download_create_info.h"
#include "content/browser/download/download_file.h"
#include "content/browser/download/download_interrupt_reasons_impl.h"
#include "content/browser/download/download_item_impl_delegate.h"
#include "content/browser/download/download_request_handle.h"
#include "content/browser/download/download_stats.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_danger_type.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_url_parameters.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "net/base/net_util.h"
namespace content {
namespace {
bool DeleteDownloadedFile(const base::FilePath& path) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
// Make sure we only delete files.
if (base::DirectoryExists(path))
return true;
return base::DeleteFile(path, false);
}
void DeleteDownloadedFileDone(
base::WeakPtr<DownloadItemImpl> item,
const base::Callback<void(bool)>& callback,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (success && item.get())
item->OnDownloadedFileRemoved();
callback.Run(success);
}
// Wrapper around DownloadFile::Detach and DownloadFile::Cancel that
// takes ownership of the DownloadFile and hence implicitly destroys it
// at the end of the function.
static base::FilePath DownloadFileDetach(
scoped_ptr<DownloadFile> download_file) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
base::FilePath full_path = download_file->FullPath();
download_file->Detach();
return full_path;
}
static void DownloadFileCancel(scoped_ptr<DownloadFile> download_file) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
download_file->Cancel();
}
bool IsDownloadResumptionEnabled() {
return base::FeatureList::IsEnabled(features::kDownloadResumption);
}
} // namespace
const uint32_t DownloadItem::kInvalidId = 0;
const char DownloadItem::kEmptyFileHash[] = "";
// The maximum number of attempts we will make to resume automatically.
const int DownloadItemImpl::kMaxAutoResumeAttempts = 5;
// Constructor for reading from the history service.
DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate,
uint32_t download_id,
const base::FilePath& current_path,
const base::FilePath& target_path,
const std::vector<GURL>& url_chain,
const GURL& referrer_url,
const std::string& mime_type,
const std::string& original_mime_type,
const base::Time& start_time,
const base::Time& end_time,
const std::string& etag,
const std::string& last_modified,
int64_t received_bytes,
int64_t total_bytes,
DownloadItem::DownloadState state,
DownloadDangerType danger_type,
DownloadInterruptReason interrupt_reason,
bool opened,
const net::BoundNetLog& bound_net_log)
: is_save_package_download_(false),
download_id_(download_id),
current_path_(current_path),
target_path_(target_path),
target_disposition_(TARGET_DISPOSITION_OVERWRITE),
url_chain_(url_chain),
referrer_url_(referrer_url),
transition_type_(ui::PAGE_TRANSITION_LINK),
has_user_gesture_(false),
mime_type_(mime_type),
original_mime_type_(original_mime_type),
total_bytes_(total_bytes),
received_bytes_(received_bytes),
bytes_per_sec_(0),
last_modified_time_(last_modified),
etag_(etag),
last_reason_(interrupt_reason),
start_tick_(base::TimeTicks()),
state_(ExternalToInternalState(state)),
danger_type_(danger_type),
start_time_(start_time),
end_time_(end_time),
delegate_(delegate),
is_paused_(false),
auto_resume_count_(0),
open_when_complete_(false),
file_externally_removed_(false),
auto_opened_(false),
is_temporary_(false),
all_data_saved_(state == COMPLETE),
destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
opened_(opened),
delegate_delayed_complete_(false),
bound_net_log_(bound_net_log),
weak_ptr_factory_(this) {
delegate_->Attach();
DCHECK(state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL ||
state_ == CANCELLED_INTERNAL);
Init(false /* not actively downloading */, SRC_HISTORY_IMPORT);
}
// Constructing for a regular download:
DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate,
uint32_t download_id,
const DownloadCreateInfo& info,
const net::BoundNetLog& bound_net_log)
: is_save_package_download_(false),
download_id_(download_id),
target_disposition_((info.save_info->prompt_for_save_location)
? TARGET_DISPOSITION_PROMPT
: TARGET_DISPOSITION_OVERWRITE),
url_chain_(info.url_chain),
referrer_url_(info.referrer_url),
tab_url_(info.tab_url),
tab_referrer_url_(info.tab_referrer_url),
suggested_filename_(base::UTF16ToUTF8(info.save_info->suggested_name)),
forced_file_path_(info.save_info->file_path),
transition_type_(info.transition_type),
has_user_gesture_(info.has_user_gesture),
content_disposition_(info.content_disposition),
mime_type_(info.mime_type),
original_mime_type_(info.original_mime_type),
remote_address_(info.remote_address),
total_bytes_(info.total_bytes),
received_bytes_(0),
bytes_per_sec_(0),
last_modified_time_(info.last_modified),
etag_(info.etag),
last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
start_tick_(base::TimeTicks::Now()),
state_(INITIAL_INTERNAL),
danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
start_time_(info.start_time),
delegate_(delegate),
is_paused_(false),
auto_resume_count_(0),
open_when_complete_(false),
file_externally_removed_(false),
auto_opened_(false),
is_temporary_(!info.save_info->file_path.empty()),
all_data_saved_(false),
destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
opened_(false),
delegate_delayed_complete_(false),
bound_net_log_(bound_net_log),
weak_ptr_factory_(this) {
delegate_->Attach();
Init(true /* actively downloading */, SRC_ACTIVE_DOWNLOAD);
// Link the event sources.
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_URL_REQUEST,
info.request_bound_net_log.source().ToEventParametersCallback());
info.request_bound_net_log.AddEvent(
net::NetLog::TYPE_DOWNLOAD_STARTED,
bound_net_log_.source().ToEventParametersCallback());
}
// Constructing for the "Save Page As..." feature:
DownloadItemImpl::DownloadItemImpl(
DownloadItemImplDelegate* delegate,
uint32_t download_id,
const base::FilePath& path,
const GURL& url,
const std::string& mime_type,
scoped_ptr<DownloadRequestHandleInterface> request_handle,
const net::BoundNetLog& bound_net_log)
: is_save_package_download_(true),
request_handle_(std::move(request_handle)),
download_id_(download_id),
current_path_(path),
target_path_(path),
target_disposition_(TARGET_DISPOSITION_OVERWRITE),
url_chain_(1, url),
referrer_url_(GURL()),
transition_type_(ui::PAGE_TRANSITION_LINK),
has_user_gesture_(false),
mime_type_(mime_type),
original_mime_type_(mime_type),
total_bytes_(0),
received_bytes_(0),
bytes_per_sec_(0),
last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
start_tick_(base::TimeTicks::Now()),
state_(IN_PROGRESS_INTERNAL),
danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
start_time_(base::Time::Now()),
delegate_(delegate),
is_paused_(false),
auto_resume_count_(0),
open_when_complete_(false),
file_externally_removed_(false),
auto_opened_(false),
is_temporary_(false),
all_data_saved_(false),
destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
opened_(false),
delegate_delayed_complete_(false),
bound_net_log_(bound_net_log),
weak_ptr_factory_(this) {
delegate_->Attach();
Init(true /* actively downloading */, SRC_SAVE_PAGE_AS);
}
DownloadItemImpl::~DownloadItemImpl() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Should always have been nuked before now, at worst in
// DownloadManager shutdown.
DCHECK(!download_file_.get());
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadDestroyed(this));
delegate_->AssertStateConsistent(this);
delegate_->Detach();
}
void DownloadItemImpl::AddObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
observers_.AddObserver(observer);
}
void DownloadItemImpl::RemoveObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
observers_.RemoveObserver(observer);
}
void DownloadItemImpl::UpdateObservers() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this));
}
void DownloadItemImpl::ValidateDangerousDownload() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!IsDone());
DCHECK(IsDangerous());
DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
if (IsDone() || !IsDangerous())
return;
RecordDangerousDownloadAccept(GetDangerType(),
GetTargetFilePath());
danger_type_ = DOWNLOAD_DANGER_TYPE_USER_VALIDATED;
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED,
base::Bind(&ItemCheckedNetLogCallback, GetDangerType()));
UpdateObservers();
MaybeCompleteDownload();
}
void DownloadItemImpl::StealDangerousDownload(
const AcquireFileCallback& callback) {
DVLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(IsDangerous());
if (download_file_) {
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&DownloadFileDetach, base::Passed(&download_file_)),
callback);
} else {
callback.Run(current_path_);
}
current_path_.clear();
Remove();
// We have now been deleted.
}
void DownloadItemImpl::Pause() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Ignore irrelevant states.
if (is_paused_)
return;
switch (state_) {
case INITIAL_INTERNAL:
case COMPLETING_INTERNAL:
case COMPLETE_INTERNAL:
case INTERRUPTED_INTERNAL:
case CANCELLED_INTERNAL:
case RESUMING_INTERNAL:
// No active request.
return;
case TARGET_PENDING_INTERNAL:
case IN_PROGRESS_INTERNAL:
request_handle_->PauseRequest();
is_paused_ = true;
UpdateObservers();
return;
case MAX_DOWNLOAD_INTERNAL_STATE:
case TARGET_RESOLVED_INTERNAL:
NOTREACHED();
}
}
void DownloadItemImpl::Resume() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
switch (state_) {
case INITIAL_INTERNAL:
case COMPLETING_INTERNAL:
case COMPLETE_INTERNAL:
case CANCELLED_INTERNAL:
// Nothing to resume.
case RESUMING_INTERNAL:
// Resumption in progress.
DCHECK(!is_paused_);
return;
case TARGET_PENDING_INTERNAL:
case IN_PROGRESS_INTERNAL:
if (!is_paused_)
return;
request_handle_->ResumeRequest();
is_paused_ = false;
UpdateObservers();
return;
case INTERRUPTED_INTERNAL:
auto_resume_count_ = 0; // User input resets the counter.
ResumeInterruptedDownload();
return;
case MAX_DOWNLOAD_INTERNAL_STATE:
case TARGET_RESOLVED_INTERNAL:
NOTREACHED();
}
}
void DownloadItemImpl::Cancel(bool user_cancel) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
Interrupt(user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
: DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN);
}
void DownloadItemImpl::Remove() {
DVLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->AssertStateConsistent(this);
Interrupt(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
delegate_->AssertStateConsistent(this);
NotifyRemoved();
delegate_->DownloadRemoved(this);
// We have now been deleted.
}
void DownloadItemImpl::OpenDownload() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsDone()) {
// We don't honor the open_when_complete_ flag for temporary
// downloads. Don't set it because it shows up in the UI.
if (!IsTemporary())
open_when_complete_ = !open_when_complete_;
return;
}
if (state_ != COMPLETE_INTERNAL || file_externally_removed_)
return;
// Ideally, we want to detect errors in opening and report them, but we
// don't generally have the proper interface for that to the external
// program that opens the file. So instead we spawn a check to update
// the UI if the file has been deleted in parallel with the open.
delegate_->CheckForFileRemoval(this);
RecordOpen(GetEndTime(), !GetOpened());
opened_ = true;
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this));
delegate_->OpenDownload(this);
}
void DownloadItemImpl::ShowDownloadInShell() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->ShowDownloadInShell(this);
}
uint32_t DownloadItemImpl::GetId() const {
return download_id_;
}
DownloadItem::DownloadState DownloadItemImpl::GetState() const {
return InternalToExternalState(state_);
}
DownloadInterruptReason DownloadItemImpl::GetLastReason() const {
return last_reason_;
}
bool DownloadItemImpl::IsPaused() const {
return is_paused_;
}
bool DownloadItemImpl::IsTemporary() const {
return is_temporary_;
}
bool DownloadItemImpl::CanResume() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
switch (state_) {
case INITIAL_INTERNAL:
case COMPLETING_INTERNAL:
case COMPLETE_INTERNAL:
case CANCELLED_INTERNAL:
case RESUMING_INTERNAL:
return false;
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
case IN_PROGRESS_INTERNAL:
return is_paused_;
case INTERRUPTED_INTERNAL: {
ResumeMode resume_mode = GetResumeMode();
// Only allow Resume() calls if the resumption mode requires a user
// action.
return IsDownloadResumptionEnabled() &&
(resume_mode == RESUME_MODE_USER_RESTART ||
resume_mode == RESUME_MODE_USER_CONTINUE);
}
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
}
return false;
}
bool DownloadItemImpl::IsDone() const {
switch (state_) {
case INITIAL_INTERNAL:
case COMPLETING_INTERNAL:
case RESUMING_INTERNAL:
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
case IN_PROGRESS_INTERNAL:
return false;
case COMPLETE_INTERNAL:
case CANCELLED_INTERNAL:
return true;
case INTERRUPTED_INTERNAL:
return !CanResume();
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
}
return false;
}
const GURL& DownloadItemImpl::GetURL() const {
return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.back();
}
const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const {
return url_chain_;
}
const GURL& DownloadItemImpl::GetOriginalUrl() const {
// Be careful about taking the front() of possibly-empty vectors!
// http://crbug.com/190096
return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.front();
}
const GURL& DownloadItemImpl::GetReferrerUrl() const {
return referrer_url_;
}
const GURL& DownloadItemImpl::GetTabUrl() const {
return tab_url_;
}
const GURL& DownloadItemImpl::GetTabReferrerUrl() const {
return tab_referrer_url_;
}
std::string DownloadItemImpl::GetSuggestedFilename() const {
return suggested_filename_;
}
std::string DownloadItemImpl::GetContentDisposition() const {
return content_disposition_;
}
std::string DownloadItemImpl::GetMimeType() const {
return mime_type_;
}
std::string DownloadItemImpl::GetOriginalMimeType() const {
return original_mime_type_;
}
std::string DownloadItemImpl::GetRemoteAddress() const {
return remote_address_;
}
bool DownloadItemImpl::HasUserGesture() const {
return has_user_gesture_;
};
ui::PageTransition DownloadItemImpl::GetTransitionType() const {
return transition_type_;
};
const std::string& DownloadItemImpl::GetLastModifiedTime() const {
return last_modified_time_;
}
const std::string& DownloadItemImpl::GetETag() const {
return etag_;
}
bool DownloadItemImpl::IsSavePackageDownload() const {
return is_save_package_download_;
}
const base::FilePath& DownloadItemImpl::GetFullPath() const {
return current_path_;
}
const base::FilePath& DownloadItemImpl::GetTargetFilePath() const {
return target_path_;
}
const base::FilePath& DownloadItemImpl::GetForcedFilePath() const {
// TODO(asanka): Get rid of GetForcedFilePath(). We should instead just
// require that clients respect GetTargetFilePath() if it is already set.
return forced_file_path_;
}
base::FilePath DownloadItemImpl::GetFileNameToReportUser() const {
if (!display_name_.empty())
return display_name_;
return target_path_.BaseName();
}
DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const {
return target_disposition_;
}
const std::string& DownloadItemImpl::GetHash() const {
return hash_;
}
const std::string& DownloadItemImpl::GetHashState() const {
return hash_state_;
}
bool DownloadItemImpl::GetFileExternallyRemoved() const {
return file_externally_removed_;
}
void DownloadItemImpl::DeleteFile(const base::Callback<void(bool)>& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (GetState() != DownloadItem::COMPLETE) {
// Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DeleteDownloadedFileDone,
base::WeakPtr<DownloadItemImpl>(), callback, false));
return;
}
if (current_path_.empty() || file_externally_removed_) {
// Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DeleteDownloadedFileDone,
base::WeakPtr<DownloadItemImpl>(), callback, true));
return;
}
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE, FROM_HERE,
base::Bind(&DeleteDownloadedFile, current_path_),
base::Bind(&DeleteDownloadedFileDone,
weak_ptr_factory_.GetWeakPtr(), callback));
}
bool DownloadItemImpl::IsDangerous() const {
return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT ||
danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST ||
danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED);
}
DownloadDangerType DownloadItemImpl::GetDangerType() const {
return danger_type_;
}
bool DownloadItemImpl::TimeRemaining(base::TimeDelta* remaining) const {
if (total_bytes_ <= 0)
return false; // We never received the content_length for this download.
int64_t speed = CurrentSpeed();
if (speed == 0)
return false;
*remaining = base::TimeDelta::FromSeconds(
(total_bytes_ - received_bytes_) / speed);
return true;
}
int64_t DownloadItemImpl::CurrentSpeed() const {
if (is_paused_)
return 0;
return bytes_per_sec_;
}
int DownloadItemImpl::PercentComplete() const {
// If the delegate is delaying completion of the download, then we have no
// idea how long it will take.
if (delegate_delayed_complete_ || total_bytes_ <= 0)
return -1;
return static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
}
bool DownloadItemImpl::AllDataSaved() const {
return all_data_saved_;
}
int64_t DownloadItemImpl::GetTotalBytes() const {
return total_bytes_;
}
int64_t DownloadItemImpl::GetReceivedBytes() const {
return received_bytes_;
}
base::Time DownloadItemImpl::GetStartTime() const {
return start_time_;
}
base::Time DownloadItemImpl::GetEndTime() const {
return end_time_;
}
bool DownloadItemImpl::CanShowInFolder() {
// A download can be shown in the folder if the downloaded file is in a known
// location.
return CanOpenDownload() && !GetFullPath().empty();
}
bool DownloadItemImpl::CanOpenDownload() {
// We can open the file or mark it for opening on completion if the download
// is expected to complete successfully. Exclude temporary downloads, since
// they aren't owned by the download system.
const bool is_complete = GetState() == DownloadItem::COMPLETE;
return (!IsDone() || is_complete) && !IsTemporary() &&
!file_externally_removed_;
}
bool DownloadItemImpl::ShouldOpenFileBasedOnExtension() {
return delegate_->ShouldOpenFileBasedOnExtension(GetTargetFilePath());
}
bool DownloadItemImpl::GetOpenWhenComplete() const {
return open_when_complete_;
}
bool DownloadItemImpl::GetAutoOpened() {
return auto_opened_;
}
bool DownloadItemImpl::GetOpened() const {
return opened_;
}
BrowserContext* DownloadItemImpl::GetBrowserContext() const {
return delegate_->GetBrowserContext();
}
WebContents* DownloadItemImpl::GetWebContents() const {
// TODO(rdsmith): Remove null check after removing GetWebContents() from
// paths that might be used by DownloadItems created from history import.
// Currently such items have null request_handle_s, where other items
// (regular and SavePackage downloads) have actual objects off the pointer.
if (request_handle_)
return request_handle_->GetWebContents();
return NULL;
}
void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(AllDataSaved());
// Danger type is only allowed to be set on an active download after all data
// has been saved. This excludes all other states. In particular,
// OnContentCheckCompleted() isn't allowed on an INTERRUPTED download since
// such an interruption would need to happen between OnAllDataSaved() and
// OnContentCheckCompleted() during which no disk or network activity
// should've taken place.
DCHECK_EQ(state_, IN_PROGRESS_INTERNAL);
DVLOG(20) << __FUNCTION__ << " danger_type=" << danger_type
<< " download=" << DebugString(true);
SetDangerType(danger_type);
UpdateObservers();
}
void DownloadItemImpl::SetOpenWhenComplete(bool open) {
open_when_complete_ = open;
}
void DownloadItemImpl::SetIsTemporary(bool temporary) {
is_temporary_ = temporary;
}
void DownloadItemImpl::SetOpened(bool opened) {
opened_ = opened;
}
void DownloadItemImpl::SetDisplayName(const base::FilePath& name) {
display_name_ = name;
}
std::string DownloadItemImpl::DebugString(bool verbose) const {
std::string description =
base::StringPrintf("{ id = %d"
" state = %s",
download_id_,
DebugDownloadStateString(state_));
// Construct a string of the URL chain.
std::string url_list("<none>");
if (!url_chain_.empty()) {
std::vector<GURL>::const_iterator iter = url_chain_.begin();
std::vector<GURL>::const_iterator last = url_chain_.end();
url_list = (*iter).is_valid() ? (*iter).spec() : "<invalid>";
++iter;
for ( ; verbose && (iter != last); ++iter) {
url_list += " ->\n\t";
const GURL& next_url = *iter;
url_list += next_url.is_valid() ? next_url.spec() : "<invalid>";
}
}
if (verbose) {
description += base::StringPrintf(
" total = %" PRId64
" received = %" PRId64
" reason = %s"
" paused = %c"
" resume_mode = %s"
" auto_resume_count = %d"
" danger = %d"
" all_data_saved = %c"
" last_modified = '%s'"
" etag = '%s'"
" has_download_file = %s"
" url_chain = \n\t\"%s\"\n\t"
" full_path = \"%" PRFilePath "\"\n\t"
" target_path = \"%" PRFilePath "\"",
GetTotalBytes(),
GetReceivedBytes(),
DownloadInterruptReasonToString(last_reason_).c_str(),
IsPaused() ? 'T' : 'F',
DebugResumeModeString(GetResumeMode()),
auto_resume_count_,
GetDangerType(),
AllDataSaved() ? 'T' : 'F',
GetLastModifiedTime().c_str(),
GetETag().c_str(),
download_file_.get() ? "true" : "false",
url_list.c_str(),
GetFullPath().value().c_str(),
GetTargetFilePath().value().c_str());
} else {
description += base::StringPrintf(" url = \"%s\"", url_list.c_str());
}
description += " }";
return description;
}
DownloadItemImpl::ResumeMode DownloadItemImpl::GetResumeMode() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsDownloadResumptionEnabled())
return RESUME_MODE_INVALID;
// Only support resumption for HTTP(S).
if (!GetURL().SchemeIsHTTPOrHTTPS())
return RESUME_MODE_INVALID;
// We can't continue without a handle on the intermediate file.
// We also can't continue if we don't have some verifier to make sure
// we're getting the same file.
const bool force_restart =
(current_path_.empty() || (etag_.empty() && last_modified_time_.empty()));
// We won't auto-restart if we've used up our attempts or the
// download has been paused by user action.
const bool force_user =
(auto_resume_count_ >= kMaxAutoResumeAttempts || is_paused_);
ResumeMode mode = RESUME_MODE_INVALID;
switch(last_reason_) {
case DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
case DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
if (force_restart && force_user)
mode = RESUME_MODE_USER_RESTART;
else if (force_restart)
mode = RESUME_MODE_IMMEDIATE_RESTART;
else if (force_user)
mode = RESUME_MODE_USER_CONTINUE;
else
mode = RESUME_MODE_IMMEDIATE_CONTINUE;
break;
case DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE:
case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
if (force_user)
mode = RESUME_MODE_USER_RESTART;
else
mode = RESUME_MODE_IMMEDIATE_RESTART;
break;
case DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
case DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
case DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
case DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST:
case DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
case DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
case DOWNLOAD_INTERRUPT_REASON_CRASH:
if (force_restart)
mode = RESUME_MODE_USER_RESTART;
else
mode = RESUME_MODE_USER_CONTINUE;
break;
case DOWNLOAD_INTERRUPT_REASON_FILE_FAILED:
case DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
case DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
mode = RESUME_MODE_USER_RESTART;
break;
case DOWNLOAD_INTERRUPT_REASON_NONE:
case DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
case DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
case DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
case DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
case DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED:
case DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM:
case DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN:
mode = RESUME_MODE_INVALID;
break;
}
return mode;
}
void DownloadItemImpl::MergeOriginInfoOnResume(
const DownloadCreateInfo& new_create_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(RESUMING_INTERNAL, state_);
DCHECK(!new_create_info.url_chain.empty());
// We are going to tack on any new redirects to our list of redirects.
// When a download is resumed, the URL used for the resumption request is the
// one at the end of the previous redirect chain. Tacking additional redirects
// to the end of this chain ensures that:
// - If the download needs to be resumed again, the ETag/Last-Modified headers
// will be used with the last server that sent them to us.
// - The redirect chain contains all the servers that were involved in this
// download since the initial request, in order.
std::vector<GURL>::const_iterator chain_iter =
new_create_info.url_chain.begin();
if (*chain_iter == url_chain_.back())
++chain_iter;
// Record some stats. If the precondition failed (the server returned
// HTTP_PRECONDITION_FAILED), then the download will automatically retried as
// a full request rather than a partial. Full restarts clobber validators.
int origin_state = 0;
if (chain_iter != new_create_info.url_chain.end())
origin_state |= ORIGIN_STATE_ON_RESUMPTION_ADDITIONAL_REDIRECTS;
if (etag_ != new_create_info.etag ||
last_modified_time_ != new_create_info.last_modified)
origin_state |= ORIGIN_STATE_ON_RESUMPTION_VALIDATORS_CHANGED;
if (content_disposition_ != new_create_info.content_disposition)
origin_state |= ORIGIN_STATE_ON_RESUMPTION_CONTENT_DISPOSITION_CHANGED;
RecordOriginStateOnResumption(new_create_info.save_info->offset != 0,
origin_state);
url_chain_.insert(
url_chain_.end(), chain_iter, new_create_info.url_chain.end());
etag_ = new_create_info.etag;
last_modified_time_ = new_create_info.last_modified;
content_disposition_ = new_create_info.content_disposition;
// Don't update observers. This method is expected to be called just before a
// DownloadFile is created and Start() is called. The observers will be
// notified when the download transitions to the IN_PROGRESS state.
}
void DownloadItemImpl::NotifyRemoved() {
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadRemoved(this));
}
void DownloadItemImpl::OnDownloadedFileRemoved() {
file_externally_removed_ = true;
DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
UpdateObservers();
}
base::WeakPtr<DownloadDestinationObserver>
DownloadItemImpl::DestinationObserverAsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
const net::BoundNetLog& DownloadItemImpl::GetBoundNetLog() const {
return bound_net_log_;
}
void DownloadItemImpl::SetTotalBytes(int64_t total_bytes) {
total_bytes_ = total_bytes;
}
void DownloadItemImpl::OnAllDataSaved(const std::string& final_hash) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!all_data_saved_);
all_data_saved_ = true;
DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
// Store final hash and null out intermediate serialized hash state.
hash_ = final_hash;
hash_state_ = "";
UpdateObservers();
}
void DownloadItemImpl::MarkAsComplete() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(all_data_saved_);
end_time_ = base::Time::Now();
TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS);
}
void DownloadItemImpl::DestinationUpdate(int64_t bytes_so_far,
int64_t bytes_per_sec,
const std::string& hash_state) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If the download is in any other state we don't expect any
// DownloadDestinationObserver callbacks. An interruption or a cancellation
// results in a call to ReleaseDownloadFile which invalidates the weak
// reference held by the DownloadFile and hence cuts off any pending
// callbacks.
DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
DVLOG(20) << __FUNCTION__ << " so_far=" << bytes_so_far
<< " per_sec=" << bytes_per_sec
<< " download=" << DebugString(true);
bytes_per_sec_ = bytes_per_sec;
hash_state_ = hash_state;
received_bytes_ = bytes_so_far;
// If we've received more data than we were expecting (bad server info?),
// revert to 'unknown size mode'.
if (received_bytes_ > total_bytes_)
total_bytes_ = 0;
if (bound_net_log_.IsCapturing()) {
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_UPDATED,
net::NetLog::Int64Callback("bytes_so_far", received_bytes_));
}
UpdateObservers();
}
void DownloadItemImpl::DestinationError(DownloadInterruptReason reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If the download is in any other state we don't expect any
// DownloadDestinationObserver callbacks. An interruption or a cancellation
// results in a call to ReleaseDownloadFile which invalidates the weak
// reference held by the DownloadFile and hence cuts off any pending
// callbacks.
DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
DVLOG(20) << __FUNCTION__
<< "() reason:" << DownloadInterruptReasonToString(reason);
// Postpone recognition of this error until after file name determination
// has completed and the intermediate file has been renamed to simplify
// resumption conditions.
if (state_ != IN_PROGRESS_INTERNAL)
destination_error_ = reason;
else
Interrupt(reason);
}
void DownloadItemImpl::DestinationCompleted(const std::string& final_hash) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If the download is in any other state we don't expect any
// DownloadDestinationObserver callbacks. An interruption or a cancellation
// results in a call to ReleaseDownloadFile which invalidates the weak
// reference held by the DownloadFile and hence cuts off any pending
// callbacks.
DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
OnAllDataSaved(final_hash);
MaybeCompleteDownload();
}
// **** Download progression cascade
void DownloadItemImpl::Init(bool active,
DownloadType download_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (active)
RecordDownloadCount(START_COUNT);
std::string file_name;
if (download_type == SRC_HISTORY_IMPORT) {
// target_path_ works for History and Save As versions.
file_name = target_path_.AsUTF8Unsafe();
} else {
// See if it's set programmatically.
file_name = forced_file_path_.AsUTF8Unsafe();
// Possibly has a 'download' attribute for the anchor.
if (file_name.empty())
file_name = suggested_filename_;
// From the URL file name.
if (file_name.empty())
file_name = GetURL().ExtractFileName();
}
net::NetLog::ParametersCallback active_data =
base::Bind(&ItemActivatedNetLogCallback, this, download_type, &file_name);
if (active) {
bound_net_log_.BeginEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, active_data);
} else {
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, active_data);
}
DVLOG(20) << __FUNCTION__ << "() " << DebugString(true);
}
// We're starting the download.
void DownloadItemImpl::Start(
scoped_ptr<DownloadFile> file,
scoped_ptr<DownloadRequestHandleInterface> req_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!download_file_.get());
DCHECK(file.get());
DCHECK(req_handle.get());
DVLOG(20) << __FUNCTION__ << "() this=" << DebugString(true);
download_file_ = std::move(file);
request_handle_ = std::move(req_handle);
if (state_ == CANCELLED_INTERNAL) {
// The download was in the process of resuming when it was cancelled. Don't
// proceed.
ReleaseDownloadFile(true);
request_handle_->CancelRequest();
return;
}
TransitionTo(TARGET_PENDING_INTERNAL, UPDATE_OBSERVERS);
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&DownloadFile::Initialize,
// Safe because we control download file lifetime.
base::Unretained(download_file_.get()),
base::Bind(&DownloadItemImpl::OnDownloadFileInitialized,
weak_ptr_factory_.GetWeakPtr())));
}
void DownloadItemImpl::OnDownloadFileInitialized(
DownloadInterruptReason result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
DVLOG(20) << __FUNCTION__
<< "() result:" << DownloadInterruptReasonToString(result);
if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
// Transition out to TARGET_RESOLVED_INTERNAL since this DownloadItem is
// skipping the download target determination process.
TransitionTo(TARGET_RESOLVED_INTERNAL, DONT_UPDATE_OBSERVERS);
Interrupt(result);
// TODO(rdsmith/asanka): Arguably we should show this in the UI, but
// it's not at all clear what to show--we haven't done filename
// determination, so we don't know what name to display. OTOH,
// the failure mode of not showing the DI if the file initialization
// fails isn't a good one. Can we hack up a name based on the
// URLRequest? We'll need to make sure that initialization happens
// properly. Possibly the right thing is to have the UI handle
// this case specially.
return;
}
delegate_->DetermineDownloadTarget(
this, base::Bind(&DownloadItemImpl::OnDownloadTargetDetermined,
weak_ptr_factory_.GetWeakPtr()));
}
// Called by delegate_ when the download target path has been
// determined.
void DownloadItemImpl::OnDownloadTargetDetermined(
const base::FilePath& target_path,
TargetDisposition disposition,
DownloadDangerType danger_type,
const base::FilePath& intermediate_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
// If the |target_path| is empty, then we consider this download to be
// canceled.
if (target_path.empty()) {
Cancel(true);
return;
}
// TODO(rdsmith,asanka): We are ignoring the possibility that the download
// has been interrupted at this point until we finish the intermediate
// rename and set the full path. That's dangerous, because we might race
// with resumption, either manual (because the interrupt is visible to the
// UI) or automatic. If we keep the "ignore an error on download until file
// name determination complete" semantics, we need to make sure that the
// error is kept completely invisible until that point.
DVLOG(20) << __FUNCTION__ << " " << target_path.value() << " " << disposition
<< " " << danger_type << " " << DebugString(true);
target_path_ = target_path;
target_disposition_ = disposition;
SetDangerType(danger_type);
// We want the intermediate and target paths to refer to the same directory so
// that they are both on the same device and subject to same
// space/permission/availability constraints.
DCHECK(intermediate_path.DirName() == target_path.DirName());
// During resumption, we may choose to proceed with the same intermediate
// file. No rename is necessary if our intermediate file already has the
// correct name.
//
// The intermediate name may change from its original value during filename
// determination on resumption, for example if the reason for the interruption
// was the download target running out space, resulting in a user prompt.
if (intermediate_path == current_path_) {
OnDownloadRenamedToIntermediateName(DOWNLOAD_INTERRUPT_REASON_NONE,
intermediate_path);
return;
}
// Rename to intermediate name.
// TODO(asanka): Skip this rename if AllDataSaved() is true. This avoids a
// spurious rename when we can just rename to the final
// filename. Unnecessary renames may cause bugs like
// http://crbug.com/74187.
DCHECK(!is_save_package_download_);
DCHECK(download_file_.get());
DownloadFile::RenameCompletionCallback callback =
base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName,
weak_ptr_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&DownloadFile::RenameAndUniquify,
// Safe because we control download file lifetime.
base::Unretained(download_file_.get()),
intermediate_path, callback));
}
void DownloadItemImpl::OnDownloadRenamedToIntermediateName(
DownloadInterruptReason reason,
const base::FilePath& full_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
TransitionTo(TARGET_RESOLVED_INTERNAL, DONT_UPDATE_OBSERVERS);
if (DOWNLOAD_INTERRUPT_REASON_NONE != destination_error_) {
// Process destination error. If both |reason| and |destination_error_|
// refer to actual errors, we want to use the |destination_error_| as the
// argument to the Interrupt() routine, as it happened first.
if (reason == DOWNLOAD_INTERRUPT_REASON_NONE)
SetFullPath(full_path);
Interrupt(destination_error_);
destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
} else if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
Interrupt(reason);
// All file errors result in file deletion above; no need to cleanup. The
// current_path_ should be empty. Resuming this download will force a
// restart and a re-doing of filename determination.
DCHECK(current_path_.empty());
} else {
SetFullPath(full_path);
TransitionTo(IN_PROGRESS_INTERNAL, UPDATE_OBSERVERS);
MaybeCompleteDownload();
}
}
// When SavePackage downloads MHTML to GData (see
// SavePackageFilePickerChromeOS), GData calls MaybeCompleteDownload() like it
// does for non-SavePackage downloads, but SavePackage downloads never satisfy
// IsDownloadReadyForCompletion(). GDataDownloadObserver manually calls
// DownloadItem::UpdateObservers() when the upload completes so that SavePackage
// notices that the upload has completed and runs its normal Finish() pathway.
// MaybeCompleteDownload() is never the mechanism by which SavePackage completes
// downloads. SavePackage always uses its own Finish() to mark downloads
// complete.
void DownloadItemImpl::MaybeCompleteDownload() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!is_save_package_download_);
if (!IsDownloadReadyForCompletion(
base::Bind(&DownloadItemImpl::MaybeCompleteDownload,
weak_ptr_factory_.GetWeakPtr())))
return;
// Confirm we're in the proper set of states to be here; have all data, have a
// history handle, (validated or safe).
DCHECK_EQ(IN_PROGRESS_INTERNAL, state_);
DCHECK(!IsDangerous());
DCHECK(all_data_saved_);
OnDownloadCompleting();
}
// Called by MaybeCompleteDownload() when it has determined that the download
// is ready for completion.
void DownloadItemImpl::OnDownloadCompleting() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (state_ != IN_PROGRESS_INTERNAL)
return;
DVLOG(20) << __FUNCTION__ << "()"
<< " " << DebugString(true);
DCHECK(!GetTargetFilePath().empty());
DCHECK(!IsDangerous());
// TODO(rdsmith/benjhayden): Remove as part of SavePackage integration.
if (is_save_package_download_) {
// Avoid doing anything on the file thread; there's nothing we control
// there.
// Strictly speaking, this skips giving the embedder a chance to open
// the download. But on a save package download, there's no real
// concept of opening.
Completed();
return;
}
DCHECK(download_file_.get());
// Unilaterally rename; even if it already has the right name,
// we need theannotation.
DownloadFile::RenameCompletionCallback callback =
base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName,
weak_ptr_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&DownloadFile::RenameAndAnnotate,
base::Unretained(download_file_.get()),
GetTargetFilePath(), callback));
}
void DownloadItemImpl::OnDownloadRenamedToFinalName(
DownloadInterruptReason reason,
const base::FilePath& full_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!is_save_package_download_);
// If a cancel or interrupt hit, we'll cancel the DownloadFile, which
// will result in deleting the file on the file thread. So we don't
// care about the name having been changed.
if (state_ != IN_PROGRESS_INTERNAL)
return;
DVLOG(20) << __FUNCTION__ << "()"
<< " full_path = \"" << full_path.value() << "\""
<< " " << DebugString(false);
if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
Interrupt(reason);
// All file errors should have resulted in in file deletion above. On
// resumption we will need to re-do filename determination.
DCHECK(current_path_.empty());
return;
}
DCHECK(target_path_ == full_path);
if (full_path != current_path_) {
// full_path is now the current and target file path.
DCHECK(!full_path.empty());
SetFullPath(full_path);
}
// Complete the download and release the DownloadFile.
DCHECK(download_file_);
ReleaseDownloadFile(false);
// We're not completely done with the download item yet, but at this
// point we're committed to complete the download. Cancels (or Interrupts,
// though it's not clear how they could happen) after this point will be
// ignored.
TransitionTo(COMPLETING_INTERNAL, DONT_UPDATE_OBSERVERS);
if (delegate_->ShouldOpenDownload(
this, base::Bind(&DownloadItemImpl::DelayedDownloadOpened,
weak_ptr_factory_.GetWeakPtr()))) {
Completed();
} else {
delegate_delayed_complete_ = true;
UpdateObservers();
}
}
void DownloadItemImpl::DelayedDownloadOpened(bool auto_opened) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto_opened_ = auto_opened;
Completed();
}
void DownloadItemImpl::Completed() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << __FUNCTION__ << "() " << DebugString(false);
DCHECK(all_data_saved_);
end_time_ = base::Time::Now();
TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS);
RecordDownloadCompleted(start_tick_, received_bytes_);
if (auto_opened_) {
// If it was already handled by the delegate, do nothing.
} else if (GetOpenWhenComplete() ||
ShouldOpenFileBasedOnExtension() ||
IsTemporary()) {
// If the download is temporary, like in drag-and-drop, do not open it but
// we still need to set it auto-opened so that it can be removed from the
// download shelf.
if (!IsTemporary())
OpenDownload();
auto_opened_ = true;
UpdateObservers();
}
}
void DownloadItemImpl::OnResumeRequestStarted(
DownloadItem* item,
DownloadInterruptReason interrupt_reason) {
// If |item| is not NULL, then Start() has been called already, and nothing
// more needs to be done here.
if (item) {
DCHECK_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
DCHECK_EQ(static_cast<DownloadItem*>(this), item);
return;
}
// Otherwise, the request failed without passing through
// DownloadResourceHandler::OnResponseStarted.
DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
Interrupt(interrupt_reason);
}
// **** End of Download progression cascade
// An error occurred somewhere.
void DownloadItemImpl::Interrupt(DownloadInterruptReason reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason);
DVLOG(20) << __FUNCTION__
<< "() reason:" << DownloadInterruptReasonToString(reason)
<< " this=" << DebugString(true);
// Somewhat counter-intuitively, it is possible for us to receive an
// interrupt after we've already been interrupted. The generation of
// interrupts from the file thread Renames and the generation of
// interrupts from disk writes go through two different mechanisms (driven
// by rename requests from UI thread and by write requests from IO thread,
// respectively), and since we choose not to keep state on the File thread,
// this is the place where the races collide. It's also possible for
// interrupts to race with cancels.
switch (state_) {
case CANCELLED_INTERNAL:
// If the download is already cancelled, then there's no point in
// transitioning out to interrupted.
case COMPLETING_INTERNAL:
case COMPLETE_INTERNAL:
// Already complete.
return;
case INITIAL_INTERNAL:
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
return;
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
case IN_PROGRESS_INTERNAL:
// last_reason_ needs to be set for GetResumeMode() to work.
last_reason_ = reason;
if (download_file_) {
ResumeMode resume_mode = GetResumeMode();
ReleaseDownloadFile(resume_mode != RESUME_MODE_IMMEDIATE_CONTINUE &&
resume_mode != RESUME_MODE_USER_CONTINUE);
}
break;
case RESUMING_INTERNAL:
case INTERRUPTED_INTERNAL:
// The first non-cancel interrupt reason wins in cases where multiple
// things go wrong.
if (reason != DOWNLOAD_INTERRUPT_REASON_USER_CANCELED &&
reason != DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN)
return;
last_reason_ = reason;
if (!current_path_.empty()) {
// There is no download file and this is transitioning from INTERRUPTED
// to CANCELLED. The intermediate file is no longer usable, and should
// be deleted.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(base::IgnoreResult(&DeleteDownloadedFile),
current_path_));
current_path_.clear();
}
break;
}
// Reset all data saved, as even if we did save all the data we're going to go
// through another round of downloading when we resume. There's a potential
// problem here in the abstract, as if we did download all the data and then
// run into a continuable error, on resumption we won't download any more
// data. However, a) there are currently no continuable errors that can occur
// after we download all the data, and b) if there were, that would probably
// simply result in a null range request, which would generate a
// DestinationCompleted() notification from the DownloadFile, which would
// behave properly with setting all_data_saved_ to false here.
all_data_saved_ = false;
if (request_handle_)
request_handle_->CancelRequest();
if (reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED ||
reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN) {
if (IsDangerous()) {
RecordDangerousDownloadDiscard(
reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
? DOWNLOAD_DISCARD_DUE_TO_USER_ACTION
: DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN,
GetDangerType(), GetTargetFilePath());
}
RecordDownloadCount(CANCELLED_COUNT);
TransitionTo(CANCELLED_INTERNAL, DONT_UPDATE_OBSERVERS);
} else {
RecordDownloadInterrupted(reason, received_bytes_, total_bytes_);
if (!GetWebContents())
RecordDownloadCount(INTERRUPTED_WITHOUT_WEBCONTENTS);
TransitionTo(INTERRUPTED_INTERNAL, DONT_UPDATE_OBSERVERS);
AutoResumeIfValid();
}
UpdateObservers();
}
void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << __FUNCTION__ << "() destroy_file:" << destroy_file;
if (destroy_file) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
// Will be deleted at end of task execution.
base::Bind(&DownloadFileCancel, base::Passed(&download_file_)));
// Avoid attempting to reuse the intermediate file by clearing out
// current_path_.
current_path_.clear();
} else {
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(base::IgnoreResult(&DownloadFileDetach),
// Will be deleted at end of task execution.
base::Passed(&download_file_)));
}
// Don't accept any more messages from the DownloadFile, and null
// out any previous "all data received". This also breaks links to
// other entities we've given out weak pointers to.
weak_ptr_factory_.InvalidateWeakPtrs();
}
bool DownloadItemImpl::IsDownloadReadyForCompletion(
const base::Closure& state_change_notification) {
// If the download hasn't progressed to the IN_PROGRESS state, then it's not
// ready for completion.
if (state_ != IN_PROGRESS_INTERNAL)
return false;
// If we don't have all the data, the download is not ready for
// completion.
if (!AllDataSaved())
return false;
// If the download is dangerous, but not yet validated, it's not ready for
// completion.
if (IsDangerous())
return false;
// Invariants for the IN_PROGRESS state. DCHECKs here verify that the
// invariants are still true.
DCHECK(!target_path_.empty());
DCHECK(!current_path_.empty());
DCHECK(target_path_.DirName() == current_path_.DirName());
// Give the delegate a chance to hold up a stop sign. It'll call
// use back through the passed callback if it does and that state changes.
if (!delegate_->ShouldCompleteDownload(this, state_change_notification))
return false;
return true;
}
void DownloadItemImpl::TransitionTo(DownloadInternalState new_state,
ShouldUpdateObservers notify_action) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (state_ == new_state)
return;
DownloadInternalState old_state = state_;
state_ = new_state;
DCHECK(is_save_package_download_
? IsValidSavePackageStateTransition(old_state, new_state)
: IsValidStateTransition(old_state, new_state))
<< "Invalid state transition from:" << DebugDownloadStateString(old_state)
<< " to:" << DebugDownloadStateString(new_state);
switch (state_) {
case INITIAL_INTERNAL:
NOTREACHED();
break;
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
break;
case IN_PROGRESS_INTERNAL:
DCHECK(!current_path_.empty()) << "Current output path must be known.";
DCHECK(!target_path_.empty()) << "Target path must be known.";
DCHECK(current_path_.DirName() == target_path_.DirName())
<< "Current output directory must match target directory.";
DCHECK(download_file_) << "Output file must be owned by download item.";
DCHECK(request_handle_) << "Download source must be active.";
DCHECK(!is_paused_) << "At the time a download enters IN_PROGRESS state, "
"it must not be paused.";
break;
case COMPLETING_INTERNAL:
DCHECK(all_data_saved_) << "All data must be saved prior to completion.";
DCHECK(!download_file_)
<< "Download file must be released prior to completion.";
DCHECK(!target_path_.empty()) << "Target path must be known.";
DCHECK(current_path_ == target_path_)
<< "Current output path must match target path.";
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_COMPLETING,
base::Bind(&ItemCompletingNetLogCallback, received_bytes_, &hash_));
break;
case COMPLETE_INTERNAL:
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_FINISHED,
base::Bind(&ItemFinishedNetLogCallback, auto_opened_));
break;
case INTERRUPTED_INTERNAL:
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_INTERRUPTED,
base::Bind(&ItemInterruptedNetLogCallback, last_reason_,
received_bytes_, &hash_state_));
break;
case RESUMING_INTERNAL:
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_RESUMED,
base::Bind(&ItemResumingNetLogCallback, false, last_reason_,
received_bytes_, &hash_state_));
break;
case CANCELLED_INTERNAL:
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_CANCELED,
base::Bind(&ItemCanceledNetLogCallback, received_bytes_,
&hash_state_));
break;
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
break;
}
DVLOG(20) << " " << __FUNCTION__ << "()"
<< " from:" << DebugDownloadStateString(old_state)
<< " to:" << DebugDownloadStateString(state_)
<< " this = " << DebugString(true);
bool is_done =
(state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL ||
state_ == RESUMING_INTERNAL || state_ == CANCELLED_INTERNAL);
bool was_done =
(old_state == COMPLETE_INTERNAL || old_state == INTERRUPTED_INTERNAL ||
old_state == RESUMING_INTERNAL || old_state == CANCELLED_INTERNAL);
// Termination
if (is_done && !was_done)
bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE);
// Resumption
if (was_done && !is_done) {
std::string file_name(target_path_.BaseName().AsUTF8Unsafe());
bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE,
base::Bind(&ItemActivatedNetLogCallback,
this, SRC_ACTIVE_DOWNLOAD,
&file_name));
}
if (notify_action == UPDATE_OBSERVERS)
UpdateObservers();
}
void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) {
if (danger_type != danger_type_) {
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED,
base::Bind(&ItemCheckedNetLogCallback, danger_type));
}
// Only record the Malicious UMA stat if it's going from {not malicious} ->
// {malicious}.
if ((danger_type_ == DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS ||
danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT ||
danger_type_ == DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT) &&
(danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST ||
danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
danger_type == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED)) {
RecordMaliciousDownloadClassified(danger_type);
}
danger_type_ = danger_type;
}
void DownloadItemImpl::SetFullPath(const base::FilePath& new_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(20) << __FUNCTION__ << "()"
<< " new_path = \"" << new_path.value() << "\""
<< " " << DebugString(true);
DCHECK(!new_path.empty());
bound_net_log_.AddEvent(
net::NetLog::TYPE_DOWNLOAD_ITEM_RENAMED,
base::Bind(&ItemRenamedNetLogCallback, &current_path_, &new_path));
current_path_ = new_path;
}
void DownloadItemImpl::AutoResumeIfValid() {
DVLOG(20) << __FUNCTION__ << "() " << DebugString(true);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ResumeMode mode = GetResumeMode();
if (mode != RESUME_MODE_IMMEDIATE_RESTART &&
mode != RESUME_MODE_IMMEDIATE_CONTINUE) {
return;
}
auto_resume_count_++;
ResumeInterruptedDownload();
}
void DownloadItemImpl::ResumeInterruptedDownload() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsDownloadResumptionEnabled())
return;
// If we're not interrupted, ignore the request; our caller is drunk.
if (state_ != INTERRUPTED_INTERNAL)
return;
// Reset the appropriate state if restarting.
ResumeMode mode = GetResumeMode();
if (mode == RESUME_MODE_IMMEDIATE_RESTART ||
mode == RESUME_MODE_USER_RESTART) {
received_bytes_ = 0;
hash_state_ = "";
last_modified_time_ = "";
etag_ = "";
}
scoped_ptr<DownloadUrlParameters> download_params;
if (GetWebContents()) {
download_params =
DownloadUrlParameters::FromWebContents(GetWebContents(), GetURL());
} else {
download_params = make_scoped_ptr(new DownloadUrlParameters(
GetURL(), -1, -1, -1, GetBrowserContext()->GetResourceContext()));
}
download_params->set_file_path(GetFullPath());
download_params->set_offset(GetReceivedBytes());
download_params->set_hash_state(GetHashState());
download_params->set_last_modified(GetLastModifiedTime());
download_params->set_etag(GetETag());
download_params->set_callback(
base::Bind(&DownloadItemImpl::OnResumeRequestStarted,
weak_ptr_factory_.GetWeakPtr()));
TransitionTo(RESUMING_INTERNAL, DONT_UPDATE_OBSERVERS);
delegate_->ResumeInterruptedDownload(std::move(download_params), GetId());
// Just in case we were interrupted while paused.
is_paused_ = false;
}
// static
DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState(
DownloadInternalState internal_state) {
switch (internal_state) {
case INITIAL_INTERNAL:
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
// TODO(asanka): Introduce an externally visible state to distinguish
// between the above states and IN_PROGRESS_INTERNAL. The latter (the
// state where the download is active and has a known target) is the state
// that most external users are interested in.
case IN_PROGRESS_INTERNAL:
return IN_PROGRESS;
case COMPLETING_INTERNAL:
return IN_PROGRESS;
case COMPLETE_INTERNAL:
return COMPLETE;
case CANCELLED_INTERNAL:
return CANCELLED;
case INTERRUPTED_INTERNAL:
return INTERRUPTED;
case RESUMING_INTERNAL:
return IN_PROGRESS;
case MAX_DOWNLOAD_INTERNAL_STATE:
break;
}
NOTREACHED();
return MAX_DOWNLOAD_STATE;
}
// static
DownloadItemImpl::DownloadInternalState
DownloadItemImpl::ExternalToInternalState(
DownloadState external_state) {
switch (external_state) {
case IN_PROGRESS:
return IN_PROGRESS_INTERNAL;
case COMPLETE:
return COMPLETE_INTERNAL;
case CANCELLED:
return CANCELLED_INTERNAL;
case INTERRUPTED:
return INTERRUPTED_INTERNAL;
default:
NOTREACHED();
}
return MAX_DOWNLOAD_INTERNAL_STATE;
}
// static
bool DownloadItemImpl::IsValidSavePackageStateTransition(
DownloadInternalState from,
DownloadInternalState to) {
#if DCHECK_IS_ON()
switch (from) {
case INITIAL_INTERNAL:
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
case COMPLETING_INTERNAL:
case COMPLETE_INTERNAL:
case INTERRUPTED_INTERNAL:
case RESUMING_INTERNAL:
case CANCELLED_INTERNAL:
return false;
case IN_PROGRESS_INTERNAL:
return to == CANCELLED_INTERNAL || to == COMPLETE_INTERNAL;
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
}
return false;
#else
return true;
#endif
}
// static
bool DownloadItemImpl::IsValidStateTransition(DownloadInternalState from,
DownloadInternalState to) {
#if DCHECK_IS_ON()
switch (from) {
case INITIAL_INTERNAL:
return to == TARGET_PENDING_INTERNAL || to == INTERRUPTED_INTERNAL;
case TARGET_PENDING_INTERNAL:
return to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL;
case TARGET_RESOLVED_INTERNAL:
return to == IN_PROGRESS_INTERNAL || to == INTERRUPTED_INTERNAL ||
to == CANCELLED_INTERNAL;
case IN_PROGRESS_INTERNAL:
return to == COMPLETING_INTERNAL || to == CANCELLED_INTERNAL ||
to == INTERRUPTED_INTERNAL;
case COMPLETING_INTERNAL:
return to == COMPLETE_INTERNAL;
case COMPLETE_INTERNAL:
return false;
case INTERRUPTED_INTERNAL:
return to == RESUMING_INTERNAL || to == CANCELLED_INTERNAL;
case RESUMING_INTERNAL:
return to == TARGET_PENDING_INTERNAL || to == CANCELLED_INTERNAL;
case CANCELLED_INTERNAL:
return false;
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
}
return false;
#else
return true;
#endif // DCHECK_IS_ON()
}
const char* DownloadItemImpl::DebugDownloadStateString(
DownloadInternalState state) {
switch (state) {
case INITIAL_INTERNAL:
return "INITIAL";
case TARGET_PENDING_INTERNAL:
return "TARGET_PENDING";
case TARGET_RESOLVED_INTERNAL:
return "TARGET_RESOLVED";
case IN_PROGRESS_INTERNAL:
return "IN_PROGRESS";
case COMPLETING_INTERNAL:
return "COMPLETING";
case COMPLETE_INTERNAL:
return "COMPLETE";
case CANCELLED_INTERNAL:
return "CANCELLED";
case INTERRUPTED_INTERNAL:
return "INTERRUPTED";
case RESUMING_INTERNAL:
return "RESUMING";
case MAX_DOWNLOAD_INTERNAL_STATE:
break;
};
NOTREACHED() << "Unknown download state " << state;
return "unknown";
}
const char* DownloadItemImpl::DebugResumeModeString(ResumeMode mode) {
switch (mode) {
case RESUME_MODE_INVALID:
return "INVALID";
case RESUME_MODE_IMMEDIATE_CONTINUE:
return "IMMEDIATE_CONTINUE";
case RESUME_MODE_IMMEDIATE_RESTART:
return "IMMEDIATE_RESTART";
case RESUME_MODE_USER_CONTINUE:
return "USER_CONTINUE";
case RESUME_MODE_USER_RESTART:
return "USER_RESTART";
}
NOTREACHED() << "Unknown resume mode " << mode;
return "unknown";
}
} // namespace content