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.
5 #include "chrome/browser/devtools/devtools_file_helper.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/file_util.h"
13 #include "base/lazy_instance.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"
44 using content::BrowserContext
;
45 using content::BrowserThread
;
46 using content::DownloadManager
;
47 using content::RenderViewHost
;
48 using content::WebContents
;
53 base::LazyInstance
<base::FilePath
>::Leaky
54 g_last_save_path
= LAZY_INSTANCE_INITIALIZER
;
60 typedef Callback
<void(const base::FilePath
&)> SelectedCallback
;
61 typedef Callback
<void(void)> CanceledCallback
;
63 class SelectFileDialog
: public ui::SelectFileDialog::Listener
,
64 public base::RefCounted
<SelectFileDialog
> {
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
));
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(
85 base::FilePath::StringType(),
86 platform_util::GetTopLevel(web_contents_
->GetView()->GetNativeView()),
90 // ui::SelectFileDialog::Listener implementation.
91 virtual void FileSelected(const base::FilePath
& path
,
93 void* params
) OVERRIDE
{
94 selected_callback_
.Run(path
);
95 Release(); // Balanced in ::Show.
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";
104 virtual void FileSelectionCanceled(void* params
) OVERRIDE
{
105 canceled_callback_
.Run();
106 Release(); // Balanced in ::Show.
110 friend class base::RefCounted
<SelectFileDialog
>;
111 virtual ~SelectFileDialog() {}
113 scoped_refptr
<ui::SelectFileDialog
> select_file_dialog_
;
114 SelectedCallback selected_callback_
;
115 CanceledCallback canceled_callback_
;
116 WebContents
* web_contents_
;
118 DISALLOW_COPY_AND_ASSIGN(SelectFileDialog
);
121 void WriteToFile(const base::FilePath
& path
, const std::string
& content
) {
122 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
123 DCHECK(!path
.empty());
125 file_util::WriteFile(path
, content
.c_str(), content
.length());
128 void AppendToFile(const base::FilePath
& path
, const std::string
& content
) {
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
130 DCHECK(!path
.empty());
132 file_util::AppendToFile(path
, content
.c_str(), content
.length());
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
;
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
);
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
);
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
);
166 return file_system_id
;
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(
178 std::string root_url
= fileapi::GetIsolatedFileSystemRootURIString(
182 return DevToolsFileHelper::FileSystem(file_system_name
,
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());
200 DevToolsFileHelper::FileSystem::FileSystem() {
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
),
208 file_system_path(file_system_path
) {
211 DevToolsFileHelper::DevToolsFileHelper(WebContents
* web_contents
,
213 : web_contents_(web_contents
),
215 weak_factory_(this) {
218 DevToolsFileHelper::~DevToolsFileHelper() {
221 void DevToolsFileHelper::Save(const std::string
& url
,
222 const std::string
& content
,
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
);
232 const base::DictionaryValue
* file_map
=
233 profile_
->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles
);
234 base::FilePath initial_path
;
236 const base::Value
* path_value
;
237 if (file_map
->Get(base::MD5String(url
), &path_value
))
238 base::GetValueAsFilePath(*path_value
, &initial_path
);
240 if (initial_path
.empty()) {
242 std::string suggested_file_name
= gurl
.is_valid() ?
243 gurl
.ExtractFileName() : url
;
245 if (suggested_file_name
.length() > 64)
246 suggested_file_name
= suggested_file_name
.substr(0, 64);
248 if (!g_last_save_path
.Pointer()->empty()) {
249 initial_path
= g_last_save_path
.Pointer()->DirName().AppendASCII(
250 suggested_file_name
);
252 base::FilePath download_path
= DownloadPrefs::FromDownloadManager(
253 BrowserContext::GetDownloadManager(profile_
))->DownloadPath();
254 initial_path
= download_path
.AppendASCII(suggested_file_name
);
258 scoped_refptr
<SelectFileDialog
> select_file_dialog
= new SelectFileDialog(
259 Bind(&DevToolsFileHelper::SaveAsFileSelected
,
260 weak_factory_
.GetWeakPtr(),
264 Bind(&DevToolsFileHelper::SaveAsFileSelectionCanceled
,
265 weak_factory_
.GetWeakPtr(),
268 select_file_dialog
->Show(ui::SelectFileDialog::SELECT_SAVEAS_FILE
,
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())
279 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
280 Bind(&AppendToFile
, it
->second
, content
));
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
;
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
));
296 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
297 Bind(&WriteToFile
, path
, content
));
300 void DevToolsFileHelper::SaveAsFileSelectionCanceled(
301 const SaveCallback
& 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(),
312 show_info_bar_callback
),
313 Bind(callback
, FileSystem()),
315 select_file_dialog
->Show(ui::SelectFileDialog::SELECT_FOLDER
,
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());
330 std::vector
<fileapi::MountPoints::MountPointInfo
> mount_points
;
331 isolated_context()->GetDraggedFileInfo(root_url
.filesystem_id(),
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
);
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();
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());
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(
359 Bind(&DevToolsFileHelper::AddUserConfirmedFileSystem
,
360 weak_factory_
.GetWeakPtr(),
364 void DevToolsFileHelper::AddUserConfirmedFileSystem(
365 const AddFileSystemCallback
& callback
,
366 const base::FilePath
& path
,
369 callback
.Run(FileSystem());
372 std::string registered_name
;
373 std::string file_system_id
= RegisterFileSystem(web_contents_
,
376 std::string file_system_path
= path
.AsUTF8Unsafe();
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());
384 FileSystem filesystem
= CreateFileSystemStruct(web_contents_
,
388 callback
.Run(filesystem
);
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
);
400 std::string registered_name
;
401 std::string file_system_id
= RegisterFileSystem(web_contents_
,
404 FileSystem filesystem
= CreateFileSystemStruct(web_contents_
,
408 file_systems
.push_back(filesystem
);
410 callback
.Run(file_systems
);
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
);
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
);
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();