Home | History | Annotate | Download | only in browser
      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 {
     38   ActiveDirectoryEnumeration() {}
     39 
     40   scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
     41   scoped_refptr<net::DirectoryLister> lister_;
     42   RenderViewHost* rvh_;
     43   std::vector<FilePath> results_;
     44 };
     45 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    303 FileSelectObserver::FileSelectObserver(TabContents* tab_contents)
    304     : TabContentsObserver(tab_contents) {
    305 }
    306 
    307 FileSelectObserver::~FileSelectObserver() {
    308 }
    309 
    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 
    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 
    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