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