| // Copyright 2020 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 "content/browser/web_contents/file_chooser_impl.h" |
| |
| #include "base/memory/ptr_util.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 { |
| |
| FileChooserImpl::FileSelectListenerImpl::~FileSelectListenerImpl() { |
| #if DCHECK_IS_ON() |
| DCHECK(was_file_select_listener_function_called_) |
| << "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_) |
| owner_->FileSelected(std::move(files), 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_(render_frame_host) { |
| Observe(WebContents::FromRenderFrameHost(render_frame_host)); |
| } |
| |
| FileChooserImpl::~FileChooserImpl() { |
| if (listener_impl_) |
| listener_impl_->ResetOwner(); |
| } |
| |
| 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; |
| } |
| |
| // 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)); |
| |
| static_cast<WebContentsImpl*>(web_contents()) |
| ->RunFileChooser(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()->GetID(), |
| directory_path)) { |
| static_cast<WebContentsImpl*>(web_contents()) |
| ->EnumerateDirectory(render_frame_host_, std::move(listener), |
| directory_path); |
| } else { |
| listener->FileSelectionCanceled(); |
| } |
| } |
| |
| void FileChooserImpl::FileSelected( |
| std::vector<blink::mojom::FileChooserFileInfoPtr> files, |
| const base::FilePath& base_dir, |
| blink::mojom::FileChooserParams::Mode mode) { |
| 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()->GetID(); |
| 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); |
| } else { |
| if (file->is_file_system()) { |
| if (!file_system_context) { |
| file_system_context = |
| render_frame_host_->GetStoragePartition()->GetFileSystemContext(); |
| } |
| policy->GrantReadFileSystem( |
| pid, file_system_context->CrackURL(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); |
| } |
| |
| void FileChooserImpl::RenderFrameHostChanged(RenderFrameHost* old_host, |
| RenderFrameHost* new_host) { |
| if (old_host == render_frame_host_) |
| render_frame_host_ = nullptr; |
| } |
| |
| void FileChooserImpl::RenderFrameDeleted(RenderFrameHost* render_frame_host) { |
| if (render_frame_host == render_frame_host_) |
| render_frame_host_ = nullptr; |
| } |
| |
| void FileChooserImpl::WebContentsDestroyed() { |
| render_frame_host_ = nullptr; |
| } |
| |
| } // namespace content |