| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/download/download_manager_impl.h" |
| |
| #include <iterator> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/debug/alias.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/i18n/case_conversion.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/observer_list.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/supports_user_data.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "build/build_config.h" |
| #include "components/download/database/in_progress/download_entry.h" |
| #include "components/download/public/common/download_create_info.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_factory.h" |
| #include "components/download/public/common/download_item_impl.h" |
| #include "components/download/public/common/download_item_rename_handler.h" |
| #include "components/download/public/common/download_stats.h" |
| #include "components/download/public/common/download_target_info.h" |
| #include "components/download/public/common/download_task_runner.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 "components/download/public/common/url_download_handler_factory.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/blob_storage/chrome_blob_storage_context.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/data_url_loader_factory.h" |
| #include "content/browser/devtools/devtools_instrumentation.h" |
| #include "content/browser/download/embedder_download_data.pb.h" |
| #include "content/browser/file_system/file_system_url_loader_factory.h" |
| #include "content/browser/loader/file_url_loader_factory.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/site_info.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/device_service.h" |
| #include "content/public/browser/disallow_activation_reason.h" |
| #include "content/public/browser/download_item_utils.h" |
| #include "content/public/browser/download_manager_delegate.h" |
| #include "content/public/browser/download_request_utils.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/shared_cors_origin_access_list.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/browser/web_ui_url_loader_factory.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/url_constants.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "net/base/elements_upload_data_stream.h" |
| #include "net/base/isolation_info.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/base/request_priority.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/is_potentially_trustworthy.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/url_loader_factory_builder.h" |
| #include "services/network/public/cpp/url_util.h" |
| #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" |
| #include "third_party/blink/public/common/loader/referrer_utils.h" |
| #include "third_party/blink/public/common/loader/throttling_url_loader.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "net/http/http_content_disposition.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| namespace content { |
| namespace { |
| #if BUILDFLAG(IS_ANDROID) |
| // PDF MIME type. |
| constexpr char kPdfMimeType[] = "application/pdf"; |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| void DeleteDownloadedFileOnUIThread(const base::FilePath& file_path) { |
| if (!file_path.empty()) { |
| download::GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&download::DeleteDownloadedFile), |
| file_path)); |
| } |
| } |
| |
| StoragePartitionImpl* GetStoragePartitionForConfig( |
| BrowserContext* context, |
| const StoragePartitionConfig& storage_partition_config) { |
| return static_cast<StoragePartitionImpl*>( |
| context->GetStoragePartition(storage_partition_config)); |
| } |
| |
| void OnDownloadStarted( |
| download::DownloadItemImpl* download, |
| download::DownloadUrlParameters::OnStartedCallback on_started) { |
| if (on_started.is_null()) |
| return; |
| |
| if (!download || download->GetState() == download::DownloadItem::CANCELLED) { |
| std::move(on_started) |
| .Run(nullptr, download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); |
| } else { |
| std::move(on_started) |
| .Run(download, download::DOWNLOAD_INTERRUPT_REASON_NONE); |
| } |
| } |
| |
| // Creates an interrupted download and calls StartDownload. Can be called on |
| // any thread. |
| void CreateInterruptedDownload( |
| std::unique_ptr<download::DownloadUrlParameters> params, |
| download::DownloadInterruptReason reason, |
| base::WeakPtr<DownloadManagerImpl> download_manager) { |
| std::unique_ptr<download::DownloadCreateInfo> failed_created_info( |
| new download::DownloadCreateInfo( |
| base::Time::Now(), base::WrapUnique(new download::DownloadSaveInfo))); |
| failed_created_info->url_chain.push_back(params->url()); |
| failed_created_info->result = reason; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DownloadManagerImpl::StartDownload, download_manager, |
| std::move(failed_created_info), |
| std::make_unique<download::InputStream>(), |
| std::move(params->callback()))); |
| } |
| |
| class DownloadItemFactoryImpl : public download::DownloadItemFactory { |
| public: |
| DownloadItemFactoryImpl() {} |
| ~DownloadItemFactoryImpl() override {} |
| |
| download::DownloadItemImpl* CreatePersistedItem( |
| download::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 std::string& serialized_embedder_download_data, |
| const GURL& tab_url, |
| const GURL& tab_refererr_url, |
| const std::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, |
| const std::string& hash, |
| download::DownloadItem::DownloadState state, |
| download::DownloadDangerType danger_type, |
| download::DownloadInterruptReason interrupt_reason, |
| bool opened, |
| base::Time last_access_time, |
| bool transient, |
| const std::vector<download::DownloadItem::ReceivedSlice>& received_slices) |
| override { |
| // For history download only as history don't have auto resumption count |
| // saved. |
| int auto_resume_count = download::DownloadItemImpl::kMaxAutoResumeAttempts; |
| |
| return new download::DownloadItemImpl( |
| delegate, guid, download_id, current_path, target_path, url_chain, |
| referrer_url, serialized_embedder_download_data, tab_url, |
| tab_refererr_url, request_initiator, mime_type, original_mime_type, |
| start_time, end_time, etag, last_modified, received_bytes, total_bytes, |
| auto_resume_count, hash, state, danger_type, interrupt_reason, |
| false /* paused */, false /* allow_metered */, opened, last_access_time, |
| transient, received_slices, download::kInvalidRange, |
| download::kInvalidRange, nullptr /* download_entry */); |
| } |
| |
| download::DownloadItemImpl* CreateActiveItem( |
| download::DownloadItemImplDelegate* delegate, |
| uint32_t download_id, |
| const download::DownloadCreateInfo& info) override { |
| return new download::DownloadItemImpl(delegate, download_id, info); |
| } |
| |
| download::DownloadItemImpl* CreateSavePageItem( |
| download::DownloadItemImplDelegate* delegate, |
| uint32_t download_id, |
| const base::FilePath& path, |
| const base::FilePath& display_name, |
| const GURL& url, |
| const std::string& mime_type, |
| download::DownloadJob::CancelRequestCallback cancel_request_callback) |
| override { |
| return new download::DownloadItemImpl(delegate, download_id, path, |
| display_name, url, mime_type, |
| std::move(cancel_request_callback)); |
| } |
| }; |
| |
| std::unique_ptr<network::PendingSharedURLLoaderFactory> |
| CreatePendingSharedURLLoaderFactory(StoragePartitionImpl* storage_partition, |
| RenderFrameHost* rfh) { |
| network::URLLoaderFactoryBuilder factory_builder; |
| |
| if (rfh) { |
| // Allow DevTools to potentially inject itself into `factory_builder`. |
| devtools_instrumentation::WillCreateURLLoaderFactoryParams::ForFrame( |
| static_cast<RenderFrameHostImpl*>(rfh)) |
| .Run(/*is_navigation=*/true, |
| /*is_download=*/true, factory_builder, |
| nullptr /* factory_override */); |
| |
| // Also allow the Content embedder to inject itself if it wants to. |
| GetContentClient()->browser()->WillCreateURLLoaderFactory( |
| rfh->GetSiteInstance()->GetBrowserContext(), rfh, |
| rfh->GetProcess()->GetDeprecatedID(), |
| ContentBrowserClient::URLLoaderFactoryType::kDownload, url::Origin(), |
| net::IsolationInfo(), /*navigation_id=*/std::nullopt, |
| ukm::kInvalidSourceIdObj, factory_builder, /*header_client=*/nullptr, |
| /*bypass_redirect_checks=*/nullptr, /*disable_secure_dns=*/nullptr, |
| /*factory_override=*/nullptr, |
| /*navigation_response_task_runner=*/nullptr); |
| } |
| |
| return std::make_unique<network::PendingSharedURLLoaderFactoryWithBuilder>( |
| std::move(factory_builder), |
| storage_partition->GetURLLoaderFactoryForBrowserProcessIOThread()); |
| } |
| |
| void RecordDownloadOpenerType(RenderFrameHost* current, |
| RenderFrameHost* opener) { |
| DCHECK(current); |
| DCHECK(opener); |
| if (!opener->GetLastCommittedURL().SchemeIsHTTPOrHTTPS() || |
| !current->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { |
| UMA_HISTOGRAM_ENUMERATION("Download.InitiatedByWindowOpener", |
| InitiatedByWindowOpenerType::kNonHTTPOrHTTPS); |
| return; |
| } |
| if (opener->GetLastCommittedOrigin() == current->GetLastCommittedOrigin()) { |
| UMA_HISTOGRAM_ENUMERATION("Download.InitiatedByWindowOpener", |
| InitiatedByWindowOpenerType::kSameOrigin); |
| return; |
| } |
| if (net::registry_controlled_domains::SameDomainOrHost( |
| opener->GetLastCommittedOrigin(), current->GetLastCommittedOrigin(), |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { |
| UMA_HISTOGRAM_ENUMERATION("Download.InitiatedByWindowOpener", |
| InitiatedByWindowOpenerType::kSameSite); |
| return; |
| } |
| UMA_HISTOGRAM_ENUMERATION("Download.InitiatedByWindowOpener", |
| InitiatedByWindowOpenerType::kCrossOrigin); |
| } |
| |
| } // namespace |
| |
| DownloadManagerImpl::DownloadManagerImpl(BrowserContext* browser_context) |
| : item_factory_(new DownloadItemFactoryImpl()), |
| shutdown_needed_(true), |
| history_db_initialized_(false), |
| in_progress_cache_initialized_(false), |
| browser_context_(browser_context), |
| delegate_(nullptr), |
| in_progress_manager_( |
| browser_context_->RetrieveInProgressDownloadManager()), |
| next_download_id_(download::DownloadItem::kInvalidId), |
| is_history_download_id_retrieved_(false), |
| should_persist_new_download_(false), |
| disk_access_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})) { |
| DCHECK(browser_context); |
| |
| download::SetIOTaskRunner(GetIOThreadTaskRunner({})); |
| |
| if (!in_progress_manager_) { |
| auto* proto_db_provider = browser_context->GetDefaultStoragePartition() |
| ->GetProtoDatabaseProvider(); |
| in_progress_manager_ = |
| std::make_unique<download::InProgressDownloadManager>( |
| this, base::FilePath(), proto_db_provider, |
| base::BindRepeating(&network::IsUrlPotentiallyTrustworthy), |
| base::BindRepeating(&DownloadRequestUtils::IsURLSafe), |
| /*wake_lock_provider_binder=*/base::NullCallback()); |
| } else { |
| in_progress_manager_->SetDelegate(this); |
| in_progress_manager_->set_download_start_observer(nullptr); |
| in_progress_manager_->set_is_origin_secure_cb( |
| base::BindRepeating(&network::IsUrlPotentiallyTrustworthy)); |
| } |
| } |
| |
| DownloadManagerImpl::~DownloadManagerImpl() { |
| DCHECK(!shutdown_needed_); |
| } |
| |
| download::DownloadItemImpl* DownloadManagerImpl::CreateActiveItem( |
| uint32_t id, |
| const download::DownloadCreateInfo& info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (base::Contains(downloads_by_guid_, info.guid)) |
| return nullptr; |
| |
| download::DownloadItemImpl* download = |
| item_factory_->CreateActiveItem(this, id, info); |
| |
| downloads_[id] = base::WrapUnique(download); |
| downloads_by_guid_[download->GetGuid()] = download; |
| GlobalRenderFrameHostId global_id(info.render_process_id, |
| info.render_frame_id); |
| DownloadItemUtils::AttachInfo( |
| download, GetBrowserContext(), |
| WebContentsImpl::FromRenderFrameHostID(global_id), global_id); |
| if (delegate_) { |
| delegate_->AttachExtraInfo(download); |
| #if BUILDFLAG(IS_ANDROID) |
| download->set_is_from_external_app(delegate_->IsFromExternalApp(download)); |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| return download; |
| } |
| |
| void DownloadManagerImpl::GetNextId(GetNextIdCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (IsNextIdInitialized()) { |
| std::move(callback).Run(next_download_id_++); |
| return; |
| } |
| |
| id_callbacks_.emplace_back( |
| std::make_unique<GetNextIdCallback>(std::move(callback))); |
| // If we are first time here, call the delegate to get the next ID from |
| // history db. |
| if (!is_history_download_id_retrieved_ && id_callbacks_.size() == 1u) { |
| if (delegate_) { |
| delegate_->GetNextId( |
| base::BindOnce(&DownloadManagerImpl::OnHistoryNextIdRetrieved, |
| weak_factory_.GetWeakPtr())); |
| } else { |
| OnHistoryNextIdRetrieved(download::DownloadItem::kInvalidId); |
| } |
| } |
| } |
| |
| void DownloadManagerImpl::SetNextId(uint32_t next_id) { |
| if (next_id > next_download_id_) |
| next_download_id_ = next_id; |
| if (!IsNextIdInitialized()) |
| return; |
| |
| for (auto& callback : id_callbacks_) |
| std::move(*callback).Run(next_download_id_++); |
| id_callbacks_.clear(); |
| } |
| |
| std::string |
| DownloadManagerImpl::StoragePartitionConfigToSerializedEmbedderDownloadData( |
| const StoragePartitionConfig& storage_partition_config) { |
| proto::EmbedderDownloadData embedder_download_data; |
| proto::StoragePartitionConfig* config_proto = |
| embedder_download_data.mutable_storage_partition_config(); |
| config_proto->set_partition_domain( |
| storage_partition_config.partition_domain()); |
| config_proto->set_partition_name(storage_partition_config.partition_name()); |
| config_proto->set_in_memory(storage_partition_config.in_memory()); |
| |
| switch ( |
| storage_partition_config.fallback_to_partition_domain_for_blob_urls()) { |
| case StoragePartitionConfig::FallbackMode::kNone: |
| config_proto->set_fallback_mode( |
| proto::StoragePartitionConfig_FallbackMode_kNone); |
| break; |
| case StoragePartitionConfig::FallbackMode::kFallbackPartitionOnDisk: |
| config_proto->set_fallback_mode( |
| proto::StoragePartitionConfig_FallbackMode_kPartitionOnDisk); |
| break; |
| case StoragePartitionConfig::FallbackMode::kFallbackPartitionInMemory: |
| config_proto->set_fallback_mode( |
| proto::StoragePartitionConfig_FallbackMode_kPartitionInMemory); |
| break; |
| default: |
| config_proto->set_fallback_mode( |
| proto::StoragePartitionConfig_FallbackMode_kNone); |
| } |
| return embedder_download_data.SerializeAsString(); |
| } |
| |
| StoragePartitionConfig |
| DownloadManagerImpl::SerializedEmbedderDownloadDataToStoragePartitionConfig( |
| const std::string& serialized_embedder_download_data) { |
| proto::EmbedderDownloadData embedder_download_data; |
| bool success = |
| embedder_download_data.ParseFromString(serialized_embedder_download_data); |
| DCHECK(success); |
| |
| StoragePartitionConfig::FallbackMode fallback_mode; |
| switch (embedder_download_data.storage_partition_config().fallback_mode()) { |
| case proto::StoragePartitionConfig_FallbackMode_kNone: |
| fallback_mode = StoragePartitionConfig::FallbackMode::kNone; |
| break; |
| case proto::StoragePartitionConfig_FallbackMode_kPartitionOnDisk: |
| fallback_mode = |
| StoragePartitionConfig::FallbackMode::kFallbackPartitionOnDisk; |
| break; |
| case proto::StoragePartitionConfig_FallbackMode_kPartitionInMemory: |
| fallback_mode = |
| StoragePartitionConfig::FallbackMode::kFallbackPartitionInMemory; |
| break; |
| default: |
| fallback_mode = StoragePartitionConfig::FallbackMode::kNone; |
| break; |
| } |
| |
| StoragePartitionConfig config; |
| if (embedder_download_data.storage_partition_config() |
| .partition_domain() |
| .empty()) { |
| config = StoragePartitionConfig::CreateDefault(browser_context_); |
| } else { |
| config = StoragePartitionConfig::Create( |
| browser_context_, |
| embedder_download_data.storage_partition_config().partition_domain(), |
| embedder_download_data.storage_partition_config().partition_name(), |
| embedder_download_data.storage_partition_config().in_memory()); |
| } |
| config.set_fallback_to_partition_domain_for_blob_urls(fallback_mode); |
| return config; |
| } |
| |
| void DownloadManagerImpl::OnHistoryNextIdRetrieved(uint32_t next_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| is_history_download_id_retrieved_ = true; |
| if (next_id == download::DownloadItem::kInvalidId) |
| next_id++; |
| else |
| should_persist_new_download_ = true; |
| SetNextId(next_id); |
| } |
| |
| StoragePartitionConfig DownloadManagerImpl::GetStoragePartitionConfigForSiteUrl( |
| const GURL& site_url) { |
| return SiteInfo::GetStoragePartitionConfigForUrl(browser_context_, site_url); |
| } |
| |
| void DownloadManagerImpl::DetermineDownloadTarget( |
| download::DownloadItemImpl* item, |
| download::DownloadTargetCallback callback) { |
| if (!delegate_ || !delegate_->DetermineDownloadTarget(item, &callback)) { |
| base::FilePath target_path = item->GetForcedFilePath(); |
| // TODO(asanka): Determine a useful path if |target_path| is empty. |
| download::DownloadTargetInfo target_info; |
| target_info.target_path = target_path; |
| target_info.intermediate_path = target_path; |
| |
| std::move(callback).Run(std::move(target_info)); |
| } |
| } |
| |
| bool DownloadManagerImpl::ShouldCompleteDownload( |
| download::DownloadItemImpl* item, |
| base::OnceClosure complete_callback) { |
| if (!delegate_ || |
| delegate_->ShouldCompleteDownload(item, std::move(complete_callback))) { |
| return true; |
| } |
| // Otherwise, the delegate has accepted responsibility to run the |
| // callback when the download is ready for completion. |
| // TODO(qinmin): When returning false, the |complete_callback| should |
| // be run by this class eventually. To do so we can't pass ownership |
| // to |delegate_| unconditionally. |
| return false; |
| } |
| |
| bool DownloadManagerImpl::ShouldAutomaticallyOpenFile( |
| const GURL& url, |
| const base::FilePath& path) { |
| if (!delegate_) |
| return false; |
| |
| return delegate_->ShouldAutomaticallyOpenFile(url, path); |
| } |
| |
| bool DownloadManagerImpl::ShouldAutomaticallyOpenFileByPolicy( |
| const GURL& url, |
| const base::FilePath& path) { |
| if (!delegate_) |
| return false; |
| |
| return delegate_->ShouldAutomaticallyOpenFileByPolicy(url, path); |
| } |
| |
| bool DownloadManagerImpl::ShouldOpenDownload( |
| download::DownloadItemImpl* item, |
| ShouldOpenDownloadCallback callback) { |
| if (!delegate_) |
| return true; |
| |
| // Relies on DownloadItemImplDelegate::ShouldOpenDownloadCallback and |
| // DownloadManagerDelegate::DownloadOpenDelayedCallback "just happening" |
| // to have the same type :-}. |
| return delegate_->ShouldOpenDownload(item, std::move(callback)); |
| } |
| |
| void DownloadManagerImpl::SetDelegate(DownloadManagerDelegate* delegate) { |
| delegate_ = delegate; |
| } |
| |
| DownloadManagerDelegate* DownloadManagerImpl::GetDelegate() { |
| return delegate_; |
| } |
| |
| void DownloadManagerImpl::Shutdown() { |
| DVLOG(20) << __func__ << "() shutdown_needed_ = " << shutdown_needed_; |
| if (!shutdown_needed_) |
| return; |
| shutdown_needed_ = false; |
| |
| for (auto& observer : observers_) |
| observer.ManagerGoingDown(this); |
| // TODO(benjhayden): Consider clearing observers_. |
| |
| // If there are in-progress downloads, cancel them. This also goes for |
| // dangerous downloads which will remain in history if they aren't explicitly |
| // accepted or discarded. Canceling will remove the intermediate download |
| // file. |
| for (const auto& it : downloads_by_guid_) { |
| download::DownloadItemImpl* download = it.second; |
| if (download->GetState() == download::DownloadItem::IN_PROGRESS) { |
| download->Cancel(false); |
| if (delegate_) { |
| delegate_->OnDownloadCanceledAtShutdown(download); |
| } |
| } |
| } |
| |
| downloads_by_guid_.clear(); |
| downloads_.clear(); |
| |
| // We'll have nothing more to report to the observers after this point. |
| observers_.Clear(); |
| |
| if (in_progress_manager_) |
| in_progress_manager_->ShutDown(); |
| |
| if (delegate_) |
| delegate_->Shutdown(); |
| delegate_ = nullptr; |
| } |
| |
| bool DownloadManagerImpl::InterceptDownload( |
| const download::DownloadCreateInfo& info) { |
| WebContents* web_contents = WebContentsImpl::FromRenderFrameHostID( |
| info.render_process_id, info.render_frame_id); |
| if (info.is_new_download && |
| info.result == |
| download::DOWNLOAD_INTERRUPT_REASON_SERVER_CROSS_ORIGIN_REDIRECT) { |
| std::vector<GURL> url_chain(info.url_chain); |
| GURL url = url_chain.back(); |
| if ((url.SchemeIsHTTPOrHTTPS() || |
| GetContentClient()->browser()->IsHandledURL(url)) && |
| web_contents) { |
| url_chain.pop_back(); |
| NavigationController::LoadURLParams params(url); |
| params.has_user_gesture = info.has_user_gesture; |
| params.referrer = Referrer( |
| info.referrer_url, |
| blink::ReferrerUtils::NetToMojoReferrerPolicy(info.referrer_policy)); |
| params.redirect_chain = url_chain; |
| params.frame_tree_node_id = |
| RenderFrameHost::GetFrameTreeNodeIdForRoutingId( |
| info.render_process_id, info.render_frame_id); |
| params.from_download_cross_origin_redirect = true; |
| params.initiator_origin = info.request_initiator; |
| params.is_renderer_initiated = info.is_content_initiated; |
| |
| // Ensure the method is called from an active document. |
| // If inactive documents start download, it can be a security risk. |
| // Call ReceiveBadMessage to terminate such a renderer. |
| // TODO(crbug.com/40201479): confirm if fenced frames are allowed to start |
| // downloads. |
| if (!RenderFrameHost::FromID(info.render_process_id, info.render_frame_id) |
| ->IsActive()) { |
| bad_message::ReceivedBadMessage( |
| info.render_process_id, |
| bad_message::RFH_INTERECEPT_DOWNLOAD_WHILE_INACTIVE); |
| return false; |
| } |
| |
| web_contents->GetController().LoadURLWithParams(params); |
| } |
| return true; |
| } |
| |
| std::string user_agent; |
| for (const auto& header : info.request_headers) { |
| if (header.first == net::HttpRequestHeaders::kUserAgent) { |
| user_agent = header.second; |
| break; |
| } |
| } |
| |
| if (delegate_ && delegate_->InterceptDownloadIfApplicable( |
| info.url(), user_agent, info.content_disposition, |
| info.mime_type, info.request_origin, info.total_bytes, |
| info.transient, web_contents)) { |
| DropDownload(); |
| return true; |
| } |
| return false; |
| } |
| |
| base::FilePath DownloadManagerImpl::GetDefaultDownloadDirectory() { |
| base::FilePath default_download_directory; |
| |
| if (delegate_ && default_download_directory.empty()) { |
| base::FilePath website_save_directory; // Unused |
| delegate_->GetSaveDir(GetBrowserContext(), &website_save_directory, |
| &default_download_directory); |
| } |
| |
| if (default_download_directory.empty()) { |
| // |default_download_directory| can still be empty if ContentBrowserClient |
| // returned an empty path for the downloads directory. |
| default_download_directory = |
| GetContentClient()->browser()->GetDefaultDownloadDirectory(); |
| } |
| |
| return default_download_directory; |
| } |
| |
| void DownloadManagerImpl::OnDownloadsInitialized() { |
| in_progress_downloads_ = in_progress_manager_->TakeInProgressDownloads(); |
| uint32_t max_id = download::DownloadItem::kInvalidId; |
| for (auto it = in_progress_downloads_.begin(); |
| it != in_progress_downloads_.end();) { |
| download::DownloadItemImpl* download = it->get(); |
| uint32_t id = download->GetId(); |
| if (id > max_id) |
| max_id = id; |
| |
| // Clean up cancelled and non resumable interrupted downloads. |
| if (ShouldClearDownloadFromDB(download->GetURL(), download->GetState(), |
| download->GetLastReason(), |
| download->GetStartTime())) { |
| cleared_download_guids_on_startup_.insert(download->GetGuid()); |
| DeleteDownloadedFileOnUIThread(download->GetFullPath()); |
| it = in_progress_downloads_.erase(it); |
| continue; |
| } |
| ++it; |
| } |
| PostInitialization(DOWNLOAD_INITIALIZATION_DEPENDENCY_IN_PROGRESS_CACHE); |
| SetNextId(max_id + 1); |
| } |
| |
| void DownloadManagerImpl::StartDownloadItem( |
| std::unique_ptr<download::DownloadCreateInfo> info, |
| download::DownloadUrlParameters::OnStartedCallback on_started, |
| download::InProgressDownloadManager::StartDownloadItemCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| download::DownloadItemImpl* download = |
| static_cast<download::DownloadItemImpl*>(GetDownloadByGuid(info->guid)); |
| if (!info->is_new_download) { |
| if (!download || download->GetState() == download::DownloadItem::CANCELLED) |
| download = nullptr; |
| std::move(callback).Run(std::move(info), download, base::FilePath(), |
| should_persist_new_download_); |
| OnDownloadStarted(download, std::move(on_started)); |
| } else { |
| // If the download already in system, it can only be resumed. |
| if (!info->guid.empty() && download) { |
| LOG(WARNING) << "A download with the same GUID already exists, the new " |
| "request is ignored."; |
| return; |
| } |
| GetNextId(base::BindOnce(&DownloadManagerImpl::OnNewDownloadIdRetrieved, |
| weak_factory_.GetWeakPtr(), std::move(info), |
| std::move(on_started), std::move(callback))); |
| } |
| } |
| |
| void DownloadManagerImpl::OnNewDownloadIdRetrieved( |
| std::unique_ptr<download::DownloadCreateInfo> info, |
| download::DownloadUrlParameters::OnStartedCallback on_started, |
| download::InProgressDownloadManager::StartDownloadItemCallback callback, |
| uint32_t id) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (info->transient && !info->is_must_download && |
| delegate_->ShouldOpenPdfInline() && |
| base::EqualsCaseInsensitiveASCII(info->mime_type, kPdfMimeType)) { |
| if (IsOffTheRecord()) { |
| info->save_info->use_in_memory_file = true; |
| } else { |
| for (const auto& iter : downloads_by_guid_) { |
| download::DownloadItem* item = iter.second; |
| if (item->GetFileExternallyRemoved() || |
| item->GetState() != download::DownloadItem::COMPLETE) { |
| continue; |
| } |
| |
| if (item->GetMimeType() != kPdfMimeType || |
| item->GetUrlChain() != info->url_chain) { |
| continue; |
| } |
| |
| if (!item->IsTransient() || item->IsMustDownload()) { |
| continue; |
| } |
| |
| disk_access_task_runner_->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&base::PathExists, item->GetTargetFilePath()), |
| base::BindOnce(&DownloadManagerImpl::CreateNewDownloadItemToStart, |
| weak_factory_.GetWeakPtr(), std::move(info), |
| std::move(on_started), std::move(callback), id, |
| item->GetTargetFilePath())); |
| return; |
| } |
| } |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| CreateNewDownloadItemToStart(std::move(info), std::move(on_started), |
| std::move(callback), id, base::FilePath(), |
| false); |
| } |
| |
| void DownloadManagerImpl::CreateNewDownloadItemToStart( |
| std::unique_ptr<download::DownloadCreateInfo> info, |
| download::DownloadUrlParameters::OnStartedCallback on_started, |
| download::InProgressDownloadManager::StartDownloadItemCallback callback, |
| uint32_t id, |
| const base::FilePath& duplicate_download_file_path, |
| bool duplicate_file_exists) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| download::DownloadItemImpl* download = CreateActiveItem(id, *info); |
| if (delegate_ && info->save_info) { |
| info->save_info->needs_obfuscation = |
| delegate_->ShouldObfuscateDownload(download); |
| info->save_info->total_bytes = info->total_bytes; |
| } |
| content::devtools_instrumentation::WillBeginDownload(info.get(), download); |
| // Check if the download is a duplicate. Only GET download URL that has |
| // existed are considered duplicate. |
| bool is_duplicate = duplicate_file_exists && (info->method == "GET"); |
| if (is_duplicate) { |
| bool found_same_url = false; |
| // If there is another download with the same path, the download is |
| // not a duplicate. |
| for (auto it = downloads_.begin(); it != downloads_.end(); ++it) { |
| if (it->second->GetTargetFilePath() == duplicate_download_file_path) { |
| if (it->second->GetURL() != info->url()) { |
| is_duplicate = false; |
| break; |
| } else { |
| found_same_url = true; |
| } |
| } |
| } |
| is_duplicate = is_duplicate && found_same_url; |
| } |
| std::move(callback).Run( |
| std::move(info), download, |
| is_duplicate ? duplicate_download_file_path : base::FilePath(), |
| should_persist_new_download_); |
| if (download) { |
| // 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. |
| for (auto& observer : observers_) |
| observer.OnDownloadCreated(this, download); |
| OnNewDownloadCreated(download); |
| } |
| |
| OnDownloadStarted(download, std::move(on_started)); |
| } |
| |
| void DownloadManagerImpl::BindWakeLockProvider( |
| mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| GetDeviceService().BindWakeLockProvider(std::move(receiver)); |
| } |
| |
| download::QuarantineConnectionCallback |
| DownloadManagerImpl::GetQuarantineConnectionCallback() { |
| if (!delegate_) |
| return base::NullCallback(); |
| |
| return delegate_->GetQuarantineConnectionCallback(); |
| } |
| |
| std::unique_ptr<download::DownloadItemRenameHandler> |
| DownloadManagerImpl::GetRenameHandlerForDownload( |
| download::DownloadItemImpl* download_item) { |
| if (!delegate_) { |
| return nullptr; |
| } |
| |
| return delegate_->GetRenameHandlerForDownload(download_item); |
| } |
| |
| void DownloadManagerImpl::StartDownload( |
| std::unique_ptr<download::DownloadCreateInfo> info, |
| std::unique_ptr<download::InputStream> stream, |
| download::DownloadUrlParameters::OnStartedCallback on_started) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(info); |
| in_progress_manager_->StartDownload( |
| std::move(info), std::move(stream), |
| download::URLLoaderFactoryProvider::GetNullPtr(), base::DoNothing(), |
| std::move(on_started)); |
| } |
| |
| void DownloadManagerImpl::CheckForHistoryFilesRemoval() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| for (const auto& it : downloads_by_guid_) { |
| CheckForFileRemoval(it.second); |
| } |
| } |
| |
| void DownloadManagerImpl::OnHistoryQueryComplete( |
| base::OnceClosure load_history_downloads_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!in_progress_cache_initialized_) |
| load_history_downloads_cb_ = std::move(load_history_downloads_cb); |
| else |
| std::move(load_history_downloads_cb).Run(); |
| } |
| |
| void DownloadManagerImpl::CheckForFileRemoval( |
| download::DownloadItemImpl* download_item) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if ((download_item->GetState() != download::DownloadItem::COMPLETE) || |
| download_item->GetFileExternallyRemoved()) { |
| return; |
| } |
| |
| // Check whether an task is already queued or running for the current download |
| // and skip this check if it is the case. |
| if (!pending_disk_access_query_.insert(download_item->GetGuid()).second) |
| return; |
| |
| disk_access_task_runner_->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&base::PathExists, download_item->GetTargetFilePath()), |
| base::BindOnce(&DownloadManagerImpl::OnFileExistenceChecked, |
| weak_factory_.GetWeakPtr(), download_item->GetGuid())); |
| } |
| |
| void DownloadManagerImpl::OnFileExistenceChecked(const std::string& guid, |
| bool result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Remove the pending check flag for this download to allow new requests. |
| pending_disk_access_query_.erase(guid); |
| |
| if (!result) { // File does not exist. |
| auto it = downloads_by_guid_.find(guid); |
| if (it != downloads_by_guid_.end()) |
| it->second->OnDownloadedFileRemoved(); |
| } |
| } |
| |
| std::string DownloadManagerImpl::GetApplicationClientIdForFileScanning() const { |
| if (delegate_) |
| return delegate_->ApplicationClientIdForFileScanning(); |
| return std::string(); |
| } |
| |
| BrowserContext* DownloadManagerImpl::GetBrowserContext() { |
| return browser_context_; |
| } |
| |
| void DownloadManagerImpl::CreateSavePackageDownloadItem( |
| const base::FilePath& main_file_path, |
| const base::FilePath& main_file_display_name, |
| const GURL& page_url, |
| const std::string& mime_type, |
| int render_process_id, |
| int render_frame_id, |
| download::DownloadJob::CancelRequestCallback cancel_request_callback, |
| DownloadItemImplCreated item_created) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| GetNextId(base::BindOnce( |
| &DownloadManagerImpl::CreateSavePackageDownloadItemWithId, |
| weak_factory_.GetWeakPtr(), main_file_path, main_file_display_name, |
| page_url, mime_type, render_process_id, render_frame_id, |
| std::move(cancel_request_callback), std::move(item_created))); |
| } |
| |
| void DownloadManagerImpl::CreateSavePackageDownloadItemWithId( |
| const base::FilePath& main_file_path, |
| const base::FilePath& main_file_display_name, |
| const GURL& page_url, |
| const std::string& mime_type, |
| int render_process_id, |
| int render_frame_id, |
| download::DownloadJob::CancelRequestCallback cancel_request_callback, |
| DownloadItemImplCreated item_created, |
| uint32_t id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK_NE(download::DownloadItem::kInvalidId, id); |
| DCHECK(!base::Contains(downloads_, id)); |
| |
| download::DownloadItemImpl* download_item = item_factory_->CreateSavePageItem( |
| this, id, main_file_path, main_file_display_name, page_url, mime_type, |
| std::move(cancel_request_callback)); |
| |
| GlobalRenderFrameHostId global_id(render_process_id, render_frame_id); |
| DownloadItemUtils::AttachInfo( |
| download_item, GetBrowserContext(), |
| WebContentsImpl::FromRenderFrameHostID(global_id), global_id); |
| if (delegate_) { |
| delegate_->AttachExtraInfo(download_item); |
| } |
| |
| OnDownloadCreated(base::WrapUnique(download_item)); |
| if (!item_created.is_null()) |
| std::move(item_created).Run(download_item); |
| } |
| |
| // Resume a download of a specific URL. We send the request to the |
| // ResourceDispatcherHost, and let it send us responses like a regular |
| // download. |
| void DownloadManagerImpl::ResumeInterruptedDownload( |
| std::unique_ptr<download::DownloadUrlParameters> params, |
| const std::string& serialized_embedder_download_data) { |
| BeginDownloadInternal(std::move(params), |
| nullptr /* blob_url_loader_factory */, false, |
| serialized_embedder_download_data); |
| } |
| |
| void DownloadManagerImpl::SetDownloadItemFactoryForTesting( |
| std::unique_ptr<download::DownloadItemFactory> item_factory) { |
| item_factory_ = std::move(item_factory); |
| } |
| |
| void DownloadManagerImpl::SetDownloadFileFactoryForTesting( |
| std::unique_ptr<download::DownloadFileFactory> file_factory) { |
| in_progress_manager_->set_file_factory(std::move(file_factory)); |
| } |
| |
| download::DownloadFileFactory* |
| DownloadManagerImpl::GetDownloadFileFactoryForTesting() { |
| return in_progress_manager_->file_factory(); |
| } |
| |
| void DownloadManagerImpl::DownloadRemoved( |
| download::DownloadItemImpl* download) { |
| if (!download) |
| return; |
| |
| downloads_by_guid_.erase(download->GetGuid()); |
| downloads_.erase(download->GetId()); |
| } |
| |
| void DownloadManagerImpl::DownloadInterrupted( |
| download::DownloadItemImpl* download) { |
| WebContents* web_contents = DownloadItemUtils::GetWebContents(download); |
| if (!web_contents) { |
| download::RecordDownloadCountWithSource( |
| download::INTERRUPTED_WITHOUT_WEBCONTENTS, |
| download->GetDownloadSource()); |
| } |
| } |
| |
| bool DownloadManagerImpl::IsOffTheRecord() const { |
| return browser_context_->IsOffTheRecord(); |
| } |
| |
| void DownloadManagerImpl::ReportBytesWasted( |
| download::DownloadItemImpl* download) { |
| in_progress_manager_->ReportBytesWasted(download); |
| } |
| |
| void DownloadManagerImpl::InterceptNavigation( |
| std::unique_ptr<network::ResourceRequest> resource_request, |
| std::vector<GURL> url_chain, |
| network::mojom::URLResponseHeadPtr response_head, |
| mojo::ScopedDataPipeConsumerHandle response_body, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| net::CertStatus cert_status, |
| FrameTreeNodeId frame_tree_node_id, |
| bool from_download_cross_origin_redirect) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!delegate_) { |
| DropDownload(); |
| return; |
| } |
| |
| const GURL& url = resource_request->url; |
| const std::string& method = resource_request->method; |
| std::optional<url::Origin> request_initiator = |
| resource_request->request_initiator; |
| std::string mime_type = response_head->mime_type; |
| ui::PageTransition transition_type = |
| static_cast<ui::PageTransition>(resource_request->transition_type); |
| |
| WebContents::Getter web_contents_getter = |
| base::BindRepeating(WebContents::FromFrameTreeNodeId, frame_tree_node_id); |
| |
| base::OnceCallback<void(bool /* download allowed */)> |
| on_download_checks_done = base::BindOnce( |
| &DownloadManagerImpl::InterceptNavigationOnChecksComplete, |
| weak_factory_.GetWeakPtr(), frame_tree_node_id, |
| std::move(resource_request), std::move(url_chain), cert_status, |
| std::move(response_head), std::move(response_body), |
| std::move(url_loader_client_endpoints)); |
| |
| delegate_->CheckDownloadAllowed( |
| std::move(web_contents_getter), url, method, std::move(request_initiator), |
| from_download_cross_origin_redirect, false /*content_initiated*/, |
| mime_type, transition_type, std::move(on_download_checks_done)); |
| } |
| |
| int DownloadManagerImpl::RemoveDownloadsByURLAndTime( |
| const base::RepeatingCallback<bool(const GURL&)>& url_filter, |
| base::Time remove_begin, |
| base::Time remove_end) { |
| int count = 0; |
| auto it = downloads_by_guid_.begin(); |
| while (it != downloads_by_guid_.end()) { |
| download::DownloadItemImpl* download = it->second; |
| |
| // Increment done here to protect against invalidation below. |
| ++it; |
| |
| if (download->GetState() != download::DownloadItem::IN_PROGRESS && |
| url_filter.Run(download->GetURL()) && |
| download->GetStartTime() >= remove_begin && |
| (remove_end.is_null() || download->GetStartTime() < remove_end)) { |
| download->Remove(); |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| bool DownloadManagerImpl::CanDownload( |
| download::DownloadUrlParameters* parameters) { |
| return true; |
| } |
| |
| void DownloadManagerImpl::DownloadUrl( |
| std::unique_ptr<download::DownloadUrlParameters> params) { |
| DownloadUrl(std::move(params), nullptr /* blob_url_loader_factory */); |
| } |
| |
| void DownloadManagerImpl::DownloadUrl( |
| std::unique_ptr<download::DownloadUrlParameters> params, |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) { |
| if (params->post_id() >= 0) { |
| // Check this here so that the traceback is more useful. |
| DCHECK(params->prefer_cache()); |
| DCHECK_EQ("POST", params->method()); |
| } |
| |
| if (delegate_) |
| delegate_->SanitizeDownloadParameters(params.get()); |
| |
| download::RecordDownloadCountWithSource( |
| download::DownloadCountTypes::DOWNLOAD_TRIGGERED_COUNT, |
| params->download_source()); |
| BeginDownloadInternal(std::move(params), std::move(blob_url_loader_factory), |
| /*is_new_download=*/true, |
| /*serialized_embedder_download_data=*/std::string()); |
| } |
| |
| void DownloadManagerImpl::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void DownloadManagerImpl::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| download::DownloadItem* DownloadManagerImpl::CreateDownloadItem( |
| const std::string& guid, |
| uint32_t id, |
| const base::FilePath& current_path, |
| const base::FilePath& target_path, |
| const std::vector<GURL>& url_chain, |
| const GURL& referrer_url, |
| const StoragePartitionConfig& storage_partition_config, |
| const GURL& tab_url, |
| const GURL& tab_refererr_url, |
| const std::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, |
| const std::string& hash, |
| download::DownloadItem::DownloadState state, |
| download::DownloadDangerType danger_type, |
| download::DownloadInterruptReason interrupt_reason, |
| bool opened, |
| base::Time last_access_time, |
| bool transient, |
| const std::vector<download::DownloadItem::ReceivedSlice>& received_slices) { |
| // Retrieve the in-progress download if it exists. Notice that this also |
| // removes it from |in_progress_downloads_|. |
| auto in_progress_download = RetrieveInProgressDownload(id); |
| |
| // Return null to clear cancelled or non-resumable download. |
| if (base::Contains(cleared_download_guids_on_startup_, guid)) { |
| return nullptr; |
| } |
| |
| if (url_chain.empty() || |
| ShouldClearDownloadFromDB(url_chain.back(), state, interrupt_reason, |
| start_time)) { |
| DeleteDownloadedFileOnUIThread(current_path); |
| return nullptr; |
| } |
| auto item = base::WrapUnique(item_factory_->CreatePersistedItem( |
| this, guid, id, current_path, target_path, url_chain, referrer_url, |
| StoragePartitionConfigToSerializedEmbedderDownloadData( |
| storage_partition_config), |
| tab_url, tab_refererr_url, request_initiator, mime_type, |
| original_mime_type, start_time, end_time, etag, last_modified, |
| received_bytes, total_bytes, hash, state, danger_type, interrupt_reason, |
| opened, last_access_time, transient, received_slices)); |
| if (in_progress_download) { |
| // If a download is in both history DB and in-progress DB, we should |
| // be able to remove the in-progress entry if the following 2 conditions |
| // are both met: |
| // 1. The download state in the history DB is a terminal state. |
| // 2. The download is not currently in progress. |
| // The situation could happen when browser crashes when download just |
| // reaches a terminal state. If the download is already in progress, we |
| // should wait for it to complete so that both DBs will be updated |
| // afterwards. |
| if (item->IsDone() && in_progress_download->GetState() != |
| download::DownloadItem::IN_PROGRESS) { |
| in_progress_manager_->RemoveInProgressDownload(guid); |
| } else { |
| // If one of the conditions are not met, use the in-progress download |
| // entry. |
| // TODO(qinmin): return nullptr so that the history DB will delete |
| // the download. |
| item = std::move(in_progress_download); |
| item->SetDelegate(this); |
| } |
| } |
| #if BUILDFLAG(IS_ANDROID) |
| if (target_path.IsContentUri()) { |
| base::FilePath android_display_name = |
| in_progress_manager_->GetDownloadDisplayName(target_path); |
| if (!android_display_name.empty()) |
| item->SetDisplayName(android_display_name); |
| else |
| return nullptr; |
| } |
| #endif |
| download::DownloadItemImpl* download = item.get(); |
| DownloadItemUtils::AttachInfo(download, GetBrowserContext(), nullptr, |
| GlobalRenderFrameHostId()); |
| if (delegate_) { |
| delegate_->AttachExtraInfo(download); |
| } |
| OnDownloadCreated(std::move(item)); |
| return download; |
| } |
| |
| void DownloadManagerImpl::OnDownloadCreated( |
| std::unique_ptr<download::DownloadItemImpl> download) { |
| DCHECK(!base::Contains(downloads_, download->GetId())); |
| DCHECK(!base::Contains(downloads_by_guid_, download->GetGuid())); |
| download::DownloadItemImpl* item = download.get(); |
| downloads_[item->GetId()] = std::move(download); |
| downloads_by_guid_[item->GetGuid()] = item; |
| for (auto& observer : observers_) |
| observer.OnDownloadCreated(this, item); |
| OnNewDownloadCreated(item); |
| DVLOG(20) << __func__ << "() download = " << item->DebugString(true); |
| } |
| |
| void DownloadManagerImpl::PostInitialization( |
| DownloadInitializationDependency dependency) { |
| // If initialization has occurred (ie. in tests), skip post init steps. |
| if (initialized_) |
| return; |
| |
| switch (dependency) { |
| case DOWNLOAD_INITIALIZATION_DEPENDENCY_HISTORY_DB: |
| history_db_initialized_ = true; |
| break; |
| case DOWNLOAD_INITIALIZATION_DEPENDENCY_IN_PROGRESS_CACHE: |
| in_progress_cache_initialized_ = true; |
| // Post a task to load downloads from history db. |
| if (load_history_downloads_cb_) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, std::move(load_history_downloads_cb_)); |
| } |
| break; |
| case DOWNLOAD_INITIALIZATION_DEPENDENCY_NONE: |
| default: |
| NOTREACHED(); |
| } |
| |
| // Download manager is only initialized if both history db and in progress |
| // cache are initialized. |
| bool history_loaded = history_db_initialized_ || IsOffTheRecord(); |
| if (!history_loaded || !in_progress_cache_initialized_) |
| return; |
| |
| for (const auto& guid : cleared_download_guids_on_startup_) { |
| in_progress_manager_->RemoveInProgressDownload(guid); |
| } |
| |
| if (in_progress_downloads_.empty()) { |
| OnDownloadManagerInitialized(); |
| } else { |
| GetNextId(base::BindOnce(&DownloadManagerImpl::ImportInProgressDownloads, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void DownloadManagerImpl::ImportInProgressDownloads(uint32_t id) { |
| auto download = in_progress_downloads_.begin(); |
| while (download != in_progress_downloads_.end()) { |
| auto item = std::move(*download); |
| // If the in-progress download doesn't have an ID, generate new IDs for it. |
| if (item->GetId() == download::DownloadItem::kInvalidId) { |
| item->SetDownloadId(id++); |
| next_download_id_++; |
| if (!should_persist_new_download_) |
| in_progress_manager_->RemoveInProgressDownload(item->GetGuid()); |
| } |
| item->SetDelegate(this); |
| DownloadItemUtils::AttachInfo(item.get(), GetBrowserContext(), nullptr, |
| GlobalRenderFrameHostId()); |
| if (delegate_) { |
| delegate_->AttachExtraInfo(item.get()); |
| } |
| download = in_progress_downloads_.erase(download); |
| OnDownloadCreated(std::move(item)); |
| } |
| OnDownloadManagerInitialized(); |
| } |
| |
| void DownloadManagerImpl::OnDownloadManagerInitialized() { |
| OnInitialized(); |
| in_progress_manager_->OnAllInprogressDownloadsLoaded(); |
| for (auto& observer : observers_) |
| observer.OnManagerInitialized(); |
| } |
| |
| bool DownloadManagerImpl::IsManagerInitialized() { |
| return initialized_; |
| } |
| |
| int DownloadManagerImpl::InProgressCount() { |
| int count = 0; |
| for (const auto& it : downloads_by_guid_) { |
| if (it.second->GetState() == download::DownloadItem::IN_PROGRESS) { |
| ++count; |
| } |
| } |
| return count; |
| } |
| |
| int DownloadManagerImpl::BlockingShutdownCount() { |
| int count = 0; |
| for (const auto& it : downloads_by_guid_) { |
| download::DownloadItemImpl* download = it.second; |
| if (download->IsTransient()) { |
| continue; |
| } |
| if (download->GetState() == download::DownloadItem::IN_PROGRESS && |
| !download->IsDangerous() && !download->IsInsecure()) { |
| ++count; |
| } |
| } |
| return count; |
| } |
| |
| download::DownloadItem* DownloadManagerImpl::GetDownload(uint32_t download_id) { |
| auto it = downloads_.find(download_id); |
| return it != downloads_.end() ? it->second.get() : nullptr; |
| } |
| |
| download::DownloadItem* DownloadManagerImpl::GetDownloadByGuid( |
| const std::string& guid) { |
| if (!in_progress_downloads_.empty()) { |
| for (const auto& it : in_progress_downloads_) { |
| if (it->GetGuid() == guid) |
| return it.get(); |
| } |
| } |
| auto it = downloads_by_guid_.find(guid); |
| return it != downloads_by_guid_.end() ? it->second : nullptr; |
| } |
| |
| void DownloadManagerImpl::GetAllDownloads( |
| download::SimpleDownloadManager::DownloadVector* downloads) { |
| for (const auto& it : downloads_by_guid_) { |
| downloads->push_back(it.second); |
| } |
| } |
| |
| void DownloadManagerImpl::GetUninitializedActiveDownloadsIfAny( |
| download::SimpleDownloadManager::DownloadVector* downloads) { |
| for (const auto& it : in_progress_downloads_) |
| downloads->push_back(it.get()); |
| } |
| |
| void DownloadManagerImpl::OpenDownload(download::DownloadItemImpl* download) { |
| if (delegate_) |
| delegate_->OpenDownload(download); |
| } |
| |
| void DownloadManagerImpl::ShowDownloadInShell( |
| download::DownloadItemImpl* download) { |
| if (delegate_) |
| delegate_->ShowDownloadInShell(download); |
| } |
| |
| void DownloadManagerImpl::DropDownload() { |
| download::RecordDownloadCount(download::DOWNLOAD_DROPPED_COUNT); |
| for (auto& observer : observers_) |
| observer.OnDownloadDropped(this); |
| } |
| |
| void DownloadManagerImpl::InterceptNavigationOnChecksComplete( |
| FrameTreeNodeId frame_tree_node_id, |
| std::unique_ptr<network::ResourceRequest> resource_request, |
| std::vector<GURL> url_chain, |
| net::CertStatus cert_status, |
| network::mojom::URLResponseHeadPtr response_head, |
| mojo::ScopedDataPipeConsumerHandle response_body, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| bool is_download_allowed) { |
| if (!is_download_allowed) { |
| DropDownload(); |
| return; |
| } |
| |
| int render_process_id = -1; |
| int render_frame_id = -1; |
| GURL site_url, tab_url, tab_referrer_url; |
| RenderFrameHost* render_frame_host = nullptr; |
| auto storage_partition_config = |
| StoragePartitionConfig::CreateDefault(browser_context_); |
| auto* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
| if (ftn) { |
| render_frame_host = ftn->current_frame_host(); |
| if (render_frame_host) { |
| render_process_id = render_frame_host->GetProcess()->GetDeprecatedID(); |
| render_frame_id = render_frame_host->GetRoutingID(); |
| storage_partition_config = |
| render_frame_host->GetSiteInstance()->GetStoragePartitionConfig(); |
| } |
| auto* web_contents = WebContentsImpl::FromFrameTreeNode(ftn); |
| DCHECK(web_contents); |
| NavigationEntry* entry = web_contents->GetController().GetVisibleEntry(); |
| if (entry) { |
| tab_url = entry->GetURL(); |
| tab_referrer_url = entry->GetReferrer().url; |
| } |
| RenderFrameHost* opener = web_contents->GetOpener(); |
| if (opener) { |
| RecordDownloadOpenerType(render_frame_host, opener); |
| } |
| } |
| |
| bool is_transient = false; |
| #if BUILDFLAG(IS_ANDROID) |
| if (!download::IsContentDispositionAttachmentInHead(*response_head)) { |
| is_transient = delegate_->ShouldOpenPdfInline() && |
| base::EqualsCaseInsensitiveASCII(response_head->mime_type, |
| kPdfMimeType); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| StoragePartitionImpl* storage_partition = |
| GetStoragePartitionForConfig(browser_context_, storage_partition_config); |
| in_progress_manager_->InterceptDownloadFromNavigation( |
| std::move(resource_request), render_process_id, render_frame_id, |
| StoragePartitionConfigToSerializedEmbedderDownloadData( |
| storage_partition_config), |
| 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), |
| CreatePendingSharedURLLoaderFactory(storage_partition, render_frame_host), |
| is_transient); |
| } |
| |
| void DownloadManagerImpl::BeginResourceDownloadOnChecksComplete( |
| std::unique_ptr<download::DownloadUrlParameters> params, |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, |
| bool is_new_download, |
| const StoragePartitionConfig& storage_partition_config, |
| bool is_download_allowed) { |
| if (!is_download_allowed) { |
| DropDownload(); |
| return; |
| } |
| |
| GURL tab_url, tab_referrer_url; |
| auto* rfh = RenderFrameHost::FromID(params->render_process_host_id(), |
| params->render_frame_host_routing_id()); |
| if (rfh) { |
| auto* web_contents = WebContents::FromRenderFrameHost(rfh); |
| NavigationEntry* entry = web_contents->GetController().GetVisibleEntry(); |
| if (entry) { |
| tab_url = entry->GetURL(); |
| tab_referrer_url = entry->GetReferrer().url; |
| } |
| } |
| |
| DCHECK_EQ(params->url().SchemeIsBlob(), bool{blob_url_loader_factory}); |
| std::unique_ptr<network::PendingSharedURLLoaderFactory> |
| pending_url_loader_factory; |
| if (blob_url_loader_factory) { |
| DCHECK(params->url().SchemeIsBlob()); |
| pending_url_loader_factory = blob_url_loader_factory->Clone(); |
| } else if (params->url().SchemeIsFile()) { |
| pending_url_loader_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| FileURLLoaderFactory::Create( |
| browser_context_->GetPath(), |
| browser_context_->GetSharedCorsOriginAccessList(), |
| // USER_VISIBLE because download should progress |
| // even when there is high priority work to do. |
| base::TaskPriority::USER_VISIBLE)); |
| } else if (rfh && params->url().SchemeIs(content::kChromeUIScheme)) { |
| pending_url_loader_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| CreateWebUIURLLoaderFactory(rfh, params->url().scheme(), |
| base::flat_set<std::string>())); |
| } else if (rfh && params->url().SchemeIsFileSystem()) { |
| StoragePartitionImpl* storage_partition = GetStoragePartitionForConfig( |
| browser_context_, storage_partition_config); |
| pending_url_loader_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| CreateFileSystemURLLoaderFactory( |
| rfh->GetProcess()->GetDeprecatedID(), rfh->GetFrameTreeNodeId(), |
| storage_partition->GetFileSystemContext(), |
| storage_partition->GetPartitionDomain(), |
| static_cast<RenderFrameHostImpl*>(rfh)->GetStorageKey())); |
| } else if (params->url().SchemeIs(url::kDataScheme)) { |
| pending_url_loader_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| DataURLLoaderFactory::CreateForOneSpecificUrl(params->url())); |
| } else if (rfh && !network::IsURLHandledByNetworkService(params->url())) { |
| ContentBrowserClient::NonNetworkURLLoaderFactoryMap |
| non_network_url_loader_factories; |
| GetContentClient() |
| ->browser() |
| ->RegisterNonNetworkSubresourceURLLoaderFactories( |
| params->render_process_host_id(), |
| params->render_frame_host_routing_id(), params->initiator(), |
| &non_network_url_loader_factories); |
| auto it = non_network_url_loader_factories.find(params->url().scheme()); |
| if (it != non_network_url_loader_factories.end()) { |
| pending_url_loader_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| std::move(it->second)); |
| } else { |
| DLOG(ERROR) << "No URLLoaderFactory found to download " << params->url(); |
| return; |
| } |
| } else { |
| StoragePartitionImpl* storage_partition = GetStoragePartitionForConfig( |
| browser_context_, storage_partition_config); |
| pending_url_loader_factory = |
| CreatePendingSharedURLLoaderFactory(storage_partition, rfh); |
| } |
| |
| in_progress_manager_->BeginDownload( |
| std::move(params), std::move(pending_url_loader_factory), is_new_download, |
| StoragePartitionConfigToSerializedEmbedderDownloadData( |
| storage_partition_config), |
| tab_url, tab_referrer_url); |
| } |
| |
| void DownloadManagerImpl::BeginDownloadInternal( |
| std::unique_ptr<download::DownloadUrlParameters> params, |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, |
| bool is_new_download, |
| const std::string& serialized_embedder_download_data) { |
| // Check if the renderer is permitted to request the requested URL. |
| if (params->render_process_host_id() >= 0 && |
| !DownloadRequestUtils::IsURLSafe(params->render_process_host_id(), |
| params->url())) { |
| CreateInterruptedDownload( |
| std::move(params), |
| download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, |
| weak_factory_.GetWeakPtr()); |
| return; |
| } |
| |
| auto* rfh = RenderFrameHost::FromID(params->render_process_host_id(), |
| params->render_frame_host_routing_id()); |
| |
| StoragePartitionConfig storage_partition_config; |
| if (rfh && serialized_embedder_download_data.empty()) { |
| storage_partition_config = |
| rfh->GetSiteInstance()->GetStoragePartitionConfig(); |
| } else { |
| storage_partition_config = |
| SerializedEmbedderDownloadDataToStoragePartitionConfig( |
| serialized_embedder_download_data); |
| } |
| |
| // Ideally everywhere a blob: URL is downloaded a URLLoaderFactory for that |
| // blob URL is also passed, but since that isn't always the case, create |
| // a new factory if we don't have one already. |
| if (!blob_url_loader_factory && params->url().SchemeIsBlob()) { |
| blob_url_loader_factory = ChromeBlobStorageContext::URLLoaderFactoryForUrl( |
| GetStoragePartitionForConfig(browser_context_, |
| storage_partition_config), |
| params->url()); |
| } |
| |
| bool content_initiated = params->content_initiated(); |
| if (rfh && content_initiated) { |
| // Cancel downloads from non-active documents (e.g prerendered, bfcached) or |
| // fenced frames. |
| if (rfh->IsInactiveAndDisallowActivation( |
| DisallowActivationReasonId::kBeginDownload) || |
| rfh->IsNestedWithinFencedFrame()) { |
| DropDownload(); |
| return; |
| } |
| |
| // Push the throttle on the web-initiated downloads before allowing them to |
| // proceed. |
| if (delegate_) { |
| WebContents::Getter web_contents_getter = base::BindRepeating( |
| WebContents::FromFrameTreeNodeId, rfh->GetFrameTreeNodeId()); |
| const GURL& url = params->url(); |
| const std::string& method = params->method(); |
| std::optional<url::Origin> initiator = params->initiator(); |
| base::OnceCallback<void(bool /* download allowed */)> |
| on_can_download_checks_done = base::BindOnce( |
| &DownloadManagerImpl::BeginResourceDownloadOnChecksComplete, |
| weak_factory_.GetWeakPtr(), std::move(params), |
| std::move(blob_url_loader_factory), is_new_download, |
| storage_partition_config); |
| delegate_->CheckDownloadAllowed( |
| std::move(web_contents_getter), url, method, std::move(initiator), |
| false /* from_download_cross_origin_redirect */, content_initiated, |
| /* mime_type= */ std::string(), |
| /* page_transition= */ std::nullopt, |
| std::move(on_can_download_checks_done)); |
| return; |
| } |
| } |
| |
| BeginResourceDownloadOnChecksComplete( |
| std::move(params), std::move(blob_url_loader_factory), is_new_download, |
| storage_partition_config, rfh ? !content_initiated : true); |
| } |
| |
| bool DownloadManagerImpl::IsNextIdInitialized() const { |
| return is_history_download_id_retrieved_ && in_progress_cache_initialized_; |
| } |
| |
| bool DownloadManagerImpl::ShouldClearDownloadFromDB( |
| const GURL& url, |
| download::DownloadItem::DownloadState state, |
| download::DownloadInterruptReason reason, |
| const base::Time& start_time) { |
| if (!base::FeatureList::IsEnabled( |
| download::features::kDeleteExpiredDownloads)) { |
| return false; |
| } |
| |
| // Use system time to determine if the download is expired. Manually setting |
| // the system time can affect this. |
| bool expired = base::Time::Now() - start_time >= |
| download::GetExpiredDownloadDeleteTime(); |
| if (state == download::DownloadItem::CANCELLED && expired) |
| return true; |
| |
| if (reason != download::DOWNLOAD_INTERRUPT_REASON_NONE && |
| state == download::DownloadItem::INTERRUPTED && expired) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| std::unique_ptr<download::DownloadItemImpl> |
| DownloadManagerImpl::RetrieveInProgressDownload(uint32_t id) { |
| // In case the history DB has some invalid IDs, skip them. |
| if (id == download::DownloadItem::kInvalidId) |
| return nullptr; |
| |
| for (auto it = in_progress_downloads_.begin(); |
| it != in_progress_downloads_.end(); ++it) { |
| if ((*it)->GetId() == id) { |
| auto download = std::move(*it); |
| in_progress_downloads_.erase(it); |
| return download; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace content |