Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2020 The Chromium Authors |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Avi Drissman | bd3e98644 | 2020-05-20 21:09:20 | [diff] [blame] | 5 | #include "content/browser/web_contents/file_chooser_impl.h" |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 6 | |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 7 | #include <algorithm> |
| 8 | |
Xiaocheng Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 9 | #include "base/files/file_util.h" |
avvall | 90b2f24 | 2022-02-17 21:25:23 | [diff] [blame] | 10 | #include "base/logging.h" |
Avi Drissman | bd3e98644 | 2020-05-20 21:09:20 | [diff] [blame] | 11 | #include "base/memory/ptr_util.h" |
Xiaocheng Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 12 | #include "base/task/thread_pool.h" |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 13 | #include "content/browser/child_process_security_policy_impl.h" |
Fergal Daly | 23b8ae6 | 2021-03-30 05:43:46 | [diff] [blame] | 14 | #include "content/browser/renderer_host/back_forward_cache_disable.h" |
danakj | e34636e | 2020-09-15 22:15:00 | [diff] [blame] | 15 | #include "content/browser/renderer_host/render_frame_host_delegate.h" |
| 16 | #include "content/browser/renderer_host/render_frame_host_impl.h" |
Avi Drissman | bd3e98644 | 2020-05-20 21:09:20 | [diff] [blame] | 17 | #include "content/browser/web_contents/web_contents_impl.h" |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 18 | #include "content/public/browser/browser_context.h" |
| 19 | #include "content/public/browser/storage_partition.h" |
Patrick Monette | 8dd34d2 | 2020-05-14 19:33:39 | [diff] [blame] | 20 | #include "content/public/browser/web_contents.h" |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 21 | #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| 22 | |
| 23 | namespace content { |
| 24 | |
Xiaocheng Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 25 | namespace { |
| 26 | |
Xiaocheng Hu | 4fa830d | 2022-11-03 23:40:36 | [diff] [blame] | 27 | // 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 Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 29 | std::vector<blink::mojom::FileChooserFileInfoPtr> RemoveSymlinks( |
Xiaocheng Hu | 4fa830d | 2022-11-03 23:40:36 | [diff] [blame] | 30 | std::vector<blink::mojom::FileChooserFileInfoPtr> files, |
| 31 | base::FilePath base_dir) { |
| 32 | DCHECK(!base_dir.empty()); |
Peter Kasting | b0e8496 | 2025-01-13 18:46:03 | [diff] [blame] | 33 | auto to_remove = std::ranges::remove_if( |
Xiaocheng Hu | 4fa830d | 2022-11-03 23:40:36 | [diff] [blame] | 34 | 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 Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 45 | [](const auto& file) { return file->get_native_file()->file_path; }); |
Peter Kasting | b0e8496 | 2025-01-13 18:46:03 | [diff] [blame] | 46 | files.erase(to_remove.begin(), to_remove.end()); |
Xiaocheng Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 47 | return files; |
| 48 | } |
| 49 | |
| 50 | } // namespace |
| 51 | |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 52 | FileChooserImpl::FileSelectListenerImpl::FileSelectListenerImpl( |
| 53 | FileChooserImpl* owner) |
| 54 | : owner_(owner ? owner->GetWeakPtr() : nullptr) {} |
| 55 | |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 56 | FileChooserImpl::FileSelectListenerImpl::~FileSelectListenerImpl() { |
| 57 | #if DCHECK_IS_ON() |
avvall | 90b2f24 | 2022-02-17 21:25:23 | [diff] [blame] | 58 | if (!was_file_select_listener_function_called_) { |
| 59 | LOG(ERROR) << "Must call either FileSelectListener::FileSelected() or " |
| 60 | "FileSelectListener::FileSelectionCanceled()"; |
| 61 | } |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 62 | // 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 Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 67 | } |
| 68 | |
| 69 | void 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 | |
| 79 | void 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 Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 89 | 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 Hu | 4fa830d | 2022-11-03 23:40:36 | [diff] [blame] | 100 | base::BindOnce(&RemoveSymlinks, std::move(files), base_dir), |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 101 | base::BindOnce(&FileChooserImpl::FileSelected, owner_, base_dir, mode)); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | void 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 | |
| 115 | void FileChooserImpl::FileSelectListenerImpl:: |
| 116 | SetListenerFunctionCalledTrueForTesting() { |
| 117 | #if DCHECK_IS_ON() |
| 118 | was_file_select_listener_function_called_ = true; |
| 119 | #endif |
| 120 | } |
| 121 | |
| 122 | // static |
| 123 | void FileChooserImpl::Create( |
| 124 | RenderFrameHostImpl* render_frame_host, |
| 125 | mojo::PendingReceiver<blink::mojom::FileChooser> receiver) { |
| 126 | mojo::MakeSelfOwnedReceiver( |
Avi Drissman | bd3e98644 | 2020-05-20 21:09:20 | [diff] [blame] | 127 | base::WrapUnique(new FileChooserImpl(render_frame_host)), |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 128 | std::move(receiver)); |
| 129 | } |
| 130 | |
Avi Drissman | bd3e98644 | 2020-05-20 21:09:20 | [diff] [blame] | 131 | // static |
| 132 | mojo::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 Arhar | 05ae5bb9 | 2021-02-15 04:06:54 | [diff] [blame] | 139 | // static |
| 140 | std::pair<FileChooserImpl*, mojo::Remote<blink::mojom::FileChooser>> |
| 141 | FileChooserImpl::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 Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 149 | FileChooserImpl::FileChooserImpl(RenderFrameHostImpl* render_frame_host) |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 150 | : render_frame_host_id_(render_frame_host->GetGlobalId()) {} |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 151 | |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 152 | FileChooserImpl::~FileChooserImpl() = default; |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 153 | |
| 154 | void FileChooserImpl::OpenFileChooser(blink::mojom::FileChooserParamsPtr params, |
| 155 | OpenFileChooserCallback callback) { |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 156 | if (listener_impl_ || !render_frame_host()) { |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 157 | std::move(callback).Run(nullptr); |
| 158 | return; |
| 159 | } |
| 160 | callback_ = std::move(callback); |
Kent Tamura | 3abb32d | 2020-07-02 00:23:01 | [diff] [blame] | 161 | auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 162 | 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 Kamishetty | bfd828d | 2020-06-10 11:56:05 | [diff] [blame] | 172 | |
Alesandro Ortiz | ebee769 | 2025-07-25 23:46:32 | [diff] [blame] | 173 | // 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 Kamishetty | bfd828d | 2020-06-10 11:56:05 | [diff] [blame] | 179 | // Don't allow page with open FileChooser to enter BackForwardCache to avoid |
| 180 | // any unexpected behaviour from BackForwardCache. |
Fergal Daly | 23b8ae6 | 2021-03-30 05:43:46 | [diff] [blame] | 181 | BackForwardCache::DisableForRenderFrameHost( |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 182 | render_frame_host(), |
Fergal Daly | 23b8ae6 | 2021-03-30 05:43:46 | [diff] [blame] | 183 | BackForwardCacheDisable::DisabledReason( |
| 184 | BackForwardCacheDisable::DisabledReasonId::kFileChooser)); |
Sreeja Kamishetty | bfd828d | 2020-06-10 11:56:05 | [diff] [blame] | 185 | |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 186 | WebContentsImpl::FromRenderFrameHostImpl(render_frame_host()) |
| 187 | ->RunFileChooser(GetWeakPtr(), render_frame_host(), std::move(listener), |
Bo Liu | 3afe258 | 2023-08-11 23:02:56 | [diff] [blame] | 188 | *params); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | void FileChooserImpl::EnumerateChosenDirectory( |
| 192 | const base::FilePath& directory_path, |
| 193 | EnumerateChosenDirectoryCallback callback) { |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 194 | if (listener_impl_ || !render_frame_host()) { |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 195 | std::move(callback).Run(nullptr); |
| 196 | return; |
| 197 | } |
| 198 | callback_ = std::move(callback); |
Kent Tamura | 3abb32d | 2020-07-02 00:23:01 | [diff] [blame] | 199 | auto listener = base::MakeRefCounted<FileSelectListenerImpl>(this); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 200 | listener_impl_ = listener.get(); |
| 201 | auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 202 | if (policy->CanReadFile(render_frame_host()->GetProcess()->GetDeprecatedID(), |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 203 | directory_path)) { |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 204 | WebContentsImpl::FromRenderFrameHostImpl(render_frame_host()) |
| 205 | ->EnumerateDirectory(GetWeakPtr(), render_frame_host(), |
Bo Liu | 3afe258 | 2023-08-11 23:02:56 | [diff] [blame] | 206 | std::move(listener), directory_path); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 207 | } else { |
| 208 | listener->FileSelectionCanceled(); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | void FileChooserImpl::FileSelected( |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 213 | const base::FilePath& base_dir, |
Xiaocheng Hu | 933cc81 | 2022-09-10 05:53:49 | [diff] [blame] | 214 | blink::mojom::FileChooserParams::Mode mode, |
| 215 | std::vector<blink::mojom::FileChooserFileInfoPtr> files) { |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 216 | listener_impl_ = nullptr; |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 217 | if (!render_frame_host()) { |
Joey Arhar | 05ae5bb9 | 2021-02-15 04:06:54 | [diff] [blame] | 218 | std::move(callback_).Run(nullptr); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 219 | return; |
Joey Arhar | 05ae5bb9 | 2021-02-15 04:06:54 | [diff] [blame] | 220 | } |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 221 | storage::FileSystemContext* file_system_context = nullptr; |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 222 | const int pid = render_frame_host()->GetProcess()->GetDeprecatedID(); |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 223 | 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 Zhang | 65ab6da0 | 2025-07-24 23:24:09 | [diff] [blame] | 228 | 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 Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 235 | } |
Lei Zhang | 65ab6da0 | 2025-07-24 23:24:09 | [diff] [blame] | 236 | 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 Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 242 | } |
| 243 | } |
| 244 | std::move(callback_).Run(FileChooserResult::New(std::move(files), base_dir)); |
| 245 | } |
| 246 | |
| 247 | void FileChooserImpl::FileSelectionCanceled() { |
| 248 | listener_impl_ = nullptr; |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 249 | std::move(callback_).Run(nullptr); |
| 250 | } |
| 251 | |
Kevin McNee | 321dfe2 | 2023-12-04 20:23:26 | [diff] [blame] | 252 | RenderFrameHostImpl* FileChooserImpl::render_frame_host() { |
| 253 | RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(render_frame_host_id_); |
Lei Zhang | 65ab6da0 | 2025-07-24 23:24:09 | [diff] [blame] | 254 | return (rfh && rfh->IsRenderFrameLive()) ? rfh : nullptr; |
Avi Drissman | 1a55a9d6 | 2020-03-10 18:56:45 | [diff] [blame] | 255 | } |
| 256 | |
| 257 | } // namespace content |