summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-02 08:03:55 +0000
committerkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-02 08:03:55 +0000
commitaf8b34000feb2fd001ecd940191c2ac38583be44 (patch)
tree310f55d98dae863679d26d7c38ccab368fadb5ec /webkit
parent5b7b9598c5e69e6d7c11d073205bd87d29a9b39f (diff)
downloadchromium_src-af8b34000feb2fd001ecd940191c2ac38583be44.zip
chromium_src-af8b34000feb2fd001ecd940191c2ac38583be44.tar.gz
chromium_src-af8b34000feb2fd001ecd940191c2ac38583be44.tar.bz2
Add isolated filesystem context for directory (and file) drag-and-drop support.
- Adding IsolatedContext class which keeps track of the isolated filesystem namespaces for dropped files/directories - Adding WebDropData.filesystem_id to send the filesystem ID to the renderer - Adding GrantAccessFileSystem() method to ChildProcessSecurityPolicy (the permission is to be revoked when the child goes away) design doc (internal): https://docs.google.com/a/google.com/document/d/1hSdCHy7qWXYSp9nlUT7JJd5Jli_fMWQxgyQBM1GhRVk/edit?hl=en_US BUG=99823 TEST=test_shell_tests:IsolatedContextTest* Review URL: https://chromiumcodereview.appspot.com/9204009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@120178 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/fileapi/isolated_context.cc143
-rw-r--r--webkit/fileapi/isolated_context.h106
-rw-r--r--webkit/fileapi/isolated_context_unittest.cc157
-rw-r--r--webkit/fileapi/webkit_fileapi.gypi2
-rw-r--r--webkit/glue/webdropdata.cc4
-rw-r--r--webkit/glue/webdropdata.h3
-rw-r--r--webkit/tools/test_shell/test_shell.gypi1
7 files changed, 416 insertions, 0 deletions
diff --git a/webkit/fileapi/isolated_context.cc b/webkit/fileapi/isolated_context.cc
new file mode 100644
index 0000000..11b5eb7
--- /dev/null
+++ b/webkit/fileapi/isolated_context.cc
@@ -0,0 +1,143 @@
+// 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 "webkit/fileapi/isolated_context.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+
+namespace fileapi {
+
+static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+IsolatedContext* IsolatedContext::GetInstance() {
+ return &g_isolated_context.Get();
+}
+
+std::string IsolatedContext::RegisterIsolatedFileSystem(
+ const std::set<FilePath>& files) {
+ base::AutoLock locker(lock_);
+ std::string filesystem_id = GetNewFileSystemId();
+ // Stores basename to fullpath map, as we store the basenames as
+ // the filesystem's toplevel entries.
+ PathMap toplevels;
+ for (std::set<FilePath>::const_iterator iter = files.begin();
+ iter != files.end(); ++iter) {
+ // If the given path contains any '..' or is not an absolute path,
+ // return an empty (invalid) id.
+ if (iter->ReferencesParent() || !iter->IsAbsolute())
+ return std::string();
+
+ // Register the basename -> fullpath map. (We only expose the basename
+ // part to the user scripts)
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ FilePath fullpath = iter->NormalizeWindowsPathSeparators();
+#else
+ FilePath fullpath = *iter;
+#endif
+ FilePath basename = iter->BaseName();
+ // TODO(kinuko): Append a suffix or something if we have multiple pathnames
+ // with the same basename. For now we only register the first one.
+ toplevels.insert(std::make_pair(basename, fullpath));
+ }
+ toplevel_map_[filesystem_id] = toplevels;
+ return filesystem_id;
+}
+
+// Revoke any registered drag context for the child_id.
+void IsolatedContext::RevokeIsolatedFileSystem(
+ const std::string& filesystem_id) {
+ base::AutoLock locker(lock_);
+ toplevel_map_.erase(filesystem_id);
+}
+
+bool IsolatedContext::CrackIsolatedPath(const FilePath& virtual_path,
+ std::string* filesystem_id,
+ FilePath* platform_path) const {
+ DCHECK(filesystem_id);
+ DCHECK(platform_path);
+
+ // This should not contain any '..' references.
+ if (virtual_path.ReferencesParent())
+ return false;
+
+ // The virtual_path should comprise <filesystem_id> and <relative_path> parts.
+ std::vector<FilePath::StringType> components;
+ virtual_path.GetComponents(&components);
+ if (components.size() < 1)
+ return false;
+
+ base::AutoLock locker(lock_);
+ std::string fsid = FilePath(components[0]).MaybeAsASCII();
+ if (fsid.empty())
+ return false;
+ IDToPathMap::const_iterator found_toplevels = toplevel_map_.find(fsid);
+ if (found_toplevels == toplevel_map_.end())
+ return false;
+ *filesystem_id = fsid;
+ if (components.size() == 1) {
+ platform_path->clear();
+ return true;
+ }
+ // components[1] should be a toplevel path of the dropped paths.
+ PathMap::const_iterator found = found_toplevels->second.find(
+ FilePath(components[1]));
+ if (found == found_toplevels->second.end())
+ return false;
+ FilePath path = found->second;
+ for (size_t i = 2; i < components.size(); ++i) {
+ path = path.Append(components[i]);
+ }
+ *platform_path = path;
+ return true;
+}
+
+bool IsolatedContext::GetTopLevelPaths(std::string filesystem_id,
+ std::vector<FilePath>* paths) const {
+ DCHECK(paths);
+ IDToPathMap::const_iterator found = toplevel_map_.find(filesystem_id);
+ if (found == toplevel_map_.end())
+ return false;
+ paths->clear();
+ PathMap toplevels = found->second;
+ for (PathMap::const_iterator iter = toplevels.begin();
+ iter != toplevels.end(); ++iter) {
+ // Each path map entry holds a map of a toplevel name to its full path.
+ paths->push_back(iter->second);
+ }
+ return true;
+}
+
+FilePath IsolatedContext::CreateVirtualPath(
+ const std::string& filesystem_id, const FilePath& relative_path) const {
+ FilePath full_path;
+ full_path = full_path.AppendASCII(filesystem_id);
+ if (relative_path.value() != FILE_PATH_LITERAL("/"))
+ full_path = full_path.Append(relative_path);
+ return full_path;
+}
+
+IsolatedContext::IsolatedContext() {
+}
+
+IsolatedContext::~IsolatedContext() {
+}
+
+std::string IsolatedContext::GetNewFileSystemId() const {
+ // Returns an arbitrary random string which must be unique in the map.
+ uint32 random_data[4];
+ std::string id;
+ do {
+ base::RandBytes(random_data, sizeof(random_data));
+ id = base::HexEncode(random_data, sizeof(random_data));
+ } while (toplevel_map_.find(id) != toplevel_map_.end());
+ return id;
+}
+
+} // namespace fileapi
diff --git a/webkit/fileapi/isolated_context.h b/webkit/fileapi/isolated_context.h
new file mode 100644
index 0000000..e9233c3
--- /dev/null
+++ b/webkit/fileapi/isolated_context.h
@@ -0,0 +1,106 @@
+// 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.
+
+#ifndef WEBKIT_FILEAPI_ISOLATED_CONTEXT_H_
+#define WEBKIT_FILEAPI_ISOLATED_CONTEXT_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "base/lazy_instance.h"
+
+namespace fileapi {
+
+// Manages isolated filename namespaces. A namespace is simply defined as a
+// set of file paths and corresponding filesystem ID. This context class is
+// a singleton and access to the context is thread-safe (protected with a
+// lock).
+// Some methods of this class are virtual just for mocking.
+class IsolatedContext {
+ public:
+ // The instance is lazily created per browser process.
+ static IsolatedContext* GetInstance();
+
+ // Registers a new file isolated filesystem with the given set of files
+ // and returns the new filesystem_id. The files are registered with their
+ // basenames as their keys so that later we can resolve the full paths
+ // for the given file name in the isolated filesystem. We only expose the
+ // key and the ID for the newly created filesystem to the renderer for
+ // the sake of security.
+ //
+ // The renderer will be sending filesystem requests with a virtual path like
+ // '/<filesystem_id>/<relative_path_from_the_basename_of_dropped_path>'
+ // for which we could crack in the browser by calling CrackIsolatedPath to
+ // get the full path.
+ //
+ // For example: if a dropped file has a path like '/a/b/foo' we register
+ // the path with the key 'foo' in the newly created filesystem.
+ // Later if the context is asked to crack a virtual path like '/<fsid>/foo'
+ // it can properly return the original path '/a/b/foo' by looking up the
+ // internal mapping. Similarly if a dropped entry is a directory and its
+ // path is like '/a/b/dir' a virtual path like '/<fsid>/dir/foo' can be
+ // cracked into '/a/b/dir/foo'.
+ //
+ // This may return an empty string (thus invalid as an ID) if the given
+ // file set contains non absolute paths.
+ std::string RegisterIsolatedFileSystem(const std::set<FilePath>& fileset);
+
+ // Revokes filesystem specified by the given filesystem_id.
+ void RevokeIsolatedFileSystem(const std::string& filesystem_id);
+
+ // Cracks the given |virtual_path| (which should look like
+ // "/<filesystem_id>/<relative_path>") and populates the |filesystem_id|
+ // and |platform_path| if the embedded <filesystem_id> is registered
+ // to this context.
+ // Returns false if the given virtual_path or the cracked filesystem_id
+ // is not valid.
+ // Note that |platform_path| is set to an empty path if |virtual_path| has no
+ // <relative_path> part (i.e. pointing to the virtual root).
+ bool CrackIsolatedPath(const FilePath& virtual_path,
+ std::string* filesystem_id,
+ FilePath* platform_path) const;
+
+ // Returns a vector of the full paths of the top-level entry paths
+ // registered for the |filesystem_id|. Returns false if the
+ // |filesystem_is| is not valid.
+ bool GetTopLevelPaths(std::string filesystem_id,
+ std::vector<FilePath>* paths) const;
+
+ // Returns the virtual path that looks like /<filesystem_id>/<relative_path>.
+ // This method is only used by the testing code (as the actual virtual path
+ // in the real code is created in the renderer side).
+ FilePath CreateVirtualPath(const std::string& filesystem_id,
+ const FilePath& relative_path) const;
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<IsolatedContext>;
+
+ // Maps from filesystem id to a path conversion map for top-level entries.
+ typedef std::map<FilePath, FilePath> PathMap;
+ typedef std::map<std::string, PathMap> IDToPathMap;
+
+ // Obtain an instance of this class via GetInstance().
+ IsolatedContext();
+ ~IsolatedContext();
+
+ // Returns a new filesystem_id. Called with lock.
+ std::string GetNewFileSystemId() const;
+
+ // This lock needs to be obtained when accessing the fileset_.
+ mutable base::Lock lock_;
+
+ // Maps the toplevel entries to the filesystem id.
+ IDToPathMap toplevel_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(IsolatedContext);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_FILEAPI_ISOLATED_CONTEXT_H_
diff --git a/webkit/fileapi/isolated_context_unittest.cc b/webkit/fileapi/isolated_context_unittest.cc
new file mode 100644
index 0000000..a882993
--- /dev/null
+++ b/webkit/fileapi/isolated_context_unittest.cc
@@ -0,0 +1,157 @@
+// 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 "webkit/fileapi/isolated_context.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+namespace fileapi {
+
+namespace {
+
+const FilePath kTestPaths[] = {
+ FilePath(FPL("/a/b")),
+ FilePath(FPL("/c/d/e/f/g")),
+ FilePath(FPL("/h/")),
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ FilePath(FPL("c:/foo/bar")),
+ FilePath(FPL("x:\\foo\\baz")),
+ FilePath(FPL("\\foo\\boom")),
+#endif
+};
+
+} // namespace
+
+class IsolatedContextTest : public testing::Test {
+ public:
+ IsolatedContextTest()
+ : fileset_(kTestPaths, kTestPaths + arraysize(kTestPaths)) {
+ }
+
+ void SetUp() {
+ id_ = IsolatedContext::GetInstance()->RegisterIsolatedFileSystem(fileset_);
+ ASSERT_FALSE(id_.empty());
+ }
+
+ void TearDown() {
+ IsolatedContext::GetInstance()->RevokeIsolatedFileSystem(id_);
+ }
+
+ IsolatedContext* isolated_context() const {
+ return IsolatedContext::GetInstance();
+ }
+
+ protected:
+ std::string id_;
+ std::set<FilePath> fileset_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IsolatedContextTest);
+};
+
+TEST_F(IsolatedContextTest, RegisterAndRevokeTest) {
+ // See if the returned top-level entries match with what we registered.
+ std::vector<FilePath> toplevels;
+ ASSERT_TRUE(isolated_context()->GetTopLevelPaths(id_, &toplevels));
+ ASSERT_EQ(fileset_.size(), toplevels.size());
+ for (size_t i = 0; i < toplevels.size(); ++i) {
+ ASSERT_TRUE(fileset_.find(toplevels[i]) != fileset_.end());
+ }
+
+ // See if the basename of each registered kTestPaths (that is what we
+ // register in SetUp() by RegisterIsolatedFileSystem) is properly cracked as
+ // a valid virtual path in the isolated filesystem.
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i) {
+ FilePath virtual_path = isolated_context()->CreateVirtualPath(
+ id_, kTestPaths[i].BaseName());
+ std::string cracked_id;
+ FilePath cracked_path;
+ ASSERT_TRUE(isolated_context()->CrackIsolatedPath(
+ virtual_path, &cracked_id, &cracked_path));
+ ASSERT_EQ(kTestPaths[i].value(), cracked_path.value());
+ ASSERT_EQ(id_, cracked_id);
+ }
+
+ // Revoking the current one and registering a new (empty) one.
+ isolated_context()->RevokeIsolatedFileSystem(id_);
+ std::string id2 = isolated_context()->RegisterIsolatedFileSystem(
+ std::set<FilePath>());
+
+ // Make sure the GetTopLevelPaths returns true only for the new one.
+ ASSERT_TRUE(isolated_context()->GetTopLevelPaths(id2, &toplevels));
+ ASSERT_FALSE(isolated_context()->GetTopLevelPaths(id_, &toplevels));
+
+ isolated_context()->RevokeIsolatedFileSystem(id2);
+}
+
+TEST_F(IsolatedContextTest, CrackWithRelativePaths) {
+ const struct {
+ FilePath::StringType path;
+ bool valid;
+ } relatives[] = {
+ { FPL("foo"), true },
+ { FPL("foo/bar"), true },
+ { FPL(".."), false },
+ { FPL("foo/.."), false },
+ { FPL("foo/../bar"), false },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+# define SHOULD_FAIL_WITH_WIN_SEPARATORS false
+#else
+# define SHOULD_FAIL_WITH_WIN_SEPARATORS true
+#endif
+ { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS },
+ { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS },
+ };
+
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i) {
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(relatives); ++j) {
+ SCOPED_TRACE(testing::Message() << "Testing "
+ << kTestPaths[i].value() << " " << relatives[j].path);
+ FilePath virtual_path = isolated_context()->CreateVirtualPath(
+ id_, kTestPaths[i].BaseName().Append(relatives[j].path));
+ std::string cracked_id;
+ FilePath cracked_path;
+ if (!relatives[j].valid) {
+ ASSERT_FALSE(isolated_context()->CrackIsolatedPath(
+ virtual_path, &cracked_id, &cracked_path));
+ continue;
+ }
+ ASSERT_TRUE(isolated_context()->CrackIsolatedPath(
+ virtual_path, &cracked_id, &cracked_path));
+ ASSERT_EQ(kTestPaths[i].Append(relatives[j].path).value(),
+ cracked_path.value());
+ ASSERT_EQ(id_, cracked_id);
+ }
+ }
+}
+
+TEST_F(IsolatedContextTest, TestWithVirtualRoot) {
+ std::string cracked_id;
+ FilePath cracked_path;
+ const FilePath root(FPL("/"));
+
+ // Trying to crack virtual root "/" returns true but with empty cracked path
+ // as "/" of the isolated filesystem is a pure virtual directory
+ // that has no corresponding platform directory.
+ FilePath virtual_path = isolated_context()->CreateVirtualPath(id_, root);
+ ASSERT_TRUE(isolated_context()->CrackIsolatedPath(
+ virtual_path, &cracked_id, &cracked_path));
+ ASSERT_EQ(FPL(""), cracked_path.value());
+ ASSERT_EQ(id_, cracked_id);
+
+ // Trying to crack "/foo" should fail (because "foo" is not the one
+ // included in the kTestPaths).
+ virtual_path = isolated_context()->CreateVirtualPath(
+ id_, FilePath(FPL("foo")));
+ ASSERT_FALSE(isolated_context()->CrackIsolatedPath(
+ virtual_path, &cracked_id, &cracked_path));
+}
+
+} // namespace fileapi
diff --git a/webkit/fileapi/webkit_fileapi.gypi b/webkit/fileapi/webkit_fileapi.gypi
index be1a194..a645cb4 100644
--- a/webkit/fileapi/webkit_fileapi.gypi
+++ b/webkit/fileapi/webkit_fileapi.gypi
@@ -52,6 +52,8 @@
'file_system_util.h',
'file_writer_delegate.cc',
'file_writer_delegate.h',
+ 'isolated_context.cc',
+ 'isolated_context.h',
'local_file_util.cc',
'local_file_util.h',
'native_file_util.cc',
diff --git a/webkit/glue/webdropdata.cc b/webkit/glue/webdropdata.cc
index bb898fa..53e16d4 100644
--- a/webkit/glue/webdropdata.cc
+++ b/webkit/glue/webdropdata.cc
@@ -54,6 +54,10 @@ WebDragData WebDropData::ToDragData() const {
result.setURL(url);
result.setURLTitle(url_title);
result.setFilenames(filenames);
+#ifdef WEBKIT_DRAG_DROP_SUPPORT_FILESYSTEM
+ // TODO(kinuko): remove this ifdef once we update the WebKit API.
+ result.setFilesystemId(filesystem_id);
+#endif
result.setPlainText(plain_text);
result.setHTMLText(text_html);
result.setHTMLBaseURL(html_base_url);
diff --git a/webkit/glue/webdropdata.h b/webkit/glue/webdropdata.h
index dc6e3fb..14387b3 100644
--- a/webkit/glue/webdropdata.h
+++ b/webkit/glue/webdropdata.h
@@ -41,6 +41,9 @@ struct WEBKIT_GLUE_EXPORT WebDropData {
// User is dropping one or more files on the webview.
std::vector<string16> filenames;
+ // Isolated filesystem ID for the files being dragged on the webview.
+ string16 filesystem_id;
+
// User is dragging plain text into the webview.
string16 plain_text;
diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi
index 797abec..f261f51 100644
--- a/webkit/tools/test_shell/test_shell.gypi
+++ b/webkit/tools/test_shell/test_shell.gypi
@@ -414,6 +414,7 @@
'../../fileapi/file_system_test_helper.h',
'../../fileapi/file_system_usage_cache_unittest.cc',
'../../fileapi/file_system_util_unittest.cc',
+ '../../fileapi/isolated_context_unittest.cc',
'../../fileapi/local_file_util_unittest.cc',
'../../fileapi/mock_file_system_options.cc',
'../../fileapi/mock_file_system_options.h',