blob: 7104addc38f724aaa7c5591844af1aa1ad21117a [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2020 The Chromium Authors
Avi Drissman1a55a9d62020-03-10 18:56:452// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Avi Drissmanbd3e986442020-05-20 21:09:205#include "content/browser/web_contents/file_chooser_impl.h"
Avi Drissman1a55a9d62020-03-10 18:56:456
Peter Kasting1557e5f2025-01-28 01:14:087#include <algorithm>
8
Xiaocheng Hu933cc812022-09-10 05:53:499#include "base/files/file_util.h"
avvall90b2f242022-02-17 21:25:2310#include "base/logging.h"
Avi Drissmanbd3e986442020-05-20 21:09:2011#include "base/memory/ptr_util.h"
Xiaocheng Hu933cc812022-09-10 05:53:4912#include "base/task/thread_pool.h"
Avi Drissman1a55a9d62020-03-10 18:56:4513#include "content/browser/child_process_security_policy_impl.h"
Fergal Daly23b8ae62021-03-30 05:43:4614#include "content/browser/renderer_host/back_forward_cache_disable.h"
danakje34636e2020-09-15 22:15:0015#include "content/browser/renderer_host/render_frame_host_delegate.h"
16#include "content/browser/renderer_host/render_frame_host_impl.h"
Avi Drissmanbd3e986442020-05-20 21:09:2017#include "content/browser/web_contents/web_contents_impl.h"
Avi Drissman1a55a9d62020-03-10 18:56:4518#include "content/public/browser/browser_context.h"
19#include "content/public/browser/storage_partition.h"
Patrick Monette8dd34d22020-05-14 19:33:3920#include "content/public/browser/web_contents.h"
Avi Drissman1a55a9d62020-03-10 18:56:4521#include "mojo/public/cpp/bindings/self_owned_receiver.h"
22
23namespace content {
24
Xiaocheng Hu933cc812022-09-10 05:53:4925namespace {
26
Xiaocheng Hu4fa830d2022-11-03 23:40:3627// Removes any file that is a symlink or is inside a directory symlink.
28// For |kUploadFolder| mode only. |base_dir| is the folder being uploaded.
Xiaocheng Hu933cc812022-09-10 05:53:4929std::vector<blink::mojom::FileChooserFileInfoPtr> RemoveSymlinks(
Xiaocheng Hu4fa830d2022-11-03 23:40:3630 std::vector<blink::mojom::FileChooserFileInfoPtr> files,
31 base::FilePath base_dir) {
32 DCHECK(!base_dir.empty());
Peter Kastingb0e84962025-01-13 18:46:0333 auto to_remove = std::ranges::remove_if(
Xiaocheng Hu4fa830d2022-11-03 23:40:3634 files,
35 [&base_dir](const base::FilePath& file_path) {
36 if (base::IsLink(file_path))
37 return true;
38 for (base::FilePath path = file_path.DirName(); base_dir.IsParent(path);
39 path = path.DirName()) {
40 if (base::IsLink(path))
41 return true;
42 }
43 return false;
44 },
Xiaocheng Hu933cc812022-09-10 05:53:4945 [](const auto& file) { return file->get_native_file()->file_path; });
Peter Kastingb0e84962025-01-13 18:46:0346 files.erase(to_remove.begin(), to_remove.end());
Xiaocheng Hu933cc812022-09-10 05:53:4947 return files;
48}
49
50} // namespace
51
Kevin McNee321dfe22023-12-04 20:23:2652FileChooserImpl::FileSelectListenerImpl::FileSelectListenerImpl(
53 FileChooserImpl* owner)
54 : owner_(owner ? owner->GetWeakPtr() : nullptr) {}
55
Avi Drissman1a55a9d62020-03-10 18:56:4556FileChooserImpl::FileSelectListenerImpl::~FileSelectListenerImpl() {
57#if DCHECK_IS_ON()
avvall90b2f242022-02-17 21:25:2358 if (!was_file_select_listener_function_called_) {
59 LOG(ERROR) << "Must call either FileSelectListener::FileSelected() or "
60 "FileSelectListener::FileSelectionCanceled()";
61 }
Avi Drissman1a55a9d62020-03-10 18:56:4562 // TODO(avi): Turn on the DCHECK on the following line. This cannot yet be
63 // done because I can't say for sure that I know who all the callers who bind
64 // blink::mojom::FileChooser are. https://crbug.com/1054811
65 /* DCHECK(was_fullscreen_block_set_) << "The fullscreen block was not set"; */
66#endif
Avi Drissman1a55a9d62020-03-10 18:56:4567}
68
69void FileChooserImpl::FileSelectListenerImpl::SetFullscreenBlock(
70 base::ScopedClosureRunner fullscreen_block) {
71#if DCHECK_IS_ON()
72 DCHECK(!was_fullscreen_block_set_)
73 << "Fullscreen block must only be set once";
74 was_fullscreen_block_set_ = true;
75#endif
76 fullscreen_block_ = std::move(fullscreen_block);
77}
78
79void FileChooserImpl::FileSelectListenerImpl::FileSelected(
80 std::vector<blink::mojom::FileChooserFileInfoPtr> files,
81 const base::FilePath& base_dir,
82 blink::mojom::FileChooserParams::Mode mode) {
83#if DCHECK_IS_ON()
84 DCHECK(!was_file_select_listener_function_called_)
85 << "Must not call both of FileSelectListener::FileSelected() and "
86 "FileSelectListener::FileSelectionCanceled()";
87 was_file_select_listener_function_called_ = true;
88#endif
Xiaocheng Hu933cc812022-09-10 05:53:4989 if (!owner_)
90 return;
91
92 if (mode != blink::mojom::FileChooserParams::Mode::kUploadFolder) {
93 owner_->FileSelected(base_dir, mode, std::move(files));
94 return;
95 }
96
97 base::ThreadPool::PostTaskAndReplyWithResult(
98 FROM_HERE,
99 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
Xiaocheng Hu4fa830d2022-11-03 23:40:36100 base::BindOnce(&RemoveSymlinks, std::move(files), base_dir),
Kevin McNee321dfe22023-12-04 20:23:26101 base::BindOnce(&FileChooserImpl::FileSelected, owner_, base_dir, mode));
Avi Drissman1a55a9d62020-03-10 18:56:45102}
103
104void FileChooserImpl::FileSelectListenerImpl::FileSelectionCanceled() {
105#if DCHECK_IS_ON()
106 DCHECK(!was_file_select_listener_function_called_)
107 << "Should not call both of FileSelectListener::FileSelected() and "
108 "FileSelectListener::FileSelectionCanceled()";
109 was_file_select_listener_function_called_ = true;
110#endif
111 if (owner_)
112 owner_->FileSelectionCanceled();
113}
114
115void FileChooserImpl::FileSelectListenerImpl::
116 SetListenerFunctionCalledTrueForTesting() {
117#if DCHECK_IS_ON()
118 was_file_select_listener_function_called_ = true;
119#endif
120}
121
122// static
123void FileChooserImpl::Create(
124 RenderFrameHostImpl* render_frame_host,
125 mojo::PendingReceiver<blink::mojom::FileChooser> receiver) {
126 mojo::MakeSelfOwnedReceiver(
Avi Drissmanbd3e986442020-05-20 21:09:20127 base::WrapUnique(new FileChooserImpl(render_frame_host)),
Avi Drissman1a55a9d62020-03-10 18:56:45128 std::move(receiver));
129}
130
Avi Drissmanbd3e986442020-05-20 21:09:20131// static
132mojo::Remote<blink::mojom::FileChooser> FileChooserImpl::CreateBoundForTesting(
133 RenderFrameHostImpl* render_frame_host) {
134 mojo::Remote<blink::mojom::FileChooser> chooser;
135 Create(render_frame_host, chooser.BindNewPipeAndPassReceiver());
136 return chooser;
137}
138
Joey Arhar05ae5bb92021-02-15 04:06:54139// static
140std::pair<FileChooserImpl*, mojo::Remote<blink::mojom::FileChooser>>
141FileChooserImpl::CreateForTesting(RenderFrameHostImpl* render_frame_host) {
142 mojo::Remote<blink::mojom::FileChooser> chooser;
143 FileChooserImpl* impl = new FileChooserImpl(render_frame_host);
144 mojo::MakeSelfOwnedReceiver(base::WrapUnique(impl),
145 chooser.BindNewPipeAndPassReceiver());
146 return std::make_pair(impl, std::move(chooser));
147}
148
Avi Drissman1a55a9d62020-03-10 18:56:45149FileChooserImpl::FileChooserImpl(RenderFrameHostImpl* render_frame_host)
Kevin McNee321dfe22023-12-04 20:23:26150 : render_frame_host_id_(render_frame_host->GetGlobalId()) {}
Avi Drissman1a55a9d62020-03-10 18:56:45151
Kevin McNee321dfe22023-12-04 20:23:26152FileChooserImpl::~FileChooserImpl() = default;
Avi Drissman1a55a9d62020-03-10 18:56:45153
154void FileChooserImpl::OpenFileChooser(blink::mojom::FileChooserParamsPtr params,
155 OpenFileChooserCallback callback) {
Kevin McNee321dfe22023-12-04 20:23:26156 if (listener_impl_ || !render_frame_host()) {
Avi Drissman1a55a9d62020-03-10 18:56:45157 std::move(callback).Run(nullptr);
158 return;
159 }
160 callback_ = std::move(callback);
Kent Tamura3abb32d2020-07-02 00:23:01161 auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this);
Avi Drissman1a55a9d62020-03-10 18:56:45162 listener_impl_ = listener.get();
163 // Do not allow messages with absolute paths in them as this can permit a
164 // renderer to coerce the browser to perform I/O on a renderer controlled
165 // path.
166 if (params->default_file_name != params->default_file_name.BaseName()) {
167 mojo::ReportBadMessage(
168 "FileChooser: The default file name must not be an absolute path.");
169 listener->FileSelectionCanceled();
170 return;
171 }
Sreeja Kamishettybfd828d2020-06-10 11:56:05172
Alesandro Ortizebee7692025-07-25 23:46:32173 // Do not allow open dialogs to have renderer-controlled default_file_name.
174 // See https://crbug.com/433800617 for context.
175 if (params->mode != blink::mojom::FileChooserParams::Mode::kSave) {
176 params->default_file_name = base::FilePath();
177 }
178
Sreeja Kamishettybfd828d2020-06-10 11:56:05179 // Don't allow page with open FileChooser to enter BackForwardCache to avoid
180 // any unexpected behaviour from BackForwardCache.
Fergal Daly23b8ae62021-03-30 05:43:46181 BackForwardCache::DisableForRenderFrameHost(
Kevin McNee321dfe22023-12-04 20:23:26182 render_frame_host(),
Fergal Daly23b8ae62021-03-30 05:43:46183 BackForwardCacheDisable::DisabledReason(
184 BackForwardCacheDisable::DisabledReasonId::kFileChooser));
Sreeja Kamishettybfd828d2020-06-10 11:56:05185
Kevin McNee321dfe22023-12-04 20:23:26186 WebContentsImpl::FromRenderFrameHostImpl(render_frame_host())
187 ->RunFileChooser(GetWeakPtr(), render_frame_host(), std::move(listener),
Bo Liu3afe2582023-08-11 23:02:56188 *params);
Avi Drissman1a55a9d62020-03-10 18:56:45189}
190
191void FileChooserImpl::EnumerateChosenDirectory(
192 const base::FilePath& directory_path,
193 EnumerateChosenDirectoryCallback callback) {
Kevin McNee321dfe22023-12-04 20:23:26194 if (listener_impl_ || !render_frame_host()) {
Avi Drissman1a55a9d62020-03-10 18:56:45195 std::move(callback).Run(nullptr);
196 return;
197 }
198 callback_ = std::move(callback);
Kent Tamura3abb32d2020-07-02 00:23:01199 auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this);
Avi Drissman1a55a9d62020-03-10 18:56:45200 listener_impl_ = listener.get();
201 auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
Emily Andrewsd15fd762024-12-10 20:41:54202 if (policy->CanReadFile(render_frame_host()->GetProcess()->GetDeprecatedID(),
Avi Drissman1a55a9d62020-03-10 18:56:45203 directory_path)) {
Kevin McNee321dfe22023-12-04 20:23:26204 WebContentsImpl::FromRenderFrameHostImpl(render_frame_host())
205 ->EnumerateDirectory(GetWeakPtr(), render_frame_host(),
Bo Liu3afe2582023-08-11 23:02:56206 std::move(listener), directory_path);
Avi Drissman1a55a9d62020-03-10 18:56:45207 } else {
208 listener->FileSelectionCanceled();
209 }
210}
211
212void FileChooserImpl::FileSelected(
Avi Drissman1a55a9d62020-03-10 18:56:45213 const base::FilePath& base_dir,
Xiaocheng Hu933cc812022-09-10 05:53:49214 blink::mojom::FileChooserParams::Mode mode,
215 std::vector<blink::mojom::FileChooserFileInfoPtr> files) {
Avi Drissman1a55a9d62020-03-10 18:56:45216 listener_impl_ = nullptr;
Kevin McNee321dfe22023-12-04 20:23:26217 if (!render_frame_host()) {
Joey Arhar05ae5bb92021-02-15 04:06:54218 std::move(callback_).Run(nullptr);
Avi Drissman1a55a9d62020-03-10 18:56:45219 return;
Joey Arhar05ae5bb92021-02-15 04:06:54220 }
Avi Drissman1a55a9d62020-03-10 18:56:45221 storage::FileSystemContext* file_system_context = nullptr;
Emily Andrewsd15fd762024-12-10 20:41:54222 const int pid = render_frame_host()->GetProcess()->GetDeprecatedID();
Avi Drissman1a55a9d62020-03-10 18:56:45223 auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
224 // Grant the security access requested to the given files.
225 for (const auto& file : files) {
226 if (mode == blink::mojom::FileChooserParams::Mode::kSave) {
227 policy->GrantCreateReadWriteFile(pid, file->get_native_file()->file_path);
Lei Zhang65ab6da02025-07-24 23:24:09228 continue;
229 }
230
231 if (file->is_file_system()) {
232 if (!file_system_context) {
233 file_system_context =
234 render_frame_host()->GetStoragePartition()->GetFileSystemContext();
Avi Drissman1a55a9d62020-03-10 18:56:45235 }
Lei Zhang65ab6da02025-07-24 23:24:09236 policy->GrantReadFileSystem(
237 pid, file_system_context
238 ->CrackURLInFirstPartyContext(file->get_file_system()->url)
239 .mount_filesystem_id());
240 } else {
241 policy->GrantReadFile(pid, file->get_native_file()->file_path);
Avi Drissman1a55a9d62020-03-10 18:56:45242 }
243 }
244 std::move(callback_).Run(FileChooserResult::New(std::move(files), base_dir));
245}
246
247void FileChooserImpl::FileSelectionCanceled() {
248 listener_impl_ = nullptr;
Avi Drissman1a55a9d62020-03-10 18:56:45249 std::move(callback_).Run(nullptr);
250}
251
Kevin McNee321dfe22023-12-04 20:23:26252RenderFrameHostImpl* FileChooserImpl::render_frame_host() {
253 RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(render_frame_host_id_);
Lei Zhang65ab6da02025-07-24 23:24:09254 return (rfh && rfh->IsRenderFrameLive()) ? rfh : nullptr;
Avi Drissman1a55a9d62020-03-10 18:56:45255}
256
257} // namespace content