blob: 3ee8c5a406cdc3da94c4fb262cc4105430699662 [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 {
38 ActiveDirectoryEnumeration() {}
39
40 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
41 scoped_refptr<net::DirectoryLister> lister_;
42 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) {
65 if (iter->second->lister_.get()) {
66 iter->second->lister_->set_delegate(NULL);
67 iter->second->lister_->Cancel();
68 }
69 delete iter->second;
[email protected]ba70d082010-09-10 16:54:4970 }
71}
72
73void FileSelectHelper::FileSelected(const FilePath& path,
74 int index, void* params) {
75 if (!render_view_host_)
76 return;
77
78 profile_->set_last_selected_directory(path.DirName());
79
80 if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
[email protected]600ea402011-04-12 00:01:5181 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
[email protected]ba70d082010-09-10 16:54:4982 return;
83 }
84
85 std::vector<FilePath> files;
86 files.push_back(path);
87 render_view_host_->FilesSelectedInChooser(files);
88 // We are done with this showing of the dialog.
89 render_view_host_ = NULL;
90}
91
92void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
93 void* params) {
94 if (!files.empty())
95 profile_->set_last_selected_directory(files[0].DirName());
96 if (!render_view_host_)
97 return;
98
99 render_view_host_->FilesSelectedInChooser(files);
100 // We are done with this showing of the dialog.
101 render_view_host_ = NULL;
102}
103
104void FileSelectHelper::FileSelectionCanceled(void* params) {
105 if (!render_view_host_)
106 return;
107
108 // If the user cancels choosing a file to upload we pass back an
109 // empty vector.
110 render_view_host_->FilesSelectedInChooser(std::vector<FilePath>());
111
112 // We are done with this showing of the dialog.
113 render_view_host_ = NULL;
114}
115
[email protected]600ea402011-04-12 00:01:51116void FileSelectHelper::StartNewEnumeration(const FilePath& path,
117 int request_id,
118 RenderViewHost* render_view_host) {
119 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
120 entry->rvh_ = render_view_host;
121 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
122 entry->lister_ = new net::DirectoryLister(path,
123 true,
124 net::DirectoryLister::NO_SORT,
125 entry->delegate_.get());
126 if (!entry->lister_->Start()) {
127 if (request_id == kFileSelectEnumerationId)
128 FileSelectionCanceled(NULL);
129 else
130 render_view_host->DirectoryEnumerationFinished(request_id,
131 entry->results_);
132 } else {
133 directory_enumerations_[request_id] = entry.release();
134 }
[email protected]ba70d082010-09-10 16:54:49135}
136
137void FileSelectHelper::OnListFile(
[email protected]600ea402011-04-12 00:01:51138 int id,
[email protected]ba70d082010-09-10 16:54:49139 const net::DirectoryLister::DirectoryListerData& data) {
[email protected]600ea402011-04-12 00:01:51140 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
141
[email protected]9897e092011-02-04 22:09:11142 // Directory upload returns directories via a "." file, so that
143 // empty directories are included. This util call just checks
[email protected]ba70d082010-09-10 16:54:49144 // the flags in the structure; there's no file I/O going on.
145 if (file_util::FileEnumerator::IsDirectory(data.info))
[email protected]600ea402011-04-12 00:01:51146 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
[email protected]9897e092011-02-04 22:09:11147 else
[email protected]600ea402011-04-12 00:01:51148 entry->results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49149}
150
[email protected]600ea402011-04-12 00:01:51151void FileSelectHelper::OnListDone(int id, int error) {
152 // This entry needs to be cleaned up when this function is done.
153 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
154 directory_enumerations_.erase(id);
155 if (!entry->rvh_)
[email protected]ba70d082010-09-10 16:54:49156 return;
[email protected]ba70d082010-09-10 16:54:49157 if (error) {
158 FileSelectionCanceled(NULL);
159 return;
160 }
[email protected]600ea402011-04-12 00:01:51161 if (id == kFileSelectEnumerationId)
162 entry->rvh_->FilesSelectedInChooser(entry->results_);
163 else
164 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
[email protected]ba70d082010-09-10 16:54:49165}
166
167SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
168 const string16& accept_types) {
169 if (accept_types.empty())
170 return NULL;
171
172 // Split the accept-type string on commas.
173 std::vector<string16> mime_types;
174 base::SplitStringUsingSubstr(accept_types, ASCIIToUTF16(","), &mime_types);
175 if (mime_types.empty())
176 return NULL;
177
178 // Create FileTypeInfo and pre-allocate for the first extension list.
179 scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
180 new SelectFileDialog::FileTypeInfo());
181 file_type->include_all_files = true;
182 file_type->extensions.resize(1);
183 std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
184
185 // Find the correspondinge extensions.
186 int valid_type_count = 0;
187 int description_id = 0;
188 for (size_t i = 0; i < mime_types.size(); ++i) {
189 string16 mime_type = mime_types[i];
190 std::string ascii_mime_type = StringToLowerASCII(UTF16ToASCII(mime_type));
191
192 TrimWhitespace(ascii_mime_type, TRIM_ALL, &ascii_mime_type);
193 if (ascii_mime_type.empty())
194 continue;
195
196 size_t old_extension_size = extensions->size();
197 if (ascii_mime_type == "image/*") {
198 description_id = IDS_IMAGE_FILES;
199 net::GetImageExtensions(extensions);
200 } else if (ascii_mime_type == "audio/*") {
201 description_id = IDS_AUDIO_FILES;
202 net::GetAudioExtensions(extensions);
203 } else if (ascii_mime_type == "video/*") {
204 description_id = IDS_VIDEO_FILES;
205 net::GetVideoExtensions(extensions);
206 } else {
207 net::GetExtensionsForMimeType(ascii_mime_type, extensions);
208 }
209
210 if (extensions->size() > old_extension_size)
211 valid_type_count++;
212 }
213
[email protected]cbcd12ed2010-12-16 23:42:57214 // If no valid extension is added, bail out.
215 if (valid_type_count == 0)
216 return NULL;
217
[email protected]ba70d082010-09-10 16:54:49218 // Use a generic description "Custom Files" if either of the following is
219 // true:
220 // 1) There're multiple types specified, like "audio/*,video/*"
221 // 2) There're multiple extensions for a MIME type without parameter, like
222 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
223 // dialog uses the first extension in the list to form the description,
224 // like "EHTML Files". This is not what we want.
225 if (valid_type_count > 1 ||
226 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
227 description_id = IDS_CUSTOM_FILES;
228
229 if (description_id) {
230 file_type->extension_description_overrides.push_back(
231 l10n_util::GetStringUTF16(description_id));
232 }
233
234 return file_type.release();
235}
236
237void FileSelectHelper::RunFileChooser(
238 RenderViewHost* render_view_host,
[email protected]d9898912011-04-15 21:10:00239 TabContents* tab_contents,
[email protected]aaed2522011-03-11 18:50:54240 const ViewHostMsg_RunFileChooser_Params& params) {
[email protected]ba70d082010-09-10 16:54:49241 DCHECK(!render_view_host_);
242 render_view_host_ = render_view_host;
243 notification_registrar_.RemoveAll();
244 notification_registrar_.Add(this,
245 NotificationType::RENDER_WIDGET_HOST_DESTROYED,
246 Source<RenderViewHost>(render_view_host));
247
248 if (!select_file_dialog_.get())
249 select_file_dialog_ = SelectFileDialog::Create(this);
250
251 switch (params.mode) {
[email protected]0aed2f52011-03-23 18:06:36252 case ViewHostMsg_RunFileChooser_Mode::Open:
[email protected]ba70d082010-09-10 16:54:49253 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
254 break;
[email protected]0aed2f52011-03-23 18:06:36255 case ViewHostMsg_RunFileChooser_Mode::OpenMultiple:
[email protected]ba70d082010-09-10 16:54:49256 dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
257 break;
[email protected]0aed2f52011-03-23 18:06:36258 case ViewHostMsg_RunFileChooser_Mode::OpenFolder:
[email protected]ba70d082010-09-10 16:54:49259 dialog_type_ = SelectFileDialog::SELECT_FOLDER;
260 break;
[email protected]0aed2f52011-03-23 18:06:36261 case ViewHostMsg_RunFileChooser_Mode::Save:
[email protected]ba70d082010-09-10 16:54:49262 dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
263 break;
264 default:
265 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE; // Prevent warning.
266 NOTREACHED();
267 }
268 scoped_ptr<SelectFileDialog::FileTypeInfo> file_types(
269 GetFileTypesFromAcceptType(params.accept_types));
270 FilePath default_file_name = params.default_file_name;
271 if (default_file_name.empty())
272 default_file_name = profile_->last_selected_directory();
273
274 gfx::NativeWindow owning_window =
275 platform_util::GetTopLevel(render_view_host_->view()->GetNativeView());
[email protected]d9898912011-04-15 21:10:00276
[email protected]ba70d082010-09-10 16:54:49277 select_file_dialog_->SelectFile(dialog_type_,
278 params.title,
279 default_file_name,
280 file_types.get(),
[email protected]cbcd12ed2010-12-16 23:42:57281 file_types.get() ? 1 : 0, // 1-based index.
[email protected]ba70d082010-09-10 16:54:49282 FILE_PATH_LITERAL(""),
[email protected]d9898912011-04-15 21:10:00283 tab_contents,
[email protected]ba70d082010-09-10 16:54:49284 owning_window,
285 NULL);
286}
287
[email protected]600ea402011-04-12 00:01:51288void FileSelectHelper::EnumerateDirectory(int request_id,
289 RenderViewHost* render_view_host,
290 const FilePath& path) {
291 DCHECK_NE(kFileSelectEnumerationId, request_id);
292 StartNewEnumeration(path, request_id, render_view_host);
293}
294
[email protected]ba70d082010-09-10 16:54:49295void FileSelectHelper::Observe(NotificationType type,
296 const NotificationSource& source,
297 const NotificationDetails& details) {
298 DCHECK(type == NotificationType::RENDER_WIDGET_HOST_DESTROYED);
299 DCHECK(Details<RenderViewHost>(details).ptr() == render_view_host_);
300 render_view_host_ = NULL;
301}
[email protected]aaed2522011-03-11 18:50:54302
303FileSelectObserver::FileSelectObserver(TabContents* tab_contents)
304 : TabContentsObserver(tab_contents) {
305}
306
307FileSelectObserver::~FileSelectObserver() {
308}
309
310bool FileSelectObserver::OnMessageReceived(const IPC::Message& message) {
311 bool handled = true;
312 IPC_BEGIN_MESSAGE_MAP(FileSelectObserver, message)
313 IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser)
[email protected]600ea402011-04-12 00:01:51314 IPC_MESSAGE_HANDLER(ViewHostMsg_EnumerateDirectory, OnEnumerateDirectory)
[email protected]aaed2522011-03-11 18:50:54315 IPC_MESSAGE_UNHANDLED(handled = false)
316 IPC_END_MESSAGE_MAP()
317
318 return handled;
319}
320
321void FileSelectObserver::OnRunFileChooser(
322 const ViewHostMsg_RunFileChooser_Params& params) {
323 if (!file_select_helper_.get())
324 file_select_helper_.reset(new FileSelectHelper(tab_contents()->profile()));
325 file_select_helper_->RunFileChooser(tab_contents()->render_view_host(),
[email protected]d9898912011-04-15 21:10:00326 tab_contents(),
[email protected]aaed2522011-03-11 18:50:54327 params);
328}
[email protected]600ea402011-04-12 00:01:51329
330void FileSelectObserver::OnEnumerateDirectory(int request_id,
331 const FilePath& path) {
332 ChildProcessSecurityPolicy* policy =
333 ChildProcessSecurityPolicy::GetInstance();
334 if (!policy->CanReadDirectory(
335 tab_contents()->render_view_host()->process()->id(),
336 path)) {
337 return;
338 }
339
340 if (!file_select_helper_.get())
341 file_select_helper_.reset(new FileSelectHelper(tab_contents()->profile()));
342 file_select_helper_->EnumerateDirectory(request_id,
343 tab_contents()->render_view_host(),
344 path);
345}