summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authoryoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-23 21:25:16 +0000
committeryoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-23 21:25:16 +0000
commit993da5e316353bf36dc27df8e01e91b36ad3cab1 (patch)
tree1962a24c54d212e1d459eb3f58c8f2e478cc2e6c /extensions
parent5799a694a40b1c443788df56aa749d1c059577fd (diff)
downloadchromium_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/DEPS14
-rw-r--r--extensions/browser/DEPS3
-rw-r--r--extensions/browser/file_reader.cc33
-rw-r--r--extensions/browser/file_reader.h44
-rw-r--r--extensions/browser/file_reader_unittest.cc105
-rw-r--r--extensions/common/DEPS4
-rw-r--r--extensions/common/constants.cc9
-rw-r--r--extensions/common/constants.h14
-rw-r--r--extensions/common/crx_file.cc68
-rw-r--r--extensions/common/crx_file.h76
-rw-r--r--extensions/common/extension_resource.cc126
-rw-r--r--extensions/common/extension_resource.h89
-rw-r--r--extensions/common/extension_resource_unittest.cc163
-rw-r--r--extensions/common/id_util.cc73
-rw-r--r--extensions/common/id_util.h35
-rw-r--r--extensions/common/id_util_unittest.cc44
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