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