//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- //Turn on to log info about the before and after headers/messages //#define DEBUG_MARSHALING namespace System.ServiceModel.Routing { using System; using System.Collections.Generic; using System.Globalization; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Dispatcher; using System.Runtime; using System.ServiceModel.Description; using System.ServiceModel.Configuration; using System.ServiceModel.Security; using System.Xml; using SR2 = System.ServiceModel.Routing.SR; public class SoapProcessingBehavior : IEndpointBehavior { const string IncomingViaName = "IncomingVia"; const string IncomingHttpRequestName = "IncomingHttpRequest"; public SoapProcessingBehavior() { this.ProcessMessages = true; } public bool ProcessMessages { get; set; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { if (endpoint == null) { throw FxTrace.Exception.ArgumentNull("endpoint"); } if (this.ProcessMessages) { SoapProcessingInspector inspector = new SoapProcessingInspector(endpoint, clientRuntime); clientRuntime.MessageInspectors.Add(inspector); clientRuntime.CallbackDispatchRuntime.MessageInspectors.Add(inspector); foreach (ClientOperation clientOp in clientRuntime.Operations) { clientOp.ParameterInspectors.Add(inspector); } } } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { throw FxTrace.Exception.AsError(new NotSupportedException(SR2.MarshalingBehaviorNotSupported)); } public void Validate(ServiceEndpoint endpoint) { } class SoapProcessingInspector : IClientMessageInspector, IDispatchMessageInspector, IParameterInspector { static HashSet addressingHeadersToFlow = InitializeHeadersToFlow(); bool manualAddressing; MessageVersion sourceMessageVersion; MessageVersion sendMessageVersion; public SoapProcessingInspector(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { Fx.Assert(endpoint != null, "endpoint cannot be null"); this.sendMessageVersion = endpoint.Binding.MessageVersion; this.manualAddressing = clientRuntime.ManualAddressing; } void IParameterInspector.AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { // This call occurs AFTER IClientMessageInspector.AfterReceiveReply. // We don't need to do any additional marshaling here. } object IParameterInspector.BeforeCall(string operationName, object[] inputs) { if (inputs == null || inputs.Length == 0) { throw FxTrace.Exception.ArgumentNullOrEmpty("inputs"); } //We have to remove some addressing headers to avoid errors before the //Soap Processing MessageInspector gets called. See CSDMain 117167. Message request = (Message)inputs[0]; this.PreProcess(request); return null; } void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState) { if (reply != null) { Message originalMessage = (Message)correlationState; MessageHeaders originalHeaders = originalMessage.Headers; Uri to = originalHeaders.To; MessageVersion version = originalMessage.Version; reply = MarshalMessage(reply, to, version); //BasicHttpContext looks at the original request message when //constructing the Set-Cookie header, need to put these back. Uri incomingVia; MessageProperties originalProperties = originalMessage.Properties; if (originalProperties.TryGetValue(IncomingViaName, out incomingVia)) { originalProperties.Via = incomingVia; } object incomingHttpRequest; if (originalProperties.TryGetValue(IncomingHttpRequestName, out incomingHttpRequest)) { originalProperties[HttpRequestMessageProperty.Name] = incomingHttpRequest; } //Reponses from BasicHttp (Addressing.None) don't have any action. If we're marshaling //to any addressing version other than none, we create a ReplyAction here MessageHeaders replyHeaders = reply.Headers; if (replyHeaders.Action == null && version.Addressing != AddressingVersion.None && originalHeaders.Action != null) { replyHeaders.Action = originalHeaders.Action + "Response"; } } } object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel) { Message originalRequest = request; if (this.sourceMessageVersion == null) { this.sourceMessageVersion = originalRequest.Version; } request = MarshalMessage(request, request.Headers.To, sendMessageVersion); return originalRequest; } object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { request = MarshalMessage(request, request.Headers.To, this.sourceMessageVersion); return null; } void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState) { if (reply != null) { reply = MarshalMessage(reply, reply.Headers.To, sendMessageVersion); } } static HashSet InitializeHeadersToFlow() { HashSet headersToFlow = new HashSet(StringComparer.Ordinal); headersToFlow.Add("Action"); headersToFlow.Add("MessageID"); headersToFlow.Add("RelatesTo"); headersToFlow.Add("To"); return headersToFlow; } void PreProcess(Message message) { //If the user does not have manual addressing enabled then clear these out if (!this.manualAddressing) { MessageHeaders headers = message.Headers; string addressingNamespace = RoutingUtilities.GetAddressingNamespace(headers.MessageVersion.Addressing); //Go through in reverse to reduce shifting after RemoveAt(i) for (int i = headers.Count - 1; i >= 0; --i) { MessageHeaderInfo header = headers[i]; if (string.Equals(header.Namespace, addressingNamespace, StringComparison.Ordinal)) { if (!addressingHeadersToFlow.Contains(header.Name)) { headers.RemoveAt(i); } } } } } internal Message MarshalMessage(Message source, Uri to, MessageVersion targetVersion) { Message result; MessageHeaders sourceHeaders = source.Headers; MessageVersion sourceVersion = source.Version; UnderstoodHeaders understoodHeaders = sourceHeaders.UnderstoodHeaders; HashSet understoodHeadersSet = CreateKeys(understoodHeaders); #if DEBUG_MARSHALING System.Text.StringBuilder details = new System.Text.StringBuilder(); details.AppendFormat("Original Message:\r\n{0}\r\n", source); details.AppendLine("Understood Headers:"); foreach (MessageHeaderInfo understoodHeader in understoodHeaders) { details.AppendFormat("\t{0}\t({1})\r\n", understoodHeader.Name, understoodHeader.Namespace); } details.AppendLine("Properties:"); foreach (KeyValuePair item in source.Properties) { details.AppendFormat("\t{0}\t({1})\r\n", item.Key, item.Value); } #endif //DEBUG_MARSHALING //if we've understood and verified the security of the message, we need to create a new message if (sourceVersion == targetVersion && !RoutingUtilities.IsMessageUsingWSSecurity(understoodHeaders)) { FilterHeaders(sourceHeaders, understoodHeadersSet); FilterProperties(source.Properties); result = source; } else { if (source.IsFault) { MessageFault messageFault = MessageFault.CreateFault(source, int.MaxValue); string action = sourceHeaders.Action; if (string.Equals(action, sourceVersion.Addressing.DefaultFaultAction, StringComparison.Ordinal)) { //The action was the default for the sourceVersion set it to the default for the targetVersion. action = targetVersion.Addressing.DefaultFaultAction; } result = Message.CreateMessage(targetVersion, messageFault, action); } else if (source.IsEmpty) { result = Message.CreateMessage(targetVersion, sourceHeaders.Action); } else { XmlDictionaryReader bodyReader = source.GetReaderAtBodyContents(); result = Message.CreateMessage(targetVersion, sourceHeaders.Action, bodyReader); } CloneHeaders(result.Headers, sourceHeaders, to, understoodHeadersSet); CloneProperties(result.Properties, source.Properties); } #if DEBUG_MARSHALING details.AppendFormat("\r\nMarshaled Message:\r\n{0}\r\n", result); details.AppendLine("Properties:"); foreach (KeyValuePair item in result.Properties) { details.AppendFormat("\t{0}\t({1})\r\n", item.Key, item.Value); } System.Diagnostics.Trace.WriteLine(details); TD.RoutingServiceDisplayConfig(details.ToString(), ""); #endif //DEBUG_MARSHALING return result; } static HashSet CreateKeys(UnderstoodHeaders headers) { HashSet toReturn = new HashSet(); foreach (MessageHeaderInfo header in headers) { toReturn.Add(MessageHeaderKey(header)); } return toReturn; } static string MessageHeaderKey(MessageHeaderInfo header) { return header.Name + "+" + header.Namespace; } void FilterHeaders(MessageHeaders headers, HashSet understoodHeadersSet) { string addressingNamespace = RoutingUtilities.GetAddressingNamespace(headers.MessageVersion.Addressing); //Go in reverse to reduce shifting after RemoveAt(i) for (int i = headers.Count - 1; i >= 0; --i) { MessageHeaderInfo header = headers[i]; bool removeHeader = false; if (string.Equals(header.Namespace, addressingNamespace, StringComparison.Ordinal) && (addressingHeadersToFlow.Contains(header.Name) || this.manualAddressing)) { continue; } if (understoodHeadersSet.Contains(MessageHeaderKey(header))) { // This header was understood at this endpoint, do _not_ flow it removeHeader = true; } else if (ActorIsNextDestination(header, headers.MessageVersion)) { //This was a header targeted at the SOAP Intermediary ("actor/next", which is us) //It can explicitly tell us to relay this header when we don't understand it. if (!header.Relay) { removeHeader = true; } } if (removeHeader) { headers.RemoveAt(i); } } } static bool ActorIsNextDestination(MessageHeaderInfo header, MessageVersion messageVersion) { return (header.Actor != null && string.Equals(header.Actor, messageVersion.Envelope.NextDestinationActorValue)); } void CloneHeaders(MessageHeaders targetHeaders, MessageHeaders sourceHeaders, Uri to, HashSet understoodHeadersSet) { for (int i = 0; i < sourceHeaders.Count; ++i) { MessageHeaderInfo header = sourceHeaders[i]; if (!understoodHeadersSet.Contains(MessageHeaderKey(header))) { //If Actor is SOAP Intermediary ("*actor/next" which is us) check the Relay flag if (!ActorIsNextDestination(header, sourceHeaders.MessageVersion) || header.Relay) { //Always wrap the header because BufferedHeader isn't smart enough to allow custom //headers to switch message versions MessageHeader messageHeader = new DelegatingHeader(header, sourceHeaders); targetHeaders.Add(messageHeader); } } } // To and Action (already specified) are 'special' and may be set even with AddressingVersion.None targetHeaders.To = to; if (targetHeaders.MessageVersion.Addressing != AddressingVersion.None) { //These are used as correlation IDs. Copy these regardless of manual addressing. targetHeaders.MessageId = sourceHeaders.MessageId; targetHeaders.RelatesTo = sourceHeaders.RelatesTo; if (this.manualAddressing) { //These are addresses, only copy when ManualAddressing is enabled targetHeaders.FaultTo = sourceHeaders.FaultTo; targetHeaders.ReplyTo = sourceHeaders.ReplyTo; targetHeaders.From = sourceHeaders.From; } } } static void CloneProperties(MessageProperties destination, MessageProperties source) { MessageEncoder encoder = destination.Encoder; destination.CopyProperties(source); destination.Encoder = encoder; FilterProperties(destination); } static void FilterProperties(MessageProperties destination) { // We have the clear out the HTTP Req/Resp props, so we don't accidentally // tell the outbound binding to use whatever Content-Type/Method/QueryString // that the inboudnd message/response used. Otherwise BasicHttp<->WsHttp won't work. object incomingHttpRequest; if (destination.TryGetValue(HttpRequestMessageProperty.Name, out incomingHttpRequest)) { //Store the inbound value for later retoration destination[IncomingHttpRequestName] = incomingHttpRequest; destination.Remove(HttpRequestMessageProperty.Name); } destination.Remove(HttpResponseMessageProperty.Name); //Preserve the Via on the outbound message, HTTP Context using cookies looks at this value //on the original request when sending the response "Set-Cookie" header. Uri incomingVia = destination.Via; if (incomingVia != null) { destination[IncomingViaName] = incomingVia; } } } } }