blob: 1f889a95e925f73449e8748f6caf1fed26e64e3b [file] [log] [blame]
// 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/optional.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 "weblayer/browser/browser_context_impl.h"
#include "weblayer/browser/browser_process.h"
#include "weblayer/browser/download_impl.h"
#include "weblayer/browser/download_manager_delegate_impl.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<void(const base::FilePath&)> 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::nullopt /*download_schedule*/,
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) {
// 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<BrowserContextImpl*>(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,
base::Optional<url::Origin> 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) {
item->AddObserver(this);
// Create a DownloadImpl which will be owned by |item|.
DownloadImpl::Create(item);
auto* local_state = BrowserProcess::GetInstance()->GetLocalState();
int next_id = local_state->GetInteger(kDownloadNextIDPref);
if (item->GetId() >= static_cast<uint32_t>(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<uint32_t>::max() / 2) - 1)
next_id = 0;
local_state->SetInteger(kDownloadNextIDPref, next_id);
}
if (item->GetLastReason() == download::DOWNLOAD_INTERRUPT_REASON_CRASH &&
item->CanResume() &&
// Don't automatically resume downloads which were previously paused.
!item->IsPaused()) {
DownloadImpl::Get(item)->Resume();
}
auto* delegate = GetDelegate(item);
if (delegate)
delegate->DownloadStarted(DownloadImpl::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<BrowserContextImpl*>(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(DownloadImpl::Get(item));
else
delegate->DownloadFailed(DownloadImpl::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(DownloadImpl::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::nullopt /*download_schedule*/,
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