diff options
author | kinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-02 08:03:55 +0000 |
---|---|---|
committer | kinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-02 08:03:55 +0000 |
commit | af8b34000feb2fd001ecd940191c2ac38583be44 (patch) | |
tree | 310f55d98dae863679d26d7c38ccab368fadb5ec /webkit | |
parent | 5b7b9598c5e69e6d7c11d073205bd87d29a9b39f (diff) | |
download | chromium_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.cc | 143 | ||||
-rw-r--r-- | webkit/fileapi/isolated_context.h | 106 | ||||
-rw-r--r-- | webkit/fileapi/isolated_context_unittest.cc | 157 | ||||
-rw-r--r-- | webkit/fileapi/webkit_fileapi.gypi | 2 | ||||
-rw-r--r-- | webkit/glue/webdropdata.cc | 4 | ||||
-rw-r--r-- | webkit/glue/webdropdata.h | 3 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gypi | 1 |
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', |