blob: 24805016a813679419c88d8fa8c15fdb35c6ff74 [file] [log] [blame]
[email protected]b3841c502011-03-09 01:21:311// Copyright (c) 2011 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
[email protected]5ac950b2010-12-09 21:34:257#include <string>
8
[email protected]ba70d082010-09-10 16:54:499#include "base/file_util.h"
10#include "base/string_split.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
[email protected]ba70d082010-09-10 16:54:4913#include "chrome/browser/platform_util.h"
[email protected]8ecad5e2010-12-02 21:18:3314#include "chrome/browser/profiles/profile.h"
[email protected]600ea402011-04-12 00:01:5115#include "content/browser/child_process_security_policy.h"
16#include "content/browser/renderer_host/render_process_host.h"
[email protected]5de634712011-03-02 00:20:1917#include "content/browser/renderer_host/render_view_host.h"
18#include "content/browser/renderer_host/render_widget_host_view.h"
[email protected]aaed2522011-03-11 18:50:5419#include "content/browser/tab_contents/tab_contents.h"
[email protected]d9898912011-04-15 21:10:0020#include "chrome/browser/ui/browser.h"
21#include "chrome/browser/ui/browser_list.h"
[email protected]b3841c502011-03-09 01:21:3122#include "content/common/notification_details.h"
23#include "content/common/notification_source.h"
[email protected]0aed2f52011-03-23 18:06:3624#include "content/common/view_messages.h"
[email protected]ba70d082010-09-10 16:54:4925#include "grit/generated_resources.h"
[email protected]b3841c502011-03-09 01:21:3126#include "net/base/mime_util.h"
[email protected]c051a1b2011-01-21 23:30:1727#include "ui/base/l10n/l10n_util.h"
[email protected]ba70d082010-09-10 16:54:4928
[email protected]600ea402011-04-12 00:01:5129namespace {
30
31// There is only one file-selection happening at any given time,
32// so we allocate an enumeration ID for that purpose. All IDs from
33// the renderer must start at 0 and increase.
34static const int kFileSelectEnumerationId = -1;
35}
36
[email protected]485a5272011-04-12 00:49:2937struct FileSelectHelper::ActiveDirectoryEnumeration {
[email protected]d45f7512011-06-21 21:18:2738 ActiveDirectoryEnumeration() : rvh_(NULL) {}
[email protected]485a5272011-04-12 00:49:2939
40 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
[email protected]05a814182011-04-27 19:50:3441 scoped_ptr<net::DirectoryLister> lister_;
[email protected]485a5272011-04-12 00:49:2942 RenderViewHost* rvh_;
43 std::vector<FilePath> results_;
44};
45
[email protected]ba70d082010-09-10 16:54:4946FileSelectHelper::FileSelectHelper(Profile* profile)
47 : profile_(profile),
48 render_view_host_(NULL),
49 select_file_dialog_(),
50 dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
51}
52
53FileSelectHelper::~FileSelectHelper() {
54 // There may be pending file dialogs, we need to tell them that we've gone
55 // away so they don't try and call back to us.
56 if (select_file_dialog_.get())
57 select_file_dialog_->ListenerDestroyed();
58
[email protected]600ea402011-04-12 00:01:5159 // Stop any pending directory enumeration, prevent a callback, and free
60 // allocated memory.
61 std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
62 for (iter = directory_enumerations_.begin();
63 iter != directory_enumerations_.end();
64 ++iter) {
[email protected]05a814182011-04-27 19:50:3465 iter->second->lister_.reset();
[email protected]600ea402011-04-12 00:01:5166 delete iter->second;
[email protected]ba70d082010-09-10 16:54:4967 }
68}
69
70void FileSelectHelper::FileSelected(const FilePath& path,
71 int index, void* params) {
72 if (!render_view_host_)
73 return;
74
75 profile_->set_last_selected_directory(path.DirName());
76
77 if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
[email protected]600ea402011-04-12 00:01:5178 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
[email protected]ba70d082010-09-10 16:54:4979 return;
80 }
81
82 std::vector<FilePath> files;
83 files.push_back(path);
84 render_view_host_->FilesSelectedInChooser(files);
85 // We are done with this showing of the dialog.
86 render_view_host_ = NULL;
87}
88
89void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
90 void* params) {
91 if (!files.empty())
92 profile_->set_last_selected_directory(files[0].DirName());
93 if (!render_view_host_)
94 return;
95
96 render_view_host_->FilesSelectedInChooser(files);
97 // We are done with this showing of the dialog.
98 render_view_host_ = NULL;
99}
100
101void FileSelectHelper::FileSelectionCanceled(void* params) {
102 if (!render_view_host_)
103 return;
104
105 // If the user cancels choosing a file to upload we pass back an
106 // empty vector.
107 render_view_host_->FilesSelectedInChooser(std::vector<FilePath>());
108
109 // We are done with this showing of the dialog.
110 render_view_host_ = NULL;
111}
112
[email protected]600ea402011-04-12 00:01:51113void FileSelectHelper::StartNewEnumeration(const FilePath& path,
114 int request_id,
115 RenderViewHost* render_view_host) {
116 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
117 entry->rvh_ = render_view_host;
118 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
[email protected]05a814182011-04-27 19:50:34119 entry->lister_.reset(new net::DirectoryLister(path,
120 true,
121 net::DirectoryLister::NO_SORT,
122 entry->delegate_.get()));
[email protected]600ea402011-04-12 00:01:51123 if (!entry->lister_->Start()) {
124 if (request_id == kFileSelectEnumerationId)
125 FileSelectionCanceled(NULL);
126 else
127 render_view_host->DirectoryEnumerationFinished(request_id,
128 entry->results_);
129 } else {
130 directory_enumerations_[request_id] = entry.release();
131 }
[email protected]ba70d082010-09-10 16:54:49132}
133
134void FileSelectHelper::OnListFile(
[email protected]600ea402011-04-12 00:01:51135 int id,
[email protected]ba70d082010-09-10 16:54:49136 const net::DirectoryLister::DirectoryListerData& data) {
[email protected]600ea402011-04-12 00:01:51137 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
138
[email protected]9897e092011-02-04 22:09:11139 // Directory upload returns directories via a "." file, so that
140 // empty directories are included. This util call just checks
[email protected]ba70d082010-09-10 16:54:49141 // the flags in the structure; there's no file I/O going on.
142 if (file_util::FileEnumerator::IsDirectory(data.info))
[email protected]600ea402011-04-12 00:01:51143 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
[email protected]9897e092011-02-04 22:09:11144 else
[email protected]600ea402011-04-12 00:01:51145 entry->results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49146}
147
[email protected]600ea402011-04-12 00:01:51148void FileSelectHelper::OnListDone(int id, int error) {
149 // This entry needs to be cleaned up when this function is done.
150 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
151 directory_enumerations_.erase(id);
152 if (!entry->rvh_)
[email protected]ba70d082010-09-10 16:54:49153 return;
[email protected]ba70d082010-09-10 16:54:49154 if (error) {
155 FileSelectionCanceled(NULL);
156 return;
157 }
[email protected]600ea402011-04-12 00:01:51158 if (id == kFileSelectEnumerationId)
159 entry->rvh_->FilesSelectedInChooser(entry->results_);
160 else
161 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
[email protected]ba70d082010-09-10 16:54:49162}
163
164SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
165 const string16& accept_types) {
166 if (accept_types.empty())
167 return NULL;
168
169 // Split the accept-type string on commas.
170 std::vector<string16> mime_types;
171 base::SplitStringUsingSubstr(accept_types, ASCIIToUTF16(","), &mime_types);
172 if (mime_types.empty())
173 return NULL;
174
175 // Create FileTypeInfo and pre-allocate for the first extension list.
176 scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
177 new SelectFileDialog::FileTypeInfo());
178 file_type->include_all_files = true;
179 file_type->extensions.resize(1);
180 std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
181
182 // Find the correspondinge extensions.
183 int valid_type_count = 0;
184 int description_id = 0;
185 for (size_t i = 0; i < mime_types.size(); ++i) {
186 string16 mime_type = mime_types[i];
187 std::string ascii_mime_type = StringToLowerASCII(UTF16ToASCII(mime_type));
188
189 TrimWhitespace(ascii_mime_type, TRIM_ALL, &ascii_mime_type);
190 if (ascii_mime_type.empty())
191 continue;
192
193 size_t old_extension_size = extensions->size();
194 if (ascii_mime_type == "image/*") {
195 description_id = IDS_IMAGE_FILES;
196 net::GetImageExtensions(extensions);
197 } else if (ascii_mime_type == "audio/*") {
198 description_id = IDS_AUDIO_FILES;
199 net::GetAudioExtensions(extensions);
200 } else if (ascii_mime_type == "video/*") {
201 description_id = IDS_VIDEO_FILES;
202 net::GetVideoExtensions(extensions);
203 } else {
204 net::GetExtensionsForMimeType(ascii_mime_type, extensions);
205 }
206
207 if (extensions->size() > old_extension_size)
208 valid_type_count++;
209 }
210
[email protected]cbcd12ed2010-12-16 23:42:57211 // If no valid extension is added, bail out.
212 if (valid_type_count == 0)
213 return NULL;
214
[email protected]ba70d082010-09-10 16:54:49215 // Use a generic description "Custom Files" if either of the following is
216 // true:
217 // 1) There're multiple types specified, like "audio/*,video/*"
218 // 2) There're multiple extensions for a MIME type without parameter, like
219 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
220 // dialog uses the first extension in the list to form the description,
221 // like "EHTML Files". This is not what we want.
222 if (valid_type_count > 1 ||
223 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
224 description_id = IDS_CUSTOM_FILES;
225
226 if (description_id) {
227 file_type->extension_description_overrides.push_back(
228 l10n_util::GetStringUTF16(description_id));
229 }
230
231 return file_type.release();
232}
233
234void FileSelectHelper::RunFileChooser(
235 RenderViewHost* render_view_host,
[email protected]d9898912011-04-15 21:10:00236 TabContents* tab_contents,
[email protected]aaed2522011-03-11 18:50:54237 const ViewHostMsg_RunFileChooser_Params& params) {
[email protected]ba70d082010-09-10 16:54:49238 DCHECK(!render_view_host_);
239 render_view_host_ = render_view_host;
240 notification_registrar_.RemoveAll();
241 notification_registrar_.Add(this,
242 NotificationType::RENDER_WIDGET_HOST_DESTROYED,
243 Source<RenderViewHost>(render_view_host));
244
245 if (!select_file_dialog_.get())
246 select_file_dialog_ = SelectFileDialog::Create(this);
247
248 switch (params.mode) {
[email protected]0aed2f52011-03-23 18:06:36249 case ViewHostMsg_RunFileChooser_Mode::Open:
[email protected]ba70d082010-09-10 16:54:49250 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
251 break;
[email protected]0aed2f52011-03-23 18:06:36252 case ViewHostMsg_RunFileChooser_Mode::OpenMultiple:
[email protected]ba70d082010-09-10 16:54:49253 dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
254 break;
[email protected]0aed2f52011-03-23 18:06:36255 case ViewHostMsg_RunFileChooser_Mode::OpenFolder:
[email protected]ba70d082010-09-10 16:54:49256 dialog_type_ = SelectFileDialog::SELECT_FOLDER;
257 break;
[email protected]0aed2f52011-03-23 18:06:36258 case ViewHostMsg_RunFileChooser_Mode::Save:
[email protected]ba70d082010-09-10 16:54:49259 dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
260 break;
261 default:
262 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE; // Prevent warning.
263 NOTREACHED();
264 }
265 scoped_ptr<SelectFileDialog::FileTypeInfo> file_types(
266 GetFileTypesFromAcceptType(params.accept_types));
267 FilePath default_file_name = params.default_file_name;
268 if (default_file_name.empty())
269 default_file_name = profile_->last_selected_directory();
270
271 gfx::NativeWindow owning_window =
272 platform_util::GetTopLevel(render_view_host_->view()->GetNativeView());
[email protected]d9898912011-04-15 21:10:00273
[email protected]ba70d082010-09-10 16:54:49274 select_file_dialog_->SelectFile(dialog_type_,
275 params.title,
276 default_file_name,
277 file_types.get(),
[email protected]cbcd12ed2010-12-16 23:42:57278 file_types.get() ? 1 : 0, // 1-based index.
[email protected]ba70d082010-09-10 16:54:49279 FILE_PATH_LITERAL(""),
[email protected]d9898912011-04-15 21:10:00280 tab_contents,
[email protected]ba70d082010-09-10 16:54:49281 owning_window,
282 NULL);
283}
284
[email protected]600ea402011-04-12 00:01:51285void FileSelectHelper::EnumerateDirectory(int request_id,
286 RenderViewHost* render_view_host,
287 const FilePath& path) {
288 DCHECK_NE(kFileSelectEnumerationId, request_id);
289 StartNewEnumeration(path, request_id, render_view_host);
290}
291
[email protected]ba70d082010-09-10 16:54:49292void FileSelectHelper::Observe(NotificationType type,
293 const NotificationSource& source,
294 const NotificationDetails& details) {
295 DCHECK(type == NotificationType::RENDER_WIDGET_HOST_DESTROYED);
296 DCHECK(Details<RenderViewHost>(details).ptr() == render_view_host_);
297 render_view_host_ = NULL;
298}
[email protected]aaed2522011-03-11 18:50:54299
300FileSelectObserver::FileSelectObserver(TabContents* tab_contents)
301 : TabContentsObserver(tab_contents) {
302}
303
304FileSelectObserver::~FileSelectObserver() {
305}
306
307bool FileSelectObserver::OnMessageReceived(const IPC::Message& message) {
308 bool handled = true;
309 IPC_BEGIN_MESSAGE_MAP(FileSelectObserver, message)
310 IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser)
[email protected]600ea402011-04-12 00:01:51311 IPC_MESSAGE_HANDLER(ViewHostMsg_EnumerateDirectory, OnEnumerateDirectory)
[email protected]aaed2522011-03-11 18:50:54312 IPC_MESSAGE_UNHANDLED(handled = false)
313 IPC_END_MESSAGE_MAP()
314
315 return handled;
316}
317
318void FileSelectObserver::OnRunFileChooser(
319 const ViewHostMsg_RunFileChooser_Params& params) {
320 if (!file_select_helper_.get())
321 file_select_helper_.reset(new FileSelectHelper(tab_contents()->profile()));
322 file_select_helper_->RunFileChooser(tab_contents()->render_view_host(),
[email protected]d9898912011-04-15 21:10:00323 tab_contents(),
[email protected]aaed2522011-03-11 18:50:54324 params);
325}
[email protected]600ea402011-04-12 00:01:51326
327void FileSelectObserver::OnEnumerateDirectory(int request_id,
328 const FilePath& path) {
329 ChildProcessSecurityPolicy* policy =
330 ChildProcessSecurityPolicy::GetInstance();
331 if (!policy->CanReadDirectory(
332 tab_contents()->render_view_host()->process()->id(),
333 path)) {
334 return;
335 }
336
337 if (!file_select_helper_.get())
338 file_select_helper_.reset(new FileSelectHelper(tab_contents()->profile()));
339 file_select_helper_->EnumerateDirectory(request_id,
340 tab_contents()->render_view_host(),
341 path);
342}