Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3862)

Unified Diff: chrome/browser/extensions/extension_file_browser_private_api.cc

Issue 6749021: Added new fileBrowserPrivate and fileHandler extension APIs (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/extension_file_browser_private_api.cc
===================================================================
--- chrome/browser/extensions/extension_file_browser_private_api.cc (revision 80410)
+++ chrome/browser/extensions/extension_file_browser_private_api.cc (working copy)
@@ -4,20 +4,241 @@
#include "chrome/browser/extensions/extension_file_browser_private_api.h"
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/crypto/symmetric_key.h"
+#include "base/hmac.h"
#include "base/json/json_writer.h"
+#include "base/memory/singleton.h"
+#include "base/stringprintf.h"
#include "base/task.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/extensions/extension_function_dispatcher.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/tab_contents/context_menu_utils.h"
+#include "chrome/browser/ui/webui/extension_icon_source.h"
+#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "content/browser/browser_thread.h"
+#include "content/browser/child_process_security_policy.h"
+#include "content/browser/renderer_host/render_process_host.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "webkit/fileapi/file_system_context.h"
+#include "webkit/fileapi/file_system_mount_point_provider.h"
#include "webkit/fileapi/file_system_operation.h"
+#include "webkit/fileapi/file_system_path_manager.h"
#include "webkit/fileapi/file_system_types.h"
+#include "webkit/glue/context_menu.h"
+#define SHA1_SIZE_IN_BITS 160
+
+const char kContextTaskIdSchema[] = "context-task";
+
+const int kReadOnlyFilePermissions = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_EXCLUSIVE_READ |
+ base::PLATFORM_FILE_ASYNC;
+
+const int kReadWriteFilePermissions = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_EXCLUSIVE_READ |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE |
+ base::PLATFORM_FILE_ASYNC |
+ base::PLATFORM_FILE_TRUNCATE |
+ base::PLATFORM_FILE_WRITE_ATTRIBUTES;
+
+bool GetContextMenuItems(Profile* profile,
+ const ContextMenuParams& params,
+ ExtensionMenuItem::List* results) {
+ ExtensionService* service = profile->GetExtensionService();
+ if (!service)
+ return false; // In unit-tests, we may not have an ExtensionService.
+
+ // Get a list of extension id's that have context menu items, and sort it by
+ // the extension's name.
+ ExtensionMenuManager* menu_manager = service->menu_manager();
+ std::set<std::string> ids = menu_manager->ExtensionIds();
+ std::vector<std::pair<std::string, std::string> > sorted_ids;
+ for (std::set<std::string>::iterator i = ids.begin(); i != ids.end(); ++i) {
+ const Extension* extension = service->GetExtensionById(*i, false);
+ if (extension)
+ sorted_ids.push_back(
+ std::pair<std::string, std::string>(extension->name(), *i));
+ }
+
+ if (sorted_ids.empty())
+ return true;
+
+ std::vector<std::pair<std::string, std::string> >::const_iterator i;
+ for (i = sorted_ids.begin(); i != sorted_ids.end(); ++i) {
+ const std::string& extension_id = i->second;
+ const Extension* extension = service->GetExtensionById(extension_id, false);
+ bool can_cross_incognito = service->CanCrossIncognito(extension);
+ const ExtensionMenuItem::List* all_items =
+ menu_manager->MenuItems(extension_id);
+ ExtensionMenuItem::List relevant_items =
+ ContextMenuUtils::GetRelevantExtensionItems(*all_items,
+ params,
+ profile,
+ can_cross_incognito);
+ results->insert(results->end(), relevant_items.begin(),
+ relevant_items.end());
+ }
+ return true;
+}
+
+void CreateContextMenuParams(const GURL& source_url,
+ const std::string& file_url,
+ ContextMenuParams* params) {
+ DCHECK(params);
+ params->is_image_blocked = false;
+ params->spellcheck_enabled = false;
+ params->is_editable = false;
+ params->media_flags = 0;
+ params->edit_flags = 0;
+ params->media_type = WebKit::WebContextMenuData::MediaTypeFile;
+ params->src_url = GURL(file_url);
+ params->page_url = source_url;
+}
+
+// Given the list of selected files, returns array of context menu tasks
+// that are sahred
+bool FindCommonTasks(Profile* profile,
+ const GURL& source_url,
+ ListValue* files_list,
+ ExtensionMenuItem::List* common_tasks) {
+ common_tasks->clear();
+ for (size_t i = 0; i < files_list->GetSize(); ++i) {
+ std::string file_url;
+ if (!files_list->GetString(i, &file_url))
+ return false;
+
+ ContextMenuParams params;
+ CreateContextMenuParams(source_url, file_url, &params);
+
+ ExtensionMenuItem::List file_actions;
+ if (!GetContextMenuItems(profile, params, &file_actions))
+ return false;
+ // If there is nothing to do for one file, the intersection of tasks for all
+ // files will be empty at the end.
+ if (!file_actions.size()) {
+ common_tasks->clear();
+ return true;
+ }
+ // For the very first file, just copy elements.
+ if (i == 0) {
+ common_tasks->insert(common_tasks->begin(),
+ file_actions.begin(),
+ file_actions.end());
+ std::sort(common_tasks->begin(), common_tasks->end());
+ } else if (common_tasks->size()) {
+ // For all additional files, find intersection between the accumulated
+ // and file specific set.
+ std::sort(file_actions.begin(), file_actions.end());
+ ExtensionMenuItem::List intersection(common_tasks->size());
+ ExtensionMenuItem::List::iterator intersection_end =
+ std::set_intersection(common_tasks->begin(),
+ common_tasks->end(),
+ file_actions.begin(),
+ file_actions.end(),
+ intersection.begin());
+ common_tasks->clear();
+ common_tasks->insert(common_tasks->begin(),
+ intersection.begin(),
+ intersection_end);
+ std::sort(common_tasks->begin(), common_tasks->end());
+ }
+ }
+ return true;
+}
+
+// Breaks down task_id that is used between getFileTasks() and executeTask() on
+// its building blocks. task_id field the following structure:
+// <task-type>:<extension-id>/<task-action-id>
+// Currently, the only supported task-type is of 'context'.
+bool CrackTaskIdentifier(const std::string& task_id,
+ std::string* task_type,
+ std::string* target_extension_id,
+ std::string* action_id) {
+ std::string::size_type pos_col = task_id.find(':');
+ if (pos_col == std::string::npos)
+ return false;
+ *task_type = task_id.substr(0, pos_col);
+ std::string::size_type pos_slash = task_id.find('/');
+ if (pos_slash == std::string::npos)
+ return false;
+ *target_extension_id = task_id.substr(pos_col + 1, pos_slash - pos_col - 1);
+ *action_id = task_id.substr(pos_slash + 1);
+ return true;
+}
+
+std::string MakeTaskID(const char* task_schema,
+ const char* extension_id,
+ int action_id) {
+ return base::StringPrintf("%s:%s/%d", task_schema, extension_id, action_id);
+}
+
+// Generates hashes used for filesystem: urls that are sent to 3rd party
+// extension. Hashes generated from extension id and file url.
+// They are valid only during the lifetime of the browser instance.
+class FileHashGenerator {
+ public:
+ ~FileHashGenerator() {}
+ static FileHashGenerator* GetInstance() {
+ return Singleton<FileHashGenerator>::get();
+ }
+ // Generate hash for given url and extension combination.
+ std::string GenerateFileHash(const std::string& file_url,
+ const std::string& extension_id) {
+ std::string data(file_url);
+ data = data.append(extension_id);
+ scoped_ptr<unsigned char> digest(new unsigned char[SHA1_SIZE_IN_BITS/8]);
+ if (!hmac_.Sign(data, digest.get(), SHA1_SIZE_IN_BITS/8))
+ return std::string();
+ std::string output;
+ if (!base::Base64Encode(std::string(reinterpret_cast<char*>(digest.get()),
+ SHA1_SIZE_IN_BITS/8),
+ &output))
+ return std::string();
+ return output;
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<FileHashGenerator>;
+
+ FileHashGenerator() : hmac_(base::HMAC::SHA1) {
+ scoped_ptr<base::SymmetricKey> sym_key(
+ base::SymmetricKey::GenerateRandomKey(base::SymmetricKey::AES,
+ 256));
abarth-chromium 2011/04/06 02:17:32 Should 256 be a named constant? Why are we using
zel 2011/04/06 05:25:17 Added constant kAESKeyLengthInBits. symetric_key_
+ std::string raw_key;
+ sym_key->GetRawKey(&raw_key);
+ hmac_.Init(raw_key);
+ }
+
+ base::HMAC hmac_;
+ DISALLOW_COPY_AND_ASSIGN(FileHashGenerator);
+};
+
class LocalFileSystemCallbackDispatcher
: public fileapi::FileSystemCallbackDispatcher {
public:
explicit LocalFileSystemCallbackDispatcher(
- RequestLocalFileSystemFunction* function) : function_(function) {
+ RequestLocalFileSystemFunctionBase* function,
+ Profile* profile,
+ int child_id,
+ const GURL& source_url,
+ const std::string& file_url)
+ : function_(function),
+ profile_(profile),
+ child_id_(child_id),
+ source_url_(source_url),
+ file_url_(file_url) {
DCHECK(function_);
}
// fileapi::FileSystemCallbackDispatcher overrides.
@@ -38,62 +259,349 @@
}
virtual void DidOpenFileSystem(const std::string& name,
const FilePath& path) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ // Set up file permission access.
+ if (file_url_.length()) {
+ if (!SetupFileAccessPermissions()) {
+ DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
+ return;
+ }
+ } else if (!SetupFileSystemAccessPermissions()) {
+ DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
+ return;
+ }
+
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(function_,
- &RequestLocalFileSystemFunction::RespondSuccessOnUIThread,
+ &RequestLocalFileSystemFunctionBase::RespondSuccessOnUIThread,
name,
- path));
+ path,
+ file_url_));
}
virtual void DidFail(base::PlatformFileError error_code) OVERRIDE {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(function_,
- &RequestLocalFileSystemFunction::RespondFailedOnUIThread,
+ &RequestLocalFileSystemFunctionBase::RespondFailedOnUIThread,
error_code));
}
private:
- RequestLocalFileSystemFunction* function_;
+
+ const Extension* GetExtension() {
+ std::string extension_id = source_url_.GetOrigin().host();
+ ExtensionService* service = profile_->GetExtensionService();
+ if (!service)
+ return NULL;
+ return service->GetExtensionById(extension_id,
+ false); // include_disabled
+ }
+
+ // Checks file url to make sure this particular extension has access to it.
+ // For non-component extensions, hash code of the file url will be verified.
+ bool VerifyFileUrl(const Extension* extension, std::string* base_file_url) {
+ // Component extensions should be able to access any filesystem url exposed
+ // by the provider.
+ if (extension->location() == Extension::COMPONENT
+#ifndef NDEBUG
+ || CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kExposePrivateExtensionApi)
+#endif
+ ) {
+ *base_file_url = file_url_;
+ return true;
+ }
+
+ // For non-component extensions, we need to check file url hash.
+ std::string::size_type pos = file_url_.find('#');
abarth-chromium 2011/04/06 02:17:32 You're storing something magical in the fragment o
zel 2011/04/06 05:25:17 I tried GURL. It does not like filesystem: and doe
+ if (pos == std::string::npos)
+ return false;
+
+ std::string url_hash = file_url_.substr(pos + 1);
+ std::string file_url = file_url_.substr(0, pos);
+ std::string extension_id = source_url_.GetOrigin().host();
+ std::string expected_hash = FileHashGenerator::GetInstance()->
+ GenerateFileHash(file_url, extension_id);
+ if (expected_hash != url_hash)
+ return false;
+ *base_file_url = file_url;
+ return true;
+ }
+
+ // Checks legitimacy of file url and grants RO access permissions for that
+ // file to the target renderer process.
+ bool SetupFileAccessPermissions() {
+ GURL file_origin_url;
+ FilePath virtual_path;
+ fileapi::FileSystemType type;
+ fileapi::FileSystemPathManager* path_manager =
+ profile_->GetFileSystemContext()->path_manager();
+
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return false;
+
+ std::string base_file_url;
+ if (!VerifyFileUrl(extension, &base_file_url))
+ return false;
+
+ if (!path_manager->CrackFileSystemPath(FilePath(base_file_url),
+ &file_origin_url,
+ &type,
+ &virtual_path)) {
+ return false;
+ }
+ // Make sure this url really used by the right caller extension.
+ if (source_url_.GetOrigin() != file_origin_url) {
+ DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
+ return false;
+ }
+ FilePath root_path = path_manager->GetFileSystemRootPathOnFileThread(
+ file_origin_url,
+ fileapi::kFileSystemTypeLocal,
+ FilePath(virtual_path),
+ false); // create
+ FilePath finalFilePath = root_path.Append(virtual_path);\
+
+ // TODO(zelidrag): Add explicit R/W + R/O permissions for non-component
+ // extensions.
+
+ // Grant R/O access permission to non-component extension and R/W to
+ // component extensions.
+ ChildProcessSecurityPolicy::GetInstance()->GrantPermissionsForFile(
+ child_id_, finalFilePath,
+ extension->location() != Extension::COMPONENT ?
+ kReadOnlyFilePermissions : kReadWriteFilePermissions);
+ return true;
+ }
+
+ // Grants file system access permissions to file browser component.
+ bool SetupFileSystemAccessPermissions() {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return false;
+
+ // Make sure that only component extension can access the entire
+ // local file system.
+ if (extension->location() != Extension::COMPONENT
+#ifndef NDEBUG
+ && !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kExposePrivateExtensionApi)
+#endif
+ ) {
+ NOTREACHED() << "Private method access by non-component extension "
+ << extension->id();
+ return false;
+ }
+
+ fileapi::FileSystemPathManager* path_manager =
+ profile_->GetFileSystemContext()->path_manager();
+ fileapi::FileSystemMountPointProvider* provider =
+ path_manager->local_provider();
+ if (!provider)
+ return false;
+
+ // Grant R/W file permissions to the renderer hosting component
+ // extension for all paths exposed by our local file system provider.
+ std::vector<FilePath> root_dirs = provider->GetRootDirectories();
+ for (std::vector<FilePath>::iterator iter = root_dirs.begin();
+ iter != root_dirs.end();
+ ++iter) {
+ ChildProcessSecurityPolicy::GetInstance()->GrantPermissionsForFile(
+ child_id_, *iter, kReadWriteFilePermissions);
+ }
+ return true;
+ }
+
+ RequestLocalFileSystemFunctionBase* function_;
+ Profile* profile_;
+ // Renderer process id.
+ int child_id_;
+ // Extension source URL.
+ GURL source_url_;
+ std::string file_url_;
DISALLOW_COPY_AND_ASSIGN(LocalFileSystemCallbackDispatcher);
};
-RequestLocalFileSystemFunction::RequestLocalFileSystemFunction() {
-}
-
-RequestLocalFileSystemFunction::~RequestLocalFileSystemFunction() {
-}
-
-bool RequestLocalFileSystemFunction::RunImpl() {
+void RequestLocalFileSystemFunctionBase::RequestOnFileThread(
+ const GURL& source_url, const std::string& file_url) {
fileapi::FileSystemOperation* operation =
new fileapi::FileSystemOperation(
- new LocalFileSystemCallbackDispatcher(this),
+ new LocalFileSystemCallbackDispatcher(
+ this,
+ profile(),
+ dispatcher()->render_view_host()->process()->id(),
+ source_url,
+ file_url),
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
profile()->GetFileSystemContext(),
NULL);
- GURL origin_url = source_url().GetOrigin();
+ GURL origin_url = source_url.GetOrigin();
operation->OpenFileSystem(origin_url, fileapi::kFileSystemTypeLocal,
false); // create
+}
+
+bool RequestLocalFileSystemFunctionBase::RunImpl() {
+ std::string file_url;
+ if (args_->GetSize())
+ args_->GetString(0, &file_url);
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ NewRunnableMethod(this,
+ &RequestLocalFileSystemFunctionBase::RequestOnFileThread,
+ source_url_,
+ file_url));
// Will finish asynchronously.
return true;
}
-void RequestLocalFileSystemFunction::RespondSuccessOnUIThread(
- const std::string& name, const FilePath& path) {
+void RequestLocalFileSystemFunctionBase::RespondSuccessOnUIThread(
+ const std::string& name, const FilePath& path,
+ const std::string& file_url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
result_.reset(new DictionaryValue());
DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get());
dict->SetString("name", name);
dict->SetString("path", path.value());
dict->SetInteger("error", base::PLATFORM_FILE_OK);
+ if (file_url.size())
+ dict->SetString("fileUrl", file_url);
SendResponse(true);
}
-void RequestLocalFileSystemFunction::RespondFailedOnUIThread(
+void RequestLocalFileSystemFunctionBase::RespondFailedOnUIThread(
base::PlatformFileError error_code) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
result_.reset(new DictionaryValue());
DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get());
dict->SetInteger("error", static_cast<int>(error_code));
+ SendResponse(false);
+}
+
+bool GetFileTasksFileBrowserFunction::RunImpl() {
+ ListValue* files_list = NULL;
+ if (!args_->GetList(0, &files_list))
+ return false;
+
+ result_.reset(new ListValue());
+ ListValue* result_list = reinterpret_cast<ListValue*>(result_.get());
+
+ ExtensionMenuItem::List common_tasks;
+ if (!FindCommonTasks(profile_, source_url_, files_list, &common_tasks))
+ return false;
+
+ ExtensionService* service = profile_->GetExtensionService();
+ for (ExtensionMenuItem::List::iterator iter = common_tasks.begin();
+ iter != common_tasks.end();
+ ++iter) {
+ if ((*iter)->type() != ExtensionMenuItem::NORMAL)
+ continue;
+ const std::string extension_id = (*iter)->extension_id();
+ const Extension* extension = service->GetExtensionById(extension_id, false);
+ if (!extension) {
+ LOG(WARNING) << "Disabled extension" << extension_id;
+ continue;
+ }
+ DictionaryValue* task = new DictionaryValue();
+ task->SetString("taskId", MakeTaskID(kContextTaskIdSchema,
+ extension_id.c_str(),
+ (*iter)->id().uid));
+ task->SetString("title", (*iter)->title());
+ GURL icon =
+ ExtensionIconSource::GetIconURL(extension,
+ Extension::EXTENSION_ICON_SMALLISH,
+ ExtensionIconSet::MATCH_BIGGER,
+ false); // grayscale
+ task->SetString("iconUrl", icon.spec());
+ result_list->Append(task);
+ }
+
+ // TODO(zelidrag, serya): Add intent content tasks to result_list once we
+ // implement that API.
SendResponse(true);
+ return true;
}
+bool ExecuteTasksFileBrowserFunction::RunImpl() {
+ // First param is task id that was to the extension with getFileTasks call.
+ std::string task_id;
+ if (!args_->GetString(0, &task_id) || !task_id.size())
+ return false;
+
+ // The second param is the list of files that need to be executed with this
+ // task.
+ ListValue* files_list = NULL;
+ if (!args_->GetList(1, &files_list))
+ return false;
+
+ if (!files_list->GetSize())
+ return true;
+
+ std::string task_type;
+ std::string target_extension_id;
+ std::string action_id;
+ if (!CrackTaskIdentifier(task_id, &task_type, &target_extension_id,
+ &action_id)) {
+ return false;
+ }
+
+ if (task_type == kContextTaskIdSchema) {
+ ExecuteContextMenuTasks(target_extension_id, action_id, files_list);
+ } else {
+ LOG(WARNING) << "Unsupported task type of: " << task_type;
+ // TODO(zelidrag, serya): Add intent content tasks here once we implement
+ // that API.
+ return false;
+ }
+ SendResponse(true);
+ return true;
+}
+
+std::string ExecuteTasksFileBrowserFunction::MakeSafeFileUrl(
+ const std::string& origin_file_url, const std::string& extension_id) {
+ // Replace extension part of the url with one from the target.
+ std::string::size_type pos = origin_file_url.find("/local/");
abarth-chromium 2011/04/06 02:17:32 What about a URL like the following: filesystem:h
zel 2011/04/06 05:25:17 I used GURL as much as I could. This class is not
+ if (pos == std::string::npos)
+ return std::string();
+ std::string file_url = base::StringPrintf(
+ "filesystem:chrome-extension://%s/%s",
+ extension_id.c_str(),
+ origin_file_url.substr(pos + 1).c_str());
+ std::string hash =
+ FileHashGenerator::GetInstance()->GenerateFileHash(file_url,
+ extension_id);
+ file_url = file_url.append("#");
abarth-chromium 2011/04/06 02:17:32 What if the file_url already has a fragment? You
zel 2011/04/06 05:25:17 Same as above.
+ return file_url.append(hash);
+}
+
+bool ExecuteTasksFileBrowserFunction::ExecuteContextMenuTasks(
+ const std::string& extension_id, const std::string& action_id,
+ ListValue* files_list) {
+ ExtensionMenuManager* manager =
+ profile_->GetExtensionService()->menu_manager();
+ for (size_t i = 0; i < files_list->GetSize(); i++) {
+ std::string origin_file_url;
+ if (!files_list->GetString(i, &origin_file_url)) {
+ result_.reset(new FundamentalValue(false));
+ SendResponse(true);
+ return false;
+ }
+ std::string file_url = MakeSafeFileUrl(origin_file_url, extension_id);
+ if (!file_url.size()) {
+ result_.reset(new FundamentalValue(false));
+ SendResponse(true);
+ return false;
+ }
+ ContextMenuParams params;
+ CreateContextMenuParams(source_url_, file_url, &params);
+ ExtensionMenuItem::Id menuItemId(profile_, extension_id,
+ atoi(action_id.c_str()));
+ manager->ExecuteCommand(profile_,
+ NULL, // tab_contents, not needed in args.
+ params,
+ menuItemId);
+ }
+ result_.reset(new FundamentalValue(true));
+ SendResponse(true);
+ return true;
+}

Powered by Google App Engine
This is Rietveld 408576698