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