blob: 5bf825484834084f6de7c54ac0d7e8afdc22b598 [file] [log] [blame]
// Copyright 2018 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.
#include "components/download/public/common/in_progress_download_manager.h"
#include "base/bind.h"
#include "base/optional.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/download/database/download_db_entry.h"
#include "components/download/database/download_db_impl.h"
#include "components/download/database/download_namespace.h"
#include "components/download/internal/common/download_db_cache.h"
#include "components/download/internal/common/resource_downloader.h"
#include "components/download/public/common/download_features.h"
#include "components/download/public/common/download_file.h"
#include "components/download/public/common/download_item_impl.h"
#include "components/download/public/common/download_start_observer.h"
#include "components/download/public/common/download_stats.h"
#include "components/download/public/common/download_task_runner.h"
#include "components/download/public/common/download_url_loader_factory_getter.h"
#include "components/download/public/common/download_url_parameters.h"
#include "components/download/public/common/download_utils.h"
#include "components/download/public/common/input_stream.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/service_manager/public/cpp/connector.h"
#if defined(OS_ANDROID)
#include "components/download/internal/common/android/download_collection_bridge.h"
#include "components/download/public/common/download_path_reservation_tracker.h"
#endif
namespace download {
namespace {
std::unique_ptr<DownloadItemImpl> CreateDownloadItemImpl(
DownloadItemImplDelegate* delegate,
const DownloadDBEntry entry) {
if (!entry.download_info)
return nullptr;
// DownloadDBEntry migrated from in-progress cache has negative Ids.
if (entry.download_info->id < 0)
return nullptr;
base::Optional<InProgressInfo> in_progress_info =
entry.download_info->in_progress_info;
if (!in_progress_info)
return nullptr;
return std::make_unique<DownloadItemImpl>(
delegate, entry.download_info->guid, entry.download_info->id,
in_progress_info->current_path, in_progress_info->target_path,
in_progress_info->url_chain, in_progress_info->referrer_url,
in_progress_info->site_url, in_progress_info->tab_url,
in_progress_info->tab_referrer_url, base::nullopt,
in_progress_info->mime_type, in_progress_info->original_mime_type,
in_progress_info->start_time, in_progress_info->end_time,
in_progress_info->etag, in_progress_info->last_modified,
in_progress_info->received_bytes, in_progress_info->total_bytes,
in_progress_info->auto_resume_count, in_progress_info->hash,
in_progress_info->state, in_progress_info->danger_type,
in_progress_info->interrupt_reason, in_progress_info->paused,
in_progress_info->metered, false, base::Time(),
in_progress_info->transient, in_progress_info->received_slices);
}
void OnUrlDownloadHandlerCreated(
UrlDownloadHandler::UniqueUrlDownloadHandlerPtr downloader,
base::WeakPtr<InProgressDownloadManager> download_manager,
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner) {
main_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&UrlDownloadHandler::Delegate::OnUrlDownloadHandlerCreated,
download_manager, std::move(downloader)));
}
void BeginResourceDownload(
std::unique_ptr<DownloadUrlParameters> params,
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
const URLSecurityPolicy& url_security_policy,
bool is_new_download,
base::WeakPtr<InProgressDownloadManager> download_manager,
const GURL& site_url,
const GURL& tab_url,
const GURL& tab_referrer_url,
std::unique_ptr<service_manager::Connector> connector,
bool is_background_mode,
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner) {
DCHECK(GetIOTaskRunner()->BelongsToCurrentThread());
UrlDownloadHandler::UniqueUrlDownloadHandlerPtr downloader(
ResourceDownloader::BeginDownload(
download_manager, std::move(params), std::move(request),
std::move(url_loader_factory_getter), url_security_policy, site_url,
tab_url, tab_referrer_url, is_new_download, false,
std::move(connector), is_background_mode, main_task_runner)
.release(),
base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
OnUrlDownloadHandlerCreated(std::move(downloader), download_manager,
main_task_runner);
}
void CreateDownloadHandlerForNavigation(
base::WeakPtr<InProgressDownloadManager> download_manager,
std::unique_ptr<network::ResourceRequest> resource_request,
int render_process_id,
int render_frame_id,
const GURL& site_url,
const GURL& tab_url,
const GURL& tab_referrer_url,
std::vector<GURL> url_chain,
net::CertStatus cert_status,
scoped_refptr<network::ResourceResponse> response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
const URLSecurityPolicy& url_security_policy,
std::unique_ptr<service_manager::Connector> connector,
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner) {
DCHECK(GetIOTaskRunner()->BelongsToCurrentThread());
UrlDownloadHandler::UniqueUrlDownloadHandlerPtr downloader(
ResourceDownloader::InterceptNavigationResponse(
download_manager, std::move(resource_request), render_process_id,
render_frame_id, site_url, tab_url, tab_referrer_url,
std::move(url_chain), std::move(cert_status),
std::move(response_head), std::move(response_body),
std::move(url_loader_client_endpoints),
std::move(url_loader_factory_getter), url_security_policy,
std::move(connector), main_task_runner)
.release(),
base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
OnUrlDownloadHandlerCreated(std::move(downloader), download_manager,
main_task_runner);
}
#if defined(OS_ANDROID)
void OnDownloadDisplayNamesReturned(
DownloadCollectionBridge::GetDisplayNamesCallback callback,
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner,
InProgressDownloadManager::DisplayNames download_names) {
main_task_runner->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), std::move(download_names)));
}
void OnPathReserved(
const DownloadItemImplDelegate::DownloadTargetCallback& callback,
DownloadDangerType danger_type,
const InProgressDownloadManager::IntermediatePathCallback&
intermediate_path_cb,
const base::FilePath& forced_file_path,
PathValidationResult result,
const base::FilePath& target_path) {
base::FilePath intermediate_path;
if (!target_path.empty() &&
(result == PathValidationResult::SUCCESS ||
result == download::PathValidationResult::SAME_AS_SOURCE)) {
if (!forced_file_path.empty()) {
DCHECK_EQ(target_path, forced_file_path);
intermediate_path = target_path;
} else if (intermediate_path_cb) {
intermediate_path = intermediate_path_cb.Run(target_path);
}
}
RecordBackgroundTargetDeterminationResult(
intermediate_path.empty()
? BackgroudTargetDeterminationResultTypes::kPathReservationFailed
: BackgroudTargetDeterminationResultTypes::kSuccess);
callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
danger_type, intermediate_path,
intermediate_path.empty() ? DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
: DOWNLOAD_INTERRUPT_REASON_NONE);
}
#endif
} // namespace
bool InProgressDownloadManager::Delegate::InterceptDownload(
const DownloadCreateInfo& download_create_info) {
return false;
}
base::FilePath
InProgressDownloadManager::Delegate::GetDefaultDownloadDirectory() {
return base::FilePath();
}
InProgressDownloadManager::InProgressDownloadManager(
Delegate* delegate,
const base::FilePath& in_progress_db_dir,
const IsOriginSecureCallback& is_origin_secure_cb,
const URLSecurityPolicy& url_security_policy,
service_manager::Connector* connector)
: delegate_(delegate),
file_factory_(new DownloadFileFactory()),
download_start_observer_(nullptr),
is_origin_secure_cb_(is_origin_secure_cb),
url_security_policy_(url_security_policy),
use_empty_db_(in_progress_db_dir.empty()),
connector_(connector) {
Initialize(in_progress_db_dir);
}
InProgressDownloadManager::~InProgressDownloadManager() = default;
void InProgressDownloadManager::OnUrlDownloadStarted(
std::unique_ptr<DownloadCreateInfo> download_create_info,
std::unique_ptr<InputStream> input_stream,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
const DownloadUrlParameters::OnStartedCallback& callback) {
StartDownload(std::move(download_create_info), std::move(input_stream),
std::move(url_loader_factory_getter), callback);
}
void InProgressDownloadManager::OnUrlDownloadStopped(
UrlDownloadHandler* downloader) {
for (auto ptr = url_download_handlers_.begin();
ptr != url_download_handlers_.end(); ++ptr) {
if (ptr->get() == downloader) {
url_download_handlers_.erase(ptr);
return;
}
}
}
void InProgressDownloadManager::OnUrlDownloadHandlerCreated(
UrlDownloadHandler::UniqueUrlDownloadHandlerPtr downloader) {
if (downloader)
url_download_handlers_.push_back(std::move(downloader));
}
void InProgressDownloadManager::DownloadUrl(
std::unique_ptr<DownloadUrlParameters> params) {
if (!CanDownload(params.get()))
return;
// Start the new download, the download should be saved to the file path
// specifcied in the |params|.
BeginDownload(std::move(params), url_loader_factory_getter_,
true /* is_new_download */, GURL() /* site_url */,
GURL() /* tab_url */, GURL() /* tab_referral_url */);
}
bool InProgressDownloadManager::CanDownload(DownloadUrlParameters* params) {
if (!params->is_transient())
return false;
if (!url_loader_factory_getter_)
return false;
if (params->require_safety_checks())
return false;
if (params->file_path().empty())
return false;
return true;
}
void InProgressDownloadManager::GetAllDownloads(
SimpleDownloadManager::DownloadVector* downloads) {
for (auto& item : in_progress_downloads_)
downloads->push_back(item.get());
}
DownloadItem* InProgressDownloadManager::GetDownloadByGuid(
const std::string& guid) {
for (auto& item : in_progress_downloads_) {
if (item->GetGuid() == guid)
return item.get();
}
return nullptr;
}
void InProgressDownloadManager::BeginDownload(
std::unique_ptr<DownloadUrlParameters> params,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
bool is_new_download,
const GURL& site_url,
const GURL& tab_url,
const GURL& tab_referrer_url) {
std::unique_ptr<network::ResourceRequest> request =
CreateResourceRequest(params.get());
GetIOTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&BeginResourceDownload, std::move(params), std::move(request),
std::move(url_loader_factory_getter), url_security_policy_,
is_new_download, weak_factory_.GetWeakPtr(), site_url, tab_url,
tab_referrer_url, connector_ ? connector_->Clone() : nullptr,
!delegate_ /* is_background_mode */,
base::ThreadTaskRunnerHandle::Get()));
}
void InProgressDownloadManager::InterceptDownloadFromNavigation(
std::unique_ptr<network::ResourceRequest> resource_request,
int render_process_id,
int render_frame_id,
const GURL& site_url,
const GURL& tab_url,
const GURL& tab_referrer_url,
std::vector<GURL> url_chain,
net::CertStatus cert_status,
scoped_refptr<network::ResourceResponse> response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter) {
GetIOTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&CreateDownloadHandlerForNavigation, weak_factory_.GetWeakPtr(),
std::move(resource_request), render_process_id, render_frame_id,
site_url, tab_url, tab_referrer_url, std::move(url_chain),
std::move(cert_status), std::move(response_head),
std::move(response_body), std::move(url_loader_client_endpoints),
std::move(url_loader_factory_getter), url_security_policy_,
connector_ ? connector_->Clone() : nullptr,
base::ThreadTaskRunnerHandle::Get()));
}
void InProgressDownloadManager::Initialize(
const base::FilePath& in_progress_db_dir) {
download_db_cache_ = std::make_unique<DownloadDBCache>(
in_progress_db_dir.empty()
? std::make_unique<DownloadDB>()
: std::make_unique<DownloadDBImpl>(
DownloadNamespace::NAMESPACE_BROWSER_DOWNLOAD,
in_progress_db_dir));
download_db_cache_->Initialize(base::BindOnce(
&InProgressDownloadManager::OnDBInitialized, weak_factory_.GetWeakPtr()));
}
void InProgressDownloadManager::ShutDown() {
url_download_handlers_.clear();
}
void InProgressDownloadManager::DetermineDownloadTarget(
DownloadItemImpl* download,
const DownloadTargetCallback& callback) {
#if defined(OS_ANDROID)
base::FilePath target_path = download->GetForcedFilePath().empty()
? download->GetTargetFilePath()
: download->GetForcedFilePath();
if (target_path.empty()) {
callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
download->GetDangerType(), target_path,
DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
RecordBackgroundTargetDeterminationResult(
BackgroudTargetDeterminationResultTypes::kTargetPathMissing);
return;
}
// If final target is a content URI, the intermediate path should
// be identical to it.
if (target_path.IsContentUri()) {
callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
download->GetDangerType(), target_path,
DOWNLOAD_INTERRUPT_REASON_NONE);
RecordBackgroundTargetDeterminationResult(
BackgroudTargetDeterminationResultTypes::kSuccess);
return;
}
DownloadPathReservationTracker::GetReservedPath(
download, target_path, target_path.DirName(), default_download_dir_,
true /* create_directory */,
download->GetForcedFilePath().empty()
? DownloadPathReservationTracker::UNIQUIFY
: DownloadPathReservationTracker::OVERWRITE,
base::Bind(&OnPathReserved, callback, download->GetDangerType(),
intermediate_path_cb_, download->GetForcedFilePath()));
#endif
}
void InProgressDownloadManager::ResumeInterruptedDownload(
std::unique_ptr<DownloadUrlParameters> params,
const GURL& site_url) {
if (!url_loader_factory_getter_)
return;
BeginDownload(std::move(params), url_loader_factory_getter_, false, site_url,
GURL(), GURL());
}
bool InProgressDownloadManager::ShouldOpenDownload(
DownloadItemImpl* item,
const ShouldOpenDownloadCallback& callback) {
return true;
}
base::Optional<DownloadEntry> InProgressDownloadManager::GetInProgressEntry(
DownloadItemImpl* download) {
if (!download)
return base::Optional<DownloadEntry>();
if (base::Contains(download_entries_, download->GetGuid()))
return download_entries_[download->GetGuid()];
return base::Optional<DownloadEntry>();
}
void InProgressDownloadManager::ReportBytesWasted(DownloadItemImpl* download) {
download_db_cache_->OnDownloadUpdated(download);
}
void InProgressDownloadManager::RemoveInProgressDownload(
const std::string& guid) {
download_db_cache_->RemoveEntry(guid);
}
void InProgressDownloadManager::StartDownload(
std::unique_ptr<DownloadCreateInfo> info,
std::unique_ptr<InputStream> stream,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
const DownloadUrlParameters::OnStartedCallback& on_started) {
DCHECK(info);
if (info->is_new_download &&
(info->result == DOWNLOAD_INTERRUPT_REASON_NONE ||
info->result ==
DOWNLOAD_INTERRUPT_REASON_SERVER_CROSS_ORIGIN_REDIRECT)) {
if (delegate_ && delegate_->InterceptDownload(*info)) {
GetDownloadTaskRunner()->DeleteSoon(FROM_HERE, stream.release());
return;
}
}
// |stream| is only non-null if the download request was successful.
DCHECK(
(info->result == DOWNLOAD_INTERRUPT_REASON_NONE && !stream->IsEmpty()) ||
(info->result != DOWNLOAD_INTERRUPT_REASON_NONE && stream->IsEmpty()));
DVLOG(20) << __func__
<< "() result=" << DownloadInterruptReasonToString(info->result);
GURL url = info->url();
std::vector<GURL> url_chain = info->url_chain;
std::string mime_type = info->mime_type;
if (info->is_new_download) {
RecordDownloadContentTypeSecurity(info->url(), info->url_chain,
info->mime_type, is_origin_secure_cb_);
}
// If the download cannot be found locally, ask |delegate_| to provide the
// DownloadItem.
if (delegate_ && !GetDownloadByGuid(info->guid)) {
delegate_->StartDownloadItem(
std::move(info), on_started,
base::BindOnce(&InProgressDownloadManager::StartDownloadWithItem,
weak_factory_.GetWeakPtr(), std::move(stream),
std::move(url_loader_factory_getter)));
} else {
std::string guid = info->guid;
if (info->is_new_download) {
auto download = std::make_unique<DownloadItemImpl>(
this, DownloadItem::kInvalidId, *info);
OnNewDownloadCreated(download.get());
in_progress_downloads_.push_back(std::move(download));
}
StartDownloadWithItem(
std::move(stream), std::move(url_loader_factory_getter),
std::move(info),
static_cast<DownloadItemImpl*>(GetDownloadByGuid(guid)), false);
}
}
void InProgressDownloadManager::StartDownloadWithItem(
std::unique_ptr<InputStream> stream,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
std::unique_ptr<DownloadCreateInfo> info,
DownloadItemImpl* download,
bool should_persist_new_download) {
if (!download) {
// If the download is no longer known to the DownloadManager, then it was
// removed after it was resumed. Ignore. If the download is cancelled
// while resuming, then also ignore the request.
if (info->request_handle)
info->request_handle->CancelRequest(true);
// The ByteStreamReader lives and dies on the download sequence.
if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE)
GetDownloadTaskRunner()->DeleteSoon(FROM_HERE, stream.release());
return;
}
base::FilePath default_download_directory;
if (delegate_)
default_download_directory = delegate_->GetDefaultDownloadDirectory();
if (info->is_new_download && !should_persist_new_download)
non_persistent_download_guids_.insert(download->GetGuid());
// If the download is not persisted, don't notify |download_db_cache_|.
if (!base::Contains(non_persistent_download_guids_, download->GetGuid())) {
download_db_cache_->AddOrReplaceEntry(
CreateDownloadDBEntryFromItem(*download));
download->RemoveObserver(download_db_cache_.get());
download->AddObserver(download_db_cache_.get());
}
std::unique_ptr<DownloadFile> download_file;
if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE) {
DCHECK(stream);
download_file.reset(file_factory_->CreateFile(
std::move(info->save_info), default_download_directory,
std::move(stream), download->GetId(),
download->DestinationObserverAsWeakPtr()));
}
// It is important to leave info->save_info intact in the case of an interrupt
// so that the DownloadItem can salvage what it can out of a failed
// resumption attempt.
download->Start(std::move(download_file), std::move(info->request_handle),
*info, std::move(url_loader_factory_getter), nullptr);
if (download_start_observer_)
download_start_observer_->OnDownloadStarted(download);
}
void InProgressDownloadManager::OnDBInitialized(
bool success,
std::unique_ptr<std::vector<DownloadDBEntry>> entries) {
#if defined(OS_ANDROID)
if (!use_empty_db_ &&
DownloadCollectionBridge::NeedToRetrieveDisplayNames()) {
DownloadCollectionBridge::GetDisplayNamesCallback callback =
base::BindOnce(&InProgressDownloadManager::OnDownloadNamesRetrieved,
weak_factory_.GetWeakPtr(), std::move(entries));
GetDownloadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&DownloadCollectionBridge::GetDisplayNamesForDownloads,
base::BindOnce(&OnDownloadDisplayNamesReturned, std::move(callback),
base::ThreadTaskRunnerHandle::Get())));
return;
}
#endif
OnDownloadNamesRetrieved(std::move(entries), nullptr);
}
void InProgressDownloadManager::OnDownloadNamesRetrieved(
std::unique_ptr<std::vector<DownloadDBEntry>> entries,
DisplayNames display_names) {
std::set<uint32_t> download_ids;
int num_duplicates = 0;
display_names_ = std::move(display_names);
for (const auto& entry : *entries) {
base::Optional<DownloadEntry> download_entry =
CreateDownloadEntryFromDownloadDBEntry(entry);
if (download_entry)
download_entries_[download_entry->guid] = download_entry.value();
if (base::FeatureList::IsEnabled(features::kDownloadDBForNewDownloads)) {
auto item = CreateDownloadItemImpl(this, entry);
if (!item)
continue;
uint32_t download_id = item->GetId();
// Remove entries with duplicate ids.
if (download_id != DownloadItem::kInvalidId &&
base::Contains(download_ids, download_id)) {
RemoveInProgressDownload(item->GetGuid());
num_duplicates++;
continue;
}
#if defined(OS_ANDROID)
const base::FilePath& path = item->GetTargetFilePath();
if (path.IsContentUri()) {
base::FilePath display_name = GetDownloadDisplayName(path);
// If a download doesn't have a display name, remove it.
if (display_name.empty()) {
RemoveInProgressDownload(item->GetGuid());
continue;
} else {
item->SetDisplayName(display_name);
}
}
#endif
item->AddObserver(download_db_cache_.get());
OnNewDownloadCreated(item.get());
in_progress_downloads_.emplace_back(std::move(item));
download_ids.insert(download_id);
}
}
if (num_duplicates > 0)
RecordDuplicateInProgressDownloadIdCount(num_duplicates);
OnInitialized();
OnDownloadsInitialized();
}
std::vector<std::unique_ptr<download::DownloadItemImpl>>
InProgressDownloadManager::TakeInProgressDownloads() {
return std::move(in_progress_downloads_);
}
base::FilePath InProgressDownloadManager::GetDownloadDisplayName(
const base::FilePath& path) {
#if defined(OS_ANDROID)
if (!display_names_)
return base::FilePath();
auto iter = display_names_->find(path.value());
if (iter != display_names_->end())
return iter->second;
#endif
return base::FilePath();
}
void InProgressDownloadManager::OnAllInprogressDownloadsLoaded() {
download_entries_.clear();
display_names_.reset();
}
void InProgressDownloadManager::SetDelegate(Delegate* delegate) {
delegate_ = delegate;
if (initialized_)
OnDownloadsInitialized();
}
void InProgressDownloadManager::OnDownloadsInitialized() {
if (delegate_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&InProgressDownloadManager::NotifyDownloadsInitialized,
weak_factory_.GetWeakPtr()));
}
}
void InProgressDownloadManager::NotifyDownloadsInitialized() {
if (delegate_)
delegate_->OnDownloadsInitialized();
}
void InProgressDownloadManager::AddInProgressDownloadForTest(
std::unique_ptr<download::DownloadItemImpl> download) {
in_progress_downloads_.push_back(std::move(download));
}
} // namespace download