blob: f0c6c330f426582471dd3d3ac6251197c14b01f9 [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 "components/download/public/common/download_item_impl.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/guid.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "build/build_config.h"
#include "components/download/database/in_progress/download_entry.h"
#include "components/download/internal/common/download_job_impl.h"
#include "components/download/internal/common/parallel_download_utils.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_features.h"
#include "components/download/public/common/download_file.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_item_impl_delegate.h"
#include "components/download/public/common/download_job_factory.h"
#include "components/download/public/common/download_stats.h"
#include "components/download/public/common/download_task_runner.h"
#include "components/download/public/common/download_ukm_helper.h"
#include "components/download/public/common/download_url_parameters.h"
#include "components/download/public/common/download_utils.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/service_manager/public/cpp/connector.h"
#if defined(OS_ANDROID)
#include "components/download/internal/common/android/download_collection_bridge.h"
#endif // defined(OS_ANDROID)
namespace download {
namespace {
void DeleteDownloadedFileDone(base::WeakPtr<DownloadItemImpl> item,
const base::Callback<void(bool)>& callback,
bool success) {
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.
base::FilePath DownloadFileDetach(std::unique_ptr<DownloadFile> download_file) {
DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
base::FilePath full_path = download_file->FullPath();
download_file->Detach();
return full_path;
}
base::FilePath MakeCopyOfDownloadFile(DownloadFile* download_file) {
DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
base::FilePath temp_file_path;
if (!base::CreateTemporaryFile(&temp_file_path))
return base::FilePath();
if (!base::CopyFile(download_file->FullPath(), temp_file_path)) {
DeleteDownloadedFile(temp_file_path);
return base::FilePath();
}
return temp_file_path;
}
void DownloadFileCancel(std::unique_ptr<DownloadFile> download_file) {
DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
download_file->Cancel();
}
// Most of the cancellation pathways behave the same whether the cancellation
// was initiated by ther user (CANCELED) or initiated due to browser context
// shutdown (SHUTDOWN).
bool IsCancellation(DownloadInterruptReason reason) {
return reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN ||
reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED;
}
std::string GetDownloadCreationTypeNames(
DownloadItem::DownloadCreationType type) {
switch (type) {
case DownloadItem::TYPE_ACTIVE_DOWNLOAD:
return "NEW_DOWNLOAD";
case DownloadItem::TYPE_HISTORY_IMPORT:
return "HISTORY_IMPORT";
case DownloadItem::TYPE_SAVE_PAGE_AS:
return "SAVE_PAGE_AS";
default:
NOTREACHED();
return "INVALID_TYPE";
}
}
std::string GetDownloadDangerNames(DownloadDangerType type) {
switch (type) {
case DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
return "NOT_DANGEROUS";
case DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
return "DANGEROUS_FILE";
case DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
return "DANGEROUS_URL";
case DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
return "DANGEROUS_CONTENT";
case DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
return "MAYBE_DANGEROUS_CONTENT";
case DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
return "UNCOMMON_CONTENT";
case DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
return "USER_VALIDATED";
case DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
return "DANGEROUS_HOST";
case DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
return "POTENTIALLY_UNWANTED";
case DOWNLOAD_DANGER_TYPE_WHITELISTED_BY_POLICY:
return "WHITELISTED_BY_POLICY";
default:
NOTREACHED();
return "UNKNOWN_DANGER_TYPE";
}
}
class DownloadItemActivatedData
: public base::trace_event::ConvertableToTraceFormat {
public:
DownloadItemActivatedData(DownloadItem::DownloadCreationType download_type,
uint32_t download_id,
std::string original_url,
std::string final_url,
std::string file_name,
DownloadDangerType danger_type,
int64_t start_offset,
bool has_user_gesture)
: download_type_(download_type),
download_id_(download_id),
original_url_(original_url),
final_url_(final_url),
file_name_(file_name),
danger_type_(danger_type),
start_offset_(start_offset),
has_user_gesture_(has_user_gesture) {}
~DownloadItemActivatedData() override = default;
void AppendAsTraceFormat(std::string* out) const override {
out->append("{");
out->append(base::StringPrintf(
"\"type\":\"%s\",",
GetDownloadCreationTypeNames(download_type_).c_str()));
out->append(base::StringPrintf("\"id\":\"%d\",", download_id_));
out->append("\"original_url\":");
base::EscapeJSONString(original_url_, true, out);
out->append(",");
out->append("\"final_url\":");
base::EscapeJSONString(final_url_, true, out);
out->append(",");
out->append("\"file_name\":");
base::EscapeJSONString(file_name_, true, out);
out->append(",");
out->append(
base::StringPrintf("\"danger_type\":\"%s\",",
GetDownloadDangerNames(danger_type_).c_str()));
out->append(
base::StringPrintf("\"start_offset\":\"%" PRId64 "\",", start_offset_));
out->append(base::StringPrintf("\"has_user_gesture\":\"%s\"",
has_user_gesture_ ? "true" : "false"));
out->append("}");
}
private:
DownloadItem::DownloadCreationType download_type_;
uint32_t download_id_;
std::string original_url_;
std::string final_url_;
std::string file_name_;
DownloadDangerType danger_type_;
int64_t start_offset_;
bool has_user_gesture_;
DISALLOW_COPY_AND_ASSIGN(DownloadItemActivatedData);
};
} // namespace
// The maximum number of attempts we will make to resume automatically.
const int DownloadItemImpl::kMaxAutoResumeAttempts = 5;
DownloadItemImpl::RequestInfo::RequestInfo(
const std::vector<GURL>& url_chain,
const GURL& referrer_url,
const GURL& site_url,
const GURL& tab_url,
const GURL& tab_referrer_url,
const base::Optional<url::Origin>& request_initiator,
const std::string& suggested_filename,
const base::FilePath& forced_file_path,
ui::PageTransition transition_type,
bool has_user_gesture,
const std::string& remote_address,
base::Time start_time)
: url_chain(url_chain),
referrer_url(referrer_url),
site_url(site_url),
tab_url(tab_url),
tab_referrer_url(tab_referrer_url),
request_initiator(request_initiator),
suggested_filename(suggested_filename),
forced_file_path(forced_file_path),
transition_type(transition_type),
has_user_gesture(has_user_gesture),
remote_address(remote_address),
start_time(start_time) {}
DownloadItemImpl::RequestInfo::RequestInfo(const GURL& url)
: url_chain(std::vector<GURL>(1, url)), start_time(base::Time::Now()) {}
DownloadItemImpl::RequestInfo::RequestInfo() = default;
DownloadItemImpl::RequestInfo::RequestInfo(
const DownloadItemImpl::RequestInfo& other) = default;
DownloadItemImpl::RequestInfo::~RequestInfo() = default;
DownloadItemImpl::DestinationInfo::DestinationInfo(
const base::FilePath& target_path,
const base::FilePath& current_path,
int64_t received_bytes,
bool all_data_saved,
const std::string& hash,
base::Time end_time)
: target_path(target_path),
current_path(current_path),
received_bytes(received_bytes),
all_data_saved(all_data_saved),
hash(hash),
end_time(end_time) {}
DownloadItemImpl::DestinationInfo::DestinationInfo(
TargetDisposition target_disposition)
: target_disposition(target_disposition) {}
DownloadItemImpl::DestinationInfo::DestinationInfo() = default;
DownloadItemImpl::DestinationInfo::DestinationInfo(
const DownloadItemImpl::DestinationInfo& other) = default;
DownloadItemImpl::DestinationInfo::~DestinationInfo() = default;
// Constructor for reading from the history service.
DownloadItemImpl::DownloadItemImpl(
DownloadItemImplDelegate* delegate,
const std::string& guid,
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 GURL& site_url,
const GURL& tab_url,
const GURL& tab_refererr_url,
const base::Optional<url::Origin>& request_initiator,
const std::string& mime_type,
const std::string& original_mime_type,
base::Time start_time,
base::Time end_time,
const std::string& etag,
const std::string& last_modified,
int64_t received_bytes,
int64_t total_bytes,
int32_t auto_resume_count,
const std::string& hash,
DownloadItem::DownloadState state,
DownloadDangerType danger_type,
DownloadInterruptReason interrupt_reason,
bool paused,
bool allow_metered,
bool opened,
base::Time last_access_time,
bool transient,
const std::vector<DownloadItem::ReceivedSlice>& received_slices)
: request_info_(url_chain,
referrer_url,
site_url,
tab_url,
tab_refererr_url,
request_initiator,
std::string(),
base::FilePath(),
ui::PAGE_TRANSITION_LINK,
false,
std::string(),
start_time),
guid_(guid),
download_id_(download_id),
mime_type_(mime_type),
original_mime_type_(original_mime_type),
total_bytes_(total_bytes),
last_reason_(interrupt_reason),
start_tick_(base::TimeTicks()),
state_(ExternalToInternalState(state)),
danger_type_(danger_type),
delegate_(delegate),
paused_(paused),
allow_metered_(allow_metered),
opened_(opened),
last_access_time_(last_access_time),
transient_(transient),
destination_info_(target_path,
current_path,
received_bytes,
state == COMPLETE,
hash,
end_time),
auto_resume_count_(auto_resume_count),
last_modified_time_(last_modified),
etag_(etag),
received_slices_(received_slices),
is_updating_observers_(false) {
delegate_->Attach();
DCHECK(state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL ||
state_ == CANCELLED_INTERNAL);
DCHECK(base::IsValidGUID(guid_));
Init(false /* not actively downloading */, TYPE_HISTORY_IMPORT);
}
// Constructing for a regular download:
DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate,
uint32_t download_id,
const DownloadCreateInfo& info)
: request_info_(info.url_chain,
info.referrer_url,
info.site_url,
info.tab_url,
info.tab_referrer_url,
info.request_initiator,
base::UTF16ToUTF8(info.save_info->suggested_name),
info.save_info->file_path,
info.transition_type ? info.transition_type.value()
: ui::PAGE_TRANSITION_LINK,
info.has_user_gesture,
info.remote_address,
info.start_time),
guid_(info.guid.empty() ? base::GenerateGUID() : info.guid),
download_id_(download_id),
response_headers_(info.response_headers),
content_disposition_(info.content_disposition),
mime_type_(info.mime_type),
original_mime_type_(info.original_mime_type),
total_bytes_(info.total_bytes),
last_reason_(info.result),
start_tick_(base::TimeTicks::Now()),
state_(INITIAL_INTERNAL),
delegate_(delegate),
is_temporary_(!info.transient && !info.save_info->file_path.empty()),
transient_(info.transient),
destination_info_(info.save_info->prompt_for_save_location
? TARGET_DISPOSITION_PROMPT
: TARGET_DISPOSITION_OVERWRITE),
last_modified_time_(info.last_modified),
etag_(info.etag),
is_updating_observers_(false),
fetch_error_body_(info.fetch_error_body),
request_headers_(info.request_headers),
download_source_(info.download_source) {
delegate_->Attach();
Init(true /* actively downloading */, TYPE_ACTIVE_DOWNLOAD);
allow_metered_ |= delegate_->IsActiveNetworkMetered();
TRACE_EVENT_INSTANT0("download", "DownloadStarted", TRACE_EVENT_SCOPE_THREAD);
}
// 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,
std::unique_ptr<DownloadRequestHandleInterface> request_handle)
: request_info_(url),
guid_(base::GenerateGUID()),
download_id_(download_id),
mime_type_(mime_type),
original_mime_type_(mime_type),
start_tick_(base::TimeTicks::Now()),
state_(IN_PROGRESS_INTERNAL),
delegate_(delegate),
destination_info_(path, path, 0, false, std::string(), base::Time()),
is_updating_observers_(false) {
job_ = DownloadJobFactory::CreateJob(this, std::move(request_handle),
DownloadCreateInfo(), true, nullptr,
nullptr, nullptr);
delegate_->Attach();
Init(true /* actively downloading */, TYPE_SAVE_PAGE_AS);
}
DownloadItemImpl::~DownloadItemImpl() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Should always have been nuked before now, at worst in
// DownloadManager shutdown.
DCHECK(!download_file_);
CHECK(!is_updating_observers_);
for (auto& observer : observers_)
observer.OnDownloadDestroyed(this);
delegate_->Detach();
}
void DownloadItemImpl::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
observers_.AddObserver(observer);
}
void DownloadItemImpl::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
observers_.RemoveObserver(observer);
}
void DownloadItemImpl::UpdateObservers() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "()";
// Nested updates should not be allowed.
DCHECK(!is_updating_observers_);
is_updating_observers_ = true;
for (auto& observer : observers_)
observer.OnDownloadUpdated(this);
is_updating_observers_ = false;
}
void DownloadItemImpl::ValidateDangerousDownload() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!IsDone());
DCHECK(IsDangerous());
DVLOG(20) << __func__ << "() download=" << DebugString(true);
if (IsDone() || !IsDangerous())
return;
RecordDangerousDownloadAccept(GetDangerType(), GetTargetFilePath());
danger_type_ = DOWNLOAD_DANGER_TYPE_USER_VALIDATED;
TRACE_EVENT_INSTANT1("download", "DownloadItemSaftyStateUpdated",
TRACE_EVENT_SCOPE_THREAD, "danger_type",
GetDownloadDangerNames(danger_type_).c_str());
UpdateObservers(); // TODO(asanka): This is potentially unsafe. The download
// may not be in a consistent state or around at all after
// invoking observers. http://crbug.com/586610
MaybeCompleteDownload();
}
void DownloadItemImpl::StealDangerousDownload(
bool delete_file_afterward,
const AcquireFileCallback& callback) {
DVLOG(20) << __func__ << "() download = " << DebugString(true);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(IsDangerous());
DCHECK(AllDataSaved());
if (delete_file_afterward) {
if (download_file_) {
base::PostTaskAndReplyWithResult(
GetDownloadTaskRunner().get(), FROM_HERE,
base::Bind(&DownloadFileDetach, base::Passed(&download_file_)),
callback);
} else {
callback.Run(GetFullPath());
}
destination_info_.current_path.clear();
Remove();
// Download item has now been deleted.
} else if (download_file_) {
base::PostTaskAndReplyWithResult(
GetDownloadTaskRunner().get(), FROM_HERE,
base::Bind(&MakeCopyOfDownloadFile, download_file_.get()), callback);
} else {
callback.Run(GetFullPath());
}
}
void DownloadItemImpl::Pause() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Ignore irrelevant states.
if (IsPaused())
return;
switch (state_) {
case CANCELLED_INTERNAL:
case COMPLETE_INTERNAL:
case COMPLETING_INTERNAL:
return;
case INITIAL_INTERNAL:
case INTERRUPTED_INTERNAL:
case INTERRUPTED_TARGET_PENDING_INTERNAL:
case RESUMING_INTERNAL:
// No active request.
paused_ = true;
UpdateObservers();
return;
case IN_PROGRESS_INTERNAL:
case TARGET_PENDING_INTERNAL:
paused_ = true;
job_->Pause();
UpdateObservers();
return;
case MAX_DOWNLOAD_INTERNAL_STATE:
case TARGET_RESOLVED_INTERNAL:
NOTREACHED();
}
}
void DownloadItemImpl::Resume(bool user_resume) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() download = " << DebugString(true);
switch (state_) {
case CANCELLED_INTERNAL: // Nothing to resume.
case COMPLETE_INTERNAL:
case COMPLETING_INTERNAL:
case INITIAL_INTERNAL:
case INTERRUPTED_TARGET_PENDING_INTERNAL:
case RESUMING_INTERNAL: // Resumption in progress.
return;
case TARGET_PENDING_INTERNAL:
case IN_PROGRESS_INTERNAL:
if (!IsPaused())
return;
paused_ = false;
if (job_)
job_->Resume(true);
UpdateResumptionInfo(true);
UpdateObservers();
return;
case INTERRUPTED_INTERNAL:
UpdateResumptionInfo(paused_ || user_resume);
paused_ = false;
if (auto_resume_count_ >= kMaxAutoResumeAttempts)
return;
ResumeInterruptedDownload(user_resume
? ResumptionRequestSource::USER
: ResumptionRequestSource::AUTOMATIC);
UpdateObservers();
return;
case MAX_DOWNLOAD_INTERNAL_STATE:
case TARGET_RESOLVED_INTERNAL:
NOTREACHED();
}
}
void DownloadItemImpl::UpdateResumptionInfo(bool user_resume) {
if (user_resume) {
allow_metered_ |= delegate_->IsActiveNetworkMetered();
bytes_wasted_ = 0;
}
auto_resume_count_ = user_resume ? 0 : ++auto_resume_count_;
}
void DownloadItemImpl::Cancel(bool user_cancel) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() download = " << DebugString(true);
InterruptAndDiscardPartialState(
user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
: DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN);
UpdateObservers();
}
void DownloadItemImpl::Remove() {
DVLOG(20) << __func__ << "() download = " << DebugString(true);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RecordDownloadDeletion(GetEndTime(), GetMimeType());
InterruptAndDiscardPartialState(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
UpdateObservers();
NotifyRemoved();
delegate_->DownloadRemoved(this);
// We have now been deleted.
}
void DownloadItemImpl::OpenDownload() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
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());
opened_ = true;
last_access_time_ = base::Time::Now();
for (auto& observer : observers_)
observer.OnDownloadOpened(this);
delegate_->OpenDownload(this);
}
void DownloadItemImpl::ShowDownloadInShell() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Ideally, we want to detect errors in showing 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 show.
delegate_->CheckForFileRemoval(this);
delegate_->ShowDownloadInShell(this);
}
void DownloadItemImpl::RenameDownloadedFileDone(RenameDownloadCallback callback,
const base::FilePath& new_path,
DownloadRenameResult result) {
if (result == DownloadRenameResult::SUCCESS) {
destination_info_.target_path = new_path;
destination_info_.current_path = new_path;
UpdateObservers();
}
std::move(callback).Run(result);
}
void DownloadItemImpl::Rename(const base::FilePath& name,
DownloadItem::RenameDownloadCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (name.IsAbsolute()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&DownloadItemImpl::RenameDownloadedFileDone,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback), GetFullPath(),
DownloadRenameResult::FAILURE_NAME_INVALID));
return;
}
auto full_path = base::FilePath(GetFullPath().DirName()).Append(name);
base::PostTaskAndReplyWithResult(
GetDownloadTaskRunner().get(), FROM_HERE,
base::BindOnce(&download::RenameDownloadedFile, GetFullPath(), full_path),
base::BindOnce(&DownloadItemImpl::RenameDownloadedFileDone,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
full_path));
}
uint32_t DownloadItemImpl::GetId() const {
return download_id_;
}
const std::string& DownloadItemImpl::GetGuid() const {
return guid_;
}
DownloadItem::DownloadState DownloadItemImpl::GetState() const {
return InternalToExternalState(state_);
}
DownloadInterruptReason DownloadItemImpl::GetLastReason() const {
return last_reason_;
}
bool DownloadItemImpl::IsPaused() const {
return paused_;
}
bool DownloadItemImpl::AllowMetered() const {
return allow_metered_;
}
bool DownloadItemImpl::IsTemporary() const {
return is_temporary_;
}
bool DownloadItemImpl::CanResume() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
switch (state_) {
case INITIAL_INTERNAL:
case COMPLETING_INTERNAL:
case COMPLETE_INTERNAL:
case CANCELLED_INTERNAL:
case RESUMING_INTERNAL:
case INTERRUPTED_TARGET_PENDING_INTERNAL:
return false;
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
case IN_PROGRESS_INTERNAL:
return IsPaused();
case INTERRUPTED_INTERNAL: {
ResumeMode resume_mode = GetResumeMode();
// Only allow Resume() calls if the resumption mode requires a user
// action.
return resume_mode == ResumeMode::USER_RESTART ||
resume_mode == ResumeMode::USER_CONTINUE;
}
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
}
return false;
}
bool DownloadItemImpl::IsDone() const {
return IsDownloadDone(GetURL(), GetState(), GetLastReason());
}
int64_t DownloadItemImpl::GetBytesWasted() const {
return bytes_wasted_;
}
int32_t DownloadItemImpl::GetAutoResumeCount() const {
return auto_resume_count_;
}
const GURL& DownloadItemImpl::GetURL() const {
return request_info_.url_chain.empty() ? GURL::EmptyGURL()
: request_info_.url_chain.back();
}
const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const {
return request_info_.url_chain;
}
const GURL& DownloadItemImpl::GetOriginalUrl() const {
// Be careful about taking the front() of possibly-empty vectors!
// http://crbug.com/190096
return request_info_.url_chain.empty() ? GURL::EmptyGURL()
: request_info_.url_chain.front();
}
const GURL& DownloadItemImpl::GetReferrerUrl() const {
return request_info_.referrer_url;
}
const GURL& DownloadItemImpl::GetSiteUrl() const {
return request_info_.site_url;
}
const GURL& DownloadItemImpl::GetTabUrl() const {
return request_info_.tab_url;
}
const GURL& DownloadItemImpl::GetTabReferrerUrl() const {
return request_info_.tab_referrer_url;
}
const base::Optional<url::Origin>& DownloadItemImpl::GetRequestInitiator()
const {
return request_info_.request_initiator;
}
std::string DownloadItemImpl::GetSuggestedFilename() const {
return request_info_.suggested_filename;
}
const scoped_refptr<const net::HttpResponseHeaders>&
DownloadItemImpl::GetResponseHeaders() const {
return response_headers_;
}
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 request_info_.remote_address;
}
bool DownloadItemImpl::HasUserGesture() const {
return request_info_.has_user_gesture;
}
ui::PageTransition DownloadItemImpl::GetTransitionType() const {
return request_info_.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 job_ && job_->IsSavePackageDownload();
}
const base::FilePath& DownloadItemImpl::GetFullPath() const {
return destination_info_.current_path;
}
const base::FilePath& DownloadItemImpl::GetTargetFilePath() const {
return destination_info_.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 request_info_.forced_file_path;
}
base::FilePath DownloadItemImpl::GetTemporaryFilePath() const {
if (state_ == TARGET_PENDING_INTERNAL || INTERRUPTED_TARGET_PENDING_INTERNAL)
return download_file_ ? download_file_->FullPath() : base::FilePath();
return base::FilePath();
}
base::FilePath DownloadItemImpl::GetFileNameToReportUser() const {
if (!display_name_.empty())
return display_name_;
return GetTargetFilePath().BaseName();
}
DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const {
return destination_info_.target_disposition;
}
const std::string& DownloadItemImpl::GetHash() const {
return destination_info_.hash;
}
bool DownloadItemImpl::GetFileExternallyRemoved() const {
return file_externally_removed_;
}
void DownloadItemImpl::DeleteFile(const base::Callback<void(bool)>& callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (GetState() != DownloadItem::COMPLETE) {
// Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DeleteDownloadedFileDone,
base::WeakPtr<DownloadItemImpl>(), callback, false));
return;
}
if (GetFullPath().empty() || file_externally_removed_) {
// Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DeleteDownloadedFileDone,
base::WeakPtr<DownloadItemImpl>(), callback, true));
return;
}
base::PostTaskAndReplyWithResult(
GetDownloadTaskRunner().get(), FROM_HERE,
base::Bind(&DeleteDownloadedFile, GetFullPath()),
base::Bind(&DeleteDownloadedFileDone, weak_ptr_factory_.GetWeakPtr(),
callback));
}
DownloadFile* DownloadItemImpl::GetDownloadFile() {
return download_file_.get();
}
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_ - GetReceivedBytes()) / speed);
return true;
}
int64_t DownloadItemImpl::CurrentSpeed() const {
if (IsPaused())
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>(GetReceivedBytes() * 100.0 / total_bytes_);
}
bool DownloadItemImpl::AllDataSaved() const {
return destination_info_.all_data_saved;
}
int64_t DownloadItemImpl::GetTotalBytes() const {
return total_bytes_;
}
int64_t DownloadItemImpl::GetReceivedBytes() const {
return destination_info_.received_bytes;
}
const std::vector<DownloadItem::ReceivedSlice>&
DownloadItemImpl::GetReceivedSlices() const {
return received_slices_;
}
base::Time DownloadItemImpl::GetStartTime() const {
return request_info_.start_time;
}
base::Time DownloadItemImpl::GetEndTime() const {
return destination_info_.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_;
}
base::Time DownloadItemImpl::GetLastAccessTime() const {
return last_access_time_;
}
bool DownloadItemImpl::IsTransient() const {
return transient_;
}
bool DownloadItemImpl::IsParallelDownload() const {
bool is_parallelizable = job_ ? job_->IsParallelizable() : false;
return is_parallelizable && download::IsParallelDownloadEnabled();
}
DownloadItem::DownloadCreationType DownloadItemImpl::GetDownloadCreationType()
const {
return download_type_;
}
void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type,
DownloadInterruptReason reason) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
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) << __func__ << "() danger_type=" << danger_type
<< " download=" << DebugString(true);
SetDangerType(danger_type);
if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
InterruptAndDiscardPartialState(reason);
DCHECK_EQ(ResumeMode::INVALID, GetResumeMode());
}
UpdateObservers();
}
void DownloadItemImpl::SetOpenWhenComplete(bool open) {
open_when_complete_ = open;
}
void DownloadItemImpl::SetOpened(bool opened) {
opened_ = opened;
}
void DownloadItemImpl::SetLastAccessTime(base::Time last_access_time) {
last_access_time_ = last_access_time;
UpdateObservers();
}
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 (!request_info_.url_chain.empty()) {
auto iter = request_info_.url_chain.begin();
auto last = request_info_.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"
" current_path = \"%" PRFilePath
"\"\n\t"
" target_path = \"%" PRFilePath
"\""
" referrer = \"%s\""
" site_url = \"%s\"",
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_ ? "true" : "false", url_list.c_str(),
GetFullPath().value().c_str(), GetTargetFilePath().value().c_str(),
GetReferrerUrl().spec().c_str(), GetSiteUrl().spec().c_str());
} else {
description += base::StringPrintf(" url = \"%s\"", url_list.c_str());
}
description += " }";
return description;
}
void DownloadItemImpl::SimulateErrorForTesting(DownloadInterruptReason reason) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
InterruptWithPartialState(GetReceivedBytes(), nullptr, reason);
UpdateObservers();
}
ResumeMode DownloadItemImpl::GetResumeMode() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// 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.
bool restart_required =
(GetFullPath().empty() ||
(!HasStrongValidators() &&
!base::FeatureList::IsEnabled(
features::kAllowDownloadResumptionWithoutStrongValidators)));
// We won't auto-restart if we've used up our attempts or the
// download has been paused by user action.
bool user_action_required =
(auto_resume_count_ >= kMaxAutoResumeAttempts || IsPaused());
return GetDownloadResumeMode(GetURL(), last_reason_, restart_required,
user_action_required);
}
bool DownloadItemImpl::HasStrongValidators() const {
return !etag_.empty() || !last_modified_time_.empty();
}
void DownloadItemImpl::UpdateValidatorsOnResumption(
const DownloadCreateInfo& new_create_info) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
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.
auto chain_iter = new_create_info.url_chain.begin();
if (*chain_iter == request_info_.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.
if (etag_ != new_create_info.etag ||
last_modified_time_ != new_create_info.last_modified) {
if (destination_info_.received_bytes > 0) {
RecordResumptionRestartCount(
ResumptionRestartCountTypes::kStrongValidatorChangesCount);
}
received_slices_.clear();
destination_info_.received_bytes = 0;
}
if (destination_info_.received_bytes > 0 && new_create_info.offset == 0) {
if (!base::FeatureList::IsEnabled(
features::kAllowDownloadResumptionWithoutStrongValidators) ||
GetDownloadValidationLengthConfig() >
destination_info_.received_bytes) {
RecordResumptionRestartCount(
ResumptionRestartCountTypes::kRequestedByServerCount);
}
}
request_info_.url_chain.insert(request_info_.url_chain.end(), chain_iter,
new_create_info.url_chain.end());
etag_ = new_create_info.etag;
last_modified_time_ = new_create_info.last_modified;
response_headers_ = new_create_info.response_headers;
content_disposition_ = new_create_info.content_disposition;
// It is possible that the previous download attempt failed right before the
// response is received. Need to reset the MIME type.
mime_type_ = new_create_info.mime_type;
// 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 (auto& observer : observers_)
observer.OnDownloadRemoved(this);
}
void DownloadItemImpl::OnDownloadedFileRemoved() {
file_externally_removed_ = true;
DVLOG(20) << __func__ << "() download=" << DebugString(true);
UpdateObservers();
}
base::WeakPtr<DownloadDestinationObserver>
DownloadItemImpl::DestinationObserverAsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void DownloadItemImpl::SetTotalBytes(int64_t total_bytes) {
total_bytes_ = total_bytes;
}
void DownloadItemImpl::OnAllDataSaved(
int64_t total_bytes,
std::unique_ptr<crypto::SecureHash> hash_state) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!AllDataSaved());
destination_info_.all_data_saved = true;
SetTotalBytes(total_bytes);
UpdateProgress(total_bytes, 0);
received_slices_.clear();
SetHashState(std::move(hash_state));
hash_state_.reset(); // No need to retain hash_state_ since we are done with
// the download and don't expect to receive any more
// data.
if (received_bytes_at_length_mismatch_ > 0) {
if (total_bytes > received_bytes_at_length_mismatch_) {
RecordDownloadCountWithSource(
MORE_BYTES_RECEIVED_AFTER_CONTENT_LENGTH_MISMATCH_COUNT,
download_source_);
} else if (total_bytes == received_bytes_at_length_mismatch_) {
RecordDownloadCountWithSource(
NO_BYTES_RECEIVED_AFTER_CONTENT_LENGTH_MISMATCH_COUNT,
download_source_);
} else {
// This could happen if the content changes on the server.
}
}
DVLOG(20) << __func__ << "() download=" << DebugString(true);
UpdateObservers();
}
void DownloadItemImpl::MarkAsComplete() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(AllDataSaved());
destination_info_.end_time = base::Time::Now();
TransitionTo(COMPLETE_INTERNAL);
UpdateObservers();
}
void DownloadItemImpl::DestinationUpdate(
int64_t bytes_so_far,
int64_t bytes_per_sec,
const std::vector<DownloadItem::ReceivedSlice>& received_slices) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// 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 ||
state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
// There must be no pending deferred_interrupt_reason_.
DCHECK(deferred_interrupt_reason_ == DOWNLOAD_INTERRUPT_REASON_NONE ||
state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
DVLOG(20) << __func__ << "() so_far=" << bytes_so_far
<< " per_sec=" << bytes_per_sec
<< " download=" << DebugString(true);
UpdateProgress(bytes_so_far, bytes_per_sec);
received_slices_ = received_slices;
TRACE_EVENT_INSTANT1("download", "DownloadItemUpdated",
TRACE_EVENT_SCOPE_THREAD, "bytes_so_far",
GetReceivedBytes());
if (IsPaused() && destination_info_.received_bytes == bytes_so_far)
return;
UpdateObservers();
}
void DownloadItemImpl::DestinationError(
DownloadInterruptReason reason,
int64_t bytes_so_far,
std::unique_ptr<crypto::SecureHash> secure_hash) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// 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) << __func__
<< "() reason:" << DownloadInterruptReasonToString(reason)
<< " this:" << DebugString(true);
InterruptWithPartialState(bytes_so_far, std::move(secure_hash), reason);
UpdateObservers();
}
void DownloadItemImpl::DestinationCompleted(
int64_t total_bytes,
std::unique_ptr<crypto::SecureHash> secure_hash) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// 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 ||
state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
DVLOG(20) << __func__ << "() download=" << DebugString(true);
OnAllDataSaved(total_bytes, std::move(secure_hash));
MaybeCompleteDownload();
}
void DownloadItemImpl::SetDelegate(DownloadItemImplDelegate* delegate) {
delegate_->Detach();
delegate_ = delegate;
delegate_->Attach();
}
void DownloadItemImpl::SetDownloadId(uint32_t download_id) {
download_id_ = download_id;
}
void DownloadItemImpl::SetAutoResumeCountForTesting(int32_t auto_resume_count) {
auto_resume_count_ = auto_resume_count;
}
// **** Download progression cascade
void DownloadItemImpl::Init(bool active,
DownloadItem::DownloadCreationType download_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
download_type_ = download_type;
std::string file_name;
if (download_type == TYPE_HISTORY_IMPORT) {
// target_path_ works for History and Save As versions.
file_name = GetTargetFilePath().AsUTF8Unsafe();
} else {
// See if it's set programmatically.
file_name = GetForcedFilePath().AsUTF8Unsafe();
// Possibly has a 'download' attribute for the anchor.
if (file_name.empty())
file_name = GetSuggestedFilename();
// From the URL file name.
if (file_name.empty())
file_name = GetURL().ExtractFileName();
}
auto active_data = std::make_unique<DownloadItemActivatedData>(
download_type, GetId(), GetOriginalUrl().spec(), GetURL().spec(),
file_name, GetDangerType(), GetReceivedBytes(), HasUserGesture());
if (active) {
TRACE_EVENT_ASYNC_BEGIN1("download", "DownloadItemActive", download_id_,
"download_item", std::move(active_data));
ukm_download_id_ = GetUniqueDownloadId();
} else {
TRACE_EVENT_INSTANT1("download", "DownloadItemActive",
TRACE_EVENT_SCOPE_THREAD, "download_item",
std::move(active_data));
// Read data from in-progress cache.
// TODO(qinmin): Remove this once we initialize the data in DownloadItemImpl
// ctor.
auto in_progress_entry = delegate_->GetInProgressEntry(this);
if (in_progress_entry) {
download_source_ = in_progress_entry->download_source;
fetch_error_body_ = in_progress_entry->fetch_error_body;
request_headers_ = in_progress_entry->request_headers;
ukm_download_id_ = in_progress_entry->ukm_download_id;
bytes_wasted_ = in_progress_entry->bytes_wasted;
} else {
ukm_download_id_ = GetUniqueDownloadId();
}
}
DVLOG(20) << __func__ << "() " << DebugString(true);
}
// We're starting the download.
void DownloadItemImpl::Start(
std::unique_ptr<DownloadFile> file,
std::unique_ptr<DownloadRequestHandleInterface> req_handle,
const DownloadCreateInfo& new_create_info,
scoped_refptr<download::DownloadURLLoaderFactoryGetter>
url_loader_factory_getter,
net::URLRequestContextGetter* url_request_context_getter) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!download_file_);
DVLOG(20) << __func__ << "() this=" << DebugString(true);
RecordDownloadCountWithSource(START_COUNT, download_source_);
download_file_ = std::move(file);
job_ = DownloadJobFactory::CreateJob(
this, std::move(req_handle), new_create_info, false,
std::move(url_loader_factory_getter), url_request_context_getter,
delegate_ ? delegate_->GetServiceManagerConnector() : nullptr);
if (job_->IsParallelizable()) {
RecordParallelizableDownloadCount(START_COUNT, IsParallelDownloadEnabled());
}
deferred_interrupt_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE;
if (state_ == CANCELLED_INTERNAL) {
// The download was in the process of resuming when it was cancelled. Don't
// proceed.
ReleaseDownloadFile(true);
job_->Cancel(true);
return;
}
// The state could be one of the following:
//
// INITIAL_INTERNAL: A normal download attempt.
//
// RESUMING_INTERNAL: A resumption attempt. May or may not have been
// successful.
DCHECK(state_ == INITIAL_INTERNAL || state_ == RESUMING_INTERNAL);
// If the state_ is INITIAL_INTERNAL, then the target path must be empty.
DCHECK(state_ != INITIAL_INTERNAL || GetTargetFilePath().empty());
// If a resumption attempted failed, or if the download was DOA, then the
// download should go back to being interrupted.
if (new_create_info.result != DOWNLOAD_INTERRUPT_REASON_NONE) {
DCHECK(!download_file_);
// Download requests that are interrupted by Start() should result in a
// DownloadCreateInfo with an intact DownloadSaveInfo.
DCHECK(new_create_info.save_info);
std::unique_ptr<crypto::SecureHash> hash_state =
new_create_info.save_info->hash_state
? new_create_info.save_info->hash_state->Clone()
: nullptr;
hash_state_ = std::move(hash_state);
destination_info_.hash.clear();
deferred_interrupt_reason_ = new_create_info.result;
TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
DetermineDownloadTarget();
return;
}
if (state_ == INITIAL_INTERNAL) {
RecordDownloadCountWithSource(NEW_DOWNLOAD_COUNT, download_source_);
if (job_->IsParallelizable()) {
RecordParallelizableDownloadCount(NEW_DOWNLOAD_COUNT,
IsParallelDownloadEnabled());
}
RecordDownloadMimeType(mime_type_);
DownloadContent file_type = DownloadContentFromMimeType(mime_type_, false);
bool is_same_host_download =
base::StringPiece(new_create_info.url().host())
.ends_with(new_create_info.site_url.host());
DownloadConnectionSecurity state = CheckDownloadConnectionSecurity(
new_create_info.url(), new_create_info.url_chain);
DownloadUkmHelper::RecordDownloadStarted(
ukm_download_id_, new_create_info.ukm_source_id, file_type,
download_source_, state, is_same_host_download);
RecordDownloadValidationMetrics(DownloadMetricsCallsite::kDownloadItem,
state, file_type);
if (!delegate_->IsOffTheRecord()) {
RecordDownloadCountWithSource(NEW_DOWNLOAD_COUNT_NORMAL_PROFILE,
download_source_);
RecordDownloadMimeTypeForNormalProfile(mime_type_);
}
}
// Successful download start.
DCHECK(download_file_);
DCHECK(job_);
if (state_ == RESUMING_INTERNAL)
UpdateValidatorsOnResumption(new_create_info);
// If the download is not parallel, clear the |received_slices_|.
if (!received_slices_.empty() && !job_->IsParallelizable()) {
destination_info_.received_bytes =
GetMaxContiguousDataBlockSizeFromBeginning(received_slices_);
received_slices_.clear();
}
TransitionTo(TARGET_PENDING_INTERNAL);
job_->Start(download_file_.get(),
base::Bind(&DownloadItemImpl::OnDownloadFileInitialized,
weak_ptr_factory_.GetWeakPtr()),
GetReceivedSlices());
}
void DownloadItemImpl::OnDownloadFileInitialized(DownloadInterruptReason result,
int64_t bytes_wasted) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(state_ == TARGET_PENDING_INTERNAL ||
state_ == INTERRUPTED_TARGET_PENDING_INTERNAL)
<< "Unexpected state: " << DebugDownloadStateString(state_);
DVLOG(20) << __func__
<< "() result:" << DownloadInterruptReasonToString(result);
if (bytes_wasted > 0) {
bytes_wasted_ += bytes_wasted;
delegate_->ReportBytesWasted(this);
}
// Handle download interrupt reason.
if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
ReleaseDownloadFile(true);
InterruptAndDiscardPartialState(result);
}
DetermineDownloadTarget();
}
void DownloadItemImpl::DetermineDownloadTarget() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() " << DebugString(true);
RecordDownloadCountWithSource(DETERMINE_DOWNLOAD_TARGET_COUNT,
download_source_);
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,
DownloadInterruptReason interrupt_reason) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (state_ == CANCELLED_INTERNAL)
return;
DCHECK(state_ == TARGET_PENDING_INTERNAL ||
state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
DVLOG(20) << __func__ << "() target_path:" << target_path.value()
<< " intermediate_path:" << intermediate_path.value()
<< " disposition:" << disposition << " danger_type:" << danger_type
<< " interrupt_reason:"
<< DownloadInterruptReasonToString(interrupt_reason)
<< " this:" << DebugString(true);
RecordDownloadCountWithSource(DOWNLOAD_TARGET_DETERMINED_COUNT,
download_source_);
if (IsCancellation(interrupt_reason) || target_path.empty()) {
Cancel(true);
return;
}
// There were no other pending errors, and we just failed to determined the
// download target. The target path, if it is non-empty, should be considered
// suspect. The safe option here is to interrupt the download without doing an
// intermediate rename. In the case of a new download, we'll lose the partial
// data that may have been downloaded, but that should be a small loss.
if (state_ == TARGET_PENDING_INTERNAL &&
interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
deferred_interrupt_reason_ = interrupt_reason;
TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
OnTargetResolved();
return;
}
destination_info_.target_path = target_path;
destination_info_.target_disposition = disposition;
SetDangerType(danger_type);
// This was an interrupted download that was looking for a filename. Resolve
// early without performing the intermediate rename. If there is a
// DownloadFile, then that should be renamed to the intermediate name before
// we can interrupt the download. Otherwise we may lose intermediate state.
if (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL && !download_file_) {
OnTargetResolved();
return;
}
// 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 == GetFullPath()) {
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(!IsSavePackageDownload());
DownloadFile::RenameCompletionCallback callback =
base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName,
weak_ptr_factory_.GetWeakPtr());
#if defined(OS_ANDROID)
if ((download_type_ == TYPE_ACTIVE_DOWNLOAD &&
DownloadCollectionBridge::ShouldPublishDownload(GetTargetFilePath())) ||
GetTargetFilePath().IsContentUri()) {
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadFile::RenameToIntermediateUri,
// Safe because we control download file lifetime.
base::Unretained(download_file_.get()), GetOriginalUrl(),
GetReferrerUrl(), GetFileNameToReportUser(),
GetMimeType(), GetTargetFilePath(),
std::move(callback)));
return;
}
#endif // defined(OS_ANDROID)
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadFile::RenameAndUniquify,
// Safe because we control download file lifetime.
base::Unretained(download_file_.get()), intermediate_path,
std::move(callback)));
}
void DownloadItemImpl::OnDownloadRenamedToIntermediateName(
DownloadInterruptReason reason,
const base::FilePath& full_path) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(state_ == TARGET_PENDING_INTERNAL ||
state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
DCHECK(download_file_);
DVLOG(20) << __func__ << "() download=" << DebugString(true);
if (DOWNLOAD_INTERRUPT_REASON_NONE == reason) {
SetFullPath(full_path);
#if defined(OS_ANDROID)
// For content URIs, target file path is the same as the current path.
if (full_path.IsContentUri()) {
destination_info_.target_path = full_path;
if (display_name_.empty())
SetDisplayName(download_file_->GetDisplayName());
}
#endif // defined(OS_ANDROID)
} else {
// TODO(asanka): Even though the rename failed, it may still be possible to
// recover the partial state from the 'before' name.
deferred_interrupt_reason_ = reason;
TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
}
OnTargetResolved();
}
void DownloadItemImpl::OnTargetResolved() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() download=" << DebugString(true);
DCHECK((state_ == TARGET_PENDING_INTERNAL &&
deferred_interrupt_reason_ == DOWNLOAD_INTERRUPT_REASON_NONE) ||
(state_ == INTERRUPTED_TARGET_PENDING_INTERNAL &&
deferred_interrupt_reason_ != DOWNLOAD_INTERRUPT_REASON_NONE))
<< " deferred_interrupt_reason_:"
<< DownloadInterruptReasonToString(deferred_interrupt_reason_)
<< " this:" << DebugString(true);
// This transition is here to ensure that the DownloadItemImpl state machine
// doesn't transition to INTERRUPTED or IN_PROGRESS from
// TARGET_PENDING_INTERNAL directly. Doing so without passing through
// OnTargetResolved() can result in an externally visible state where the
// download is interrupted but doesn't have a target path associated with it.
//
// While not terrible, this complicates the DownloadItem<->Observer
// relationship since an observer that needs a target path in order to respond
// properly to an interruption will need to wait for another OnDownloadUpdated
// notification. This requirement currently affects all of our UIs.
TransitionTo(TARGET_RESOLVED_INTERNAL);
if (DOWNLOAD_INTERRUPT_REASON_NONE != deferred_interrupt_reason_) {
InterruptWithPartialState(GetReceivedBytes(), std::move(hash_state_),
deferred_interrupt_reason_);
deferred_interrupt_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE;
UpdateObservers();
return;
}
TransitionTo(IN_PROGRESS_INTERNAL);
// TODO(asanka): Calling UpdateObservers() prior to MaybeCompleteDownload() is
// not safe. The download could be in an underminate state after invoking
// observers. http://crbug.com/586610
UpdateObservers();
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_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!IsSavePackageDownload());
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(AllDataSaved());
OnDownloadCompleting();
}
// Called by MaybeCompleteDownload() when it has determined that the download
// is ready for completion.
void DownloadItemImpl::OnDownloadCompleting() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (state_ != IN_PROGRESS_INTERNAL)
return;
DVLOG(20) << __func__ << "() " << DebugString(true);
DCHECK(!GetTargetFilePath().empty());
DCHECK(!IsDangerous());
DCHECK(download_file_);
// Unilaterally rename; even if it already has the right name,
// we need theannotation.
DownloadFile::RenameCompletionCallback callback =
base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName,
weak_ptr_factory_.GetWeakPtr());
#if defined(OS_ANDROID)
if (GetTargetFilePath().IsContentUri()) {
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadFile::PublishDownload,
// Safe because we control download file lifetime.
base::Unretained(download_file_.get()),
std::move(callback)));
return;
}
#endif // defined(OS_ANDROID)
std::unique_ptr<service_manager::Connector> new_connector;
service_manager::Connector* connector =
delegate_->GetServiceManagerConnector();
if (connector)
new_connector = connector->Clone();
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadFile::RenameAndAnnotate,
base::Unretained(download_file_.get()),
GetTargetFilePath(),
delegate_->GetApplicationClientIdForFileScanning(),
delegate_->IsOffTheRecord() ? GURL() : GetURL(),
delegate_->IsOffTheRecord() ? GURL() : GetReferrerUrl(),
std::move(new_connector), std::move(callback)));
}
void DownloadItemImpl::OnDownloadRenamedToFinalName(
DownloadInterruptReason reason,
const base::FilePath& full_path) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!IsSavePackageDownload());
// 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) << __func__ << "() full_path = \"" << full_path.value() << "\" "
<< DebugString(false);
if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
// Failure to perform the final rename is considered fatal. TODO(asanka): It
// may not be, in which case we should figure out whether we can recover the
// state.
InterruptAndDiscardPartialState(reason);
UpdateObservers();
return;
}
DCHECK(GetTargetFilePath() == full_path);
if (full_path != GetFullPath()) {
// 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);
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_CALLED_ON_VALID_THREAD(thread_checker_);
auto_opened_ = auto_opened;
Completed();
}
void DownloadItemImpl::Completed() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() " << DebugString(false);
DCHECK(AllDataSaved());
destination_info_.end_time = base::Time::Now();
TransitionTo(COMPLETE_INTERNAL);
bool is_parallelizable = job_ && job_->IsParallelizable();
RecordDownloadCompleted(GetReceivedBytes(), is_parallelizable,
download_source_, has_resumed_,
HasStrongValidators());
if (!delegate_->IsOffTheRecord()) {
RecordDownloadCountWithSource(COMPLETED_COUNT_NORMAL_PROFILE,
download_source_);
}
if (is_parallelizable) {
RecordParallelizableDownloadCount(COMPLETED_COUNT,
IsParallelDownloadEnabled());
int64_t content_length = -1;
if (response_headers_->response_code() != net::HTTP_PARTIAL_CONTENT) {
content_length = response_headers_->GetContentLength();
} else {
int64_t first_byte = -1;
int64_t last_byte = -1;
response_headers_->GetContentRangeFor206(&first_byte, &last_byte,
&content_length);
}
if (content_length > 0)
RecordParallelizableContentLength(content_length);
}
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;
}
base::TimeDelta time_since_start = GetEndTime() - GetStartTime();
// If all data is saved, the number of received bytes is resulting file size.
int resulting_file_size = GetReceivedBytes();
DownloadUkmHelper::RecordDownloadCompleted(
ukm_download_id_, resulting_file_size, time_since_start, bytes_wasted_);
// After all of the records are done, then update the observers.
UpdateObservers();
}
// **** End of Download progression cascade
void DownloadItemImpl::InterruptAndDiscardPartialState(
DownloadInterruptReason reason) {
InterruptWithPartialState(0, nullptr, reason);
}
void DownloadItemImpl::InterruptWithPartialState(
int64_t bytes_so_far,
std::unique_ptr<crypto::SecureHash> hash_state,
DownloadInterruptReason reason) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason);
DVLOG(20) << __func__
<< "() reason:" << DownloadInterruptReasonToString(reason)
<< " bytes_so_far:" << bytes_so_far
<< " hash_state:" << (hash_state ? "Valid" : "Invalid")
<< " 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 INTERRUPTED_TARGET_PENDING_INTERNAL:
// Postpone recognition of this error until after file name determination
// has completed and the intermediate file has been renamed to simplify
// resumption conditions. The target determination logic is much simpler
// if the state of the download remains constant until that stage
// completes.
//
// current_path_ may be empty because it is possible for
// DownloadItem to receive a DestinationError prior to the
// download file initialization complete callback.
if (!IsCancellation(reason)) {
UpdateProgress(bytes_so_far, 0);
SetHashState(std::move(hash_state));
deferred_interrupt_reason_ = reason;
TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
return;
}
// else - Fallthrough for cancellation handling which is equivalent to the
// IN_PROGRESS state.
FALLTHROUGH;
case IN_PROGRESS_INTERNAL:
case TARGET_RESOLVED_INTERNAL: {
// last_reason_ needs to be set for GetResumeMode() to work.
last_reason_ = reason;
ResumeMode resume_mode = GetResumeMode();
ReleaseDownloadFile(resume_mode != ResumeMode::IMMEDIATE_CONTINUE &&
resume_mode != ResumeMode::USER_CONTINUE);
} break;
case RESUMING_INTERNAL:
case INTERRUPTED_INTERNAL:
DCHECK(!download_file_);
// The first non-cancel interrupt reason wins in cases where multiple
// things go wrong.
if (!IsCancellation(reason))
return;
last_reason_ = reason;
// There is no download file and this is transitioning from INTERRUPTED
// to CANCELLED. The intermediate file is no longer usable, and should
// be deleted.
DeleteDownloadFile();
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.
destination_info_.all_data_saved = false;
if (GetFullPath().empty()) {
hash_state_.reset();
destination_info_.hash.clear();
destination_info_.received_bytes = 0;
received_slices_.clear();
} else {
UpdateProgress(bytes_so_far, 0);
SetHashState(std::move(hash_state));
}
if (job_)
job_->Cancel(false);
if (IsCancellation(reason)) {
RecordDownloadCountWithSource(CANCELLED_COUNT, download_source_);
if (job_ && job_->IsParallelizable()) {
RecordParallelizableDownloadCount(CANCELLED_COUNT,
IsParallelDownloadEnabled());
}
DCHECK_EQ(last_reason_, reason);
TransitionTo(CANCELLED_INTERNAL);
return;
}
RecordDownloadInterrupted(reason, GetReceivedBytes(), total_bytes_,
job_ && job_->IsParallelizable(),
IsParallelDownloadEnabled(), download_source_,
received_bytes_at_length_mismatch_ > 0);
base::TimeDelta time_since_start = base::Time::Now() - GetStartTime();
int resulting_file_size = GetReceivedBytes();
base::Optional<int> change_in_file_size;
if (total_bytes_ >= 0) {
change_in_file_size = total_bytes_ - resulting_file_size;
}
DownloadUkmHelper::RecordDownloadInterrupted(
ukm_download_id_, change_in_file_size, reason, resulting_file_size,
time_since_start, bytes_wasted_);
if (reason == DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH) {
received_bytes_at_length_mismatch_ = GetReceivedBytes();
}
// TODO(asanka): This is not good. We can transition to interrupted from
// target-pending, which is something we don't want to do. Perhaps we should
// explicitly transition to target-resolved prior to switching to interrupted.
DCHECK_EQ(last_reason_, reason);
TransitionTo(INTERRUPTED_INTERNAL);
delegate_->DownloadInterrupted(this);
AutoResumeIfValid();
}
void DownloadItemImpl::UpdateProgress(int64_t bytes_so_far,
int64_t bytes_per_sec) {
destination_info_.received_bytes = bytes_so_far;
bytes_per_sec_ = bytes_per_sec;
// If we've received more data than we were expecting (bad server info?),
// revert to 'unknown size mode'.
if (bytes_so_far > total_bytes_)
total_bytes_ = 0;
}
void DownloadItemImpl::SetHashState(
std::unique_ptr<crypto::SecureHash> hash_state) {
hash_state_ = std::move(hash_state);
if (!hash_state_) {
destination_info_.hash.clear();
return;
}
std::unique_ptr<crypto::SecureHash> clone_of_hash_state(hash_state_->Clone());
std::vector<char> hash_value(clone_of_hash_state->GetHashLength());
clone_of_hash_state->Finish(&hash_value.front(), hash_value.size());
destination_info_.hash.assign(hash_value.begin(), hash_value.end());
}
void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() destroy_file:" << destroy_file;
if (destroy_file) {
if (download_file_) {
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
// Will be deleted at end of task execution.
base::BindOnce(&DownloadFileCancel, std::move(download_file_)));
} else {
DeleteDownloadFile();
}
// Avoid attempting to reuse the intermediate file by clearing out
// current_path_ and received slices.
destination_info_.current_path.clear();
received_slices_.clear();
} else if (download_file_) {
GetDownloadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(base::IgnoreResult(&DownloadFileDetach),
// Will be deleted at end of task execution.
std::move(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();
}
void DownloadItemImpl::DeleteDownloadFile() {
if (GetFullPath().empty())
return;
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&DeleteDownloadedFile), GetFullPath()));
destination_info_.current_path.clear();
}
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;
// Check for consistency before invoking delegate. Since there are no pending
// target determination calls and the download is in progress, both the target
// and current paths should be non-empty and they should point to the same
// directory.
DCHECK(!GetTargetFilePath().empty());
DCHECK(!GetFullPath().empty());
DCHECK(GetTargetFilePath().DirName() == GetFullPath().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) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (state_ == new_state)
return;
DownloadInternalState old_state = state_;
state_ = new_state;
DCHECK(IsSavePackageDownload()
? 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 INTERRUPTED_TARGET_PENDING_INTERNAL:
DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, deferred_interrupt_reason_)
<< "Interrupt reason must be set prior to transitioning into "
"TARGET_PENDING";
break;
case IN_PROGRESS_INTERNAL:
DCHECK(!GetFullPath().empty()) << "Current output path must be known.";
DCHECK(!GetTargetFilePath().empty()) << "Target path must be known.";
DCHECK(GetFullPath().DirName() == GetTargetFilePath().DirName())
<< "Current output directory must match target directory.";
DCHECK(download_file_) << "Output file must be owned by download item.";
DCHECK(job_) << "Must have active download job.";
DCHECK(!job_->is_paused())
<< "At the time a download enters IN_PROGRESS state, "
"it must not be paused.";
break;
case COMPLETING_INTERNAL:
DCHECK(AllDataSaved()) << "All data must be saved prior to completion.";
DCHECK(!download_file_)
<< "Download file must be released prior to completion.";
DCHECK(!GetTargetFilePath().empty()) << "Target path must be known.";
DCHECK(GetFullPath() == GetTargetFilePath())
<< "Current output path must match target path.";
TRACE_EVENT_INSTANT2("download", "DownloadItemCompleting",
TRACE_EVENT_SCOPE_THREAD, "bytes_so_far",
GetReceivedBytes(), "final_hash",
destination_info_.hash);
break;
case COMPLETE_INTERNAL:
TRACE_EVENT_INSTANT1("download", "DownloadItemFinished",
TRACE_EVENT_SCOPE_THREAD, "auto_opened",
auto_opened_ ? "yes" : "no");
break;
case INTERRUPTED_INTERNAL:
DCHECK(!download_file_)
<< "Download file must be released prior to interruption.";
DCHECK_NE(last_reason_, DOWNLOAD_INTERRUPT_REASON_NONE);
TRACE_EVENT_INSTANT2("download", "DownloadItemInterrupted",
TRACE_EVENT_SCOPE_THREAD, "interrupt_reason",
DownloadInterruptReasonToString(last_reason_),
"bytes_so_far", GetReceivedBytes());
break;
case RESUMING_INTERNAL:
TRACE_EVENT_INSTANT2("download", "DownloadItemResumed",
TRACE_EVENT_SCOPE_THREAD, "interrupt_reason",
DownloadInterruptReasonToString(last_reason_),
"bytes_so_far", GetReceivedBytes());
break;
case CANCELLED_INTERNAL:
TRACE_EVENT_INSTANT1("download", "DownloadItemCancelled",
TRACE_EVENT_SCOPE_THREAD, "bytes_so_far",
GetReceivedBytes());
break;
case MAX_DOWNLOAD_INTERNAL_STATE:
NOTREACHED();
break;
}
DVLOG(20) << __func__ << "() 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)
TRACE_EVENT_ASYNC_END0("download", "DownloadItemActive", download_id_);
// Resumption
if (was_done && !is_done) {
std::string file_name(GetTargetFilePath().BaseName().AsUTF8Unsafe());
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"download", "DownloadItemActive", download_id_, "download_item",
std::make_unique<DownloadItemActivatedData>(
TYPE_ACTIVE_DOWNLOAD, GetId(), GetOriginalUrl().spec(),
GetURL().spec(), file_name, GetDangerType(), GetReceivedBytes(),
HasUserGesture()));
}
}
void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) {
if (danger_type != danger_type_) {
TRACE_EVENT_INSTANT1("download", "DownloadItemSaftyStateUpdated",
TRACE_EVENT_SCOPE_THREAD, "danger_type",
GetDownloadDangerNames(danger_type).c_str());
}
// 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_WHITELISTED_BY_POLICY) &&
(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_CALLED_ON_VALID_THREAD(thread_checker_);
DVLOG(20) << __func__ << "() new_path = \"" << new_path.value() << "\" "
<< DebugString(true);
DCHECK(!new_path.empty());
TRACE_EVENT_INSTANT2("download", "DownloadItemRenamed",
TRACE_EVENT_SCOPE_THREAD, "old_filename",
destination_info_.current_path.AsUTF8Unsafe(),
"new_filename", new_path.AsUTF8Unsafe());
destination_info_.current_path = new_path;
}
void DownloadItemImpl::AutoResumeIfValid() {
DVLOG(20) << __func__ << "() " << DebugString(true);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ResumeMode mode = GetResumeMode();
if (mode != ResumeMode::IMMEDIATE_RESTART &&
mode != ResumeMode::IMMEDIATE_CONTINUE) {
return;
}
auto_resume_count_++;
ResumeInterruptedDownload(ResumptionRequestSource::AUTOMATIC);
}
void DownloadItemImpl::ResumeInterruptedDownload(
ResumptionRequestSource source) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// If we're not interrupted, ignore the request; our caller is drunk.
if (state_ != INTERRUPTED_INTERNAL)
return;
// We are starting a new request. Shake off all pending operations.
DCHECK(!download_file_);
weak_ptr_factory_.InvalidateWeakPtrs();
// Reset the appropriate state if restarting.
ResumeMode mode = GetResumeMode();
if (mode == ResumeMode::IMMEDIATE_RESTART ||
mode == ResumeMode::USER_RESTART) {
LOG_IF(ERROR, !GetFullPath().empty())
<< "Download full path should be empty before resumption";
if (destination_info_.received_bytes > 0) {
if (!HasStrongValidators()) {
RecordResumptionRestartCount(
ResumptionRestartCountTypes::kMissingStrongValidatorsCount);
}
RecordResumptionRestartReason(last_reason_);
}
destination_info_.received_bytes = 0;
last_modified_time_.clear();
etag_.clear();
destination_info_.hash.clear();
hash_state_.reset();
received_slices_.clear();
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("download_manager_resume", R"(
semantics {
sender: "Download Manager"
description:
"When user resumes downloading a file, a network request is made "
"to fetch it."
trigger:
"User resumes a download."
data: "None."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled in settings, but it is activated "
"by direct user action."
chrome_policy {
DownloadRestrictions {
DownloadRestrictions: 3
}
}
})");
// Avoid using the WebContents even if it's still around. Resumption requests
// are consistently routed through the no-renderer code paths so that the
// request will not be dropped if the WebContents (and by extension, the
// associated renderer) goes away before a response is received.
std::unique_ptr<DownloadUrlParameters> download_params(
new DownloadUrlParameters(GetURL(), traffic_annotation));
download_params->set_file_path(GetFullPath());
if (received_slices_.size() > 0) {
std::vector<DownloadItem::ReceivedSlice> slices_to_download =
FindSlicesToDownload(received_slices_);
download_params->set_offset(slices_to_download[0].offset);
} else {
download_params->set_offset(GetReceivedBytes());
}
download_params->set_last_modified(GetLastModifiedTime());
download_params->set_etag(GetETag());
download_params->set_hash_of_partial_file(GetHash());
download_params->set_hash_state(std::move(hash_state_));
download_params->set_guid(guid_);
if (!HasStrongValidators() && download_params->offset() > 0 &&
base::FeatureList::IsEnabled(
features::kAllowDownloadResumptionWithoutStrongValidators)) {
download_params->set_use_if_range(false);
download_params->set_file_offset(download_params->offset());
int64_t validation_length = GetDownloadValidationLengthConfig();
download_params->set_offset(download_params->offset() > validation_length
? download_params->offset() -
validation_length
: 0);
}
// TODO(xingliu): Read |fetch_error_body| and |request_headers_| from the
// cache, and don't copy them into DownloadItemImpl.
download_params->set_fetch_error_body(fetch_error_body_);
for (const auto& header : request_headers_) {
download_params->add_request_header(header.first, header.second);
}
// The offset is calculated after decompression, so the range request cannot
// involve any compression,
download_params->add_request_header("Accept-Encoding", "identity");
// Note that resumed downloads disallow redirects. Hence the referrer URL
// (which is the contents of the Referer header for the last download request)
// will only be sent to the URL returned by GetURL().
download_params->set_referrer(GetReferrerUrl());
download_params->set_referrer_policy(net::URLRequest::NEVER_CLEAR_REFERRER);
download_params->set_follow_cross_origin_redirects(false);
// If the interruption was caused by content length mismatch, ignore it during
// resumption.
if (last_reason_ ==
DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH) {
download_params->set_ignore_content_length_mismatch(true);
}
TransitionTo(RESUMING_INTERNAL);
RecordDownloadCountWithSource(source == ResumptionRequestSource::USER
? MANUAL_RESUMPTION_COUNT
: AUTO_RESUMPTION_COUNT,
download_source_);
base::TimeDelta time_since_start = base::Time::Now() - GetStartTime();
DownloadUkmHelper::RecordDownloadResumed(ukm_download_id_, GetResumeMode(),
time_since_start);
RecordDownloadResumed(HasStrongValidators());
delegate_->ResumeInterruptedDownload(std::move(download_params),
request_info_.site_url);
has_resumed_ = true;
if (job_)
job_->Resume(false);
}
// static
DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState(
DownloadInternalState internal_state) {
switch (internal_state) {
case INITIAL_INTERNAL:
case TARGET_PENDING_INTERNAL:
case TARGET_RESOLVED_INTERNAL:
case INTERRUPTED_TARGET_PENDING_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 INTERRUPTED_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_TARGET_PENDING_INTERNAL;
case TARGET_PENDING_INTERNAL:
return to == INTERRUPTED_TARGET_PENDING_INTERNAL ||
to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL;
case INTERRUPTED_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 == INTERRUPTED_TARGET_PENDING_INTERNAL ||
to == TARGET_RESOLVED_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 INTERRUPTED_TARGET_PENDING_INTERNAL:
return "INTERRUPTED_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 ResumeMode::INVALID:
return "INVALID";
case ResumeMode::IMMEDIATE_CONTINUE:
return "IMMEDIATE_CONTINUE";
case ResumeMode::IMMEDIATE_RESTART:
return "IMMEDIATE_RESTART";
case ResumeMode::USER_CONTINUE:
return "USER_CONTINUE";
case ResumeMode::USER_RESTART:
return "USER_RESTART";
}
NOTREACHED() << "Unknown resume mode " << static_cast<int>(mode);
return "unknown";
}
} // namespace download