Have the browser process rewrite manifest.json and theme/page action images
that the extension unpacker process parsed. BUG=11680 Review URL: http://codereview.chromium.org/115595 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16768 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
chrome
@ -5,6 +5,7 @@
|
||||
#include "chrome/browser/extensions/extensions_service.h"
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/gfx/png_encoder.h"
|
||||
#include "base/scoped_handle.h"
|
||||
#include "base/scoped_temp_dir.h"
|
||||
#include "base/string_util.h"
|
||||
@ -29,6 +30,7 @@
|
||||
#include "chrome/common/pref_service.h"
|
||||
#include "chrome/common/unzip.h"
|
||||
#include "chrome/common/url_constants.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "base/registry.h"
|
||||
@ -129,35 +131,42 @@ class ExtensionsServiceBackend::UnpackerClient
|
||||
// Cheesy... but if we don't have a ResourceDispatcherHost, assume we're
|
||||
// in a unit test and run the unpacker directly in-process.
|
||||
ExtensionUnpacker unpacker(temp_extension_path_);
|
||||
bool success = unpacker.Run();
|
||||
OnUnpackExtensionReply(success, unpacker.error_message());
|
||||
if (unpacker.Run()) {
|
||||
OnUnpackExtensionSucceeded(*unpacker.parsed_manifest(),
|
||||
unpacker.decoded_images());
|
||||
} else {
|
||||
OnUnpackExtensionFailed(unpacker.error_message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// UtilityProcessHost::Client
|
||||
virtual void OnProcessCrashed() {
|
||||
OnUnpackExtensionReply(false, "Chrome crashed while trying to install");
|
||||
OnUnpackExtensionFailed("Chrome crashed while trying to install");
|
||||
}
|
||||
|
||||
virtual void OnUnpackExtensionReply(bool success,
|
||||
const std::string& error_message) {
|
||||
if (success) {
|
||||
// The extension was unpacked to the temp dir inside our unpacking dir.
|
||||
FilePath extension_dir = temp_extension_path_.DirName().AppendASCII(
|
||||
ExtensionsServiceBackend::kTempExtensionName);
|
||||
backend_->OnExtensionUnpacked(extension_path_, extension_dir,
|
||||
expected_id_, from_external_);
|
||||
} else {
|
||||
backend_->ReportExtensionInstallError(extension_path_, error_message);
|
||||
}
|
||||
virtual void OnUnpackExtensionSucceeded(
|
||||
const DictionaryValue& manifest,
|
||||
const std::vector< Tuple2<SkBitmap, FilePath> >& images) {
|
||||
// The extension was unpacked to the temp dir inside our unpacking dir.
|
||||
FilePath extension_dir = temp_extension_path_.DirName().AppendASCII(
|
||||
ExtensionsServiceBackend::kTempExtensionName);
|
||||
backend_->OnExtensionUnpacked(extension_path_, extension_dir,
|
||||
expected_id_, from_external_,
|
||||
manifest, images);
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
virtual void OnUnpackExtensionFailed(const std::string& error_message) {
|
||||
backend_->ReportExtensionInstallError(extension_path_, error_message);
|
||||
Cleanup();
|
||||
Release(); // balanced in Run()
|
||||
}
|
||||
|
||||
// Cleans up our temp directory.
|
||||
void Cleanup() {
|
||||
file_util::Delete(temp_extension_path_.DirName(), true);
|
||||
Release(); // balanced in Run()
|
||||
}
|
||||
|
||||
// Starts the utility process that unpacks our extension.
|
||||
@ -812,19 +821,12 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
|
||||
const FilePath& extension_path,
|
||||
const FilePath& temp_extension_dir,
|
||||
const std::string expected_id,
|
||||
bool from_external) {
|
||||
// TODO(mpcomplete): the utility process should pass up a parsed manifest that
|
||||
// we rewrite in the browser.
|
||||
// Bug http://code.google.com/p/chromium/issues/detail?id=11680
|
||||
scoped_ptr<DictionaryValue> manifest(ReadManifest(extension_path));
|
||||
if (!manifest.get()) {
|
||||
// ReadManifest has already reported the extension error.
|
||||
return;
|
||||
}
|
||||
|
||||
bool from_external,
|
||||
const DictionaryValue& manifest,
|
||||
const std::vector< Tuple2<SkBitmap, FilePath> >& images) {
|
||||
Extension extension;
|
||||
std::string error;
|
||||
if (!extension.InitFromValue(*manifest,
|
||||
if (!extension.InitFromValue(manifest,
|
||||
true, // require ID
|
||||
&error)) {
|
||||
ReportExtensionInstallError(extension_path, "Invalid extension manifest.");
|
||||
@ -849,16 +851,61 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
|
||||
was_update = true;
|
||||
}
|
||||
|
||||
// Write our parsed manifest back to disk, to ensure it doesn't contain an
|
||||
// exploitable bug that can be used to compromise the browser.
|
||||
std::string manifest_json;
|
||||
JSONStringValueSerializer serializer(&manifest_json);
|
||||
serializer.set_pretty_print(true);
|
||||
if (!serializer.Serialize(manifest)) {
|
||||
ReportExtensionInstallError(extension_path,
|
||||
"Error serializing manifest.json.");
|
||||
return;
|
||||
}
|
||||
|
||||
FilePath manifest_path =
|
||||
temp_extension_dir.AppendASCII(Extension::kManifestFilename);
|
||||
if (!file_util::WriteFile(manifest_path,
|
||||
manifest_json.data(), manifest_json.size())) {
|
||||
ReportExtensionInstallError(extension_path, "Error saving manifest.json.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Write our parsed images back to disk as well.
|
||||
for (size_t i = 0; i < images.size(); ++i) {
|
||||
const SkBitmap& image = images[i].a;
|
||||
FilePath path = temp_extension_dir.Append(images[i].b);
|
||||
|
||||
std::vector<unsigned char> image_data;
|
||||
// TODO(mpcomplete): It's lame that we're encoding all images as PNG, even
|
||||
// though they may originally be .jpg, etc. Figure something out.
|
||||
// http://code.google.com/p/chromium/issues/detail?id=12459
|
||||
if (!PNGEncoder::EncodeBGRASkBitmap(image, false, &image_data)) {
|
||||
ReportExtensionInstallError(extension_path,
|
||||
"Error re-encoding theme image.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: we're overwriting existing files that the utility process wrote,
|
||||
// so we can be sure the directory exists.
|
||||
const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
|
||||
if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) {
|
||||
ReportExtensionInstallError(extension_path, "Error saving theme image.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// <profile>/Extensions/<dir_name>/<version>
|
||||
FilePath version_dir = dest_dir.AppendASCII(version);
|
||||
|
||||
// If anything fails after this, we want to delete the extension dir.
|
||||
ScopedTempDir scoped_version_dir;
|
||||
scoped_version_dir.Set(version_dir);
|
||||
|
||||
if (!InstallDirSafely(temp_extension_dir, version_dir))
|
||||
return;
|
||||
|
||||
if (!SetCurrentVersion(dest_dir, version)) {
|
||||
if (!file_util::Delete(version_dir, true))
|
||||
LOG(WARNING) << "Can't remove " << dest_dir.value();
|
||||
if (!SetCurrentVersion(dest_dir, version))
|
||||
return;
|
||||
}
|
||||
|
||||
// To mark that this extension was installed from an external source, create a
|
||||
// zero-length file. At load time, this is used to indicate that the
|
||||
@ -872,7 +919,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
|
||||
// Load the extension immediately and then report installation success. We
|
||||
// don't load extensions for external installs because external installation
|
||||
// occurs before the normal startup so we just let startup pick them up. We
|
||||
// don't notify installation because there is no UI or external install so
|
||||
// don't notify installation because there is no UI for external install so
|
||||
// there is nobody to notify.
|
||||
if (!from_external) {
|
||||
Extension* extension = LoadExtension(version_dir, true); // require id
|
||||
@ -890,6 +937,8 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
|
||||
// Hand off ownership of the loaded extensions to the frontend.
|
||||
ReportExtensionsLoaded(extensions.release());
|
||||
}
|
||||
|
||||
scoped_version_dir.Take();
|
||||
}
|
||||
|
||||
void ExtensionsServiceBackend::ReportExtensionInstallError(
|
||||
|
@ -22,6 +22,7 @@ class GURL;
|
||||
class PrefService;
|
||||
class Profile;
|
||||
class ResourceDispatcherHost;
|
||||
class SkBitmap;
|
||||
class SiteInstance;
|
||||
class UserScriptMaster;
|
||||
typedef std::vector<Extension*> ExtensionList;
|
||||
@ -171,10 +172,15 @@ class ExtensionsServiceBackend
|
||||
// Finish installing an extension after it has been unpacked to
|
||||
// |temp_extension_dir| by our utility process. If |expected_id| is not
|
||||
// empty, it's verified against the extension's manifest before installation.
|
||||
void OnExtensionUnpacked(const FilePath& extension_path,
|
||||
const FilePath& temp_extension_dir,
|
||||
const std::string expected_id,
|
||||
bool from_external);
|
||||
// |manifest| and |images| are parsed information from the extension that
|
||||
// we want to write to disk in the browser process.
|
||||
void OnExtensionUnpacked(
|
||||
const FilePath& extension_path,
|
||||
const FilePath& temp_extension_dir,
|
||||
const std::string expected_id,
|
||||
bool from_external,
|
||||
const DictionaryValue& manifest,
|
||||
const std::vector< Tuple2<SkBitmap, FilePath> >& images);
|
||||
|
||||
// Notify the frontend that there was an error loading an extension.
|
||||
void ReportExtensionLoadError(const FilePath& extension_path,
|
||||
|
@ -77,6 +77,7 @@ class ExtensionsServiceTest
|
||||
profile_.reset(new TestingProfile());
|
||||
service_ = new ExtensionsService(profile_.get(), &loop_, &loop_,
|
||||
registry_path_);
|
||||
total_successes_ = 0;
|
||||
}
|
||||
|
||||
static void SetUpTestCase() {
|
||||
@ -126,10 +127,13 @@ class ExtensionsServiceTest
|
||||
loop_.RunAllPending();
|
||||
std::vector<std::string> errors = GetErrors();
|
||||
if (should_succeed) {
|
||||
++total_successes_;
|
||||
|
||||
EXPECT_TRUE(installed_) << path.value();
|
||||
EXPECT_EQ(1u, loaded_.size()) << path.value();
|
||||
EXPECT_EQ(0u, errors.size()) << path.value();
|
||||
EXPECT_EQ(1u, service_->extensions()->size()) << path.value();
|
||||
EXPECT_EQ(total_successes_, service_->extensions()->size()) <<
|
||||
path.value();
|
||||
EXPECT_TRUE(service_->GetExtensionByID(loaded_[0]->id())) << path.value();
|
||||
for (std::vector<std::string>::iterator err = errors.begin();
|
||||
err != errors.end(); ++err) {
|
||||
@ -148,6 +152,7 @@ class ExtensionsServiceTest
|
||||
protected:
|
||||
scoped_ptr<TestingProfile> profile_;
|
||||
scoped_refptr<ExtensionsService> service_;
|
||||
size_t total_successes_;
|
||||
MessageLoop loop_;
|
||||
std::vector<Extension*> loaded_;
|
||||
std::string unloaded_id_;
|
||||
@ -303,6 +308,14 @@ TEST_F(ExtensionsServiceTest, InstallExtension) {
|
||||
TestInstallExtension(path, true);
|
||||
// TODO(erikkay): verify the contents of the installed extension.
|
||||
|
||||
// An extension with theme images.
|
||||
path = extensions_path.AppendASCII("theme.crx");
|
||||
TestInstallExtension(path, true);
|
||||
|
||||
// An extension with page actions.
|
||||
path = extensions_path.AppendASCII("page_action.crx");
|
||||
TestInstallExtension(path, true);
|
||||
|
||||
// 0-length extension file.
|
||||
path = extensions_path.AppendASCII("not_an_extension.crx");
|
||||
TestInstallExtension(path, false);
|
||||
@ -369,6 +382,7 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) {
|
||||
|
||||
// Uninstall it.
|
||||
service_->UninstallExtension(extension_id);
|
||||
total_successes_ = 0;
|
||||
|
||||
// We should get an unload notification.
|
||||
ASSERT_TRUE(unloaded_id_.length());
|
||||
|
@ -81,7 +81,9 @@ void UtilityProcessHost::OnChannelError() {
|
||||
void UtilityProcessHost::Client::OnMessageReceived(
|
||||
const IPC::Message& message) {
|
||||
IPC_BEGIN_MESSAGE_MAP(UtilityProcessHost, message)
|
||||
IPC_MESSAGE_HANDLER(UtilityHostMsg_UnpackExtension_Reply,
|
||||
Client::OnUnpackExtensionReply)
|
||||
IPC_MESSAGE_HANDLER(UtilityHostMsg_UnpackExtension_Succeeded,
|
||||
Client::OnUnpackExtensionSucceeded)
|
||||
IPC_MESSAGE_HANDLER(UtilityHostMsg_UnpackExtension_Failed,
|
||||
Client::OnUnpackExtensionFailed)
|
||||
IPC_END_MESSAGE_MAP_EX()
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define CHROME_BROWSER_UTILITY_PROCESS_HOST_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/ref_counted.h"
|
||||
@ -14,7 +15,9 @@
|
||||
#include "chrome/common/ipc_channel.h"
|
||||
|
||||
class CommandLine;
|
||||
class DictionaryValue;
|
||||
class MessageLoop;
|
||||
class SkBitmap;
|
||||
|
||||
// This class acts as the browser-side host to a utility child process. A
|
||||
// utility process is a short-lived sandboxed process that is created to run
|
||||
@ -32,10 +35,17 @@ class UtilityProcessHost : public ChildProcessHost {
|
||||
// Called when the process has crashed.
|
||||
virtual void OnProcessCrashed() {}
|
||||
|
||||
// Called when the process sends a reply to an UnpackExtension message.
|
||||
// If success if false, error_message contains a description of the problem.
|
||||
virtual void OnUnpackExtensionReply(bool success,
|
||||
const std::string& error_message) {}
|
||||
// Called when the extension has unpacked successfully. |manifest| is the
|
||||
// parsed manifest.json file. |images| contains a list of decoded images
|
||||
// and the associated paths where those images live on disk.
|
||||
virtual void OnUnpackExtensionSucceeded(
|
||||
const DictionaryValue& manifest,
|
||||
const std::vector< Tuple2<SkBitmap, FilePath> >& images) {}
|
||||
|
||||
// Called when an error occurred while unpacking the extension.
|
||||
// |error_message| contains a description of the problem.
|
||||
virtual void OnUnpackExtensionFailed(const std::string& error_message) {}
|
||||
|
||||
private:
|
||||
friend class UtilityProcessHost;
|
||||
void OnMessageReceived(const IPC::Message& message);
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "chrome/common/notification_service.h"
|
||||
#include "chrome/common/unzip.h"
|
||||
#include "chrome/common/url_constants.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "webkit/glue/image_decoder.h"
|
||||
|
||||
namespace {
|
||||
const char kCurrentVersionFileName[] = "Current Version";
|
||||
@ -59,12 +61,47 @@ const char kExternalInstallFile[] = "EXTERNAL_INSTALL";
|
||||
|
||||
// The version of the extension package that this code understands.
|
||||
const uint32 kExpectedVersion = 1;
|
||||
} // namespace
|
||||
|
||||
static SkBitmap DecodeImage(const FilePath& path) {
|
||||
// Read the file from disk.
|
||||
std::string file_contents;
|
||||
if (!file_util::PathExists(path) ||
|
||||
!file_util::ReadFileToString(path, &file_contents)) {
|
||||
return SkBitmap();
|
||||
}
|
||||
|
||||
// Decode the image using WebKit's image decoder.
|
||||
const unsigned char* data =
|
||||
reinterpret_cast<const unsigned char*>(file_contents.data());
|
||||
webkit_glue::ImageDecoder decoder;
|
||||
return decoder.Decode(data, file_contents.length());
|
||||
}
|
||||
|
||||
static bool PathContainsParentDirectory(const FilePath& path) {
|
||||
const FilePath::StringType kSeparators(FilePath::kSeparators);
|
||||
const FilePath::StringType kParentDirectory(FilePath::kParentDirectory);
|
||||
const size_t npos = FilePath::StringType::npos;
|
||||
const FilePath::StringType& value = path.value();
|
||||
|
||||
for (size_t i = 0; i < value.length(); ) {
|
||||
i = value.find(kParentDirectory, i);
|
||||
if (i != npos) {
|
||||
if ((i == 0 || kSeparators.find(value[i-1]) == npos) &&
|
||||
(i+1 < value.length() || kSeparators.find(value[i+1]) == npos)) {
|
||||
return true;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The extension file format is a header, followed by the manifest, followed
|
||||
// by the zip file. The header is a magic number, a version, the size of the
|
||||
// header, and the size of the manifest. These ints are 4 byte little endian.
|
||||
DictionaryValue* ExtensionUnpacker::ReadManifest() {
|
||||
DictionaryValue* ExtensionUnpacker::ReadPackageHeader() {
|
||||
ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb"));
|
||||
if (!file.get()) {
|
||||
SetError("no such extension file");
|
||||
@ -166,44 +203,114 @@ DictionaryValue* ExtensionUnpacker::ReadManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
DictionaryValue* ExtensionUnpacker::ReadManifest() {
|
||||
FilePath manifest_path =
|
||||
temp_install_dir_.AppendASCII(Extension::kManifestFilename);
|
||||
if (!file_util::PathExists(manifest_path)) {
|
||||
SetError(Extension::kInvalidManifestError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JSONFileValueSerializer serializer(manifest_path);
|
||||
std::string error;
|
||||
scoped_ptr<Value> root(serializer.Deserialize(&error));
|
||||
if (!root.get()) {
|
||||
SetError(error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!root->IsType(Value::TYPE_DICTIONARY)) {
|
||||
SetError(Extension::kInvalidManifestError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return static_cast<DictionaryValue*>(root.release());
|
||||
}
|
||||
|
||||
bool ExtensionUnpacker::Run() {
|
||||
LOG(INFO) << "Installing extension " << extension_path_.value();
|
||||
|
||||
// Read and verify the extension.
|
||||
scoped_ptr<DictionaryValue> manifest(ReadManifest());
|
||||
if (!manifest.get()) {
|
||||
// ReadManifest has already reported the extension error.
|
||||
return false;
|
||||
}
|
||||
Extension extension;
|
||||
std::string error;
|
||||
if (!extension.InitFromValue(*manifest,
|
||||
true, // require ID
|
||||
&error)) {
|
||||
SetError("Invalid extension manifest.");
|
||||
scoped_ptr<DictionaryValue> header_manifest(ReadPackageHeader());
|
||||
if (!header_manifest.get()) {
|
||||
// ReadPackageHeader has already reported the extension error.
|
||||
return false;
|
||||
}
|
||||
|
||||
// ID is required for installed extensions.
|
||||
if (extension.id().empty()) {
|
||||
SetError("Required value 'id' is missing.");
|
||||
// TODO(mpcomplete): it looks like this isn't actually necessary. We don't
|
||||
// use header_extension, and we check that the unzipped manifest is valid.
|
||||
Extension header_extension;
|
||||
std::string error;
|
||||
if (!header_extension.InitFromValue(*header_manifest,
|
||||
true, // require ID
|
||||
&error)) {
|
||||
SetError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// <profile>/Extensions/INSTALL_TEMP/<version>
|
||||
std::string version = extension.VersionString();
|
||||
FilePath temp_install =
|
||||
temp_install_dir_ =
|
||||
extension_path_.DirName().AppendASCII(kTempExtensionName);
|
||||
if (!file_util::CreateDirectory(temp_install)) {
|
||||
if (!file_util::CreateDirectory(temp_install_dir_)) {
|
||||
SetError("Couldn't create directory for unzipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Unzip(extension_path_, temp_install, NULL)) {
|
||||
if (!Unzip(extension_path_, temp_install_dir_, NULL)) {
|
||||
SetError("Couldn't unzip extension.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the manifest.
|
||||
parsed_manifest_.reset(ReadManifest());
|
||||
if (!parsed_manifest_.get())
|
||||
return false; // Error was already reported.
|
||||
|
||||
// Re-read the actual manifest into our extension struct.
|
||||
Extension extension;
|
||||
if (!extension.InitFromValue(*parsed_manifest_,
|
||||
true, // require ID
|
||||
&error)) {
|
||||
SetError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode any images that the browser needs to display.
|
||||
DictionaryValue* images = extension.GetThemeImages();
|
||||
if (images) {
|
||||
for (DictionaryValue::key_iterator it = images->begin_keys();
|
||||
it != images->end_keys(); ++it) {
|
||||
std::wstring val;
|
||||
if (images->GetString(*it, &val)) {
|
||||
if (!AddDecodedImage(FilePath::FromWStringHack(val)))
|
||||
return false; // Error was already reported.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (PageActionMap::const_iterator it = extension.page_actions().begin();
|
||||
it != extension.page_actions().end(); ++it) {
|
||||
if (!AddDecodedImage(it->second->icon_path()))
|
||||
return false; // Error was already reported.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtensionUnpacker::AddDecodedImage(const FilePath& path) {
|
||||
// Make sure it's not referencing a file outside the extension's subdir.
|
||||
if (path.IsAbsolute() || PathContainsParentDirectory(path)) {
|
||||
SetError("Path names must not be absolute or contain '..'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SkBitmap image_bitmap = DecodeImage(temp_install_dir_.Append(path));
|
||||
if (image_bitmap.isNull()) {
|
||||
SetError("Could not decode theme image.");
|
||||
return false;
|
||||
}
|
||||
|
||||
decoded_images_.push_back(MakeTuple(image_bitmap, path));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -6,16 +6,22 @@
|
||||
#define CHROME_COMMON_EXTENSIONS_EXTENSION_UNPACKER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/tuple.h"
|
||||
|
||||
class DictionaryValue;
|
||||
class SkBitmap;
|
||||
|
||||
// Implements IO for the ExtensionsService.
|
||||
// TODO(aa): Extract an interface out of this for testing the frontend, once the
|
||||
// frontend has significant logic to test.
|
||||
class ExtensionUnpacker {
|
||||
public:
|
||||
typedef std::vector< Tuple2<SkBitmap, FilePath> > DecodedImages;
|
||||
|
||||
explicit ExtensionUnpacker(const FilePath& extension_path)
|
||||
: extension_path_(extension_path) {}
|
||||
|
||||
@ -24,18 +30,40 @@ class ExtensionUnpacker {
|
||||
bool Run();
|
||||
|
||||
const std::string& error_message() { return error_message_; }
|
||||
DictionaryValue* parsed_manifest() {
|
||||
return parsed_manifest_.get();
|
||||
}
|
||||
const DecodedImages& decoded_images() { return decoded_images_; }
|
||||
|
||||
private:
|
||||
// Read the manifest from the front of the extension file.
|
||||
// Parse the header on the front of the extension file and return the manifest
|
||||
// inside it. Caller takes ownership of return value.
|
||||
DictionaryValue* ReadPackageHeader();
|
||||
|
||||
// Parse the manifest.json file inside the extension (not in the header).
|
||||
// Caller takes ownership of return value.
|
||||
DictionaryValue* ReadManifest();
|
||||
|
||||
// Decodes the image at the given path and puts it in our list of decoded
|
||||
// images.
|
||||
bool AddDecodedImage(const FilePath& path);
|
||||
|
||||
// Set the error message.
|
||||
void SetError(const std::string& error);
|
||||
|
||||
// The extension to unpack.
|
||||
FilePath extension_path_;
|
||||
|
||||
// The place we unpacked the extension to.
|
||||
FilePath temp_install_dir_;
|
||||
|
||||
// The parsed version of the manifest JSON contained in the extension.
|
||||
scoped_ptr<DictionaryValue> parsed_manifest_;
|
||||
|
||||
// A list of decoded images and the paths where those images came from. Paths
|
||||
// are relative to the manifest file.
|
||||
DecodedImages decoded_images_;
|
||||
|
||||
// The last error message that was set. Empty if there were no errors.
|
||||
std::string error_message_;
|
||||
|
||||
|
@ -47,6 +47,8 @@ namespace base {
|
||||
class Time;
|
||||
}
|
||||
|
||||
class SkBitmap;
|
||||
|
||||
// Parameters structure for ViewMsg_Navigate, which has too many data
|
||||
// parameters to be reasonably put in a predefined IPC message.
|
||||
struct ViewMsg_Navigate_Params {
|
||||
@ -385,6 +387,10 @@ struct ViewHostMsg_ShowPopup_Params {
|
||||
std::vector<WebMenuItem> popup_items;
|
||||
};
|
||||
|
||||
// Used by UtilityHostMsg_UnpackExtension_Succeeded. We must define a typedef
|
||||
// because the preprocessor is stupid about commas inside macros.
|
||||
typedef Tuple2<SkBitmap, FilePath> UnpackExtension_ImagePathPair;
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template <>
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "base/gfx/rect.h"
|
||||
#include "base/gfx/native_widget_types.h"
|
||||
#include "base/shared_memory.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/common/ipc_message_macros.h"
|
||||
#include "chrome/common/transport_dib.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
@ -1393,9 +1394,16 @@ IPC_BEGIN_MESSAGES(ViewHost)
|
||||
// These are messages from the utility process to the browser. They're here
|
||||
// because we ran out of spare message types.
|
||||
|
||||
// Reply when the utility process is done unpacking an extension. |success|
|
||||
// argument is true if the extension unpacked and verified successfully.
|
||||
IPC_MESSAGE_CONTROL2(UtilityHostMsg_UnpackExtension_Reply,
|
||||
bool /* success */,
|
||||
// Reply when the utility process is done unpacking an extension. |manifest|
|
||||
// is the parsed manifest.json file. |images| is a list of decoded images
|
||||
// and the path to where they should be written to, relative to the
|
||||
// manifest file.
|
||||
IPC_MESSAGE_CONTROL2(UtilityHostMsg_UnpackExtension_Succeeded,
|
||||
DictionaryValue /* manifest */,
|
||||
std::vector<UnpackExtension_ImagePathPair> /* images */)
|
||||
|
||||
// Reply when the utility process has failed while unpacking an extension.
|
||||
// |error_message| is a user-displayable explanation of what went wrong.
|
||||
IPC_MESSAGE_CONTROL1(UtilityHostMsg_UnpackExtension_Failed,
|
||||
std::string /* error_message, if any */)
|
||||
IPC_END_MESSAGES(ViewHost)
|
||||
|
BIN
chrome/test/data/extensions/page_action.crx
Normal file
BIN
chrome/test/data/extensions/page_action.crx
Normal file
Binary file not shown.
@ -4,6 +4,7 @@
|
||||
|
||||
#include "chrome/utility/utility_thread.h"
|
||||
|
||||
#include "base/values.h"
|
||||
#include "chrome/common/child_process.h"
|
||||
#include "chrome/common/extensions/extension_unpacker.h"
|
||||
#include "chrome/common/render_messages.h"
|
||||
@ -32,9 +33,13 @@ void UtilityThread::OnControlMessageReceived(const IPC::Message& msg) {
|
||||
|
||||
void UtilityThread::OnUnpackExtension(const FilePath& extension_path) {
|
||||
ExtensionUnpacker unpacker(extension_path);
|
||||
bool success = unpacker.Run();
|
||||
Send(new UtilityHostMsg_UnpackExtension_Reply(success,
|
||||
unpacker.error_message()));
|
||||
if (unpacker.Run()) {
|
||||
Send(new UtilityHostMsg_UnpackExtension_Succeeded(
|
||||
*unpacker.parsed_manifest(), unpacker.decoded_images()));
|
||||
} else {
|
||||
Send(new UtilityHostMsg_UnpackExtension_Failed(
|
||||
unpacker.error_message()));
|
||||
}
|
||||
|
||||
ChildProcess::current()->ReleaseProcess();
|
||||
}
|
||||
|
Reference in New Issue
Block a user