1 // Copyright (c) 2012 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/devtools/devtools_file_helper.h" 6 7 #include <set> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/callback.h" 12 #include "base/file_util.h" 13 #include "base/lazy_instance.h" 14 #include "base/md5.h" 15 #include "base/prefs/pref_service.h" 16 #include "base/prefs/scoped_user_pref_update.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/value_conversions.h" 19 #include "chrome/browser/browser_process.h" 20 #include "chrome/browser/download/download_prefs.h" 21 #include "chrome/browser/platform_util.h" 22 #include "chrome/browser/profiles/profile.h" 23 #include "chrome/browser/ui/chrome_select_file_policy.h" 24 #include "chrome/common/pref_names.h" 25 #include "content/public/browser/browser_context.h" 26 #include "content/public/browser/browser_thread.h" 27 #include "content/public/browser/child_process_security_policy.h" 28 #include "content/public/browser/download_manager.h" 29 #include "content/public/browser/render_process_host.h" 30 #include "content/public/browser/render_view_host.h" 31 #include "content/public/browser/web_contents.h" 32 #include "content/public/browser/web_contents_view.h" 33 #include "content/public/common/content_client.h" 34 #include "content/public/common/url_constants.h" 35 #include "grit/generated_resources.h" 36 #include "ui/base/l10n/l10n_util.h" 37 #include "ui/shell_dialogs/select_file_dialog.h" 38 #include "webkit/browser/fileapi/file_system_url.h" 39 #include "webkit/browser/fileapi/isolated_context.h" 40 #include "webkit/common/fileapi/file_system_util.h" 41 42 using base::Bind; 43 using base::Callback; 44 using content::BrowserContext; 45 using content::BrowserThread; 46 using content::DownloadManager; 47 using content::RenderViewHost; 48 using content::WebContents; 49 using std::set; 50 51 namespace { 52 53 base::LazyInstance<base::FilePath>::Leaky 54 g_last_save_path = LAZY_INSTANCE_INITIALIZER; 55 56 } // namespace 57 58 namespace { 59 60 typedef Callback<void(const base::FilePath&)> SelectedCallback; 61 typedef Callback<void(void)> CanceledCallback; 62 63 class SelectFileDialog : public ui::SelectFileDialog::Listener, 64 public base::RefCounted<SelectFileDialog> { 65 public: 66 SelectFileDialog(const SelectedCallback& selected_callback, 67 const CanceledCallback& canceled_callback, 68 WebContents* web_contents) 69 : selected_callback_(selected_callback), 70 canceled_callback_(canceled_callback), 71 web_contents_(web_contents) { 72 select_file_dialog_ = ui::SelectFileDialog::Create( 73 this, new ChromeSelectFilePolicy(web_contents)); 74 } 75 76 void Show(ui::SelectFileDialog::Type type, 77 const base::FilePath& default_path) { 78 AddRef(); // Balanced in the three listener outcomes. 79 select_file_dialog_->SelectFile( 80 type, 81 base::string16(), 82 default_path, 83 NULL, 84 0, 85 base::FilePath::StringType(), 86 platform_util::GetTopLevel(web_contents_->GetView()->GetNativeView()), 87 NULL); 88 } 89 90 // ui::SelectFileDialog::Listener implementation. 91 virtual void FileSelected(const base::FilePath& path, 92 int index, 93 void* params) OVERRIDE { 94 selected_callback_.Run(path); 95 Release(); // Balanced in ::Show. 96 } 97 98 virtual void MultiFilesSelected(const std::vector<base::FilePath>& files, 99 void* params) OVERRIDE { 100 Release(); // Balanced in ::Show. 101 NOTREACHED() << "Should not be able to select multiple files"; 102 } 103 104 virtual void FileSelectionCanceled(void* params) OVERRIDE { 105 canceled_callback_.Run(); 106 Release(); // Balanced in ::Show. 107 } 108 109 private: 110 friend class base::RefCounted<SelectFileDialog>; 111 virtual ~SelectFileDialog() {} 112 113 scoped_refptr<ui::SelectFileDialog> select_file_dialog_; 114 SelectedCallback selected_callback_; 115 CanceledCallback canceled_callback_; 116 WebContents* web_contents_; 117 118 DISALLOW_COPY_AND_ASSIGN(SelectFileDialog); 119 }; 120 121 void WriteToFile(const base::FilePath& path, const std::string& content) { 122 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 123 DCHECK(!path.empty()); 124 125 file_util::WriteFile(path, content.c_str(), content.length()); 126 } 127 128 void AppendToFile(const base::FilePath& path, const std::string& content) { 129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 130 DCHECK(!path.empty()); 131 132 file_util::AppendToFile(path, content.c_str(), content.length()); 133 } 134 135 fileapi::IsolatedContext* isolated_context() { 136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 137 fileapi::IsolatedContext* isolated_context = 138 fileapi::IsolatedContext::GetInstance(); 139 DCHECK(isolated_context); 140 return isolated_context; 141 } 142 143 std::string RegisterFileSystem(WebContents* web_contents, 144 const base::FilePath& path, 145 std::string* registered_name) { 146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 147 CHECK(web_contents->GetURL().SchemeIs(chrome::kChromeDevToolsScheme)); 148 std::string file_system_id = isolated_context()->RegisterFileSystemForPath( 149 fileapi::kFileSystemTypeNativeLocal, path, registered_name); 150 151 content::ChildProcessSecurityPolicy* policy = 152 content::ChildProcessSecurityPolicy::GetInstance(); 153 RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); 154 int renderer_id = render_view_host->GetProcess()->GetID(); 155 policy->GrantReadFileSystem(renderer_id, file_system_id); 156 policy->GrantWriteFileSystem(renderer_id, file_system_id); 157 policy->GrantCreateFileForFileSystem(renderer_id, file_system_id); 158 policy->GrantDeleteFromFileSystem(renderer_id, file_system_id); 159 160 // We only need file level access for reading FileEntries. Saving FileEntries 161 // just needs the file system to have read/write access, which is granted 162 // above if required. 163 if (!policy->CanReadFile(renderer_id, path)) 164 policy->GrantReadFile(renderer_id, path); 165 166 return file_system_id; 167 } 168 169 DevToolsFileHelper::FileSystem CreateFileSystemStruct( 170 WebContents* web_contents, 171 const std::string& file_system_id, 172 const std::string& registered_name, 173 const std::string& file_system_path) { 174 const GURL origin = web_contents->GetURL().GetOrigin(); 175 std::string file_system_name = fileapi::GetIsolatedFileSystemName( 176 origin, 177 file_system_id); 178 std::string root_url = fileapi::GetIsolatedFileSystemRootURIString( 179 origin, 180 file_system_id, 181 registered_name); 182 return DevToolsFileHelper::FileSystem(file_system_name, 183 root_url, 184 file_system_path); 185 } 186 187 set<std::string> GetAddedFileSystemPaths(Profile* profile) { 188 const DictionaryValue* file_systems_paths_value = 189 profile->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths); 190 set<std::string> result; 191 for (DictionaryValue::Iterator it(*file_systems_paths_value); !it.IsAtEnd(); 192 it.Advance()) { 193 result.insert(it.key()); 194 } 195 return result; 196 } 197 198 } // namespace 199 200 DevToolsFileHelper::FileSystem::FileSystem() { 201 } 202 203 DevToolsFileHelper::FileSystem::FileSystem(const std::string& file_system_name, 204 const std::string& root_url, 205 const std::string& file_system_path) 206 : file_system_name(file_system_name), 207 root_url(root_url), 208 file_system_path(file_system_path) { 209 } 210 211 DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents, 212 Profile* profile) 213 : web_contents_(web_contents), 214 profile_(profile), 215 weak_factory_(this) { 216 } 217 218 DevToolsFileHelper::~DevToolsFileHelper() { 219 } 220 221 void DevToolsFileHelper::Save(const std::string& url, 222 const std::string& content, 223 bool save_as, 224 const SaveCallback& saveCallback, 225 const SaveCallback& cancelCallback) { 226 PathsMap::iterator it = saved_files_.find(url); 227 if (it != saved_files_.end() && !save_as) { 228 SaveAsFileSelected(url, content, saveCallback, it->second); 229 return; 230 } 231 232 const DictionaryValue* file_map = 233 profile_->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles); 234 base::FilePath initial_path; 235 236 const Value* path_value; 237 if (file_map->Get(base::MD5String(url), &path_value)) 238 base::GetValueAsFilePath(*path_value, &initial_path); 239 240 if (initial_path.empty()) { 241 GURL gurl(url); 242 std::string suggested_file_name = gurl.is_valid() ? 243 gurl.ExtractFileName() : url; 244 245 if (suggested_file_name.length() > 64) 246 suggested_file_name = suggested_file_name.substr(0, 64); 247 248 if (!g_last_save_path.Pointer()->empty()) { 249 initial_path = g_last_save_path.Pointer()->DirName().AppendASCII( 250 suggested_file_name); 251 } else { 252 base::FilePath download_path = DownloadPrefs::FromDownloadManager( 253 BrowserContext::GetDownloadManager(profile_))->DownloadPath(); 254 initial_path = download_path.AppendASCII(suggested_file_name); 255 } 256 } 257 258 scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog( 259 Bind(&DevToolsFileHelper::SaveAsFileSelected, 260 weak_factory_.GetWeakPtr(), 261 url, 262 content, 263 saveCallback), 264 Bind(&DevToolsFileHelper::SaveAsFileSelectionCanceled, 265 weak_factory_.GetWeakPtr(), 266 cancelCallback), 267 web_contents_); 268 select_file_dialog->Show(ui::SelectFileDialog::SELECT_SAVEAS_FILE, 269 initial_path); 270 } 271 272 void DevToolsFileHelper::Append(const std::string& url, 273 const std::string& content, 274 const AppendCallback& callback) { 275 PathsMap::iterator it = saved_files_.find(url); 276 if (it == saved_files_.end()) 277 return; 278 callback.Run(); 279 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 280 Bind(&AppendToFile, it->second, content)); 281 } 282 283 void DevToolsFileHelper::SaveAsFileSelected(const std::string& url, 284 const std::string& content, 285 const SaveCallback& callback, 286 const base::FilePath& path) { 287 *g_last_save_path.Pointer() = path; 288 saved_files_[url] = path; 289 290 DictionaryPrefUpdate update(profile_->GetPrefs(), 291 prefs::kDevToolsEditedFiles); 292 DictionaryValue* files_map = update.Get(); 293 files_map->SetWithoutPathExpansion(base::MD5String(url), 294 base::CreateFilePathValue(path)); 295 callback.Run(); 296 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 297 Bind(&WriteToFile, path, content)); 298 } 299 300 void DevToolsFileHelper::SaveAsFileSelectionCanceled( 301 const SaveCallback& callback) { 302 callback.Run(); 303 } 304 305 void DevToolsFileHelper::AddFileSystem( 306 const AddFileSystemCallback& callback, 307 const ShowInfoBarCallback& show_info_bar_callback) { 308 scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog( 309 Bind(&DevToolsFileHelper::InnerAddFileSystem, 310 weak_factory_.GetWeakPtr(), 311 callback, 312 show_info_bar_callback), 313 Bind(callback, FileSystem()), 314 web_contents_); 315 select_file_dialog->Show(ui::SelectFileDialog::SELECT_FOLDER, 316 base::FilePath()); 317 } 318 319 void DevToolsFileHelper::UpgradeDraggedFileSystemPermissions( 320 const std::string& file_system_url, 321 const AddFileSystemCallback& callback, 322 const ShowInfoBarCallback& show_info_bar_callback) { 323 fileapi::FileSystemURL root_url = 324 isolated_context()->CrackURL(GURL(file_system_url)); 325 if (!root_url.is_valid() || !root_url.path().empty()) { 326 callback.Run(FileSystem()); 327 return; 328 } 329 330 std::vector<fileapi::MountPoints::MountPointInfo> mount_points; 331 isolated_context()->GetDraggedFileInfo(root_url.filesystem_id(), 332 &mount_points); 333 334 std::vector<fileapi::MountPoints::MountPointInfo>::const_iterator it = 335 mount_points.begin(); 336 for (; it != mount_points.end(); ++it) 337 InnerAddFileSystem(callback, show_info_bar_callback, it->path); 338 } 339 340 void DevToolsFileHelper::InnerAddFileSystem( 341 const AddFileSystemCallback& callback, 342 const ShowInfoBarCallback& show_info_bar_callback, 343 const base::FilePath& path) { 344 std::string file_system_path = path.AsUTF8Unsafe(); 345 346 const DictionaryValue* file_systems_paths_value = 347 profile_->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths); 348 if (file_systems_paths_value->HasKey(file_system_path)) { 349 callback.Run(FileSystem()); 350 return; 351 } 352 353 std::string path_display_name = path.AsEndingWithSeparator().AsUTF8Unsafe(); 354 base::string16 message = l10n_util::GetStringFUTF16( 355 IDS_DEV_TOOLS_CONFIRM_ADD_FILE_SYSTEM_MESSAGE, 356 UTF8ToUTF16(path_display_name)); 357 show_info_bar_callback.Run( 358 message, 359 Bind(&DevToolsFileHelper::AddUserConfirmedFileSystem, 360 weak_factory_.GetWeakPtr(), 361 callback, path)); 362 } 363 364 void DevToolsFileHelper::AddUserConfirmedFileSystem( 365 const AddFileSystemCallback& callback, 366 const base::FilePath& path, 367 bool allowed) { 368 if (!allowed) { 369 callback.Run(FileSystem()); 370 return; 371 } 372 std::string registered_name; 373 std::string file_system_id = RegisterFileSystem(web_contents_, 374 path, 375 ®istered_name); 376 std::string file_system_path = path.AsUTF8Unsafe(); 377 378 DictionaryPrefUpdate update(profile_->GetPrefs(), 379 prefs::kDevToolsFileSystemPaths); 380 DictionaryValue* file_systems_paths_value = update.Get(); 381 file_systems_paths_value->SetWithoutPathExpansion(file_system_path, 382 Value::CreateNullValue()); 383 384 FileSystem filesystem = CreateFileSystemStruct(web_contents_, 385 file_system_id, 386 registered_name, 387 file_system_path); 388 callback.Run(filesystem); 389 } 390 391 void DevToolsFileHelper::RequestFileSystems( 392 const RequestFileSystemsCallback& callback) { 393 set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_); 394 set<std::string>::const_iterator it = file_system_paths.begin(); 395 std::vector<FileSystem> file_systems; 396 for (; it != file_system_paths.end(); ++it) { 397 std::string file_system_path = *it; 398 base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); 399 400 std::string registered_name; 401 std::string file_system_id = RegisterFileSystem(web_contents_, 402 path, 403 ®istered_name); 404 FileSystem filesystem = CreateFileSystemStruct(web_contents_, 405 file_system_id, 406 registered_name, 407 file_system_path); 408 file_systems.push_back(filesystem); 409 } 410 callback.Run(file_systems); 411 } 412 413 void DevToolsFileHelper::RemoveFileSystem(const std::string& file_system_path) { 414 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 415 base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); 416 isolated_context()->RevokeFileSystemByPath(path); 417 418 DictionaryPrefUpdate update(profile_->GetPrefs(), 419 prefs::kDevToolsFileSystemPaths); 420 DictionaryValue* file_systems_paths_value = update.Get(); 421 file_systems_paths_value->RemoveWithoutPathExpansion(file_system_path, NULL); 422 } 423 424 bool DevToolsFileHelper::IsFileSystemAdded( 425 const std::string& file_system_path) { 426 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 427 set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_); 428 return file_system_paths.find(file_system_path) != file_system_paths.end(); 429 } 430