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/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                                                   &registered_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                                                     &registered_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