diff options
49 files changed, 2211 insertions, 349 deletions
diff --git a/chrome/browser/component_updater/component_patcher.cc b/chrome/browser/component_updater/component_patcher.cc new file mode 100644 index 0000000..54bc62d --- /dev/null +++ b/chrome/browser/component_updater/component_patcher.cc @@ -0,0 +1,81 @@ +// Copyright 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 "chrome/browser/component_updater/component_patcher.h" + +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/values.h" +#include "chrome/browser/component_updater/component_patcher_operation.h" +#include "chrome/browser/component_updater/component_updater_service.h" + +namespace { + +// Deserialize the commands file (present in delta update packages). The top +// level must be a list. +base::ListValue* ReadCommands(const base::FilePath& unpack_path) { + const base::FilePath commands = + unpack_path.Append(FILE_PATH_LITERAL("commands.json")); + if (!file_util::PathExists(commands)) + return NULL; + + JSONFileValueSerializer serializer(commands); + scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL)); + + return (root.get() && root->IsType(base::Value::TYPE_LIST)) ? + static_cast<base::ListValue*>(root.release()) : NULL; +} + +} // namespace + + +// The patching support is not cross-platform at the moment. +ComponentPatcherCrossPlatform::ComponentPatcherCrossPlatform() {} + +ComponentUnpacker::Error ComponentPatcherCrossPlatform::Patch( + PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) { + return ComponentUnpacker::kDeltaUnsupportedCommand; +} + + +// Takes the contents of a differential component update in input_dir +// and produces the contents of a full component update in unpack_dir +// using input_abs_path_ files that the installer knows about. +ComponentUnpacker::Error DifferentialUpdatePatch( + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentPatcher* patcher, + ComponentInstaller* installer, + int* error) { + *error = 0; + scoped_ptr<base::ListValue> commands(ReadCommands(input_dir)); + if (!commands.get()) + return ComponentUnpacker::kDeltaBadCommands; + + for (base::ValueVector::const_iterator command = commands->begin(), + end = commands->end(); command != end; command++) { + if (!(*command)->IsType(base::Value::TYPE_DICTIONARY)) + return ComponentUnpacker::kDeltaBadCommands; + base::DictionaryValue* command_args = + static_cast<base::DictionaryValue*>(*command); + scoped_ptr<DeltaUpdateOp> operation(CreateDeltaUpdateOp(command_args)); + if (!operation) + return ComponentUnpacker::kDeltaUnsupportedCommand; + + ComponentUnpacker::Error result = operation->Run( + command_args, input_dir, unpack_dir, patcher, installer, error); + if (result != ComponentUnpacker::kNone) + return result; + } + + return ComponentUnpacker::kNone; +} + diff --git a/chrome/browser/component_updater/component_patcher.h b/chrome/browser/component_updater/component_patcher.h new file mode 100644 index 0000000..1990922 --- /dev/null +++ b/chrome/browser/component_updater/component_patcher.h @@ -0,0 +1,86 @@ +// Copyright 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. + +// Component updates can be either differential updates or full updates. +// Full updates come in CRX format; differential updates come in CRX-style +// archives, but have a different magic number. They contain "commands.json", a +// list of commands for the patcher to follow. The patcher uses these commands, +// the other files in the archive, and the files from the existing installation +// of the component to create the contents of a full update, which is then +// installed normally. +// Component updates are specified by the 'codebasediff' attribute of an +// updatecheck response: +// <updatecheck codebase="http://example.com/extension_1.2.3.4.crx" +// hash="12345" size="9854" status="ok" version="1.2.3.4" +// prodversionmin="2.0.143.0" +// codebasediff="http://example.com/diff_1.2.3.4.crx" +// hashdiff="123" sizediff="101" +// fp="1.123" /> +// The component updater will attempt a differential update if it is available +// and allowed to, and fall back to a full update if it fails. +// +// After installation (diff or full), the component updater records "fp", the +// fingerprint of the installed files, to later identify the existing files to +// the server so that a proper differential update can be provided next cycle. + + +#ifndef CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_H_ +#define CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/component_updater/component_unpacker.h" + +namespace base { +class FilePath; +} + +class ComponentInstaller; + +// Applies a delta patch to a single file. Specifically, creates a file at +// |output_file| using |input_file| patched according to the algorithm +// specified by |patch_type| using |patch_file|. Sets the value of error to +// the error code of the failing patch operation, if there is such a failure. +class ComponentPatcher { + public: + // The type of a patch file. + enum PatchType { + kPatchTypeUnknown, + kPatchTypeCourgette, + kPatchTypeBsdiff, + }; + + virtual ComponentUnpacker::Error Patch(PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) = 0; + virtual ~ComponentPatcher() {} +}; + +class ComponentPatcherCrossPlatform : public ComponentPatcher { + public: + ComponentPatcherCrossPlatform(); + virtual ComponentUnpacker::Error Patch(PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) OVERRIDE; + private: + DISALLOW_COPY_AND_ASSIGN(ComponentPatcherCrossPlatform); +}; + +// This function takes an unpacked differential CRX (|input_dir|) and a +// component installer, and creates a new (non-differential) unpacked CRX, which +// is then installed normally. +// The non-differential files are written into the |unpack_dir| directory. +// Sets |error| to the error code of the first failing patch operation. +ComponentUnpacker::Error DifferentialUpdatePatch( + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentPatcher* component_patcher, + ComponentInstaller* installer, + int* error); + +#endif // CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_H_ diff --git a/chrome/browser/component_updater/component_patcher_operation.cc b/chrome/browser/component_updater/component_patcher_operation.cc new file mode 100644 index 0000000..0c119919 --- /dev/null +++ b/chrome/browser/component_updater/component_patcher_operation.cc @@ -0,0 +1,222 @@ +// Copyright 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 "chrome/browser/component_updater/component_patcher_operation.h" + +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/json/json_file_value_serializer.h" +#include "base/memory/scoped_handle.h" +#include "base/path_service.h" +#include "base/strings/string_number_conversions.h" +#include "chrome/browser/component_updater/component_patcher.h" +#include "chrome/browser/component_updater/component_updater_service.h" +#include "chrome/common/extensions/extension_constants.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "crypto/signature_verifier.h" +#include "extensions/common/crx_file.h" +#include "third_party/zlib/google/zip.h" + +using crypto::SecureHash; + +namespace { + +const char kInput[] = "input"; +const char kOp[] = "op"; +const char kOutput[] = "output"; +const char kPatch[] = "patch"; +const char kSha256[] = "sha256"; + +} // namespace + +DeltaUpdateOp* CreateDeltaUpdateOp(base::DictionaryValue* command) { + std::string operation; + if (!command->GetString(kOp, &operation)) + return NULL; + if (operation == "copy") + return new DeltaUpdateOpCopy(); + else if (operation == "create") + return new DeltaUpdateOpCreate(); + else if (operation == "bsdiff") + return new DeltaUpdateOpPatchBsdiff(); + else if (operation == "courgette") + return new DeltaUpdateOpPatchCourgette(); + return NULL; +} + +DeltaUpdateOp::DeltaUpdateOp() {} + +DeltaUpdateOp::~DeltaUpdateOp() {} + +ComponentUnpacker::Error DeltaUpdateOp::Run(base::DictionaryValue* command_args, + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentPatcher* patcher, + ComponentInstaller* installer, + int* error) { + std::string output_rel_path; + if (!command_args->GetString(kOutput, &output_rel_path) || + !command_args->GetString(kSha256, &output_sha256_)) + return ComponentUnpacker::kDeltaBadCommands; + + output_abs_path_ = unpack_dir.Append( + base::FilePath::FromUTF8Unsafe(output_rel_path)); + ComponentUnpacker::Error parse_result = DoParseArguments( + command_args, input_dir, installer); + if (parse_result != ComponentUnpacker::kNone) + return parse_result; + + const base::FilePath parent = output_abs_path_.DirName(); + if (!file_util::DirectoryExists(parent)) { + if (!file_util::CreateDirectory(parent)) + return ComponentUnpacker::kIoError; + } + + ComponentUnpacker::Error run_result = DoRun(patcher, error); + if (run_result != ComponentUnpacker::kNone) + return run_result; + + return CheckHash(); +} + +// Uses the hash as a checksum to confirm that the file now residing in the +// output directory probably has the contents it should. +ComponentUnpacker::Error DeltaUpdateOp::CheckHash() { + std::vector<uint8> expected_hash; + if (!base::HexStringToBytes(output_sha256_, &expected_hash) || + expected_hash.size() != crypto::kSHA256Length) + return ComponentUnpacker::kDeltaVerificationFailure; + + base::MemoryMappedFile output_file_mmapped; + if (!output_file_mmapped.Initialize(output_abs_path_)) + return ComponentUnpacker::kDeltaVerificationFailure; + + uint8 actual_hash[crypto::kSHA256Length] = {0}; + const scoped_ptr<SecureHash> hasher(SecureHash::Create(SecureHash::SHA256)); + hasher->Update(output_file_mmapped.data(), output_file_mmapped.length()); + hasher->Finish(actual_hash, sizeof(actual_hash)); + if (memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash))) + return ComponentUnpacker::kDeltaVerificationFailure; + + return ComponentUnpacker::kNone; +} + +DeltaUpdateOpCopy::DeltaUpdateOpCopy() {} + +ComponentUnpacker::Error DeltaUpdateOpCopy::DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string input_rel_path; + if (!command_args->GetString(kInput, &input_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) + return ComponentUnpacker::kDeltaMissingExistingFile; + + return ComponentUnpacker::kNone; +} + +ComponentUnpacker::Error DeltaUpdateOpCopy::DoRun(ComponentPatcher*, + int* error) { + *error = 0; + if (!file_util::CopyFile(input_abs_path_, output_abs_path_)) + return ComponentUnpacker::kDeltaOperationFailure; + + return ComponentUnpacker::kNone; +} + +DeltaUpdateOpCreate::DeltaUpdateOpCreate() {} + +ComponentUnpacker::Error DeltaUpdateOpCreate::DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string patch_rel_path; + if (!command_args->GetString(kPatch, &patch_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + patch_abs_path_ = input_dir.Append( + base::FilePath::FromUTF8Unsafe(patch_rel_path)); + + return ComponentUnpacker::kNone; +} + +ComponentUnpacker::Error DeltaUpdateOpCreate::DoRun(ComponentPatcher*, + int* error) { + *error = 0; + if (!file_util::Move(patch_abs_path_, output_abs_path_)) + return ComponentUnpacker::kDeltaOperationFailure; + + return ComponentUnpacker::kNone; +} + +DeltaUpdateOpPatchBsdiff::DeltaUpdateOpPatchBsdiff() {} + +ComponentUnpacker::Error DeltaUpdateOpPatchBsdiff::DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string patch_rel_path; + std::string input_rel_path; + if (!command_args->GetString(kPatch, &patch_rel_path) || + !command_args->GetString(kInput, &input_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) + return ComponentUnpacker::kDeltaMissingExistingFile; + + patch_abs_path_ = input_dir.Append( + base::FilePath::FromUTF8Unsafe(patch_rel_path)); + + return ComponentUnpacker::kNone; +} + +ComponentUnpacker::Error DeltaUpdateOpPatchBsdiff::DoRun( + ComponentPatcher* patcher, + int* error) { + *error = 0; + return patcher->Patch(ComponentPatcher::kPatchTypeBsdiff, + input_abs_path_, + patch_abs_path_, + output_abs_path_, + error); +} + +DeltaUpdateOpPatchCourgette::DeltaUpdateOpPatchCourgette() {} + +ComponentUnpacker::Error DeltaUpdateOpPatchCourgette::DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) { + std::string patch_rel_path; + std::string input_rel_path; + if (!command_args->GetString(kPatch, &patch_rel_path) || + !command_args->GetString(kInput, &input_rel_path)) + return ComponentUnpacker::kDeltaBadCommands; + + if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) + return ComponentUnpacker::kDeltaMissingExistingFile; + + patch_abs_path_ = input_dir.Append( + base::FilePath::FromUTF8Unsafe(patch_rel_path)); + + return ComponentUnpacker::kNone; +} + +ComponentUnpacker::Error DeltaUpdateOpPatchCourgette::DoRun( + ComponentPatcher* patcher, + int* error) { + *error = 0; + return patcher->Patch(ComponentPatcher::kPatchTypeCourgette, + input_abs_path_, + patch_abs_path_, + output_abs_path_, + error); +} + diff --git a/chrome/browser/component_updater/component_patcher_operation.h b/chrome/browser/component_updater/component_patcher_operation.h new file mode 100644 index 0000000..6e9fe5be --- /dev/null +++ b/chrome/browser/component_updater/component_patcher_operation.h @@ -0,0 +1,158 @@ +// Copyright 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 CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_OPERATION_H_ +#define CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_OPERATION_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/component_updater/component_unpacker.h" + +namespace base { + +class FilePath; +class DictionaryValue; + +} // namespace base + +class ComponentInstaller; +class ComponentPatcher; + +class DeltaUpdateOp { + public: + + DeltaUpdateOp(); + virtual ~DeltaUpdateOp(); + + // Parses, runs, and verifies the operation, returning an error code if an + // error is encountered, and DELTA_OK otherwise. In case of errors, + // extended error information can be returned in the |error| parameter. + ComponentUnpacker::Error Run( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + const base::FilePath& unpack_dir, + ComponentPatcher* patcher, + ComponentInstaller* installer, + int* error); + + protected: + std::string output_sha256_; + base::FilePath output_abs_path_; + + private: + ComponentUnpacker::Error CheckHash(); + + // Subclasses must override DoParseArguments to parse operation-specific + // arguments. DoParseArguments returns DELTA_OK on success; any other code + // represents failure. + virtual ComponentUnpacker::Error DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) = 0; + + // Subclasses must override DoRun to actually perform the patching operation. + // DoRun returns DELTA_OK on success; any other code represents failure. + // Additional error information can be returned in the |error| parameter. + virtual ComponentUnpacker::Error DoRun(ComponentPatcher* patcher, + int* error) = 0; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOp); +}; + +// A 'copy' operation takes a file currently residing on the disk and moves it +// into the unpacking directory: this represents "no change" in the file being +// installed. +class DeltaUpdateOpCopy : public DeltaUpdateOp { + public: + DeltaUpdateOpCopy(); + + private: + // Overrides of DeltaUpdateOp. + virtual ComponentUnpacker::Error DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) OVERRIDE; + + virtual ComponentUnpacker::Error DoRun(ComponentPatcher* patcher, + int* error) OVERRIDE; + + base::FilePath input_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCopy); +}; + +// A 'create' operation takes a full file that was sent in the delta update +// archive and moves it into the unpacking directory: this represents the +// addition of a new file, or a file so different that no bandwidth could be +// saved by transmitting a differential update. +class DeltaUpdateOpCreate : public DeltaUpdateOp { + public: + DeltaUpdateOpCreate(); + + private: + // Overrides of DeltaUpdateOp. + virtual ComponentUnpacker::Error DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) OVERRIDE; + + virtual ComponentUnpacker::Error DoRun(ComponentPatcher* patcher, + int* error) OVERRIDE; + + base::FilePath patch_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCreate); +}; + +// A 'bsdiff' operation takes an existing file on disk, and a bsdiff- +// format patch file provided in the delta update package, and runs bsdiff +// to construct an output file in the unpacking directory. +class DeltaUpdateOpPatchBsdiff : public DeltaUpdateOp { + public: + DeltaUpdateOpPatchBsdiff(); + + private: + // Overrides of DeltaUpdateOp. + virtual ComponentUnpacker::Error DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) OVERRIDE; + + virtual ComponentUnpacker::Error DoRun(ComponentPatcher* patcher, + int* error) OVERRIDE; + + base::FilePath patch_abs_path_; + base::FilePath input_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpPatchBsdiff); +}; + +// A 'courgette' operation takes an existing file on disk, and a Courgette- +// format patch file provided in the delta update package, and runs Courgette +// to construct an output file in the unpacking directory. +class DeltaUpdateOpPatchCourgette : public DeltaUpdateOp { + public: + DeltaUpdateOpPatchCourgette(); + + private: + // Overrides of DeltaUpdateOp. + virtual ComponentUnpacker::Error DoParseArguments( + base::DictionaryValue* command_args, + const base::FilePath& input_dir, + ComponentInstaller* installer) OVERRIDE; + + virtual ComponentUnpacker::Error DoRun(ComponentPatcher* patcher, + int* error) OVERRIDE; + + base::FilePath patch_abs_path_; + base::FilePath input_abs_path_; + + DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpPatchCourgette); +}; + +// Factory function to create DeltaUpdateOp instances. +DeltaUpdateOp* CreateDeltaUpdateOp(base::DictionaryValue* command); + +#endif // CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_OPERATION_H_ diff --git a/chrome/browser/component_updater/component_patcher_win.cc b/chrome/browser/component_updater/component_patcher_win.cc new file mode 100644 index 0000000..97aaeee --- /dev/null +++ b/chrome/browser/component_updater/component_patcher_win.cc @@ -0,0 +1,98 @@ +// Copyright 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 "chrome/browser/component_updater/component_patcher_win.h" + +#include <string> + +#include "base/base_paths.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "chrome/installer/util/util_constants.h" + +namespace { + +std::string PatchTypeToCommandLineSwitch( + ComponentPatcher::PatchType patch_type) { + if (patch_type == ComponentPatcher::kPatchTypeCourgette) + return std::string(installer::kCourgette); + else if (patch_type == ComponentPatcher::kPatchTypeBsdiff) + return std::string(installer::kBsdiff); + else + return std::string(); +} + +// Finds the path to the setup.exe. First, it looks for the program in the +// "installer" directory. If the program is not found there, it tries to find it +// in the directory where chrome.dll lives. Returns the path to the setup.exe, +// if the path exists, otherwise it returns an an empty path. +base::FilePath FindSetupProgram() { + base::FilePath exe_dir; + if (!PathService::Get(base::DIR_MODULE, &exe_dir)) + return base::FilePath(); + + const std::string installer_dir(WideToASCII(installer::kInstallerDir)); + const std::string setup_exe(WideToASCII(installer::kSetupExe)); + + base::FilePath setup_path = exe_dir; + setup_path = setup_path.AppendASCII(installer_dir); + setup_path = setup_path.AppendASCII(setup_exe); + if (file_util::PathExists(setup_path)) + return setup_path; + + setup_path = exe_dir; + setup_path = setup_path.AppendASCII(setup_exe); + if (file_util::PathExists(setup_path)) + return setup_path; + + return base::FilePath(); +} + +} // namespace + +// Applies the patch to the input file. Returns kNone if the patch was +// successfully applied, kDeltaOperationFailure if the patch operation +// encountered errors, and kDeltaPatchProcessFailure if there was an error +// when running the patch code out of process. In the error case, detailed error +// information could be returned in the error parameter. +ComponentUnpacker::Error ComponentPatcherWin::Patch( + PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) { + *error = 0; + + const base::FilePath exe_path = FindSetupProgram(); + if (exe_path.empty()) + return ComponentUnpacker::kDeltaPatchProcessFailure; + + const std::string patch_type_str(PatchTypeToCommandLineSwitch(patch_type)); + + CommandLine cl(CommandLine::NO_PROGRAM); + cl.AppendSwitchASCII(installer::switches::kPatch, patch_type_str.c_str()); + cl.AppendSwitchPath(installer::switches::kInputFile, input_file); + cl.AppendSwitchPath(installer::switches::kPatchFile, patch_file); + cl.AppendSwitchPath(installer::switches::kOutputFile, output_file); + + base::LaunchOptions launch_options; + launch_options.wait = true; + launch_options.start_hidden = true; + CommandLine setup_path(exe_path); + setup_path.AppendArguments(cl, false); + + base::ProcessHandle ph; + int exit_code = 0; + if (!base::LaunchProcess(setup_path, launch_options, &ph) || + !base::WaitForExitCode(ph, &exit_code)) + return ComponentUnpacker::kDeltaPatchProcessFailure; + + *error = exit_code; + return *error ? ComponentUnpacker::kDeltaOperationFailure : + ComponentUnpacker::kNone; +} + diff --git a/chrome/browser/component_updater/component_patcher_win.h b/chrome/browser/component_updater/component_patcher_win.h new file mode 100644 index 0000000..d87ba54 --- /dev/null +++ b/chrome/browser/component_updater/component_patcher_win.h @@ -0,0 +1,24 @@ +// Copyright 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 CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_WIN_H_ +#define CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_WIN_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/component_updater/component_patcher.h" + +class ComponentPatcherWin : public ComponentPatcher { + public: + ComponentPatcherWin() {} + virtual ComponentUnpacker::Error Patch(PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) OVERRIDE; + private: + DISALLOW_COPY_AND_ASSIGN(ComponentPatcherWin); +}; + +#endif // CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_PATCHER_WIN_H_ diff --git a/chrome/browser/component_updater/component_unpacker.cc b/chrome/browser/component_updater/component_unpacker.cc index 9454637..341df22 100644 --- a/chrome/browser/component_updater/component_unpacker.cc +++ b/chrome/browser/component_updater/component_unpacker.cc @@ -12,6 +12,7 @@ #include "base/memory/scoped_handle.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" +#include "chrome/browser/component_updater/component_patcher.h" #include "chrome/browser/component_updater/component_updater_service.h" #include "chrome/common/extensions/extension_constants.h" #include "crypto/secure_hash.h" @@ -22,11 +23,12 @@ using crypto::SecureHash; namespace { + // This class makes sure that the CRX digital signature is valid // and well formed. class CRXValidator { public: - explicit CRXValidator(FILE* crx_file) : valid_(false) { + explicit CRXValidator(FILE* crx_file) : valid_(false), delta_(false) { extensions::CrxFile::Header header; size_t len = fread(&header, 1, sizeof(header), crx_file); if (len < sizeof(header)) @@ -37,6 +39,7 @@ class CRXValidator { extensions::CrxFile::Parse(header, &error)); if (!crx.get()) return; + delta_ = extensions::CrxFile::HeaderIsDelta(header); std::vector<uint8> key(header.key_size); len = fread(&key[0], sizeof(uint8), header.key_size, crx_file); @@ -72,10 +75,13 @@ class CRXValidator { bool valid() const { return valid_; } + bool delta() const { return delta_; } + const std::vector<uint8>& public_key() const { return public_key_; } private: bool valid_; + bool delta_; std::vector<uint8> public_key_; }; @@ -98,12 +104,29 @@ base::DictionaryValue* ReadManifest(const base::FilePath& unpack_path) { return static_cast<base::DictionaryValue*>(root.release()); } +// Deletes a path if it exists, and then creates a directory there. +// Returns true if and only if these operations were successful. +// This method doesn't take any special steps to prevent files from +// being inserted into the target directory by another process or thread. +bool MakeEmptyDirectory(const base::FilePath& path) { + if (file_util::PathExists(path)) { + if (!file_util::Delete(path, true)) + return false; + } + if (!file_util::CreateDirectory(path)) + return false; + return true; +} + } // namespace. ComponentUnpacker::ComponentUnpacker(const std::vector<uint8>& pk_hash, const base::FilePath& path, + const std::string& fingerprint, + ComponentPatcher* patcher, ComponentInstaller* installer) - : error_(kNone) { + : error_(kNone), + extended_error_(0) { if (pk_hash.empty() || path.empty()) { error_ = kInvalidParams; return; @@ -135,31 +158,61 @@ ComponentUnpacker::ComponentUnpacker(const std::vector<uint8>& pk_hash, return; } // We want the temporary directory to be unique and yet predictable, so - // we can easily find the package in a end user machine. - std::string dir( + // we can easily find the package in an end user machine. + const std::string dir( base::StringPrintf("CRX_%s", base::HexEncode(hash, 6).c_str())); unpack_path_ = path.DirName().AppendASCII(dir.c_str()); - if (file_util::DirectoryExists(unpack_path_)) { - if (!file_util::Delete(unpack_path_, true)) { - unpack_path_.clear(); - error_ = kUzipPathError; - return; - } - } - if (!file_util::CreateDirectory(unpack_path_)) { + if (!MakeEmptyDirectory(unpack_path_)) { unpack_path_.clear(); - error_ = kUzipPathError; + error_ = kUnzipPathError; return; } - if (!zip::Unzip(path, unpack_path_)) { - error_ = kUnzipFailed; - return; + if (validator.delta()) { // Package is a diff package. + // We want a different temp directory for the delta files; we'll put the + // patch output into unpack_path_. + std::string dir( + base::StringPrintf("CRX_%s_diff", base::HexEncode(hash, 6).c_str())); + base::FilePath unpack_diff_path = path.DirName().AppendASCII(dir.c_str()); + if (!MakeEmptyDirectory(unpack_diff_path)) { + error_ = kUnzipPathError; + return; + } + if (!zip::Unzip(path, unpack_diff_path)) { + error_ = kUnzipFailed; + return; + } + ComponentUnpacker::Error result = DifferentialUpdatePatch(unpack_diff_path, + unpack_path_, + patcher, + installer, + &extended_error_); + file_util::Delete(unpack_diff_path, true); + unpack_diff_path.clear(); + error_ = result; + if (error_ != kNone) { + return; + } + } else { + // Package is a normal update/install; unzip it into unpack_path_ directly. + if (!zip::Unzip(path, unpack_path_)) { + error_ = kUnzipFailed; + return; + } } scoped_ptr<base::DictionaryValue> manifest(ReadManifest(unpack_path_)); if (!manifest.get()) { error_ = kBadManifest; return; } + // Write the fingerprint to disk. + if (static_cast<int>(fingerprint.size()) != + file_util::WriteFile( + unpack_path_.Append(FILE_PATH_LITERAL("manifest.fingerprint")), + fingerprint.c_str(), + fingerprint.size())) { + error_ = kFingerprintWriteFailed; + return; + } if (!installer->Install(*manifest, unpack_path_)) { error_ = kInstallerError; return; @@ -169,7 +222,6 @@ ComponentUnpacker::ComponentUnpacker(const std::vector<uint8>& pk_hash, } ComponentUnpacker::~ComponentUnpacker() { - if (!unpack_path_.empty()) { + if (!unpack_path_.empty()) file_util::Delete(unpack_path_, true); - } } diff --git a/chrome/browser/component_updater/component_unpacker.h b/chrome/browser/component_updater/component_unpacker.h index e51555c..6ae7277 100644 --- a/chrome/browser/component_updater/component_unpacker.h +++ b/chrome/browser/component_updater/component_unpacker.h @@ -5,11 +5,13 @@ #ifndef CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_UNPACKER_H_ #define CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_UNPACKER_H_ +#include <string> #include <vector> - +#include "base/basictypes.h" #include "base/files/file_path.h" class ComponentInstaller; +class ComponentPatcher; // In charge of unpacking the component CRX package and verifying that it is // well formed and the cryptographic signature is correct. If there is no @@ -26,22 +28,33 @@ class ComponentInstaller; class ComponentUnpacker { public: // Possible error conditions. + // Add only to the bottom of this enum; the order must be kept stable. enum Error { kNone, kInvalidParams, kInvalidFile, - kUzipPathError, + kUnzipPathError, kUnzipFailed, kNoManifest, kBadManifest, kBadExtension, kInvalidId, kInstallerError, + kIoError, + kDeltaVerificationFailure, + kDeltaBadCommands, + kDeltaUnsupportedCommand, + kDeltaOperationFailure, + kDeltaPatchProcessFailure, + kDeltaMissingExistingFile, + kFingerprintWriteFailed, }; // Unpacks, verifies and calls the installer. |pk_hash| is the expected // public key SHA256 hash. |path| is the current location of the CRX. ComponentUnpacker(const std::vector<uint8>& pk_hash, const base::FilePath& path, + const std::string& fingerprint, + ComponentPatcher* patcher, ComponentInstaller* installer); // If something went wrong during unpacking or installer invocation, the @@ -50,9 +63,12 @@ class ComponentUnpacker { Error error() const { return error_; } + int extended_error() const { return extended_error_; } + private: base::FilePath unpack_path_; Error error_; + int extended_error_; // Provides additional error information. }; #endif // CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_UNPACKER_H_ diff --git a/chrome/browser/component_updater/component_updater_configurator.cc b/chrome/browser/component_updater/component_updater_configurator.cc index 16fa3e9..43e9072 100644 --- a/chrome/browser/component_updater/component_updater_configurator.cc +++ b/chrome/browser/component_updater/component_updater_configurator.cc @@ -14,31 +14,39 @@ #include "base/strings/string_util.h" #include "base/win/windows_version.h" #include "build/build_config.h" +#include "chrome/browser/component_updater/component_patcher.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/omaha_query_params/omaha_query_params.h" #include "net/url_request/url_request_context_getter.h" +#if defined(OS_WIN) +#include "chrome/browser/component_updater/component_patcher_win.h" +#endif + namespace { + // Default time constants. const int kDelayOneMinute = 60; const int kDelayOneHour = kDelayOneMinute * 60; // Debug values you can pass to --component-updater-debug=value1,value2. // Speed up component checking. -const char kDebugFastUpdate[] = "fast-update"; +const char kSwitchFastUpdate[] = "fast-update"; // Force out-of-process-xml parsing. -const char kDebugOutOfProcess[] = "out-of-process"; +const char kSwitchOutOfProcess[] = "out-of-process"; // Add "testrequest=1" parameter to the update check query. -const char kDebugRequestParam[] = "test-request"; +const char kSwitchRequestParam[] = "test-request"; +// Disables differential updates. +const char kSwitchDisableDeltaUpdates[] = "disable-delta-updates"; // The urls from which an update manifest can be fetched. const char* kUrlSources[] = { "http://clients2.google.com/service/update2/crx", // BANDAID "http://omaha.google.com/service/update2/crx", // CWS_PUBLIC - "http://omaha.sandbox.google.com/service/update2/crx" // CWS_SANDBOX + "http://omaha.sandbox.google.com/service/update2/crx", // CWS_SANDBOX }; -bool HasDebugValue(const std::vector<std::string>& vec, const char* test) { +bool HasSwitchValue(const std::vector<std::string>& vec, const char* test) { if (vec.empty()) return 0; return (std::find(vec.begin(), vec.end(), test) != vec.end()); @@ -64,25 +72,36 @@ class ChromeConfigurator : public ComponentUpdateService::Configurator { virtual net::URLRequestContextGetter* RequestContext() OVERRIDE; virtual bool InProcess() OVERRIDE; virtual void OnEvent(Events event, int val) OVERRIDE; + virtual ComponentPatcher* CreateComponentPatcher() OVERRIDE; + virtual bool DeltasEnabled() const OVERRIDE; private: net::URLRequestContextGetter* url_request_getter_; std::string extra_info_; bool fast_update_; bool out_of_process_; + bool deltas_enabled_; }; ChromeConfigurator::ChromeConfigurator(const CommandLine* cmdline, net::URLRequestContextGetter* url_request_getter) : url_request_getter_(url_request_getter), extra_info_(chrome::OmahaQueryParams::Get( - chrome::OmahaQueryParams::CHROME)) { + chrome::OmahaQueryParams::CHROME)), + fast_update_(false), + out_of_process_(false), + deltas_enabled_(false) { // Parse comma-delimited debug flags. - std::vector<std::string> debug_values; - Tokenize(cmdline->GetSwitchValueASCII(switches::kComponentUpdaterDebug), - ",", &debug_values); - fast_update_ = HasDebugValue(debug_values, kDebugFastUpdate); - out_of_process_ = HasDebugValue(debug_values, kDebugOutOfProcess); + std::vector<std::string> switch_values; + Tokenize(cmdline->GetSwitchValueASCII(switches::kComponentUpdater), + ",", &switch_values); + fast_update_ = HasSwitchValue(switch_values, kSwitchFastUpdate); + out_of_process_ = HasSwitchValue(switch_values, kSwitchOutOfProcess); +#if defined(OS_WIN) + deltas_enabled_ = !HasSwitchValue(switch_values, kSwitchDisableDeltaUpdates); +#else + deltas_enabled_ = false; +#endif // Make the extra request params, they are necessary so omaha does // not deliver components that are going to be rejected at install time. @@ -91,7 +110,7 @@ ChromeConfigurator::ChromeConfigurator(const CommandLine* cmdline, base::win::OSInfo::WOW64_ENABLED) extra_info_ += "&wow64=1"; #endif - if (HasDebugValue(debug_values, kDebugRequestParam)) + if (HasSwitchValue(switch_values, kSwitchRequestParam)) extra_info_ += "&testrequest=1"; } @@ -161,6 +180,18 @@ void ChromeConfigurator::OnEvent(Events event, int val) { } } +ComponentPatcher* ChromeConfigurator::CreateComponentPatcher() { +#if defined(OS_WIN) + return new ComponentPatcherWin(); +#else + return new ComponentPatcherCrossPlatform(); +#endif +} + +bool ChromeConfigurator::DeltasEnabled() const { + return deltas_enabled_; +} + ComponentUpdateService::Configurator* MakeChromeComponentUpdaterConfigurator( const CommandLine* cmdline, net::URLRequestContextGetter* context_getter) { return new ChromeConfigurator(cmdline, context_getter); diff --git a/chrome/browser/component_updater/component_updater_service.cc b/chrome/browser/component_updater/component_updater_service.cc index f3eee77..367ff6e 100644 --- a/chrome/browser/component_updater/component_updater_service.cc +++ b/chrome/browser/component_updater/component_updater_service.cc @@ -10,6 +10,7 @@ #include "base/at_exit.h" #include "base/bind.h" +#include "base/compiler_specific.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/logging.h" @@ -21,11 +22,13 @@ #include "base/strings/stringprintf.h" #include "base/timer.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/component_updater/component_patcher.h" #include "chrome/browser/component_updater/component_unpacker.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_utility_messages.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/omaha_query_params/omaha_query_params.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/utility_process_host.h" @@ -47,21 +50,24 @@ using extensions::Extension; // base::Bind() calls are not refcounted. namespace { + // Manifest sources, from most important to least important. const CrxComponent::UrlSource kManifestSources[] = { CrxComponent::BANDAID, CrxComponent::CWS_PUBLIC, - CrxComponent::CWS_SANDBOX + CrxComponent::CWS_SANDBOX, }; // Extends an omaha compatible update check url |query| string. Does // not mutate the string if it would be longer than |limit| chars. bool AddQueryString(const std::string& id, const std::string& version, + const std::string& fingerprint, size_t limit, std::string* query) { std::string additional = - base::StringPrintf("id=%s&v=%s&uc", id.c_str(), version.c_str()); + base::StringPrintf("id=%s&v=%s&fp=%s&uc", + id.c_str(), version.c_str(), fingerprint.c_str()); additional = "x=" + net::EscapeQueryParamValue(additional, true); if ((additional.size() + query->size() + 1) > limit) return false; @@ -163,7 +169,7 @@ void StartFetch(net::URLFetcher* fetcher, fetcher->Start(); } -// Returs true if the url request of |fetcher| was succesful. +// Returns true if the url request of |fetcher| was succesful. bool FetchSuccess(const net::URLFetcher& fetcher) { return (fetcher.GetStatus().status() == net::URLRequestStatus::SUCCESS) && (fetcher.GetResponseCode() == 200); @@ -174,23 +180,44 @@ bool FetchSuccess(const net::URLFetcher& fetcher) { // which is supplied by the the component updater client and |status| which // is modified as the item is processed by the update pipeline. The expected // transition graph is: -// error error error -// +--kNoUpdate<------<-------+------<------+------<------+ -// | | | | -// V yes | | | -// kNew --->kChecking-->[update?]----->kCanUpdate-->kDownloading-->kUpdating -// ^ | | -// | |no | -// |--kUpToDate<---+ | -// | success | -// +--kUpdated<-------------------------------------------+ +// +// kNew +// | +// V +// +----------------------> kChecking -<---------+-----<-------+ +// | | | | +// | error V no | | +// kNoUpdate <---------------- [update?] ->---- kUpToDate kUpdated +// ^ | ^ +// | yes | | +// | diff=false V | +// | +-----------> kCanUpdate | +// | | | | +// | | V no | +// | | [differential update?]->----+ | +// | | | | | +// | | yes | | | +// | | error V | | +// | +---------<- kDownloadingDiff | | +// | | | | | +// | | | | | +// | | error V | | +// | +---------<- kUpdatingDiff ->--------|-----------+ success +// | | | +// | error V | +// +----------------------------------------- kDownloading | +// | | | +// | error V | +// +------------------------------------------ kUpdating ->----+ success // struct CrxUpdateItem { enum Status { kNew, kChecking, kCanUpdate, + kDownloadingDiff, kDownloading, + kUpdatingDiff, kUpdating, kUpdated, kUpToDate, @@ -199,13 +226,33 @@ struct CrxUpdateItem { }; Status status; - GURL crx_url; std::string id; - base::Time last_check; CrxComponent component; + + base::Time last_check; + + // These members are initialized with their corresponding values from the + // update server response. + GURL crx_url; + GURL diff_crx_url; + int size; + int diff_size; + + // The from/to version and fingerprint values. + Version previous_version; Version next_version; + std::string previous_fp; + std::string next_fp; - CrxUpdateItem() : status(kNew) {} + // True if the differential update failed for any reason. + bool diff_update_failed; + + CrxUpdateItem() + : status(kNew), + size(0), + diff_size(0), + diff_update_failed(false) { + } // Function object used to find a specific component. class FindById { @@ -220,9 +267,21 @@ struct CrxUpdateItem { }; }; -} // namespace. +// Returns true if a differential update is available for the update item. +bool IsDiffUpdateAvailable(const CrxUpdateItem* update_item) { + return update_item->diff_crx_url.is_valid(); +} -typedef ComponentUpdateService::Configurator Config; +// Returns true if a differential update is available, it has not failed yet, +// and the configuration allows it. +bool CanTryDiffUpdate(const CrxUpdateItem* update_item, + const ComponentUpdateService::Configurator& config) { + return IsDiffUpdateAvailable(update_item) && + !update_item->diff_update_failed && + config.DeltasEnabled(); +} + +} // namespace. CrxComponent::CrxComponent() : installer(NULL), @@ -240,7 +299,7 @@ CrxComponent::~CrxComponent() { // rest of the browser, so even if we have many components registered and // eligible for update, we only do one thing at a time with pauses in between // the tasks. Also when we do network requests there is only one |url_fetcher_| -// in flight at at a time. +// in flight at a time. // There are no locks in this code, the main structure |work_items_| is mutated // only from the UI thread. The unpack and installation is done in the file // thread and the network requests are done in the IO thread and in the file @@ -304,6 +363,7 @@ class CrxUpdateService : public ComponentUpdateService { ComponentInstaller* installer; std::vector<uint8> pk_hash; std::string id; + std::string fingerprint; CRXContext() : installer(NULL) {} }; @@ -319,8 +379,7 @@ class CrxUpdateService : public ComponentUpdateService { const UpdateManifest::Results& results); // See ManifestParserBridge. - void OnParseUpdateManifestFailed( - const std::string& error_message); + void OnParseUpdateManifestFailed(const std::string& error_message); bool AddItemToUpdateCheck(CrxUpdateItem* item, std::string* query); @@ -333,19 +392,22 @@ class CrxUpdateService : public ComponentUpdateService { void Install(const CRXContext* context, const base::FilePath& crx_path); void DoneInstalling(const std::string& component_id, - ComponentUnpacker::Error error); + ComponentUnpacker::Error error, + int extended_error); size_t ChangeItemStatus(CrxUpdateItem::Status from, CrxUpdateItem::Status to); CrxUpdateItem* FindUpdateItemById(const std::string& id); - scoped_ptr<Config> config_; + scoped_ptr<ComponentUpdateService::Configurator> config_; + + scoped_ptr<ComponentPatcher> component_patcher_; scoped_ptr<net::URLFetcher> url_fetcher_; - typedef std::vector<CrxUpdateItem*> UpdateItems; // A collection of every work item. + typedef std::vector<CrxUpdateItem*> UpdateItems; UpdateItems work_items_; // A particular set of items from work_items_, which should be checked ASAP. @@ -353,7 +415,8 @@ class CrxUpdateService : public ComponentUpdateService { base::OneShotTimer<CrxUpdateService> timer_; - Version chrome_version_; + const Version chrome_version_; + const std::string prod_id_; bool running_; @@ -362,10 +425,12 @@ class CrxUpdateService : public ComponentUpdateService { ////////////////////////////////////////////////////////////////////////////// -CrxUpdateService::CrxUpdateService( - ComponentUpdateService::Configurator* config) +CrxUpdateService::CrxUpdateService(ComponentUpdateService::Configurator* config) : config_(config), + component_patcher_(config->CreateComponentPatcher()), chrome_version_(chrome::VersionInfo().Version()), + prod_id_(chrome::OmahaQueryParams::GetProdIdString( + chrome::OmahaQueryParams::CHROME)), running_(false) { } @@ -450,7 +515,7 @@ CrxUpdateItem* CrxUpdateService::FindUpdateItemById(const std::string& id) { } // Changes all the components in |work_items_| that have |from| status to -// |to| statatus and returns how many have been changed. +// |to| status and returns how many have been changed. size_t CrxUpdateService::ChangeItemStatus(CrxUpdateItem::Status from, CrxUpdateItem::Status to) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -491,6 +556,7 @@ ComponentUpdateService::Status CrxUpdateService::RegisterComponent( uit = new CrxUpdateItem; uit->id.swap(id); uit->component = component; + work_items_.push_back(uit); // If this is the first component registered we call Start to // schedule the first timer. @@ -501,17 +567,24 @@ ComponentUpdateService::Status CrxUpdateService::RegisterComponent( } // Sets a component to be checked for updates. -// The componet to add is |crxit| and the |query| string is modified with the +// The component to add is |crxit| and the |query| string is modified with the // required omaha compatible query. Returns false when the query strings // is longer than specified by UrlSizeLimit(). bool CrxUpdateService::AddItemToUpdateCheck(CrxUpdateItem* item, std::string* query) { if (!AddQueryString(item->id, item->component.version.GetString(), + item->component.fingerprint, config_->UrlSizeLimit(), query)) return false; + item->status = CrxUpdateItem::kChecking; item->last_check = base::Time::Now(); + item->previous_version = item->component.version; + item->next_version = Version(); + item->previous_fp = item->component.fingerprint; + item->next_fp.clear(); + item->diff_update_failed = false; return true; } @@ -535,16 +608,17 @@ ComponentUpdateService::Status CrxUpdateService::CheckForUpdateSoon( // Check if the request is too soon. base::TimeDelta delta = base::Time::Now() - uit->last_check; - if (delta < base::TimeDelta::FromSeconds(config_->OnDemandDelay())) { + if (delta < base::TimeDelta::FromSeconds(config_->OnDemandDelay())) return kError; - } switch (uit->status) { // If the item is already in the process of being updated, there is // no point in this call, so return kInProgress. case CrxUpdateItem::kChecking: case CrxUpdateItem::kCanUpdate: + case CrxUpdateItem::kDownloadingDiff: case CrxUpdateItem::kDownloading: + case CrxUpdateItem::kUpdatingDiff: case CrxUpdateItem::kUpdating: return kInProgress; // Otherwise the item was already checked a while back (or it is new), @@ -583,13 +657,21 @@ void CrxUpdateService::ProcessPendingItems() { if (item->status != CrxUpdateItem::kCanUpdate) continue; // Found component to update, start the process. - item->status = CrxUpdateItem::kDownloading; CRXContext* context = new CRXContext; context->pk_hash = item->component.pk_hash; context->id = item->id; context->installer = item->component.installer; + context->fingerprint = item->next_fp; + GURL package_url; + if (CanTryDiffUpdate(item, *config_)) { + package_url = item->diff_crx_url; + item->status = CrxUpdateItem::kDownloadingDiff; + } else { + package_url = item->crx_url; + item->status = CrxUpdateItem::kDownloading; + } url_fetcher_.reset(net::URLFetcher::Create( - 0, item->crx_url, net::URLFetcher::GET, + 0, package_url, net::URLFetcher::GET, MakeContextDelegate(this, context))); StartFetch(url_fetcher_.get(), config_->RequestContext(), true); return; @@ -700,11 +782,10 @@ void CrxUpdateService::ParseManifest(const std::string& xml) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (config_->InProcess()) { UpdateManifest manifest; - if (!manifest.Parse(xml)) { + if (!manifest.Parse(xml)) CrxUpdateService::OnParseUpdateManifestFailed(manifest.errors()); - } else { + else CrxUpdateService::OnParseUpdateManifestSucceeded(manifest.results()); - } } else { UtilityProcessHost* host = UtilityProcessHost::Create(new ManifestParserBridge(this), @@ -753,8 +834,12 @@ void CrxUpdateService::OnParseUpdateManifestSucceeded( // All test passed. Queue an upgrade for this component and fire the // notifications. crx->crx_url = it->crx_url; + crx->size = it->size; + crx->diff_crx_url = it->diff_crx_url; + crx->diff_size = it->diff_size; crx->status = CrxUpdateItem::kCanUpdate; crx->next_version = Version(it->version); + crx->next_fp = it->package_fingerprint; ++update_pending; content::NotificationService::current()->Notify( @@ -789,19 +874,39 @@ void CrxUpdateService::OnURLFetchComplete(const net::URLFetcher* source, DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); int error_code = net::OK; + CrxUpdateItem* crx = FindUpdateItemById(context->id); + DCHECK(crx->status == CrxUpdateItem::kDownloadingDiff || + crx->status == CrxUpdateItem::kDownloading); + if (source->FileErrorOccurred(&error_code) || !FetchSuccess(*source)) { + if (crx->status == CrxUpdateItem::kDownloadingDiff) { + size_t count = ChangeItemStatus(CrxUpdateItem::kDownloadingDiff, + CrxUpdateItem::kCanUpdate); + DCHECK_EQ(count, 1ul); + ScheduleNextRun(true); + return; + } size_t count = ChangeItemStatus(CrxUpdateItem::kDownloading, CrxUpdateItem::kNoUpdate); DCHECK_EQ(count, 1ul); config_->OnEvent(Configurator::kNetworkError, CrxIdtoUMAId(context->id)); url_fetcher_.reset(); + ScheduleNextRun(false); } else { base::FilePath temp_crx_path; CHECK(source->GetResponseAsFilePath(true, &temp_crx_path)); - size_t count = ChangeItemStatus(CrxUpdateItem::kDownloading, - CrxUpdateItem::kUpdating); + + size_t count = 0; + if (crx->status == CrxUpdateItem::kDownloadingDiff) { + count = ChangeItemStatus(CrxUpdateItem::kDownloadingDiff, + CrxUpdateItem::kUpdatingDiff); + } else { + count = ChangeItemStatus(CrxUpdateItem::kDownloading, + CrxUpdateItem::kUpdating); + } DCHECK_EQ(count, 1ul); + url_fetcher_.reset(); content::NotificationService::current()->Notify( @@ -829,17 +934,19 @@ void CrxUpdateService::Install(const CRXContext* context, const base::FilePath& crx_path) { // This function owns the |crx_path| and the |context| object. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - ComponentUnpacker - unpacker(context->pk_hash, crx_path, context->installer); - if (!file_util::Delete(crx_path, false)) { + ComponentUnpacker unpacker(context->pk_hash, + crx_path, + context->fingerprint, + component_patcher_.get(), + context->installer); + if (!file_util::Delete(crx_path, false)) NOTREACHED() << crx_path.value(); - } // Why unretained? See comment at top of file. BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, base::Bind(&CrxUpdateService::DoneInstalling, base::Unretained(this), - context->id, unpacker.error()), + context->id, unpacker.error(), unpacker.extended_error()), base::TimeDelta::FromMilliseconds(config_->StepDelay())); delete context; } @@ -847,14 +954,28 @@ void CrxUpdateService::Install(const CRXContext* context, // Installation has been completed. Adjust the component status and // schedule the next check. void CrxUpdateService::DoneInstalling(const std::string& component_id, - ComponentUnpacker::Error error) { + ComponentUnpacker::Error error, + int extra_code) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); CrxUpdateItem* item = FindUpdateItemById(component_id); + if (item->status == CrxUpdateItem::kUpdatingDiff) { + if (error != ComponentUnpacker::kNone) { + item->diff_update_failed = true; + size_t count = ChangeItemStatus(CrxUpdateItem::kUpdatingDiff, + CrxUpdateItem::kCanUpdate); + DCHECK_EQ(count, 1ul); + ScheduleNextRun(true); + return; + } + } + item->status = (error == ComponentUnpacker::kNone) ? CrxUpdateItem::kUpdated : CrxUpdateItem::kNoUpdate; - if (item->status == CrxUpdateItem::kUpdated) + if (item->status == CrxUpdateItem::kUpdated) { item->component.version = item->next_version; + item->component.fingerprint = item->next_fp; + } Configurator::Events event; switch (error) { diff --git a/chrome/browser/component_updater/component_updater_service.h b/chrome/browser/component_updater/component_updater_service.h index 3ac645d..bf011eb4 100644 --- a/chrome/browser/component_updater/component_updater_service.h +++ b/chrome/browser/component_updater/component_updater_service.h @@ -20,6 +20,8 @@ class DictionaryValue; class FilePath; } +class ComponentPatcher; + // Component specific installers must derive from this class and implement // OnUpdateError() and Install(). A valid instance of this class must be // given to ComponentUpdateService::RegisterComponent(). @@ -37,15 +39,22 @@ class ComponentInstaller { virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) = 0; + // Set |installed_file| to the full path to the installed |file|. |file| is + // the filename of the file in this component's CRX. Returns false if this is + // not possible (the file has been removed or modified, or its current + // location is unknown). Otherwise, returns true. + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) = 0; + protected: virtual ~ComponentInstaller() {} }; // Describes a particular component that can be installed or updated. This // structure is required to register a component with the component updater. -// Only |name| is optional. |pk_hash| is the SHA256 hash of the component's -// public key. If the component is to be installed then version should be -// "0" or "0.0", else it should be the current version. +// |pk_hash| is the SHA256 hash of the component's public key. If the component +// is to be installed then version should be "0" or "0.0", else it should be +// the current version. |fingerprint| and |name| are optional. // |source| is by default pointing to BANDAID but if needed it can be made // to point to the webstore (CWS_PUBLIC) or to the webstore sandbox. It is // important to note that the BANDAID source if active throught the day @@ -55,12 +64,13 @@ struct CrxComponent { enum UrlSource { BANDAID, CWS_PUBLIC, - CWS_SANDBOX + CWS_SANDBOX, }; std::vector<uint8> pk_hash; ComponentInstaller* installer; Version version; + std::string fingerprint; std::string name; UrlSource source; CrxComponent(); @@ -128,6 +138,11 @@ class ComponentUpdateService { // happens. It should be used mostly as a place to add application specific // logging or telemetry. |extra| is |event| dependent. virtual void OnEvent(Events event, int extra) = 0; + // Creates a new ComponentPatcher in a platform-specific way. This is useful + // for dependency injection. + virtual ComponentPatcher* CreateComponentPatcher() = 0; + // True means that this client can handle delta updates. + virtual bool DeltasEnabled() const = 0; }; // Start doing update checks and installing new versions of registered diff --git a/chrome/browser/component_updater/pepper_flash_component_installer.cc b/chrome/browser/component_updater/pepper_flash_component_installer.cc index 696c6d9..009afa0 100644 --- a/chrome/browser/component_updater/pepper_flash_component_installer.cc +++ b/chrome/browser/component_updater/pepper_flash_component_installer.cc @@ -250,6 +250,9 @@ class PepperFlashComponentInstaller : public ComponentInstaller { virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) OVERRIDE; + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + private: Version current_version_; }; @@ -292,6 +295,11 @@ bool PepperFlashComponentInstaller::Install( return true; } +bool PepperFlashComponentInstaller::GetInstalledFile( + const std::string& file, base::FilePath* installed_file) { + return false; +} + bool CheckPepperFlashManifest(const base::DictionaryValue& manifest, Version* version_out) { std::string name; diff --git a/chrome/browser/component_updater/pnacl/pnacl_component_installer.cc b/chrome/browser/component_updater/pnacl/pnacl_component_installer.cc index cc8504d..a0d3250 100644 --- a/chrome/browser/component_updater/pnacl/pnacl_component_installer.cc +++ b/chrome/browser/component_updater/pnacl/pnacl_component_installer.cc @@ -281,6 +281,11 @@ bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest, return true; } +bool PnaclComponentInstaller::GetInstalledFile( + const std::string& file, base::FilePath* installed_file) { + return false; +} + namespace { void DoCheckForUpdate(ComponentUpdateService* cus, diff --git a/chrome/browser/component_updater/pnacl/pnacl_component_installer.h b/chrome/browser/component_updater/pnacl/pnacl_component_installer.h index 9ba2997..3ecf2a9 100644 --- a/chrome/browser/component_updater/pnacl/pnacl_component_installer.h +++ b/chrome/browser/component_updater/pnacl/pnacl_component_installer.h @@ -5,6 +5,9 @@ #ifndef CHROME_BROWSER_COMPONENT_UPDATER_PNACL_PNACL_COMPONENT_INSTALLER_H_ #define CHROME_BROWSER_COMPONENT_UPDATER_PNACL_PNACL_COMPONENT_INSTALLER_H_ +#include <string> + +#include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/version.h" @@ -31,6 +34,9 @@ class PnaclComponentInstaller : public ComponentInstaller { virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) OVERRIDE; + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + // Register a PNaCl component for the first time. void RegisterPnaclComponent(ComponentUpdateService* cus, const CommandLine& command_line); diff --git a/chrome/browser/component_updater/pnacl/pnacl_profile_observer.h b/chrome/browser/component_updater/pnacl/pnacl_profile_observer.h index 52ab984..18ef4e8 100644 --- a/chrome/browser/component_updater/pnacl/pnacl_profile_observer.h +++ b/chrome/browser/component_updater/pnacl/pnacl_profile_observer.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_COMPONENT_UPDATER_PNACL_PNACL_PROFILE_OBSERVER_H_ #define CHROME_BROWSER_COMPONENT_UPDATER_PNACL_PNACL_PROFILE_OBSERVER_H_ +#include "base/compiler_specific.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" diff --git a/chrome/browser/component_updater/recovery_component_installer.cc b/chrome/browser/component_updater/recovery_component_installer.cc index c2a7d5a..4891aab1 100644 --- a/chrome/browser/component_updater/recovery_component_installer.cc +++ b/chrome/browser/component_updater/recovery_component_installer.cc @@ -56,6 +56,9 @@ class RecoveryComponentInstaller : public ComponentInstaller { virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) OVERRIDE; + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + private: Version current_version_; PrefService* prefs_; @@ -130,6 +133,11 @@ bool RecoveryComponentInstaller::Install(const base::DictionaryValue& manifest, return base::LaunchProcess(cmdline, base::LaunchOptions(), NULL); } +bool RecoveryComponentInstaller::GetInstalledFile( + const std::string& file, base::FilePath* installed_file) { + return false; +} + void RegisterRecoveryComponent(ComponentUpdateService* cus, PrefService* prefs) { #if !defined(OS_CHROMEOS) diff --git a/chrome/browser/component_updater/swiftshader_component_installer.cc b/chrome/browser/component_updater/swiftshader_component_installer.cc index e09e524..5c2eca8 100644 --- a/chrome/browser/component_updater/swiftshader_component_installer.cc +++ b/chrome/browser/component_updater/swiftshader_component_installer.cc @@ -105,6 +105,9 @@ class SwiftShaderComponentInstaller : public ComponentInstaller { virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) OVERRIDE; + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + private: Version current_version_; }; @@ -149,6 +152,11 @@ bool SwiftShaderComponentInstaller::Install( return true; } +bool SwiftShaderComponentInstaller::GetInstalledFile( + const std::string& file, base::FilePath* installed_file) { + return false; +} + void FinishSwiftShaderUpdateRegistration(ComponentUpdateService* cus, const Version& version) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); diff --git a/chrome/browser/component_updater/test/DEPS b/chrome/browser/component_updater/test/DEPS index e63018d..397b12e 100644 --- a/chrome/browser/component_updater/test/DEPS +++ b/chrome/browser/component_updater/test/DEPS @@ -1,4 +1,5 @@ include_rules = [ # For access to the ppapi test globals. "+ppapi/shared_impl", + "+courgette", ] diff --git a/chrome/browser/component_updater/test/component_patcher_mock.h b/chrome/browser/component_updater/test/component_patcher_mock.h new file mode 100644 index 0000000..1843b29 --- /dev/null +++ b/chrome/browser/component_updater/test/component_patcher_mock.h @@ -0,0 +1,28 @@ +// Copyright 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 CHROME_BROWSER_COMPONENT_UPDATER_TEST_COMPONENT_PATCHER_MOCK_H_ +#define CHROME_BROWSER_COMPONENT_UPDATER_TEST_COMPONENT_PATCHER_MOCK_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/component_updater/component_patcher.h" + +namespace base { +class FilePath; +} + +class MockComponentPatcher : public ComponentPatcher { + public: + MockComponentPatcher() {} + virtual ComponentUnpacker::Error Patch(PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) OVERRIDE; + private: + DISALLOW_COPY_AND_ASSIGN(MockComponentPatcher); +}; + +#endif // CHROME_BROWSER_COMPONENT_UPDATER_TEST_COMPONENT_PATCHER_MOCK_H_ diff --git a/chrome/browser/component_updater/test/component_patcher_unittest.cc b/chrome/browser/component_updater/test/component_patcher_unittest.cc new file mode 100644 index 0000000..a675f28 --- /dev/null +++ b/chrome/browser/component_updater/test/component_patcher_unittest.cc @@ -0,0 +1,200 @@ +// Copyright 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/compiler_specific.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/values.h" +#include "chrome/browser/component_updater/component_patcher.h" +#include "chrome/browser/component_updater/component_patcher_operation.h" +#include "chrome/browser/component_updater/component_updater_service.h" +#include "chrome/browser/component_updater/test/component_patcher_mock.h" +#include "chrome/browser/component_updater/test/test_installer.h" +#include "chrome/common/chrome_paths.h" +#include "courgette/courgette.h" +#include "courgette/third_party/bsdiff.h" +#include "testing/gtest/include/gtest/gtest.h" + +const char binary_output_hash[] = + "599aba6d15a7da390621ef1bacb66601ed6aed04dadc1f9b445dcfe31296142a"; + +// These constants are duplicated from chrome/installer/util/util_constants.h, +// to avoid introducing a dependency from the unit tests to the installer. +const int kCourgetteErrorOffset = 300; +const int kBsdiffErrorOffset = 600; + +base::FilePath test_file(const char* file); + +class ComponentPatcherOperationTest : public testing::Test { + public: + ComponentPatcherOperationTest(); + + protected: + base::ScopedTempDir input_dir_; + base::ScopedTempDir installed_dir_; + base::ScopedTempDir unpack_dir_; + scoped_ptr<MockComponentPatcher> patcher_; + scoped_ptr<ReadOnlyTestInstaller> installer_; +}; + +base::FilePath test_file(const char* file) { + base::FilePath path; + PathService::Get(chrome::DIR_TEST_DATA, &path); + return path.AppendASCII("components").AppendASCII(file); +} + +ComponentPatcherOperationTest::ComponentPatcherOperationTest() { + EXPECT_TRUE(unpack_dir_.CreateUniqueTempDir()); + EXPECT_TRUE(input_dir_.CreateUniqueTempDir()); + EXPECT_TRUE(installed_dir_.CreateUniqueTempDir()); + patcher_.reset(new MockComponentPatcher()); + installer_.reset(new ReadOnlyTestInstaller(installed_dir_.path())); +} + +ComponentUnpacker::Error MockComponentPatcher::Patch( + PatchType patch_type, + const base::FilePath& input_file, + const base::FilePath& patch_file, + const base::FilePath& output_file, + int* error) { + *error = 0; + int exit_code; + if (patch_type == kPatchTypeCourgette) { + exit_code = courgette::ApplyEnsemblePatch(input_file.value().c_str(), + patch_file.value().c_str(), + output_file.value().c_str()); + if (exit_code == courgette::C_OK) + return ComponentUnpacker::kNone; + *error = exit_code + kCourgetteErrorOffset; + } else if (patch_type == kPatchTypeBsdiff) { + exit_code = courgette::ApplyBinaryPatch(input_file, + patch_file, + output_file); + if (exit_code == courgette::OK) + return ComponentUnpacker::kNone; + *error = exit_code + kBsdiffErrorOffset; + } + return ComponentUnpacker::kDeltaOperationFailure; +} + +// Verify that a 'create' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckCreateOperation) { + EXPECT_TRUE(file_util::CopyFile( + test_file("binary_output.bin"), + input_dir_.path().Append(FILE_PATH_LITERAL("binary_output.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "create"); + command_args->SetString("patch", "binary_output.bin"); + + int error = 0; + scoped_ptr<DeltaUpdateOp> op(new DeltaUpdateOpCreate()); + ComponentUnpacker::Error result = op->Run(command_args.get(), + input_dir_.path(), + unpack_dir_.path(), + patcher_.get(), + NULL, + &error); + + EXPECT_EQ(ComponentUnpacker::kNone, result); + EXPECT_EQ(0, error); + EXPECT_TRUE(file_util::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +// Verify that a 'copy' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckCopyOperation) { + EXPECT_TRUE(file_util::CopyFile( + test_file("binary_output.bin"), + installed_dir_.path().Append(FILE_PATH_LITERAL("binary_output.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "copy"); + command_args->SetString("input", "binary_output.bin"); + + int error = 0; + scoped_ptr<DeltaUpdateOp> op(new DeltaUpdateOpCopy()); + ComponentUnpacker::Error result = op->Run(command_args.get(), + input_dir_.path(), + unpack_dir_.path(), + patcher_.get(), + installer_.get(), + &error); + EXPECT_EQ(ComponentUnpacker::kNone, result); + EXPECT_EQ(0, error); + EXPECT_TRUE(file_util::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +// Verify that a 'courgette' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckCourgetteOperation) { + EXPECT_TRUE(file_util::CopyFile( + test_file("binary_input.bin"), + installed_dir_.path().Append(FILE_PATH_LITERAL("binary_input.bin")))); + EXPECT_TRUE(file_util::CopyFile( + test_file("binary_courgette_patch.bin"), + input_dir_.path().Append( + FILE_PATH_LITERAL("binary_courgette_patch.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "courgette"); + command_args->SetString("input", "binary_input.bin"); + command_args->SetString("patch", "binary_courgette_patch.bin"); + + int error = 0; + scoped_ptr<DeltaUpdateOp> op(new DeltaUpdateOpPatchCourgette()); + ComponentUnpacker::Error result = op->Run(command_args.get(), + input_dir_.path(), + unpack_dir_.path(), + patcher_.get(), + installer_.get(), + &error); + EXPECT_EQ(ComponentUnpacker::kNone, result); + EXPECT_EQ(0, error); + EXPECT_TRUE(file_util::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + +// Verify that a 'bsdiff' delta update operation works correctly. +TEST_F(ComponentPatcherOperationTest, CheckBsdiffOperation) { + EXPECT_TRUE(file_util::CopyFile( + test_file("binary_input.bin"), + installed_dir_.path().Append(FILE_PATH_LITERAL("binary_input.bin")))); + EXPECT_TRUE(file_util::CopyFile( + test_file("binary_bsdiff_patch.bin"), + input_dir_.path().Append(FILE_PATH_LITERAL("binary_bsdiff_patch.bin")))); + + scoped_ptr<base::DictionaryValue> command_args(new base::DictionaryValue()); + command_args->SetString("output", "output.bin"); + command_args->SetString("sha256", binary_output_hash); + command_args->SetString("op", "courgette"); + command_args->SetString("input", "binary_input.bin"); + command_args->SetString("patch", "binary_bsdiff_patch.bin"); + + int error = 0; + scoped_ptr<DeltaUpdateOp> op(new DeltaUpdateOpPatchBsdiff()); + ComponentUnpacker::Error result = op->Run(command_args.get(), + input_dir_.path(), + unpack_dir_.path(), + patcher_.get(), + installer_.get(), + &error); + EXPECT_EQ(ComponentUnpacker::kNone, result); + EXPECT_EQ(0, error); + EXPECT_TRUE(file_util::ContentsEqual( + unpack_dir_.path().Append(FILE_PATH_LITERAL("output.bin")), + test_file("binary_output.bin"))); +} + diff --git a/chrome/browser/component_updater/test/component_updater_service_unittest.cc b/chrome/browser/component_updater/test/component_updater_service_unittest.cc index 080b1d2..b389e80 100644 --- a/chrome/browser/component_updater/test/component_updater_service_unittest.cc +++ b/chrome/browser/component_updater/test/component_updater_service_unittest.cc @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/component_updater/component_updater_service.h" - #include <list> #include <utility> @@ -13,7 +11,12 @@ #include "base/memory/scoped_vector.h" #include "base/message_loop.h" #include "base/path_service.h" +#include "base/stringprintf.h" +#include "base/strings/string_number_conversions.h" #include "base/values.h" +#include "chrome/browser/component_updater/component_updater_service.h" +#include "chrome/browser/component_updater/test/component_patcher_mock.h" +#include "chrome/browser/component_updater/test/test_installer.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/notification_observer.h" @@ -23,7 +26,11 @@ #include "content/test/net/url_request_prepackaged_interceptor.h" #include "googleurl/src/gurl.h" #include "libxml/globals.h" +#include "net/base/upload_bytes_element_reader.h" #include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_filter.h" +#include "net/url_request/url_request_simple_job.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -31,98 +38,68 @@ using content::BrowserThread; using content::TestNotificationTracker; namespace { -// Overrides some of the component updater behaviors so it is easier to test -// and loops faster. In actual usage it takes hours do to a full cycle. + +// component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and +// the RSA public key the following hash: +const uint8 jebg_hash[] = {0x94, 0x16, 0x0b, 0x6d, 0x41, 0x75, 0xe9, 0xec, + 0x8e, 0xd5, 0xfa, 0x54, 0xb0, 0xd2, 0xdd, 0xa5, + 0x6e, 0x05, 0x6b, 0xe8, 0x73, 0x47, 0xf6, 0xc4, + 0x11, 0x9f, 0xbc, 0xb3, 0x09, 0xb3, 0x5b, 0x40}; +// component 2 has extension id "abagagagagagagagagagagagagagagag", and +// the RSA public key the following hash: +const uint8 abag_hash[] = {0x01, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x01}; +// component 3 has extension id "ihfokbkgjpifnbbojhneepfflplebdkc", and +// the RSA public key the following hash: +const uint8 ihfo_hash[] = {0x87, 0x5e, 0xa1, 0xa6, 0x9f, 0x85, 0xd1, 0x1e, + 0x97, 0xd4, 0x4f, 0x55, 0xbf, 0xb4, 0x13, 0xa2, + 0xe7, 0xc5, 0xc8, 0xf5, 0x60, 0x19, 0x78, 0x1b, + 0x6d, 0xe9, 0x4c, 0xeb, 0x96, 0x05, 0x42, 0x17}; + class TestConfigurator : public ComponentUpdateService::Configurator { public: - TestConfigurator() - : times_(1), recheck_time_(0), ondemand_time_(0), cus_(NULL) { - } + TestConfigurator(); - virtual int InitialDelay() OVERRIDE { return 0; } + virtual int InitialDelay() OVERRIDE; typedef std::pair<CrxComponent*, int> CheckAtLoopCount; - virtual int NextCheckDelay() OVERRIDE { - // This is called when a new full cycle of checking for updates is going - // to happen. In test we normally only test one cycle so it is a good - // time to break from the test messageloop Run() method so the test can - // finish. - if (--times_ <= 0) { - base::MessageLoop::current()->Quit(); - return 0; + virtual int NextCheckDelay() OVERRIDE; - } + virtual int StepDelay() OVERRIDE; - // Look for checks to issue in the middle of the loop. - for (std::list<CheckAtLoopCount>::iterator - i = components_to_check_.begin(); - i != components_to_check_.end(); ) { - if (i->second == times_) { - cus_->CheckForUpdateSoon(*i->first); - i = components_to_check_.erase(i); - } else { - ++i; - } - } - return 1; - } + virtual int MinimumReCheckWait() OVERRIDE; - virtual int StepDelay() OVERRIDE { - return 0; - } + virtual int OnDemandDelay() OVERRIDE; - virtual int MinimumReCheckWait() OVERRIDE { - return recheck_time_; - } + virtual GURL UpdateUrl(CrxComponent::UrlSource source) OVERRIDE; - virtual int OnDemandDelay() OVERRIDE { - return ondemand_time_; - } + virtual const char* ExtraRequestParams() OVERRIDE; - virtual GURL UpdateUrl(CrxComponent::UrlSource source) OVERRIDE { - switch (source) { - case CrxComponent::BANDAID: - return GURL("http://localhost/upd"); - case CrxComponent::CWS_PUBLIC: - return GURL("http://localhost/cws"); - default: - return GURL("http://wronghost/bad"); - }; - } + virtual size_t UrlSizeLimit() OVERRIDE; - virtual const char* ExtraRequestParams() OVERRIDE { return "extra=foo"; } + virtual net::URLRequestContextGetter* RequestContext() OVERRIDE; - virtual size_t UrlSizeLimit() OVERRIDE { return 256; } + // Don't use the utility process to decode files. + virtual bool InProcess() OVERRIDE; - virtual net::URLRequestContextGetter* RequestContext() OVERRIDE { - return new net::TestURLRequestContextGetter( - BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); - } + virtual void OnEvent(Events event, int extra) OVERRIDE; - // Don't use the utility process to decode files. - virtual bool InProcess() OVERRIDE { return true; } + virtual ComponentPatcher* CreateComponentPatcher() OVERRIDE; - virtual void OnEvent(Events event, int extra) OVERRIDE { } + virtual bool DeltasEnabled() const OVERRIDE; - // Set how many update checks are called, the default value is just once. - void SetLoopCount(int times) { times_ = times; } + void SetLoopCount(int times); - void SetRecheckTime(int seconds) { - recheck_time_ = seconds; - } + void SetRecheckTime(int seconds); - void SetOnDemandTime(int seconds) { - ondemand_time_ = seconds; - } + void SetOnDemandTime(int seconds); - void AddComponentToCheck(CrxComponent* com, int at_loop_iter) { - components_to_check_.push_back(std::make_pair(com, at_loop_iter)); - } + void AddComponentToCheck(CrxComponent* com, int at_loop_iter); - void SetComponentUpdateService(ComponentUpdateService* cus) { - cus_ = cus; - } + void SetComponentUpdateService(ComponentUpdateService* cus); private: int times_; @@ -133,132 +110,209 @@ class TestConfigurator : public ComponentUpdateService::Configurator { ComponentUpdateService* cus_; }; -class TestInstaller : public ComponentInstaller { - public : - explicit TestInstaller() - : error_(0), install_count_(0) { - } +class ComponentUpdaterTest : public testing::Test { + public: + enum TestComponents { + kTestComponent_abag, + kTestComponent_jebg, + kTestComponent_ihfo, + }; - virtual void OnUpdateError(int error) OVERRIDE { - EXPECT_NE(0, error); - error_ = error; - } + ComponentUpdaterTest(); - virtual bool Install(const base::DictionaryValue& manifest, - const base::FilePath& unpack_path) OVERRIDE { - ++install_count_; - return file_util::Delete(unpack_path, true); - } + virtual ~ComponentUpdaterTest(); - int error() const { return error_; } + virtual void TearDown(); - int install_count() const { return install_count_; } + ComponentUpdateService* component_updater(); + + // Makes the full path to a component updater test file. + const base::FilePath test_file(const char* file); + + TestNotificationTracker& notification_tracker(); + + TestConfigurator* test_configurator(); + + ComponentUpdateService::Status RegisterComponent(CrxComponent* com, + TestComponents component, + const Version& version, + TestInstaller* installer); private: - int error_; - int install_count_; + scoped_ptr<ComponentUpdateService> component_updater_; + base::FilePath test_data_dir_; + TestNotificationTracker notification_tracker_; + TestConfigurator* test_config_; }; -// component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and -// the RSA public key the following hash: -const uint8 jebg_hash[] = {0x94,0x16,0x0b,0x6d,0x41,0x75,0xe9,0xec,0x8e,0xd5, - 0xfa,0x54,0xb0,0xd2,0xdd,0xa5,0x6e,0x05,0x6b,0xe8, - 0x73,0x47,0xf6,0xc4,0x11,0x9f,0xbc,0xb3,0x09,0xb3, - 0x5b,0x40}; -// component 2 has extension id "abagagagagagagagagagagagagagagag", and -// the RSA public key the following hash: -const uint8 abag_hash[] = {0x01,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, - 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, - 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, - 0x06,0x01}; - const char expected_crx_url[] = "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"; } // namespace -// Common fixture for all the component updater tests. -class ComponentUpdaterTest : public testing::Test { - public: - enum TestComponents { - kTestComponent_abag, - kTestComponent_jebg - }; +TestConfigurator::TestConfigurator() + : times_(1), recheck_time_(0), ondemand_time_(0), cus_(NULL) { +} - ComponentUpdaterTest() : test_config_(NULL) { - // The component updater instance under test. - test_config_ = new TestConfigurator; - component_updater_.reset(ComponentUpdateServiceFactory(test_config_)); - test_config_->SetComponentUpdateService(component_updater_.get()); - // The test directory is chrome/test/data/components. - PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); - test_data_dir_ = test_data_dir_.AppendASCII("components"); - - // Subscribe to all component updater notifications. - const int notifications[] = { - chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, - chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, - chrome::NOTIFICATION_COMPONENT_UPDATE_FOUND, - chrome::NOTIFICATION_COMPONENT_UPDATE_READY - }; - - for (int ix = 0; ix != arraysize(notifications); ++ix) { - notification_tracker_.ListenFor( - notifications[ix], content::NotificationService::AllSources()); - } - net::URLFetcher::SetEnableInterceptionForTests(true); - } +int TestConfigurator::InitialDelay() { return 0; } - virtual ~ComponentUpdaterTest() { - net::URLFetcher::SetEnableInterceptionForTests(false); +int TestConfigurator::NextCheckDelay() { + // This is called when a new full cycle of checking for updates is going + // to happen. In test we normally only test one cycle so it is a good + // time to break from the test messageloop Run() method so the test can + // finish. + if (--times_ <= 0) { + base::MessageLoop::current()->Quit(); + return 0; } - virtual void TearDown() { - xmlCleanupGlobals(); + // Look for checks to issue in the middle of the loop. + for (std::list<CheckAtLoopCount>::iterator + i = components_to_check_.begin(); + i != components_to_check_.end(); ) { + if (i->second == times_) { + cus_->CheckForUpdateSoon(*i->first); + i = components_to_check_.erase(i); + } else { + ++i; + } } + return 1; +} + +int TestConfigurator::StepDelay() { + return 0; +} - ComponentUpdateService* component_updater() { - return component_updater_.get(); +int TestConfigurator::MinimumReCheckWait() { + return recheck_time_; +} + +int TestConfigurator::OnDemandDelay() { + return ondemand_time_; +} + +GURL TestConfigurator::UpdateUrl(CrxComponent::UrlSource source) { + switch (source) { + case CrxComponent::BANDAID: + return GURL("http://localhost/upd"); + case CrxComponent::CWS_PUBLIC: + return GURL("http://localhost/cws"); + default: + return GURL("http://wronghost/bad"); + }; +} + +const char* TestConfigurator::ExtraRequestParams() { return "extra=foo"; } + +size_t TestConfigurator::UrlSizeLimit() { return 256; } + +net::URLRequestContextGetter* TestConfigurator::RequestContext() { + return new net::TestURLRequestContextGetter( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); +} + +// Don't use the utility process to decode files. +bool TestConfigurator::InProcess() { return true; } + +void TestConfigurator::OnEvent(Events event, int extra) { } + +ComponentPatcher* TestConfigurator::CreateComponentPatcher() { + return new MockComponentPatcher(); +} + +bool TestConfigurator::DeltasEnabled() const { + return true; +} + +// Set how many update checks are called, the default value is just once. +void TestConfigurator::SetLoopCount(int times) { times_ = times; } + +void TestConfigurator::SetRecheckTime(int seconds) { + recheck_time_ = seconds; +} + +void TestConfigurator::SetOnDemandTime(int seconds) { + ondemand_time_ = seconds; +} + +void TestConfigurator::AddComponentToCheck(CrxComponent* com, + int at_loop_iter) { + components_to_check_.push_back(std::make_pair(com, at_loop_iter)); +} + +void TestConfigurator::SetComponentUpdateService(ComponentUpdateService* cus) { + cus_ = cus; +} + +ComponentUpdaterTest::ComponentUpdaterTest() : test_config_(NULL) { + // The component updater instance under test. + test_config_ = new TestConfigurator; + component_updater_.reset(ComponentUpdateServiceFactory(test_config_)); + test_config_->SetComponentUpdateService(component_updater_.get()); + // The test directory is chrome/test/data/components. + PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); + test_data_dir_ = test_data_dir_.AppendASCII("components"); + + // Subscribe to all component updater notifications. + const int notifications[] = { + chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, + chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, + chrome::NOTIFICATION_COMPONENT_UPDATE_FOUND, + chrome::NOTIFICATION_COMPONENT_UPDATE_READY + }; + + for (int ix = 0; ix != arraysize(notifications); ++ix) { + notification_tracker_.ListenFor( + notifications[ix], content::NotificationService::AllSources()); } + net::URLFetcher::SetEnableInterceptionForTests(true); +} + +ComponentUpdaterTest::~ComponentUpdaterTest() { + net::URLFetcher::SetEnableInterceptionForTests(false); +} + +void ComponentUpdaterTest::TearDown() { + xmlCleanupGlobals(); +} + +ComponentUpdateService* ComponentUpdaterTest::component_updater() { + return component_updater_.get(); +} // Makes the full path to a component updater test file. - const base::FilePath test_file(const char* file) { - return test_data_dir_.AppendASCII(file); - } +const base::FilePath ComponentUpdaterTest::test_file(const char* file) { + return test_data_dir_.AppendASCII(file); +} - TestNotificationTracker& notification_tracker() { - return notification_tracker_; - } +TestNotificationTracker& ComponentUpdaterTest::notification_tracker() { + return notification_tracker_; +} - TestConfigurator* test_configurator() { - return test_config_; - } +TestConfigurator* ComponentUpdaterTest::test_configurator() { + return test_config_; +} - ComponentUpdateService::Status RegisterComponent(CrxComponent* com, - TestComponents component, - const Version& version) { - if (component == kTestComponent_abag) { - com->name = "test_abag"; - com->pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash)); - } else { - com->name = "test_jebg"; - com->pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); - } - com->version = version; - TestInstaller* installer = new TestInstaller; - com->installer = installer; - test_installers_.push_back(installer); - return component_updater_->RegisterComponent(*com); +ComponentUpdateService::Status ComponentUpdaterTest::RegisterComponent( + CrxComponent* com, + TestComponents component, + const Version& version, + TestInstaller* installer) { + if (component == kTestComponent_abag) { + com->name = "test_abag"; + com->pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash)); + } else if (component == kTestComponent_jebg) { + com->name = "test_jebg"; + com->pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + } else { + com->name = "test_ihfo"; + com->pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); } - - private: - scoped_ptr<ComponentUpdateService> component_updater_; - base::FilePath test_data_dir_; - TestNotificationTracker notification_tracker_; - TestConfigurator* test_config_; - // ComponentInstaller objects to delete after each test. - ScopedVector<TestInstaller> test_installers_; -}; + com->version = version; + com->installer = installer; + return component_updater_->RegisterComponent(*com); +} // Verify that our test fixture work and the component updater can // be created and destroyed with no side effects. @@ -295,13 +349,17 @@ TEST_F(ComponentUpdaterTest, CheckCrxSleep) { content::URLLocalHostRequestPrepackagedInterceptor interceptor; + TestInstaller installer; CrxComponent com; EXPECT_EQ(ComponentUpdateService::kOk, - RegisterComponent(&com, kTestComponent_abag, Version("1.1"))); + RegisterComponent(&com, + kTestComponent_abag, + Version("1.1"), + &installer)); const GURL expected_update_url( - "http://localhost/upd?extra=foo&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D1.1%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D1.1%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url, test_file("updatecheck_reply_1.xml")); @@ -374,20 +432,22 @@ TEST_F(ComponentUpdaterTest, InstallCrx) { content::URLLocalHostRequestPrepackagedInterceptor interceptor; + TestInstaller installer1; CrxComponent com1; - RegisterComponent(&com1, kTestComponent_jebg, Version("0.9")); + RegisterComponent(&com1, kTestComponent_jebg, Version("0.9"), &installer1); + TestInstaller installer2; CrxComponent com2; - RegisterComponent(&com2, kTestComponent_abag, Version("2.2")); + RegisterComponent(&com2, kTestComponent_abag, Version("2.2"), &installer2); const GURL expected_update_url_1( - "http://localhost/upd?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"); const GURL expected_update_url_2( - "http://localhost/upd?extra=foo&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url_1, test_file("updatecheck_reply_1.xml")); @@ -439,19 +499,21 @@ TEST_F(ComponentUpdaterTest, InstallCrxTwoSources) { content::URLLocalHostRequestPrepackagedInterceptor interceptor; + TestInstaller installer1; CrxComponent com1; - RegisterComponent(&com1, kTestComponent_abag, Version("2.2")); + RegisterComponent(&com1, kTestComponent_abag, Version("2.2"), &installer1); + TestInstaller installer2; CrxComponent com2; com2.source = CrxComponent::CWS_PUBLIC; - RegisterComponent(&com2, kTestComponent_jebg, Version("0.9")); + RegisterComponent(&com2, kTestComponent_jebg, Version("0.9"), &installer2); const GURL expected_update_url_1( "http://localhost/upd?extra=foo&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"); + "abagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"); const GURL expected_update_url_2( "http://localhost/cws?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc"); + "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url_1, test_file("updatecheck_reply_3.xml")); @@ -511,12 +573,13 @@ TEST_F(ComponentUpdaterTest, ProdVersionCheck) { content::URLLocalHostRequestPrepackagedInterceptor interceptor; + TestInstaller installer; CrxComponent com; - RegisterComponent(&com, kTestComponent_jebg, Version("0.9")); + RegisterComponent(&com, kTestComponent_jebg, Version("0.9"), &installer); const GURL expected_update_url( "http://localhost/upd?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc"); + "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url, test_file("updatecheck_reply_2.xml")); @@ -551,20 +614,22 @@ TEST_F(ComponentUpdaterTest, CheckForUpdateSoon) { content::URLLocalHostRequestPrepackagedInterceptor interceptor; + TestInstaller installer1; CrxComponent com1; - RegisterComponent(&com1, kTestComponent_abag, Version("2.2")); + RegisterComponent(&com1, kTestComponent_abag, Version("2.2"), &installer1); + TestInstaller installer2; CrxComponent com2; - RegisterComponent(&com2, kTestComponent_jebg, Version("0.9")); + RegisterComponent(&com2, kTestComponent_jebg, Version("0.9"), &installer2); const GURL expected_update_url_1( - "http://localhost/upd?extra=foo&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc"); const GURL expected_update_url_2( - "http://localhost/upd?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url_1, test_file("updatecheck_reply_empty")); @@ -613,9 +678,9 @@ TEST_F(ComponentUpdaterTest, CheckForUpdateSoon) { // Test a few error cases. NOTE: We don't have callbacks for // when the updates failed yet. const GURL expected_update_url_3( - "http://localhost/upd?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26uc&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26fp%3D%26uc" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"); // No update: error from no server response interceptor.SetResponse(expected_update_url_3, @@ -667,21 +732,23 @@ TEST_F(ComponentUpdaterTest, CheckReRegistration) { content::URLLocalHostRequestPrepackagedInterceptor interceptor; + TestInstaller installer1; CrxComponent com1; - RegisterComponent(&com1, kTestComponent_jebg, Version("0.9")); + RegisterComponent(&com1, kTestComponent_jebg, Version("0.9"), &installer1); + TestInstaller installer2; CrxComponent com2; - RegisterComponent(&com2, kTestComponent_abag, Version("2.2")); + RegisterComponent(&com2, kTestComponent_abag, Version("2.2"), &installer2); // Start with 0.9, and update to 1.0 const GURL expected_update_url_1( - "http://localhost/upd?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"); const GURL expected_update_url_2( - "http://localhost/upd?extra=foo&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url_1, test_file("updatecheck_reply_1.xml")); @@ -722,16 +789,20 @@ TEST_F(ComponentUpdaterTest, CheckReRegistration) { EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev4.type); // Now re-register, pretending to be an even newer version (2.2) + TestInstaller installer3; component_updater()->Stop(); EXPECT_EQ(ComponentUpdateService::kReplaced, - RegisterComponent(&com1, kTestComponent_jebg, Version("2.2"))); + RegisterComponent(&com1, + kTestComponent_jebg, + Version("2.2"), + &installer3)); // Check that we send out 2.2 as our version. // Interceptor's hit count should go up by 1. const GURL expected_update_url_3( - "http://localhost/upd?extra=foo&x=id%3D" - "jebgalgnebhfojomionfpkfelancnnkf%26v%3D2.2%26uc&x=id%3D" - "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"); + "http://localhost/upd?extra=foo" + "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D2.2%26fp%3D%26uc" + "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"); interceptor.SetResponse(expected_update_url_3, test_file("updatecheck_reply_1.xml")); @@ -753,8 +824,7 @@ TEST_F(ComponentUpdaterTest, CheckReRegistration) { EXPECT_EQ(4, interceptor.GetHitCount()); - // The test harness's Register() function creates a new installer, - // so the counts go back to 0. + // We created a new installer, so the counts go back to 0. EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error()); EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->install_count()); EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error()); @@ -762,3 +832,190 @@ TEST_F(ComponentUpdaterTest, CheckReRegistration) { component_updater()->Stop(); } + +// Verify that we can download and install a component and a differential +// update to that component. We do three loops; the final loop should do +// nothing. +// We also check that exactly 5 network requests are issued: +// 1- update check (response: v1 available) +// 2- download crx (v1) +// 3- update check (response: v2 available) +// 4- download differential crx (v1 to v2) +// 5- update check (response: no further update available) +TEST_F(ComponentUpdaterTest, DifferentialUpdate) { + base::MessageLoop message_loop; + content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop); + content::TestBrowserThread file_thread(BrowserThread::FILE); + content::TestBrowserThread io_thread(BrowserThread::IO); + + io_thread.StartIOThread(); + file_thread.Start(); + + content::URLLocalHostRequestPrepackagedInterceptor interceptor; + + VersionedTestInstaller installer; + CrxComponent com; + RegisterComponent(&com, kTestComponent_ihfo, Version("0.0"), &installer); + + const GURL expected_update_url_0( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D0.0%26fp%3D%26uc"); + const GURL expected_update_url_1( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D1.0%26fp%3D1%26uc"); + const GURL expected_update_url_2( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D2.0%26fp%3Df22%26uc"); + const GURL expected_crx_url_1( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"); + const GURL expected_crx_url_1_diff_2( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"); + + interceptor.SetResponse(expected_update_url_0, + test_file("updatecheck_diff_reply_1.xml")); + interceptor.SetResponse(expected_update_url_1, + test_file("updatecheck_diff_reply_2.xml")); + interceptor.SetResponse(expected_update_url_2, + test_file("updatecheck_diff_reply_3.xml")); + interceptor.SetResponse(expected_crx_url_1, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx")); + interceptor.SetResponse( + expected_crx_url_1_diff_2, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx")); + + test_configurator()->SetLoopCount(3); + + component_updater()->Start(); + message_loop.Run(); + + EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error()); + EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count()); + + EXPECT_EQ(5, interceptor.GetHitCount()); + + component_updater()->Stop(); +} + +// Verify that component installation falls back to downloading and installing +// a full update if the differential update fails (in this case, because the +// installer does not know about the existing files). We do two loops; the final +// loop should do nothing. +// We also check that exactly 4 network requests are issued: +// 1- update check (loop 1) +// 2- download differential crx +// 3- download full crx +// 4- update check (loop 2 - no update available) +TEST_F(ComponentUpdaterTest, DifferentialUpdateFails) { + base::MessageLoop message_loop; + content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop); + content::TestBrowserThread file_thread(BrowserThread::FILE); + content::TestBrowserThread io_thread(BrowserThread::IO); + + io_thread.StartIOThread(); + file_thread.Start(); + + content::URLLocalHostRequestPrepackagedInterceptor interceptor; + + TestInstaller installer; + CrxComponent com; + RegisterComponent(&com, kTestComponent_ihfo, Version("1.0"), &installer); + + const GURL expected_update_url_1( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D1.0%26fp%3D%26uc"); + const GURL expected_update_url_2( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D2.0%26fp%3Df22%26uc"); + const GURL expected_crx_url_1( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"); + const GURL expected_crx_url_1_diff_2( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"); + const GURL expected_crx_url_2( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"); + + interceptor.SetResponse(expected_update_url_1, + test_file("updatecheck_diff_reply_2.xml")); + interceptor.SetResponse(expected_update_url_2, + test_file("updatecheck_diff_reply_3.xml")); + interceptor.SetResponse(expected_crx_url_1, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx")); + interceptor.SetResponse( + expected_crx_url_1_diff_2, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx")); + interceptor.SetResponse(expected_crx_url_2, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx")); + + test_configurator()->SetLoopCount(2); + + component_updater()->Start(); + message_loop.Run(); + + // A failed differential update does not count as a failed install. + EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error()); + EXPECT_EQ(1, static_cast<TestInstaller*>(com.installer)->install_count()); + + EXPECT_EQ(4, interceptor.GetHitCount()); + + component_updater()->Stop(); +} + +// Verify that we successfully propagate a patcher error. +// ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx contains an incorrect +// patching instruction that should fail. +TEST_F(ComponentUpdaterTest, DifferentialUpdateFailErrorcode) { + base::MessageLoop message_loop; + content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop); + content::TestBrowserThread file_thread(BrowserThread::FILE); + content::TestBrowserThread io_thread(BrowserThread::IO); + + io_thread.StartIOThread(); + file_thread.Start(); + + content::URLLocalHostRequestPrepackagedInterceptor interceptor; + + VersionedTestInstaller installer; + CrxComponent com; + RegisterComponent(&com, kTestComponent_ihfo, Version("0.0"), &installer); + + const GURL expected_update_url_0( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D0.0%26fp%3D%26uc"); + const GURL expected_update_url_1( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D1.0%26fp%3D1%26uc"); + const GURL expected_update_url_2( + "http://localhost/upd?extra=foo" + "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D2.0%26fp%3Df22%26uc"); + const GURL expected_crx_url_1( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"); + const GURL expected_crx_url_1_diff_2( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"); + const GURL expected_crx_url_2( + "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"); + + interceptor.SetResponse(expected_update_url_0, + test_file("updatecheck_diff_reply_1.xml")); + interceptor.SetResponse(expected_update_url_1, + test_file("updatecheck_diff_reply_2.xml")); + interceptor.SetResponse(expected_update_url_2, + test_file("updatecheck_diff_reply_3.xml")); + interceptor.SetResponse(expected_crx_url_1, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx")); + interceptor.SetResponse( + expected_crx_url_1_diff_2, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx")); + interceptor.SetResponse(expected_crx_url_2, + test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx")); + + test_configurator()->SetLoopCount(3); + + component_updater()->Start(); + message_loop.Run(); + + EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error()); + EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count()); + + EXPECT_EQ(6, interceptor.GetHitCount()); + + component_updater()->Stop(); +} diff --git a/chrome/browser/component_updater/test/test_installer.cc b/chrome/browser/component_updater/test/test_installer.cc new file mode 100644 index 0000000..9a4098c --- /dev/null +++ b/chrome/browser/component_updater/test/test_installer.cc @@ -0,0 +1,81 @@ +// Copyright 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 "chrome/browser/component_updater/test/test_installer.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/values.h" + +TestInstaller::TestInstaller() + : error_(0), install_count_(0) { +} + +void TestInstaller::OnUpdateError(int error) { + error_ = error; +} + +bool TestInstaller::Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + ++install_count_; + return file_util::Delete(unpack_path, true); +} + +bool TestInstaller::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + return false; +} + +int TestInstaller::error() const { return error_; } + +int TestInstaller::install_count() const { return install_count_; } + + +ReadOnlyTestInstaller::ReadOnlyTestInstaller(const base::FilePath& install_dir) + : install_directory_(install_dir) { +} + +ReadOnlyTestInstaller::~ReadOnlyTestInstaller() { +} + +bool ReadOnlyTestInstaller::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + *installed_file = install_directory_.AppendASCII(file); + return true; +} + + +VersionedTestInstaller::VersionedTestInstaller() { + file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("TEST_"), + &install_directory_); +} + +VersionedTestInstaller::~VersionedTestInstaller() { + file_util::Delete(install_directory_, true); +} + + +bool VersionedTestInstaller::Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + std::string version_string; + manifest.GetStringASCII("version", &version_string); + Version version(version_string.c_str()); + + base::FilePath path; + path = install_directory_.AppendASCII(version.GetString()); + file_util::CreateDirectory(path.DirName()); + if (!file_util::Move(unpack_path, path)) + return false; + current_version_ = version; + ++install_count_; + return true; +} + +bool VersionedTestInstaller::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + base::FilePath path; + path = install_directory_.AppendASCII(current_version_.GetString()); + *installed_file = path.Append(base::FilePath::FromUTF8Unsafe(file)); + return true; +} diff --git a/chrome/browser/component_updater/test/test_installer.h b/chrome/browser/component_updater/test/test_installer.h new file mode 100644 index 0000000..2233d88 --- /dev/null +++ b/chrome/browser/component_updater/test/test_installer.h @@ -0,0 +1,73 @@ +// Copyright 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 CHROME_BROWSER_COMPONENT_UPDATER_TEST_TEST_INSTALLER_H_ +#define CHROME_BROWSER_COMPONENT_UPDATER_TEST_TEST_INSTALLER_H_ + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "chrome/browser/component_updater/component_updater_service.h" + +namespace base { +class DictionaryValue; +} + +// A TestInstaller is an installer that does nothing for installation except +// increment a counter. +class TestInstaller : public ComponentInstaller { + public: + explicit TestInstaller(); + + virtual void OnUpdateError(int error) OVERRIDE; + + virtual bool Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) OVERRIDE; + + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + + int error() const; + + int install_count() const; + + protected: + int error_; + int install_count_; +}; + +// A ReadOnlyTestInstaller is an installer that knows about files in an existing +// directory. It will not write to the directory. +class ReadOnlyTestInstaller : public TestInstaller { + public: + explicit ReadOnlyTestInstaller(const base::FilePath& installed_path); + + virtual ~ReadOnlyTestInstaller(); + + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + + private: + base::FilePath install_directory_; +}; + +// A VersionedTestInstaller is an installer that installs files into versioned +// directories (e.g. somedir/25.23.89.141/<files>). +class VersionedTestInstaller : public TestInstaller { + public : + explicit VersionedTestInstaller(); + + virtual ~VersionedTestInstaller(); + + virtual bool Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) OVERRIDE; + + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + + private: + base::FilePath install_directory_; + Version current_version_; +}; + +#endif // CHROME_BROWSER_COMPONENT_UPDATER_TEST_TEST_INSTALLER_H_ diff --git a/chrome/browser/component_updater/widevine_cdm_component_installer.cc b/chrome/browser/component_updater/widevine_cdm_component_installer.cc index 822aff2..8bc3fef 100644 --- a/chrome/browser/component_updater/widevine_cdm_component_installer.cc +++ b/chrome/browser/component_updater/widevine_cdm_component_installer.cc @@ -195,6 +195,9 @@ class WidevineCdmComponentInstaller : public ComponentInstaller { virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) OVERRIDE; + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; + private: base::Version current_version_; }; @@ -246,6 +249,11 @@ bool WidevineCdmComponentInstaller::Install( return true; } +bool WidevineCdmComponentInstaller::GetInstalledFile( + const std::string& file, base::FilePath* installed_file) { + return false; +} + void FinishWidevineCdmUpdateRegistration(ComponentUpdateService* cus, const base::Version& version) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); diff --git a/chrome/browser/net/crl_set_fetcher.cc b/chrome/browser/net/crl_set_fetcher.cc index eba5d7c..796b2f3 100644 --- a/chrome/browser/net/crl_set_fetcher.cc +++ b/chrome/browser/net/crl_set_fetcher.cc @@ -205,4 +205,9 @@ bool CRLSetFetcher::Install(const base::DictionaryValue& manifest, return true; } +bool CRLSetFetcher::GetInstalledFile( + const std::string& file, base::FilePath* installed_file) { + return false; +} + CRLSetFetcher::~CRLSetFetcher() {} diff --git a/chrome/browser/net/crl_set_fetcher.h b/chrome/browser/net/crl_set_fetcher.h index 476e1b9..5f7bdf3 100644 --- a/chrome/browser/net/crl_set_fetcher.h +++ b/chrome/browser/net/crl_set_fetcher.h @@ -33,6 +33,8 @@ class CRLSetFetcher : public ComponentInstaller, virtual void OnUpdateError(int error) OVERRIDE; virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) OVERRIDE; + virtual bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) OVERRIDE; private: friend class base::RefCountedThreadSafe<CRLSetFetcher>; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 9ecf4ae..c61dcc5 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -382,6 +382,12 @@ 'browser/command_updater_delegate.h', 'browser/common/cancelable_request.cc', 'browser/common/cancelable_request.h', + 'browser/component_updater/component_patcher.cc', + 'browser/component_updater/component_patcher.h', + 'browser/component_updater/component_patcher_operation.cc', + 'browser/component_updater/component_patcher_operation.h', + 'browser/component_updater/component_patcher_win.cc', + 'browser/component_updater/component_patcher_win.h', 'browser/component_updater/component_updater_configurator.cc', 'browser/component_updater/component_updater_configurator.h', 'browser/component_updater/component_unpacker.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 501cc0c..d263df0 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -437,6 +437,7 @@ 'test_support_common', 'test_support_unit', # 3) anything tests directly depend on + '../courgette/courgette.gyp:courgette_lib', '../google_apis/google_apis.gyp:google_apis', '../skia/skia.gyp:skia', '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation', @@ -686,7 +687,10 @@ 'browser/chromeos/web_socket_proxy_helper_unittest.cc', 'browser/command_updater_unittest.cc', 'browser/component_updater/test/component_installers_unittest.cc', + 'browser/component_updater/test/component_patcher_mock.h', + 'browser/component_updater/test/component_patcher_unittest.cc', 'browser/component_updater/test/component_updater_service_unittest.cc', + 'browser/component_updater/test/test_installer.cc', 'browser/content_settings/content_settings_default_provider_unittest.cc', 'browser/content_settings/content_settings_mock_observer.cc', 'browser/content_settings/content_settings_mock_observer.h', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 18d1cb1..2317369 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -208,7 +208,7 @@ const char kCloudPrintServiceURL[] = "cloud-print-service"; // Comma-separated options to troubleshoot the component updater. Only valid // for the browser process. -const char kComponentUpdaterDebug[] = "component-updater-debug"; +const char kComponentUpdater[] = "component-updater"; // Causes the browser process to inspect loaded and registered DLLs for known // conflicts and warn the user. diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 7e404c1..f2199c9 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -71,7 +71,7 @@ extern const char kCloudPrintFileType[]; extern const char kCloudPrintPrintTicket[]; extern const char kCloudPrintSetupProxy[]; extern const char kCloudPrintServiceURL[]; -extern const char kComponentUpdaterDebug[]; +extern const char kComponentUpdater[]; extern const char kConflictingModulesCheck[]; extern const char kContentSettings2[]; extern const char kCountry[]; diff --git a/chrome/common/extensions/update_manifest.cc b/chrome/common/extensions/update_manifest.cc index feb6f70..0ed653f 100644 --- a/chrome/common/extensions/update_manifest.cc +++ b/chrome/common/extensions/update_manifest.cc @@ -19,7 +19,9 @@ static const char* kExpectedGupdateProtocol = "2.0"; static const char* kExpectedGupdateXmlns = "http://www.google.com/update2/response"; -UpdateManifest::Result::Result() {} +UpdateManifest::Result::Result() + : size(0), + diff_size(0) {} UpdateManifest::Result::~Result() {} @@ -154,7 +156,7 @@ static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace, result->crx_url = GURL(GetAttribute(updatecheck, "codebase")); if (!result->crx_url.is_valid()) { *error_detail = "Invalid codebase url: '"; - *error_detail += GetAttribute(updatecheck, "codebase"); + *error_detail += result->crx_url.possibly_invalid_spec(); *error_detail += "'."; return false; } @@ -189,6 +191,23 @@ static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace, // sha256 hash of the package in hex format. result->package_hash = GetAttribute(updatecheck, "hash"); + int size = 0; + if (base::StringToInt(GetAttribute(updatecheck, "size"), &size)) { + result->size = size; + } + + // package_fingerprint is optional. It identifies the package, preferably + // with a modified sha256 hash of the package in hex format. + result->package_fingerprint = GetAttribute(updatecheck, "fp"); + + // Differential update information is optional. + result->diff_crx_url = GURL(GetAttribute(updatecheck, "codebasediff")); + result->diff_package_hash = GetAttribute(updatecheck, "hashdiff"); + int sizediff = 0; + if (base::StringToInt(GetAttribute(updatecheck, "sizediff"), &sizediff)) { + result->diff_size = sizediff; + } + return true; } diff --git a/chrome/common/extensions/update_manifest.h b/chrome/common/extensions/update_manifest.h index d847743..bff70b4 100644 --- a/chrome/common/extensions/update_manifest.h +++ b/chrome/common/extensions/update_manifest.h @@ -15,13 +15,16 @@ class UpdateManifest { // An update manifest looks like this: // - // <?xml version='1.0' encoding='UTF-8'?> - // <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> - // <daystart elapsed_seconds='300' /> - // <app appid='12345'> - // <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' - // version='1.2.3.4' prodversionmin='2.0.143.0' - // hash="12345"/> + // <?xml version="1.0" encoding="UTF-8"?> + // <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0"> + // <daystart elapsed_seconds="300" /> + // <app appid="12345" status="ok"> + // <updatecheck codebase="http://example.com/extension_1.2.3.4.crx" + // hash="12345" size="9854" status="ok" version="1.2.3.4" + // prodversionmin="2.0.143.0" + // codebasediff="http://example.com/diff_1.2.3.4.crx" + // hashdiff="123" sizediff="101" + // fp="1.123" /> // </app> // </gupdate> // @@ -33,6 +36,9 @@ class UpdateManifest { // fetch the updated crx file, and the "prodversionmin" attribute refers to // the minimum version of the chrome browser that the update applies to. + // The diff data members correspond to the differential update package, if + // a differential update is specified in the response. + // The result of parsing one <app> tag in an xml update check manifest. struct Result { Result(); @@ -41,8 +47,17 @@ class UpdateManifest { std::string extension_id; std::string version; std::string browser_min_version; - std::string package_hash; + + // Attributes for the full update. GURL crx_url; + std::string package_hash; + int size; + std::string package_fingerprint; + + // Attributes for the differential update. + GURL diff_crx_url; + std::string diff_package_hash; + int diff_size; }; static const int kNoDaystart = -1; diff --git a/chrome/common/omaha_query_params/omaha_query_params.cc b/chrome/common/omaha_query_params/omaha_query_params.cc index 6db55d9..04fb7ab 100644 --- a/chrome/common/omaha_query_params/omaha_query_params.cc +++ b/chrome/common/omaha_query_params/omaha_query_params.cc @@ -49,22 +49,6 @@ const char kChrome[] = "chrome"; const char kChromeCrx[] = "chromecrx"; const char kChromiumCrx[] = "chromiumcrx"; -const char* GetProdIdString(chrome::OmahaQueryParams::ProdId prod) { - switch (prod) { - case chrome::OmahaQueryParams::CHROME: - return kChrome; - break; - case chrome::OmahaQueryParams::CRX: -#if defined(GOOGLE_CHROME_BUILD) - return kChromeCrx; -#else - return kChromiumCrx; -#endif - break; - } - return kUnknown; -} - const char kStable[] = "stable"; const char kBeta[] = "beta"; const char kDev[] = "dev"; @@ -95,6 +79,7 @@ const char* GetChannelString() { namespace chrome { +// static std::string OmahaQueryParams::Get(ProdId prod) { return base::StringPrintf( "os=%s&arch=%s&nacl_arch=%s&prod=%s&prodchannel=%s&prodversion=%s", @@ -107,6 +92,24 @@ std::string OmahaQueryParams::Get(ProdId prod) { } // static +const char* OmahaQueryParams::GetProdIdString( + chrome::OmahaQueryParams::ProdId prod) { + switch (prod) { + case chrome::OmahaQueryParams::CHROME: + return kChrome; + break; + case chrome::OmahaQueryParams::CRX: +#if defined(GOOGLE_CHROME_BUILD) + return kChromeCrx; +#else + return kChromiumCrx; +#endif + break; + } + return kUnknown; +} + +// static const char* OmahaQueryParams::getOS() { return kOs; } diff --git a/chrome/common/omaha_query_params/omaha_query_params.h b/chrome/common/omaha_query_params/omaha_query_params.h index 7f74be1..91f6f18 100644 --- a/chrome/common/omaha_query_params/omaha_query_params.h +++ b/chrome/common/omaha_query_params/omaha_query_params.h @@ -23,6 +23,10 @@ class OmahaQueryParams { // prod, prodchannel, prodversion. static std::string Get(ProdId prod); + // Returns the value we use for the "prod=" parameter. Possible return values + // include "chrome", "chromecrx", "chromiumcrx", and "unknown". + static const char* GetProdIdString(chrome::OmahaQueryParams::ProdId prod); + // Returns the value we use for the "os=" parameter. Possible return values // include: "mac", "win", "android", "cros", "linux", and "openbsd". static const char* getOS(); diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc index 735be3f..2c60a20 100644 --- a/chrome/installer/setup/setup_main.cc +++ b/chrome/installer/setup/setup_main.cc @@ -14,6 +14,7 @@ #include "base/command_line.h" #include "base/file_util.h" #include "base/file_version_info.h" +#include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" #include "base/path_service.h" #include "base/process_util.h" @@ -491,7 +492,6 @@ bool CheckAppHostPreconditions(const InstallationState& original_state, InstallerState* installer_state, installer::InstallStatus* status) { if (installer_state->FindProduct(BrowserDistribution::CHROME_APP_HOST)) { - if (!installer_state->is_multi_install()) { LOG(DFATAL) << "App Launcher requires multi install"; *status = installer::APP_HOST_REQUIRES_MULTI_INSTALL; @@ -507,7 +507,6 @@ bool CheckAppHostPreconditions(const InstallationState& original_state, installer_state->WriteInstallerResult(*status, 0, NULL); return false; } - } return true; @@ -1477,6 +1476,27 @@ bool HandleNonInstallCmdLineOptions(const InstallationState& original_state, } else if (cmd_line.HasSwitch(installer::switches::kChromeFrameQuickEnable)) { *exit_code = installer::ChromeFrameQuickEnable(original_state, installer_state); + } else if (cmd_line.HasSwitch(installer::switches::kPatch)) { + const std::string patch_type_str( + cmd_line.GetSwitchValueASCII(installer::switches::kPatch)); + const base::FilePath input_file( + cmd_line.GetSwitchValuePath(installer::switches::kInputFile)); + const base::FilePath patch_file( + cmd_line.GetSwitchValuePath(installer::switches::kPatchFile)); + const base::FilePath output_file( + cmd_line.GetSwitchValuePath(installer::switches::kOutputFile)); + + if (patch_type_str == installer::kCourgette) { + *exit_code = installer::CourgettePatchFiles(input_file, + patch_file, + output_file); + } else if (patch_type_str == installer::kBsdiff) { + *exit_code = installer::BsdiffPatchFiles(input_file, + patch_file, + output_file); + } else { + *exit_code = installer::PATCH_INVALID_ARGUMENTS; + } } else { handled = false; } diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc index ef0f8360..d679274 100644 --- a/chrome/installer/setup/setup_util.cc +++ b/chrome/installer/setup/setup_util.cc @@ -24,6 +24,7 @@ #include "chrome/installer/util/util_constants.h" #include "chrome/installer/util/work_item.h" #include "courgette/courgette.h" +#include "courgette/third_party/bsdiff.h" #include "third_party/bspatch/mbspatch.h" namespace installer { @@ -86,12 +87,59 @@ bool SupportsSingleInstall(BrowserDistribution::Type type) { } // namespace +int CourgettePatchFiles(const base::FilePath& src, + const base::FilePath& patch, + const base::FilePath& dest) { + VLOG(1) << "Applying Courgette patch " << patch.value() + << " to file " << src.value() + << " and generating file " << dest.value(); + + if (src.empty() || patch.empty() || dest.empty()) + return installer::PATCH_INVALID_ARGUMENTS; + + const courgette::Status patch_status = + courgette::ApplyEnsemblePatch(src.value().c_str(), + patch.value().c_str(), + dest.value().c_str()); + const int exit_code = (patch_status != courgette::C_OK) ? + static_cast<int>(patch_status) + kCourgetteErrorOffset : 0; + + LOG_IF(ERROR, exit_code) + << "Failed to apply Courgette patch " << patch.value() + << " to file " << src.value() << " and generating file " << dest.value() + << ". err=" << exit_code; + + return exit_code; +} + +int BsdiffPatchFiles(const base::FilePath& src, + const base::FilePath& patch, + const base::FilePath& dest) { + VLOG(1) << "Applying bsdiff patch " << patch.value() + << " to file " << src.value() + << " and generating file " << dest.value(); + + if (src.empty() || patch.empty() || dest.empty()) + return installer::PATCH_INVALID_ARGUMENTS; + + const int patch_status = courgette::ApplyBinaryPatch(src, patch, dest); + const int exit_code = patch_status != OK ? + patch_status + kBsdiffErrorOffset : 0; + + LOG_IF(ERROR, exit_code) + << "Failed to apply bsdiff patch " << patch.value() + << " to file " << src.value() << " and generating file " << dest.value() + << ". err=" << exit_code; + + return exit_code; +} + int ApplyDiffPatch(const base::FilePath& src, const base::FilePath& patch, const base::FilePath& dest, const InstallerState* installer_state) { - VLOG(1) << "Applying patch " << patch.value() << " to file " << src.value() - << " and generating file " << dest.value(); + VLOG(1) << "Applying patch " << patch.value() << " to file " + << src.value() << " and generating file " << dest.value(); if (installer_state != NULL) installer_state->UpdateStage(installer::ENSEMBLE_PATCHING); @@ -105,8 +153,10 @@ int ApplyDiffPatch(const base::FilePath& src, if (patch_status == courgette::C_OK) return 0; - VLOG(1) << "Failed to apply patch " << patch.value() - << " using courgette. err=" << patch_status; + LOG(ERROR) + << "Failed to apply patch " << patch.value() + << " to file " << src.value() << " and generating file " << dest.value() + << " using courgette. err=" << patch_status; // If we ran out of memory or disk space, then these are likely the errors // we will see. If we run into them, return an error and stay on the @@ -119,8 +169,16 @@ int ApplyDiffPatch(const base::FilePath& src, if (installer_state != NULL) installer_state->UpdateStage(installer::BINARY_PATCHING); - return ApplyBinaryPatch(src.value().c_str(), patch.value().c_str(), - dest.value().c_str()); + int binary_patch_status = ApplyBinaryPatch(src.value().c_str(), + patch.value().c_str(), + dest.value().c_str()); + + LOG_IF(ERROR, binary_patch_status != OK) + << "Failed to apply patch " << patch.value() + << " to file " << src.value() << " and generating file " << dest.value() + << " using bsdiff. err=" << binary_patch_status; + + return binary_patch_status; } Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) { diff --git a/chrome/installer/setup/setup_util.h b/chrome/installer/setup/setup_util.h index 2a7530a..97bf4d4 100644 --- a/chrome/installer/setup/setup_util.h +++ b/chrome/installer/setup/setup_util.h @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// This file declares util functions for setup project. +// This file declares util functions for setup project. It also declares a +// few functions that the Chrome component updater uses for patching binary +// deltas. #ifndef CHROME_INSTALLER_SETUP_SETUP_UTIL_H_ #define CHROME_INSTALLER_SETUP_SETUP_UTIL_H_ @@ -28,15 +30,32 @@ class InstallationState; class InstallerState; class ProductState; -// Apply a diff patch to source file. First tries to apply it using courgette -// since it checks for courgette header and fails quickly. If that fails -// tries to apply the patch using regular bsdiff. Returns status code. +// Apply a diff patch to source file. First tries to apply it using Courgette +// since it checks for Courgette header and fails quickly. If that fails +// tries to apply the patch using regular bsdiff. Returns status code as +// defined by the bsdiff code (see third_party/bspatch/mbspatch.h for the +// definitions of the codes). // The installer stage is updated if |installer_state| is non-NULL. int ApplyDiffPatch(const base::FilePath& src, const base::FilePath& patch, const base::FilePath& dest, const InstallerState* installer_state); +// Applies a patch file to source file using Courgette. Returns 0 in case of +// success. In case of errors, it returns kCourgetteErrorOffset + a Courgette +// status code, as defined in courgette/courgette.h +int CourgettePatchFiles(const base::FilePath& src, + const base::FilePath& patch, + const base::FilePath& dest); + +// Applies a patch file to source file using bsdiff. This function uses +// Courgette's flavor of bsdiff. Returns 0 in case of success, or +// kBsdiffErrorOffset + a bsdiff status code in case of errors. +// See courgette/third_party/bsdiff.h for details. +int BsdiffPatchFiles(const base::FilePath& src, + const base::FilePath& patch, + const base::FilePath& dest); + // Find the version of Chrome from an install source directory. // Chrome_path should contain at least one version folder. // Returns the maximum version found or NULL if no version is found. diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc index c380a5f..e293e76 100644 --- a/chrome/installer/util/util_constants.cc +++ b/chrome/installer/util/util_constants.cc @@ -196,6 +196,15 @@ const char kExperimentGroup[] = "experiment-group"; // to. See DuplicateGoogleUpdateSystemClientKey for details. const char kToastResultsKey[] = "toast-results-key"; +// Applies a binary patch to a file. The input, patch, and the output file are +// specified as command line arguments following the --patch switch. +// Ex: --patch=courgette --input_file='input' --patch_file='patch' +// --output_file='output' +const char kPatch[] = "patch"; +const char kInputFile[] = "input-file"; +const char kPatchFile[] = "patch-file"; +const char kOutputFile[] = "output-file"; + } // namespace switches // The Active Setup executable will be an identical copy of setup.exe; this is @@ -261,4 +270,7 @@ const wchar_t kChromeChannelStable[] = L""; const size_t kMaxAppModelIdLength = 64U; +const char kCourgette[] = "courgette"; +const char kBsdiff[] = "bsdiff"; + } // namespace installer diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h index bb7b693..3f9a496 100644 --- a/chrome/installer/util/util_constants.h +++ b/chrome/installer/util/util_constants.h @@ -80,12 +80,14 @@ enum InstallStatus { INVALID_STATE_FOR_OPTION, // 47. A non-install option was called with an // invalid installer state. WAIT_FOR_EXISTING_FAILED, // 48. OS error waiting for existing setup.exe. + PATCH_INVALID_ARGUMENTS, // 49. The arguments of --patch were missing or + // they were invalid for any reason. // Friendly reminder: note the COMPILE_ASSERT below. }; // Existing InstallStatus values must not change. Always add to the end. -COMPILE_ASSERT(installer::WAIT_FOR_EXISTING_FAILED == 48, +COMPILE_ASSERT(installer::PATCH_INVALID_ARGUMENTS == 49, dont_change_enum); // The type of an update archive. @@ -128,6 +130,7 @@ COMPILE_ASSERT(DEFERRING_TO_HIGHER_VERSION == 18, never_ever_ever_change_InstallerStage_values_bang); namespace switches { + extern const char kAutoLaunchChrome[]; extern const char kChrome[]; extern const char kChromeAppHostDeprecated[]; // TODO(huangs): Remove by M27. @@ -177,6 +180,11 @@ extern const char kInactiveUserToast[]; extern const char kSystemLevelToast[]; extern const char kExperimentGroup[]; extern const char kToastResultsKey[]; +extern const char kPatch[]; +extern const char kInputFile[]; +extern const char kPatchFile[]; +extern const char kOutputFile[]; + } // namespace switches extern const wchar_t kActiveSetupExe[]; @@ -237,6 +245,16 @@ extern const wchar_t kChromeChannelStable[]; extern const size_t kMaxAppModelIdLength; +// The range of error values for the installer, Courgette, and bsdiff is +// overlapping. These offset values disambiguate between different sets +// of errors by shifting the values up with the specified offset. +const int kCourgetteErrorOffset = 300; +const int kBsdiffErrorOffset = 600; + +// Arguments to --patch switch +extern const char kCourgette[]; +extern const char kBsdiff[]; + } // namespace installer #endif // CHROME_INSTALLER_UTIL_UTIL_CONSTANTS_H_ diff --git a/chrome/test/data/components/updatecheck_diff_reply_1.xml b/chrome/test/data/components/updatecheck_diff_reply_1.xml new file mode 100644 index 0000000..894c966 --- /dev/null +++ b/chrome/test/data/components/updatecheck_diff_reply_1.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck codebase='http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx' version='1.0' prodversionmin='11.0.1.0' fp='1'/> + </app> +</gupdate> diff --git a/chrome/test/data/components/updatecheck_diff_reply_2.xml b/chrome/test/data/components/updatecheck_diff_reply_2.xml new file mode 100644 index 0000000..5afa6d3 --- /dev/null +++ b/chrome/test/data/components/updatecheck_diff_reply_2.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck codebase='http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx' version='2.0' prodversionmin='11.0.1.0' fp='f22' codebasediff='http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'/> + </app> +</gupdate> diff --git a/chrome/test/data/components/updatecheck_diff_reply_3.xml b/chrome/test/data/components/updatecheck_diff_reply_3.xml new file mode 100644 index 0000000..83bd41f --- /dev/null +++ b/chrome/test/data/components/updatecheck_diff_reply_3.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck status='noupdate'/> + </app> +</gupdate> diff --git a/chrome/test/data/components/updatecheck_reply_noupdate.xml b/chrome/test/data/components/updatecheck_reply_noupdate.xml new file mode 100644 index 0000000..254b167 --- /dev/null +++ b/chrome/test/data/components/updatecheck_reply_noupdate.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> + <app appid='jebgalgnebhfojomionfpkfelancnnkf'> + <updatecheck status='noupdate' /> + </app> +</gupdate> diff --git a/courgette/disassembler_elf_32.h b/courgette/disassembler_elf_32.h index 5c3f8a2..b1832dc 100644 --- a/courgette/disassembler_elf_32.h +++ b/courgette/disassembler_elf_32.h @@ -47,7 +47,7 @@ class DisassemblerElf32 : public Disassembler { // Misc Section Helpers - const Elf32_Half SectionHeaderCount() const { + Elf32_Half SectionHeaderCount() const { return section_header_table_size_; } @@ -60,13 +60,13 @@ class DisassemblerElf32 : public Disassembler { return OffsetToPointer(SectionHeader(id)->sh_offset); } - const Elf32_Word SectionBodySize(int id) const { + Elf32_Word SectionBodySize(int id) const { return SectionHeader(id)->sh_size; } // Misc Segment Helpers - const Elf32_Half ProgramSegmentHeaderCount() const { + Elf32_Half ProgramSegmentHeaderCount() const { return program_header_table_size_; } @@ -76,22 +76,22 @@ class DisassemblerElf32 : public Disassembler { } // The virtual memory address at which this program segment will be loaded - const Elf32_Addr ProgramSegmentMemoryBegin(int id) const { + Elf32_Addr ProgramSegmentMemoryBegin(int id) const { return ProgramSegmentHeader(id)->p_vaddr; } // The number of virtual memory bytes for this program segment - const Elf32_Word ProgramSegmentMemorySize(int id) const { + Elf32_Word ProgramSegmentMemorySize(int id) const { return ProgramSegmentHeader(id)->p_memsz; } // Pointer into the source file for this program segment - const Elf32_Addr ProgramSegmentFileOffset(int id) const { + Elf32_Addr ProgramSegmentFileOffset(int id) const { return ProgramSegmentHeader(id)->p_offset; } // Number of file bytes for this program segment. Is <= ProgramMemorySize. - const Elf32_Word ProgramSegmentFileSize(int id) const { + Elf32_Word ProgramSegmentFileSize(int id) const { return ProgramSegmentHeader(id)->p_filesz; } diff --git a/courgette/disassembler_elf_32_x86.h b/courgette/disassembler_elf_32_x86.h index 6096781..28de7cf 100644 --- a/courgette/disassembler_elf_32_x86.h +++ b/courgette/disassembler_elf_32_x86.h @@ -23,13 +23,12 @@ class DisassemblerElf32X86 : public DisassemblerElf32 { virtual e_machine_values ElfEM() { return EM_386; } protected: - virtual CheckBool RelToRVA(Elf32_Rel rel, RVA* result) - const WARN_UNUSED_RESULT; + const WARN_UNUSED_RESULT; virtual CheckBool ParseRelocationSection( const Elf32_Shdr *section_header, - AssemblyProgram* program) WARN_UNUSED_RESULT; + AssemblyProgram* program) WARN_UNUSED_RESULT; virtual CheckBool ParseRel32RelocsFromSection( const Elf32_Shdr* section) WARN_UNUSED_RESULT; diff --git a/courgette/third_party/bsdiff.h b/courgette/third_party/bsdiff.h index 5e5683a..77897a8 100644 --- a/courgette/third_party/bsdiff.h +++ b/courgette/third_party/bsdiff.h @@ -30,12 +30,15 @@ * 2009-03-31 - Change to use Streams. Move CRC code to crc.{h,cc} * Changed status to an enum, removed unused status codes. * --Stephen Adams <sra@chromium.org> + * 2013-04-10 - Added wrapper to apply a patch directly to files. + * --Joshua Pawlicki <waffles@chromium.org> */ #ifndef COURGETTE_BSDIFF_H_ #define COURGETTE_BSDIFF_H_ #include "base/basictypes.h" +#include "base/file_util.h" namespace courgette { @@ -44,7 +47,8 @@ enum BSDiffStatus { MEM_ERROR = 1, CRC_ERROR = 2, READ_ERROR = 3, - UNEXPECTED_ERROR = 4 + UNEXPECTED_ERROR = 4, + WRITE_ERROR = 5 }; class SourceStream; @@ -64,6 +68,10 @@ BSDiffStatus ApplyBinaryPatch(SourceStream* old_stream, SourceStream* patch_stream, SinkStream* new_stream); +// As above, but simply takes the file paths. +BSDiffStatus ApplyBinaryPatch(const base::FilePath& old_stream, + const base::FilePath& patch_stream, + const base::FilePath& new_stream); // The following declarations are common to the patch-creation and // patch-application code. diff --git a/courgette/third_party/bsdiff_apply.cc b/courgette/third_party/bsdiff_apply.cc index 762c12c..3ed346e 100644 --- a/courgette/third_party/bsdiff_apply.cc +++ b/courgette/third_party/bsdiff_apply.cc @@ -26,6 +26,8 @@ * Changelog: * 2009-03-31 - Change to use Streams. Move CRC code to crc.{h,cc} * --Stephen Adams <sra@chromium.org> + * 2013-04-10 - Add wrapper method to apply a patch to files directly. + * --Joshua Pawlicki <waffles@chromium.org> */ // Copyright (c) 2009 The Chromium Authors. All rights reserved. @@ -34,6 +36,7 @@ #include "courgette/third_party/bsdiff.h" +#include "base/files/memory_mapped_file.h" #include "courgette/crc.h" #include "courgette/streams.h" @@ -168,4 +171,42 @@ BSDiffStatus ApplyBinaryPatch(SourceStream* old_stream, return OK; } +BSDiffStatus ApplyBinaryPatch(const base::FilePath& old_file_path, + const base::FilePath& patch_file_path, + const base::FilePath& new_file_path) { + // Set up the old stream. + base::MemoryMappedFile old_file; + if (!old_file.Initialize(old_file_path)) { + return READ_ERROR; + } + SourceStream old_file_stream; + old_file_stream.Init(old_file.data(), old_file.length()); + + // Set up the patch stream. + base::MemoryMappedFile patch_file; + if (!patch_file.Initialize(patch_file_path)) { + return READ_ERROR; + } + SourceStream patch_file_stream; + patch_file_stream.Init(patch_file.data(), patch_file.length()); + + // Set up the new stream and apply the patch. + SinkStream new_sink_stream; + BSDiffStatus status = ApplyBinaryPatch(&old_file_stream, + &patch_file_stream, + &new_sink_stream); + if (status != OK) { + return status; + } + + // Write the stream to disk. + int written = file_util::WriteFile( + new_file_path, + reinterpret_cast<const char*>(new_sink_stream.Buffer()), + static_cast<int>(new_sink_stream.Length())); + if (written != static_cast<int>(new_sink_stream.Length())) + return WRITE_ERROR; + return OK; +} + } // namespace diff --git a/extensions/common/crx_file.cc b/extensions/common/crx_file.cc index 4f50962..73e7f7b 100644 --- a/extensions/common/crx_file.cc +++ b/extensions/common/crx_file.cc @@ -11,6 +11,9 @@ namespace { // The current version of the crx format. static const uint32 kCurrentVersion = 2; +// The current version of the crx diff format. +static const uint32 kCurrentDiffVersion = 0; + // The maximum size the crx parser will tolerate for a public key. static const uint32 kMaxPublicKeySize = 1 << 16; @@ -21,6 +24,7 @@ static const uint32 kMaxSignatureSize = 1 << 16; // The magic string embedded in the header. const char kCrxFileHeaderMagic[] = "Cr24"; +const char kCrxDiffFileHeaderMagic[] = "CrOD"; scoped_ptr<CrxFile> CrxFile::Parse(const CrxFile::Header& header, CrxFile::Error* error) { @@ -45,12 +49,21 @@ scoped_ptr<CrxFile> CrxFile::Create(const uint32 key_size, CrxFile::CrxFile(const Header& header) : header_(header) { } +bool CrxFile::HeaderIsDelta(const CrxFile::Header& header) { + return !strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic)); +} + bool CrxFile::HeaderIsValid(const CrxFile::Header& header, CrxFile::Error* error) { bool valid = false; - if (strncmp(kCrxFileHeaderMagic, header.magic, sizeof(header.magic))) + bool diffCrx = false; + if (!strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic))) + diffCrx = true; + if (strncmp(kCrxFileHeaderMagic, header.magic, sizeof(header.magic)) && + !diffCrx) *error = kWrongMagic; - else if (header.version != kCurrentVersion) + else if (header.version != kCurrentVersion + && !(diffCrx && header.version == kCurrentDiffVersion)) *error = kInvalidVersion; else if (header.key_size > kMaxPublicKeySize) *error = kInvalidKeyTooLarge; diff --git a/extensions/common/crx_file.h b/extensions/common/crx_file.h index cb450af..3ac8189 100644 --- a/extensions/common/crx_file.h +++ b/extensions/common/crx_file.h @@ -60,6 +60,10 @@ class CrxFile { // Returns the header structure for writing out to a CRX file. const Header& header() const { return header_; } + // Checks a valid |header| to determine whether or not the CRX represents a + // differential CRX. + static bool HeaderIsDelta(const Header& header); + private: Header header_; |