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
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:
SelectFileDialog(const SelectedCallback & selected_callback,const CanceledCallback & canceled_callback,WebContents * web_contents)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
Show(ui::SelectFileDialog::Type type,const base::FilePath & default_path)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.
FileSelected(const base::FilePath & path,int index,void * params)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
MultiFilesSelected(const std::vector<base::FilePath> & files,void * params)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
FileSelectionCanceled(void * params)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>;
~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
WriteToFile(const base::FilePath & path,const std::string & content)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
AppendToFile(const base::FilePath & path,const std::string & content)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
isolated_context()134 fileapi::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
RegisterFileSystem(WebContents * web_contents,const base::FilePath & path,std::string * registered_name)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 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
CreateFileSystemStruct(WebContents * web_contents,const std::string & file_system_id,const std::string & registered_name,const std::string & file_system_path)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
GetAddedFileSystemPaths(Profile * profile)187 set<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
FileSystem()200 DevToolsFileHelper::FileSystem::FileSystem() {
201 }
202
FileSystem(const std::string & file_system_name,const std::string & root_url,const std::string & file_system_path)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
DevToolsFileHelper(WebContents * web_contents,Profile * profile)211 DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents,
212 Profile* profile)
213 : web_contents_(web_contents),
214 profile_(profile),
215 weak_factory_(this) {
216 }
217
~DevToolsFileHelper()218 DevToolsFileHelper::~DevToolsFileHelper() {
219 }
220
Save(const std::string & url,const std::string & content,bool save_as,const SaveCallback & saveCallback,const SaveCallback & cancelCallback)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 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
Append(const std::string & url,const std::string & content,const AppendCallback & callback)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
SaveAsFileSelected(const std::string & url,const std::string & content,const SaveCallback & callback,const base::FilePath & path)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 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
SaveAsFileSelectionCanceled(const SaveCallback & callback)300 void DevToolsFileHelper::SaveAsFileSelectionCanceled(
301 const SaveCallback& callback) {
302 callback.Run();
303 }
304
AddFileSystem(const AddFileSystemCallback & callback,const ShowInfoBarCallback & show_info_bar_callback)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
UpgradeDraggedFileSystemPermissions(const std::string & file_system_url,const AddFileSystemCallback & callback,const ShowInfoBarCallback & show_info_bar_callback)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
InnerAddFileSystem(const AddFileSystemCallback & callback,const ShowInfoBarCallback & show_info_bar_callback,const base::FilePath & path)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 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
AddUserConfirmedFileSystem(const AddFileSystemCallback & callback,const base::FilePath & path,bool allowed)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 ®istered_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
RequestFileSystems(const RequestFileSystemsCallback & callback)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 ®istered_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
RemoveFileSystem(const std::string & file_system_path)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 base::DictionaryValue* file_systems_paths_value = update.Get();
421 file_systems_paths_value->RemoveWithoutPathExpansion(file_system_path, NULL);
422 }
423
IsFileSystemAdded(const std::string & file_system_path)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