blob: c1541c4a2dcfcddade558e6ac839f70b4a4e578f [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/devtools/devtools_file_helper.h"
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/lazy_instance.h"
#include "base/md5.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/utf_string_conversions.h"
#include "base/value_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/content_client.h"
#include "content/public/common/url_constants.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "webkit/browser/fileapi/isolated_context.h"
#include "webkit/common/fileapi/file_system_util.h"
using base::Bind;
using base::Callback;
using content::BrowserContext;
using content::BrowserThread;
using content::DownloadManager;
using content::RenderViewHost;
using content::WebContents;
using std::set;
namespace {
base::LazyInstance<base::FilePath>::Leaky
g_last_save_path = LAZY_INSTANCE_INITIALIZER;
} // namespace
namespace {
typedef Callback<void(const base::FilePath&)> SelectedCallback;
typedef Callback<void(void)> CanceledCallback;
class SelectFileDialog : public ui::SelectFileDialog::Listener,
public base::RefCounted<SelectFileDialog> {
public:
SelectFileDialog(const SelectedCallback& selected_callback,
const CanceledCallback& canceled_callback,
WebContents* web_contents)
: selected_callback_(selected_callback),
canceled_callback_(canceled_callback),
web_contents_(web_contents) {
select_file_dialog_ = ui::SelectFileDialog::Create(
this, new ChromeSelectFilePolicy(NULL));
}
void Show(ui::SelectFileDialog::Type type,
const base::FilePath& default_path) {
AddRef(); // Balanced in the three listener outcomes.
select_file_dialog_->SelectFile(
type,
string16(),
default_path,
NULL,
0,
base::FilePath::StringType(),
platform_util::GetTopLevel(web_contents_->GetView()->GetNativeView()),
NULL);
}
// ui::SelectFileDialog::Listener implementation.
virtual void FileSelected(const base::FilePath& path,
int index,
void* params) OVERRIDE {
selected_callback_.Run(path);
Release(); // Balanced in ::Show.
}
virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
void* params) OVERRIDE {
Release(); // Balanced in ::Show.
NOTREACHED() << "Should not be able to select multiple files";
}
virtual void FileSelectionCanceled(void* params) OVERRIDE {
canceled_callback_.Run();
Release(); // Balanced in ::Show.
}
private:
friend class base::RefCounted<SelectFileDialog>;
virtual ~SelectFileDialog() {}
scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
SelectedCallback selected_callback_;
CanceledCallback canceled_callback_;
WebContents* web_contents_;
};
void WriteToFile(const base::FilePath& path, const std::string& content) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!path.empty());
file_util::WriteFile(path, content.c_str(), content.length());
}
void AppendToFile(const base::FilePath& path, const std::string& content) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!path.empty());
file_util::AppendToFile(path, content.c_str(), content.length());
}
fileapi::IsolatedContext* isolated_context() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
fileapi::IsolatedContext* isolated_context =
fileapi::IsolatedContext::GetInstance();
DCHECK(isolated_context);
return isolated_context;
}
std::string RegisterFileSystem(WebContents* web_contents,
const base::FilePath& path,
std::string* registered_name) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
CHECK(web_contents->GetURL().SchemeIs(chrome::kChromeDevToolsScheme));
std::string file_system_id = isolated_context()->RegisterFileSystemForPath(
fileapi::kFileSystemTypeNativeLocal, path, registered_name);
content::ChildProcessSecurityPolicy* policy =
content::ChildProcessSecurityPolicy::GetInstance();
RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
int renderer_id = render_view_host->GetProcess()->GetID();
policy->GrantReadFileSystem(renderer_id, file_system_id);
policy->GrantWriteFileSystem(renderer_id, file_system_id);
policy->GrantCreateFileForFileSystem(renderer_id, file_system_id);
policy->GrantDeleteFromFileSystem(renderer_id, file_system_id);
// We only need file level access for reading FileEntries. Saving FileEntries
// just needs the file system to have read/write access, which is granted
// above if required.
if (!policy->CanReadFile(renderer_id, path))
policy->GrantReadFile(renderer_id, path);
return file_system_id;
}
DevToolsFileHelper::FileSystem CreateFileSystemStruct(
WebContents* web_contents,
const std::string& file_system_id,
const std::string& registered_name,
const std::string& file_system_path) {
const GURL origin = web_contents->GetURL().GetOrigin();
std::string file_system_name = fileapi::GetIsolatedFileSystemName(
origin,
file_system_id);
std::string root_url = fileapi::GetIsolatedFileSystemRootURIString(
origin,
file_system_id,
registered_name);
return DevToolsFileHelper::FileSystem(file_system_name,
root_url,
file_system_path);
}
set<std::string> GetAddedFileSystemPaths(Profile* profile) {
const DictionaryValue* file_systems_paths_value =
profile->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths);
set<std::string> result;
for (DictionaryValue::Iterator it(*file_systems_paths_value); !it.IsAtEnd();
it.Advance()) {
result.insert(it.key());
}
return result;
}
} // namespace
DevToolsFileHelper::FileSystem::FileSystem() {
}
DevToolsFileHelper::FileSystem::FileSystem(const std::string& file_system_name,
const std::string& root_url,
const std::string& file_system_path)
: file_system_name(file_system_name),
root_url(root_url),
file_system_path(file_system_path) {
}
DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents,
Profile* profile)
: web_contents_(web_contents),
profile_(profile),
weak_factory_(this) {
}
DevToolsFileHelper::~DevToolsFileHelper() {
}
void DevToolsFileHelper::Save(const std::string& url,
const std::string& content,
bool save_as,
const SaveCallback& callback) {
PathsMap::iterator it = saved_files_.find(url);
if (it != saved_files_.end() && !save_as) {
SaveAsFileSelected(url, content, callback, it->second);
return;
}
const DictionaryValue* file_map =
profile_->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles);
base::FilePath initial_path;
const Value* path_value;
if (file_map->Get(base::MD5String(url), &path_value))
base::GetValueAsFilePath(*path_value, &initial_path);
if (initial_path.empty()) {
GURL gurl(url);
std::string suggested_file_name = gurl.is_valid() ?
gurl.ExtractFileName() : url;
if (suggested_file_name.length() > 64)
suggested_file_name = suggested_file_name.substr(0, 64);
if (!g_last_save_path.Pointer()->empty()) {
initial_path = g_last_save_path.Pointer()->DirName().AppendASCII(
suggested_file_name);
} else {
base::FilePath download_path = DownloadPrefs::FromDownloadManager(
BrowserContext::GetDownloadManager(profile_))->DownloadPath();
initial_path = download_path.AppendASCII(suggested_file_name);
}
}
scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog(
Bind(&DevToolsFileHelper::SaveAsFileSelected,
weak_factory_.GetWeakPtr(),
url,
content,
callback),
Bind(&DevToolsFileHelper::SaveAsFileSelectionCanceled,
weak_factory_.GetWeakPtr()),
web_contents_);
select_file_dialog->Show(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
initial_path);
}
void DevToolsFileHelper::Append(const std::string& url,
const std::string& content,
const AppendCallback& callback) {
PathsMap::iterator it = saved_files_.find(url);
if (it == saved_files_.end())
return;
callback.Run();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
Bind(&AppendToFile, it->second, content));
}
void DevToolsFileHelper::SaveAsFileSelected(const std::string& url,
const std::string& content,
const SaveCallback& callback,
const base::FilePath& path) {
*g_last_save_path.Pointer() = path;
saved_files_[url] = path;
DictionaryPrefUpdate update(profile_->GetPrefs(),
prefs::kDevToolsEditedFiles);
DictionaryValue* files_map = update.Get();
files_map->SetWithoutPathExpansion(base::MD5String(url),
base::CreateFilePathValue(path));
callback.Run();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
Bind(&WriteToFile, path, content));
}
void DevToolsFileHelper::SaveAsFileSelectionCanceled() {
}
void DevToolsFileHelper::AddFileSystem(
const AddFileSystemCallback& callback,
const ShowInfoBarCallback& show_info_bar_callback) {
scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog(
Bind(&DevToolsFileHelper::InnerAddFileSystem,
weak_factory_.GetWeakPtr(),
callback,
show_info_bar_callback),
Bind(callback, FileSystem()),
web_contents_);
select_file_dialog->Show(ui::SelectFileDialog::SELECT_FOLDER,
base::FilePath());
}
void DevToolsFileHelper::InnerAddFileSystem(
const AddFileSystemCallback& callback,
const ShowInfoBarCallback& show_info_bar_callback,
const base::FilePath& path) {
std::string file_system_path = path.AsUTF8Unsafe();
const DictionaryValue* file_systems_paths_value =
profile_->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths);
if (file_systems_paths_value->HasKey(file_system_path)) {
callback.Run(FileSystem());
return;
}
std::string path_display_name = path.AsEndingWithSeparator().AsUTF8Unsafe();
string16 message = l10n_util::GetStringFUTF16(
IDS_DEV_TOOLS_CONFIRM_ADD_FILE_SYSTEM_MESSAGE,
UTF8ToUTF16(path_display_name));
show_info_bar_callback.Run(
message,
Bind(&DevToolsFileHelper::AddUserConfirmedFileSystem,
weak_factory_.GetWeakPtr(),
callback, path));
}
void DevToolsFileHelper::AddUserConfirmedFileSystem(
const AddFileSystemCallback& callback,
const base::FilePath& path,
bool allowed) {
if (!allowed) {
callback.Run(FileSystem());
return;
}
std::string registered_name;
std::string file_system_id = RegisterFileSystem(web_contents_,
path,
&registered_name);
std::string file_system_path = path.AsUTF8Unsafe();
DictionaryPrefUpdate update(profile_->GetPrefs(),
prefs::kDevToolsFileSystemPaths);
DictionaryValue* file_systems_paths_value = update.Get();
file_systems_paths_value->SetWithoutPathExpansion(file_system_path,
Value::CreateNullValue());
FileSystem filesystem = CreateFileSystemStruct(web_contents_,
file_system_id,
registered_name,
file_system_path);
callback.Run(filesystem);
}
void DevToolsFileHelper::RequestFileSystems(
const RequestFileSystemsCallback& callback) {
set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_);
set<std::string>::const_iterator it = file_system_paths.begin();
std::vector<FileSystem> file_systems;
for (; it != file_system_paths.end(); ++it) {
std::string file_system_path = *it;
base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path);
std::string registered_name;
std::string file_system_id = RegisterFileSystem(web_contents_,
path,
&registered_name);
FileSystem filesystem = CreateFileSystemStruct(web_contents_,
file_system_id,
registered_name,
file_system_path);
file_systems.push_back(filesystem);
}
callback.Run(file_systems);
}
void DevToolsFileHelper::RemoveFileSystem(const std::string& file_system_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path);
isolated_context()->RevokeFileSystemByPath(path);
DictionaryPrefUpdate update(profile_->GetPrefs(),
prefs::kDevToolsFileSystemPaths);
DictionaryValue* file_systems_paths_value = update.Get();
file_systems_paths_value->RemoveWithoutPathExpansion(file_system_path, NULL);
}
bool DevToolsFileHelper::IsFileSystemAdded(
const std::string& file_system_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_);
return file_system_paths.find(file_system_path) != file_system_paths.end();
}