Home | History | Annotate | Download | only in devtools
      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                                                   &registered_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                                                     &registered_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