| // 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/post_task.h" |
| #include "base/threading/thread_task_runner_handle.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" |
| |
| 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, 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->hash, |
| in_progress_info->state, in_progress_info->danger_type, |
| in_progress_info->interrupt_reason, in_progress_info->paused, 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, |
| 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, 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, |
| scoped_refptr<network::ResourceResponse> response, |
| net::CertStatus cert_status, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| scoped_refptr<DownloadURLLoaderFactoryGetter> url_loader_factory_getter, |
| const URLSecurityPolicy& url_security_policy, |
| 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(response), std::move(cert_status), |
| std::move(url_loader_client_endpoints), |
| std::move(url_loader_factory_getter), url_security_policy, |
| main_task_runner) |
| .release(), |
| base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get())); |
| |
| OnUrlDownloadHandlerCreated(std::move(downloader), download_manager, |
| main_task_runner); |
| } |
| |
| } // namespace |
| |
| InProgressDownloadManager::InProgressDownloadManager( |
| Delegate* delegate, |
| const base::FilePath& in_progress_db_dir, |
| const IsOriginSecureCallback& is_origin_secure_cb, |
| const URLSecurityPolicy& url_security_policy) |
| : is_initialized_(false), |
| delegate_(delegate), |
| file_factory_(new DownloadFileFactory()), |
| download_start_observer_(nullptr), |
| is_origin_secure_cb_(is_origin_secure_cb), |
| url_security_policy_(url_security_policy), |
| weak_factory_(this) { |
| 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::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, 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, |
| 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), |
| std::move(response), std::move(cert_status), |
| std::move(url_loader_client_endpoints), |
| std::move(url_loader_factory_getter), url_security_policy_, |
| 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::OnInitialized, weak_factory_.GetWeakPtr())); |
| } |
| |
| void InProgressDownloadManager::ShutDown() { |
| url_download_handlers_.clear(); |
| } |
| |
| void InProgressDownloadManager::DetermineDownloadTarget( |
| DownloadItemImpl* download, |
| const DownloadTargetCallback& callback) { |
| // TODO(http://crbug.com/851581): handle the case that |target_path| and |
| // |intermediate_path| are empty. |
| base::FilePath target_path = download->GetTargetFilePath().empty() |
| ? download->GetForcedFilePath() |
| : download->GetTargetFilePath(); |
| base::FilePath intermediate_path = download->GetFullPath().empty() |
| ? download->GetForcedFilePath() |
| : download->GetFullPath(); |
| callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, |
| download->GetDangerType(), intermediate_path, |
| DOWNLOAD_INTERRUPT_REASON_NONE); |
| } |
| |
| 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::ContainsKey(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) { |
| RecordDownloadConnectionSecurity(info->url(), info->url_chain); |
| RecordDownloadContentTypeSecurity(info->url(), info->url_chain, |
| info->mime_type, is_origin_secure_cb_); |
| } |
| |
| if (delegate_) { |
| 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; |
| StartDownloadWithItem(std::move(stream), |
| std::move(url_loader_factory_getter), std::move(info), |
| GetInProgressDownload(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::ContainsKey(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. |
| |
| net::URLRequestContextGetter* url_request_context_getter = nullptr; |
| if (delegate_ && |
| !base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| url_request_context_getter = delegate_->GetURLRequestContextGetter(*info); |
| } |
| |
| download->Start(std::move(download_file), std::move(info->request_handle), |
| *info, std::move(url_loader_factory_getter), |
| url_request_context_getter); |
| |
| if (download_start_observer_) |
| download_start_observer_->OnDownloadStarted(download); |
| } |
| |
| void InProgressDownloadManager::OnInitialized( |
| bool success, |
| std::unique_ptr<std::vector<DownloadDBEntry>> entries) { |
| 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; |
| item->AddObserver(download_db_cache_.get()); |
| in_progress_downloads_.emplace_back(std::move(item)); |
| } |
| } |
| if (base::FeatureList::IsEnabled(features::kDownloadDBForNewDownloads)) |
| OnAllInprogressDownloadsLoaded(); |
| is_initialized_ = true; |
| for (auto& callback : on_initialized_callbacks_) |
| std::move(*callback).Run(); |
| on_initialized_callbacks_.clear(); |
| } |
| |
| DownloadItemImpl* InProgressDownloadManager::GetInProgressDownload( |
| const std::string& guid) { |
| for (auto& item : in_progress_downloads_) { |
| if (item->GetGuid() == guid) |
| return item.get(); |
| } |
| return nullptr; |
| } |
| |
| void InProgressDownloadManager::NotifyWhenInitialized( |
| base::OnceClosure on_initialized_cb) { |
| if (is_initialized_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| std::move(on_initialized_cb)); |
| return; |
| } |
| on_initialized_callbacks_.emplace_back( |
| std::make_unique<base::OnceClosure>(std::move(on_initialized_cb))); |
| } |
| |
| std::vector<std::unique_ptr<download::DownloadItemImpl>> |
| InProgressDownloadManager::TakeInProgressDownloads() { |
| return std::move(in_progress_downloads_); |
| } |
| |
| void InProgressDownloadManager::OnAllInprogressDownloadsLoaded() { |
| download_entries_.clear(); |
| } |
| |
| } // namespace download |