blob: c09262d444d8fae2b6e03d52d07046340beea580 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/updater/safe_manifest_parser.h"
#include <memory>
#include "base/bind.h"
#include "base/metrics/histogram_macros.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 "services/data_decoder/public/cpp/data_decoder.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 {
constexpr char kExpectedGupdateProtocol[] = "2.0";
constexpr char kExpectedGupdateXmlns[] =
"http://www.google.com/update2/response";
void ReportError(ParseUpdateManifestCallback callback,
const ManifestParseFailure& failure) {
std::move(callback).Run(/*results=*/nullptr, failure);
}
// 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 |result->parse_error|.
bool ParseSingleAppTag(const base::Value& app_element,
const std::string& xml_namespace,
UpdateManifestResult* result,
int* prodversionmin_count) {
// Read the extension id.
result->extension_id = GetXmlElementAttribute(app_element, "appid");
if (result->extension_id.empty()) {
result->parse_error =
ManifestParseFailure(std::move("Missing appid on app node"),
ManifestInvalidError::MISSING_APP_ID);
return false;
}
// Get the app status.
result->app_status = GetXmlElementAttribute(app_element, "status");
if (!result->app_status.empty() && result->app_status != "ok") {
result->parse_error = ManifestParseFailure(
"App status is not OK", ManifestInvalidError::BAD_APP_STATUS);
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) {
result->parse_error = ManifestParseFailure(
updatecheck_count == 0
? std::move("Missing updatecheck on app.")
: std::move("Too many updatecheck tags on app (expecting only 1)."),
updatecheck_count == 0
? ManifestInvalidError::MISSING_UPDATE_CHECK_TAGS
: ManifestInvalidError::MULTIPLE_UPDATE_CHECK_TAGS);
return false;
}
const base::Value* updatecheck =
data_decoder::GetXmlElementChildWithTag(app_element, updatecheck_name);
result->status = GetXmlElementAttribute(*updatecheck, "status");
if (result->status == "noupdate") {
result->info = GetXmlElementAttribute(*updatecheck, "info");
return true;
}
// Get the optional minimum browser version.
result->browser_min_version =
GetXmlElementAttribute(*updatecheck, "prodversionmin");
if (!result->browser_min_version.empty()) {
*prodversionmin_count += 1;
base::Version browser_min_version(result->browser_min_version);
if (!browser_min_version.IsValid()) {
std::string error_detail;
error_detail = "Invalid prodversionmin: '";
error_detail += result->browser_min_version;
error_detail += "'.";
result->parse_error =
ManifestParseFailure(std::move(error_detail),
ManifestInvalidError::INVALID_PRODVERSION_MIN);
return false;
}
}
// Find the url to the crx file.
result->crx_url = GURL(GetXmlElementAttribute(*updatecheck, "codebase"));
if (!result->crx_url.is_valid()) {
if (result->crx_url.is_empty()) {
result->parse_error =
ManifestParseFailure(std::move("Empty codebase url."),
ManifestInvalidError::EMPTY_CODEBASE_URL);
return false;
}
std::string error_detail;
error_detail = "Invalid codebase url: '";
error_detail += result->crx_url.possibly_invalid_spec();
error_detail += "'.";
result->parse_error = ManifestParseFailure(
std::move(error_detail), ManifestInvalidError::INVALID_CODEBASE_URL);
return false;
}
// Get the version.
result->version = GetXmlElementAttribute(*updatecheck, "version");
if (result->version.empty()) {
result->parse_error = ManifestParseFailure(
std::move("Missing version for updatecheck."),
ManifestInvalidError::MISSING_VERSION_FOR_UPDATE_CHECK);
return false;
}
base::Version version(result->version);
if (!version.IsValid()) {
std::string error_detail;
error_detail = "Invalid version: '";
error_detail += result->version;
error_detail += "'.";
result->parse_error = ManifestParseFailure(
std::move(error_detail), ManifestInvalidError::INVALID_VERSION);
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,
data_decoder::DataDecoder::ValueOrError result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!result.value) {
ManifestParseFailure failure("Failed to parse XML: " + *result.error,
ManifestInvalidError::XML_PARSING_FAILED);
ReportError(std::move(callback), failure);
return;
}
auto results = std::make_unique<UpdateManifestResults>();
base::Value& root = *result.value;
// Look for the required namespace declaration.
std::string gupdate_ns;
if (!GetXmlElementNamespacePrefix(root, kExpectedGupdateXmlns, &gupdate_ns)) {
ManifestParseFailure failure(
"Missing or incorrect xmlns on gupdate tag",
ManifestInvalidError::INVALID_XLMNS_ON_GUPDATE_TAG);
ReportError(std::move(callback), failure);
return;
}
if (!IsXmlElementNamed(root, GetXmlQualifiedName(gupdate_ns, "gupdate"))) {
ManifestParseFailure failure("Missing gupdate tag",
ManifestInvalidError::MISSING_GUPDATE_TAG);
ReportError(std::move(callback), failure);
return;
}
// Check for the gupdate "protocol" attribute.
if (GetXmlElementAttribute(root, "protocol") != kExpectedGupdateProtocol) {
ManifestParseFailure failure(
std::string("Missing/incorrect protocol on gupdate tag "
"(expected '") +
kExpectedGupdateProtocol + "')",
ManifestInvalidError::INVALID_PROTOCOL_ON_GUPDATE_TAG);
ReportError(std::move(callback), failure);
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;
int prodversionmin_count = 0;
for (const auto* app : apps) {
UpdateManifestResult result;
ParseSingleAppTag(*app, gupdate_ns, &result, &prodversionmin_count);
results->update_list.push_back(result);
}
// Parsing error corresponding to each extension are stored in the results.
std::move(callback).Run(std::move(results), base::nullopt);
}
} // namespace
ManifestParseFailure::ManifestParseFailure() = default;
ManifestParseFailure::ManifestParseFailure(const ManifestParseFailure& other) =
default;
ManifestParseFailure::ManifestParseFailure(std::string error_detail,
ManifestInvalidError error)
: error_detail(std::move(error_detail)), error(error) {}
ManifestParseFailure::~ManifestParseFailure() = default;
UpdateManifestResult::UpdateManifestResult() = default;
UpdateManifestResult::UpdateManifestResult(const UpdateManifestResult& other) =
default;
UpdateManifestResult::~UpdateManifestResult() = default;
UpdateManifestResults::UpdateManifestResults() = default;
UpdateManifestResults::UpdateManifestResults(
const UpdateManifestResults& other) = default;
UpdateManifestResults::~UpdateManifestResults() = default;
std::map<std::string, std::vector<const UpdateManifestResult*>>
UpdateManifestResults::GroupSuccessfulByID() const {
std::map<std::string, std::vector<const UpdateManifestResult*>> groups;
for (const UpdateManifestResult& update_result : update_list) {
if (!update_result.parse_error)
groups[update_result.extension_id].push_back(&update_result);
}
return groups;
}
void ParseUpdateManifest(const std::string& xml,
ParseUpdateManifestCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(callback);
data_decoder::DataDecoder::ParseXmlIsolated(
xml, base::BindOnce(&ParseXmlDone, std::move(callback)));
}
} // namespace extensions