blob: c5b3dea3c7c87617d817a1a8a8ce0e14410b36fd [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/optional.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/download/downloader/in_progress/in_progress_cache_impl.h"
#include "components/download/internal/common/resource_downloader.h"
#include "components/download/public/common/download_file.h"
#include "components/download/public/common/download_item_impl.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/resource_request.h"
#include "services/network/public/cpp/resource_response.h"
namespace download {
namespace {
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,
uint32_t download_id,
base::WeakPtr<InProgressDownloadManager> download_manager,
const GURL& site_url,
const GURL& tab_url,
const GURL& tab_referrer_url,
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), site_url, tab_url,
tab_referrer_url, download_id, false, 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,
const base::Optional<std::string>& suggested_filename,
scoped_refptr<network::ResourceResponse> response,
net::CertStatus cert_status,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
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), suggested_filename, std::move(response),
std::move(cert_status), std::move(url_loader_client_endpoints),
std::move(url_loader_factory_getter), main_task_runner)
.release(),
base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
OnUrlDownloadHandlerCreated(std::move(downloader), download_manager,
main_task_runner);
}
// Responsible for persisting the in-progress metadata associated with a
// download.
class InProgressDownloadObserver : public DownloadItem::Observer {
public:
explicit InProgressDownloadObserver(InProgressCache* in_progress_cache);
~InProgressDownloadObserver() override;
private:
// DownloadItem::Observer
void OnDownloadUpdated(DownloadItem* download) override;
void OnDownloadRemoved(DownloadItem* download) override;
// The persistent cache to store in-progress metadata.
InProgressCache* in_progress_cache_;
DISALLOW_COPY_AND_ASSIGN(InProgressDownloadObserver);
};
InProgressDownloadObserver::InProgressDownloadObserver(
InProgressCache* in_progress_cache)
: in_progress_cache_(in_progress_cache) {}
InProgressDownloadObserver::~InProgressDownloadObserver() = default;
void InProgressDownloadObserver::OnDownloadUpdated(DownloadItem* download) {
// TODO(crbug.com/778425): Properly handle fail/resume/retry for downloads
// that are in the INTERRUPTED state for a long time.
if (!in_progress_cache_)
return;
switch (download->GetState()) {
case DownloadItem::DownloadState::COMPLETE:
// Intentional fallthrough.
case DownloadItem::DownloadState::CANCELLED:
if (in_progress_cache_)
in_progress_cache_->RemoveEntry(download->GetGuid());
break;
case DownloadItem::DownloadState::INTERRUPTED:
// Intentional fallthrough.
case DownloadItem::DownloadState::IN_PROGRESS: {
// Make sure the entry exists in the cache.
base::Optional<DownloadEntry> entry_opt =
in_progress_cache_->RetrieveEntry(download->GetGuid());
DownloadEntry entry;
if (!entry_opt.has_value()) {
entry = CreateDownloadEntryFromItem(
*download, std::string(), /* request_origin */
DownloadSource::UNKNOWN, false, /* fetch_error_body */
DownloadUrlParameters::RequestHeadersType());
in_progress_cache_->AddOrReplaceEntry(entry);
break;
}
entry = entry_opt.value();
break;
}
default:
break;
}
}
void InProgressDownloadObserver::OnDownloadRemoved(DownloadItem* download) {
if (!in_progress_cache_)
return;
in_progress_cache_->RemoveEntry(download->GetGuid());
}
} // namespace
InProgressDownloadManager::InProgressDownloadManager(
Delegate* delegate,
const IsOriginSecureCallback& is_origin_secure_cb)
: delegate_(delegate),
file_factory_(new DownloadFileFactory()),
is_origin_secure_cb_(is_origin_secure_cb),
weak_factory_(this) {}
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::BeginDownload(
std::unique_ptr<DownloadUrlParameters> params,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
uint32_t download_id,
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),
download_id, weak_factory_.GetWeakPtr(), site_url, tab_url,
tab_referrer_url, 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,
const base::Optional<std::string>& suggested_filename,
scoped_refptr<network::ResourceResponse> response,
net::CertStatus cert_status,
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), suggested_filename,
std::move(response), std::move(cert_status),
std::move(url_loader_client_endpoints),
std::move(url_loader_factory_getter),
base::ThreadTaskRunnerHandle::Get()));
}
void InProgressDownloadManager::Initialize(
const base::FilePath& metadata_cache_dir,
const base::RepeatingClosure& callback) {
DCHECK(!metadata_cache_dir.empty());
base::FilePath metadata_cache_file =
metadata_cache_dir.Append(kDownloadMetadataStoreFilename);
download_metadata_cache_ = std::make_unique<InProgressCacheImpl>(
metadata_cache_file,
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
download_metadata_cache_->Initialize(callback);
}
void InProgressDownloadManager::ShutDown() {
url_download_handlers_.clear();
}
void InProgressDownloadManager::ResumeInterruptedDownload(
std::unique_ptr<DownloadUrlParameters> params,
uint32_t id,
const GURL& site_url) {}
base::Optional<DownloadEntry> InProgressDownloadManager::GetInProgressEntry(
DownloadItemImpl* download) {
if (!download || !download_metadata_cache_)
return base::Optional<DownloadEntry>();
return download_metadata_cache_->RetrieveEntry(download->GetGuid());
}
void InProgressDownloadManager::ReportBytesWasted(DownloadItemImpl* download) {
if (!download_metadata_cache_)
return;
base::Optional<DownloadEntry> entry_opt =
download_metadata_cache_->RetrieveEntry(download->GetGuid());
if (entry_opt.has_value()) {
DownloadEntry entry = entry_opt.value();
entry.bytes_wasted = download->GetBytesWasted();
download_metadata_cache_->AddOrReplaceEntry(entry);
}
}
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);
uint32_t download_id = info->download_id;
bool new_download = (download_id == DownloadItem::kInvalidId);
if (new_download && info->result == DOWNLOAD_INTERRUPT_REASON_NONE) {
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 (new_download) {
RecordDownloadConnectionSecurity(info->url(), info->url_chain);
RecordDownloadContentTypeSecurity(info->url(), info->url_chain,
info->mime_type, is_origin_secure_cb_);
}
base::RepeatingCallback<void(uint32_t)> got_id(base::BindRepeating(
&InProgressDownloadManager::StartDownloadWithId,
weak_factory_.GetWeakPtr(), base::Passed(&info), base::Passed(&stream),
std::move(url_loader_factory_getter), on_started, new_download));
if (new_download) {
// TODO(qinmin): use GUID as the key for downloads history table so we don't
// rely on the delegate to provide the next download ID.
if (delegate_)
delegate_->GetNextId(std::move(got_id));
} else {
std::move(got_id).Run(download_id);
}
}
void InProgressDownloadManager::StartDownloadWithId(
std::unique_ptr<DownloadCreateInfo> info,
std::unique_ptr<InputStream> stream,
scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter,
const DownloadUrlParameters::OnStartedCallback& on_started,
bool new_download,
uint32_t id) {
DCHECK_NE(DownloadItem::kInvalidId, id);
DownloadItemImpl* download =
delegate_ ? delegate_->GetDownloadItem(id, new_download, *info) : nullptr;
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);
if (!on_started.is_null())
on_started.Run(nullptr, DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
// 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 (download_metadata_cache_) {
base::Optional<DownloadEntry> entry_opt =
download_metadata_cache_->RetrieveEntry(download->GetGuid());
if (!entry_opt.has_value()) {
download_metadata_cache_->AddOrReplaceEntry(CreateDownloadEntryFromItem(
*download, info->request_origin, info->download_source,
info->fetch_error_body, info->request_headers));
}
}
if (!in_progress_download_observer_) {
in_progress_download_observer_ =
std::make_unique<InProgressDownloadObserver>(
download_metadata_cache_.get());
}
// May already observe this item, remove observer first.
download->RemoveObserver(in_progress_download_observer_.get());
download->AddObserver(in_progress_download_observer_.get());
std::unique_ptr<DownloadFile> download_file;
if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE) {
DCHECK(stream.get());
download_file.reset(file_factory_->CreateFile(
std::move(info->save_info), default_download_directory,
std::move(stream), id, 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),
delegate_ ? delegate_->GetURLRequestContextGetter(*info) : nullptr);
// For interrupted downloads, Start() will transition the state to
// IN_PROGRESS and consumers will be notified via OnDownloadUpdated().
// For new downloads, we notify here, rather than earlier, so that
// the download_file is bound to download and all the usual
// setters (e.g. Cancel) work.
if (new_download && delegate_)
delegate_->OnNewDownloadStarted(download);
if (!on_started.is_null())
on_started.Run(download, DOWNLOAD_INTERRUPT_REASON_NONE);
}
} // namespace download