Commit 52054ad2 authored by Jay Civelli's avatar Jay Civelli Committed by Commit Bot

Changing SafeManifestParser to use the SafeXmlParser service.

Changing SafeManifestParser to use the SafeXmlParser service instead of
having a dedicated Mojo interface for doing the parsing of extension
manifests.
SafeXmlParser parses the XML in a sandboxed process and returns a
base::Value that can be traversed in the browser process.

As a result, the mojom, traits, typemap files have been removed.
The implementation of the parsing has been moved to
safe_manifest_parser.cc.

The update_manifest_unittest.cc was moved to
safe_manifest_parser_unittest.cc and split into several tests (no
changes to the logic).

Bug: 786613
Change-Id: Ia586ea0e9689d45130013b6d4d992318eb9b7dc4
Reviewed-on: https://chromium-review.googlesource.com/777939
Commit-Queue: Jay Civelli <jcivelli@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#520802}
parent 1544cd09
......@@ -26,6 +26,7 @@
#include "extensions/browser/notification_types.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "extensions/common/extension.h"
#include "content/public/common/service_manager_connection.h"
#include "extensions/common/extension_urls.h"
#include "net/url_request/url_request_context_getter.h"
......@@ -218,6 +219,11 @@ bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
return false;
}
service_manager::Connector* ExternalCache::GetConnector() {
return content::ServiceManagerConnection::GetForProcess()->GetConnector();
}
void ExternalCache::UpdateExtensionLoader() {
VLOG(1) << "Notify ExternalCache delegate about cache update";
if (delegate_)
......@@ -231,7 +237,7 @@ void ExternalCache::CheckCache() {
// If request_context_ is missing we can't download anything.
if (request_context_.get()) {
downloader_ = ChromeExtensionDownloaderFactory::CreateForRequestContext(
request_context_.get(), this);
request_context_.get(), this, GetConnector());
}
cached_extensions_->Clear();
......
......@@ -31,6 +31,10 @@ namespace net {
class URLRequestContextGetter;
}
namespace service_manager {
class Connector;
}
namespace chromeos {
// The ExternalCache manages a cache for external extensions.
......@@ -136,6 +140,10 @@ class ExternalCache : public content::NotificationObserver,
void set_flush_on_put(bool flush_on_put) { flush_on_put_ = flush_on_put; }
protected:
// Overridden in tests.
virtual service_manager::Connector* GetConnector();
private:
// Notifies the that the cache has been updated, providing
// extensions loader with an updated list of extensions.
......
......@@ -40,6 +40,27 @@ const char kNonWebstoreUpdateUrl[] = "https://localhost/service/update2/crx";
namespace chromeos {
class TestExternalCache : public ExternalCache {
public:
TestExternalCache(const base::FilePath& cache_dir,
net::URLRequestContextGetter* request_context,
const scoped_refptr<base::SequencedTaskRunner>&
backend_task_runner,
Delegate* delegate,
bool always_check_updates,
bool wait_for_cache_initialization)
: ExternalCache(cache_dir, request_context, backend_task_runner, delegate,
always_check_updates, wait_for_cache_initialization) {}
protected:
service_manager::Connector* GetConnector() override {
return nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(TestExternalCache);
};
class ExternalCacheTest : public testing::Test,
public ExternalCache::Delegate {
public:
......@@ -141,7 +162,7 @@ class ExternalCacheTest : public testing::Test,
TEST_F(ExternalCacheTest, Basic) {
base::FilePath cache_dir(CreateCacheDir(false));
ExternalCache external_cache(
TestExternalCache external_cache(
cache_dir, request_context_getter(),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}), this, true,
false);
......@@ -264,7 +285,7 @@ TEST_F(ExternalCacheTest, Basic) {
TEST_F(ExternalCacheTest, PreserveInstalled) {
base::FilePath cache_dir(CreateCacheDir(false));
ExternalCache external_cache(
TestExternalCache external_cache(
cache_dir, request_context_getter(),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}), this, true,
false);
......
......@@ -7,7 +7,6 @@
#include <string>
#include <utility>
#include "base/command_line.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/browser/profiles/profile.h"
......@@ -19,6 +18,7 @@
#include "components/update_client/update_query_params.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "google_apis/gaia/identity_provider.h"
#include "content/public/common/service_manager_connection.h"
using extensions::ExtensionDownloader;
using extensions::ExtensionDownloaderDelegate;
......@@ -31,9 +31,10 @@ const char kTestRequestParam[] = "extension-updater-test-request";
std::unique_ptr<ExtensionDownloader>
ChromeExtensionDownloaderFactory::CreateForRequestContext(
net::URLRequestContextGetter* request_context,
ExtensionDownloaderDelegate* delegate) {
ExtensionDownloaderDelegate* delegate,
service_manager::Connector* connector) {
std::unique_ptr<ExtensionDownloader> downloader(
new ExtensionDownloader(delegate, request_context));
new ExtensionDownloader(delegate, request_context, connector));
#if defined(GOOGLE_CHROME_BUILD)
std::string brand;
google_brand::GetBrand(&brand);
......@@ -61,8 +62,11 @@ ChromeExtensionDownloaderFactory::CreateForProfile(
SigninManagerFactory::GetForProfile(profile),
ProfileOAuth2TokenServiceFactory::GetForProfile(profile),
LoginUIServiceFactory::GetShowLoginPopupCallbackForProfile(profile)));
service_manager::Connector* connector =
content::ServiceManagerConnection::GetForProcess()->GetConnector();
std::unique_ptr<ExtensionDownloader> downloader =
CreateForRequestContext(profile->GetRequestContext(), delegate);
CreateForRequestContext(profile->GetRequestContext(), delegate,
connector);
downloader->SetWebstoreIdentityProvider(std::move(identity_provider));
return downloader;
}
......@@ -18,6 +18,10 @@ namespace net {
class URLRequestContextGetter;
}
namespace service_manager {
class Connector;
}
// This provides a simple static interface for constructing an
// ExtensionDownloader suitable for use from within Chrome.
class ChromeExtensionDownloaderFactory {
......@@ -26,7 +30,8 @@ class ChromeExtensionDownloaderFactory {
// is associated with this downloader.
static std::unique_ptr<extensions::ExtensionDownloader>
CreateForRequestContext(net::URLRequestContextGetter* request_context,
extensions::ExtensionDownloaderDelegate* delegate);
extensions::ExtensionDownloaderDelegate* delegate,
service_manager::Connector* connector);
// Creates a downloader for a given Profile. This downloader will be able
// to authenticate as the signed-in user in the event that it's asked to
......
......@@ -74,6 +74,7 @@
#include "net/http/http_request_headers.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_status.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/third_party/mozilla/url_parse.h"
......@@ -377,7 +378,8 @@ class MockService : public TestExtensionService {
ChromeExtensionDownloaderFactory::CreateForRequestContext(
request_context(), downloader_delegate_override_
? downloader_delegate_override_
: delegate);
: delegate,
/*connector=*/nullptr);
return downloader;
}
......@@ -679,8 +681,8 @@ class ExtensionUpdaterTest : public testing::Test {
void AddParseResult(const std::string& id,
const std::string& version,
const std::string& url,
UpdateManifest::Results* results) {
UpdateManifest::Result result;
UpdateManifestResults* results) {
UpdateManifestResult result;
result.extension_id = id;
result.version = version;
result.crx_url = GURL(url);
......@@ -820,7 +822,8 @@ class ExtensionUpdaterTest : public testing::Test {
MockService service(prefs_.get());
MockExtensionDownloaderDelegate delegate;
ExtensionDownloader downloader(&delegate, service.request_context());
ExtensionDownloader downloader(&delegate, service.request_context(),
data_decoder_service_connector());
ExtensionList extensions;
service.CreateTestExtensions(1, num_extensions, &extensions, &update_url,
......@@ -909,12 +912,13 @@ class ExtensionUpdaterTest : public testing::Test {
void TestDetermineUpdates() {
TestingProfile profile;
MockExtensionDownloaderDelegate delegate;
ExtensionDownloader downloader(&delegate, profile.GetRequestContext());
ExtensionDownloader downloader(&delegate, profile.GetRequestContext(),
data_decoder_service_connector());
// Check passing an empty list of parse results to DetermineUpdates
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo")));
UpdateManifest::Results updates;
UpdateManifestResults updates;
std::vector<int> updateable;
downloader.DetermineUpdates(*fetch_data, updates, &updateable);
EXPECT_TRUE(updateable.empty());
......@@ -955,11 +959,12 @@ class ExtensionUpdaterTest : public testing::Test {
TestingProfile profile;
MockExtensionDownloaderDelegate delegate;
ExtensionDownloader downloader(&delegate, profile.GetRequestContext());
ExtensionDownloader downloader(&delegate, profile.GetRequestContext(),
data_decoder_service_connector());
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo")));
UpdateManifest::Results updates;
UpdateManifestResults updates;
std::list<std::string> ids_for_update_check;
pending_extension_manager->GetPendingIdsForUpdateCheck(
......@@ -993,7 +998,8 @@ class ExtensionUpdaterTest : public testing::Test {
net::TestURLFetcher* fetcher = NULL;
MockService service(prefs_.get());
MockExtensionDownloaderDelegate delegate;
ExtensionDownloader downloader(&delegate, service.request_context());
ExtensionDownloader downloader(&delegate, service.request_context(),
data_decoder_service_connector());
downloader.manifests_queue_.set_backoff_policy(&kNoBackoffPolicy);
GURL kUpdateUrl("http://localhost/manifest1");
......@@ -1141,7 +1147,8 @@ class ExtensionUpdaterTest : public testing::Test {
NotificationsObserver observer;
MockService service(prefs_.get());
MockExtensionDownloaderDelegate delegate;
ExtensionDownloader downloader(&delegate, service.request_context());
ExtensionDownloader downloader(&delegate, service.request_context(),
data_decoder_service_connector());
downloader.manifests_queue_.set_backoff_policy(&kNoBackoffPolicy);
GURL kUpdateUrl("http://localhost/manifest1");
......@@ -1843,15 +1850,18 @@ class ExtensionUpdaterTest : public testing::Test {
&kNeverPingedData, kEmptyUpdateUrlData,
std::string(),
ManifestFetchData::FetchPriority::BACKGROUND);
UpdateManifest::Results results;
results.daystart_elapsed_seconds = 750;
auto results = std::make_unique<UpdateManifestResults>();
constexpr int kDaystartElapsedSeconds = 750;
results->daystart_elapsed_seconds = kDaystartElapsedSeconds;
updater.downloader_->HandleManifestResults(fetch_data.get(), &results);
updater.downloader_->HandleManifestResults(
std::move(fetch_data), std::move(results),
/*error=*/base::Optional<std::string>());
Time last_ping_day =
service.extension_prefs()->LastPingDay(extension->id());
EXPECT_FALSE(last_ping_day.is_null());
int64_t seconds_diff = (Time::Now() - last_ping_day).InSeconds();
EXPECT_LT(seconds_diff - results.daystart_elapsed_seconds, 5);
EXPECT_LT(seconds_diff - kDaystartElapsedSeconds, 5);
}
// This lets us run a test with some enabled and some disabled
......@@ -1993,10 +2003,15 @@ class ExtensionUpdaterTest : public testing::Test {
update_url, ManifestFetchData::FetchPriority::BACKGROUND);
}
service_manager::Connector* data_decoder_service_connector() const {
return test_data_decoder_service_.connector();
}
private:
content::TestBrowserThreadBundle thread_bundle_;
content::InProcessUtilityThreadHelper in_process_utility_thread_helper_;
ScopedTestingLocalState testing_local_state_;
data_decoder::TestDataDecoderService test_data_decoder_service_;
#if defined OS_CHROMEOS
chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
......@@ -2231,8 +2246,8 @@ TEST_F(ExtensionUpdaterTest, TestManifestFetchesBuilderAddExtension) {
net::TestURLFetcherFactory factory;
MockService service(prefs_.get());
MockExtensionDownloaderDelegate delegate;
std::unique_ptr<ExtensionDownloader> downloader(
new ExtensionDownloader(&delegate, service.request_context()));
std::unique_ptr<ExtensionDownloader> downloader(new ExtensionDownloader(
&delegate, service.request_context(), data_decoder_service_connector()));
EXPECT_EQ(0u, ManifestFetchersCount(downloader.get()));
// First, verify that adding valid extensions does invoke the callbacks on
......@@ -2265,8 +2280,8 @@ TEST_F(ExtensionUpdaterTest, TestManifestFetchesBuilderAddExtension) {
// converted from user scripts are rejected.
// Reset the ExtensionDownloader so that it drops the current fetcher.
downloader.reset(
new ExtensionDownloader(&delegate, service.request_context()));
downloader.reset(new ExtensionDownloader(&delegate, service.request_context(),
data_decoder_service_connector()));
EXPECT_EQ(0u, ManifestFetchersCount(downloader.get()));
// Extensions with empty update URLs should have a default one
......@@ -2288,7 +2303,8 @@ TEST_F(ExtensionUpdaterTest, TestStartUpdateCheckMemory) {
net::TestURLFetcherFactory factory;
MockService service(prefs_.get());
MockExtensionDownloaderDelegate delegate;
ExtensionDownloader downloader(&delegate, service.request_context());
ExtensionDownloader downloader(&delegate, service.request_context(),
data_decoder_service_connector());
StartUpdateCheck(&downloader,
CreateManifestFetchData(GURL("http://localhost/foo")));
......
......@@ -217,6 +217,8 @@ test("extensions_unittests") {
"//extensions/renderer:unit_tests",
"//extensions/shell:unit_tests",
"//extensions/utility:unit_tests",
"//services/data_decoder:lib",
"//services/service_manager/public/cpp/test:test_support",
"//ui/gl:test_support",
]
......
......@@ -557,6 +557,7 @@ source_set("unit_tests") {
"requirements_checker_unittest.cc",
"runtime_data_unittest.cc",
"sandboxed_unpacker_unittest.cc",
"updater/safe_manifest_parser_unittest.cc",
"updater/update_service_unittest.cc",
"value_store/leveldb_scoped_database_unittest.cc",
"value_store/leveldb_value_store_unittest.cc",
......@@ -595,6 +596,7 @@ source_set("unit_tests") {
"//extensions/features",
"//ipc:test_support",
"//net:test_support",
"//services/data_decoder/public/cpp:test_support",
"//services/device/public/interfaces",
"//storage/browser:test_support",
"//third_party/leveldatabase",
......
......@@ -23,6 +23,7 @@ include_rules = [
"+net",
# This directory contains build flags and does not pull all of PPAPI in.
"+ppapi/features",
"+services/data_decoder/public/cpp",
"+services/network/public/cpp",
"+services/preferences/public/cpp",
"+services/service_manager/public/cpp",
......
......@@ -41,5 +41,6 @@ source_set("updater") {
deps = [
"//extensions/common",
"//extensions/strings",
"//services/data_decoder/public/cpp",
]
}
......@@ -30,7 +30,6 @@
#include "extensions/browser/updater/extension_cache.h"
#include "extensions/browser/updater/extension_downloader_test_delegate.h"
#include "extensions/browser/updater/request_queue_impl.h"
#include "extensions/browser/updater/safe_manifest_parser.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/manifest_url_handlers.h"
#include "google_apis/gaia/identity_provider.h"
......@@ -196,10 +195,12 @@ ExtensionDownloader::ExtraParams::ExtraParams() : is_corrupt_reinstall(false) {}
ExtensionDownloader::ExtensionDownloader(
ExtensionDownloaderDelegate* delegate,
net::URLRequestContextGetter* request_context)
net::URLRequestContextGetter* request_context,
service_manager::Connector* connector)
: OAuth2TokenService::Consumer(kTokenServiceConsumerId),
delegate_(delegate),
request_context_(request_context),
connector_(connector),
manifests_queue_(&kDefaultBackoffPolicy,
base::Bind(&ExtensionDownloader::CreateManifestFetcher,
base::Unretained(this))),
......@@ -578,11 +579,10 @@ void ExtensionDownloader::OnManifestFetchComplete(
manifests_queue_.active_request_failure_count(),
url);
VLOG(2) << "beginning manifest parse for " << url;
auto callback = base::Bind(
&ExtensionDownloader::HandleManifestResults,
weak_ptr_factory_.GetWeakPtr(),
base::Owned(manifests_queue_.reset_active_request().release()));
ParseUpdateManifest(data, callback);
auto callback = base::BindOnce(&ExtensionDownloader::HandleManifestResults,
weak_ptr_factory_.GetWeakPtr(),
manifests_queue_.reset_active_request());
ParseUpdateManifest(connector_, data, std::move(callback));
} else {
VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
......@@ -607,8 +607,9 @@ void ExtensionDownloader::OnManifestFetchComplete(
}
void ExtensionDownloader::HandleManifestResults(
const ManifestFetchData* fetch_data,
const UpdateManifest::Results* results) {
std::unique_ptr<ManifestFetchData> fetch_data,
std::unique_ptr<UpdateManifestResults> results,
const base::Optional<std::string>& error) {
// Keep a list of extensions that will not be updated, so that the |delegate_|
// can be notified once we're done here.
std::set<std::string> not_updated(fetch_data->extension_ids());
......@@ -627,7 +628,7 @@ void ExtensionDownloader::HandleManifestResults(
std::vector<int> updates;
DetermineUpdates(*fetch_data, *results, &updates);
for (size_t i = 0; i < updates.size(); i++) {
const UpdateManifest::Result* update = &(results->list.at(updates[i]));
const UpdateManifestResult* update = &(results->list.at(updates[i]));
const std::string& id = update->extension_id;
not_updated.erase(id);
......@@ -679,10 +680,10 @@ void ExtensionDownloader::HandleManifestResults(
void ExtensionDownloader::DetermineUpdates(
const ManifestFetchData& fetch_data,
const UpdateManifest::Results& possible_updates,
const UpdateManifestResults& possible_updates,
std::vector<int>* result) {
for (size_t i = 0; i < possible_updates.list.size(); i++) {
const UpdateManifest::Result* update = &possible_updates.list[i];
const UpdateManifestResult* update = &possible_updates.list[i];
const std::string& id = update->extension_id;
if (!fetch_data.Includes(id)) {
......
......@@ -19,8 +19,8 @@
#include "extensions/browser/updater/extension_downloader_delegate.h"
#include "extensions/browser/updater/manifest_fetch_data.h"
#include "extensions/browser/updater/request_queue.h"
#include "extensions/browser/updater/safe_manifest_parser.h"
#include "extensions/common/extension.h"
#include "extensions/common/update_manifest.h"
#include "google_apis/gaia/oauth2_token_service.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "url/gurl.h"
......@@ -33,6 +33,10 @@ class URLRequestContextGetter;
class URLRequestStatus;
}
namespace service_manager {
class Connector;
}
namespace extensions {
struct UpdateDetails {
......@@ -63,7 +67,8 @@ class ExtensionDownloader : public net::URLFetcherDelegate,
// |delegate| is stored as a raw pointer and must outlive the
// ExtensionDownloader.
ExtensionDownloader(ExtensionDownloaderDelegate* delegate,
net::URLRequestContextGetter* request_context);
net::URLRequestContextGetter* request_context,
service_manager::Connector* connector);
~ExtensionDownloader() override;
// Adds |extension| to the list of extensions to check for updates.
......@@ -237,13 +242,14 @@ class ExtensionDownloader : public net::URLFetcherDelegate,
// Once a manifest is parsed, this starts fetches of any relevant crx files.
// If |results| is null, it means something went wrong when parsing it.
void HandleManifestResults(const ManifestFetchData* fetch_data,
const UpdateManifest::Results* results);
void HandleManifestResults(std::unique_ptr<ManifestFetchData> fetch_data,
std::unique_ptr<UpdateManifestResults> results,
const base::Optional<std::string>& error);
// Given a list of potential updates, returns the indices of the ones that are
// applicable (are actually a new version, etc.) in |result|.
void DetermineUpdates(const ManifestFetchData& fetch_data,
const UpdateManifest::Results& possible_updates,
const UpdateManifestResults& possible_updates,
std::vector<int>* result);
// Begins (or queues up) download of an updated extension.
......@@ -311,6 +317,9 @@ class ExtensionDownloader : public net::URLFetcherDelegate,
// The request context to use for the URLFetchers.
scoped_refptr<net::URLRequestContextGetter> request_context_;
// The connector to the ServiceManager.
service_manager::Connector* connector_;
// Collects UMA samples that are reported when ReportStats() is called.
URLStats url_stats_;
......
......@@ -8,45 +8,220 @@
#include "base/bind.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "base/version.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/utility_process_mojo_client.h"
#include "extensions/common/manifest_parser.mojom.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "services/data_decoder/public/cpp/safe_xml_parser.h"
namespace extensions {
using data_decoder::GetXmlElementAttribute;
using data_decoder::GetXmlElementChildWithTag;
using data_decoder::GetXmlElementNamespacePrefix;
using data_decoder::GetXmlQualifiedName;
using data_decoder::IsXmlElementNamed;
namespace {
using UtilityProcess =
content::UtilityProcessMojoClient<extensions::mojom::ManifestParser>;
constexpr char kExpectedGupdateProtocol[] = "2.0";
constexpr char kExpectedGupdateXmlns[] =
"http://www.google.com/update2/response";
void ReportError(ParseUpdateManifestCallback callback,
const std::string& error) {
std::move(callback).Run(/*results=*/nullptr, error);
}
// Helper function that reads in values for a single <app> tag. It returns a
// boolean indicating success or failure. On failure, it writes a error message
// into |error_detail|.
bool ParseSingleAppTag(const base::Value& app_element,
const std::string& xml_namespace,
UpdateManifestResult* result,
std::string* error_detail) {
// Read the extension id.
result->extension_id = GetXmlElementAttribute(app_element, "appid");
if (result->extension_id.empty()) {
*error_detail = "Missing appid on app node";
return false;
}
// Get the updatecheck node.
std::string updatecheck_name =
GetXmlQualifiedName(xml_namespace, "updatecheck");
int updatecheck_count =
data_decoder::GetXmlElementChildrenCount(app_element, updatecheck_name);
if (updatecheck_count != 1) {
*error_detail = updatecheck_count == 0
? "Too many updatecheck tags on app (expecting only 1)."
: "Missing updatecheck on app.";
return false;
}
const base::Value* updatecheck =
data_decoder::GetXmlElementChildWithTag(app_element, updatecheck_name);
using ResultCallback = base::Callback<void(const UpdateManifest::Results*)>;
if (GetXmlElementAttribute(*updatecheck, "status") == "noupdate")
return true;
void ParseDone(std::unique_ptr<UtilityProcess> /* utility_process */,
const ResultCallback& callback,
const base::Optional<UpdateManifest::Results>& results) {
// Find the url to the crx file.
result->crx_url = GURL(GetXmlElementAttribute(*updatecheck, "codebase"));
if (!result->crx_url.is_valid()) {
*error_detail = "Invalid codebase url: '";
*error_detail += result->crx_url.possibly_invalid_spec();
*error_detail += "'.";
return false;
}
// Get the version.
result->version = GetXmlElementAttribute(*updatecheck, "version");
if (result->version.empty()) {
*error_detail = "Missing version for updatecheck.";
return false;
}
base::Version version(result->version);
if (!version.IsValid()) {
*error_detail = "Invalid version: '";
*error_detail += result->version;
*error_detail += "'.";
return false;
}
// Get the optional minimum browser version.
result->browser_min_version =
GetXmlElementAttribute(*updatecheck, "prodversionmin");
if (!result->browser_min_version.empty()) {
base::Version browser_min_version(result->browser_min_version);
if (!browser_min_version.IsValid()) {
*error_detail = "Invalid prodversionmin: '";
*error_detail += result->browser_min_version;
*error_detail += "'.";
return false;
}
}
// package_hash is optional. It is a sha256 hash of the package in hex format.
result->package_hash = GetXmlElementAttribute(*updatecheck, "hash_sha256");
int size = 0;
if (base::StringToInt(GetXmlElementAttribute(*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 = GetXmlElementAttribute(*updatecheck, "fp");
// Differential update information is optional.
result->diff_crx_url =
GURL(GetXmlElementAttribute(*updatecheck, "codebasediff"));
result->diff_package_hash = GetXmlElementAttribute(*updatecheck, "hashdiff");
int sizediff = 0;
if (base::StringToInt(GetXmlElementAttribute(*updatecheck, "sizediff"),
&sizediff)) {
result->diff_size = sizediff;
}
return true;
}
void ParseXmlDone(ParseUpdateManifestCallback callback,
std::unique_ptr<base::Value> root,
const base::Optional<std::string>& error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
callback.Run(results ? &results.value() : nullptr);
if (error) {
ReportError(std::move(callback), "Failed to parse XML: " + *error);
return;
}
auto results = std::make_unique<UpdateManifestResults>();
if (!root) {
ReportError(std::move(callback), "Empty XML");
return;
}
// Look for the required namespace declaration.
std::string gupdate_ns;
if (!GetXmlElementNamespacePrefix(*root, kExpectedGupdateXmlns,
&gupdate_ns)) {
ReportError(std::move(callback),
"Missing or incorrect xmlns on gupdate tag");
return;
}
if (!IsXmlElementNamed(*root, GetXmlQualifiedName(gupdate_ns, "gupdate"))) {
ReportError(std::move(callback), "Missing gupdate tag");
return;
}
// Check for the gupdate "protocol" attribute.
if (GetXmlElementAttribute(*root, "protocol") != kExpectedGupdateProtocol) {
ReportError(std::move(callback),
std::string("Missing/incorrect protocol on gupdate tag "
"(expected '") +
kExpectedGupdateProtocol + "')");
return;
}
// Parse the first <daystart> if it's present.
const base::Value* daystart = GetXmlElementChildWithTag(
*root, GetXmlQualifiedName(gupdate_ns, "daystart"));
if (daystart) {
std::string elapsed_seconds =
GetXmlElementAttribute(*daystart, "elapsed_seconds");
int parsed_elapsed = kNoDaystart;
if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
results->daystart_elapsed_seconds = parsed_elapsed;
}
}
// Parse each of the <app> tags.
std::vector<const base::Value*> apps;
data_decoder::GetAllXmlElementChildrenWithTag(
*root, GetXmlQualifiedName(gupdate_ns, "app"), &apps);
std::string error_msg;
for (const auto* app : apps) {
UpdateManifestResult result;
std::string app_error;
if (!ParseSingleAppTag(*app, gupdate_ns, &result, &app_error)) {
if (!error_msg.empty())
error_msg += "\r\n"; // Should we have an OS specific EOL?
error_msg += app_error;
} else {
results->list.push_back(result);
}
}
std::move(callback).Run(
results->list.empty() ? nullptr : std::move(results),
error_msg.empty() ? base::Optional<std::string>() : error_msg);
}
} // namespace
namespace extensions {
UpdateManifestResult::UpdateManifestResult() = default;
void ParseUpdateManifest(const std::string& xml,
const ResultCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(callback);
UpdateManifestResult::UpdateManifestResult(const UpdateManifestResult& other) =
default;
UpdateManifestResult::~UpdateManifestResult() = default;
auto process = std::make_unique<UtilityProcess>(
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_MANIFEST_PARSER_NAME));
auto* utility_process = process.get();
auto done = base::Bind(&ParseDone, base::Passed(&process), callback);
utility_process->set_error_callback(base::Bind(done, base::nullopt));
UpdateManifestResults::UpdateManifestResults() = default;
utility_process->Start();
UpdateManifestResults::UpdateManifestResults(
const UpdateManifestResults& other) = default;
utility_process->service()->Parse(xml, done);
UpdateManifestResults::~UpdateManifestResults() = default;
void ParseUpdateManifest(service_manager::Connector* connector,
const std::string& xml,
ParseUpdateManifestCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(callback);
data_decoder::ParseXml(connector, xml,
base::BindOnce(&ParseXmlDone, std::move(callback)));
}
} // namespace extensions
......@@ -5,19 +5,90 @@
#ifndef EXTENSIONS_BROWSER_UPDATER_SAFE_MANIFEST_PARSER_H_
#define EXTENSIONS_BROWSER_UPDATER_SAFE_MANIFEST_PARSER_H_
#include <memory>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "extensions/common/update_manifest.h"
#include "base/optional.h"
#include "url/gurl.h"
namespace service_manager {
class Connector;
}
namespace extensions {
struct UpdateManifestResult {
UpdateManifestResult();
UpdateManifestResult(const UpdateManifestResult& other);
~UpdateManifestResult();
std::string extension_id;
std::string version;
std::string browser_min_version;
// Attributes for the full update.
GURL crx_url;
std::string package_hash;
int size = 0;
std::string package_fingerprint;
// Attributes for the differential update.
GURL diff_crx_url;
std::string diff_package_hash;
int diff_size = 0;
};
constexpr int kNoDaystart = -1;
struct UpdateManifestResults {
UpdateManifestResults();
UpdateManifestResults(const UpdateManifestResults& other);
UpdateManifestResults& operator=(const UpdateManifestResults& other);
~UpdateManifestResults();
std::vector<UpdateManifestResult> list;
// This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
int daystart_elapsed_seconds = kNoDaystart;
};
// Parses an update manifest |xml| safely in a utility process and calls
// |callback| with the results, which will be null on failure. Runs on
// the UI thread.
void ParseUpdateManifest(
const std::string& xml,
const base::Callback<void(const UpdateManifest::Results*)>& callback);
//
// 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" 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>
//
// The <daystart> tag contains a "elapsed_seconds" attribute which refers to
// the server's notion of how many seconds it has been since midnight.
//
// The "appid" attribute of the <app> tag refers to the unique id of the
// extension. The "codebase" attribute of the <updatecheck> tag is the url to
// 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.
using ParseUpdateManifestCallback =
base::OnceCallback<void(std::unique_ptr<UpdateManifestResults> results,
const base::Optional<std::string>& error)>;
void ParseUpdateManifest(service_manager::Connector* connector,
const std::string& xml,
ParseUpdateManifestCallback callback);
} // namespace extensions
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/updater/safe_manifest_parser.h"
#include "base/run_loop.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/updater/safe_manifest_parser.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
class ExtensionUpdateManifestTest : public testing::Test {
public:
void TestParseUpdateManifest(const std::string& xml) {
base::RunLoop run_loop;
ParseUpdateManifest(
test_data_decoder_service_.connector(), xml,
base::BindOnce(&ExtensionUpdateManifestTest::OnUpdateManifestParsed,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
protected:
UpdateManifestResults* results() const { return results_.get(); }
const base::Optional<std::string>& error() const { return error_; }
void ExpectNoError() {
EXPECT_FALSE(error_) << "Unexpected error: '" << *error_;
}
private:
void OnUpdateManifestParsed(base::Closure quit_loop,
std::unique_ptr<UpdateManifestResults> results,
const base::Optional<std::string>& error) {
results_ = std::move(results);
error_ = error;
std::move(quit_loop).Run();
}
content::TestBrowserThreadBundle browser_thread_bundle_;
std::unique_ptr<UpdateManifestResults> results_;
base::Optional<std::string> error_;
data_decoder::TestDataDecoderService test_data_decoder_service_;
};
} // namespace
TEST_F(ExtensionUpdateManifestTest, InvalidXml) {
TestParseUpdateManifest(std::string());
EXPECT_FALSE(results());
EXPECT_TRUE(error());
}
TEST_F(ExtensionUpdateManifestTest, MissingAppId) {
TestParseUpdateManifest(
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' />"
" </app>"
"</gupdate>");
EXPECT_FALSE(results());
EXPECT_TRUE(error());
}
TEST_F(ExtensionUpdateManifestTest, InvalidCodebase) {
TestParseUpdateManifest(
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345' status='ok'>"
" <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' />"
" </app>"
"</gupdate>");
EXPECT_FALSE(results());
EXPECT_TRUE(error());
}
TEST_F(ExtensionUpdateManifestTest, MissingVersion) {
TestParseUpdateManifest(
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345' status='ok'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
" </app>"
"</gupdate>");
EXPECT_FALSE(results());
EXPECT_TRUE(error());
}
TEST_F(ExtensionUpdateManifestTest, InvalidVersion) {
TestParseUpdateManifest(
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345' status='ok'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
" version='1.2.3.a'/>"
" </app>"
"</gupdate>");
EXPECT_FALSE(results());
EXPECT_TRUE(error());
}
TEST_F(ExtensionUpdateManifestTest, ValidXml) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->list.size());
const UpdateManifestResult& first_result = results()->list.at(0);
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
first_result.crx_url);
EXPECT_EQ("1.2.3.4", first_result.version);
EXPECT_EQ("2.0.143.0", first_result.browser_min_version);
}
TEST_F(ExtensionUpdateManifestTest, ValidXmlWithNamespacePrefix) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<g:gupdate xmlns:g='http://www.google.com/update2/response'"
" protocol='2.0'>"
" <g:app appid='12345'>"
" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </g:app>"
"</g:gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->list.size());
const UpdateManifestResult& first_result = results()->list.at(0);
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
first_result.crx_url);
EXPECT_EQ("1.2.3.4", first_result.version);
EXPECT_EQ("2.0.143.0", first_result.browser_min_version);
}
TEST_F(ExtensionUpdateManifestTest, SimilarTagnames) {
// Includes unrelated <app> tags from other xml namespaces.
// This should not cause problems, the unrelated app tags should be ignored.
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response'"
" xmlns:a='http://a' protocol='2.0'>"
" <a:app/>"
" <b:app xmlns:b='http://b' />"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
" <a:app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </a:app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
// We should still have parsed the gupdate app tag.
EXPECT_EQ(1U, results()->list.size());
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
results()->list.at(0).crx_url);
}
TEST_F(ExtensionUpdateManifestTest, XmlWithHash) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <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_sha256='1234'/>"
" </app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->list.size());
const UpdateManifestResult& first_result = results()->list.at(0);
EXPECT_EQ("1234", first_result.package_hash);
}
TEST_F(ExtensionUpdateManifestTest, XmlWithDaystart) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <daystart elapsed_seconds='456' />"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(results()->daystart_elapsed_seconds, 456);
}
TEST_F(ExtensionUpdateManifestTest, NoUpdateResponse) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345'>"
" <updatecheck status='noupdate' />"
" </app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
ASSERT_FALSE(results()->list.empty());
const UpdateManifestResult& first_result = results()->list.at(0);
EXPECT_EQ(first_result.extension_id, "12345");
EXPECT_TRUE(first_result.version.empty());
}
TEST_F(ExtensionUpdateManifestTest, TwoAppsOneError) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='aaaaaaaa' status='error-unknownApplication'>"
" <updatecheck status='error-unknownapplication'/>"
" </app>"
" <app appid='bbbbbbbb'>"
" <updatecheck codebase='http://example.com/b_3.1.crx' version='3.1'/>"
" </app>"
"</gupdate>");
EXPECT_TRUE(error());
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->list.size());
const UpdateManifestResult& first_result = results()->list.at(0);
EXPECT_EQ(first_result.extension_id, "bbbbbbbb");
}
} // namespace extensions
......@@ -24,7 +24,6 @@ if (enable_extensions) {
mojom("mojo") {
sources = [
"extension_unpacker.mojom",
"manifest_parser.mojom",
"mojo/app_window.mojom",
"mojo/keep_alive.mojom",
]
......@@ -263,8 +262,6 @@ if (enable_extensions) {
"stack_frame.h",
"switches.cc",
"switches.h",
"update_manifest.cc",
"update_manifest.h",
"url_pattern.cc",
"url_pattern.h",
"url_pattern_set.cc",
......@@ -313,7 +310,6 @@ if (enable_extensions) {
"//net",
"//third_party/boringssl",
"//third_party/icu",
"//third_party/libxml",
"//third_party/re2",
"//ui/base",
"//ui/gfx/geometry",
......@@ -390,7 +386,6 @@ if (enable_extensions) {
"permissions/socket_permission_unittest.cc",
"permissions/usb_device_permission_unittest.cc",
"stack_frame_unittest.cc",
"update_manifest_unittest.cc",
"url_pattern_set_unittest.cc",
"url_pattern_unittest.cc",
"user_script_unittest.cc",
......
// Copyright 2017 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.
// Secure chrome extension update manifest parser provided by the utility
// process and exposed by mojo policy to the chrome browser process.
module extensions.mojom;
import "url/mojo/url.mojom";
interface ManifestParser {
// Parse an extensions update manifest |xml| document and return the
// |results|, or null if parsing fails.
Parse(string xml) => (UpdateManifestResults? results);
};
struct UpdateManifestResults {
array<UpdateManifestResult> list;
int32 daystart_elapsed_seconds;
};
struct UpdateManifestResult {
string extension_id;
string version;
string browser_min_version;
url.mojom.Url crx_url;
string package_hash;
int32 size;
string package_fingerprint;
url.mojom.Url diff_crx_url;
string diff_package_hash;
int32 diff_size;
};
# Copyright 2017 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.
mojom = "//extensions/common/manifest_parser.mojom"
public_headers = [ "//extensions/common/update_manifest.h" ]
traits_headers = [ "//extensions/common/manifest_parser_struct_traits.h" ]
sources = [
"//extensions/common/manifest_parser_struct_traits.cc",
]
type_mappings = [
"extensions.mojom.UpdateManifestResults=::UpdateManifest::Results",
"extensions.mojom.UpdateManifestResult=::UpdateManifest::Result",
]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/manifest_parser_struct_traits.h"
#include "url/mojo/url_gurl_struct_traits.h"
namespace mojo {
// static
bool StructTraits<::extensions::mojom::UpdateManifestResults::DataView,
::UpdateManifest::Results>::
Read(::extensions::mojom::UpdateManifestResults::DataView input,
::UpdateManifest::Results* output) {
if (!input.ReadList(&output->list))
return false;
output->daystart_elapsed_seconds = input.daystart_elapsed_seconds();
return true;
}
// static
bool StructTraits<::extensions::mojom::UpdateManifestResult::DataView,
::UpdateManifest::Result>::
Read(::extensions::mojom::UpdateManifestResult::DataView input,
::UpdateManifest::Result* output) {
if (!input.ReadExtensionId(&output->extension_id))
return false;
if (!input.ReadVersion(&output->version))
return false;
if (!input.ReadBrowserMinVersion(&output->browser_min_version))
return false;
if (!input.ReadCrxUrl(&output->crx_url))
return false;
if (!input.ReadPackageHash(&output->package_hash))
return false;
output->size = input.size();
if (!input.ReadPackageFingerprint(&output->package_fingerprint))
return false;
if (!input.ReadDiffCrxUrl(&output->diff_crx_url))
return false;
if (!input.ReadDiffPackageHash(&output->diff_package_hash))
return false;
output->diff_size = input.diff_size();
return true;
}
} // namespace mojo
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef EXTENSIONS_COMMON_MANIFEST_PARSER_STRUCT_TRAITS_H
#define EXTENSIONS_COMMON_MANIFEST_PARSER_STRUCT_TRAITS_H
#include "extensions/common/manifest_parser.mojom.h"
#include "extensions/common/update_manifest.h"
namespace mojo {
template <>
struct StructTraits<::extensions::mojom::UpdateManifestResults::DataView,
::UpdateManifest::Results> {
static const std::vector<::UpdateManifest::Result>& list(
const ::UpdateManifest::Results& input) {
return input.list;
}
static int daystart_elapsed_seconds(const ::UpdateManifest::Results& input) {
return input.daystart_elapsed_seconds;
}
static bool Read(::extensions::mojom::UpdateManifestResults::DataView input,
::UpdateManifest::Results* output);
};
template <>
struct StructTraits<::extensions::mojom::UpdateManifestResult::DataView,
::UpdateManifest::Result> {
static const std::string& extension_id(
const ::UpdateManifest::Result& input) {
return input.extension_id;
}
static const std::string& version(const ::UpdateManifest::Result& input) {
return input.version;
}
static const std::string& browser_min_version(
const ::UpdateManifest::Result& input) {
return input.browser_min_version;
}
static const GURL& crx_url(const ::UpdateManifest::Result& input) {
return input.crx_url;
}
static const std::string& package_hash(
const ::UpdateManifest::Result& input) {
return input.package_hash;
}
static int size(const ::UpdateManifest::Result& input) { return input.size; }
static const std::string& package_fingerprint(
const ::UpdateManifest::Result& input) {
return input.package_fingerprint;
}
static const GURL& diff_crx_url(const ::UpdateManifest::Result& input) {
return input.diff_crx_url;
}
static const std::string& diff_package_hash(
const ::UpdateManifest::Result& input) {
return input.diff_package_hash;
}
static int diff_size(const ::UpdateManifest::Result& input) {
return input.diff_size;
}
static bool Read(::extensions::mojom::UpdateManifestResult::DataView input,
::UpdateManifest::Result* output);
};
} // namespace mojo
#endif // EXTENSIONS_COMMON_MANIFEST_PARSER_STRUCT_TRAITS_H
......@@ -2,7 +2,4 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
typemaps = [
"//extensions/common/extension_unpacker.typemap",
"//extensions/common/manifest_parser.typemap",
]
typemaps = [ "//extensions/common/extension_unpacker.typemap" ]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/update_manifest.h"
#include <algorithm>
#include <memory>
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/version.h"
#include "libxml/tree.h"
#include "third_party/libxml/chromium/libxml_utils.h"
static const char* kExpectedGupdateProtocol = "2.0";
static const char* kExpectedGupdateXmlns =
"http://www.google.com/update2/response";
UpdateManifest::Result::Result() : size(0), diff_size(0) {}
UpdateManifest::Result::Result(const Result& other) = default;
UpdateManifest::Result::~Result() = default;
UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
UpdateManifest::Results::Results(const Results& other) = default;
UpdateManifest::Results& UpdateManifest::Results::operator=(
const Results& other) = default;
UpdateManifest::Results::~Results() = default;
UpdateManifest::UpdateManifest() = default;
UpdateManifest::~UpdateManifest() = default;
void UpdateManifest::ParseError(const char* details, ...) {
va_list args;
va_start(args, details);
if (errors_.length() > 0) {
// TODO(asargent) make a platform abstracted newline?
errors_ += "\r\n";
}
base::StringAppendV(&errors_, details, args);
va_end(args);
}
// Checks whether a given node's name matches |expected_name| and
// |expected_namespace|.
static bool TagNameEquals(const xmlNode* node, const char* expected_name,
const xmlNs* expected_namespace) {
if (node->ns != expected_namespace) {
return false;
}
return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
}
// Returns child nodes of |root| with name |name| in namespace |xml_namespace|.
static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace,
const char* name) {
std::vector<xmlNode*> result;
for (xmlNode* child = root->children; child != NULL; child = child->next) {
if (!TagNameEquals(child, name, xml_namespace)) {
continue;
}
result.push_back(child);
}
return result;
}
// Returns the value of a named attribute, or the empty string.
static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
if (!xmlStrcmp(attr->name, name) && attr->children &&
attr->children->content) {
return std::string(reinterpret_cast<const char*>(
attr->children->content));
}
}
return std::string();
}
// This is used for the xml parser to report errors. This assumes the context
// is a pointer to a std::string where the error message should be appended.
static void XmlErrorFunc(void *context, const char *message, ...) {
va_list args;
va_start(args, message);
std::string* error = static_cast<std::string*>(context);
base::StringAppendV(error, message, args);
va_end(args);
}
// Utility class for cleaning up the xml document when leaving a scope.
class ScopedXmlDocument {
public:
explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
~ScopedXmlDocument() {
if (document_)
xmlFreeDoc(document_);
}
xmlDocPtr get() {
return document_;
}
private:
xmlDocPtr document_;
};
// Returns a pointer to the xmlNs on |node| with the |expected_href|, or
// NULL if there isn't one with that href.
static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) {
const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href);
for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) {
if (ns->href && !xmlStrcmp(ns->href, href)) {
return ns;
}
}
return NULL;
}
// Helper function that reads in values for a single <app> tag. It returns a
// boolean indicating success or failure. On failure, it writes a error message
// into |error_detail|.
static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace,
UpdateManifest::Result* result,
std::string *error_detail) {
// Read the extension id.
result->extension_id = GetAttribute(app_node, "appid");
if (result->extension_id.length() == 0) {
*error_detail = "Missing appid on app node";
return false;
}
// Get the updatecheck node.
std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace,
"updatecheck");
if (updates.size() > 1) {
*error_detail = "Too many updatecheck tags on app (expecting only 1).";
return false;
}
if (updates.empty()) {
*error_detail = "Missing updatecheck on app.";
return false;
}
xmlNode *updatecheck = updates[0];
if (GetAttribute(updatecheck, "status") == "noupdate") {
return true;
}
// Find the url to the crx file.
result->crx_url = GURL(GetAttribute(updatecheck, "codebase"));
if (!result->crx_url.is_valid()) {
*error_detail = "Invalid codebase url: '";
*error_detail += result->crx_url.possibly_invalid_spec();
*error_detail += "'.";
return false;
}
// Get the version.
result->version = GetAttribute(updatecheck, "version");
if (result->version.length() == 0) {
*error_detail = "Missing version for updatecheck.";
return false;
}
base::Version version(result->version);
if (!version.IsValid()) {
*error_detail = "Invalid version: '";
*error_detail += result->version;
*error_detail += "'.";
return false;
}
// Get the minimum browser version (not required).
result->browser_min_version = GetAttribute(updatecheck, "prodversionmin");
if (result->browser_min_version.length()) {
base::Version browser_min_version(result->browser_min_version);
if (!browser_min_version.IsValid()) {
*error_detail = "Invalid prodversionmin: '";
*error_detail += result->browser_min_version;
*error_detail += "'.";
return false;
}
}
// package_hash is optional. It is a sha256 hash of the package in hex
// format.
result->package_hash = GetAttribute(updatecheck, "hash_sha256");
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;
}
bool UpdateManifest::Parse(const std::string& manifest_xml) {
results_.list.resize(0);
results_.daystart_elapsed_seconds = kNoDaystart;
errors_ = "";
if (manifest_xml.length() < 1) {
ParseError("Empty xml");
return false;
}
std::string xml_errors;
ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
// Start up the xml parser with the manifest_xml contents.
ScopedXmlDocument document(xmlParseDoc(
reinterpret_cast<const xmlChar*>(manifest_xml.c_str())));
if (!document.get()) {
ParseError("%s", xml_errors.c_str());
return false;
}
xmlNode *root = xmlDocGetRootElement(document.get());
if (!root) {
ParseError("Missing root node");
return false;
}
// Look for the required namespace declaration.
xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
if (!gupdate_ns) {
ParseError("Missing or incorrect xmlns on gupdate tag");
return false;
}
if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
ParseError("Missing gupdate tag");
return false;
}
// Check for the gupdate "protocol" attribute.
if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) {
ParseError("Missing/incorrect protocol on gupdate tag "
"(expected '%s')", kExpectedGupdateProtocol);
return false;
}
// Parse the first <daystart> if it's present.
std::vector<xmlNode*> daystarts = GetChildren(root, gupdate_ns, "daystart");
if (!daystarts.empty()) {
xmlNode* first = daystarts[0];
std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
int parsed_elapsed = kNoDaystart;
if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
results_.daystart_elapsed_seconds = parsed_elapsed;
}
}
// Parse each of the <app> tags.
std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app");
for (unsigned int i = 0; i < apps.size(); i++) {
Result current;
std::string error;
if (!ParseSingleAppTag(apps[i], gupdate_ns, &current, &error)) {
ParseError("%s", error.c_str());
} else {
results_.list.push_back(current);
}
}
return true;
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef EXTENSIONS_COMMON_UPDATE_MANIFEST_H_
#define EXTENSIONS_COMMON_UPDATE_MANIFEST_H_
#include <string>
#include <vector>
#include "base/macros.h"
#include "url/gurl.h"
class UpdateManifest {
public:
// 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" 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>
//
// The <daystart> tag contains a "elapsed_seconds" attribute which refers to
// the server's notion of how many seconds it has been since midnight.
//
// The "appid" attribute of the <app> tag refers to the unique id of the
// extension. The "codebase" attribute of the <updatecheck> tag is the url to
// 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.
// TODO(crbug.com/692120): consider removing struct Result and Results and
// using the corresponding mojo type instead. This would also remove the
// need for the Mojo struct traits that are currently defined / used to
// cart these Result/Results structs over Mojo IPC.
struct Result {
Result();
Result(const Result& other);
~Result();
std::string extension_id;
std::string version;
std::string browser_min_version;
// 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;
struct Results {
Results();
Results(const Results& other);
Results& operator=(const Results& other);
~Results();
std::vector<Result> list;
// This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
int daystart_elapsed_seconds;
};
UpdateManifest();
~UpdateManifest();
// Parses an update manifest xml string into Result data. Returns a bool
// indicating success or failure. On success, the results are available by
// calling results(). The details for any failures are available by calling
// errors().
bool Parse(const std::string& manifest_xml);
const Results& results() { return results_; }
const std::string& errors() { return errors_; }
private:
Results results_;
std::string errors_;
// Helper function that adds parse error details to our errors_ string.
void ParseError(const char* details, ...);
DISALLOW_COPY_AND_ASSIGN(UpdateManifest);
};
#endif // EXTENSIONS_COMMON_UPDATE_MANIFEST_H_
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/update_manifest.h"
#include "testing/gtest/include/gtest/gtest.h"
static const char kValidXml[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
"</gupdate>";
static const char valid_xml_with_hash[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <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_sha256='1234'/>"
" </app>"
"</gupdate>";
static const char kMissingAppId[] =
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' />"
" </app>"
"</gupdate>";
static const char kInvalidCodebase[] =
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345' status='ok'>"
" <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' />"
" </app>"
"</gupdate>";
static const char kMissingVersion[] =
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345' status='ok'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
" </app>"
"</gupdate>";
static const char kInvalidVersion[] =
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345' status='ok'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
" version='1.2.3.a'/>"
" </app>"
"</gupdate>";
static const char kUsesNamespacePrefix[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<g:gupdate xmlns:g='http://www.google.com/update2/response' protocol='2.0'>"
" <g:app appid='12345'>"
" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </g:app>"
"</g:gupdate>";
// Includes unrelated <app> tags from other xml namespaces - this should
// not cause problems.
static const char kSimilarTagnames[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response'"
" xmlns:a='http://a' protocol='2.0'>"
" <a:app/>"
" <b:app xmlns:b='http://b' />"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
"</gupdate>";
// Includes a <daystart> tag.
static const char kWithDaystart[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <daystart elapsed_seconds='456' />"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
"</gupdate>";
// Indicates no updates available - this should not be a parse error.
static const char kNoUpdate[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='12345'>"
" <updatecheck status='noupdate' />"
" </app>"
"</gupdate>";
// Includes two <app> tags, one with an error.
static const char kTwoAppsOneError[] =
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='aaaaaaaa' status='error-unknownApplication'>"
" <updatecheck status='error-unknownapplication'/>"
" </app>"
" <app appid='bbbbbbbb'>"
" <updatecheck codebase='http://example.com/b_3.1.crx' version='3.1'/>"
" </app>"
"</gupdate>";
TEST(ExtensionUpdateManifestTest, TestUpdateManifest) {
UpdateManifest parser;
// Test parsing of a number of invalid xml cases
EXPECT_FALSE(parser.Parse(std::string()));
EXPECT_FALSE(parser.errors().empty());
EXPECT_TRUE(parser.Parse(kMissingAppId));
EXPECT_TRUE(parser.results().list.empty());
EXPECT_FALSE(parser.errors().empty());
EXPECT_TRUE(parser.Parse(kInvalidCodebase));
EXPECT_TRUE(parser.results().list.empty());
EXPECT_FALSE(parser.errors().empty());
EXPECT_TRUE(parser.Parse(kMissingVersion));
EXPECT_TRUE(parser.results().list.empty());
EXPECT_FALSE(parser.errors().empty());
EXPECT_TRUE(parser.Parse(kInvalidVersion));
EXPECT_TRUE(parser.results().list.empty());
EXPECT_FALSE(parser.errors().empty());
// Parse some valid XML, and check that all params came out as expected
EXPECT_TRUE(parser.Parse(kValidXml));
EXPECT_TRUE(parser.errors().empty());
EXPECT_FALSE(parser.results().list.empty());
const UpdateManifest::Result* firstResult = &parser.results().list.at(0);
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
firstResult->crx_url);
EXPECT_EQ("1.2.3.4", firstResult->version);
EXPECT_EQ("2.0.143.0", firstResult->browser_min_version);
// Parse some xml that uses namespace prefixes.
EXPECT_TRUE(parser.Parse(kUsesNamespacePrefix));
EXPECT_TRUE(parser.errors().empty());
EXPECT_TRUE(parser.Parse(kSimilarTagnames));
EXPECT_TRUE(parser.errors().empty());
// Parse xml with hash value
EXPECT_TRUE(parser.Parse(valid_xml_with_hash));
EXPECT_TRUE(parser.errors().empty());
EXPECT_FALSE(parser.results().list.empty());
firstResult = &parser.results().list.at(0);
EXPECT_EQ("1234", firstResult->package_hash);
// Parse xml with a <daystart> element.
EXPECT_TRUE(parser.Parse(kWithDaystart));
EXPECT_TRUE(parser.errors().empty());
EXPECT_FALSE(parser.results().list.empty());
EXPECT_EQ(parser.results().daystart_elapsed_seconds, 456);
// Parse a no-update response.
EXPECT_TRUE(parser.Parse(kNoUpdate));
EXPECT_TRUE(parser.errors().empty());
EXPECT_FALSE(parser.results().list.empty());
firstResult = &parser.results().list.at(0);
EXPECT_EQ(firstResult->extension_id, "12345");
EXPECT_EQ(firstResult->version, "");
// Parse xml with one error and one success <app> tag.
EXPECT_TRUE(parser.Parse(kTwoAppsOneError));
EXPECT_FALSE(parser.errors().empty());
EXPECT_EQ(1u, parser.results().list.size());
firstResult = &parser.results().list.at(0);
EXPECT_EQ(firstResult->extension_id, "bbbbbbbb");
}
......@@ -19,9 +19,6 @@
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/feature_session_type.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_parser.mojom.h"
#include "extensions/common/update_manifest.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "extensions/utility/unpacker.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
......@@ -167,30 +164,6 @@ class ExtensionUnpackerImpl : public extensions::mojom::ExtensionUnpacker {
DISALLOW_COPY_AND_ASSIGN(ExtensionUnpackerImpl);
};
class ManifestParserImpl : public extensions::mojom::ManifestParser {
public:
ManifestParserImpl() = default;
~ManifestParserImpl() override = default;
static void Create(extensions::mojom::ManifestParserRequest request) {
mojo::MakeStrongBinding(std::make_unique<ManifestParserImpl>(),
std::move(request));
}
private:
void Parse(const std::string& xml, ParseCallback callback) override {
UpdateManifest manifest;
if (manifest.Parse(xml)) {
std::move(callback).Run(manifest.results());
} else {
LOG(WARNING) << "Error parsing update manifest:\n" << manifest.errors();
std::move(callback).Run(base::nullopt);
}
}
DISALLOW_COPY_AND_ASSIGN(ManifestParserImpl);
};
} // namespace
namespace utility_handler {
......@@ -211,8 +184,6 @@ void ExposeInterfacesToBrowser(service_manager::BinderRegistry* registry,
registry->AddInterface(base::Bind(&ExtensionUnpackerImpl::Create),
base::ThreadTaskRunnerHandle::Get());
registry->AddInterface(base::Bind(&ManifestParserImpl::Create),
base::ThreadTaskRunnerHandle::Get());
}
} // namespace utility_handler
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment