blob: b3a1a878d49a560a13c436b1a4426f05c19add46 [file]
// Copyright 2022 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 "chrome/updater/win/protocol_parser_xml.h"
// clang-format off
#include <objbase.h>
#include <msxml2.h>
// clang-format on
#include <wrl/client.h>
#include <cstdint>
#include <string>
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/strings/string_number_conversions_win.h"
#include "base/strings/sys_string_conversions.h"
#include "base/version.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_variant.h"
#include "components/update_client/protocol_definition.h"
#include "url/gurl.h"
namespace updater {
namespace {
bool ReadAttribute(IXMLDOMNode* node,
const std::wstring& attribute_name,
BSTR* value) {
Microsoft::WRL::ComPtr<IXMLDOMNamedNodeMap> attributes;
HRESULT hr = node->get_attributes(&attributes);
if (FAILED(hr) || !attributes) {
return false;
}
Microsoft::WRL::ComPtr<IXMLDOMNode> attribute_node;
base::win::ScopedVariant node_value;
base::win::ScopedBstr attr_name(attribute_name);
hr = attributes->getNamedItem(attr_name.Get(), &attribute_node);
if (FAILED(hr) || !attribute_node) {
return false;
}
hr = attribute_node->get_nodeValue(node_value.Receive());
if (FAILED(hr) || node_value.type() == VT_EMPTY) {
return false;
}
DCHECK_EQ(node_value.type(), VT_BSTR);
VARIANT released_variant = node_value.Release();
*value = V_BSTR(&released_variant);
return true;
}
bool ReadInt64Attribute(IXMLDOMNode* node,
const std::wstring& attribute_name,
int64_t* value) {
base::win::ScopedBstr node_value;
return ReadAttribute(node, attribute_name, node_value.Receive()) &&
base::StringToInt64(node_value.Get(), value);
}
bool ReadStringAttribute(IXMLDOMNode* node,
const std::wstring& attribute_name,
std::string* value) {
base::win::ScopedBstr node_value;
if (!ReadAttribute(node, attribute_name, node_value.Receive())) {
return false;
}
*value = base::SysWideToUTF8(node_value.Get());
return true;
}
} // namespace
bool ProtocolParserXML::ParseAction(IXMLDOMNode* node, Results* results) {
if (!results->list.back().manifest.run.empty()) {
// All actions except the first one are ignored.
return true;
}
if (!ReadStringAttribute(node, L"run", &results->list.back().manifest.run) ||
results->list.back().manifest.run.empty()) {
ParseError("Missing `run` attribute in <action>");
return false;
}
ReadStringAttribute(node, L"arguments",
&results->list.back().manifest.arguments);
return true;
}
bool ProtocolParserXML::ParseActions(IXMLDOMNode* node, Results* results) {
return ParseChildren(
{
{L"action", &ProtocolParserXML::ParseAction},
},
node, results);
}
bool ProtocolParserXML::ParseApp(IXMLDOMNode* node, Results* results) {
Result result;
if (!ReadStringAttribute(node, L"appid", &result.extension_id)) {
ParseError("Missing `appid` attribute in <app>");
return false;
}
if (!ReadStringAttribute(node, L"status", &result.status)) {
ParseError("Missing `status` attribute in <app>");
return false;
}
if (result.status != "ok") {
ParseError("Unexpected app status: %s", result.status.c_str());
return false;
}
results->list.push_back(result);
return ParseChildren(
{
{L"data", &ProtocolParserXML::ParseData},
{L"updatecheck", &ProtocolParserXML::ParseUpdateCheck},
},
node, results);
}
bool ProtocolParserXML::ParseData(IXMLDOMNode* node, Results* results) {
ProtocolParser::Result::Data data;
if (!ReadStringAttribute(node, L"status", &data.status)) {
ParseError("Missing `status` attribute in <data>");
return false;
}
if (!ReadStringAttribute(node, L"index", &data.install_data_index)) {
ParseError("Missing `index` attribute in <data>");
return false;
}
if (!ReadStringAttribute(node, L"name", &data.name)) {
ParseError("Missing `name` attribute in <data>");
return false;
}
base::win::ScopedBstr text;
HRESULT hr = node->get_text(text.Receive());
if (FAILED(hr)) {
ParseError("Failed to get text from <data>: 0x%x", hr);
return false;
}
data.text = base::SysWideToUTF8(text.Get());
results->list.back().data.push_back(data);
return true;
}
bool ProtocolParserXML::ParseManifest(IXMLDOMNode* node, Results* results) {
std::string version;
if (!ReadStringAttribute(node, L"version", &version) ||
!base::Version(version).IsValid()) {
ParseError("Bad `version` attribute in <manifest>: %s", version.c_str());
return false;
}
results->list.back().manifest.version = version;
return ParseChildren(
{
{L"actions", &ProtocolParserXML::ParseActions},
{L"packages", &ProtocolParserXML::ParsePackages},
},
node, results);
}
bool ProtocolParserXML::ParsePackage(IXMLDOMNode* node, Results* results) {
ProtocolParser::Result::Manifest::Package p;
if (!ReadStringAttribute(node, L"name", &p.name) || p.name.empty()) {
ParseError("Missing `name` attribute in <package>");
return false;
}
if (!ReadStringAttribute(node, L"hash_sha256", &p.hash_sha256)) {
ParseError("Missing `hash_sha256` attribute in <package>");
return false;
}
if (!ReadInt64Attribute(node, L"size", &p.size)) {
ParseError("Missing `size` attribute in <package>");
return false;
}
ReadStringAttribute(node, L"fp", &p.fingerprint);
results->list.back().manifest.packages.push_back(p);
return true;
}
bool ProtocolParserXML::ParsePackages(IXMLDOMNode* node, Results* results) {
return ParseChildren(
{
{L"package", &ProtocolParserXML::ParsePackage},
},
node, results);
}
bool ProtocolParserXML::ParseResponse(IXMLDOMNode* node, Results* results) {
return ParseChildren(
{
{L"app", &ProtocolParserXML::ParseApp},
{L"systemrequirements", &ProtocolParserXML::ParseSystemRequirements},
},
node, results);
}
bool ProtocolParserXML::ParseSystemRequirements(IXMLDOMNode* node,
Results* results) {
return true;
}
bool ProtocolParserXML::ParseUpdateCheck(IXMLDOMNode* node, Results* results) {
Result& result = results->list.back();
if (!ReadStringAttribute(node, L"status", &result.status)) {
ParseError("Missing `status` attribute in <updatecheck>");
return false;
}
const ElementHandlerMap child_handlers = {
{L"manifest", &ProtocolParserXML::ParseManifest},
{L"urls", &ProtocolParserXML::ParseUrls},
};
return ParseChildren(child_handlers, node, results);
}
bool ProtocolParserXML::ParseUrl(IXMLDOMNode* node, Results* results) {
std::string url_string;
if (!ReadStringAttribute(node, L"codebase", &url_string)) {
ParseError("Missing `codebase` attribute in <url>");
return false;
}
GURL url(url_string);
if (!url.is_valid()) {
ParseError("Invalid URL codebase in <url>: %s", url_string.c_str());
return false;
}
results->list.back().crx_urls.push_back(url);
return true;
}
bool ProtocolParserXML::ParseUrls(IXMLDOMNode* node, Results* results) {
return ParseChildren(
{
{L"url", &ProtocolParserXML::ParseUrl},
},
node, results);
}
bool ProtocolParserXML::ParseElement(const ElementHandlerMap& handler_map,
IXMLDOMNode* node,
Results* results) {
base::win::ScopedBstr basename;
if (FAILED(node->get_baseName(basename.Receive()))) {
ParseError("Failed to get name for a DOM element.");
return false;
}
for (const auto& handler : handler_map) {
if (handler.first == basename.Get()) {
return (this->*handler.second)(node, results);
}
}
ParseError("Unrecognized element: %s",
base::SysWideToUTF8(basename.Get()).c_str());
return false;
}
bool ProtocolParserXML::ParseChildren(const ElementHandlerMap& handler_map,
IXMLDOMNode* node,
Results* results) {
Microsoft::WRL::ComPtr<IXMLDOMNodeList> children_list;
HRESULT hr = node->get_childNodes(&children_list);
if (FAILED(hr)) {
ParseError("Failed to get child elements: 0x%x", hr);
return false;
}
long num_children = 0;
hr = children_list->get_length(&num_children);
if (FAILED(hr)) {
ParseError("Failed to get the number of child elements: 0x%x", hr);
return false;
}
for (long i = 0; i < num_children; ++i) {
Microsoft::WRL::ComPtr<IXMLDOMNode> child_node;
hr = children_list->get_item(i, &child_node);
if (FAILED(hr)) {
ParseError("Failed to get %d-th child element: 0x%x", i, hr);
return false;
}
DOMNodeType type = NODE_INVALID;
hr = child_node->get_nodeType(&type);
if (FAILED(hr)) {
ParseError("Failed to get %d-th child element type: 0x%x", i, hr);
return false;
}
if (type == NODE_ELEMENT &&
!ParseElement(handler_map, child_node.Get(), results)) {
return false;
}
}
return true;
}
bool ProtocolParserXML::DoParse(const std::string& response_xml,
Results* results) {
DCHECK(results);
Microsoft::WRL::ComPtr<IXMLDOMDocument> xmldoc;
HRESULT hr = ::CoCreateInstance(CLSID_DOMDocument30, nullptr, CLSCTX_ALL,
IID_IXMLDOMDocument, &xmldoc);
if (FAILED(hr)) {
ParseError("IXMLDOMDocument.CoCreateInstance failed: 0x%x", hr);
return false;
}
hr = xmldoc->put_resolveExternals(VARIANT_FALSE);
if (FAILED(hr)) {
ParseError("IXMLDOMDocument.put_resolveExternals failed: 0x%x", hr);
return false;
}
VARIANT_BOOL is_successful(VARIANT_FALSE);
auto xml_bstr =
base::win::ScopedBstr(base::SysUTF8ToWide(response_xml).c_str());
hr = xmldoc->loadXML(xml_bstr.Get(), &is_successful);
if (FAILED(hr)) {
ParseError("Load maniftest failed: 0x%x", hr);
return false;
}
Microsoft::WRL::ComPtr<IXMLDOMElement> root_node;
hr = xmldoc->get_documentElement(&root_node);
if (FAILED(hr) || !root_node) {
ParseError("Load maniftest failed: 0x%x", hr);
return false;
}
return ParseElement(
{
{L"response", &ProtocolParserXML::ParseResponse},
},
root_node.Get(), results);
}
} // namespace updater