| // 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 |