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