File: ManifestUtil\XmlUtil.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
 
#nullable disable
 
namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities
{
    internal static class XmlUtil
    {
        private static readonly ResourceResolver s_resolver = new ResourceResolver();
 
        public static string GetQName(XmlTextReader r, XmlNamespaceManager nsmgr)
        {
            string prefix = !String.IsNullOrEmpty(r.Prefix) ? r.Prefix : nsmgr.LookupPrefix(r.NamespaceURI);
            if (!String.IsNullOrEmpty(prefix))
            {
                return prefix + ":" + r.LocalName;
            }
            else
            {
                return r.LocalName;
            }
        }
 
        // NOTE: XmlDocument.ImportNode munges "xmlns:asmv2" to "xmlns:d1p1" for some reason, use XmlUtil.CloneElementToDocument instead
        public static XmlElement CloneElementToDocument(XmlElement element, XmlDocument document, string namespaceURI)
        {
            XmlElement newElement = document.CreateElement(element.Name, namespaceURI);
            foreach (XmlAttribute attribute in element.Attributes)
            {
                XmlAttribute newAttribute = document.CreateAttribute(attribute.Name);
                newAttribute.Value = attribute.Value;
                newElement.Attributes.Append(newAttribute);
            }
            foreach (XmlNode node in element.ChildNodes)
            {
                if (node.NodeType == XmlNodeType.Element)
                {
                    XmlElement childElement = CloneElementToDocument((XmlElement)node, document, namespaceURI);
                    newElement.AppendChild(childElement);
                }
                else if (node.NodeType == XmlNodeType.Comment)
                {
                    XmlComment childComment = document.CreateComment(((XmlComment)node).Data);
                    newElement.AppendChild(childComment);
                }
            }
            return newElement;
        }
 
        public static string TrimPrefix(string s)
        {
            int i = s.IndexOf(':');
            if (i < 0)
            {
                return s;
            }
 
            return s.Substring(i + 1);
        }
 
        [SuppressMessage("Microsoft.Security.Xml", "CA3073: ReviewTrustedXsltUse.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")]
        [SuppressMessage("Microsoft.Security.Xml", "CA3053: UseSecureXmlResolver.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")]
        [SuppressMessage("Microsoft.Security.Xml", "CA3059: UseXmlReaderForXPathDocument.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")]
        public static Stream XslTransform(string resource, Stream input, params DictionaryEntry[] entries)
        {
            int t1 = Environment.TickCount;
 
            Stream s = Util.GetEmbeddedResourceStream(resource);
 
            int t2 = Environment.TickCount;
            XPathDocument d = new XPathDocument(s);
            Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "new XPathDocument(1) t={0}", Environment.TickCount - t2));
 
            int t3 = Environment.TickCount;
            var xslc = new XslCompiledTransform();
            // Using the Trusted Xslt is fine as the style sheet comes from our own assemblies.
            // This is similar to the prior this.GetType().Assembly/Evidence method that was used in the now depricated XslTransform.
            xslc.Load(d, XsltSettings.TrustedXslt, s_resolver);
            Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform.Load t={0}", Environment.TickCount - t3));
 
            // Need to copy input stream because XmlReader will close it,
            // causing errors for later callers that access the same stream
            var clonedInput = new MemoryStream();
            Util.CopyStream(input, clonedInput);
 
            int t4 = Environment.TickCount;
            using (XmlReader reader = XmlReader.Create(clonedInput))
            {
                Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "new XmlReader(2) t={0}", Environment.TickCount - t4));
 
                XsltArgumentList args = null;
                if (entries.Length > 0)
                {
                    args = new XsltArgumentList();
                    foreach (DictionaryEntry entry in entries)
                    {
                        string key = entry.Key.ToString();
                        object val = entry.Value.ToString();
                        args.AddParam(key, "", val);
                        Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "arg: key='{0}' value='{1}'", key, val.ToString()));
                    }
                }
 
#pragma warning disable CA2000 // Dispose objects before losing scope - the caller expects to receive an open stream
                var m = new MemoryStream();
                var w = new XmlTextWriter(m, Encoding.UTF8);
#pragma warning restore CA2000 // Dispose objects before losing scope
                w.WriteStartDocument();
 
                int t5 = Environment.TickCount;
                xslc.Transform(reader, args, w, s_resolver);
                Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform.Transform t={0}", Environment.TickCount - t4));
 
                w.WriteEndDocument();
                w.Flush();
                m.Position = 0;
 
                Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform(\"{0}\") t={1}", resource, Environment.TickCount - t1));
 
                return m;
            }
        }
 
        private class ResourceResolver : XmlUrlResolver
        {
            public override Object GetEntity(Uri uri, string role, Type t)
            {
                if (!uri.IsAbsoluteUri)
                {
                    // As this is not an absolute URI, the file operations below won't work anyways, so we return null.
                    // This method used to throw an exception on an absolute URI, but it was silently consumed by XslTransform.  XslCompiledTransform is no longer silent about these inner exceptions.
                    return null;
                }
 
                string filename = uri.Segments[uri.Segments.Length - 1];
                Stream s = null;
 
                // If path is in temp then we immediately know we can skip the first two checks...
                if (!uri.LocalPath.StartsWith(Path.GetTempPath(), StringComparison.Ordinal))
                {
                    // First look in assembly resources...
                    Assembly a = Assembly.GetExecutingAssembly();
                    s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(Util).Namespace, filename));
 
                    if (s != null)
                    {
                        return s;
                    }
 
                    // Next look in current directory...
                    try
                    {
                        s = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                    }
                    catch (FileNotFoundException)
                    {
                    }
                    if (s != null)
                    {
                        return s;
                    }
                }
 
                // Lastly, look at full specified uri path...
                try
                {
                    s = new FileStream(uri.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                }
                catch (DirectoryNotFoundException)
                {
                }
                catch (FileNotFoundException)
                {
                }
                if (s != null)
                {
                    return s;
                }
 
                // Didn't find the resource...
                Debug.Fail(String.Format(CultureInfo.CurrentCulture, "ResourceResolver could not find file '{0}'", filename));
                return null;
            }
        }
    }
}