File: System\Web\Services\Protocols\SoapHeader.cs
Project: ndp\cdf\src\NetFx20\System.Web.Services\System.Web.Services.csproj (System.Web.Services)
//------------------------------------------------------------------------------
// <copyright file="SoapHeader.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Web.Services.Protocols {
    using System.Web.Services;
    using System.Xml.Serialization;
    using System;
    using System.Reflection;
    using System.Xml;
    using System.Collections;
    using System.IO;
    using System.ComponentModel;
    using System.Threading;
    using System.Security.Permissions;
    using System.Runtime.InteropServices;
    using System.Web.Services.Diagnostics;
 
    /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader"]/*' />
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    [XmlType(IncludeInSchema = false), SoapType(IncludeInSchema = false)]
    public abstract class SoapHeader {
        string actor;
        bool mustUnderstand;
        bool didUnderstand;
        bool relay;
        // prop getters should return a value when version == Default or when version == correctVersion.
        // all version tests in getters should use != incorrectVersion
        internal SoapProtocolVersion version = SoapProtocolVersion.Default;
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.MustUnderstandEncoded"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlAttribute("mustUnderstand", Namespace = "http://schemas.xmlsoap.org/soap/envelope/"),
        SoapAttribute("mustUnderstand", Namespace = "http://schemas.xmlsoap.org/soap/envelope/"),
        DefaultValue("0")]
        public string EncodedMustUnderstand {
            get { return version != SoapProtocolVersion.Soap12 && MustUnderstand ? "1" : "0"; }
            set {
                switch (value) {
                    case "false":
                    case "0": MustUnderstand = false; break;
                    case "true":
                    case "1": MustUnderstand = true; break;
                    default: throw new ArgumentException(Res.GetString(Res.WebHeaderInvalidMustUnderstand, value));
                }
            }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.EncodedMustUnderstand12"]/*' />
        [XmlAttribute("mustUnderstand", Namespace = Soap12.Namespace),
        SoapAttribute("mustUnderstand", Namespace = Soap12.Namespace),
        DefaultValue("0"),
        ComVisible(false)]
        public string EncodedMustUnderstand12 {
            get { return version != SoapProtocolVersion.Soap11 && MustUnderstand ? "1" : "0"; }
            set {
                EncodedMustUnderstand = value;
            }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.MustUnderstand"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlIgnore, SoapIgnore]
        public bool MustUnderstand {
            get { return InternalMustUnderstand; }
            set { InternalMustUnderstand = value; }
        }
 
        internal virtual bool InternalMustUnderstand {
            [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
            get { return mustUnderstand; }
            [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
            set { mustUnderstand = value; }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.Actor"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlAttribute("actor", Namespace = "http://schemas.xmlsoap.org/soap/envelope/"),
        SoapAttribute("actor", Namespace = "http://schemas.xmlsoap.org/soap/envelope/"),
        DefaultValue("")]
        public string Actor {
            get { return version != SoapProtocolVersion.Soap12 ? InternalActor : ""; }
            set { InternalActor = value; }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.Role"]/*' />
        [XmlAttribute("role", Namespace = Soap12.Namespace),
        SoapAttribute("role", Namespace = Soap12.Namespace),
        DefaultValue(""),
        ComVisible(false)]
        public string Role {
            get { return version != SoapProtocolVersion.Soap11 ? InternalActor : ""; }
            set { InternalActor = value; }
        }
 
        internal virtual string InternalActor {
            [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
            get { return actor == null ? string.Empty : actor; }
            [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
            set { actor = value; }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.DidUnderstand"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlIgnore, SoapIgnore]
        public bool DidUnderstand {
            get { return didUnderstand; }
            set { didUnderstand = value; }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.EncodedRelay"]/*' />
        [XmlAttribute("relay", Namespace = Soap12.Namespace),
        SoapAttribute("relay", Namespace = Soap12.Namespace),
        DefaultValue("0"),
        ComVisible(false)]
        public string EncodedRelay {
            get { return version != SoapProtocolVersion.Soap11 && Relay ? "1" : "0"; }
            set {
                switch (value) {
                    case "false":
                    case "0": Relay = false; break;
                    case "true":
                    case "1": Relay = true; break;
                    default: throw new ArgumentException(Res.GetString(Res.WebHeaderInvalidRelay, value));
                }
            }
        }
 
        /// <include file='doc\SoapHeader.uex' path='docs/doc[@for="SoapHeader.Relay"]/*' />
        [XmlIgnore, SoapIgnore, ComVisible(false)]
        public bool Relay {
            get { return InternalRelay; }
            set { InternalRelay = value; }
        }
 
        internal virtual bool InternalRelay {
            [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
            get { return relay; }
            [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
            set { relay = value; }
        }
    }
 
    [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
    public sealed class SoapHeaderMapping {
        //
        // Block external construction
        //
        internal SoapHeaderMapping() {
        }
 
        internal Type headerType;
        internal bool repeats;
        internal bool custom;
        internal SoapHeaderDirection direction;
        internal MemberInfo memberInfo;
 
        public Type HeaderType {
            get {
                return headerType;
            }
        }
 
        public bool Repeats {
            get {
                return repeats;
            }
        }
 
        public bool Custom {
            get {
                return custom;
            }
        }
 
        public SoapHeaderDirection Direction {
            get {
                return direction;
            }
        }
 
        public MemberInfo MemberInfo {
            get {
                return memberInfo;
            }
        }
    }
 
    [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
    public sealed class SoapHeaderHandling {
        SoapHeaderCollection unknownHeaders;
        SoapHeaderCollection unreferencedHeaders;
        int currentThread;
        string envelopeNS;
 
        void OnUnknownElement(object sender, XmlElementEventArgs e) {
            if (Thread.CurrentThread.GetHashCode() != this.currentThread) return;
            if (e.Element == null) return;
            SoapUnknownHeader header = new SoapUnknownHeader();
            header.Element = e.Element;
            unknownHeaders.Add(header);
        }
 
        void OnUnreferencedObject(object sender, UnreferencedObjectEventArgs e) {
            if (Thread.CurrentThread.GetHashCode() != this.currentThread) return;
            object o = e.UnreferencedObject;
            if (o == null) return;
            if (typeof(SoapHeader).IsAssignableFrom(o.GetType())) {
                unreferencedHeaders.Add((SoapHeader)o);
            }
        }
 
        // return first missing header name;
        public string ReadHeaders(XmlReader reader, XmlSerializer serializer, SoapHeaderCollection headers, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, string envelopeNS, string encodingStyle, bool checkRequiredHeaders) {
            string missingHeader = null;
            reader.MoveToContent();
            if (!reader.IsStartElement(Soap.Element.Header, envelopeNS)) {
                if (checkRequiredHeaders && mappings != null && mappings.Length > 0)
                    missingHeader = GetHeaderElementName(mappings[0].headerType);
                return missingHeader;
            }
            if (reader.IsEmptyElement) { reader.Skip(); return missingHeader; }
 
            this.unknownHeaders = new SoapHeaderCollection();
            this.unreferencedHeaders = new SoapHeaderCollection();
            // thread hash code is used to differentiate between deserializations in event callbacks
            this.currentThread = Thread.CurrentThread.GetHashCode();
            this.envelopeNS = envelopeNS;
 
            int depth = reader.Depth;
            reader.ReadStartElement();
            reader.MoveToContent();
 
            XmlDeserializationEvents events = new XmlDeserializationEvents();
            events.OnUnknownElement = new XmlElementEventHandler(this.OnUnknownElement);
            events.OnUnreferencedObject = new UnreferencedObjectEventHandler(this.OnUnreferencedObject);
 
            TraceMethod caller = Tracing.On ? new TraceMethod(this, "ReadHeaders") : null;
            if (Tracing.On) Tracing.Enter(Tracing.TraceId(Res.TraceReadHeaders), caller, new TraceMethod(serializer, "Deserialize", reader, encodingStyle));
            object[] headerValues = (object[])serializer.Deserialize(reader, encodingStyle, events);
            if (Tracing.On) Tracing.Exit(Tracing.TraceId(Res.TraceReadHeaders), caller);
            for (int i = 0; i < headerValues.Length; i++) {
                if (headerValues[i] != null) {
                    SoapHeader header = (SoapHeader)headerValues[i];
                    header.DidUnderstand = true;
                    headers.Add(header);
                }
                else if (checkRequiredHeaders) {
                    // run time check for R2738 A MESSAGE MUST include all soapbind:headers specified on a wsdl:input or wsdl:output of a wsdl:operationwsdl:binding that describes it. 
                    if (missingHeader == null)
                        missingHeader = GetHeaderElementName(mappings[i].headerType);
                }
            }
            this.currentThread = 0;
            this.envelopeNS = null;
 
            foreach (SoapHeader header in this.unreferencedHeaders) {
                headers.Add(header);
            }
            this.unreferencedHeaders = null;
 
            foreach (SoapHeader header in this.unknownHeaders) {
                headers.Add(header);
            }
            this.unknownHeaders = null;
 
            // Consume soap:Body and soap:Envelope closing tags
            while (depth < reader.Depth && reader.Read()) {
                // Nothing, just read on
            }
            // consume end tag
            if (reader.NodeType == XmlNodeType.EndElement) {
                reader.Read();
            }
 
            return missingHeader;
        }
 
        public static void WriteHeaders(XmlWriter writer, XmlSerializer serializer, SoapHeaderCollection headers, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, bool isEncoded, string defaultNS, bool serviceDefaultIsEncoded, string envelopeNS) {
            if (headers.Count == 0) return;
            writer.WriteStartElement(Soap.Element.Header, envelopeNS);
            SoapProtocolVersion version;
            string encodingStyle;
            if (envelopeNS == Soap12.Namespace) {
                version = SoapProtocolVersion.Soap12;
                encodingStyle = Soap12.Encoding;
            }
            else {
                version = SoapProtocolVersion.Soap11;
                encodingStyle = Soap.Encoding;
            }
 
            int unknownHeaderCount = 0;
            ArrayList otherHeaders = new ArrayList();
            SoapHeader[] headerArray = new SoapHeader[mappings.Length];
            bool[] headerSet = new bool[headerArray.Length];
            for (int i = 0; i < headers.Count; i++) {
                SoapHeader header = headers[i];
                if (header == null) continue;
                int headerPosition;
                header.version = version;
                if (header is SoapUnknownHeader) {
                    otherHeaders.Add(header);
                    unknownHeaderCount++;
                }
                else if ((headerPosition = FindMapping(mappings, header, direction)) >= 0 && !headerSet[headerPosition]) {
                    headerArray[headerPosition] = header;
                    headerSet[headerPosition] = true;
                }
                else {
                    otherHeaders.Add(header);
                }
            }
            int otherHeaderCount = otherHeaders.Count - unknownHeaderCount;
            if (isEncoded && otherHeaderCount > 0) {
                SoapHeader[] newHeaderArray = new SoapHeader[mappings.Length + otherHeaderCount];
                headerArray.CopyTo(newHeaderArray, 0);
 
                // fill in the non-statically known headers (otherHeaders) starting after the statically-known ones
                int count = mappings.Length;
                for (int i = 0; i < otherHeaders.Count; i++) {
                    if (!(otherHeaders[i] is SoapUnknownHeader))
                        newHeaderArray[count++] = (SoapHeader)otherHeaders[i];
                }
 
                headerArray = newHeaderArray;
            }
 
            TraceMethod caller = Tracing.On ? new TraceMethod(typeof(SoapHeaderHandling), "WriteHeaders") : null;
            if (Tracing.On) Tracing.Enter(Tracing.TraceId(Res.TraceWriteHeaders), caller, new TraceMethod(serializer, "Serialize", writer, headerArray, null, isEncoded ? encodingStyle : null, "h_"));
            serializer.Serialize(writer, headerArray, null, isEncoded ? encodingStyle : null, "h_");
            if (Tracing.On) Tracing.Exit(Tracing.TraceId(Res.TraceWriteHeaders), caller);
 
            foreach (SoapHeader header in otherHeaders) {
                if (header is SoapUnknownHeader) {
                    SoapUnknownHeader unknown = (SoapUnknownHeader)header;
                    if (unknown.Element != null)
                        unknown.Element.WriteTo(writer);
                }
                else if (!isEncoded) { // encoded headers already appended to members mapping
                    string ns = SoapReflector.GetLiteralNamespace(defaultNS, serviceDefaultIsEncoded);
                    XmlSerializer headerSerializer = new XmlSerializer(header.GetType(), ns);
 
                    if (Tracing.On) Tracing.Enter(Tracing.TraceId(Res.TraceWriteHeaders), caller, new TraceMethod(headerSerializer, "Serialize", writer, header));
                    headerSerializer.Serialize(writer, header);
                    if (Tracing.On) Tracing.Exit(Tracing.TraceId(Res.TraceWriteHeaders), caller);
                }
            }
 
            // reset the soap version
            for (int i = 0; i < headers.Count; i++) {
                SoapHeader header = headers[i];
                if (header != null)
                    header.version = SoapProtocolVersion.Default;
            }
 
            writer.WriteEndElement();
            writer.Flush();
        }
 
        public static void WriteUnknownHeaders(XmlWriter writer, SoapHeaderCollection headers, string envelopeNS) {
            bool first = true;
            foreach (SoapHeader header in headers) {
                SoapUnknownHeader unknown = header as SoapUnknownHeader;
                if (unknown != null) {
                    if (first) {
                        writer.WriteStartElement(Soap.Element.Header, envelopeNS);
                        first = false;
                    }
                    if (unknown.Element != null)
                        unknown.Element.WriteTo(writer);
                }
            }
            if (!first)
                writer.WriteEndElement(); // </soap:Header>
        }
 
        public static void SetHeaderMembers(SoapHeaderCollection headers, object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, bool client) {
            bool[] headerHandled = new bool[headers.Count];
            if (mappings != null) {
                for (int i = 0; i < mappings.Length; i++) {
                    SoapHeaderMapping mapping = mappings[i];
                    if ((mapping.direction & direction) == 0) continue;
                    if (mapping.repeats) {
                        ArrayList list = new ArrayList();
                        for (int j = 0; j < headers.Count; j++) {
                            SoapHeader header = headers[j];
                            if (headerHandled[j]) continue;
                            if (mapping.headerType.IsAssignableFrom(header.GetType())) {
                                list.Add(header);
                                headerHandled[j] = true;
                            }
                        }
                        MemberHelper.SetValue(mapping.memberInfo, target, list.ToArray(mapping.headerType));
                    }
                    else {
                        bool handled = false;
                        for (int j = 0; j < headers.Count; j++) {
                            SoapHeader header = headers[j];
                            if (headerHandled[j]) continue;
                            if (mapping.headerType.IsAssignableFrom(header.GetType())) {
                                if (handled) {
                                    header.DidUnderstand = false;
                                    continue;
                                }
                                handled = true;
                                MemberHelper.SetValue(mapping.memberInfo, target, header);
                                headerHandled[j] = true;
                            }
                        }
                    }
                }
            }
            for (int i = 0; i < headerHandled.Length; i++) {
                if (!headerHandled[i]) {
                    SoapHeader header = headers[i];
                    if (header.MustUnderstand && !header.DidUnderstand) {
                        throw new SoapHeaderException(Res.GetString(Res.WebCannotUnderstandHeader, GetHeaderElementName(header)),
                            new XmlQualifiedName(Soap.Code.MustUnderstand, Soap.Namespace));
                    }
                }
            }
        }
 
        public static void GetHeaderMembers(SoapHeaderCollection headers, object target, SoapHeaderMapping[] mappings, SoapHeaderDirection direction, bool client) {
            if (mappings == null || mappings.Length == 0) return;
            for (int i = 0; i < mappings.Length; i++) {
                SoapHeaderMapping mapping = mappings[i];
                if ((mapping.direction & direction) == 0) continue;
                object value = MemberHelper.GetValue(mapping.memberInfo, target);
                if (mapping.repeats) {
                    object[] values = (object[])value;
                    if (values == null) continue;
                    for (int j = 0; j < values.Length; j++) {
                        if (values[j] != null) headers.Add((SoapHeader)values[j]);
                    }
                }
                else {
                    if (value != null) headers.Add((SoapHeader)value);
                }
            }
        }
 
        public static void EnsureHeadersUnderstood(SoapHeaderCollection headers) {
            for (int i = 0; i < headers.Count; i++) {
                SoapHeader header = headers[i];
                if (header.MustUnderstand && !header.DidUnderstand) {
                    throw new SoapHeaderException(Res.GetString(Res.WebCannotUnderstandHeader, GetHeaderElementName(header)),
                        new XmlQualifiedName(Soap.Code.MustUnderstand, Soap.Namespace));
                }
            }
        }
 
        static int FindMapping(SoapHeaderMapping[] mappings, SoapHeader header, SoapHeaderDirection direction) {
            if (mappings == null || mappings.Length == 0) return -1;
            Type headerType = header.GetType();
            for (int i = 0; i < mappings.Length; i++) {
                SoapHeaderMapping mapping = mappings[i];
                if ((mapping.direction & direction) == 0) continue;
                if (!mapping.custom) continue;
                if (mapping.headerType.IsAssignableFrom(headerType)) {
                    return i;
                }
            }
            return -1;
        }
 
        static string GetHeaderElementName(Type headerType) {
            XmlReflectionImporter importer = SoapReflector.CreateXmlImporter(null, false);
 
            XmlTypeMapping mapping = importer.ImportTypeMapping(headerType);
            return mapping.XsdElementName;
        }
 
        static string GetHeaderElementName(SoapHeader header) {
            if (header is SoapUnknownHeader) {
                return ((SoapUnknownHeader)header).Element.LocalName;
            }
            else {
                return GetHeaderElementName(header.GetType());
            }
        }
    }
}