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
41using base::Bind;
42using base::Callback;
43using content::BrowserContext;
44using content::BrowserThread;
45using content::DownloadManager;
46using content::RenderViewHost;
47using content::WebContents;
48using std::set;
49
50namespace {
51
52base::LazyInstance<base::FilePath>::Leaky
53    g_last_save_path = LAZY_INSTANCE_INITIALIZER;
54
55}  // namespace
56
57namespace {
58
59typedef Callback<void(const base::FilePath&)> SelectedCallback;
60typedef Callback<void(void)> CanceledCallback;
61
62class 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
120void 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
127void 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
134storage::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
142std::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
171DevToolsFileHelper::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
186set<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
199DevToolsFileHelper::FileSystem::FileSystem() {
200}
201
202DevToolsFileHelper::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
210DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents,
211                                       Profile* profile)
212    : web_contents_(web_contents),
213      profile_(profile),
214      weak_factory_(this) {
215}
216
217DevToolsFileHelper::~DevToolsFileHelper() {
218}
219
220void 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
271void 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
282void 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
299void DevToolsFileHelper::SaveAsFileSelectionCanceled(
300    const SaveCallback& callback) {
301  callback.Run();
302}
303
304void 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
318void 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
339void 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
363void 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
390void 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
412void 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
423bool 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