diff options
author | yoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-23 21:25:16 +0000 |
---|---|---|
committer | yoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-23 21:25:16 +0000 |
commit | 993da5e316353bf36dc27df8e01e91b36ad3cab1 (patch) | |
tree | 1962a24c54d212e1d459eb3f58c8f2e478cc2e6c /extensions | |
parent | 5799a694a40b1c443788df56aa749d1c059577fd (diff) | |
download | chromium_src-993da5e316353bf36dc27df8e01e91b36ad3cab1.zip chromium_src-993da5e316353bf36dc27df8e01e91b36ad3cab1.tar.gz chromium_src-993da5e316353bf36dc27df8e01e91b36ad3cab1.tar.bz2 |
Move CrxFile, FileReader, ExtensionResource to src/extensions.
Also move ExtensionResource to extensions namespace.
Also clean up the GenerateId and ShouldLocalizeManfest interfaces.
BUG=159265
Review URL: https://chromiumcodereview.appspot.com/12578008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@190078 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/DEPS | 14 | ||||
-rw-r--r-- | extensions/browser/DEPS | 3 | ||||
-rw-r--r-- | extensions/browser/file_reader.cc | 33 | ||||
-rw-r--r-- | extensions/browser/file_reader.h | 44 | ||||
-rw-r--r-- | extensions/browser/file_reader_unittest.cc | 105 | ||||
-rw-r--r-- | extensions/common/DEPS | 4 | ||||
-rw-r--r-- | extensions/common/constants.cc | 9 | ||||
-rw-r--r-- | extensions/common/constants.h | 14 | ||||
-rw-r--r-- | extensions/common/crx_file.cc | 68 | ||||
-rw-r--r-- | extensions/common/crx_file.h | 76 | ||||
-rw-r--r-- | extensions/common/extension_resource.cc | 126 | ||||
-rw-r--r-- | extensions/common/extension_resource.h | 89 | ||||
-rw-r--r-- | extensions/common/extension_resource_unittest.cc | 163 | ||||
-rw-r--r-- | extensions/common/id_util.cc | 73 | ||||
-rw-r--r-- | extensions/common/id_util.h | 35 | ||||
-rw-r--r-- | extensions/common/id_util_unittest.cc | 44 |
16 files changed, 895 insertions, 5 deletions
diff --git a/extensions/DEPS b/extensions/DEPS index 3a88773..68da161 100644 --- a/extensions/DEPS +++ b/extensions/DEPS @@ -1,5 +1,17 @@ include_rules = [ "+base", + "+content/public/common", + "+crypto", "+googleurl", - "+testing" + "+testing", + "+ui" ] + +# More specific rules for what we are allowed to include. +specific_include_rules = { + ".*test\.cc": [ + # For chrome::TEST_DATA_DIR; test data should be migrated to src/extensions. + "+chrome/common/chrome_paths.h", + "+content/public/test", + ] +} diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS new file mode 100644 index 0000000..1c35d9c --- /dev/null +++ b/extensions/browser/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/browser", +] diff --git a/extensions/browser/file_reader.cc b/extensions/browser/file_reader.cc new file mode 100644 index 0000000..0ae8ccd --- /dev/null +++ b/extensions/browser/file_reader.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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 "extensions/browser/file_reader.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +FileReader::FileReader(const extensions::ExtensionResource& resource, + const Callback& callback) + : resource_(resource), + callback_(callback), + origin_loop_(MessageLoop::current()) { +} + +void FileReader::Start() { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&FileReader::ReadFileOnBackgroundThread, this)); +} + +FileReader::~FileReader() {} + +void FileReader::ReadFileOnBackgroundThread() { + std::string data; + bool success = file_util::ReadFileToString(resource_.GetFilePath(), &data); + origin_loop_->PostTask(FROM_HERE, base::Bind(callback_, success, data)); +} diff --git a/extensions/browser/file_reader.h b/extensions/browser/file_reader.h new file mode 100644 index 0000000..f103cd3 --- /dev/null +++ b/extensions/browser/file_reader.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 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 EXTENSIONS_BROWSER_FILE_READER_H_ +#define EXTENSIONS_BROWSER_FILE_READER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "extensions/common/extension_resource.h" + +class MessageLoop; + +// This file defines an interface for reading a file asynchronously on a +// background thread. +// Consider abstracting out a FilePathProvider (ExtensionResource) and moving +// back to chrome/browser/net if other subsystems want to use it. +class FileReader : public base::RefCountedThreadSafe<FileReader> { + public: + // Reports success or failure and the data of the file upon success. + typedef base::Callback<void(bool, const std::string&)> Callback; + + FileReader(const extensions::ExtensionResource& resource, + const Callback& callback); + + // Called to start reading the file on a background thread. Upon completion, + // the callback will be notified of the results. + void Start(); + + private: + friend class base::RefCountedThreadSafe<FileReader>; + + virtual ~FileReader(); + + void ReadFileOnBackgroundThread(); + + extensions::ExtensionResource resource_; + Callback callback_; + MessageLoop* origin_loop_; +}; + +#endif // EXTENSIONS_BROWSER_FILE_READER_H_ diff --git a/extensions/browser/file_reader_unittest.cc b/extensions/browser/file_reader_unittest.cc new file mode 100644 index 0000000..3c3a4d2 --- /dev/null +++ b/extensions/browser/file_reader_unittest.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "content/public/test/test_browser_thread.h" +#include "extensions/browser/file_reader.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; + +namespace { + +class FileReaderTest : public testing::Test { + public: + FileReaderTest() : file_thread_(BrowserThread::FILE) { + file_thread_.Start(); + } + private: + MessageLoop message_loop_; + content::TestBrowserThread file_thread_; +}; + +class Receiver { + public: + Receiver() : succeeded_(false) { + } + + FileReader::Callback NewCallback() { + return base::Bind(&Receiver::DidReadFile, base::Unretained(this)); + } + + bool succeeded() const { return succeeded_; } + const std::string& data() const { return data_; } + + private: + void DidReadFile(bool success, const std::string& data) { + succeeded_ = success; + data_ = data; + MessageLoop::current()->Quit(); + } + + bool succeeded_; + std::string data_; +}; + +void RunBasicTest(const char* filename) { + base::FilePath path; + PathService::Get(chrome::DIR_TEST_DATA, &path); + std::string extension_id = extensions::id_util::GenerateId("test"); + extensions::ExtensionResource resource( + extension_id, path, base::FilePath().AppendASCII(filename)); + path = path.AppendASCII(filename); + + std::string file_contents; + bool file_exists = file_util::ReadFileToString(path, &file_contents); + + Receiver receiver; + + scoped_refptr<FileReader> file_reader( + new FileReader(resource, receiver.NewCallback())); + file_reader->Start(); + + MessageLoop::current()->Run(); + + EXPECT_EQ(file_exists, receiver.succeeded()); + EXPECT_EQ(file_contents, receiver.data()); +} + +TEST_F(FileReaderTest, SmallFile) { + RunBasicTest("title1.html"); +} + +TEST_F(FileReaderTest, BiggerFile) { + RunBasicTest("download-test1.lib"); +} + +TEST_F(FileReaderTest, NonExistantFile) { + base::FilePath path; + PathService::Get(chrome::DIR_TEST_DATA, &path); + std::string extension_id = extensions::id_util::GenerateId("test"); + extensions::ExtensionResource resource(extension_id, path, base::FilePath( + FILE_PATH_LITERAL("file_that_does_not_exist"))); + path = path.AppendASCII("file_that_does_not_exist"); + + Receiver receiver; + + scoped_refptr<FileReader> file_reader( + new FileReader(resource, receiver.NewCallback())); + file_reader->Start(); + + MessageLoop::current()->Run(); + + EXPECT_FALSE(receiver.succeeded()); +} + +} // namespace diff --git a/extensions/common/DEPS b/extensions/common/DEPS deleted file mode 100644 index 7be5af8..0000000 --- a/extensions/common/DEPS +++ /dev/null @@ -1,4 +0,0 @@ -include_rules = [ - "+content/public/common", - "+ui/gfx" -] diff --git a/extensions/common/constants.cc b/extensions/common/constants.cc index ecafa43..d931137 100644 --- a/extensions/common/constants.cc +++ b/extensions/common/constants.cc @@ -8,4 +8,13 @@ namespace extensions { const char kExtensionScheme[] = "chrome-extension"; +const base::FilePath::CharType kManifestFilename[] = + FILE_PATH_LITERAL("manifest.json"); +const base::FilePath::CharType kLocaleFolder[] = + FILE_PATH_LITERAL("_locales"); +const base::FilePath::CharType kMessagesFilename[] = + FILE_PATH_LITERAL("messages.json"); +const base::FilePath::CharType kPlatformSpecificFolder[] = + FILE_PATH_LITERAL("_platform_specific"); + } // namespace extensions diff --git a/extensions/common/constants.h b/extensions/common/constants.h index f601b38..31a4985 100644 --- a/extensions/common/constants.h +++ b/extensions/common/constants.h @@ -5,11 +5,25 @@ #ifndef EXTENSIONS_COMMON_CONSTANTS_H_ #define EXTENSIONS_COMMON_CONSTANTS_H_ +#include "base/files/file_path.h" + namespace extensions { // Scheme we serve extension content from. extern const char kExtensionScheme[]; + // The name of the manifest inside an extension. +extern const base::FilePath::CharType kManifestFilename[]; + + // The name of locale folder inside an extension. +extern const base::FilePath::CharType kLocaleFolder[]; + + // The name of the messages file inside an extension. +extern const base::FilePath::CharType kMessagesFilename[]; + +// The base directory for subdirectories with platform-specific code. +extern const base::FilePath::CharType kPlatformSpecificFolder[]; + } // namespace extensions #endif // EXTENSIONS_COMMON_CONSTANTS_H_ diff --git a/extensions/common/crx_file.cc b/extensions/common/crx_file.cc new file mode 100644 index 0000000..4f50962 --- /dev/null +++ b/extensions/common/crx_file.cc @@ -0,0 +1,68 @@ +// 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 "extensions/common/crx_file.h" + +namespace extensions { + +namespace { + +// The current version of the crx format. +static const uint32 kCurrentVersion = 2; + +// The maximum size the crx parser will tolerate for a public key. +static const uint32 kMaxPublicKeySize = 1 << 16; + +// The maximum size the crx parser will tolerate for a signature. +static const uint32 kMaxSignatureSize = 1 << 16; + +} // namespace + +// The magic string embedded in the header. +const char kCrxFileHeaderMagic[] = "Cr24"; + +scoped_ptr<CrxFile> CrxFile::Parse(const CrxFile::Header& header, + CrxFile::Error* error) { + if (HeaderIsValid(header, error)) + return scoped_ptr<CrxFile>(new CrxFile(header)); + return scoped_ptr<CrxFile>(); +} + +scoped_ptr<CrxFile> CrxFile::Create(const uint32 key_size, + const uint32 signature_size, + CrxFile::Error* error) { + CrxFile::Header header; + memcpy(&header.magic, kCrxFileHeaderMagic, kCrxFileHeaderMagicSize); + header.version = kCurrentVersion; + header.key_size = key_size; + header.signature_size = signature_size; + if (HeaderIsValid(header, error)) + return scoped_ptr<CrxFile>(new CrxFile(header)); + return scoped_ptr<CrxFile>(); +} + +CrxFile::CrxFile(const Header& header) : header_(header) { +} + +bool CrxFile::HeaderIsValid(const CrxFile::Header& header, + CrxFile::Error* error) { + bool valid = false; + if (strncmp(kCrxFileHeaderMagic, header.magic, sizeof(header.magic))) + *error = kWrongMagic; + else if (header.version != kCurrentVersion) + *error = kInvalidVersion; + else if (header.key_size > kMaxPublicKeySize) + *error = kInvalidKeyTooLarge; + else if (header.key_size == 0) + *error = kInvalidKeyTooSmall; + else if (header.signature_size > kMaxSignatureSize) + *error = kInvalidSignatureTooLarge; + else if (header.signature_size == 0) + *error = kInvalidSignatureTooSmall; + else + valid = true; + return valid; +} + +} // namespace extensions diff --git a/extensions/common/crx_file.h b/extensions/common/crx_file.h new file mode 100644 index 0000000..cb450af --- /dev/null +++ b/extensions/common/crx_file.h @@ -0,0 +1,76 @@ +// 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 EXTENSIONS_COMMON_CRX_FILE_H_ +#define EXTENSIONS_COMMON_CRX_FILE_H_ + +#include <sys/types.h> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace extensions { + +// CRX files have a header that includes a magic key, version number, and +// some signature sizing information. Use CrxFile object to validate whether +// the header is valid or not. +class CrxFile { + public: + + // The size of the magic character sequence at the beginning of each crx + // file, in bytes. This should be a multiple of 4. + static const size_t kCrxFileHeaderMagicSize = 4; + + // This header is the first data at the beginning of an extension. Its + // contents are purposely 32-bit aligned so that it can just be slurped into + // a struct without manual parsing. + struct Header { + char magic[kCrxFileHeaderMagicSize]; + uint32 version; + uint32 key_size; // The size of the public key, in bytes. + uint32 signature_size; // The size of the signature, in bytes. + // An ASN.1-encoded PublicKeyInfo structure follows. + // The signature follows. + }; + + enum Error { + kWrongMagic, + kInvalidVersion, + kInvalidKeyTooLarge, + kInvalidKeyTooSmall, + kInvalidSignatureTooLarge, + kInvalidSignatureTooSmall, + }; + + // Construct a new CRX file header object with bytes of a header + // read from a CRX file. If a null scoped_ptr is returned, |error| + // contains an error code with additional information. + static scoped_ptr<CrxFile> Parse(const Header& header, Error* error); + + // Construct a new header for the given key and signature sizes. + // Returns a null scoped_ptr if erroneous values of |key_size| and/or + // |signature_size| are provided. |error| contains an error code with + // additional information. + // Use this constructor and then .header() to obtain the Header + // for writing out to a CRX file. + static scoped_ptr<CrxFile> Create(const uint32 key_size, + const uint32 signature_size, + Error* error); + + // Returns the header structure for writing out to a CRX file. + const Header& header() const { return header_; } + + private: + Header header_; + + // Constructor is private. Clients should use static factory methods above. + explicit CrxFile(const Header& header); + + // Checks the |header| for validity and returns true if the values are valid. + // If false is returned, more detailed error code is returned in |error|. + static bool HeaderIsValid(const Header& header, Error* error); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_CRX_FILE_H_ diff --git a/extensions/common/extension_resource.cc b/extensions/common/extension_resource.cc new file mode 100644 index 0000000..5a70539 --- /dev/null +++ b/extensions/common/extension_resource.cc @@ -0,0 +1,126 @@ +// 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 "extensions/common/extension_resource.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace extensions { + +ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) { +} + +ExtensionResource::ExtensionResource(const std::string& extension_id, + const base::FilePath& extension_root, + const base::FilePath& relative_path) + : extension_id_(extension_id), + extension_root_(extension_root), + relative_path_(relative_path), + follow_symlinks_anywhere_(false) { +} + +ExtensionResource::~ExtensionResource() {} + +void ExtensionResource::set_follow_symlinks_anywhere() { + follow_symlinks_anywhere_ = true; +} + +const base::FilePath& ExtensionResource::GetFilePath() const { + if (extension_root_.empty() || relative_path_.empty()) { + DCHECK(full_resource_path_.empty()); + return full_resource_path_; + } + + // We've already checked, just return last value. + if (!full_resource_path_.empty()) + return full_resource_path_; + + full_resource_path_ = GetFilePath( + extension_root_, relative_path_, + follow_symlinks_anywhere_ ? + FOLLOW_SYMLINKS_ANYWHERE : SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); + return full_resource_path_; +} + +// static +base::FilePath ExtensionResource::GetFilePath( + const base::FilePath& extension_root, + const base::FilePath& relative_path, + SymlinkPolicy symlink_policy) { + // We need to resolve the parent references in the extension_root + // path on its own because IsParent doesn't like parent references. + base::FilePath clean_extension_root(extension_root); + if (!file_util::AbsolutePath(&clean_extension_root)) + return base::FilePath(); + + base::FilePath full_path = clean_extension_root.Append(relative_path); + + // If we are allowing the file to be a symlink outside of the root, then the + // path before resolving the symlink must still be within it. + if (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE) { + std::vector<base::FilePath::StringType> components; + relative_path.GetComponents(&components); + int depth = 0; + + for (std::vector<base::FilePath::StringType>::const_iterator + i = components.begin(); i != components.end(); i++) { + if (*i == base::FilePath::kParentDirectory) { + depth--; + } else if (*i != base::FilePath::kCurrentDirectory) { + depth++; + } + if (depth < 0) { + return base::FilePath(); + } + } + } + + // We must resolve the absolute path of the combined path when + // the relative path contains references to a parent folder (i.e., '..'). + // We also check if the path exists because the posix version of AbsolutePath + // will fail if the path doesn't exist, and we want the same behavior on + // Windows... So until the posix and Windows version of AbsolutePath are + // unified, we need an extra call to PathExists, unfortunately. + // TODO(mad): Fix this once AbsolutePath is unified. + if (file_util::AbsolutePath(&full_path) && + file_util::PathExists(full_path) && + (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE || + clean_extension_root.IsParent(full_path))) { + return full_path; + } + + return base::FilePath(); +} + +// Unit-testing helpers. +base::FilePath::StringType ExtensionResource::NormalizeSeperators( + const base::FilePath::StringType& path) const { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + base::FilePath::StringType win_path = path; + for (size_t i = 0; i < win_path.length(); i++) { + if (base::FilePath::IsSeparator(win_path[i])) + win_path[i] = base::FilePath::kSeparators[0]; + } + return win_path; +#else + return path; +#endif // FILE_PATH_USES_WIN_SEPARATORS +} + +bool ExtensionResource::ComparePathWithDefault( + const base::FilePath& path) const { + // Make sure we have a cached value to test against... + if (full_resource_path_.empty()) + GetFilePath(); + if (NormalizeSeperators(path.value()) == + NormalizeSeperators(full_resource_path_.value())) { + return true; + } else { + return false; + } +} + +} // namespace extensions diff --git a/extensions/common/extension_resource.h b/extensions/common/extension_resource.h new file mode 100644 index 0000000..806889e --- /dev/null +++ b/extensions/common/extension_resource.h @@ -0,0 +1,89 @@ +// 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 EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_ +#define EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_ + +#include <string> + +#include "base/files/file_path.h" + +namespace extensions { + +// Represents a resource inside an extension. For example, an image, or a +// JavaScript file. This is more complicated than just a simple FilePath +// because extension resources can come from multiple physical file locations +// depending on locale. +class ExtensionResource { + public: + // SymlinkPolicy decides whether we'll allow resources to be a symlink to + // anywhere, or whether they must end up within the extension root. + enum SymlinkPolicy { + SYMLINKS_MUST_RESOLVE_WITHIN_ROOT, + FOLLOW_SYMLINKS_ANYWHERE, + }; + + ExtensionResource(); + + ExtensionResource(const std::string& extension_id, + const base::FilePath& extension_root, + const base::FilePath& relative_path); + + ~ExtensionResource(); + + // set_follow_symlinks_anywhere allows the resource to be a symlink to + // anywhere in the filesystem. By default, resources have to be within + // |extension_root| after resolving symlinks. + void set_follow_symlinks_anywhere(); + + // Returns actual path to the resource (default or locale specific). In the + // browser process, this will DCHECK if not called on the file thread. To + // easily load extension images on the UI thread, see ImageLoader. + const base::FilePath& GetFilePath() const; + + // Gets the physical file path for the extension resource, taking into account + // localization. In the browser process, this will DCHECK if not called on the + // file thread. To easily load extension images on the UI thread, see + // ImageLoader. + // + // The relative path must not resolve to a location outside of + // |extension_root|. Iff |file_can_symlink_outside_root| is true, then the + // file can be a symlink that links outside of |extension_root|. + static base::FilePath GetFilePath(const base::FilePath& extension_root, + const base::FilePath& relative_path, + SymlinkPolicy symlink_policy); + + // Getters + const std::string& extension_id() const { return extension_id_; } + const base::FilePath& extension_root() const { return extension_root_; } + const base::FilePath& relative_path() const { return relative_path_; } + + bool empty() const { return extension_root().empty(); } + + // Unit test helpers. + base::FilePath::StringType NormalizeSeperators( + const base::FilePath::StringType& path) const; + bool ComparePathWithDefault(const base::FilePath& path) const; + + private: + // The id of the extension that this resource is associated with. + std::string extension_id_; + + // Extension root. + base::FilePath extension_root_; + + // Relative path to resource. + base::FilePath relative_path_; + + // If |follow_symlinks_anywhere_| is true then the resource itself must be + // within |extension_root|, but it can be a symlink to a file that is not. + bool follow_symlinks_anywhere_; + + // Full path to extension resource. Starts empty. + mutable base::FilePath full_resource_path_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_ diff --git a/extensions/common/extension_resource_unittest.cc b/extensions/common/extension_resource_unittest.cc new file mode 100644 index 0000000..b2983ea --- /dev/null +++ b/extensions/common/extension_resource_unittest.cc @@ -0,0 +1,163 @@ +// 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 <algorithm> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" + +namespace extensions { + +TEST(ExtensionResourceTest, CreateEmptyResource) { + ExtensionResource resource; + + EXPECT_TRUE(resource.extension_root().empty()); + EXPECT_TRUE(resource.relative_path().empty()); + EXPECT_TRUE(resource.GetFilePath().empty()); +} + +const base::FilePath::StringType ToLower( + const base::FilePath::StringType& in_str) { + base::FilePath::StringType str(in_str); + std::transform(str.begin(), str.end(), str.begin(), tolower); + return str; +} + +TEST(ExtensionResourceTest, CreateWithMissingResourceOnDisk) { + base::FilePath root_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &root_path)); + base::FilePath relative_path; + relative_path = relative_path.AppendASCII("cira.js"); + std::string extension_id = id_util::GenerateId("test"); + ExtensionResource resource(extension_id, root_path, relative_path); + + // The path doesn't exist on disk, we will be returned an empty path. + EXPECT_EQ(root_path.value(), resource.extension_root().value()); + EXPECT_EQ(relative_path.value(), resource.relative_path().value()); + EXPECT_TRUE(resource.GetFilePath().empty()); +} + +TEST(ExtensionResourceTest, ResourcesOutsideOfPath) { + base::ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + base::FilePath inner_dir = temp.path().AppendASCII("directory"); + ASSERT_TRUE(file_util::CreateDirectory(inner_dir)); + base::FilePath sub_dir = inner_dir.AppendASCII("subdir"); + ASSERT_TRUE(file_util::CreateDirectory(sub_dir)); + base::FilePath inner_file = inner_dir.AppendASCII("inner"); + base::FilePath outer_file = temp.path().AppendASCII("outer"); + ASSERT_TRUE(file_util::WriteFile(outer_file, "X", 1)); + ASSERT_TRUE(file_util::WriteFile(inner_file, "X", 1)); + std::string extension_id = id_util::GenerateId("test"); + +#if defined(OS_POSIX) + base::FilePath symlink_file = inner_dir.AppendASCII("symlink"); + file_util::CreateSymbolicLink( + base::FilePath().AppendASCII("..").AppendASCII("outer"), + symlink_file); +#endif + + // A non-packing extension should be able to access the file within the + // directory. + ExtensionResource r1(extension_id, inner_dir, + base::FilePath().AppendASCII("inner")); + EXPECT_FALSE(r1.GetFilePath().empty()); + + // ... but not a relative path that walks out of |inner_dir|. + ExtensionResource r2(extension_id, inner_dir, + base::FilePath().AppendASCII("..").AppendASCII("outer")); + EXPECT_TRUE(r2.GetFilePath().empty()); + + // A packing extension should also be able to access the file within the + // directory. + ExtensionResource r3(extension_id, inner_dir, + base::FilePath().AppendASCII("inner")); + r3.set_follow_symlinks_anywhere(); + EXPECT_FALSE(r3.GetFilePath().empty()); + + // ... but, again, not a relative path that walks out of |inner_dir|. + ExtensionResource r4(extension_id, inner_dir, + base::FilePath().AppendASCII("..").AppendASCII("outer")); + r4.set_follow_symlinks_anywhere(); + EXPECT_TRUE(r4.GetFilePath().empty()); + + // ... and not even when clever current-directory syntax is present. Note + // that the path for this test case can't start with the current directory + // component due to quirks in FilePath::Append(), and the path must exist. + ExtensionResource r4a( + extension_id, inner_dir, + base::FilePath().AppendASCII("subdir").AppendASCII(".").AppendASCII(".."). + AppendASCII("..").AppendASCII("outer")); + r4a.set_follow_symlinks_anywhere(); + EXPECT_TRUE(r4a.GetFilePath().empty()); + +#if defined(OS_POSIX) + // The non-packing extension should also not be able to access a resource that + // symlinks out of the directory. + ExtensionResource r5(extension_id, inner_dir, + base::FilePath().AppendASCII("symlink")); + EXPECT_TRUE(r5.GetFilePath().empty()); + + // ... but a packing extension can. + ExtensionResource r6(extension_id, inner_dir, + base::FilePath().AppendASCII("symlink")); + r6.set_follow_symlinks_anywhere(); + EXPECT_FALSE(r6.GetFilePath().empty()); +#endif +} + +TEST(ExtensionResourceTest, CreateWithAllResourcesOnDisk) { + base::ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + // Create resource in the extension root. + const char* filename = "res.ico"; + base::FilePath root_resource = temp.path().AppendASCII(filename); + std::string data = "some foo"; + ASSERT_TRUE(file_util::WriteFile(root_resource, data.c_str(), data.length())); + + // Create l10n resources (for current locale and its parents). + base::FilePath l10n_path = + temp.path().Append(kLocaleFolder); + ASSERT_TRUE(file_util::CreateDirectory(l10n_path)); + + std::vector<std::string> locales; + l10n_util::GetParentLocales(l10n_util::GetApplicationLocale(""), &locales); + ASSERT_FALSE(locales.empty()); + for (size_t i = 0; i < locales.size(); i++) { + base::FilePath make_path; + make_path = l10n_path.AppendASCII(locales[i]); + ASSERT_TRUE(file_util::CreateDirectory(make_path)); + ASSERT_TRUE(file_util::WriteFile(make_path.AppendASCII(filename), + data.c_str(), data.length())); + } + + base::FilePath path; + std::string extension_id = id_util::GenerateId("test"); + ExtensionResource resource(extension_id, temp.path(), + base::FilePath().AppendASCII(filename)); + base::FilePath resolved_path = resource.GetFilePath(); + + base::FilePath expected_path; + // Expect default path only, since fallback logic is disabled. + // See http://crbug.com/27359. + expected_path = root_resource; + ASSERT_TRUE(file_util::AbsolutePath(&expected_path)); + + EXPECT_EQ(ToLower(expected_path.value()), ToLower(resolved_path.value())); + EXPECT_EQ(ToLower(temp.path().value()), + ToLower(resource.extension_root().value())); + EXPECT_EQ(ToLower(base::FilePath().AppendASCII(filename).value()), + ToLower(resource.relative_path().value())); +} + +} // namespace extensions diff --git a/extensions/common/id_util.cc b/extensions/common/id_util.cc new file mode 100644 index 0000000..5bdeba4 --- /dev/null +++ b/extensions/common/id_util.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2013 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 "extensions/common/id_util.h" + +#include "base/files/file_path.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "crypto/sha2.h" + +namespace { + +// Converts a normal hexadecimal string into the alphabet used by extensions. +// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a +// completely numeric host, since some software interprets that as an IP +// address. +static void ConvertHexadecimalToIDAlphabet(std::string* id) { + for (size_t i = 0; i < id->size(); ++i) { + int val; + if (base::HexStringToInt(base::StringPiece(id->begin() + i, + id->begin() + i + 1), + &val)) { + (*id)[i] = val + 'a'; + } else { + (*id)[i] = 'a'; + } + } +} + +} // namespace + +namespace extensions { +namespace id_util { + +// First 16 bytes of SHA256 hashed public key. +const size_t kIdSize = 16; + +std::string GenerateId(const std::string& input) { + uint8 hash[kIdSize]; + crypto::SHA256HashString(input, hash, sizeof(hash)); + std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash))); + ConvertHexadecimalToIDAlphabet(&output); + + return output; +} + +std::string GenerateIdForPath(const base::FilePath& path) { + base::FilePath new_path = MaybeNormalizePath(path); + std::string path_bytes = + std::string(reinterpret_cast<const char*>(new_path.value().data()), + new_path.value().size() * sizeof(base::FilePath::CharType)); + return GenerateId(path_bytes); +} + +base::FilePath MaybeNormalizePath(const base::FilePath& path) { +#if defined(OS_WIN) + // Normalize any drive letter to upper-case. We do this for consistency with + // net_utils::FilePathToFileURL(), which does the same thing, to make string + // comparisons simpler. + base::FilePath::StringType path_str = path.value(); + if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' && + path_str[1] == ':') + path_str[0] += ('A' - 'a'); + + return base::FilePath(path_str); +#else + return path; +#endif +} + +} // namespace id_util +} // namespace extensions diff --git a/extensions/common/id_util.h b/extensions/common/id_util.h new file mode 100644 index 0000000..74e690d --- /dev/null +++ b/extensions/common/id_util.h @@ -0,0 +1,35 @@ +// Copyright (c) 2013 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 EXTENSIONS_COMMON_ID_UTIL_H_ +#define EXTENSIONS_COMMON_ID_UTIL_H_ + +#include <string> + +namespace base { +class FilePath; +} + +namespace extensions { +namespace id_util { + +// The number of bytes in a legal id. +extern const size_t kIdSize; + +// Generates an extension ID from arbitrary input. The same input string will +// always generate the same output ID. +std::string GenerateId(const std::string& input); + +// Generate an ID for an extension in the given path. +// Used while developing extensions, before they have a key. +std::string GenerateIdForPath(const base::FilePath& path); + +// Normalize the path for use by the extension. On Windows, this will make +// sure the drive letter is uppercase. +base::FilePath MaybeNormalizePath(const base::FilePath& path); + +} // namespace id_util +} // namespace extensions + +#endif // EXTENSIONS_COMMON_ID_UTIL_H_ diff --git a/extensions/common/id_util_unittest.cc b/extensions/common/id_util_unittest.cc new file mode 100644 index 0000000..f43d206 --- /dev/null +++ b/extensions/common/id_util_unittest.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2013 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 "base/basictypes.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { +namespace id_util { + +TEST(IDUtilTest, GenerateID) { + const uint8 public_key_info[] = { + 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, + 0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b, + 0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08, + 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04, + 0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a, + 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30, + 0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17, + 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89, + 0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85, + 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14, + 0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18, + 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6, + 0x0f, 0x02, 0x03, 0x01, 0x00, 0x01 + }; + std::string extension_id = GenerateId( + std::string(reinterpret_cast<const char*>(&public_key_info[0]), + arraysize(public_key_info))); + EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id); + + EXPECT_EQ("jpignaibiiemhngfjkcpokkamffknabf", GenerateId("test")); + + EXPECT_EQ("ncocknphbhhlhkikpnnlmbcnbgdempcd", GenerateId("_")); + + EXPECT_EQ("jimneklojkjdibfkgiiophfhjhbdgcfi", + GenerateId( + "this_string_is_longer_than_a_single_sha256_hash_digest")); +} + +} // namespace id_util +} // namespace extensions |