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/common/content_client.h"
33#include "content/public/common/url_constants.h"
34#include "grit/generated_resources.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "ui/shell_dialogs/select_file_dialog.h"
37#include "webkit/browser/fileapi/file_system_url.h"
38#include "webkit/browser/fileapi/isolated_context.h"
39#include "webkit/common/fileapi/file_system_util.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
134fileapi::IsolatedContext* isolated_context() {
135  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
136  fileapi::IsolatedContext* isolated_context =
137      fileapi::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      fileapi::kFileSystemTypeNativeLocal, std::string(), path,
149      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
169DevToolsFileHelper::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
187set<std::string> GetAddedFileSystemPaths(Profile* profile) {
188  const base::DictionaryValue* file_systems_paths_value =
189      profile->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths);
190  set<std::string> result;
191  for (base::DictionaryValue::Iterator it(*file_systems_paths_value);
192       !it.IsAtEnd(); it.Advance()) {
193    result.insert(it.key());
194  }
195  return result;
196}
197
198}  // namespace
199
200DevToolsFileHelper::FileSystem::FileSystem() {
201}
202
203DevToolsFileHelper::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
211DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents,
212                                       Profile* profile)
213    : web_contents_(web_contents),
214      profile_(profile),
215      weak_factory_(this) {
216}
217
218DevToolsFileHelper::~DevToolsFileHelper() {
219}
220
221void 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 base::DictionaryValue* file_map =
233      profile_->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles);
234  base::FilePath initial_path;
235
236  const base::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
272void 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
283void 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  base::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
300void DevToolsFileHelper::SaveAsFileSelectionCanceled(
301    const SaveCallback& callback) {
302  callback.Run();
303}
304
305void 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
319void 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
340void 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 base::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      base::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
364void 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  base::DictionaryValue* file_systems_paths_value = update.Get();
381  file_systems_paths_value->SetWithoutPathExpansion(
382      file_system_path, base::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
391void 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
413void 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  base::DictionaryValue* file_systems_paths_value = update.Get();
421  file_systems_paths_value->RemoveWithoutPathExpansion(file_system_path, NULL);
422}
423
424bool 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