• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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