1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/file_select_helper.h"
13 #include "base/files/file_util.h"
14 #include "base/functional/bind.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/task/thread_pool.h"
20 #include "base/threading/hang_watcher.h"
21 #include "build/build_config.h"
22 #include "build/chromeos_buildflags.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/enterprise/connectors/common.h"
25 #include "chrome/browser/platform_util.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/browser/ui/browser_dialogs.h"
29 #include "chrome/browser/ui/chrome_select_file_policy.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "components/enterprise/buildflags/buildflags.h"
32 #include "components/enterprise/common/proto/connectors.pb.h"
33 #include "components/safe_browsing/buildflags.h"
34 #include "content/public/browser/browser_task_traits.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/file_select_listener.h"
37 #include "content/public/browser/global_routing_id.h"
38 #include "content/public/browser/render_frame_host.h"
39 #include "content/public/browser/render_process_host.h"
40 #include "content/public/browser/storage_partition.h"
41 #include "content/public/browser/web_contents.h"
42 #include "net/base/filename_util.h"
43 #include "net/base/mime_util.h"
44 #include "ui/base/l10n/l10n_util.h"
45 #include "ui/shell_dialogs/selected_file_info.h"
47 #if BUILDFLAG(IS_ANDROID)
48 #include "chrome/browser/file_select_helper_contacts_android.h"
51 #if BUILDFLAG(IS_CHROMEOS_ASH)
52 #include "chrome/browser/ash/file_manager/fileapi_util.h"
53 #include "content/public/browser/site_instance.h"
56 #if BUILDFLAG(FULL_SAFE_BROWSING)
57 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
58 #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
59 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
62 using blink::mojom::FileChooserFileInfo;
63 using blink::mojom::FileChooserFileInfoPtr;
64 using blink::mojom::FileChooserParams;
65 using blink::mojom::FileChooserParamsPtr;
66 using content::BrowserThread;
67 using content::WebContents;
71 #if BUILDFLAG(IS_ANDROID)
72 // The MIME type for selecting contacts.
73 constexpr char16_t kContactsMimeType[] = u"text/json+contacts";
76 void DeleteFiles(std::vector<base::FilePath> paths) {
77 for (auto& file_path : paths)
78 base::DeleteFile(file_path);
81 bool IsValidProfile(Profile* profile) {
82 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
86 // No profile manager in unit tests.
87 if (!g_browser_process->profile_manager())
89 return g_browser_process->profile_manager()->IsValidProfile(profile);
92 #if BUILDFLAG(FULL_SAFE_BROWSING)
94 bool IsDownloadAllowedBySafeBrowsing(
95 safe_browsing::DownloadCheckResult result) {
96 using Result = safe_browsing::DownloadCheckResult;
98 // Only allow downloads that are marked as SAFE or UNKNOWN by SafeBrowsing.
99 // All other types are going to be blocked. UNKNOWN could be the result of a
100 // failed safe browsing ping.
101 case Result::UNKNOWN:
103 case Result::ALLOWLISTED_BY_POLICY:
106 case Result::DANGEROUS:
107 case Result::UNCOMMON:
108 case Result::DANGEROUS_HOST:
109 case Result::POTENTIALLY_UNWANTED:
110 case Result::DANGEROUS_ACCOUNT_COMPROMISE:
113 // Safe Browsing should only return these results for client downloads, not
114 // for PPAPI downloads.
115 case Result::ASYNC_SCANNING:
116 case Result::BLOCKED_PASSWORD_PROTECTED:
117 case Result::BLOCKED_TOO_LARGE:
118 case Result::SENSITIVE_CONTENT_BLOCK:
119 case Result::SENSITIVE_CONTENT_WARNING:
120 case Result::DEEP_SCANNED_SAFE:
121 case Result::PROMPT_FOR_SCANNING:
122 case Result::PROMPT_FOR_LOCAL_PASSWORD_SCANNING:
123 case Result::BLOCKED_UNSUPPORTED_FILE_TYPE:
124 case Result::DEEP_SCANNED_FAILED:
132 void InterpretSafeBrowsingVerdict(base::OnceCallback<void(bool)> recipient,
133 safe_browsing::DownloadCheckResult result) {
134 std::move(recipient).Run(IsDownloadAllowedBySafeBrowsing(result));
141 struct FileSelectHelper::ActiveDirectoryEnumeration {
142 explicit ActiveDirectoryEnumeration(const base::FilePath& path)
145 std::unique_ptr<net::DirectoryLister> lister_;
146 const base::FilePath path_;
147 std::vector<base::FilePath> results_;
150 FileSelectHelper::FileSelectHelper(Profile* profile)
152 render_frame_host_(nullptr),
153 web_contents_(nullptr),
154 select_file_dialog_(),
155 select_file_types_(),
156 dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
157 dialog_mode_(FileChooserParams::Mode::kOpen) {}
159 FileSelectHelper::~FileSelectHelper() {
160 // There may be pending file dialogs, we need to tell them that we've gone
161 // away so they don't try and call back to us.
162 if (select_file_dialog_)
163 select_file_dialog_->ListenerDestroyed();
166 void FileSelectHelper::FileSelected(const base::FilePath& path,
169 FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
172 void FileSelectHelper::FileSelectedWithExtraInfo(
173 const ui::SelectedFileInfo& file,
176 if (IsValidProfile(profile_)) {
177 base::FilePath path = file.file_path;
178 if (dialog_mode_ != FileChooserParams::Mode::kUploadFolder)
179 path = path.DirName();
180 profile_->set_last_selected_directory(path);
183 if (!render_frame_host_) {
188 const base::FilePath& path = file.local_path;
189 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
190 StartNewEnumeration(path);
194 std::vector<ui::SelectedFileInfo> files;
195 files.push_back(file);
197 #if BUILDFLAG(IS_MAC)
198 base::ThreadPool::PostTask(
200 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
201 base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
203 ConvertToFileChooserFileInfoList(files);
204 #endif // BUILDFLAG(IS_MAC)
207 void FileSelectHelper::MultiFilesSelected(
208 const std::vector<base::FilePath>& files,
210 std::vector<ui::SelectedFileInfo> selected_files =
211 ui::FilePathListToSelectedFileInfoList(files);
213 MultiFilesSelectedWithExtraInfo(selected_files, params);
216 void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
217 const std::vector<ui::SelectedFileInfo>& files,
219 if (!files.empty() && IsValidProfile(profile_)) {
220 base::FilePath path = files[0].file_path;
221 if (dialog_mode_ != FileChooserParams::Mode::kUploadFolder)
222 path = path.DirName();
223 profile_->set_last_selected_directory(path);
225 #if BUILDFLAG(IS_MAC)
226 base::ThreadPool::PostTask(
228 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
229 base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
231 ConvertToFileChooserFileInfoList(files);
232 #endif // BUILDFLAG(IS_MAC)
235 void FileSelectHelper::FileSelectionCanceled(void* params) {
239 void FileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
241 auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
242 entry->lister_ = base::WrapUnique(new net::DirectoryLister(
243 path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
244 entry->lister_->Start();
245 directory_enumeration_ = std::move(entry);
248 void FileSelectHelper::OnListFile(
249 const net::DirectoryLister::DirectoryListerData& data) {
250 // Directory upload only cares about files.
251 if (data.info.IsDirectory())
254 directory_enumeration_->results_.push_back(data.path);
257 void FileSelectHelper::LaunchConfirmationDialog(
258 const base::FilePath& path,
259 std::vector<ui::SelectedFileInfo> selected_files) {
260 ShowFolderUploadConfirmationDialog(
262 base::BindOnce(&FileSelectHelper::ConvertToFileChooserFileInfoList, this),
263 std::move(selected_files), web_contents_);
266 void FileSelectHelper::OnListDone(int error) {
267 if (!web_contents_) {
268 // Web contents was destroyed under us (probably by closing the tab). We
269 // must notify |listener_| and release our reference to
270 // ourself. RunFileChooserEnd() performs this.
275 // This entry needs to be cleaned up when this function is done.
276 std::unique_ptr<ActiveDirectoryEnumeration> entry =
277 std::move(directory_enumeration_);
279 FileSelectionCanceled(nullptr);
283 std::vector<ui::SelectedFileInfo> selected_files =
284 ui::FilePathListToSelectedFileInfoList(entry->results_);
286 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
287 LaunchConfirmationDialog(entry->path_, std::move(selected_files));
289 std::vector<FileChooserFileInfoPtr> chooser_files;
290 for (const auto& file_path : entry->results_) {
291 chooser_files.push_back(FileChooserFileInfo::NewNativeFile(
292 blink::mojom::NativeFileInfo::New(file_path, std::u16string())));
295 listener_->FileSelected(std::move(chooser_files), base_dir_,
296 FileChooserParams::Mode::kUploadFolder);
298 EnumerateDirectoryEnd();
302 void FileSelectHelper::ConvertToFileChooserFileInfoList(
303 const std::vector<ui::SelectedFileInfo>& files) {
304 if (AbortIfWebContentsDestroyed())
307 #if BUILDFLAG(IS_CHROMEOS_ASH)
308 if (!files.empty()) {
309 if (!IsValidProfile(profile_)) {
313 // Converts |files| into FileChooserFileInfo with handling of non-native
315 content::SiteInstance* site_instance =
316 render_frame_host_->GetSiteInstance();
317 storage::FileSystemContext* file_system_context =
318 profile_->GetStoragePartition(site_instance)->GetFileSystemContext();
319 file_manager::util::ConvertSelectedFileInfoListToFileChooserFileInfoList(
320 file_system_context, render_frame_host_->GetLastCommittedOrigin(),
322 base::BindOnce(&FileSelectHelper::PerformContentAnalysisIfNeeded,
326 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
328 std::vector<FileChooserFileInfoPtr> chooser_files;
329 for (const auto& file : files) {
330 chooser_files.push_back(
331 FileChooserFileInfo::NewNativeFile(blink::mojom::NativeFileInfo::New(
333 base::FilePath(file.display_name).AsUTF16Unsafe())));
336 PerformContentAnalysisIfNeeded(std::move(chooser_files));
339 void FileSelectHelper::PerformContentAnalysisIfNeeded(
340 std::vector<FileChooserFileInfoPtr> list) {
341 if (AbortIfWebContentsDestroyed())
344 #if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
345 enterprise_connectors::ContentAnalysisDelegate::Data data;
346 if (enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
347 profile_, web_contents_->GetLastCommittedURL(), &data,
348 enterprise_connectors::AnalysisConnector::FILE_ATTACHED)) {
350 enterprise_connectors::ContentAnalysisRequest::FILE_PICKER_DIALOG;
351 data.paths.reserve(list.size());
352 for (const auto& file : list) {
353 if (file && file->is_native_file())
354 data.paths.push_back(file->get_native_file()->file_path);
357 if (data.paths.empty()) {
358 NotifyListenerAndEnd(std::move(list));
360 enterprise_connectors::ContentAnalysisDelegate::CreateForWebContents(
361 web_contents_, std::move(data),
362 base::BindOnce(&FileSelectHelper::ContentAnalysisCompletionCallback,
363 this, std::move(list)),
364 safe_browsing::DeepScanAccessPoint::UPLOAD);
367 NotifyListenerAndEnd(std::move(list));
370 NotifyListenerAndEnd(std::move(list));
371 #endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
374 #if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
375 void FileSelectHelper::ContentAnalysisCompletionCallback(
376 std::vector<blink::mojom::FileChooserFileInfoPtr> list,
377 const enterprise_connectors::ContentAnalysisDelegate::Data& data,
378 enterprise_connectors::ContentAnalysisDelegate::Result& result) {
379 if (AbortIfWebContentsDestroyed())
382 DCHECK_EQ(data.text.size(), 0u);
383 DCHECK_EQ(result.text_results.size(), 0u);
384 DCHECK_EQ(data.paths.size(), result.paths_results.size());
385 DCHECK_GE(list.size(), result.paths_results.size());
387 // If the user chooses to upload a folder and the folder contains sensitive
388 // files, block the entire folder and update `result` to reflect the block
389 // verdict for all files scanned.
390 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
391 if (base::Contains(result.paths_results, false)) {
393 for (size_t index = 0; index < data.paths.size(); ++index) {
394 result.paths_results[index] = false;
397 // Early return for folder upload, regardless of list being empty or not.
398 NotifyListenerAndEnd(std::move(list));
402 // For single or multiple file uploads, remove any files that did not pass the
403 // deep scan. Non-native files are skipped.
405 for (auto it = list.begin(); it != list.end();) {
406 if ((*it)->is_native_file()) {
407 if (!result.paths_results[i]) {
414 // Skip non-native files by incrementing the iterator without changing `i`
415 // so that no result is skipped.
420 NotifyListenerAndEnd(std::move(list));
422 #endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
424 void FileSelectHelper::NotifyListenerAndEnd(
425 std::vector<blink::mojom::FileChooserFileInfoPtr> list) {
426 listener_->FileSelected(std::move(list), base_dir_, dialog_mode_);
429 // No members should be accessed from here on.
433 void FileSelectHelper::DeleteTemporaryFiles() {
434 base::ThreadPool::PostTask(
436 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
437 base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
438 base::BindOnce(&DeleteFiles, std::move(temporary_files_)));
441 void FileSelectHelper::CleanUp() {
442 if (!temporary_files_.empty()) {
443 DeleteTemporaryFiles();
445 // Now that the temporary files have been scheduled for deletion, there
446 // is no longer any reason to keep this instance around.
451 bool FileSelectHelper::AbortIfWebContentsDestroyed() {
452 if (abort_on_missing_web_contents_in_tests_ &&
453 (render_frame_host_ == nullptr || web_contents_ == nullptr)) {
461 void FileSelectHelper::SetFileSelectListenerForTesting(
462 scoped_refptr<content::FileSelectListener> listener) {
465 listener_ = std::move(listener);
468 void FileSelectHelper::DontAbortOnMissingWebContentsForTesting() {
469 abort_on_missing_web_contents_in_tests_ = false;
472 std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
473 FileSelectHelper::GetFileTypesFromAcceptType(
474 const std::vector<std::u16string>& accept_types) {
475 auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
476 if (accept_types.empty())
477 return base_file_type;
479 // Create FileTypeInfo and pre-allocate for the first extension list.
481 std::make_unique<ui::SelectFileDialog::FileTypeInfo>(*base_file_type);
482 file_type->include_all_files = true;
483 file_type->extensions.resize(1);
484 std::vector<base::FilePath::StringType>* extensions =
485 &file_type->extensions.back();
487 // Find the corresponding extensions.
488 int valid_type_count = 0;
489 int description_id = 0;
490 for (const auto& accept_type : accept_types) {
491 size_t old_extension_size = extensions->size();
492 if (accept_type[0] == '.') {
493 // If the type starts with a period it is assumed to be a file extension
494 // so we just have to add it to the list.
495 base::FilePath::StringType ext =
496 base::FilePath::FromUTF16Unsafe(accept_type).value();
497 extensions->push_back(ext.substr(1));
499 if (!base::IsStringASCII(accept_type))
501 std::string ascii_type = base::UTF16ToASCII(accept_type);
502 if (ascii_type == "image/*")
503 description_id = IDS_IMAGE_FILES;
504 else if (ascii_type == "audio/*")
505 description_id = IDS_AUDIO_FILES;
506 else if (ascii_type == "video/*")
507 description_id = IDS_VIDEO_FILES;
509 net::GetExtensionsForMimeType(ascii_type, extensions);
512 if (extensions->size() > old_extension_size)
516 // If no valid extension is added, bail out.
517 if (valid_type_count == 0)
518 return base_file_type;
520 // Use a generic description "Custom Files" if either of the following is
522 // 1) There're multiple types specified, like "audio/*,video/*"
523 // 2) There're multiple extensions for a MIME type without parameter, like
524 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
525 // dialog uses the first extension in the list to form the description,
526 // like "EHTML Files". This is not what we want.
527 if (valid_type_count > 1 ||
528 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
529 description_id = IDS_CUSTOM_FILES;
531 if (description_id) {
532 file_type->extension_description_overrides.push_back(
533 l10n_util::GetStringUTF16(description_id));
540 void FileSelectHelper::RunFileChooser(
541 content::RenderFrameHost* render_frame_host,
542 scoped_refptr<content::FileSelectListener> listener,
543 const FileChooserParams& params) {
544 Profile* profile = Profile::FromBrowserContext(
545 render_frame_host->GetProcess()->GetBrowserContext());
547 #if BUILDFLAG(IS_ANDROID)
548 if (params.accept_types.size() == 1 &&
549 params.accept_types[0] == kContactsMimeType) {
550 scoped_refptr<FileSelectHelperContactsAndroid> file_select_helper_android(
551 new FileSelectHelperContactsAndroid(profile));
552 file_select_helper_android->RunFileChooser(
553 render_frame_host, std::move(listener), params.Clone());
558 // FileSelectHelper will keep itself alive until it sends the result
560 scoped_refptr<FileSelectHelper> file_select_helper(
561 new FileSelectHelper(profile));
562 file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
567 void FileSelectHelper::EnumerateDirectory(
568 content::WebContents* tab,
569 scoped_refptr<content::FileSelectListener> listener,
570 const base::FilePath& path) {
571 Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
572 // FileSelectHelper will keep itself alive until it sends the result
574 scoped_refptr<FileSelectHelper> file_select_helper(
575 new FileSelectHelper(profile));
576 file_select_helper->EnumerateDirectoryImpl(tab, std::move(listener), path);
579 void FileSelectHelper::RunFileChooser(
580 content::RenderFrameHost* render_frame_host,
581 scoped_refptr<content::FileSelectListener> listener,
582 FileChooserParamsPtr params) {
583 DCHECK(!render_frame_host_);
584 DCHECK(!web_contents_);
587 DCHECK(params->default_file_name.empty() ||
588 params->mode == FileChooserParams::Mode::kSave)
589 << "The default_file_name parameter should only be specified for Save "
591 DCHECK(params->default_file_name == params->default_file_name.BaseName())
592 << "The default_file_name parameter should not contain path separators";
594 render_frame_host_ = render_frame_host;
595 web_contents_ = WebContents::FromRenderFrameHost(render_frame_host);
596 listener_ = std::move(listener);
597 content::WebContentsObserver::Observe(web_contents_);
599 base::ThreadPool::PostTask(
600 FROM_HERE, {base::MayBlock()},
601 base::BindOnce(&FileSelectHelper::GetFileTypesInThreadPool, this,
604 // Because this class returns notifications to the RenderViewHost, it is
605 // difficult for callers to know how long to keep a reference to this
606 // instance. We AddRef() here to keep the instance alive after we return
607 // to the caller, until the last callback is received from the file dialog.
608 // At that point, we must call RunFileChooserEnd().
612 void FileSelectHelper::GetFileTypesInThreadPool(FileChooserParamsPtr params) {
613 select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
614 select_file_types_->allowed_paths =
615 params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
616 : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
618 content::GetUIThreadTaskRunner({})->PostTask(
620 base::BindOnce(&FileSelectHelper::GetSanitizedFilenameOnUIThread, this,
624 void FileSelectHelper::GetSanitizedFilenameOnUIThread(
625 FileChooserParamsPtr params) {
626 if (AbortIfWebContentsDestroyed())
629 base::FilePath default_file_path = profile_->last_selected_directory().Append(
630 GetSanitizedFileName(params->default_file_name));
631 #if BUILDFLAG(FULL_SAFE_BROWSING)
632 if (params->mode == FileChooserParams::Mode::kSave) {
633 CheckDownloadRequestWithSafeBrowsing(default_file_path, std::move(params));
637 RunFileChooserOnUIThread(default_file_path, std::move(params));
640 #if BUILDFLAG(FULL_SAFE_BROWSING)
641 void FileSelectHelper::CheckDownloadRequestWithSafeBrowsing(
642 const base::FilePath& default_file_path,
643 FileChooserParamsPtr params) {
644 // Download Protection is not supported on Android.
645 safe_browsing::SafeBrowsingService* sb_service =
646 g_browser_process->safe_browsing_service();
647 bool real_time_download_protection_request_allowed =
648 safe_browsing::IsRealTimeDownloadProtectionRequestAllowed(
649 *profile_->GetPrefs());
651 if (!sb_service || !sb_service->download_protection_service() ||
652 !sb_service->download_protection_service()->enabled() ||
653 !real_time_download_protection_request_allowed) {
654 RunFileChooserOnUIThread(default_file_path, std::move(params));
658 std::vector<base::FilePath::StringType> alternate_extensions;
659 if (select_file_types_) {
660 for (const auto& extensions_list : select_file_types_->extensions) {
661 for (const auto& extension_in_list : extensions_list) {
662 base::FilePath::StringType extension =
663 default_file_path.ReplaceExtension(extension_in_list)
665 alternate_extensions.push_back(extension);
670 GURL requestor_url = params->requestor;
671 sb_service->download_protection_service()->CheckPPAPIDownloadRequest(
672 requestor_url, render_frame_host_, default_file_path,
673 alternate_extensions, profile_,
675 &InterpretSafeBrowsingVerdict,
676 base::BindOnce(&FileSelectHelper::ProceedWithSafeBrowsingVerdict,
677 this, default_file_path, std::move(params))));
680 void FileSelectHelper::ProceedWithSafeBrowsingVerdict(
681 const base::FilePath& default_file_path,
682 FileChooserParamsPtr params,
683 bool allowed_by_safe_browsing) {
684 if (!allowed_by_safe_browsing) {
688 RunFileChooserOnUIThread(default_file_path, std::move(params));
692 void FileSelectHelper::RunFileChooserOnUIThread(
693 const base::FilePath& default_file_path,
694 FileChooserParamsPtr params) {
696 DCHECK(!select_file_dialog_);
697 if (AbortIfWebContentsDestroyed())
700 select_file_dialog_ = ui::SelectFileDialog::Create(
701 this, std::make_unique<ChromeSelectFilePolicy>(web_contents_));
702 if (!select_file_dialog_)
705 dialog_mode_ = params->mode;
706 switch (params->mode) {
707 case FileChooserParams::Mode::kOpen:
708 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
710 case FileChooserParams::Mode::kOpenMultiple:
711 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
713 case FileChooserParams::Mode::kUploadFolder:
714 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
716 case FileChooserParams::Mode::kSave:
717 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
721 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
725 gfx::NativeWindow owning_window =
726 platform_util::GetTopLevel(web_contents_->GetNativeView());
728 #if BUILDFLAG(IS_ANDROID)
729 // Android needs the original MIME types and an additional capture value.
730 std::pair<std::vector<std::u16string>, bool> accept_types =
731 std::make_pair(params->accept_types, params->use_media_capture);
732 void* accept_types_ptr = &accept_types;
734 void* accept_types_ptr = nullptr;
737 // Never consider the current scope as hung. The hang watching deadline (if
738 // any) is not valid since the user can take unbounded time to choose the
740 base::HangWatcher::InvalidateActiveExpectations();
742 // 1-based index of default extension to show.
743 int file_type_index =
744 select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
747 &render_frame_host_->GetMainFrame()->GetLastCommittedURL();
749 select_file_dialog_->SelectFile(dialog_type_, params->title,
750 default_file_path, select_file_types_.get(),
751 file_type_index, base::FilePath::StringType(),
752 owning_window, accept_types_ptr, caller);
754 select_file_types_.reset();
757 // This method is called when we receive the last callback from the file chooser
758 // dialog or if the renderer was destroyed. Perform any cleanup and release the
759 // reference we added in RunFileChooser().
760 void FileSelectHelper::RunFileChooserEnd() {
761 // If there are temporary files, then this instance needs to stick around
762 // until web_contents_ is destroyed, so that this instance can delete the
764 if (!temporary_files_.empty())
768 listener_->FileSelectionCanceled();
769 render_frame_host_ = nullptr;
770 web_contents_ = nullptr;
771 // If the dialog was actually opened, dispose of our reference.
772 if (select_file_dialog_) {
773 select_file_dialog_->ListenerDestroyed();
774 select_file_dialog_.reset();
779 void FileSelectHelper::EnumerateDirectoryImpl(
780 content::WebContents* tab,
781 scoped_refptr<content::FileSelectListener> listener,
782 const base::FilePath& path) {
785 dialog_type_ = ui::SelectFileDialog::SELECT_NONE;
787 listener_ = std::move(listener);
788 // Because this class returns notifications to the RenderViewHost, it is
789 // difficult for callers to know how long to keep a reference to this
790 // instance. We AddRef() here to keep the instance alive after we return
791 // to the caller, until the last callback is received from the enumeration
792 // code. At that point, we must call EnumerateDirectoryEnd().
794 StartNewEnumeration(path);
797 // This method is called when we receive the last callback from the enumeration
798 // code. Perform any cleanup and release the reference we added in
799 // EnumerateDirectoryImpl().
800 void FileSelectHelper::EnumerateDirectoryEnd() {
804 void FileSelectHelper::RenderFrameHostChanged(
805 content::RenderFrameHost* old_host,
806 content::RenderFrameHost* new_host) {
807 // The |old_host| and its children are now pending deletion. Do not give them
808 // file access past this point.
809 for (content::RenderFrameHost* host = render_frame_host_; host;
810 host = host->GetParentOrOuterDocument()) {
811 if (host == old_host) {
812 render_frame_host_ = nullptr;
818 void FileSelectHelper::RenderFrameDeleted(
819 content::RenderFrameHost* render_frame_host) {
820 if (render_frame_host == render_frame_host_)
821 render_frame_host_ = nullptr;
824 void FileSelectHelper::WebContentsDestroyed() {
825 render_frame_host_ = nullptr;
826 web_contents_ = nullptr;
832 bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) {
833 // TODO(raymes): This only does some basic checks, extend to test more cases.
834 // A 1 character accept type will always be invalid (either a "." in the case
835 // of an extension or a "/" in the case of a MIME type).
837 if (accept_type.length() <= 1 ||
838 base::ToLowerASCII(accept_type) != accept_type ||
839 base::TrimWhitespaceASCII(accept_type, base::TRIM_ALL, &unused) !=
847 base::FilePath FileSelectHelper::GetSanitizedFileName(
848 const base::FilePath& suggested_filename) {
849 if (suggested_filename.empty())
850 return base::FilePath();
851 return net::GenerateFileName(
852 GURL(), std::string(), std::string(), suggested_filename.AsUTF8Unsafe(),
853 std::string(), l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));