1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // 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
7 #include <string>
8
9 #include "base/file_util.h"
10 #include "base/string_split.h"
11 #include "base/string_util.h"
12 #include "base/utf_string_conversions.h"
13 #include "chrome/browser/platform_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "content/browser/child_process_security_policy.h"
16 #include "content/browser/renderer_host/render_process_host.h"
17 #include "content/browser/renderer_host/render_view_host.h"
18 #include "content/browser/renderer_host/render_widget_host_view.h"
19 #include "content/browser/tab_contents/tab_contents.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_list.h"
22 #include "content/common/notification_details.h"
23 #include "content/common/notification_source.h"
24 #include "content/common/view_messages.h"
25 #include "grit/generated_resources.h"
26 #include "net/base/mime_util.h"
27 #include "ui/base/l10n/l10n_util.h"
28
29 namespace {
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.
34 static const int kFileSelectEnumerationId = -1;
35 }
36
37 struct FileSelectHelper::ActiveDirectoryEnumeration {
ActiveDirectoryEnumerationFileSelectHelper::ActiveDirectoryEnumeration38 ActiveDirectoryEnumeration() {}
39
40 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
41 scoped_refptr<net::DirectoryLister> lister_;
42 RenderViewHost* rvh_;
43 std::vector<FilePath> results_;
44 };
45
FileSelectHelper(Profile * profile)46 FileSelectHelper::FileSelectHelper(Profile* profile)
47 : profile_(profile),
48 render_view_host_(NULL),
49 select_file_dialog_(),
50 dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
51 }
52
~FileSelectHelper()53 FileSelectHelper::~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
59 // 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;
70 }
71 }
72
FileSelected(const FilePath & path,int index,void * params)73 void 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) {
81 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
82 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
MultiFilesSelected(const std::vector<FilePath> & files,void * params)92 void 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
FileSelectionCanceled(void * params)104 void 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
StartNewEnumeration(const FilePath & path,int request_id,RenderViewHost * render_view_host)116 void 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 }
135 }
136
OnListFile(int id,const net::DirectoryLister::DirectoryListerData & data)137 void FileSelectHelper::OnListFile(
138 int id,
139 const net::DirectoryLister::DirectoryListerData& data) {
140 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
141
142 // Directory upload returns directories via a "." file, so that
143 // empty directories are included. This util call just checks
144 // the flags in the structure; there's no file I/O going on.
145 if (file_util::FileEnumerator::IsDirectory(data.info))
146 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
147 else
148 entry->results_.push_back(data.path);
149 }
150
OnListDone(int id,int error)151 void 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_)
156 return;
157 if (error) {
158 FileSelectionCanceled(NULL);
159 return;
160 }
161 if (id == kFileSelectEnumerationId)
162 entry->rvh_->FilesSelectedInChooser(entry->results_);
163 else
164 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
165 }
166
GetFileTypesFromAcceptType(const string16 & accept_types)167 SelectFileDialog::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
214 // If no valid extension is added, bail out.
215 if (valid_type_count == 0)
216 return NULL;
217
218 // 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
RunFileChooser(RenderViewHost * render_view_host,TabContents * tab_contents,const ViewHostMsg_RunFileChooser_Params & params)237 void FileSelectHelper::RunFileChooser(
238 RenderViewHost* render_view_host,
239 TabContents* tab_contents,
240 const ViewHostMsg_RunFileChooser_Params& params) {
241 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) {
252 case ViewHostMsg_RunFileChooser_Mode::Open:
253 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
254 break;
255 case ViewHostMsg_RunFileChooser_Mode::OpenMultiple:
256 dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
257 break;
258 case ViewHostMsg_RunFileChooser_Mode::OpenFolder:
259 dialog_type_ = SelectFileDialog::SELECT_FOLDER;
260 break;
261 case ViewHostMsg_RunFileChooser_Mode::Save:
262 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());
276
277 select_file_dialog_->SelectFile(dialog_type_,
278 params.title,
279 default_file_name,
280 file_types.get(),
281 file_types.get() ? 1 : 0, // 1-based index.
282 FILE_PATH_LITERAL(""),
283 tab_contents,
284 owning_window,
285 NULL);
286 }
287
EnumerateDirectory(int request_id,RenderViewHost * render_view_host,const FilePath & path)288 void 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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)295 void 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 }
302
FileSelectObserver(TabContents * tab_contents)303 FileSelectObserver::FileSelectObserver(TabContents* tab_contents)
304 : TabContentsObserver(tab_contents) {
305 }
306
~FileSelectObserver()307 FileSelectObserver::~FileSelectObserver() {
308 }
309
OnMessageReceived(const IPC::Message & message)310 bool FileSelectObserver::OnMessageReceived(const IPC::Message& message) {
311 bool handled = true;
312 IPC_BEGIN_MESSAGE_MAP(FileSelectObserver, message)
313 IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser)
314 IPC_MESSAGE_HANDLER(ViewHostMsg_EnumerateDirectory, OnEnumerateDirectory)
315 IPC_MESSAGE_UNHANDLED(handled = false)
316 IPC_END_MESSAGE_MAP()
317
318 return handled;
319 }
320
OnRunFileChooser(const ViewHostMsg_RunFileChooser_Params & params)321 void 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(),
326 tab_contents(),
327 params);
328 }
329
OnEnumerateDirectory(int request_id,const FilePath & path)330 void 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 }
346