file_select_helper.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. // Copyright (c) 2021 Microsoft. All rights reserved.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/file_select_helper.h"
  5. #include <stddef.h>
  6. #include <memory>
  7. #include <string>
  8. #include <utility>
  9. #include "base/bind.h"
  10. #include "base/files/file_util.h"
  11. #include "base/memory/ptr_util.h"
  12. #include "base/strings/string_split.h"
  13. #include "base/strings/string_util.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/task/thread_pool.h"
  16. #include "base/threading/hang_watcher.h"
  17. #include "build/build_config.h"
  18. #include "chrome/browser/browser_process.h"
  19. #include "chrome/browser/platform_util.h"
  20. #include "chrome/common/pref_names.h"
  21. #include "chrome/grit/generated_resources.h"
  22. #include "components/prefs/pref_service.h"
  23. #include "content/public/browser/browser_task_traits.h"
  24. #include "content/public/browser/browser_thread.h"
  25. #include "content/public/browser/file_select_listener.h"
  26. #include "content/public/browser/render_frame_host.h"
  27. #include "content/public/browser/render_process_host.h"
  28. #include "content/public/browser/render_view_host.h"
  29. #include "content/public/browser/render_widget_host_view.h"
  30. #include "content/public/browser/web_contents.h"
  31. #include "net/base/filename_util.h"
  32. #include "net/base/mime_util.h"
  33. #include "shell/browser/api/electron_api_web_contents.h"
  34. #include "shell/browser/electron_browser_context.h"
  35. #include "shell/browser/native_window.h"
  36. #include "ui/base/l10n/l10n_util.h"
  37. #include "ui/shell_dialogs/select_file_policy.h"
  38. #include "ui/shell_dialogs/selected_file_info.h"
  39. using blink::mojom::FileChooserFileInfo;
  40. using blink::mojom::FileChooserFileInfoPtr;
  41. using blink::mojom::FileChooserParams;
  42. using blink::mojom::FileChooserParamsPtr;
  43. using content::BrowserThread;
  44. using content::RenderViewHost;
  45. using content::RenderWidgetHost;
  46. using content::WebContents;
  47. namespace {
  48. void DeleteFiles(std::vector<base::FilePath> paths) {
  49. for (auto& file_path : paths)
  50. base::DeleteFile(file_path);
  51. }
  52. } // namespace
  53. struct FileSelectHelper::ActiveDirectoryEnumeration {
  54. explicit ActiveDirectoryEnumeration(const base::FilePath& path)
  55. : path_(path) {}
  56. std::unique_ptr<net::DirectoryLister> lister_;
  57. const base::FilePath path_;
  58. std::vector<base::FilePath> results_;
  59. };
  60. FileSelectHelper::FileSelectHelper()
  61. : render_frame_host_(nullptr),
  62. web_contents_(nullptr),
  63. select_file_dialog_(),
  64. select_file_types_(),
  65. dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
  66. dialog_mode_(FileChooserParams::Mode::kOpen) {}
  67. FileSelectHelper::~FileSelectHelper() {
  68. // There may be pending file dialogs, we need to tell them that we've gone
  69. // away so they don't try and call back to us.
  70. if (select_file_dialog_.get())
  71. select_file_dialog_->ListenerDestroyed();
  72. }
  73. void FileSelectHelper::FileSelected(const base::FilePath& path,
  74. int index,
  75. void* params) {
  76. FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
  77. }
  78. void FileSelectHelper::FileSelectedWithExtraInfo(
  79. const ui::SelectedFileInfo& file,
  80. int index,
  81. void* params) {
  82. if (!render_frame_host_) {
  83. RunFileChooserEnd();
  84. return;
  85. }
  86. const base::FilePath& path = file.local_path;
  87. if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
  88. StartNewEnumeration(path);
  89. return;
  90. }
  91. std::vector<ui::SelectedFileInfo> files;
  92. files.push_back(file);
  93. #if BUILDFLAG(IS_MAC)
  94. base::ThreadPool::PostTask(
  95. FROM_HERE,
  96. {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
  97. base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
  98. #else
  99. ConvertToFileChooserFileInfoList(files);
  100. #endif // BUILDFLAG(IS_MAC)
  101. }
  102. void FileSelectHelper::MultiFilesSelected(
  103. const std::vector<base::FilePath>& files,
  104. void* params) {
  105. std::vector<ui::SelectedFileInfo> selected_files =
  106. ui::FilePathListToSelectedFileInfoList(files);
  107. MultiFilesSelectedWithExtraInfo(selected_files, params);
  108. }
  109. void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
  110. const std::vector<ui::SelectedFileInfo>& files,
  111. void* params) {
  112. #if BUILDFLAG(IS_MAC)
  113. base::ThreadPool::PostTask(
  114. FROM_HERE,
  115. {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
  116. base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
  117. #else
  118. ConvertToFileChooserFileInfoList(files);
  119. #endif // BUILDFLAG(IS_MAC)
  120. }
  121. void FileSelectHelper::FileSelectionCanceled(void* params) {
  122. RunFileChooserEnd();
  123. }
  124. void FileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
  125. base_dir_ = path;
  126. auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
  127. entry->lister_ = base::WrapUnique(new net::DirectoryLister(
  128. path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
  129. entry->lister_->Start();
  130. directory_enumeration_ = std::move(entry);
  131. }
  132. void FileSelectHelper::OnListFile(
  133. const net::DirectoryLister::DirectoryListerData& data) {
  134. // Directory upload only cares about files.
  135. if (data.info.IsDirectory())
  136. return;
  137. directory_enumeration_->results_.push_back(data.path);
  138. }
  139. void FileSelectHelper::LaunchConfirmationDialog(
  140. const base::FilePath& path,
  141. std::vector<ui::SelectedFileInfo> selected_files) {
  142. ConvertToFileChooserFileInfoList(std::move(selected_files));
  143. }
  144. void FileSelectHelper::OnListDone(int error) {
  145. if (!web_contents_) {
  146. // Web contents was destroyed under us (probably by closing the tab). We
  147. // must notify |listener_| and release our reference to
  148. // ourself. RunFileChooserEnd() performs this.
  149. RunFileChooserEnd();
  150. return;
  151. }
  152. // This entry needs to be cleaned up when this function is done.
  153. std::unique_ptr<ActiveDirectoryEnumeration> entry =
  154. std::move(directory_enumeration_);
  155. if (error) {
  156. FileSelectionCanceled(NULL);
  157. return;
  158. }
  159. std::vector<ui::SelectedFileInfo> selected_files =
  160. ui::FilePathListToSelectedFileInfoList(entry->results_);
  161. if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
  162. LaunchConfirmationDialog(entry->path_, std::move(selected_files));
  163. } else {
  164. std::vector<FileChooserFileInfoPtr> chooser_files;
  165. for (const auto& file_path : entry->results_) {
  166. chooser_files.push_back(FileChooserFileInfo::NewNativeFile(
  167. blink::mojom::NativeFileInfo::New(file_path, std::u16string())));
  168. }
  169. listener_->FileSelected(std::move(chooser_files), base_dir_,
  170. FileChooserParams::Mode::kUploadFolder);
  171. listener_.reset();
  172. EnumerateDirectoryEnd();
  173. }
  174. }
  175. void FileSelectHelper::ConvertToFileChooserFileInfoList(
  176. const std::vector<ui::SelectedFileInfo>& files) {
  177. if (AbortIfWebContentsDestroyed())
  178. return;
  179. std::vector<FileChooserFileInfoPtr> chooser_files;
  180. for (const auto& file : files) {
  181. chooser_files.push_back(
  182. FileChooserFileInfo::NewNativeFile(blink::mojom::NativeFileInfo::New(
  183. file.local_path,
  184. base::FilePath(file.display_name).AsUTF16Unsafe())));
  185. }
  186. PerformContentAnalysisIfNeeded(std::move(chooser_files));
  187. }
  188. void FileSelectHelper::PerformContentAnalysisIfNeeded(
  189. std::vector<FileChooserFileInfoPtr> list) {
  190. if (AbortIfWebContentsDestroyed())
  191. return;
  192. NotifyListenerAndEnd(std::move(list));
  193. }
  194. void FileSelectHelper::NotifyListenerAndEnd(
  195. std::vector<blink::mojom::FileChooserFileInfoPtr> list) {
  196. listener_->FileSelected(std::move(list), base_dir_, dialog_mode_);
  197. listener_.reset();
  198. // No members should be accessed from here on.
  199. RunFileChooserEnd();
  200. }
  201. void FileSelectHelper::DeleteTemporaryFiles() {
  202. base::ThreadPool::PostTask(
  203. FROM_HERE,
  204. {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
  205. base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
  206. base::BindOnce(&DeleteFiles, std::move(temporary_files_)));
  207. }
  208. void FileSelectHelper::CleanUp() {
  209. if (!temporary_files_.empty()) {
  210. DeleteTemporaryFiles();
  211. // Now that the temporary files have been scheduled for deletion, there
  212. // is no longer any reason to keep this instance around.
  213. Release();
  214. }
  215. }
  216. bool FileSelectHelper::AbortIfWebContentsDestroyed() {
  217. if (render_frame_host_ == nullptr || web_contents_ == nullptr) {
  218. RunFileChooserEnd();
  219. return true;
  220. }
  221. return false;
  222. }
  223. void FileSelectHelper::SetFileSelectListenerForTesting(
  224. scoped_refptr<content::FileSelectListener> listener) {
  225. DCHECK(listener);
  226. DCHECK(!listener_);
  227. listener_ = std::move(listener);
  228. }
  229. std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
  230. FileSelectHelper::GetFileTypesFromAcceptType(
  231. const std::vector<std::u16string>& accept_types) {
  232. std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> base_file_type(
  233. new ui::SelectFileDialog::FileTypeInfo());
  234. if (accept_types.empty())
  235. return base_file_type;
  236. // Create FileTypeInfo and pre-allocate for the first extension list.
  237. std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> file_type(
  238. new ui::SelectFileDialog::FileTypeInfo(*base_file_type));
  239. file_type->include_all_files = true;
  240. file_type->extensions.resize(1);
  241. std::vector<base::FilePath::StringType>* extensions =
  242. &file_type->extensions.back();
  243. // Find the corresponding extensions.
  244. int valid_type_count = 0;
  245. int description_id = 0;
  246. for (const auto& accept_type : accept_types) {
  247. size_t old_extension_size = extensions->size();
  248. if (accept_type[0] == '.') {
  249. // If the type starts with a period it is assumed to be a file extension
  250. // so we just have to add it to the list.
  251. base::FilePath::StringType ext =
  252. base::FilePath::FromUTF16Unsafe(accept_type).value();
  253. extensions->push_back(ext.substr(1));
  254. } else {
  255. if (!base::IsStringASCII(accept_type))
  256. continue;
  257. std::string ascii_type = base::UTF16ToASCII(accept_type);
  258. if (ascii_type == "image/*")
  259. description_id = IDS_IMAGE_FILES;
  260. else if (ascii_type == "audio/*")
  261. description_id = IDS_AUDIO_FILES;
  262. else if (ascii_type == "video/*")
  263. description_id = IDS_VIDEO_FILES;
  264. net::GetExtensionsForMimeType(ascii_type, extensions);
  265. }
  266. if (extensions->size() > old_extension_size)
  267. valid_type_count++;
  268. }
  269. // If no valid extension is added, bail out.
  270. if (valid_type_count == 0)
  271. return base_file_type;
  272. // Use a generic description "Custom Files" if either of the following is
  273. // true:
  274. // 1) There're multiple types specified, like "audio/*,video/*"
  275. // 2) There're multiple extensions for a MIME type without parameter, like
  276. // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
  277. // dialog uses the first extension in the list to form the description,
  278. // like "EHTML Files". This is not what we want.
  279. if (valid_type_count > 1 ||
  280. (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
  281. description_id = IDS_CUSTOM_FILES;
  282. if (description_id) {
  283. file_type->extension_description_overrides.push_back(
  284. l10n_util::GetStringUTF16(description_id));
  285. }
  286. return file_type;
  287. }
  288. // static
  289. void FileSelectHelper::RunFileChooser(
  290. content::RenderFrameHost* render_frame_host,
  291. scoped_refptr<content::FileSelectListener> listener,
  292. const FileChooserParams& params) {
  293. // FileSelectHelper will keep itself alive until it sends the result
  294. // message.
  295. scoped_refptr<FileSelectHelper> file_select_helper(new FileSelectHelper());
  296. file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
  297. params.Clone());
  298. }
  299. // static
  300. void FileSelectHelper::EnumerateDirectory(
  301. content::WebContents* tab,
  302. scoped_refptr<content::FileSelectListener> listener,
  303. const base::FilePath& path) {
  304. // FileSelectHelper will keep itself alive until it sends the result
  305. // message.
  306. scoped_refptr<FileSelectHelper> file_select_helper(new FileSelectHelper());
  307. file_select_helper->EnumerateDirectoryImpl(tab, std::move(listener), path);
  308. }
  309. void FileSelectHelper::RunFileChooser(
  310. content::RenderFrameHost* render_frame_host,
  311. scoped_refptr<content::FileSelectListener> listener,
  312. FileChooserParamsPtr params) {
  313. DCHECK(!render_frame_host_);
  314. DCHECK(!web_contents_);
  315. DCHECK(listener);
  316. DCHECK(!listener_);
  317. DCHECK(params->default_file_name.empty() ||
  318. params->mode == FileChooserParams::Mode::kSave)
  319. << "The default_file_name parameter should only be specified for Save "
  320. "file choosers";
  321. DCHECK(params->default_file_name == params->default_file_name.BaseName())
  322. << "The default_file_name parameter should not contain path separators";
  323. render_frame_host_ = render_frame_host;
  324. web_contents_ = WebContents::FromRenderFrameHost(render_frame_host);
  325. listener_ = std::move(listener);
  326. observation_.Reset();
  327. content::WebContentsObserver::Observe(web_contents_);
  328. observation_.Observe(render_frame_host_->GetRenderViewHost()->GetWidget());
  329. base::ThreadPool::PostTask(
  330. FROM_HERE, {base::MayBlock()},
  331. base::BindOnce(&FileSelectHelper::GetFileTypesInThreadPool, this,
  332. std::move(params)));
  333. // Because this class returns notifications to the RenderViewHost, it is
  334. // difficult for callers to know how long to keep a reference to this
  335. // instance. We AddRef() here to keep the instance alive after we return
  336. // to the caller, until the last callback is received from the file dialog.
  337. // At that point, we must call RunFileChooserEnd().
  338. AddRef();
  339. }
  340. void FileSelectHelper::GetFileTypesInThreadPool(FileChooserParamsPtr params) {
  341. select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
  342. select_file_types_->allowed_paths =
  343. params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
  344. : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
  345. content::GetUIThreadTaskRunner({})->PostTask(
  346. FROM_HERE,
  347. base::BindOnce(&FileSelectHelper::GetSanitizedFilenameOnUIThread, this,
  348. std::move(params)));
  349. }
  350. void FileSelectHelper::GetSanitizedFilenameOnUIThread(
  351. FileChooserParamsPtr params) {
  352. if (AbortIfWebContentsDestroyed())
  353. return;
  354. auto* browser_context = static_cast<electron::ElectronBrowserContext*>(
  355. render_frame_host_->GetProcess()->GetBrowserContext());
  356. base::FilePath default_file_path =
  357. browser_context->prefs()
  358. ->GetFilePath(prefs::kSelectFileLastDirectory)
  359. .Append(params->default_file_name);
  360. RunFileChooserOnUIThread(default_file_path, std::move(params));
  361. }
  362. void FileSelectHelper::RunFileChooserOnUIThread(
  363. const base::FilePath& default_file_path,
  364. FileChooserParamsPtr params) {
  365. DCHECK(params);
  366. select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
  367. if (!select_file_dialog_.get())
  368. return;
  369. dialog_mode_ = params->mode;
  370. switch (params->mode) {
  371. case FileChooserParams::Mode::kOpen:
  372. dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
  373. break;
  374. case FileChooserParams::Mode::kOpenMultiple:
  375. dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
  376. break;
  377. case FileChooserParams::Mode::kUploadFolder:
  378. dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
  379. break;
  380. case FileChooserParams::Mode::kSave:
  381. dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
  382. break;
  383. default:
  384. // Prevent warning.
  385. dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
  386. NOTREACHED();
  387. }
  388. auto* web_contents = electron::api::WebContents::From(
  389. content::WebContents::FromRenderFrameHost(render_frame_host_));
  390. if (!web_contents || !web_contents->owner_window())
  391. return;
  392. // Never consider the current scope as hung. The hang watching deadline (if
  393. // any) is not valid since the user can take unbounded time to choose the
  394. // file.
  395. base::HangWatcher::InvalidateActiveExpectations();
  396. select_file_dialog_->SelectFile(
  397. dialog_type_, params->title, default_file_path, select_file_types_.get(),
  398. select_file_types_.get() && !select_file_types_->extensions.empty()
  399. ? 1
  400. : 0, // 1-based index of default extension to show.
  401. base::FilePath::StringType(),
  402. web_contents->owner_window()->GetNativeWindow(), NULL);
  403. select_file_types_.reset();
  404. }
  405. // This method is called when we receive the last callback from the file chooser
  406. // dialog or if the renderer was destroyed. Perform any cleanup and release the
  407. // reference we added in RunFileChooser().
  408. void FileSelectHelper::RunFileChooserEnd() {
  409. // If there are temporary files, then this instance needs to stick around
  410. // until web_contents_ is destroyed, so that this instance can delete the
  411. // temporary files.
  412. if (!temporary_files_.empty())
  413. return;
  414. if (listener_)
  415. listener_->FileSelectionCanceled();
  416. render_frame_host_ = nullptr;
  417. web_contents_ = nullptr;
  418. Release();
  419. }
  420. void FileSelectHelper::EnumerateDirectoryImpl(
  421. content::WebContents* tab,
  422. scoped_refptr<content::FileSelectListener> listener,
  423. const base::FilePath& path) {
  424. DCHECK(listener);
  425. DCHECK(!listener_);
  426. dialog_type_ = ui::SelectFileDialog::SELECT_NONE;
  427. web_contents_ = tab;
  428. listener_ = std::move(listener);
  429. // Because this class returns notifications to the RenderViewHost, it is
  430. // difficult for callers to know how long to keep a reference to this
  431. // instance. We AddRef() here to keep the instance alive after we return
  432. // to the caller, until the last callback is received from the enumeration
  433. // code. At that point, we must call EnumerateDirectoryEnd().
  434. AddRef();
  435. StartNewEnumeration(path);
  436. }
  437. // This method is called when we receive the last callback from the enumeration
  438. // code. Perform any cleanup and release the reference we added in
  439. // EnumerateDirectoryImpl().
  440. void FileSelectHelper::EnumerateDirectoryEnd() {
  441. Release();
  442. }
  443. void FileSelectHelper::RenderWidgetHostDestroyed(
  444. content::RenderWidgetHost* widget_host) {
  445. render_frame_host_ = nullptr;
  446. DCHECK(observation_.IsObservingSource(widget_host));
  447. observation_.Reset();
  448. }
  449. void FileSelectHelper::RenderFrameHostChanged(
  450. content::RenderFrameHost* old_host,
  451. content::RenderFrameHost* new_host) {
  452. if (!render_frame_host_)
  453. return;
  454. // The |old_host| and its children are now pending deletion. Do not give them
  455. // file access past this point.
  456. for (content::RenderFrameHost* host = render_frame_host_; host;
  457. host = host->GetParentOrOuterDocument()) {
  458. if (host == old_host) {
  459. render_frame_host_ = nullptr;
  460. return;
  461. }
  462. }
  463. }
  464. void FileSelectHelper::RenderFrameDeleted(
  465. content::RenderFrameHost* render_frame_host) {
  466. if (render_frame_host == render_frame_host_)
  467. render_frame_host_ = nullptr;
  468. }
  469. void FileSelectHelper::WebContentsDestroyed() {
  470. render_frame_host_ = nullptr;
  471. web_contents_ = nullptr;
  472. CleanUp();
  473. }
  474. // static
  475. bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) {
  476. // TODO(raymes): This only does some basic checks, extend to test more cases.
  477. // A 1 character accept type will always be invalid (either a "." in the case
  478. // of an extension or a "/" in the case of a MIME type).
  479. std::string unused;
  480. if (accept_type.length() <= 1 ||
  481. base::ToLowerASCII(accept_type) != accept_type ||
  482. base::TrimWhitespaceASCII(accept_type, base::TRIM_ALL, &unused) !=
  483. base::TRIM_NONE) {
  484. return false;
  485. }
  486. return true;
  487. }
  488. // static
  489. base::FilePath FileSelectHelper::GetSanitizedFileName(
  490. const base::FilePath& suggested_filename) {
  491. if (suggested_filename.empty())
  492. return base::FilePath();
  493. return net::GenerateFileName(
  494. GURL(), std::string(), std::string(), suggested_filename.AsUTF8Unsafe(),
  495. std::string(), l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
  496. }