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/strings/utf_string_conversions.h" 17 #include "base/value_conversions.h" 18 #include "chrome/browser/browser_process.h" 19 #include "chrome/browser/download/download_prefs.h" 20 #include "chrome/browser/platform_util.h" 21 #include "chrome/browser/prefs/scoped_user_pref_update.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/isolated_context.h" 39 #include "webkit/common/fileapi/file_system_util.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(NULL)); 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 string16(), 81 default_path, 82 NULL, 83 0, 84 base::FilePath::StringType(), 85 platform_util::GetTopLevel(web_contents_->GetView()->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 118 void WriteToFile(const base::FilePath& path, const std::string& content) { 119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 120 DCHECK(!path.empty()); 121 122 file_util::WriteFile(path, content.c_str(), content.length()); 123 } 124 125 void AppendToFile(const base::FilePath& path, const std::string& content) { 126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 127 DCHECK(!path.empty()); 128 129 file_util::AppendToFile(path, content.c_str(), content.length()); 130 } 131 132 fileapi::IsolatedContext* isolated_context() { 133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 134 fileapi::IsolatedContext* isolated_context = 135 fileapi::IsolatedContext::GetInstance(); 136 DCHECK(isolated_context); 137 return isolated_context; 138 } 139 140 std::string RegisterFileSystem(WebContents* web_contents, 141 const base::FilePath& path, 142 std::string* registered_name) { 143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 144 CHECK(web_contents->GetURL().SchemeIs(chrome::kChromeDevToolsScheme)); 145 std::string file_system_id = isolated_context()->RegisterFileSystemForPath( 146 fileapi::kFileSystemTypeNativeLocal, path, registered_name); 147 148 content::ChildProcessSecurityPolicy* policy = 149 content::ChildProcessSecurityPolicy::GetInstance(); 150 RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); 151 int renderer_id = render_view_host->GetProcess()->GetID(); 152 policy->GrantReadFileSystem(renderer_id, file_system_id); 153 policy->GrantWriteFileSystem(renderer_id, file_system_id); 154 policy->GrantCreateFileForFileSystem(renderer_id, file_system_id); 155 156 // We only need file level access for reading FileEntries. Saving FileEntries 157 // just needs the file system to have read/write access, which is granted 158 // above if required. 159 if (!policy->CanReadFile(renderer_id, path)) 160 policy->GrantReadFile(renderer_id, path); 161 162 return file_system_id; 163 } 164 165 DevToolsFileHelper::FileSystem CreateFileSystemStruct( 166 WebContents* web_contents, 167 const std::string& file_system_id, 168 const std::string& registered_name, 169 const std::string& file_system_path) { 170 const GURL origin = web_contents->GetURL().GetOrigin(); 171 std::string file_system_name = fileapi::GetIsolatedFileSystemName( 172 origin, 173 file_system_id); 174 std::string root_url = fileapi::GetIsolatedFileSystemRootURIString( 175 origin, 176 file_system_id, 177 registered_name); 178 return DevToolsFileHelper::FileSystem(file_system_name, 179 root_url, 180 file_system_path); 181 } 182 183 set<std::string> GetAddedFileSystemPaths(Profile* profile) { 184 const DictionaryValue* file_systems_paths_value = 185 profile->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths); 186 set<std::string> result; 187 for (DictionaryValue::Iterator it(*file_systems_paths_value); !it.IsAtEnd(); 188 it.Advance()) { 189 result.insert(it.key()); 190 } 191 return result; 192 } 193 194 } // namespace 195 196 DevToolsFileHelper::FileSystem::FileSystem() { 197 } 198 199 DevToolsFileHelper::FileSystem::FileSystem(const std::string& file_system_name, 200 const std::string& root_url, 201 const std::string& file_system_path) 202 : file_system_name(file_system_name), 203 root_url(root_url), 204 file_system_path(file_system_path) { 205 } 206 207 DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents, 208 Profile* profile) 209 : web_contents_(web_contents), 210 profile_(profile), 211 weak_factory_(this) { 212 } 213 214 DevToolsFileHelper::~DevToolsFileHelper() { 215 } 216 217 void DevToolsFileHelper::Save(const std::string& url, 218 const std::string& content, 219 bool save_as, 220 const SaveCallback& callback) { 221 PathsMap::iterator it = saved_files_.find(url); 222 if (it != saved_files_.end() && !save_as) { 223 SaveAsFileSelected(url, content, callback, it->second); 224 return; 225 } 226 227 const DictionaryValue* file_map = 228 profile_->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles); 229 base::FilePath initial_path; 230 231 const Value* path_value; 232 if (file_map->Get(base::MD5String(url), &path_value)) 233 base::GetValueAsFilePath(*path_value, &initial_path); 234 235 if (initial_path.empty()) { 236 GURL gurl(url); 237 std::string suggested_file_name = gurl.is_valid() ? 238 gurl.ExtractFileName() : url; 239 240 if (suggested_file_name.length() > 64) 241 suggested_file_name = suggested_file_name.substr(0, 64); 242 243 if (!g_last_save_path.Pointer()->empty()) { 244 initial_path = g_last_save_path.Pointer()->DirName().AppendASCII( 245 suggested_file_name); 246 } else { 247 base::FilePath download_path = DownloadPrefs::FromDownloadManager( 248 BrowserContext::GetDownloadManager(profile_))->DownloadPath(); 249 initial_path = download_path.AppendASCII(suggested_file_name); 250 } 251 } 252 253 scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog( 254 Bind(&DevToolsFileHelper::SaveAsFileSelected, 255 weak_factory_.GetWeakPtr(), 256 url, 257 content, 258 callback), 259 Bind(&DevToolsFileHelper::SaveAsFileSelectionCanceled, 260 weak_factory_.GetWeakPtr()), 261 web_contents_); 262 select_file_dialog->Show(ui::SelectFileDialog::SELECT_SAVEAS_FILE, 263 initial_path); 264 } 265 266 void DevToolsFileHelper::Append(const std::string& url, 267 const std::string& content, 268 const AppendCallback& callback) { 269 PathsMap::iterator it = saved_files_.find(url); 270 if (it == saved_files_.end()) 271 return; 272 callback.Run(); 273 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 274 Bind(&AppendToFile, it->second, content)); 275 } 276 277 void DevToolsFileHelper::SaveAsFileSelected(const std::string& url, 278 const std::string& content, 279 const SaveCallback& callback, 280 const base::FilePath& path) { 281 *g_last_save_path.Pointer() = path; 282 saved_files_[url] = path; 283 284 DictionaryPrefUpdate update(profile_->GetPrefs(), 285 prefs::kDevToolsEditedFiles); 286 DictionaryValue* files_map = update.Get(); 287 files_map->SetWithoutPathExpansion(base::MD5String(url), 288 base::CreateFilePathValue(path)); 289 callback.Run(); 290 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 291 Bind(&WriteToFile, path, content)); 292 } 293 294 void DevToolsFileHelper::SaveAsFileSelectionCanceled() { 295 } 296 297 void DevToolsFileHelper::AddFileSystem( 298 const AddFileSystemCallback& callback, 299 const ShowInfoBarCallback& show_info_bar_callback) { 300 scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog( 301 Bind(&DevToolsFileHelper::InnerAddFileSystem, 302 weak_factory_.GetWeakPtr(), 303 callback, 304 show_info_bar_callback), 305 Bind(callback, FileSystem()), 306 web_contents_); 307 select_file_dialog->Show(ui::SelectFileDialog::SELECT_FOLDER, 308 base::FilePath()); 309 } 310 311 void DevToolsFileHelper::InnerAddFileSystem( 312 const AddFileSystemCallback& callback, 313 const ShowInfoBarCallback& show_info_bar_callback, 314 const base::FilePath& path) { 315 std::string file_system_path = path.AsUTF8Unsafe(); 316 317 const DictionaryValue* file_systems_paths_value = 318 profile_->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths); 319 if (file_systems_paths_value->HasKey(file_system_path)) { 320 callback.Run(FileSystem()); 321 return; 322 } 323 324 std::string path_display_name = path.AsEndingWithSeparator().AsUTF8Unsafe(); 325 string16 message = l10n_util::GetStringFUTF16( 326 IDS_DEV_TOOLS_CONFIRM_ADD_FILE_SYSTEM_MESSAGE, 327 UTF8ToUTF16(path_display_name)); 328 show_info_bar_callback.Run( 329 message, 330 Bind(&DevToolsFileHelper::AddUserConfirmedFileSystem, 331 weak_factory_.GetWeakPtr(), 332 callback, path)); 333 } 334 335 void DevToolsFileHelper::AddUserConfirmedFileSystem( 336 const AddFileSystemCallback& callback, 337 const base::FilePath& path, 338 bool allowed) { 339 if (!allowed) { 340 callback.Run(FileSystem()); 341 return; 342 } 343 std::string registered_name; 344 std::string file_system_id = RegisterFileSystem(web_contents_, 345 path, 346 ®istered_name); 347 std::string file_system_path = path.AsUTF8Unsafe(); 348 349 DictionaryPrefUpdate update(profile_->GetPrefs(), 350 prefs::kDevToolsFileSystemPaths); 351 DictionaryValue* file_systems_paths_value = update.Get(); 352 file_systems_paths_value->SetWithoutPathExpansion(file_system_path, 353 Value::CreateNullValue()); 354 355 FileSystem filesystem = CreateFileSystemStruct(web_contents_, 356 file_system_id, 357 registered_name, 358 file_system_path); 359 callback.Run(filesystem); 360 } 361 362 void DevToolsFileHelper::RequestFileSystems( 363 const RequestFileSystemsCallback& callback) { 364 set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_); 365 set<std::string>::const_iterator it = file_system_paths.begin(); 366 std::vector<FileSystem> file_systems; 367 for (; it != file_system_paths.end(); ++it) { 368 std::string file_system_path = *it; 369 base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); 370 371 std::string registered_name; 372 std::string file_system_id = RegisterFileSystem(web_contents_, 373 path, 374 ®istered_name); 375 FileSystem filesystem = CreateFileSystemStruct(web_contents_, 376 file_system_id, 377 registered_name, 378 file_system_path); 379 file_systems.push_back(filesystem); 380 } 381 callback.Run(file_systems); 382 } 383 384 void DevToolsFileHelper::RemoveFileSystem(const std::string& file_system_path) { 385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 386 base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); 387 isolated_context()->RevokeFileSystemByPath(path); 388 389 DictionaryPrefUpdate update(profile_->GetPrefs(), 390 prefs::kDevToolsFileSystemPaths); 391 DictionaryValue* file_systems_paths_value = update.Get(); 392 file_systems_paths_value->RemoveWithoutPathExpansion(file_system_path, NULL); 393 } 394 395 bool DevToolsFileHelper::IsFileSystemAdded( 396 const std::string& file_system_path) { 397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 398 set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_); 399 return file_system_paths.find(file_system_path) != file_system_paths.end(); 400 } 401