blob: f8c71b8f2d17117e95a90b2201754a3804999dd4 [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/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 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,
int* prodversionmin_count) {
// 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);
if (GetXmlElementAttribute(*updatecheck, "status") == "noupdate")
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()) {
*error_detail = "Invalid prodversionmin: '";
*error_detail += result->browser_min_version;
*error_detail += "'.";
return false;
}
}
// 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;
}
// 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);
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;
int prodversionmin_count = 0;
for (const auto* app : apps) {
UpdateManifestResult result;
std::string app_error;
if (!ParseSingleAppTag(*app, gupdate_ns, &result, &app_error,
&prodversionmin_count)) {
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);
}
}
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateManifestHasProdVersionMinCounts",
prodversionmin_count);
std::move(callback).Run(
results->list.empty() ? nullptr : std::move(results),
error_msg.empty() ? base::Optional<std::string>() : error_msg);
}
} // namespace
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::GroupByID() const {
std::map<std::string, std::vector<const UpdateManifestResult*>> groups;
for (const UpdateManifestResult& update_result : list) {
groups[update_result.extension_id].push_back(&update_result);
}
return groups;
}
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