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