blob: 7be912fcdd60c504b04b45dd0c2ddccb2acdf7ec [file] [log] [blame]
[email protected]5626b0892012-02-20 14:46:581// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ba70d082010-09-10 16:54:492// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/file_select_helper.h"
6
avi6846aef2015-12-26 01:09:387#include <stddef.h>
8
[email protected]5ac950b2010-12-09 21:34:259#include <string>
[email protected]b7b4beb2013-07-09 14:06:5010#include <utility>
[email protected]5ac950b2010-12-09 21:34:2511
[email protected]9f054aa12011-09-29 19:13:4512#include "base/bind.h"
[email protected]25a4c1c2013-06-08 04:53:3613#include "base/files/file_enumerator.h"
thestig18dfb7a52014-08-26 10:44:0414#include "base/files/file_util.h"
dcheng4af48582016-04-19 00:29:3515#include "base/memory/ptr_util.h"
[email protected]1988e1c2013-02-28 20:27:4216#include "base/strings/string_split.h"
[email protected]d8830562013-06-10 22:01:5417#include "base/strings/string_util.h"
[email protected]112158af2013-06-07 23:46:1818#include "base/strings/utf_string_conversions.h"
shahriar.rostami4d55d8312016-02-25 22:44:2819#include "base/threading/worker_pool.h"
avi6846aef2015-12-26 01:09:3820#include "build/build_config.h"
hironoa4a32af22014-11-07 03:17:2921#include "chrome/browser/browser_process.h"
[email protected]ba70d082010-09-10 16:54:4922#include "chrome/browser/platform_util.h"
[email protected]8ecad5e2010-12-02 21:18:3323#include "chrome/browser/profiles/profile.h"
hironoa4a32af22014-11-07 03:17:2924#include "chrome/browser/profiles/profile_manager.h"
[email protected]d9898912011-04-15 21:10:0025#include "chrome/browser/ui/browser.h"
26#include "chrome/browser/ui/browser_list.h"
[email protected]6e1fcd12012-07-02 17:14:2027#include "chrome/browser/ui/chrome_select_file_policy.h"
[email protected]af39f002014-08-22 10:18:1828#include "chrome/grit/generated_resources.h"
[email protected]6a1c98e02012-10-24 21:49:4329#include "content/public/browser/browser_thread.h"
[email protected]6c2381d2011-10-19 02:52:5330#include "content/public/browser/notification_details.h"
31#include "content/public/browser/notification_source.h"
[email protected]0d6e9bd2011-10-18 04:29:1632#include "content/public/browser/notification_types.h"
[email protected]9c1662b2012-03-06 15:44:3333#include "content/public/browser/render_view_host.h"
avif9ab5d942015-10-15 14:05:4434#include "content/public/browser/render_widget_host.h"
[email protected]5626b0892012-02-20 14:46:5835#include "content/public/browser/render_widget_host_view.h"
rdevlin.croninbfbc11d92015-06-12 23:26:0836#include "content/public/browser/storage_partition.h"
[email protected]33f8ad52012-05-22 18:10:1337#include "content/public/browser/web_contents.h"
hirono570357bd2014-10-08 12:39:2738#include "content/public/common/file_chooser_file_info.h"
[email protected]8caadeb2011-11-22 02:45:2339#include "content/public/common/file_chooser_params.h"
asankaee261f32015-10-27 21:04:2840#include "net/base/filename_util.h"
[email protected]b3841c502011-03-09 01:21:3141#include "net/base/mime_util.h"
[email protected]c051a1b2011-01-21 23:30:1742#include "ui/base/l10n/l10n_util.h"
[email protected]4344a3c2013-01-17 23:49:2043#include "ui/shell_dialogs/selected_file_info.h"
[email protected]ba70d082010-09-10 16:54:4944
hirono275bfbe2014-10-21 01:46:1745#if defined(OS_CHROMEOS)
46#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
47#include "content/public/browser/site_instance.h"
48#endif
49
asankaee261f32015-10-27 21:04:2850#if defined(FULL_SAFE_BROWSING)
asankaef1f38722016-05-20 16:17:4151#include "chrome/browser/safe_browsing/download_protection_service.h"
52#include "chrome/browser/safe_browsing/safe_browsing_service.h"
asankaee261f32015-10-27 21:04:2853#endif
54
[email protected]631bb742011-11-02 11:29:3955using content::BrowserThread;
[email protected]33f8ad52012-05-22 18:10:1356using content::FileChooserParams;
[email protected]eaabba22012-03-07 15:02:1157using content::RenderViewHost;
58using content::RenderWidgetHost;
[email protected]ea049a02011-12-25 21:37:0959using content::WebContents;
[email protected]631bb742011-11-02 11:29:3960
[email protected]600ea402011-04-12 00:01:5161namespace {
62
63// There is only one file-selection happening at any given time,
64// so we allocate an enumeration ID for that purpose. All IDs from
65// the renderer must start at 0 and increase.
[email protected]459fba82011-10-13 02:48:5066const int kFileSelectEnumerationId = -1;
67
[email protected]53f04c82012-07-26 02:31:0968// Converts a list of FilePaths to a list of ui::SelectedFileInfo.
69std::vector<ui::SelectedFileInfo> FilePathListToSelectedFileInfoList(
[email protected]650b2d52013-02-10 03:41:4570 const std::vector<base::FilePath>& paths) {
[email protected]ddb034b2012-06-26 20:31:3971 std::vector<ui::SelectedFileInfo> selected_files;
[email protected]fb11b6a42012-03-14 07:25:1272 for (size_t i = 0; i < paths.size(); ++i) {
73 selected_files.push_back(
[email protected]53f04c82012-07-26 02:31:0974 ui::SelectedFileInfo(paths[i], paths[i]));
[email protected]fb11b6a42012-03-14 07:25:1275 }
76 return selected_files;
[email protected]600ea402011-04-12 00:01:5177}
78
erikchen22de64a32014-10-10 18:27:4379void DeleteFiles(const std::vector<base::FilePath>& paths) {
80 for (auto& file_path : paths)
81 base::DeleteFile(file_path, false);
82}
83
hironoa4a32af22014-11-07 03:17:2984bool IsValidProfile(Profile* profile) {
85 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
86 return g_browser_process->profile_manager()->IsValidProfile(profile);
87}
88
asankaef1f38722016-05-20 16:17:4189#if defined(FULL_SAFE_BROWSING)
90
91bool IsDownloadAllowedBySafeBrowsing(
92 safe_browsing::DownloadProtectionService::DownloadCheckResult result) {
93 using Result = safe_browsing::DownloadProtectionService::DownloadCheckResult;
94 switch (result) {
95 // Only allow downloads that are marked as SAFE or UNKNOWN by SafeBrowsing.
96 // All other types are going to be blocked. UNKNOWN could be the result of a
97 // failed safe browsing ping.
98 case Result::UNKNOWN:
99 case Result::SAFE:
100 return true;
101
102 case Result::DANGEROUS:
103 case Result::UNCOMMON:
104 case Result::DANGEROUS_HOST:
105 case Result::POTENTIALLY_UNWANTED:
106 return false;
107 }
108 NOTREACHED();
109 return false;
110}
111
112void InterpretSafeBrowsingVerdict(
113 const base::Callback<void(bool)>& recipient,
114 safe_browsing::DownloadProtectionService::DownloadCheckResult result) {
115 recipient.Run(IsDownloadAllowedBySafeBrowsing(result));
116}
117
118#endif
119
[email protected]fb11b6a42012-03-14 07:25:12120} // namespace
121
[email protected]485a5272011-04-12 00:49:29122struct FileSelectHelper::ActiveDirectoryEnumeration {
[email protected]d45f7512011-06-21 21:18:27123 ActiveDirectoryEnumeration() : rvh_(NULL) {}
[email protected]485a5272011-04-12 00:49:29124
dcheng4af48582016-04-19 00:29:35125 std::unique_ptr<DirectoryListerDispatchDelegate> delegate_;
126 std::unique_ptr<net::DirectoryLister> lister_;
[email protected]485a5272011-04-12 00:49:29127 RenderViewHost* rvh_;
[email protected]650b2d52013-02-10 03:41:45128 std::vector<base::FilePath> results_;
[email protected]485a5272011-04-12 00:49:29129};
130
[email protected]ba70d082010-09-10 16:54:49131FileSelectHelper::FileSelectHelper(Profile* profile)
132 : profile_(profile),
133 render_view_host_(NULL),
[email protected]ea049a02011-12-25 21:37:09134 web_contents_(NULL),
[email protected]ba70d082010-09-10 16:54:49135 select_file_dialog_(),
[email protected]9f054aa12011-09-29 19:13:45136 select_file_types_(),
[email protected]bfcf1e92013-07-11 04:37:25137 dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
138 dialog_mode_(FileChooserParams::Open) {
[email protected]ba70d082010-09-10 16:54:49139}
140
141FileSelectHelper::~FileSelectHelper() {
142 // There may be pending file dialogs, we need to tell them that we've gone
143 // away so they don't try and call back to us.
144 if (select_file_dialog_.get())
145 select_file_dialog_->ListenerDestroyed();
146
[email protected]600ea402011-04-12 00:01:51147 // Stop any pending directory enumeration, prevent a callback, and free
148 // allocated memory.
149 std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
150 for (iter = directory_enumerations_.begin();
151 iter != directory_enumerations_.end();
152 ++iter) {
[email protected]05a814182011-04-27 19:50:34153 iter->second->lister_.reset();
[email protected]600ea402011-04-12 00:01:51154 delete iter->second;
[email protected]ba70d082010-09-10 16:54:49155 }
156}
157
[email protected]23827ec2012-08-10 22:08:08158void FileSelectHelper::DirectoryListerDispatchDelegate::OnListFile(
159 const net::DirectoryLister::DirectoryListerData& data) {
160 parent_->OnListFile(id_, data);
161}
162
163void FileSelectHelper::DirectoryListerDispatchDelegate::OnListDone(int error) {
164 parent_->OnListDone(id_, error);
165}
166
[email protected]650b2d52013-02-10 03:41:45167void FileSelectHelper::FileSelected(const base::FilePath& path,
[email protected]ba70d082010-09-10 16:54:49168 int index, void* params) {
[email protected]53f04c82012-07-26 02:31:09169 FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
[email protected]fb11b6a42012-03-14 07:25:12170}
171
172void FileSelectHelper::FileSelectedWithExtraInfo(
[email protected]ddb034b2012-06-26 20:31:39173 const ui::SelectedFileInfo& file,
[email protected]fb11b6a42012-03-14 07:25:12174 int index,
175 void* params) {
hironoa4a32af22014-11-07 03:17:29176 if (IsValidProfile(profile_))
177 profile_->set_last_selected_directory(file.file_path.DirName());
[email protected]ba70d082010-09-10 16:54:49178
erikchen22de64a32014-10-10 18:27:43179 if (!render_view_host_) {
180 RunFileChooserEnd();
181 return;
182 }
183
[email protected]650b2d52013-02-10 03:41:45184 const base::FilePath& path = file.local_path;
[email protected]6bedbef2013-07-31 06:33:49185 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
[email protected]600ea402011-04-12 00:01:51186 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
[email protected]ba70d082010-09-10 16:54:49187 return;
188 }
189
[email protected]ddb034b2012-06-26 20:31:39190 std::vector<ui::SelectedFileInfo> files;
[email protected]fb11b6a42012-03-14 07:25:12191 files.push_back(file);
[email protected]9f054aa12011-09-29 19:13:45192
jam1c5a91492016-02-24 20:47:53193#if defined(OS_MACOSX)
erikchen22de64a32014-10-10 18:27:43194 content::BrowserThread::PostTask(
195 content::BrowserThread::FILE_USER_BLOCKING,
196 FROM_HERE,
197 base::Bind(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
198#else
199 NotifyRenderViewHostAndEnd(files);
jam1c5a91492016-02-24 20:47:53200#endif // defined(OS_MACOSX)
[email protected]ba70d082010-09-10 16:54:49201}
202
[email protected]650b2d52013-02-10 03:41:45203void FileSelectHelper::MultiFilesSelected(
204 const std::vector<base::FilePath>& files,
205 void* params) {
[email protected]ddb034b2012-06-26 20:31:39206 std::vector<ui::SelectedFileInfo> selected_files =
[email protected]53f04c82012-07-26 02:31:09207 FilePathListToSelectedFileInfoList(files);
208
[email protected]fb11b6a42012-03-14 07:25:12209 MultiFilesSelectedWithExtraInfo(selected_files, params);
210}
211
212void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
[email protected]ddb034b2012-06-26 20:31:39213 const std::vector<ui::SelectedFileInfo>& files,
[email protected]fb11b6a42012-03-14 07:25:12214 void* params) {
hironoa4a32af22014-11-07 03:17:29215 if (!files.empty() && IsValidProfile(profile_))
[email protected]53f04c82012-07-26 02:31:09216 profile_->set_last_selected_directory(files[0].file_path.DirName());
[email protected]ba70d082010-09-10 16:54:49217
jam1c5a91492016-02-24 20:47:53218#if defined(OS_MACOSX)
erikchen22de64a32014-10-10 18:27:43219 content::BrowserThread::PostTask(
220 content::BrowserThread::FILE_USER_BLOCKING,
221 FROM_HERE,
222 base::Bind(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
223#else
224 NotifyRenderViewHostAndEnd(files);
jam1c5a91492016-02-24 20:47:53225#endif // defined(OS_MACOSX)
[email protected]ba70d082010-09-10 16:54:49226}
227
228void FileSelectHelper::FileSelectionCanceled(void* params) {
erikchen22de64a32014-10-10 18:27:43229 NotifyRenderViewHostAndEnd(std::vector<ui::SelectedFileInfo>());
[email protected]ba70d082010-09-10 16:54:49230}
231
[email protected]650b2d52013-02-10 03:41:45232void FileSelectHelper::StartNewEnumeration(const base::FilePath& path,
[email protected]600ea402011-04-12 00:01:51233 int request_id,
234 RenderViewHost* render_view_host) {
dcheng4af48582016-04-19 00:29:35235 std::unique_ptr<ActiveDirectoryEnumeration> entry(
236 new ActiveDirectoryEnumeration);
[email protected]600ea402011-04-12 00:01:51237 entry->rvh_ = render_view_host;
238 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
olli.syrjala3396ad32015-04-24 07:56:41239 entry->lister_.reset(new net::DirectoryLister(
240 path, net::DirectoryLister::NO_SORT_RECURSIVE, entry->delegate_.get()));
shahriar.rostamia8c06daf2016-02-12 00:07:04241 if (!entry->lister_->Start(base::WorkerPool::GetTaskRunner(true).get())) {
[email protected]600ea402011-04-12 00:01:51242 if (request_id == kFileSelectEnumerationId)
243 FileSelectionCanceled(NULL);
244 else
245 render_view_host->DirectoryEnumerationFinished(request_id,
246 entry->results_);
247 } else {
248 directory_enumerations_[request_id] = entry.release();
249 }
[email protected]ba70d082010-09-10 16:54:49250}
251
252void FileSelectHelper::OnListFile(
[email protected]600ea402011-04-12 00:01:51253 int id,
[email protected]ba70d082010-09-10 16:54:49254 const net::DirectoryLister::DirectoryListerData& data) {
[email protected]600ea402011-04-12 00:01:51255 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
256
[email protected]2c718a0d2014-03-24 02:42:22257 // Directory upload only cares about files.
[email protected]25a4c1c2013-06-08 04:53:36258 if (data.info.IsDirectory())
[email protected]2c718a0d2014-03-24 02:42:22259 return;
260
261 entry->results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49262}
263
[email protected]600ea402011-04-12 00:01:51264void FileSelectHelper::OnListDone(int id, int error) {
265 // This entry needs to be cleaned up when this function is done.
dcheng4af48582016-04-19 00:29:35266 std::unique_ptr<ActiveDirectoryEnumeration> entry(
267 directory_enumerations_[id]);
[email protected]600ea402011-04-12 00:01:51268 directory_enumerations_.erase(id);
269 if (!entry->rvh_)
[email protected]ba70d082010-09-10 16:54:49270 return;
[email protected]ba70d082010-09-10 16:54:49271 if (error) {
272 FileSelectionCanceled(NULL);
273 return;
274 }
[email protected]fb11b6a42012-03-14 07:25:12275
[email protected]ddb034b2012-06-26 20:31:39276 std::vector<ui::SelectedFileInfo> selected_files =
[email protected]53f04c82012-07-26 02:31:09277 FilePathListToSelectedFileInfoList(entry->results_);
[email protected]fb11b6a42012-03-14 07:25:12278
hirono275bfbe2014-10-21 01:46:17279 if (id == kFileSelectEnumerationId) {
280 NotifyRenderViewHostAndEnd(selected_files);
281 } else {
[email protected]600ea402011-04-12 00:01:51282 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
hirono275bfbe2014-10-21 01:46:17283 EnumerateDirectoryEnd();
284 }
[email protected]ba70d082010-09-10 16:54:49285}
286
erikchen22de64a32014-10-10 18:27:43287void FileSelectHelper::NotifyRenderViewHostAndEnd(
288 const std::vector<ui::SelectedFileInfo>& files) {
hirono275bfbe2014-10-21 01:46:17289 if (!render_view_host_) {
290 RunFileChooserEnd();
291 return;
292 }
293
294#if defined(OS_CHROMEOS)
295 if (!files.empty()) {
hironoa4a32af22014-11-07 03:17:29296 if (!IsValidProfile(profile_)) {
297 RunFileChooserEnd();
298 return;
299 }
hirono275bfbe2014-10-21 01:46:17300 // Converts |files| into FileChooserFileInfo with handling of non-native
301 // files.
rdevlin.croninbfbc11d92015-06-12 23:26:08302 storage::FileSystemContext* file_system_context =
303 content::BrowserContext::GetStoragePartition(
304 profile_, render_view_host_->GetSiteInstance())->
305 GetFileSystemContext();
hirono275bfbe2014-10-21 01:46:17306 file_manager::util::ConvertSelectedFileInfoListToFileChooserFileInfoList(
rdevlin.croninbfbc11d92015-06-12 23:26:08307 file_system_context,
hirono275bfbe2014-10-21 01:46:17308 web_contents_->GetSiteInstance()->GetSiteURL(),
309 files,
310 base::Bind(
hironocf350cb2014-10-23 08:37:18311 &FileSelectHelper::NotifyRenderViewHostAndEndAfterConversion,
hirono275bfbe2014-10-21 01:46:17312 this));
313 return;
314 }
315#endif // defined(OS_CHROMEOS)
316
317 std::vector<content::FileChooserFileInfo> chooser_files;
318 for (const auto& file : files) {
319 content::FileChooserFileInfo chooser_file;
320 chooser_file.file_path = file.local_path;
321 chooser_file.display_name = file.display_name;
322 chooser_files.push_back(chooser_file);
323 }
hironocf350cb2014-10-23 08:37:18324
325 NotifyRenderViewHostAndEndAfterConversion(chooser_files);
326}
327
328void FileSelectHelper::NotifyRenderViewHostAndEndAfterConversion(
329 const std::vector<content::FileChooserFileInfo>& list) {
330 if (render_view_host_)
331 render_view_host_->FilesSelectedInChooser(list, dialog_mode_);
erikchen22de64a32014-10-10 18:27:43332
333 // No members should be accessed from here on.
334 RunFileChooserEnd();
335}
336
337void FileSelectHelper::DeleteTemporaryFiles() {
338 BrowserThread::PostTask(BrowserThread::FILE,
339 FROM_HERE,
340 base::Bind(&DeleteFiles, temporary_files_));
341 temporary_files_.clear();
342}
343
lazyboy1cf60af2015-05-15 22:17:05344void FileSelectHelper::CleanUpOnRenderViewHostChange() {
345 if (!temporary_files_.empty()) {
346 DeleteTemporaryFiles();
347
348 // Now that the temporary files have been scheduled for deletion, there
349 // is no longer any reason to keep this instance around.
350 Release();
351 }
352}
353
dcheng4af48582016-04-19 00:29:35354std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
[email protected]92f54082012-07-31 01:43:14355FileSelectHelper::GetFileTypesFromAcceptType(
[email protected]d2065e062013-12-12 23:49:52356 const std::vector<base::string16>& accept_types) {
dcheng4af48582016-04-19 00:29:35357 std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> base_file_type(
[email protected]479cce782012-09-15 20:15:53358 new ui::SelectFileDialog::FileTypeInfo());
[email protected]ba70d082010-09-10 16:54:49359 if (accept_types.empty())
dchenge73d8520c2015-12-27 01:19:09360 return base_file_type;
[email protected]ba70d082010-09-10 16:54:49361
[email protected]ba70d082010-09-10 16:54:49362 // Create FileTypeInfo and pre-allocate for the first extension list.
dcheng4af48582016-04-19 00:29:35363 std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> file_type(
[email protected]479cce782012-09-15 20:15:53364 new ui::SelectFileDialog::FileTypeInfo(*base_file_type));
[email protected]ba70d082010-09-10 16:54:49365 file_type->include_all_files = true;
366 file_type->extensions.resize(1);
[email protected]650b2d52013-02-10 03:41:45367 std::vector<base::FilePath::StringType>* extensions =
368 &file_type->extensions.back();
[email protected]ba70d082010-09-10 16:54:49369
[email protected]f9a4c41a2012-05-30 00:05:32370 // Find the corresponding extensions.
[email protected]ba70d082010-09-10 16:54:49371 int valid_type_count = 0;
372 int description_id = 0;
[email protected]3314c2b12011-11-02 08:05:46373 for (size_t i = 0; i < accept_types.size(); ++i) {
[email protected]74f778e2014-03-14 21:11:46374 std::string ascii_type = base::UTF16ToASCII(accept_types[i]);
[email protected]f9a4c41a2012-05-30 00:05:32375 if (!IsAcceptTypeValid(ascii_type))
376 continue;
[email protected]ba70d082010-09-10 16:54:49377
378 size_t old_extension_size = extensions->size();
[email protected]f9a4c41a2012-05-30 00:05:32379 if (ascii_type[0] == '.') {
380 // If the type starts with a period it is assumed to be a file extension
381 // so we just have to add it to the list.
[email protected]650b2d52013-02-10 03:41:45382 base::FilePath::StringType ext(ascii_type.begin(), ascii_type.end());
[email protected]f9a4c41a2012-05-30 00:05:32383 extensions->push_back(ext.substr(1));
[email protected]ba70d082010-09-10 16:54:49384 } else {
[email protected]4a66fa0e2012-09-10 06:45:20385 if (ascii_type == "image/*")
386 description_id = IDS_IMAGE_FILES;
387 else if (ascii_type == "audio/*")
388 description_id = IDS_AUDIO_FILES;
389 else if (ascii_type == "video/*")
390 description_id = IDS_VIDEO_FILES;
391
[email protected]f9a4c41a2012-05-30 00:05:32392 net::GetExtensionsForMimeType(ascii_type, extensions);
[email protected]ba70d082010-09-10 16:54:49393 }
394
395 if (extensions->size() > old_extension_size)
396 valid_type_count++;
397 }
398
[email protected]cbcd12ed2010-12-16 23:42:57399 // If no valid extension is added, bail out.
400 if (valid_type_count == 0)
dchenge73d8520c2015-12-27 01:19:09401 return base_file_type;
[email protected]cbcd12ed2010-12-16 23:42:57402
[email protected]ba70d082010-09-10 16:54:49403 // Use a generic description "Custom Files" if either of the following is
404 // true:
405 // 1) There're multiple types specified, like "audio/*,video/*"
406 // 2) There're multiple extensions for a MIME type without parameter, like
407 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
408 // dialog uses the first extension in the list to form the description,
409 // like "EHTML Files". This is not what we want.
410 if (valid_type_count > 1 ||
411 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
412 description_id = IDS_CUSTOM_FILES;
413
414 if (description_id) {
415 file_type->extension_description_overrides.push_back(
416 l10n_util::GetStringUTF16(description_id));
417 }
418
dchenge73d8520c2015-12-27 01:19:09419 return file_type;
[email protected]ba70d082010-09-10 16:54:49420}
421
[email protected]33f8ad52012-05-22 18:10:13422// static
423void FileSelectHelper::RunFileChooser(content::WebContents* tab,
424 const FileChooserParams& params) {
425 Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
426 // FileSelectHelper will keep itself alive until it sends the result message.
427 scoped_refptr<FileSelectHelper> file_select_helper(
428 new FileSelectHelper(profile));
asanka1cbf49812015-11-03 18:28:15429 file_select_helper->RunFileChooser(
430 tab->GetRenderViewHost(), tab,
dcheng4af48582016-04-19 00:29:35431 base::WrapUnique(new content::FileChooserParams(params)));
[email protected]33f8ad52012-05-22 18:10:13432}
433
434// static
435void FileSelectHelper::EnumerateDirectory(content::WebContents* tab,
436 int request_id,
[email protected]650b2d52013-02-10 03:41:45437 const base::FilePath& path) {
[email protected]33f8ad52012-05-22 18:10:13438 Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
439 // FileSelectHelper will keep itself alive until it sends the result message.
440 scoped_refptr<FileSelectHelper> file_select_helper(
441 new FileSelectHelper(profile));
442 file_select_helper->EnumerateDirectory(
443 request_id, tab->GetRenderViewHost(), path);
444}
445
dcheng4af48582016-04-19 00:29:35446void FileSelectHelper::RunFileChooser(
447 RenderViewHost* render_view_host,
448 content::WebContents* web_contents,
449 std::unique_ptr<FileChooserParams> params) {
[email protected]ba70d082010-09-10 16:54:49450 DCHECK(!render_view_host_);
[email protected]ea049a02011-12-25 21:37:09451 DCHECK(!web_contents_);
asanka1cbf49812015-11-03 18:28:15452 DCHECK(params->default_file_name.empty() ||
453 params->mode == FileChooserParams::Save)
asankaee261f32015-10-27 21:04:28454 << "The default_file_name parameter should only be specified for Save "
455 "file choosers";
asanka1cbf49812015-11-03 18:28:15456 DCHECK(params->default_file_name == params->default_file_name.BaseName())
asankaee261f32015-10-27 21:04:28457 << "The default_file_name parameter should not contain path separators";
458
[email protected]ba70d082010-09-10 16:54:49459 render_view_host_ = render_view_host;
[email protected]ea049a02011-12-25 21:37:09460 web_contents_ = web_contents;
[email protected]ba70d082010-09-10 16:54:49461 notification_registrar_.RemoveAll();
lazyboy1cf60af2015-05-15 22:17:05462 content::WebContentsObserver::Observe(web_contents_);
[email protected]432115822011-07-10 15:52:27463 notification_registrar_.Add(
avif9ab5d942015-10-15 14:05:44464 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
465 content::Source<RenderWidgetHost>(render_view_host_->GetWidget()));
[email protected]9f054aa12011-09-29 19:13:45466
467 BrowserThread::PostTask(
468 BrowserThread::FILE, FROM_HERE,
asanka1cbf49812015-11-03 18:28:15469 base::Bind(&FileSelectHelper::GetFileTypesOnFileThread, this,
470 base::Passed(&params)));
[email protected]9f054aa12011-09-29 19:13:45471
472 // Because this class returns notifications to the RenderViewHost, it is
473 // difficult for callers to know how long to keep a reference to this
474 // instance. We AddRef() here to keep the instance alive after we return
475 // to the caller, until the last callback is received from the file dialog.
476 // At that point, we must call RunFileChooserEnd().
477 AddRef();
478}
479
asanka1cbf49812015-11-03 18:28:15480void FileSelectHelper::GetFileTypesOnFileThread(
dcheng4af48582016-04-19 00:29:35481 std::unique_ptr<FileChooserParams> params) {
asanka1cbf49812015-11-03 18:28:15482 select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
hirono4d4d3392016-02-04 05:03:50483 select_file_types_->allowed_paths =
484 params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
485 : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
[email protected]9f054aa12011-09-29 19:13:45486
487 BrowserThread::PostTask(
488 BrowserThread::UI, FROM_HERE,
asanka1cbf49812015-11-03 18:28:15489 base::Bind(&FileSelectHelper::GetSanitizedFilenameOnUIThread, this,
490 base::Passed(&params)));
[email protected]9f054aa12011-09-29 19:13:45491}
492
asanka1cbf49812015-11-03 18:28:15493void FileSelectHelper::GetSanitizedFilenameOnUIThread(
dcheng4af48582016-04-19 00:29:35494 std::unique_ptr<FileChooserParams> params) {
asanka1cbf49812015-11-03 18:28:15495 base::FilePath default_file_path = profile_->last_selected_directory().Append(
496 GetSanitizedFileName(params->default_file_name));
asanka1cbf49812015-11-03 18:28:15497#if defined(FULL_SAFE_BROWSING)
asankaef1f38722016-05-20 16:17:41498 CheckDownloadRequestWithSafeBrowsing(default_file_path, std::move(params));
499#else
dchenge73d8520c2015-12-27 01:19:09500 RunFileChooserOnUIThread(default_file_path, std::move(params));
asankaef1f38722016-05-20 16:17:41501#endif
asanka1cbf49812015-11-03 18:28:15502}
503
504#if defined(FULL_SAFE_BROWSING)
asankaef1f38722016-05-20 16:17:41505void FileSelectHelper::CheckDownloadRequestWithSafeBrowsing(
506 const base::FilePath& default_file_path,
507 std::unique_ptr<FileChooserParams> params) {
508 safe_browsing::SafeBrowsingService* sb_service =
509 g_browser_process->safe_browsing_service();
510
511 if (!sb_service || !sb_service->download_protection_service() ||
512 !sb_service->download_protection_service()->enabled()) {
513 RunFileChooserOnUIThread(default_file_path, std::move(params));
asanka1cbf49812015-11-03 18:28:15514 return;
515 }
516
asankaef1f38722016-05-20 16:17:41517 std::vector<base::FilePath::StringType> alternate_extensions;
518 if (select_file_types_) {
519 for (const auto& extensions_list : select_file_types_->extensions) {
520 for (const auto& extension_in_list : extensions_list) {
521 base::FilePath::StringType extension =
522 default_file_path.ReplaceExtension(extension_in_list)
523 .FinalExtension();
524 alternate_extensions.push_back(extension);
525 }
526 }
527 }
528
529 GURL requestor_url = params->requestor;
530 sb_service->download_protection_service()->CheckPPAPIDownloadRequest(
531 requestor_url, default_file_path, alternate_extensions,
532 base::Bind(&InterpretSafeBrowsingVerdict,
533 base::Bind(&FileSelectHelper::ProceedWithSafeBrowsingVerdict,
534 this, default_file_path, base::Passed(&params))));
535}
536
537void FileSelectHelper::ProceedWithSafeBrowsingVerdict(
538 const base::FilePath& default_file_path,
539 std::unique_ptr<content::FileChooserParams> params,
540 bool allowed_by_safe_browsing) {
541 if (!allowed_by_safe_browsing) {
542 NotifyRenderViewHostAndEnd(std::vector<ui::SelectedFileInfo>());
543 return;
544 }
545 RunFileChooserOnUIThread(default_file_path, std::move(params));
asanka1cbf49812015-11-03 18:28:15546}
547#endif
548
[email protected]9f054aa12011-09-29 19:13:45549void FileSelectHelper::RunFileChooserOnUIThread(
asanka1cbf49812015-11-03 18:28:15550 const base::FilePath& default_file_path,
dcheng4af48582016-04-19 00:29:35551 std::unique_ptr<FileChooserParams> params) {
asanka1cbf49812015-11-03 18:28:15552 DCHECK(params);
avi20e83192015-11-16 20:11:48553 if (!render_view_host_ || !web_contents_ || !IsValidProfile(profile_) ||
554 !render_view_host_->GetWidget()->GetView()) {
[email protected]b95b08d2011-12-15 20:23:16555 // If the renderer was destroyed before we started, just cancel the
556 // operation.
557 RunFileChooserEnd();
[email protected]9f054aa12011-09-29 19:13:45558 return;
[email protected]b95b08d2011-12-15 20:23:16559 }
[email protected]ba70d082010-09-10 16:54:49560
[email protected]92f54082012-07-31 01:43:14561 select_file_dialog_ = ui::SelectFileDialog::Create(
[email protected]6e1fcd12012-07-02 17:14:20562 this, new ChromeSelectFilePolicy(web_contents_));
dcheng319a21952014-08-26 22:52:40563 if (!select_file_dialog_.get())
[email protected]6910cb242014-03-20 14:33:01564 return;
[email protected]ba70d082010-09-10 16:54:49565
asanka1cbf49812015-11-03 18:28:15566 dialog_mode_ = params->mode;
567 switch (params->mode) {
[email protected]33f8ad52012-05-22 18:10:13568 case FileChooserParams::Open:
[email protected]92f54082012-07-31 01:43:14569 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
[email protected]ba70d082010-09-10 16:54:49570 break;
[email protected]33f8ad52012-05-22 18:10:13571 case FileChooserParams::OpenMultiple:
[email protected]92f54082012-07-31 01:43:14572 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
[email protected]ba70d082010-09-10 16:54:49573 break;
[email protected]6bedbef2013-07-31 06:33:49574 case FileChooserParams::UploadFolder:
575 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
[email protected]ba70d082010-09-10 16:54:49576 break;
[email protected]33f8ad52012-05-22 18:10:13577 case FileChooserParams::Save:
[email protected]92f54082012-07-31 01:43:14578 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
[email protected]ba70d082010-09-10 16:54:49579 break;
580 default:
[email protected]92f54082012-07-31 01:43:14581 // Prevent warning.
582 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
[email protected]ba70d082010-09-10 16:54:49583 NOTREACHED();
584 }
[email protected]4e9149a2012-08-15 20:43:59585
avif9ab5d942015-10-15 14:05:44586 gfx::NativeWindow owning_window = platform_util::GetTopLevel(
587 render_view_host_->GetWidget()->GetView()->GetNativeView());
[email protected]d9898912011-04-15 21:10:00588
[email protected]2d02a2002012-09-18 21:47:56589#if defined(OS_ANDROID)
590 // Android needs the original MIME types and an additional capture value.
[email protected]d2065e062013-12-12 23:49:52591 std::pair<std::vector<base::string16>, bool> accept_types =
asanka1cbf49812015-11-03 18:28:15592 std::make_pair(params->accept_types, params->capture);
[email protected]2d02a2002012-09-18 21:47:56593#endif
594
[email protected]9f054aa12011-09-29 19:13:45595 select_file_dialog_->SelectFile(
asanka1cbf49812015-11-03 18:28:15596 dialog_type_, params->title, default_file_path, select_file_types_.get(),
[email protected]007b3f82013-04-09 08:46:45597 select_file_types_.get() && !select_file_types_->extensions.empty()
598 ? 1
599 : 0, // 1-based index of default extension to show.
600 base::FilePath::StringType(),
[email protected]9f054aa12011-09-29 19:13:45601 owning_window,
[email protected]b8452fa2012-06-15 01:41:41602#if defined(OS_ANDROID)
[email protected]2d02a2002012-09-18 21:47:56603 &accept_types);
[email protected]b8452fa2012-06-15 01:41:41604#else
[email protected]9f054aa12011-09-29 19:13:45605 NULL);
[email protected]b8452fa2012-06-15 01:41:41606#endif
[email protected]9f054aa12011-09-29 19:13:45607
608 select_file_types_.reset();
609}
610
611// This method is called when we receive the last callback from the file
612// chooser dialog. Perform any cleanup and release the reference we added
613// in RunFileChooser().
614void FileSelectHelper::RunFileChooserEnd() {
erikchen22de64a32014-10-10 18:27:43615 // If there are temporary files, then this instance needs to stick around
616 // until web_contents_ is destroyed, so that this instance can delete the
617 // temporary files.
618 if (!temporary_files_.empty())
619 return;
620
[email protected]9f054aa12011-09-29 19:13:45621 render_view_host_ = NULL;
[email protected]ea049a02011-12-25 21:37:09622 web_contents_ = NULL;
[email protected]9f054aa12011-09-29 19:13:45623 Release();
[email protected]ba70d082010-09-10 16:54:49624}
625
[email protected]600ea402011-04-12 00:01:51626void FileSelectHelper::EnumerateDirectory(int request_id,
627 RenderViewHost* render_view_host,
[email protected]650b2d52013-02-10 03:41:45628 const base::FilePath& path) {
[email protected]9f054aa12011-09-29 19:13:45629
630 // Because this class returns notifications to the RenderViewHost, it is
631 // difficult for callers to know how long to keep a reference to this
632 // instance. We AddRef() here to keep the instance alive after we return
633 // to the caller, until the last callback is received from the enumeration
634 // code. At that point, we must call EnumerateDirectoryEnd().
635 AddRef();
[email protected]600ea402011-04-12 00:01:51636 StartNewEnumeration(path, request_id, render_view_host);
637}
638
[email protected]9f054aa12011-09-29 19:13:45639// This method is called when we receive the last callback from the enumeration
640// code. Perform any cleanup and release the reference we added in
641// EnumerateDirectory().
642void FileSelectHelper::EnumerateDirectoryEnd() {
643 Release();
644}
645
[email protected]432115822011-07-10 15:52:27646void FileSelectHelper::Observe(int type,
[email protected]6c2381d2011-10-19 02:52:53647 const content::NotificationSource& source,
648 const content::NotificationDetails& details) {
[email protected]9f054aa12011-09-29 19:13:45649 switch (type) {
650 case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
[email protected]6c2381d2011-10-19 02:52:53651 DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
avif9ab5d942015-10-15 14:05:44652 render_view_host_->GetWidget());
[email protected]9f054aa12011-09-29 19:13:45653 render_view_host_ = NULL;
654 break;
655 }
[email protected]9f054aa12011-09-29 19:13:45656 default:
657 NOTREACHED();
658 }
[email protected]ba70d082010-09-10 16:54:49659}
[email protected]f9a4c41a2012-05-30 00:05:32660
lazyboy1cf60af2015-05-15 22:17:05661void FileSelectHelper::RenderViewHostChanged(RenderViewHost* old_host,
662 RenderViewHost* new_host) {
663 CleanUpOnRenderViewHostChange();
664}
665
666void FileSelectHelper::WebContentsDestroyed() {
667 web_contents_ = nullptr;
668 CleanUpOnRenderViewHostChange();
669}
670
[email protected]f9a4c41a2012-05-30 00:05:32671// static
672bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) {
673 // TODO(raymes): This only does some basic checks, extend to test more cases.
674 // A 1 character accept type will always be invalid (either a "." in the case
675 // of an extension or a "/" in the case of a MIME type).
676 std::string unused;
677 if (accept_type.length() <= 1 ||
brettwfce8d192015-08-10 19:07:51678 base::ToLowerASCII(accept_type) != accept_type ||
[email protected]8af69c6c2014-03-03 19:05:31679 base::TrimWhitespaceASCII(accept_type, base::TRIM_ALL, &unused) !=
680 base::TRIM_NONE) {
[email protected]f9a4c41a2012-05-30 00:05:32681 return false;
682 }
683 return true;
684}
asankaee261f32015-10-27 21:04:28685
686// static
687base::FilePath FileSelectHelper::GetSanitizedFileName(
688 const base::FilePath& suggested_filename) {
689 if (suggested_filename.empty())
690 return base::FilePath();
691 return net::GenerateFileName(
692 GURL(), std::string(), std::string(), suggested_filename.AsUTF8Unsafe(),
693 std::string(), l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
694}