file_select_helper.cc 20 KB

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