blob: 7104addc38f724aaa7c5591844af1aa1ad21117a [file] [log] [blame]
// Copyright 2020 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/web_contents/file_chooser_impl.h"
#include <algorithm>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/task/thread_pool.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
namespace content {
namespace {
// Removes any file that is a symlink or is inside a directory symlink.
// For |kUploadFolder| mode only. |base_dir| is the folder being uploaded.
std::vector<blink::mojom::FileChooserFileInfoPtr> RemoveSymlinks(
std::vector<blink::mojom::FileChooserFileInfoPtr> files,
base::FilePath base_dir) {
DCHECK(!base_dir.empty());
auto to_remove = std::ranges::remove_if(
files,
[&base_dir](const base::FilePath& file_path) {
if (base::IsLink(file_path))
return true;
for (base::FilePath path = file_path.DirName(); base_dir.IsParent(path);
path = path.DirName()) {
if (base::IsLink(path))
return true;
}
return false;
},
[](const auto& file) { return file->get_native_file()->file_path; });
files.erase(to_remove.begin(), to_remove.end());
return files;
}
} // namespace
FileChooserImpl::FileSelectListenerImpl::FileSelectListenerImpl(
FileChooserImpl* owner)
: owner_(owner ? owner->GetWeakPtr() : nullptr) {}
FileChooserImpl::FileSelectListenerImpl::~FileSelectListenerImpl() {
#if DCHECK_IS_ON()
if (!was_file_select_listener_function_called_) {
LOG(ERROR) << "Must call either FileSelectListener::FileSelected() or "
"FileSelectListener::FileSelectionCanceled()";
}
// TODO(avi): Turn on the DCHECK on the following line. This cannot yet be
// done because I can't say for sure that I know who all the callers who bind
// blink::mojom::FileChooser are. https://crbug.com/1054811
/* DCHECK(was_fullscreen_block_set_) << "The fullscreen block was not set"; */
#endif
}
void FileChooserImpl::FileSelectListenerImpl::SetFullscreenBlock(
base::ScopedClosureRunner fullscreen_block) {
#if DCHECK_IS_ON()
DCHECK(!was_fullscreen_block_set_)
<< "Fullscreen block must only be set once";
was_fullscreen_block_set_ = true;
#endif
fullscreen_block_ = std::move(fullscreen_block);
}
void FileChooserImpl::FileSelectListenerImpl::FileSelected(
std::vector<blink::mojom::FileChooserFileInfoPtr> files,
const base::FilePath& base_dir,
blink::mojom::FileChooserParams::Mode mode) {
#if DCHECK_IS_ON()
DCHECK(!was_file_select_listener_function_called_)
<< "Must not call both of FileSelectListener::FileSelected() and "
"FileSelectListener::FileSelectionCanceled()";
was_file_select_listener_function_called_ = true;
#endif
if (!owner_)
return;
if (mode != blink::mojom::FileChooserParams::Mode::kUploadFolder) {
owner_->FileSelected(base_dir, mode, std::move(files));
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&RemoveSymlinks, std::move(files), base_dir),
base::BindOnce(&FileChooserImpl::FileSelected, owner_, base_dir, mode));
}
void FileChooserImpl::FileSelectListenerImpl::FileSelectionCanceled() {
#if DCHECK_IS_ON()
DCHECK(!was_file_select_listener_function_called_)
<< "Should not call both of FileSelectListener::FileSelected() and "
"FileSelectListener::FileSelectionCanceled()";
was_file_select_listener_function_called_ = true;
#endif
if (owner_)
owner_->FileSelectionCanceled();
}
void FileChooserImpl::FileSelectListenerImpl::
SetListenerFunctionCalledTrueForTesting() {
#if DCHECK_IS_ON()
was_file_select_listener_function_called_ = true;
#endif
}
// static
void FileChooserImpl::Create(
RenderFrameHostImpl* render_frame_host,
mojo::PendingReceiver<blink::mojom::FileChooser> receiver) {
mojo::MakeSelfOwnedReceiver(
base::WrapUnique(new FileChooserImpl(render_frame_host)),
std::move(receiver));
}
// static
mojo::Remote<blink::mojom::FileChooser> FileChooserImpl::CreateBoundForTesting(
RenderFrameHostImpl* render_frame_host) {
mojo::Remote<blink::mojom::FileChooser> chooser;
Create(render_frame_host, chooser.BindNewPipeAndPassReceiver());
return chooser;
}
// static
std::pair<FileChooserImpl*, mojo::Remote<blink::mojom::FileChooser>>
FileChooserImpl::CreateForTesting(RenderFrameHostImpl* render_frame_host) {
mojo::Remote<blink::mojom::FileChooser> chooser;
FileChooserImpl* impl = new FileChooserImpl(render_frame_host);
mojo::MakeSelfOwnedReceiver(base::WrapUnique(impl),
chooser.BindNewPipeAndPassReceiver());
return std::make_pair(impl, std::move(chooser));
}
FileChooserImpl::FileChooserImpl(RenderFrameHostImpl* render_frame_host)
: render_frame_host_id_(render_frame_host->GetGlobalId()) {}
FileChooserImpl::~FileChooserImpl() = default;
void FileChooserImpl::OpenFileChooser(blink::mojom::FileChooserParamsPtr params,
OpenFileChooserCallback callback) {
if (listener_impl_ || !render_frame_host()) {
std::move(callback).Run(nullptr);
return;
}
callback_ = std::move(callback);
auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this);
listener_impl_ = listener.get();
// Do not allow messages with absolute paths in them as this can permit a
// renderer to coerce the browser to perform I/O on a renderer controlled
// path.
if (params->default_file_name != params->default_file_name.BaseName()) {
mojo::ReportBadMessage(
"FileChooser: The default file name must not be an absolute path.");
listener->FileSelectionCanceled();
return;
}
// Do not allow open dialogs to have renderer-controlled default_file_name.
// See https://crbug.com/433800617 for context.
if (params->mode != blink::mojom::FileChooserParams::Mode::kSave) {
params->default_file_name = base::FilePath();
}
// Don't allow page with open FileChooser to enter BackForwardCache to avoid
// any unexpected behaviour from BackForwardCache.
BackForwardCache::DisableForRenderFrameHost(
render_frame_host(),
BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kFileChooser));
WebContentsImpl::FromRenderFrameHostImpl(render_frame_host())
->RunFileChooser(GetWeakPtr(), render_frame_host(), std::move(listener),
*params);
}
void FileChooserImpl::EnumerateChosenDirectory(
const base::FilePath& directory_path,
EnumerateChosenDirectoryCallback callback) {
if (listener_impl_ || !render_frame_host()) {
std::move(callback).Run(nullptr);
return;
}
callback_ = std::move(callback);
auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this);
listener_impl_ = listener.get();
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (policy->CanReadFile(render_frame_host()->GetProcess()->GetDeprecatedID(),
directory_path)) {
WebContentsImpl::FromRenderFrameHostImpl(render_frame_host())
->EnumerateDirectory(GetWeakPtr(), render_frame_host(),
std::move(listener), directory_path);
} else {
listener->FileSelectionCanceled();
}
}
void FileChooserImpl::FileSelected(
const base::FilePath& base_dir,
blink::mojom::FileChooserParams::Mode mode,
std::vector<blink::mojom::FileChooserFileInfoPtr> files) {
listener_impl_ = nullptr;
if (!render_frame_host()) {
std::move(callback_).Run(nullptr);
return;
}
storage::FileSystemContext* file_system_context = nullptr;
const int pid = render_frame_host()->GetProcess()->GetDeprecatedID();
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
// Grant the security access requested to the given files.
for (const auto& file : files) {
if (mode == blink::mojom::FileChooserParams::Mode::kSave) {
policy->GrantCreateReadWriteFile(pid, file->get_native_file()->file_path);
continue;
}
if (file->is_file_system()) {
if (!file_system_context) {
file_system_context =
render_frame_host()->GetStoragePartition()->GetFileSystemContext();
}
policy->GrantReadFileSystem(
pid, file_system_context
->CrackURLInFirstPartyContext(file->get_file_system()->url)
.mount_filesystem_id());
} else {
policy->GrantReadFile(pid, file->get_native_file()->file_path);
}
}
std::move(callback_).Run(FileChooserResult::New(std::move(files), base_dir));
}
void FileChooserImpl::FileSelectionCanceled() {
listener_impl_ = nullptr;
std::move(callback_).Run(nullptr);
}
RenderFrameHostImpl* FileChooserImpl::render_frame_host() {
RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(render_frame_host_id_);
return (rfh && rfh->IsRenderFrameLive()) ? rfh : nullptr;
}
} // namespace content