| //===-- WindowsManifestMerger.cpp ------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===---------------------------------------------------------------------===// | |
| // | |
| // This file implements the .manifest merger class. | |
| // | |
| //===---------------------------------------------------------------------===// | |
| #include "llvm/WindowsManifest/WindowsManifestMerger.h" | |
| #include "llvm/Config/config.h" | |
| #include "llvm/Support/MemoryBuffer.h" | |
| #if LLVM_ENABLE_LIBXML2 | |
| #include <libxml/xmlreader.h> | |
| #endif | |
| #define TO_XML_CHAR(X) reinterpret_cast<const unsigned char *>(X) | |
| #define FROM_XML_CHAR(X) reinterpret_cast<const char *>(X) | |
| using namespace llvm; | |
| using namespace windows_manifest; | |
| char WindowsManifestError::ID = 0; | |
| WindowsManifestError::WindowsManifestError(const Twine &Msg) : Msg(Msg.str()) {} | |
| void WindowsManifestError::log(raw_ostream &OS) const { OS << Msg; } | |
| class WindowsManifestMerger::WindowsManifestMergerImpl { | |
| public: | |
| ~WindowsManifestMergerImpl(); | |
| Error merge(MemoryBufferRef Manifest); | |
| std::unique_ptr<MemoryBuffer> getMergedManifest(); | |
| private: | |
| static void errorCallback(void *Ctx, const char *Format, ...); | |
| Error getParseError(); | |
| #if LLVM_ENABLE_LIBXML2 | |
| xmlDocPtr CombinedDoc = nullptr; | |
| std::vector<xmlDocPtr> MergedDocs; | |
| bool Merged = false; | |
| struct XmlDeleter { | |
| void operator()(xmlChar *Ptr) { xmlFree(Ptr); } | |
| void operator()(xmlDoc *Ptr) { xmlFreeDoc(Ptr); } | |
| }; | |
| int BufferSize = 0; | |
| std::unique_ptr<xmlChar, XmlDeleter> Buffer; | |
| #endif | |
| bool ParseErrorOccurred = false; | |
| }; | |
| #if LLVM_ENABLE_LIBXML2 | |
| static constexpr std::pair<StringLiteral, StringLiteral> MtNsHrefsPrefixes[] = { | |
| {"urn:schemas-microsoft-com:asm.v1", "ms_asmv1"}, | |
| {"urn:schemas-microsoft-com:asm.v2", "ms_asmv2"}, | |
| {"urn:schemas-microsoft-com:asm.v3", "ms_asmv3"}, | |
| {"http://schemas.microsoft.com/SMI/2005/WindowsSettings", | |
| "ms_windowsSettings"}, | |
| {"urn:schemas-microsoft-com:compatibility.v1", "ms_compatibilityv1"}}; | |
| static bool xmlStringsEqual(const unsigned char *A, const unsigned char *B) { | |
| // Handle null pointers. Comparison of 2 null pointers returns true because | |
| // this indicates the prefix of a default namespace. | |
| if (!A || !B) | |
| return A == B; | |
| return strcmp(FROM_XML_CHAR(A), FROM_XML_CHAR(B)) == 0; | |
| } | |
| static bool isMergeableElement(const unsigned char *ElementName) { | |
| for (StringRef S : {"application", "assembly", "assemblyIdentity", | |
| "compatibility", "noInherit", "requestedExecutionLevel", | |
| "requestedPrivileges", "security", "trustInfo"}) { | |
| if (S == FROM_XML_CHAR(ElementName)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| static xmlNodePtr getChildWithName(xmlNodePtr Parent, | |
| const unsigned char *ElementName) { | |
| for (xmlNodePtr Child = Parent->children; Child; Child = Child->next) { | |
| if (xmlStringsEqual(Child->name, ElementName)) { | |
| return Child; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| static xmlAttrPtr getAttribute(xmlNodePtr Node, | |
| const unsigned char *AttributeName) { | |
| for (xmlAttrPtr Attribute = Node->properties; Attribute != nullptr; | |
| Attribute = Attribute->next) { | |
| if (xmlStringsEqual(Attribute->name, AttributeName)) { | |
| return Attribute; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| // Check if namespace specified by HRef1 overrides that of HRef2. | |
| static bool namespaceOverrides(const unsigned char *HRef1, | |
| const unsigned char *HRef2) { | |
| auto HRef1Position = llvm::find_if( | |
| MtNsHrefsPrefixes, [=](const std::pair<StringRef, StringRef> &Element) { | |
| return xmlStringsEqual(HRef1, TO_XML_CHAR(Element.first.data())); | |
| }); | |
| auto HRef2Position = llvm::find_if( | |
| MtNsHrefsPrefixes, [=](const std::pair<StringRef, StringRef> &Element) { | |
| return xmlStringsEqual(HRef2, TO_XML_CHAR(Element.first.data())); | |
| }); | |
| return HRef1Position < HRef2Position; | |
| } | |
| // Search for prefix-defined namespace specified by HRef, starting on Node and | |
| // continuing recursively upwards. Returns the namespace or nullptr if not | |
| // found. | |
| static xmlNsPtr search(const unsigned char *HRef, xmlNodePtr Node) { | |
| for (xmlNsPtr Def = Node->nsDef; Def; Def = Def->next) { | |
| if (Def->prefix && xmlStringsEqual(Def->href, HRef)) { | |
| return Def; | |
| } | |
| } | |
| if (Node->parent) { | |
| return search(HRef, Node->parent); | |
| } | |
| return nullptr; | |
| } | |
| // Return the prefix that corresponds to the HRef. If HRef is not a recognized | |
| // URI, then just return the HRef itself to use as the prefix. | |
| static const unsigned char *getPrefixForHref(const unsigned char *HRef) { | |
| for (auto &Ns : MtNsHrefsPrefixes) { | |
| if (xmlStringsEqual(HRef, TO_XML_CHAR(Ns.first.data()))) { | |
| return TO_XML_CHAR(Ns.second.data()); | |
| } | |
| } | |
| return HRef; | |
| } | |
| // Search for prefix-defined namespace specified by HRef, starting on Node and | |
| // continuing recursively upwards. If it is found, then return it. If it is | |
| // not found, then prefix-define that namespace on the node and return a | |
| // reference to it. | |
| static Expected<xmlNsPtr> searchOrDefine(const unsigned char *HRef, | |
| xmlNodePtr Node) { | |
| if (xmlNsPtr Def = search(HRef, Node)) | |
| return Def; | |
| if (xmlNsPtr Def = xmlNewNs(Node, HRef, getPrefixForHref(HRef))) | |
| return Def; | |
| return make_error<WindowsManifestError>("failed to create new namespace"); | |
| } | |
| // Set the namespace of OrigionalAttribute on OriginalNode to be that of | |
| // AdditionalAttribute's. | |
| static Error copyAttributeNamespace(xmlAttrPtr OriginalAttribute, | |
| xmlNodePtr OriginalNode, | |
| xmlAttrPtr AdditionalAttribute) { | |
| Expected<xmlNsPtr> ExplicitOrError = | |
| searchOrDefine(AdditionalAttribute->ns->href, OriginalNode); | |
| if (!ExplicitOrError) | |
| return ExplicitOrError.takeError(); | |
| OriginalAttribute->ns = std::move(ExplicitOrError.get()); | |
| return Error::success(); | |
| } | |
| // Return the corresponding namespace definition for the prefix, defined on the | |
| // given Node. Returns nullptr if there is no such definition. | |
| static xmlNsPtr getNamespaceWithPrefix(const unsigned char *Prefix, | |
| xmlNodePtr Node) { | |
| if (Node == nullptr) | |
| return nullptr; | |
| for (xmlNsPtr Def = Node->nsDef; Def; Def = Def->next) { | |
| if (xmlStringsEqual(Def->prefix, Prefix)) { | |
| return Def; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| // Search for the closest inheritable default namespace, starting on (and | |
| // including) the Node and traveling upwards through parent nodes. Returns | |
| // nullptr if there are no inheritable default namespaces. | |
| static xmlNsPtr getClosestDefault(xmlNodePtr Node) { | |
| if (xmlNsPtr Ret = getNamespaceWithPrefix(nullptr, Node)) | |
| return Ret; | |
| if (Node->parent == nullptr) | |
| return nullptr; | |
| return getClosestDefault(Node->parent); | |
| } | |
| // Merge the attributes of AdditionalNode into OriginalNode. If attributes | |
| // with identical types are present, they are not duplicated but rather if | |
| // their values are not consistent and error is thrown. In addition, the | |
| // higher priority namespace is used for each attribute, EXCEPT in the case | |
| // of merging two default namespaces and the lower priority namespace | |
| // definition occurs closer than the higher priority one. | |
| static Error mergeAttributes(xmlNodePtr OriginalNode, | |
| xmlNodePtr AdditionalNode) { | |
| xmlNsPtr ClosestDefault = getClosestDefault(OriginalNode); | |
| for (xmlAttrPtr Attribute = AdditionalNode->properties; Attribute; | |
| Attribute = Attribute->next) { | |
| if (xmlAttrPtr OriginalAttribute = | |
| getAttribute(OriginalNode, Attribute->name)) { | |
| if (!xmlStringsEqual(OriginalAttribute->children->content, | |
| Attribute->children->content)) { | |
| return make_error<WindowsManifestError>( | |
| Twine("conflicting attributes for ") + | |
| FROM_XML_CHAR(OriginalNode->name)); | |
| } | |
| if (!Attribute->ns) { | |
| continue; | |
| } | |
| if (!OriginalAttribute->ns) { | |
| if (auto E = copyAttributeNamespace(OriginalAttribute, OriginalNode, | |
| Attribute)) { | |
| return E; | |
| } | |
| continue; | |
| } | |
| if (namespaceOverrides(OriginalAttribute->ns->href, | |
| Attribute->ns->href)) { | |
| // In this case, the original attribute has a higher priority namespace | |
| // than the incomiing attribute, however the namespace definition of | |
| // the lower priority namespace occurs first traveling upwards in the | |
| // tree. Therefore the lower priority namespace is applied. | |
| if (!OriginalAttribute->ns->prefix && !Attribute->ns->prefix && | |
| ClosestDefault && | |
| xmlStringsEqual(Attribute->ns->href, ClosestDefault->href)) { | |
| if (auto E = copyAttributeNamespace(OriginalAttribute, OriginalNode, | |
| Attribute)) { | |
| return E; | |
| } | |
| continue; | |
| } | |
| continue; | |
| // This covers the case where the incoming attribute has the higher | |
| // priority. The higher priority namespace is applied in all cases | |
| // EXCEPT when both of the namespaces are default inherited, and the | |
| // closest inherited default is the lower priority one. | |
| } | |
| if (Attribute->ns->prefix || OriginalAttribute->ns->prefix || | |
| (ClosestDefault && !xmlStringsEqual(OriginalAttribute->ns->href, | |
| ClosestDefault->href))) { | |
| if (auto E = copyAttributeNamespace(OriginalAttribute, OriginalNode, | |
| Attribute)) { | |
| return E; | |
| } | |
| continue; | |
| } | |
| continue; | |
| } | |
| // If the incoming attribute is not already found on the node, append it | |
| // to the end of the properties list. Also explicitly apply its | |
| // namespace as a prefix because it might be contained in a separate | |
| // namespace that doesn't use the attribute. | |
| xmlAttrPtr NewProp = | |
| xmlNewProp(OriginalNode, Attribute->name, Attribute->children->content); | |
| Expected<xmlNsPtr> ExplicitOrError = | |
| searchOrDefine(Attribute->ns->href, OriginalNode); | |
| if (!ExplicitOrError) | |
| return ExplicitOrError.takeError(); | |
| NewProp->ns = std::move(ExplicitOrError.get()); | |
| } | |
| return Error::success(); | |
| } | |
| // Given two nodes, return the one with the higher priority namespace. | |
| static xmlNodePtr getDominantNode(xmlNodePtr Node1, xmlNodePtr Node2) { | |
| if (!Node1 || !Node1->ns) | |
| return Node2; | |
| if (!Node2 || !Node2->ns) | |
| return Node1; | |
| if (namespaceOverrides(Node1->ns->href, Node2->ns->href)) | |
| return Node1; | |
| return Node2; | |
| } | |
| // Checks if this Node's namespace is inherited or one it defined itself. | |
| static bool hasInheritedNs(xmlNodePtr Node) { | |
| return Node->ns && Node->ns != getNamespaceWithPrefix(Node->ns->prefix, Node); | |
| } | |
| // Check if this Node's namespace is a default namespace that it inherited, as | |
| // opposed to defining itself. | |
| static bool hasInheritedDefaultNs(xmlNodePtr Node) { | |
| return hasInheritedNs(Node) && Node->ns->prefix == nullptr; | |
| } | |
| // Check if this Node's namespace is a default namespace it defined itself. | |
| static bool hasDefinedDefaultNamespace(xmlNodePtr Node) { | |
| return Node->ns && (Node->ns == getNamespaceWithPrefix(nullptr, Node)); | |
| } | |
| // For the given explicit prefix-definition of a namespace, travel downwards | |
| // from a node recursively, and for every implicit, inherited default usage of | |
| // that namespace replace it with that explicit prefix use. This is important | |
| // when namespace overriding occurs when merging, so that elements unique to a | |
| // namespace will still stay in that namespace. | |
| static void explicateNamespace(xmlNsPtr PrefixDef, xmlNodePtr Node) { | |
| // If a node as its own default namespace definition it clearly cannot have | |
| // inherited the given default namespace, and neither will any of its | |
| // children. | |
| if (hasDefinedDefaultNamespace(Node)) | |
| return; | |
| if (Node->ns && xmlStringsEqual(Node->ns->href, PrefixDef->href) && | |
| hasInheritedDefaultNs(Node)) | |
| Node->ns = PrefixDef; | |
| for (xmlAttrPtr Attribute = Node->properties; Attribute; | |
| Attribute = Attribute->next) { | |
| if (Attribute->ns && | |
| xmlStringsEqual(Attribute->ns->href, PrefixDef->href)) { | |
| Attribute->ns = PrefixDef; | |
| } | |
| } | |
| for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { | |
| explicateNamespace(PrefixDef, Child); | |
| } | |
| } | |
| // Perform the namespace merge between two nodes. | |
| static Error mergeNamespaces(xmlNodePtr OriginalNode, | |
| xmlNodePtr AdditionalNode) { | |
| // Save the original default namespace definition in case the incoming node | |
| // overrides it. | |
| const unsigned char *OriginalDefinedDefaultHref = nullptr; | |
| if (xmlNsPtr OriginalDefinedDefaultNs = | |
| getNamespaceWithPrefix(nullptr, OriginalNode)) { | |
| OriginalDefinedDefaultHref = xmlStrdup(OriginalDefinedDefaultNs->href); | |
| } | |
| const unsigned char *NewDefinedDefaultHref = nullptr; | |
| // Copy all namespace definitions. There can only be one default namespace | |
| // definition per node, so the higher priority one takes precedence in the | |
| // case of collision. | |
| for (xmlNsPtr Def = AdditionalNode->nsDef; Def; Def = Def->next) { | |
| if (xmlNsPtr OriginalNsDef = | |
| getNamespaceWithPrefix(Def->prefix, OriginalNode)) { | |
| if (!Def->prefix) { | |
| if (namespaceOverrides(Def->href, OriginalNsDef->href)) { | |
| NewDefinedDefaultHref = TO_XML_CHAR(strdup(FROM_XML_CHAR(Def->href))); | |
| } | |
| } else if (!xmlStringsEqual(OriginalNsDef->href, Def->href)) { | |
| return make_error<WindowsManifestError>( | |
| Twine("conflicting namespace definitions for ") + | |
| FROM_XML_CHAR(Def->prefix)); | |
| } | |
| } else { | |
| xmlNsPtr NewDef = xmlCopyNamespace(Def); | |
| NewDef->next = OriginalNode->nsDef; | |
| OriginalNode->nsDef = NewDef; | |
| } | |
| } | |
| // Check whether the original node or the incoming node has the higher | |
| // priority namespace. Depending on which one is dominant, we will have | |
| // to recursively apply namespace changes down to children of the original | |
| // node. | |
| xmlNodePtr DominantNode = getDominantNode(OriginalNode, AdditionalNode); | |
| xmlNodePtr NonDominantNode = | |
| DominantNode == OriginalNode ? AdditionalNode : OriginalNode; | |
| if (DominantNode == OriginalNode) { | |
| if (OriginalDefinedDefaultHref) { | |
| xmlNsPtr NonDominantDefinedDefault = | |
| getNamespaceWithPrefix(nullptr, NonDominantNode); | |
| // In this case, both the nodes defined a default namespace. However | |
| // the lower priority node ended up having a higher priority default | |
| // definition. This can occur if the higher priority node is prefix | |
| // namespace defined. In this case we have to define an explicit | |
| // prefix for the overridden definition and apply it to all children | |
| // who relied on that definition. | |
| if (NonDominantDefinedDefault && | |
| namespaceOverrides(NonDominantDefinedDefault->href, | |
| OriginalDefinedDefaultHref)) { | |
| Expected<xmlNsPtr> EC = | |
| searchOrDefine(OriginalDefinedDefaultHref, DominantNode); | |
| if (!EC) { | |
| return EC.takeError(); | |
| } | |
| xmlNsPtr PrefixDominantDefinedDefault = std::move(EC.get()); | |
| explicateNamespace(PrefixDominantDefinedDefault, DominantNode); | |
| } | |
| // In this case the node with a higher priority namespace did not have a | |
| // default namespace definition, but the lower priority node did. In this | |
| // case the new default namespace definition is copied. A side effect of | |
| // this is that all children will suddenly find themselves in a different | |
| // default namespace. To maintain correctness we need to ensure that all | |
| // children now explicitly refer to the namespace that they had previously | |
| // implicitly inherited. | |
| } else if (getNamespaceWithPrefix(nullptr, NonDominantNode)) { | |
| if (DominantNode->parent) { | |
| xmlNsPtr ClosestDefault = getClosestDefault(DominantNode->parent); | |
| Expected<xmlNsPtr> EC = | |
| searchOrDefine(ClosestDefault->href, DominantNode); | |
| if (!EC) { | |
| return EC.takeError(); | |
| } | |
| xmlNsPtr ExplicitDefault = std::move(EC.get()); | |
| explicateNamespace(ExplicitDefault, DominantNode); | |
| } | |
| } | |
| } else { | |
| // Covers case where the incoming node has a default namespace definition | |
| // that overrides the original node's namespace. This always leads to | |
| // the original node receiving that new default namespace. | |
| if (hasDefinedDefaultNamespace(DominantNode)) { | |
| NonDominantNode->ns = getNamespaceWithPrefix(nullptr, NonDominantNode); | |
| } else { | |
| // This covers the case where the incoming node either has a prefix | |
| // namespace, or an inherited default namespace. Since the namespace | |
| // may not yet be defined in the original tree we do a searchOrDefine | |
| // for it, and then set the namespace equal to it. | |
| Expected<xmlNsPtr> EC = | |
| searchOrDefine(DominantNode->ns->href, NonDominantNode); | |
| if (!EC) { | |
| return EC.takeError(); | |
| } | |
| xmlNsPtr Explicit = std::move(EC.get()); | |
| NonDominantNode->ns = Explicit; | |
| } | |
| // This covers cases where the incoming dominant node HAS a default | |
| // namespace definition, but MIGHT NOT NECESSARILY be in that namespace. | |
| if (xmlNsPtr DominantDefaultDefined = | |
| getNamespaceWithPrefix(nullptr, DominantNode)) { | |
| if (OriginalDefinedDefaultHref) { | |
| if (namespaceOverrides(DominantDefaultDefined->href, | |
| OriginalDefinedDefaultHref)) { | |
| // In this case, the incoming node's default definition overrides | |
| // the original default definition, all children who relied on that | |
| // definition must be updated accordingly. | |
| Expected<xmlNsPtr> EC = | |
| searchOrDefine(OriginalDefinedDefaultHref, NonDominantNode); | |
| if (!EC) { | |
| return EC.takeError(); | |
| } | |
| xmlNsPtr ExplicitDefault = std::move(EC.get()); | |
| explicateNamespace(ExplicitDefault, NonDominantNode); | |
| } | |
| } else { | |
| // The original did not define a default definition, however the new | |
| // default definition still applies to all children, so they must be | |
| // updated to explicitly refer to the namespace they had previously | |
| // been inheriting implicitly. | |
| xmlNsPtr ClosestDefault = getClosestDefault(NonDominantNode); | |
| Expected<xmlNsPtr> EC = | |
| searchOrDefine(ClosestDefault->href, NonDominantNode); | |
| if (!EC) { | |
| return EC.takeError(); | |
| } | |
| xmlNsPtr ExplicitDefault = std::move(EC.get()); | |
| explicateNamespace(ExplicitDefault, NonDominantNode); | |
| } | |
| } | |
| } | |
| if (NewDefinedDefaultHref) { | |
| xmlNsPtr OriginalNsDef = getNamespaceWithPrefix(nullptr, OriginalNode); | |
| xmlFree(const_cast<unsigned char *>(OriginalNsDef->href)); | |
| OriginalNsDef->href = NewDefinedDefaultHref; | |
| } | |
| xmlFree(const_cast<unsigned char *>(OriginalDefinedDefaultHref)); | |
| return Error::success(); | |
| } | |
| static bool isRecognizedNamespace(const unsigned char *NsHref) { | |
| for (auto &Ns : MtNsHrefsPrefixes) { | |
| if (xmlStringsEqual(NsHref, TO_XML_CHAR(Ns.first.data()))) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| static bool hasRecognizedNamespace(xmlNodePtr Node) { | |
| return isRecognizedNamespace(Node->ns->href); | |
| } | |
| // Ensure a node's inherited namespace is actually defined in the tree it | |
| // resides in. | |
| static Error reconcileNamespaces(xmlNodePtr Node) { | |
| if (!Node) { | |
| return Error::success(); | |
| } | |
| if (hasInheritedNs(Node)) { | |
| Expected<xmlNsPtr> ExplicitOrError = searchOrDefine(Node->ns->href, Node); | |
| if (!ExplicitOrError) { | |
| return ExplicitOrError.takeError(); | |
| } | |
| xmlNsPtr Explicit = std::move(ExplicitOrError.get()); | |
| Node->ns = Explicit; | |
| } | |
| for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { | |
| if (auto E = reconcileNamespaces(Child)) { | |
| return E; | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| // Recursively merge the two given manifest trees, depending on which elements | |
| // are of a mergeable type, and choose namespaces according to which have | |
| // higher priority. | |
| static Error treeMerge(xmlNodePtr OriginalRoot, xmlNodePtr AdditionalRoot) { | |
| if (auto E = mergeAttributes(OriginalRoot, AdditionalRoot)) | |
| return E; | |
| if (auto E = mergeNamespaces(OriginalRoot, AdditionalRoot)) | |
| return E; | |
| xmlNodePtr AdditionalFirstChild = AdditionalRoot->children; | |
| xmlNode StoreNext; | |
| for (xmlNodePtr Child = AdditionalFirstChild; Child; Child = Child->next) { | |
| xmlNodePtr OriginalChildWithName; | |
| if (!isMergeableElement(Child->name) || | |
| !(OriginalChildWithName = | |
| getChildWithName(OriginalRoot, Child->name)) || | |
| !hasRecognizedNamespace(Child)) { | |
| StoreNext.next = Child->next; | |
| xmlUnlinkNode(Child); | |
| if (!xmlAddChild(OriginalRoot, Child)) { | |
| return make_error<WindowsManifestError>(Twine("could not merge ") + | |
| FROM_XML_CHAR(Child->name)); | |
| } | |
| if (auto E = reconcileNamespaces(Child)) { | |
| return E; | |
| } | |
| Child = &StoreNext; | |
| } else if (auto E = treeMerge(OriginalChildWithName, Child)) { | |
| return E; | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| static void stripComments(xmlNodePtr Root) { | |
| xmlNode StoreNext; | |
| for (xmlNodePtr Child = Root->children; Child; Child = Child->next) { | |
| if (!xmlStringsEqual(Child->name, TO_XML_CHAR("comment"))) { | |
| stripComments(Child); | |
| continue; | |
| } | |
| StoreNext.next = Child->next; | |
| xmlNodePtr Remove = Child; | |
| Child = &StoreNext; | |
| xmlUnlinkNode(Remove); | |
| xmlFreeNode(Remove); | |
| } | |
| } | |
| // libxml2 assumes that attributes do not inherit default namespaces, whereas | |
| // the original mt.exe does make this assumption. This function reconciles | |
| // this by setting all attributes to have the inherited default namespace. | |
| static void setAttributeNamespaces(xmlNodePtr Node) { | |
| for (xmlAttrPtr Attribute = Node->properties; Attribute; | |
| Attribute = Attribute->next) { | |
| if (!Attribute->ns) { | |
| Attribute->ns = getClosestDefault(Node); | |
| } | |
| } | |
| for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { | |
| setAttributeNamespaces(Child); | |
| } | |
| } | |
| // The merging process may create too many prefix defined namespaces. This | |
| // function removes all unnecessary ones from the tree. | |
| static void checkAndStripPrefixes(xmlNodePtr Node, | |
| std::vector<xmlNsPtr> &RequiredPrefixes) { | |
| for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { | |
| checkAndStripPrefixes(Child, RequiredPrefixes); | |
| } | |
| if (Node->ns && Node->ns->prefix != nullptr) { | |
| xmlNsPtr ClosestDefault = getClosestDefault(Node); | |
| if (ClosestDefault && | |
| xmlStringsEqual(ClosestDefault->href, Node->ns->href)) { | |
| Node->ns = ClosestDefault; | |
| } else if (!llvm::is_contained(RequiredPrefixes, Node->ns)) { | |
| RequiredPrefixes.push_back(Node->ns); | |
| } | |
| } | |
| for (xmlAttrPtr Attribute = Node->properties; Attribute; | |
| Attribute = Attribute->next) { | |
| if (Attribute->ns && Attribute->ns->prefix != nullptr) { | |
| xmlNsPtr ClosestDefault = getClosestDefault(Node); | |
| if (ClosestDefault && | |
| xmlStringsEqual(ClosestDefault->href, Attribute->ns->href)) { | |
| Attribute->ns = ClosestDefault; | |
| } else if (!llvm::is_contained(RequiredPrefixes, Node->ns)) { | |
| RequiredPrefixes.push_back(Attribute->ns); | |
| } | |
| } | |
| } | |
| xmlNsPtr Prev; | |
| xmlNs Temp; | |
| for (xmlNsPtr Def = Node->nsDef; Def; Def = Def->next) { | |
| if (!Def->prefix || llvm::is_contained(RequiredPrefixes, Def)) { | |
| Prev = Def; | |
| continue; | |
| } | |
| if (Def == Node->nsDef) { | |
| Node->nsDef = Def->next; | |
| } else { | |
| Prev->next = Def->next; | |
| } | |
| Temp.next = Def->next; | |
| xmlFreeNs(Def); | |
| Def = &Temp; | |
| } | |
| } | |
| WindowsManifestMerger::WindowsManifestMergerImpl::~WindowsManifestMergerImpl() { | |
| for (auto &Doc : MergedDocs) | |
| xmlFreeDoc(Doc); | |
| } | |
| Error WindowsManifestMerger::WindowsManifestMergerImpl::merge( | |
| MemoryBufferRef Manifest) { | |
| if (Merged) | |
| return make_error<WindowsManifestError>( | |
| "merge after getMergedManifest is not supported"); | |
| if (Manifest.getBufferSize() == 0) | |
| return make_error<WindowsManifestError>( | |
| "attempted to merge empty manifest"); | |
| xmlSetGenericErrorFunc((void *)this, | |
| WindowsManifestMergerImpl::errorCallback); | |
| xmlDocPtr ManifestXML = xmlReadMemory( | |
| Manifest.getBufferStart(), Manifest.getBufferSize(), "manifest.xml", | |
| nullptr, XML_PARSE_NOBLANKS | XML_PARSE_NODICT); | |
| xmlSetGenericErrorFunc(nullptr, nullptr); | |
| if (auto E = getParseError()) | |
| return E; | |
| xmlNodePtr AdditionalRoot = xmlDocGetRootElement(ManifestXML); | |
| stripComments(AdditionalRoot); | |
| setAttributeNamespaces(AdditionalRoot); | |
| if (CombinedDoc == nullptr) { | |
| CombinedDoc = ManifestXML; | |
| } else { | |
| xmlNodePtr CombinedRoot = xmlDocGetRootElement(CombinedDoc); | |
| if (!xmlStringsEqual(CombinedRoot->name, AdditionalRoot->name) || | |
| !isMergeableElement(AdditionalRoot->name) || | |
| !hasRecognizedNamespace(AdditionalRoot)) { | |
| return make_error<WindowsManifestError>("multiple root nodes"); | |
| } | |
| if (auto E = treeMerge(CombinedRoot, AdditionalRoot)) { | |
| return E; | |
| } | |
| } | |
| MergedDocs.push_back(ManifestXML); | |
| return Error::success(); | |
| } | |
| std::unique_ptr<MemoryBuffer> | |
| WindowsManifestMerger::WindowsManifestMergerImpl::getMergedManifest() { | |
| if (!Merged) { | |
| Merged = true; | |
| if (!CombinedDoc) | |
| return nullptr; | |
| xmlNodePtr CombinedRoot = xmlDocGetRootElement(CombinedDoc); | |
| std::vector<xmlNsPtr> RequiredPrefixes; | |
| checkAndStripPrefixes(CombinedRoot, RequiredPrefixes); | |
| std::unique_ptr<xmlDoc, XmlDeleter> OutputDoc( | |
| xmlNewDoc((const unsigned char *)"1.0")); | |
| xmlDocSetRootElement(OutputDoc.get(), CombinedRoot); | |
| assert(nullptr == xmlDocGetRootElement(CombinedDoc)); | |
| xmlKeepBlanksDefault(0); | |
| xmlChar *Buff = nullptr; | |
| xmlDocDumpFormatMemoryEnc(OutputDoc.get(), &Buff, &BufferSize, "UTF-8", 1); | |
| Buffer.reset(Buff); | |
| } | |
| return BufferSize ? MemoryBuffer::getMemBufferCopy(StringRef( | |
| FROM_XML_CHAR(Buffer.get()), (size_t)BufferSize)) | |
| : nullptr; | |
| } | |
| bool windows_manifest::isAvailable() { return true; } | |
| #else | |
| WindowsManifestMerger::WindowsManifestMergerImpl::~WindowsManifestMergerImpl() { | |
| } | |
| Error WindowsManifestMerger::WindowsManifestMergerImpl::merge( | |
| MemoryBufferRef Manifest) { | |
| return make_error<WindowsManifestError>("no libxml2"); | |
| } | |
| std::unique_ptr<MemoryBuffer> | |
| WindowsManifestMerger::WindowsManifestMergerImpl::getMergedManifest() { | |
| return nullptr; | |
| } | |
| bool windows_manifest::isAvailable() { return false; } | |
| #endif | |
| WindowsManifestMerger::WindowsManifestMerger() | |
| : Impl(std::make_unique<WindowsManifestMergerImpl>()) {} | |
| WindowsManifestMerger::~WindowsManifestMerger() = default; | |
| Error WindowsManifestMerger::merge(MemoryBufferRef Manifest) { | |
| return Impl->merge(Manifest); | |
| } | |
| std::unique_ptr<MemoryBuffer> WindowsManifestMerger::getMergedManifest() { | |
| return Impl->getMergedManifest(); | |
| } | |
| void WindowsManifestMerger::WindowsManifestMergerImpl::errorCallback( | |
| void *Ctx, const char *Format, ...) { | |
| auto *Merger = (WindowsManifestMergerImpl *)Ctx; | |
| Merger->ParseErrorOccurred = true; | |
| } | |
| Error WindowsManifestMerger::WindowsManifestMergerImpl::getParseError() { | |
| if (!ParseErrorOccurred) | |
| return Error::success(); | |
| return make_error<WindowsManifestError>("invalid xml document"); | |
| } |