File: ManifestUtil\TrustInfo.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.Runtime.InteropServices;
#if !RUNTIME_TYPE_NETCORE
using System.Security;
using System.Security.Permissions;
#endif
using System.Xml;
using Microsoft.Build.Shared.FileSystem;
 
#nullable disable
 
namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities
{
    /// <summary>
    /// Describes the application security trust information.
    /// </summary>
    [ComVisible(false)]
    public sealed class TrustInfo
    {
#if !RUNTIME_TYPE_NETCORE
        // Partial trust and permission sets are not supported by .NET Core.
        // SameSite evaluation is conditioned on .NET FX but always done in .NET Core code.
        private PermissionSet _inputPermissionSet;
        private PermissionSet _outputPermissionSet;
        private bool _sameSiteChanged;
#endif
        private XmlDocument _inputTrustInfoDocument;
        private bool _isFullTrust = true;
        private string _sameSiteSetting = "site";
 
        private void AddSameSiteAttribute(XmlElement permissionSetElement)
        {
            XmlAttribute sameSiteAttribute = (XmlAttribute)permissionSetElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.sameSiteAttribute));
            if (sameSiteAttribute == null)
            {
                sameSiteAttribute = permissionSetElement.OwnerDocument.CreateAttribute(XmlUtil.TrimPrefix(XPaths.sameSiteAttribute));
                permissionSetElement.Attributes.Append(sameSiteAttribute);
            }
 
            sameSiteAttribute.Value = _sameSiteSetting;
        }
 
        /// <summary>
        /// Resets the object to its default state.
        /// </summary>
        public void Clear()
        {
#if !RUNTIME_TYPE_NETCORE
            _inputPermissionSet = null;
            _outputPermissionSet = null;
#endif
            _inputTrustInfoDocument = null;
            _isFullTrust = true;
        }
 
        private void FixupPermissionSetElement(XmlElement permissionSetElement)
        {
            XmlDocument document = permissionSetElement.OwnerDocument;
            XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(document.NameTable);
 
            if (PreserveFullTrustPermissionSet)
            {
                var unrestrictedAttribute = (XmlAttribute)permissionSetElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                if (_isFullTrust)
                {
                    if (unrestrictedAttribute == null)
                    {
                        unrestrictedAttribute = document.CreateAttribute(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                        permissionSetElement.Attributes.Append(unrestrictedAttribute);
                    }
                    unrestrictedAttribute.Value = "true";
                }
                else
                {
                    if (unrestrictedAttribute != null)
                    {
                        permissionSetElement.Attributes.RemoveNamedItem(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                    }
                }
            }
            else
            {
                if (_isFullTrust)
                {
                    var unrestrictedAttribute = (XmlAttribute)permissionSetElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                    if (unrestrictedAttribute == null)
                    {
                        unrestrictedAttribute = document.CreateAttribute(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                        permissionSetElement.Attributes.Append(unrestrictedAttribute);
                    }
                    unrestrictedAttribute.Value = "true";
                    while (permissionSetElement.FirstChild != null)
                    {
                        permissionSetElement.RemoveChild(permissionSetElement.FirstChild);
                    }
                }
            }
 
            // Add ID="Custom" attribute if there's not one already
            var idAttribute = (XmlAttribute)permissionSetElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.idAttribute));
            if (idAttribute == null)
            {
                idAttribute = document.CreateAttribute(XmlUtil.TrimPrefix(XPaths.idAttribute));
                permissionSetElement.Attributes.Append(idAttribute);
            }
 
            if (String.IsNullOrEmpty(idAttribute.Value))
            {
                idAttribute.Value = "Custom";
            }
 
            AddSameSiteAttribute(permissionSetElement);
 
            if (permissionSetElement.ParentNode == null ||
                permissionSetElement.ParentNode.NodeType == XmlNodeType.Document)
            {
                return;
            }
 
            XmlElement defaultAssemblyRequestElement = (XmlElement)permissionSetElement.ParentNode.SelectSingleNode(XPaths.defaultAssemblyRequestElement, nsmgr);
            if (defaultAssemblyRequestElement == null)
            {
                defaultAssemblyRequestElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.defaultAssemblyRequestElement), XmlNamespaces.asmv2);
                permissionSetElement.ParentNode.AppendChild(defaultAssemblyRequestElement);
            }
            XmlAttribute idrefAttribute = (XmlAttribute)permissionSetElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.permissionSetReferenceAttribute));
            if (idrefAttribute == null)
            {
                idrefAttribute = document.CreateAttribute(XmlUtil.TrimPrefix(XPaths.permissionSetReferenceAttribute));
                defaultAssemblyRequestElement.Attributes.Append(idrefAttribute);
            }
 
            if (!String.Equals(idAttribute.Value, idrefAttribute.Value, StringComparison.Ordinal))
            {
                idrefAttribute.Value = idAttribute.Value;
            }
        }
 
#if !RUNTIME_TYPE_NETCORE
        private PermissionSet GetInputPermissionSet()
        {
            if (_inputPermissionSet == null)
            {
                XmlElement psElement = GetInputPermissionSetElement();
                if (PreserveFullTrustPermissionSet)
                {
                    XmlAttribute unrestrictedAttribute = (XmlAttribute)psElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                    _isFullTrust = unrestrictedAttribute != null && Boolean.Parse(unrestrictedAttribute.Value);
                    if (_isFullTrust)
                    {
                        XmlDocument document = new XmlDocument();
                        document.AppendChild(document.ImportNode(psElement, true));
                        document.DocumentElement.Attributes.RemoveNamedItem(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
                        psElement = document.DocumentElement;
                    }
                    _inputPermissionSet = SecurityUtilities.XmlToPermissionSet(psElement);
                }
                else
                {
                    _inputPermissionSet = SecurityUtilities.XmlToPermissionSet(psElement);
                    _isFullTrust = _inputPermissionSet.IsUnrestricted();
                }
            }
            return _inputPermissionSet;
        }
#endif
 
        private XmlElement GetInputPermissionSetElement()
        {
            if (_inputTrustInfoDocument == null)
            {
                _inputTrustInfoDocument = new XmlDocument();
                XmlElement trustInfoElement = _inputTrustInfoDocument.CreateElement(XmlUtil.TrimPrefix(XPaths.trustInfoElement), XmlNamespaces.asmv2);
                _inputTrustInfoDocument.AppendChild(trustInfoElement);
            }
            return GetPermissionSetElement(_inputTrustInfoDocument);
        }
 
        private XmlElement GetInputRequestedPrivilegeElement()
        {
            if (_inputTrustInfoDocument == null)
            {
                return null;
            }
 
            XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(_inputTrustInfoDocument.NameTable);
            XmlElement trustInfoElement = _inputTrustInfoDocument.DocumentElement;
            XmlElement securityElement = (XmlElement)trustInfoElement?.SelectSingleNode(XPaths.securityElement, nsmgr);
            XmlElement requestedPrivilegeElement = (XmlElement)securityElement?.SelectSingleNode(XPaths.requestedPrivilegeElement, nsmgr);
            return requestedPrivilegeElement;
        }
 
        private static XmlElement GetRequestedPrivilegeElement(XmlElement inputRequestedPrivilegeElement, XmlDocument document)
        {
            // <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
            //      <!--
            //          UAC Manifest Options
            //          If you want to change the Windows User Account Control level replace the
            //          requestedExecutionLevel node with one of the following .
            //          <requestedExecutionLevel  level="asInvoker" />
            //          <requestedExecutionLevel  level="requireAdministrator" />
            //          <requestedExecutionLevel  level="highestAvailable" />
            //          If you want to utilize File and Registry Virtualization for backward compatibility
            //          delete the requestedExecutionLevel node.
            //      -->
            //      <requestedExecutionLevel level="asInvoker" />
            //  </requestedPrivileges>
 
            // we always create a requestedPrivilege node to put into the generated TrustInfo document
            //
            XmlElement requestedPrivilegeElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.requestedPrivilegeElement), XmlNamespaces.asmv3);
            document.AppendChild(requestedPrivilegeElement);
 
            // our three cases we need to handle are:
            //  (a) no requestedPrivilege node (and therefore no requestedExecutionLevel node as well) - use default values
            //  (b) requestedPrivilege node and no requestedExecutionLevel node - omit the requestedExecutionLevel node
            //  (c) requestedPrivilege node and requestedExecutionLevel node - use the incoming requestedExecutionLevel node values
            //
            // using null for both values is case (b) above -- do not output values
            //
            string executionLevelString = null;
            string executionUIAccessString = null;
            string commentString = null;
 
            // case (a) above -- load default values
            //
            if (inputRequestedPrivilegeElement == null)
            {
                // If UAC requestedPrivilege node is missing (possibly due to upgraded project) then automatically
                //  add a default UAC requestedPrivilege node with a default requestedExecutionLevel node set to
                //  the expected ClickOnce level (asInvoker) with uiAccess as false
                //
                executionLevelString = Constants.UACAsInvoker;
                executionUIAccessString = Constants.UACUIAccess;
 
                // load up a default comment string that we put in front of the requestedExecutionLevel node
                //  here so we can allow the passed-in node to override it if there is a comment present
                //
                System.Resources.ResourceManager resources = new System.Resources.ResourceManager("Microsoft.Build.Tasks.Core.Strings.ManifestUtilities", typeof(SecurityUtilities).Module.Assembly);
                commentString = resources.GetString("TrustInfo.RequestedExecutionLevelComment");
            }
            else
            {
                // we need to see if the requestedExecutionLevel node is present to decide whether or not to create one.
                //
                XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(document.NameTable);
                XmlElement inputRequestedExecutionLevel = (XmlElement)inputRequestedPrivilegeElement.SelectSingleNode(XPaths.requestedExecutionLevelElement, nsmgr);
 
                // case (c) above -- use incoming values [note that we should do nothing for case (b) above
                //  because the default values will make us not emit the requestedExecutionLevel and comment]
                //
                if (inputRequestedExecutionLevel != null)
                {
                    XmlNode previousNode = inputRequestedExecutionLevel.PreviousSibling;
 
                    // fetch the current comment node if there is one (if there is not one, simply
                    //  keep the default null value which means we will not create one in the
                    //  output document)
                    //
                    if (previousNode?.NodeType == XmlNodeType.Comment)
                    {
                        commentString = ((XmlComment)previousNode).Data;
                    }
 
                    // fetch the current requestedExecutionLevel node's level attribute if there is one
                    //
                    if (inputRequestedExecutionLevel.HasAttribute("level"))
                    {
                        executionLevelString = inputRequestedExecutionLevel.GetAttribute("level");
                    }
 
                    // fetch the current requestedExecutionLevel node's uiAccess attribute if there is one
                    //
                    if (inputRequestedExecutionLevel.HasAttribute("uiAccess"))
                    {
                        executionUIAccessString = inputRequestedExecutionLevel.GetAttribute("uiAccess");
                    }
                }
            }
 
            if (commentString != null)
            {
                XmlComment requestedPrivilegeComment = document.CreateComment(commentString);
                requestedPrivilegeElement.AppendChild(requestedPrivilegeComment);
            }
 
            if (executionLevelString != null)
            {
                XmlElement requestedExecutionLevelElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.requestedExecutionLevelElement), XmlNamespaces.asmv3);
                requestedPrivilegeElement.AppendChild(requestedExecutionLevelElement);
 
                XmlAttribute levelAttribute = document.CreateAttribute("level");
                levelAttribute.Value = executionLevelString;
                requestedExecutionLevelElement.Attributes.Append(levelAttribute);
 
                if (executionUIAccessString != null)
                {
                    XmlAttribute uiAccessAttribute = document.CreateAttribute("uiAccess");
                    uiAccessAttribute.Value = executionUIAccessString;
                    requestedExecutionLevelElement.Attributes.Append(uiAccessAttribute);
                }
            }
 
            return requestedPrivilegeElement;
        }
 
        // Returns permission set sub-element, creating a full-trust permission-set if one doesn't exist
        private XmlElement GetPermissionSetElement(XmlDocument document)
        {
            Debug.Assert(document != null, "GetPermissionSetElement was passed a null document");
            XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(document.NameTable);
            XmlElement trustInfoElement = document.DocumentElement;
            var securityElement = (XmlElement)trustInfoElement.SelectSingleNode(XPaths.securityElement, nsmgr);
            if (securityElement == null)
            {
                securityElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.securityElement), XmlNamespaces.asmv2);
                trustInfoElement.AppendChild(securityElement);
            }
            XmlElement applicationRequestMinimumElement = (XmlElement)securityElement.SelectSingleNode(XPaths.applicationRequestMinimumElement, nsmgr);
            if (applicationRequestMinimumElement == null)
            {
                applicationRequestMinimumElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.applicationRequestMinimumElement), XmlNamespaces.asmv2);
                securityElement.AppendChild(applicationRequestMinimumElement);
            }
            XmlElement permissionSetElement = (XmlElement)applicationRequestMinimumElement.SelectSingleNode(XPaths.permissionSetElement, nsmgr);
            if (permissionSetElement == null)
            {
                permissionSetElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.permissionSetElement), XmlNamespaces.asmv2);
                applicationRequestMinimumElement.AppendChild(permissionSetElement);
                XmlAttribute unrestrictedAttribute = document.CreateAttribute(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute), XmlNamespaces.asmv2);
                unrestrictedAttribute.Value = _isFullTrust.ToString().ToLowerInvariant();
                permissionSetElement.Attributes.Append(unrestrictedAttribute);
            }
            return permissionSetElement;
        }
 
#if !RUNTIME_TYPE_NETCORE
        private PermissionSet GetOutputPermissionSet()
        {
            if (_outputPermissionSet == null)
            {
                // NOTE: outputPermissionSet is now the same as the inputPermissionSet
                // we used to maintain a list of loadable vs. non-loadable permissions
                // this is now cut. If we get more time we should simplify this further
                // so there is only one permission set.
                _outputPermissionSet = GetInputPermissionSet();
            }
 
            return _outputPermissionSet;
        }
 
        // Computes permission set from _outputPermissionSet and _unknownPermissions and returns new document
        private XmlDocument GetOutputPermissionSetDocument()
        {
            PermissionSet outputPermissionSet = GetOutputPermissionSet();
            XmlDocument outputDocument = SecurityUtilities.PermissionSetToXml(outputPermissionSet);
 
            return outputDocument;
        }
#endif
 
        /// <summary>
        /// Determines whether the application has permission to call unmanaged code.
        /// </summary>
        public bool HasUnmanagedCodePermission
        {
            get
            {
#if RUNTIME_TYPE_NETCORE
                // Always use full-trust on .NET Core.
                return true;
#else
                PermissionSet ps = GetOutputPermissionSet();
                if (ps == null)
                {
                    return false;
                }
                var ups = new PermissionSet(PermissionState.None);
                ups.AddPermission(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode));
                return ps.Intersect(ups) != null;
#endif
            }
        }
 
        /// <summary>
        /// Determines whether the application is full trust or partial trust.
        /// </summary>
        public bool IsFullTrust
        {
            get
            {
#if RUNTIME_TYPE_NETCORE
                // Always use full-trust on .NET Core.
                return true;
#else
                GetInputPermissionSet();
                return _isFullTrust;
#endif
            }
#if !RUNTIME_TYPE_NETCORE
            set => _isFullTrust = value;
#endif
        }
 
#if !RUNTIME_TYPE_NETCORE
        /// <summary>
        /// Gets or sets the permission set object for the application trust.
        /// </summary>
        public PermissionSet PermissionSet
        {
            get => GetOutputPermissionSet();
            set => _outputPermissionSet = value ?? throw new ArgumentNullException("PermissionSet cannot be set to null.");
        }
#endif
 
        /// <summary>
        /// Determines whether to preserve partial trust permission when the full trust flag is set.
        /// If this option is false with full trust specified, then any permissions defined in the permission set object will be dropped on save.
        /// </summary>
        public bool PreserveFullTrustPermissionSet { get; set; }
 
        /// <summary>
        /// Reads the application trust from an XML file.
        /// </summary>
        /// <param name="path">The name of the input file.</param>
        public void Read(string path)
        {
            using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                Read(s);
            }
        }
 
        /// <summary>
        /// Reads the application trust from an XML file.
        /// </summary>
        /// <param name="input">Specifies an input stream.</param>
        public void Read(Stream input)
        {
            Read(input, XPaths.trustInfoPath);
        }
 
        private void Read(Stream s, string xpath)
        {
            Clear();
            var document = new XmlDocument();
            var xrSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
            using (XmlReader xr = XmlReader.Create(s, xrSettings))
            {
                document.Load(xr);
            }
            XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(document.NameTable);
            var trustInfoElement = (XmlElement)document.SelectSingleNode(xpath, nsmgr);
            if (trustInfoElement == null)
            {
                return; // no trustInfo element is okay
            }
            ReadTrustInfo(trustInfoElement.OuterXml);
        }
 
        /// <summary>
        /// Reads the application trust from a ClickOnce application manifest.
        /// </summary>
        /// <param name="path">The name of the input file.</param>
        public void ReadManifest(string path)
        {
            using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                ReadManifest(s);
            }
        }
 
        /// <summary>
        /// Reads the application trust from a ClickOnce application manifest.
        /// </summary>
        /// <param name="input">Specifies an input stream.</param>
        public void ReadManifest(Stream input)
        {
            Read(input, XPaths.manifestTrustInfoPath);
        }
 
        [SuppressMessage("Microsoft.Security.Xml", "CA3057: DoNotUseLoadXml.")]
        private void ReadTrustInfo(string xml)
        {
            _inputTrustInfoDocument = new XmlDocument();
            // CA3057: DoNotUseLoadXml. Suppressed since the suggested fix is to use XmlReader.
            // XmlReader.Create(string) requires an URI. Whereas the input parameter 'xml' is file content and not a path.
            _inputTrustInfoDocument.LoadXml(xml);
            XmlElement psElement = GetInputPermissionSetElement();
            XmlAttribute unrestrictedAttribute = (XmlAttribute)psElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute));
#if RUNTIME_TYPE_NETCORE
            // Partial trust is not supported on .NET Core.
            // Fail if loaded manifest does not specify full-trust.
            // It can happen if manifest is manually modifed.
            if (unrestrictedAttribute == null || (false == Boolean.Parse(unrestrictedAttribute.Value)))
            {
                throw new ArgumentException("Partial trust is not supported.");
            }
#else
            _isFullTrust = unrestrictedAttribute != null && Boolean.Parse(unrestrictedAttribute.Value);
#endif
            XmlAttribute sameSiteAttribute = (XmlAttribute)psElement.Attributes.GetNamedItem(XmlUtil.TrimPrefix(XPaths.sameSiteAttribute));
            if (sameSiteAttribute != null)
            {
                _sameSiteSetting = sameSiteAttribute.Value;
            }
        }
 
        /// <summary>
        /// Describes the level of "same site" access permitted, specifying whether the application has permission to communicate with the server from which it was deployed.
        /// </summary>
        public string SameSiteAccess
        {
            get => _sameSiteSetting;
            set
            {
                _sameSiteSetting = value;
#if !RUNTIME_TYPE_NETCORE
                _sameSiteChanged = true;
#endif
            }
        }
 
        public override string ToString()
        {
            var m = new MemoryStream();
            Write(m);
            m.Position = 0;
            using var r = new StreamReader(m);
 
            return r.ReadToEnd();
        }
 
        /// <summary>
        /// Writes the application trust to an XML file.
        /// </summary>
        /// <param name="path">The name of the output file.</param>
        public void Write(string path)
        {
            using (Stream s = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                Write(s);
                s.Flush();
            }
        }
 
        /// <summary>
        /// Writes the application trust to an XML file.
        /// </summary>
        /// <param name="output"></param>
        public void Write(Stream output)
        {
            var outputDocument = new XmlDocument();
            XmlElement inputPermissionSetElement = GetInputPermissionSetElement();
 
            // NOTE: XmlDocument.ImportNode munges "xmlns:asmv2" to "xmlns:d1p1" for some reason, use XmlUtil.CloneElementToDocument instead
            XmlElement outputPermissionSetElement = XmlUtil.CloneElementToDocument(inputPermissionSetElement, outputDocument, XmlNamespaces.asmv2);
            outputDocument.AppendChild(outputPermissionSetElement);
 
            string tempPrivilegeDocument = null;
 
            var privilegeDocument = new XmlDocument();
            XmlElement inputRequestedPrivilegeElement = GetInputRequestedPrivilegeElement();
 
            XmlElement requestedPrivilegeElement = GetRequestedPrivilegeElement(inputRequestedPrivilegeElement, privilegeDocument);
 
            if (requestedPrivilegeElement != null)
            {
                privilegeDocument.AppendChild(requestedPrivilegeElement);
 
                var p = new MemoryStream();
                privilegeDocument.Save(p);
                p.Position = 0;
                tempPrivilegeDocument = Util.WriteTempFile(p);
            }
 
            try
            {
                string trustInfoResource2 = "trustinfo2.xsl";
 
                // If permission set was not altered, just write out what was read in...
                MemoryStream m = new MemoryStream();
#if RUNTIME_TYPE_NETCORE
                // Simpler code on .NET Core - due to no support for custom permission sets.
                XmlElement permissionSetElement = outputDocument.DocumentElement;
                FixupPermissionSetElement(permissionSetElement);
 
                outputDocument.Save(m);
                m.Position = 0;
#else
                if (_outputPermissionSet == null && !_sameSiteChanged)
                {
                    XmlElement permissionSetElement = outputDocument.DocumentElement;
                    FixupPermissionSetElement(permissionSetElement);
 
                    outputDocument.Save(m);
                    m.Position = 0;
                }
                else
                {
                    XmlDocument permissionSetDocument = GetOutputPermissionSetDocument();
                    XmlElement permissionSetElement = permissionSetDocument.DocumentElement;
                    FixupPermissionSetElement(permissionSetElement);
 
                    if (outputDocument.DocumentElement == null)
                    {
                        permissionSetDocument.Save(m);
                        m.Position = 0;
                    }
                    else
                    {
                        XmlElement oldPermissionSetElement = outputDocument.DocumentElement;
                        XmlElement newPermissionSetElement = (XmlElement)outputDocument.ImportNode(permissionSetElement, true);
                        oldPermissionSetElement.ParentNode.ReplaceChild(newPermissionSetElement, oldPermissionSetElement);
 
                        outputDocument.Save(m);
                        m.Position = 0;
                    }
                }
#endif
 
                // Wrap <PermissionSet> in a <TrustInfo> section
                Stream s = tempPrivilegeDocument != null ? XmlUtil.XslTransform(trustInfoResource2, m, new DictionaryEntry("defaultRequestedPrivileges", tempPrivilegeDocument)) : XmlUtil.XslTransform(trustInfoResource2, m);
                Util.CopyStream(s, output);
            }
            finally
            {
                if (tempPrivilegeDocument != null)
                {
                    File.Delete(tempPrivilegeDocument);
                }
            }
        }
 
        /// <summary>
        /// Writes the application trust to a ClickOnce application manifest.
        /// If the file exists, the trust section will be updated.
        /// If the file does not exist, a new template manifest with the specified trust will be created.
        /// </summary>
        /// <param name="path">The name of the output file.</param>
        public void WriteManifest(string path)
        {
            Stream s = null;
            try
            {
                if (FileSystems.Default.FileExists(path))
                {
                    s = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                }
                else
                {
                    s = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
                }
 
                if (s.Length > 0)
                {
                    // If the file is not empty then we assume that it is already a
                    // valid manifest and that we are just modifying the trust info
                    WriteManifest(s, s);
                }
                else
                {
                    // If the file is empty we need to start with a manifest template.
                    WriteManifest(s);
                }
            }
            finally
            {
                if (s != null)
                {
                    s.Flush();
                    s.Close();
                }
            }
        }
 
        /// <summary>
        /// Writes the application trust to a new template ClickOnce application manifest.
        /// </summary>
        /// <param name="output">Specifies an output stream.</param>
        public void WriteManifest(Stream output)
        {
            string r = "manifest.xml";
            Stream input = Util.GetEmbeddedResourceStream(r);
            WriteManifest(input, output);
        }
 
        /// <summary>
        /// Updates an existing ClickOnce application manifest with the specified trust.
        /// </summary>
        /// <param name="input">Specifies an input stream.</param>
        /// <param name="output">Specifies an output stream.</param>
        public void WriteManifest(Stream input, Stream output)
        {
            int t1 = Environment.TickCount;
            var document = new XmlDocument();
            var xrSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
            using (XmlReader xr = XmlReader.Create(input, xrSettings))
            {
                document.Load(xr);
            }
            XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(document.NameTable);
            XmlElement assemblyElement = (XmlElement)document.SelectSingleNode(XPaths.assemblyElement, nsmgr);
            if (assemblyElement == null)
            {
                throw new BadImageFormatException();
            }
 
            var trustInfoElement = (XmlElement)assemblyElement.SelectSingleNode(XPaths.trustInfoElement, nsmgr);
            if (trustInfoElement == null)
            {
                trustInfoElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.trustInfoElement), XmlNamespaces.asmv2);
                assemblyElement.AppendChild(trustInfoElement);
            }
 
#if RUNTIME_TYPE_NETCORE
            // Partial trust and permission sets are not supported on .NET Core.
            // Consequently we do not support updating permissionSet element.
            XmlElement securityElement = (XmlElement)trustInfoElement.SelectSingleNode(XPaths.securityElement, nsmgr);
            if (securityElement == null)
            {
                securityElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.securityElement), XmlNamespaces.asmv2);
                trustInfoElement.AppendChild(securityElement);
            }
            XmlElement applicationRequestMinimumElement = (XmlElement)securityElement.SelectSingleNode(XPaths.applicationRequestMinimumElement, nsmgr);
            if (applicationRequestMinimumElement == null)
            {
                applicationRequestMinimumElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.applicationRequestMinimumElement), XmlNamespaces.asmv2);
                securityElement.AppendChild(applicationRequestMinimumElement);
            }
 
            XmlNodeList permissionSetNodes = applicationRequestMinimumElement.SelectNodes(XPaths.permissionSetElement, nsmgr);
            foreach (XmlNode permissionSetNode in permissionSetNodes)
            {
                applicationRequestMinimumElement.RemoveChild(permissionSetNode);
            }
 
            XmlElement fullTrustPermissionSetElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.permissionSetElement), XmlNamespaces.asmv2);
            XmlAttribute unrestrictedAttribute = document.CreateAttribute(XmlUtil.TrimPrefix(XPaths.unrestrictedAttribute), XmlNamespaces.asmv2);
            unrestrictedAttribute.Value = "true";
            applicationRequestMinimumElement.AppendChild(fullTrustPermissionSetElement);
            FixupPermissionSetElement(fullTrustPermissionSetElement);
#else
            // If we have an input trustinfo document and no output specified then just copy the input to the output
            if (_inputTrustInfoDocument != null && _outputPermissionSet == null && !_sameSiteChanged)
            {
                XmlElement newTrustInfoElement = (XmlElement)document.ImportNode(_inputTrustInfoDocument.DocumentElement, true);
                trustInfoElement.ParentNode.ReplaceChild(newTrustInfoElement, trustInfoElement);
            }
            else
            {
                var securityElement = (XmlElement)trustInfoElement.SelectSingleNode(XPaths.securityElement, nsmgr);
                if (securityElement == null)
                {
                    securityElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.securityElement), XmlNamespaces.asmv2);
                    trustInfoElement.AppendChild(securityElement);
                }
                var applicationRequestMinimumElement = (XmlElement)securityElement.SelectSingleNode(XPaths.applicationRequestMinimumElement, nsmgr);
                if (applicationRequestMinimumElement == null)
                {
                    applicationRequestMinimumElement = document.CreateElement(XmlUtil.TrimPrefix(XPaths.applicationRequestMinimumElement), XmlNamespaces.asmv2);
                    securityElement.AppendChild(applicationRequestMinimumElement);
                }
 
                XmlNodeList permissionSetNodes = applicationRequestMinimumElement.SelectNodes(XPaths.permissionSetElement, nsmgr);
                foreach (XmlNode permissionSetNode in permissionSetNodes)
                {
                    applicationRequestMinimumElement.RemoveChild(permissionSetNode);
                }
 
                XmlDocument permissionSetDocument = GetOutputPermissionSetDocument();
                var permissionSetElement = (XmlElement)document.ImportNode(permissionSetDocument.DocumentElement, true);
                applicationRequestMinimumElement.AppendChild(permissionSetElement);
                FixupPermissionSetElement(permissionSetElement);
            }
#endif
 
            // Truncate any contents that may be in the file
            if (output.Length > 0)
            {
                output.SetLength(0);
                output.Flush();
            }
            document.Save(output);
            Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.WriteTrustInfo t={0}", Environment.TickCount - t1));
        }
    }
}