// Copyright 2019 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 "weblayer/browser/download_manager_delegate_impl.h" #include "base/files/file_util.h" #include "base/task/thread_pool.h" #include "base/threading/sequenced_task_runner_handle.h" #include "build/build_config.h" #include "components/download/public/common/download_item.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item_utils.h" #include "content/public/browser/download_manager.h" #include "net/base/filename_util.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_process.h" #include "weblayer/browser/download_manager_delegate_impl.h" #include "weblayer/browser/persistent_download.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/public/download_delegate.h" namespace weblayer { namespace { void GenerateFilename( const GURL& url, const std::string& content_disposition, const std::string& suggested_filename, const std::string& mime_type, const base::FilePath& suggested_directory, base::OnceCallback callback) { base::FilePath generated_name = net::GenerateFileName(url, content_disposition, std::string(), suggested_filename, mime_type, "download"); if (!base::PathExists(suggested_directory)) base::CreateDirectory(suggested_directory); base::FilePath suggested_path(suggested_directory.Append(generated_name)); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), suggested_path)); } } // namespace const char kDownloadNextIDPref[] = "weblayer_download_next_id"; DownloadManagerDelegateImpl::DownloadManagerDelegateImpl( content::DownloadManager* download_manager) : download_manager_(download_manager) { download_manager_->AddObserver(this); // WebLayer doesn't use a history DB as the in-progress database maintained by // the download component is enough. However the download code still depends // this notification. TODO(jam): update download code to handle this. download_manager_->PostInitialization( content::DownloadManager::DOWNLOAD_INITIALIZATION_DEPENDENCY_HISTORY_DB); } DownloadManagerDelegateImpl::~DownloadManagerDelegateImpl() { download_manager_->RemoveObserver(this); // Match the AddObserver calls added in OnDownloadCreated to avoid UaF. download::SimpleDownloadManager::DownloadVector downloads; download_manager_->GetAllDownloads(&downloads); for (auto* download : downloads) download->RemoveObserver(this); } void DownloadManagerDelegateImpl::GetNextId( content::DownloadIdCallback callback) { // Need to return a unique id, even across crashes, to avoid notification // intents with different data (e.g. notification GUID) getting dup'd. This is // also persisted in the on-disk download database to support resumption. auto* local_state = BrowserProcess::GetInstance()->GetLocalState(); std::move(callback).Run(local_state->GetInteger(kDownloadNextIDPref)); } bool DownloadManagerDelegateImpl::DetermineDownloadTarget( download::DownloadItem* item, content::DownloadTargetCallback* callback) { if (!item->GetForcedFilePath().empty()) { std::move(*callback).Run( item->GetForcedFilePath(), download::DownloadItem::TARGET_DISPOSITION_OVERWRITE, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, download::DownloadItem::MixedContentStatus::UNKNOWN, item->GetForcedFilePath(), base::FilePath(), std::string() /*mime_type*/, download::DOWNLOAD_INTERRUPT_REASON_NONE); return true; } auto filename_determined_callback = base::BindOnce( &DownloadManagerDelegateImpl::OnDownloadPathGenerated, weak_ptr_factory_.GetWeakPtr(), item->GetId(), std::move(*callback)); auto* browser_context = content::DownloadItemUtils::GetBrowserContext(item); base::FilePath default_download_path; GetSaveDir(browser_context, nullptr, &default_download_path); base::ThreadPool::PostTask( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::TaskPriority::USER_VISIBLE}, base::BindOnce( GenerateFilename, item->GetURL(), item->GetContentDisposition(), item->GetSuggestedFilename(), item->GetMimeType(), default_download_path, std::move(filename_determined_callback))); return true; } bool DownloadManagerDelegateImpl::InterceptDownloadIfApplicable( const GURL& url, const std::string& user_agent, const std::string& content_disposition, const std::string& mime_type, const std::string& request_origin, int64_t content_length, bool is_transient, content::WebContents* web_contents) { // Don't intercept transient downloads (such as Background Fetches). if (is_transient) return false; // If there's no DownloadDelegate, the download is simply dropped. auto* delegate = GetDelegate(web_contents); if (!delegate) return true; return delegate->InterceptDownload(url, user_agent, content_disposition, mime_type, content_length); } void DownloadManagerDelegateImpl::GetSaveDir( content::BrowserContext* browser_context, base::FilePath* website_save_dir, base::FilePath* download_save_dir) { auto* browser_context_impl = static_cast(browser_context); auto* profile = browser_context_impl->profile_impl(); if (!profile->download_directory().empty()) *download_save_dir = profile->download_directory(); } void DownloadManagerDelegateImpl::CheckDownloadAllowed( const content::WebContents::Getter& web_contents_getter, const GURL& url, const std::string& request_method, absl::optional request_initiator, bool from_download_cross_origin_redirect, bool content_initiated, content::CheckDownloadAllowedCallback check_download_allowed_cb) { auto* web_contents = web_contents_getter.Run(); // If there's no DownloadDelegate, the download is simply dropped. auto* delegate = GetDelegate(web_contents); auto* tab = TabImpl::FromWebContents(web_contents); if (!delegate || !tab) { std::move(check_download_allowed_cb).Run(false); return; } delegate->AllowDownload(tab, url, request_method, request_initiator, std::move(check_download_allowed_cb)); } void DownloadManagerDelegateImpl::OnDownloadCreated( content::DownloadManager* manager, download::DownloadItem* item) { auto* local_state = BrowserProcess::GetInstance()->GetLocalState(); int next_id = local_state->GetInteger(kDownloadNextIDPref); if (item->GetId() >= static_cast(next_id)) { next_id = item->GetId(); // Reset the counter when it gets close to max value of unsigned 32 bit // integer since that's what the download system persists. if (++next_id == (std::numeric_limits::max() / 2) - 1) next_id = 0; local_state->SetInteger(kDownloadNextIDPref, next_id); } if (item->IsTransient()) return; // As per the documentation in DownloadItem, transient items should not // be shown in the UI. (Note that they may be surface by other means, // such as through the BackgroundFetch system.) item->AddObserver(this); // Create a PersistentDownload which will be owned by |item|. PersistentDownload::Create(item); if (item->GetLastReason() == download::DOWNLOAD_INTERRUPT_REASON_CRASH && item->CanResume() && // Don't automatically resume downloads which were previously paused. !item->IsPaused()) { PersistentDownload::Get(item)->Resume(); } auto* delegate = GetDelegate(item); if (delegate) delegate->DownloadStarted(PersistentDownload::Get(item)); } void DownloadManagerDelegateImpl::OnDownloadDropped( content::DownloadManager* manager) { if (download_dropped_callback_) download_dropped_callback_.Run(); } void DownloadManagerDelegateImpl::OnManagerInitialized() { auto* browser_context_impl = static_cast(download_manager_->GetBrowserContext()); auto* profile = browser_context_impl->profile_impl(); profile->DownloadsInitialized(); } void DownloadManagerDelegateImpl::OnDownloadUpdated( download::DownloadItem* item) { // If this is the first navigation in a tab it should be closed. Wait until // the target path is determined or the download is canceled to check. if (!item->GetTargetFilePath().empty() || item->GetState() == download::DownloadItem::CANCELLED) { content::WebContents* web_contents = content::DownloadItemUtils::GetWebContents(item); if (web_contents && web_contents->GetController().IsInitialNavigation()) web_contents->Close(); } auto* delegate = GetDelegate(item); if (item->GetState() == download::DownloadItem::COMPLETE || item->GetState() == download::DownloadItem::CANCELLED || item->GetState() == download::DownloadItem::INTERRUPTED) { // Stop observing now to ensure we only send one complete/fail notification. item->RemoveObserver(this); if (item->GetState() == download::DownloadItem::COMPLETE) delegate->DownloadCompleted(PersistentDownload::Get(item)); else delegate->DownloadFailed(PersistentDownload::Get(item)); // Needs to happen asynchronously to avoid nested observer calls. base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&DownloadManagerDelegateImpl::RemoveItem, weak_ptr_factory_.GetWeakPtr(), item->GetGuid())); return; } if (delegate) delegate->DownloadProgressChanged(PersistentDownload::Get(item)); } void DownloadManagerDelegateImpl::OnDownloadPathGenerated( uint32_t download_id, content::DownloadTargetCallback callback, const base::FilePath& suggested_path) { std::move(callback).Run( suggested_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, download::DownloadItem::MixedContentStatus::UNKNOWN, suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")), base::FilePath(), std::string() /*mime_type*/, download::DOWNLOAD_INTERRUPT_REASON_NONE); } void DownloadManagerDelegateImpl::RemoveItem(const std::string& guid) { auto* item = download_manager_->GetDownloadByGuid(guid); if (item) item->Remove(); } DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate( content::WebContents* web_contents) { if (!web_contents) return nullptr; return GetDelegate(web_contents->GetBrowserContext()); } DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate( content::BrowserContext* browser_context) { auto* profile = ProfileImpl::FromBrowserContext(browser_context); if (!profile) return nullptr; return profile->download_delegate(); } DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate( download::DownloadItem* item) { auto* browser_context = content::DownloadItemUtils::GetBrowserContext(item); return GetDelegate(browser_context); } } // namespace weblayer