#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) {
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;
}
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;
}
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;
}
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;
}
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");
}
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();
}
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;
}
static xmlNsPtr getClosestDefault(xmlNodePtr Node) {
if (xmlNsPtr Ret = getNamespaceWithPrefix(nullptr, Node))
return Ret;
if (Node->parent == nullptr)
return nullptr;
return getClosestDefault(Node->parent);
}
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)) {
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;
}
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;
}
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();
}
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;
}
static bool hasInheritedNs(xmlNodePtr Node) {
return Node->ns && Node->ns != getNamespaceWithPrefix(Node->ns->prefix, Node);
}
static bool hasInheritedDefaultNs(xmlNodePtr Node) {
return hasInheritedNs(Node) && Node->ns->prefix == nullptr;
}
static bool hasDefinedDefaultNamespace(xmlNodePtr Node) {
return Node->ns && (Node->ns == getNamespaceWithPrefix(nullptr, Node));
}
static void explicateNamespace(xmlNsPtr PrefixDef, xmlNodePtr Node) {
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);
}
}
static Error mergeNamespaces(xmlNodePtr OriginalNode,
xmlNodePtr AdditionalNode) {
const unsigned char *OriginalDefinedDefaultHref = nullptr;
if (xmlNsPtr OriginalDefinedDefaultNs =
getNamespaceWithPrefix(nullptr, OriginalNode)) {
OriginalDefinedDefaultHref = xmlStrdup(OriginalDefinedDefaultNs->href);
}
const unsigned char *NewDefinedDefaultHref = nullptr;
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;
}
}
xmlNodePtr DominantNode = getDominantNode(OriginalNode, AdditionalNode);
xmlNodePtr NonDominantNode =
DominantNode == OriginalNode ? AdditionalNode : OriginalNode;
if (DominantNode == OriginalNode) {
if (OriginalDefinedDefaultHref) {
xmlNsPtr NonDominantDefinedDefault =
getNamespaceWithPrefix(nullptr, NonDominantNode);
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);
}
} 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 {
if (hasDefinedDefaultNamespace(DominantNode)) {
NonDominantNode->ns = getNamespaceWithPrefix(nullptr, NonDominantNode);
} else {
Expected<xmlNsPtr> EC =
searchOrDefine(DominantNode->ns->href, NonDominantNode);
if (!EC) {
return EC.takeError();
}
xmlNsPtr Explicit = std::move(EC.get());
NonDominantNode->ns = Explicit;
}
if (xmlNsPtr DominantDefaultDefined =
getNamespaceWithPrefix(nullptr, DominantNode)) {
if (OriginalDefinedDefaultHref) {
if (namespaceOverrides(DominantDefaultDefined->href,
OriginalDefinedDefaultHref)) {
Expected<xmlNsPtr> EC =
searchOrDefine(OriginalDefinedDefaultHref, NonDominantNode);
if (!EC) {
return EC.takeError();
}
xmlNsPtr ExplicitDefault = std::move(EC.get());
explicateNamespace(ExplicitDefault, NonDominantNode);
}
} else {
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);
}
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();
}
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);
}
}
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);
}
}
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");
}