File: System\Security\Cryptography\Xml\TransformChain.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography.Xml\src\System.Security.Cryptography.Xml.csproj (System.Security.Cryptography.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// This file contains the classes necessary to represent the Transform processing model used in
// XMLDSIG. The basic idea is as follows. A Reference object contains within it a TransformChain, which
// is an ordered set of XMLDSIG transforms (represented by <Transform>...</Transform> clauses in the XML).
// A transform in XMLDSIG operates on an input of either an octet stream or a node set and produces
// either an octet stream or a node set. Conversion between the two types is performed by parsing (octet stream->
// node set) or C14N (node set->octet stream). We generalize this slightly to allow a transform to define an array of
// input and output types (because I believe in the future there will be perf gains by being smarter about what goes in & comes out)
// Each XMLDSIG transform is represented by a subclass of the abstract Transform class. We need to use CryptoConfig to
// associate Transform classes with URLs for transform extensibility, but that's a future concern for this code.
// Once the Transform chain is constructed, call TransformToOctetStream to convert some sort of input type to an octet
// stream. (We only bother implementing that much now since every use of transform chains in XmlDsig ultimately yields something to hash).
 
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Xml;
 
namespace System.Security.Cryptography.Xml
{
    // This class represents an ordered chain of transforms
 
    public class TransformChain
    {
        private readonly ArrayList _transforms;
 
        public TransformChain()
        {
            _transforms = new ArrayList();
        }
 
        public void Add(Transform transform)
        {
            if (transform != null)
                _transforms.Add(transform);
        }
 
        public IEnumerator GetEnumerator()
        {
            return _transforms.GetEnumerator();
        }
 
        public int Count
        {
            get { return _transforms.Count; }
        }
 
        public Transform this[int index]
        {
            get
            {
                if (index >= _transforms.Count)
                    throw new ArgumentException(SR.ArgumentOutOfRange_IndexMustBeLess, nameof(index));
                return (Transform)_transforms[index]!;
            }
        }
 
        // The goal behind this method is to pump the input stream through the transforms and get back something that
        // can be hashed
        internal Stream TransformToOctetStream(object? inputObject, XmlResolver? resolver, string? baseUri)
        {
            object? currentInput = inputObject;
            foreach (Transform transform in _transforms)
            {
                if (currentInput == null || transform.AcceptsType(currentInput.GetType()))
                {
                    //in this case, no translation necessary, pump it through
                    transform.Resolver = resolver;
                    transform.BaseURI = baseUri;
                    transform.LoadInput(currentInput!);
                    currentInput = transform.GetOutput();
                }
                else
                {
                    // We need translation
                    // For now, we just know about Stream->{XmlNodeList,XmlDocument} and {XmlNodeList,XmlDocument}->Stream
                    if (currentInput is Stream)
                    {
                        if (transform.AcceptsType(typeof(XmlDocument)))
                        {
                            Stream currentInputStream = (currentInput as Stream)!;
                            XmlDocument doc = new XmlDocument();
                            doc.PreserveWhitespace = true;
                            XmlReader valReader = Utils.PreProcessStreamInput(currentInputStream, resolver, baseUri);
                            doc.Load(valReader);
                            transform.LoadInput(doc);
                            currentInputStream.Close();
                            currentInput = transform.GetOutput();
                            continue;
                        }
                        else
                        {
                            throw new CryptographicException(SR.Cryptography_Xml_TransformIncorrectInputType);
                        }
                    }
                    if (currentInput is XmlNodeList)
                    {
                        if (transform.AcceptsType(typeof(Stream)))
                        {
                            CanonicalXml c14n = new CanonicalXml((XmlNodeList)currentInput, resolver, false);
                            MemoryStream ms = new MemoryStream(c14n.GetBytes());
                            transform.LoadInput(ms);
                            currentInput = transform.GetOutput();
                            ms.Close();
                            continue;
                        }
                        else
                        {
                            throw new CryptographicException(SR.Cryptography_Xml_TransformIncorrectInputType);
                        }
                    }
                    if (currentInput is XmlDocument)
                    {
                        if (transform.AcceptsType(typeof(Stream)))
                        {
                            CanonicalXml c14n = new CanonicalXml((XmlDocument)currentInput, resolver);
                            MemoryStream ms = new MemoryStream(c14n.GetBytes());
                            transform.LoadInput(ms);
                            currentInput = transform.GetOutput();
                            ms.Close();
                            continue;
                        }
                        else
                        {
                            throw new CryptographicException(SR.Cryptography_Xml_TransformIncorrectInputType);
                        }
                    }
                    throw new CryptographicException(SR.Cryptography_Xml_TransformIncorrectInputType);
                }
            }
 
            // Final processing, either we already have a stream or have to canonicalize
            if (currentInput is Stream inputStream)
            {
                return inputStream;
            }
            if (currentInput is XmlNodeList)
            {
                CanonicalXml c14n = new CanonicalXml((XmlNodeList)currentInput, resolver, false);
                MemoryStream? ms = new MemoryStream(c14n.GetBytes());
                return ms;
            }
            if (currentInput is XmlDocument)
            {
                CanonicalXml c14n = new CanonicalXml((XmlDocument)currentInput, resolver);
                MemoryStream? ms = new MemoryStream(c14n.GetBytes());
                return ms;
            }
            throw new CryptographicException(SR.Cryptography_Xml_TransformIncorrectInputType);
        }
 
        internal XmlElement GetXml(XmlDocument document, string ns)
        {
            XmlElement transformsElement = document.CreateElement("Transforms", ns);
            foreach (Transform transform in _transforms)
            {
                if (transform != null)
                {
                    // Construct the individual transform element
                    XmlElement transformElement = transform.GetXml(document);
                    if (transformElement != null)
                        transformsElement.AppendChild(transformElement);
                }
            }
            return transformsElement;
        }
 
        [RequiresDynamicCode(CryptoHelpers.XsltRequiresDynamicCodeMessage)]
        [RequiresUnreferencedCode(CryptoHelpers.CreateFromNameUnreferencedCodeMessage)]
        internal void LoadXml(XmlElement value)
        {
            if (value is null)
            {
                throw new ArgumentNullException(nameof(value));
            }
 
            XmlNamespaceManager nsm = new XmlNamespaceManager(value.OwnerDocument.NameTable);
            nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
 
            XmlNodeList? transformNodes = value.SelectNodes("ds:Transform", nsm);
            if (transformNodes!.Count == 0)
                throw new CryptographicException(SR.Cryptography_Xml_InvalidElement, "Transforms");
 
            _transforms.Clear();
            for (int i = 0; i < transformNodes.Count; ++i)
            {
                XmlElement transformElement = (XmlElement)transformNodes.Item(i)!;
                string? algorithm = Utils.GetAttribute(transformElement, "Algorithm", SignedXml.XmlDsigNamespaceUrl);
                Transform? transform = CryptoHelpers.CreateFromName<Transform>(algorithm);
                if (transform == null)
                    throw new CryptographicException(SR.Cryptography_Xml_UnknownTransform);
                // let the transform read the children of the transformElement for data
                transform.LoadInnerXml(transformElement.ChildNodes);
                _transforms.Add(transform);
            }
        }
    }
}