You've already forked linux-packaging-mono
Rewrite with hard-coded offsets into the PE file format to discern if a binary is PE32 or PE32+, and then to determine if it contains a "CLR Data Directory" entry that looks valid. Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native binaries, and a random assortment of garbage files. Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
4255 lines
170 KiB
C#
4255 lines
170 KiB
C#
//------------------------------------------------------------
|
||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
//------------------------------------------------------------
|
||
namespace System.ServiceModel.Channels
|
||
{
|
||
using System.Collections.Generic;
|
||
using System.Collections.Specialized;
|
||
using System.Diagnostics;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Net;
|
||
using System.Net.Http;
|
||
using System.Net.Http.Headers;
|
||
using System.Net.Mime;
|
||
using System.Net.Security;
|
||
using System.Net.Sockets;
|
||
using System.Reflection;
|
||
using System.Runtime;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Runtime.Diagnostics;
|
||
using System.Security.Authentication.ExtendedProtection;
|
||
using System.Security.Principal;
|
||
using System.ServiceModel;
|
||
using System.ServiceModel.Activation;
|
||
using System.ServiceModel.Diagnostics;
|
||
using System.ServiceModel.Diagnostics.Application;
|
||
using System.ServiceModel.Security;
|
||
using System.ServiceModel.Security.Tokens;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Xml;
|
||
|
||
// abstract out the common functionality of an "HttpInput"
|
||
abstract class HttpInput
|
||
{
|
||
const string multipartRelatedMediaType = "multipart/related";
|
||
const string startInfoHeaderParam = "start-info";
|
||
const string defaultContentType = "application/octet-stream";
|
||
|
||
BufferManager bufferManager;
|
||
bool isRequest;
|
||
MessageEncoder messageEncoder;
|
||
IHttpTransportFactorySettings settings;
|
||
bool streamed;
|
||
WebException webException;
|
||
Stream inputStream;
|
||
bool enableChannelBinding;
|
||
bool errorGettingInputStream;
|
||
|
||
protected HttpInput(IHttpTransportFactorySettings settings, bool isRequest, bool enableChannelBinding)
|
||
{
|
||
this.settings = settings;
|
||
this.bufferManager = settings.BufferManager;
|
||
this.messageEncoder = settings.MessageEncoderFactory.Encoder;
|
||
this.webException = null;
|
||
this.isRequest = isRequest;
|
||
this.inputStream = null;
|
||
this.enableChannelBinding = enableChannelBinding;
|
||
|
||
if (isRequest)
|
||
{
|
||
this.streamed = TransferModeHelper.IsRequestStreamed(settings.TransferMode);
|
||
}
|
||
else
|
||
{
|
||
this.streamed = TransferModeHelper.IsResponseStreamed(settings.TransferMode);
|
||
}
|
||
}
|
||
|
||
internal static HttpInput CreateHttpInput(HttpWebResponse httpWebResponse, IHttpTransportFactorySettings settings, ChannelBinding channelBinding)
|
||
{
|
||
return new WebResponseHttpInput(httpWebResponse, settings, channelBinding);
|
||
}
|
||
|
||
internal WebException WebException
|
||
{
|
||
get { return webException; }
|
||
set { webException = value; }
|
||
}
|
||
|
||
// Note: This method will return null in the case where throwOnError is false, and a non-fatal error occurs.
|
||
// Please exercice caution when passing in throwOnError = false. This should basically only be done in error
|
||
// code paths, or code paths where there is very good reason that you would not want this method to throw.
|
||
// When passing in throwOnError = false, please handle the case where this method returns null.
|
||
public Stream GetInputStream(bool throwOnError)
|
||
{
|
||
if (inputStream == null && (throwOnError || !this.errorGettingInputStream))
|
||
{
|
||
try
|
||
{
|
||
inputStream = GetInputStream();
|
||
this.errorGettingInputStream = false;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
this.errorGettingInputStream = true;
|
||
if (throwOnError || Fx.IsFatal(e))
|
||
{
|
||
throw;
|
||
}
|
||
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Warning);
|
||
}
|
||
}
|
||
|
||
return inputStream;
|
||
}
|
||
|
||
// -1 if chunked
|
||
public abstract long ContentLength { get; }
|
||
protected abstract string ContentTypeCore { get; }
|
||
protected abstract bool HasContent { get; }
|
||
protected abstract string SoapActionHeader { get; }
|
||
protected abstract Stream GetInputStream();
|
||
protected virtual ChannelBinding ChannelBinding { get { return null; } }
|
||
|
||
protected string ContentType
|
||
{
|
||
get
|
||
{
|
||
string contentType = ContentTypeCore;
|
||
|
||
if (string.IsNullOrEmpty(contentType))
|
||
{
|
||
return defaultContentType;
|
||
}
|
||
|
||
return contentType;
|
||
}
|
||
}
|
||
|
||
void ThrowMaxReceivedMessageSizeExceeded()
|
||
{
|
||
if (TD.MaxReceivedMessageSizeExceededIsEnabled())
|
||
{
|
||
TD.MaxReceivedMessageSizeExceeded(SR.GetString(SR.MaxReceivedMessageSizeExceeded, settings.MaxReceivedMessageSize));
|
||
}
|
||
|
||
if (isRequest)
|
||
{
|
||
ThrowHttpProtocolException(SR.GetString(SR.MaxReceivedMessageSizeExceeded, settings.MaxReceivedMessageSize), HttpStatusCode.RequestEntityTooLarge);
|
||
}
|
||
else
|
||
{
|
||
string message = SR.GetString(SR.MaxReceivedMessageSizeExceeded, settings.MaxReceivedMessageSize);
|
||
Exception inner = new QuotaExceededException(message);
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(message, inner));
|
||
}
|
||
}
|
||
|
||
Message DecodeBufferedMessage(ArraySegment<byte> buffer, Stream inputStream)
|
||
{
|
||
try
|
||
{
|
||
// if we're chunked, make sure we've consumed the whole body
|
||
if (ContentLength == -1 && buffer.Count == settings.MaxReceivedMessageSize)
|
||
{
|
||
byte[] extraBuffer = new byte[1];
|
||
int extraReceived = inputStream.Read(extraBuffer, 0, 1);
|
||
if (extraReceived > 0)
|
||
{
|
||
ThrowMaxReceivedMessageSizeExceeded();
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
return messageEncoder.ReadMessage(buffer, bufferManager, ContentType);
|
||
}
|
||
catch (XmlException xmlException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.MessageXmlProtocolError), xmlException));
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
inputStream.Close();
|
||
}
|
||
}
|
||
|
||
Message ReadBufferedMessage(Stream inputStream)
|
||
{
|
||
ArraySegment<byte> messageBuffer = GetMessageBuffer();
|
||
byte[] buffer = messageBuffer.Array;
|
||
int offset = 0;
|
||
int count = messageBuffer.Count;
|
||
|
||
while (count > 0)
|
||
{
|
||
int bytesRead = inputStream.Read(buffer, offset, count);
|
||
if (bytesRead == 0) // EOF
|
||
{
|
||
if (ContentLength != -1)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.HttpContentLengthIncorrect)));
|
||
}
|
||
|
||
break;
|
||
}
|
||
count -= bytesRead;
|
||
offset += bytesRead;
|
||
}
|
||
|
||
return DecodeBufferedMessage(new ArraySegment<byte>(buffer, 0, offset), inputStream);
|
||
}
|
||
|
||
Message ReadChunkedBufferedMessage(Stream inputStream)
|
||
{
|
||
try
|
||
{
|
||
return messageEncoder.ReadMessage(inputStream, bufferManager, settings.MaxBufferSize, ContentType);
|
||
}
|
||
catch (XmlException xmlException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.MessageXmlProtocolError), xmlException));
|
||
}
|
||
}
|
||
|
||
Message ReadStreamedMessage(Stream inputStream)
|
||
{
|
||
MaxMessageSizeStream maxMessageSizeStream = new MaxMessageSizeStream(inputStream, settings.MaxReceivedMessageSize);
|
||
|
||
try
|
||
{
|
||
return messageEncoder.ReadMessage(maxMessageSizeStream, settings.MaxBufferSize, ContentType);
|
||
}
|
||
catch (XmlException xmlException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.MessageXmlProtocolError), xmlException));
|
||
}
|
||
}
|
||
|
||
protected abstract void AddProperties(Message message);
|
||
|
||
void ApplyChannelBinding(Message message)
|
||
{
|
||
if (this.enableChannelBinding)
|
||
{
|
||
ChannelBindingUtility.TryAddToMessage(this.ChannelBinding, message, true);
|
||
}
|
||
}
|
||
|
||
// makes sure that appropriate HTTP level headers are included in the received Message
|
||
Exception ProcessHttpAddressing(Message message)
|
||
{
|
||
Exception result = null;
|
||
AddProperties(message);
|
||
|
||
// check if user is receiving WS-1 messages
|
||
if (message.Version.Addressing == AddressingVersion.None)
|
||
{
|
||
bool actionAbsent = false;
|
||
try
|
||
{
|
||
actionAbsent = (message.Headers.Action == null);
|
||
}
|
||
catch (XmlException e)
|
||
{
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
}
|
||
catch (CommunicationException e)
|
||
{
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
}
|
||
|
||
if (!actionAbsent)
|
||
{
|
||
result = new ProtocolException(SR.GetString(SR.HttpAddressingNoneHeaderOnWire,
|
||
XD.AddressingDictionary.Action.Value));
|
||
}
|
||
|
||
bool toAbsent = false;
|
||
try
|
||
{
|
||
toAbsent = (message.Headers.To == null);
|
||
}
|
||
catch (XmlException e)
|
||
{
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
}
|
||
catch (CommunicationException e)
|
||
{
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
}
|
||
|
||
if (!toAbsent)
|
||
{
|
||
result = new ProtocolException(SR.GetString(SR.HttpAddressingNoneHeaderOnWire,
|
||
XD.AddressingDictionary.To.Value));
|
||
}
|
||
message.Headers.To = message.Properties.Via;
|
||
}
|
||
|
||
if (isRequest)
|
||
{
|
||
string action = null;
|
||
|
||
if (message.Version.Envelope == EnvelopeVersion.Soap11)
|
||
{
|
||
action = SoapActionHeader;
|
||
}
|
||
else if (message.Version.Envelope == EnvelopeVersion.Soap12 && !String.IsNullOrEmpty(ContentType))
|
||
{
|
||
ContentType parsedContentType = new ContentType(ContentType);
|
||
|
||
if (parsedContentType.MediaType == multipartRelatedMediaType && parsedContentType.Parameters.ContainsKey(startInfoHeaderParam))
|
||
{
|
||
// fix to grab action from start-info as stated in RFC2387
|
||
action = new ContentType(parsedContentType.Parameters[startInfoHeaderParam]).Parameters["action"];
|
||
}
|
||
if (action == null)
|
||
{
|
||
// only if we can't find an action inside start-info
|
||
action = parsedContentType.Parameters["action"];
|
||
}
|
||
}
|
||
|
||
if (action != null)
|
||
{
|
||
action = UrlUtility.UrlDecode(action, Encoding.UTF8);
|
||
|
||
if (action.Length >= 2 && action[0] == '"' && action[action.Length - 1] == '"')
|
||
{
|
||
action = action.Substring(1, action.Length - 2);
|
||
}
|
||
|
||
if (message.Version.Addressing == AddressingVersion.None)
|
||
{
|
||
message.Headers.Action = action;
|
||
}
|
||
|
||
try
|
||
{
|
||
|
||
if (action.Length > 0 && string.Compare(message.Headers.Action, action, StringComparison.Ordinal) != 0)
|
||
{
|
||
result = new ActionMismatchAddressingException(SR.GetString(SR.HttpSoapActionMismatchFault,
|
||
message.Headers.Action, action), message.Headers.Action, action);
|
||
}
|
||
|
||
}
|
||
catch (XmlException e)
|
||
{
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
}
|
||
catch (CommunicationException e)
|
||
{
|
||
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
}
|
||
}
|
||
}
|
||
|
||
ApplyChannelBinding(message);
|
||
|
||
if (DiagnosticUtility.ShouldUseActivity)
|
||
{
|
||
TraceUtility.TransferFromTransport(message);
|
||
}
|
||
if (DiagnosticUtility.ShouldTraceInformation)
|
||
{
|
||
TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.MessageReceived, SR.GetString(SR.TraceCodeMessageReceived),
|
||
MessageTransmitTraceRecord.CreateReceiveTraceRecord(message), this, null, message);
|
||
}
|
||
|
||
// MessageLogger doesn't log AddressingVersion.None in the encoder since we want to make sure we log
|
||
// as much of the message as possible. Here we log after stamping the addressing information
|
||
if (MessageLogger.LoggingEnabled && message.Version.Addressing == AddressingVersion.None)
|
||
{
|
||
MessageLogger.LogMessage(ref message, MessageLoggingSource.TransportReceive | MessageLoggingSource.LastChance);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
void ValidateContentType()
|
||
{
|
||
if (!HasContent)
|
||
return;
|
||
|
||
if (string.IsNullOrEmpty(ContentType))
|
||
{
|
||
if (MessageLogger.ShouldLogMalformed)
|
||
{
|
||
// We pass in throwOnError = false below so that the exception which is eventually thrown is the ProtocolException below, with Http status code 415 "UnsupportedMediaType"
|
||
Stream stream = this.GetInputStream(false);
|
||
if (stream != null)
|
||
{
|
||
MessageLogger.LogMessage(stream, MessageLoggingSource.Malformed);
|
||
}
|
||
}
|
||
ThrowHttpProtocolException(SR.GetString(SR.HttpContentTypeHeaderRequired), HttpStatusCode.UnsupportedMediaType, HttpChannelUtilities.StatusDescriptionStrings.HttpContentTypeMissing);
|
||
}
|
||
if (!messageEncoder.IsContentTypeSupported(ContentType))
|
||
{
|
||
if (MessageLogger.ShouldLogMalformed)
|
||
{
|
||
// We pass in throwOnError = false below so that the exception which is eventually thrown is the ProtocolException below, with Http status code 415 "UnsupportedMediaType"
|
||
Stream stream = this.GetInputStream(false);
|
||
if (stream != null)
|
||
{
|
||
MessageLogger.LogMessage(stream, MessageLoggingSource.Malformed);
|
||
}
|
||
}
|
||
string statusDescription = string.Format(CultureInfo.InvariantCulture, HttpChannelUtilities.StatusDescriptionStrings.HttpContentTypeMismatch, ContentType, messageEncoder.ContentType);
|
||
ThrowHttpProtocolException(SR.GetString(SR.ContentTypeMismatch, ContentType, messageEncoder.ContentType), HttpStatusCode.UnsupportedMediaType, statusDescription);
|
||
}
|
||
}
|
||
|
||
public IAsyncResult BeginParseIncomingMessage(AsyncCallback callback, object state)
|
||
{
|
||
return this.BeginParseIncomingMessage(null, callback, state);
|
||
}
|
||
|
||
public IAsyncResult BeginParseIncomingMessage(HttpRequestMessage httpRequestMessage, AsyncCallback callback, object state)
|
||
{
|
||
bool throwing = true;
|
||
try
|
||
{
|
||
IAsyncResult result = new ParseMessageAsyncResult(httpRequestMessage, this, callback, state);
|
||
throwing = false;
|
||
return result;
|
||
}
|
||
finally
|
||
{
|
||
if (throwing)
|
||
{
|
||
Close();
|
||
}
|
||
}
|
||
}
|
||
|
||
public Message EndParseIncomingMessage(IAsyncResult result, out Exception requestException)
|
||
{
|
||
bool throwing = true;
|
||
try
|
||
{
|
||
Message message = ParseMessageAsyncResult.End(result, out requestException);
|
||
throwing = false;
|
||
return message;
|
||
}
|
||
finally
|
||
{
|
||
if (throwing)
|
||
{
|
||
Close();
|
||
}
|
||
}
|
||
}
|
||
|
||
public HttpRequestMessageHttpInput CreateHttpRequestMessageInput()
|
||
{
|
||
HttpRequestMessage message = new HttpRequestMessage();
|
||
|
||
if (this.HasContent)
|
||
{
|
||
message.Content = new StreamContent(new MaxMessageSizeStream(this.GetInputStream(true), this.settings.MaxReceivedMessageSize));
|
||
}
|
||
|
||
HttpChannelUtilities.EnsureHttpRequestMessageContentNotNull(message);
|
||
|
||
this.ConfigureHttpRequestMessage(message);
|
||
ChannelBinding channelBinding = this.enableChannelBinding ? this.ChannelBinding : null;
|
||
return new HttpRequestMessageHttpInput(message, this.settings, this.enableChannelBinding, channelBinding);
|
||
}
|
||
|
||
public abstract void ConfigureHttpRequestMessage(HttpRequestMessage message);
|
||
|
||
public Message ParseIncomingMessage(out Exception requestException)
|
||
{
|
||
return this.ParseIncomingMessage(null, out requestException);
|
||
}
|
||
|
||
public Message ParseIncomingMessage(HttpRequestMessage httpRequestMessage, out Exception requestException)
|
||
{
|
||
Message message = null;
|
||
requestException = null;
|
||
bool throwing = true;
|
||
try
|
||
{
|
||
ValidateContentType();
|
||
|
||
ServiceModelActivity activity = null;
|
||
if (DiagnosticUtility.ShouldUseActivity &&
|
||
((ServiceModelActivity.Current == null) ||
|
||
(ServiceModelActivity.Current.ActivityType != ActivityType.ProcessAction)))
|
||
{
|
||
activity = ServiceModelActivity.CreateBoundedActivity(true);
|
||
}
|
||
using (activity)
|
||
{
|
||
if (DiagnosticUtility.ShouldUseActivity && activity != null)
|
||
{
|
||
// Only update the Start identifier if the activity is not null.
|
||
ServiceModelActivity.Start(activity, SR.GetString(SR.ActivityProcessingMessage, TraceUtility.RetrieveMessageNumber()), ActivityType.ProcessMessage);
|
||
}
|
||
|
||
if (!this.HasContent)
|
||
{
|
||
if (this.messageEncoder.MessageVersion == MessageVersion.None)
|
||
{
|
||
message = new NullMessage();
|
||
}
|
||
else
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Stream stream = this.GetInputStream(true);
|
||
if (streamed)
|
||
{
|
||
message = ReadStreamedMessage(stream);
|
||
}
|
||
else if (this.ContentLength == -1)
|
||
{
|
||
message = ReadChunkedBufferedMessage(stream);
|
||
}
|
||
else
|
||
{
|
||
if (httpRequestMessage == null)
|
||
{
|
||
message = ReadBufferedMessage(stream);
|
||
}
|
||
else
|
||
{
|
||
message = ReadBufferedMessage(httpRequestMessage);
|
||
}
|
||
}
|
||
}
|
||
|
||
requestException = ProcessHttpAddressing(message);
|
||
|
||
throwing = false;
|
||
return message;
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (throwing)
|
||
{
|
||
Close();
|
||
}
|
||
}
|
||
}
|
||
|
||
Message ReadBufferedMessage(HttpRequestMessage httpRequestMessage)
|
||
{
|
||
Fx.Assert(httpRequestMessage != null, "httpRequestMessage cannot be null.");
|
||
|
||
Message message;
|
||
using (HttpContent currentContent = httpRequestMessage.Content)
|
||
{
|
||
int length = (int)this.ContentLength;
|
||
byte[] buffer = this.bufferManager.TakeBuffer(length);
|
||
bool success = false;
|
||
try
|
||
{
|
||
MemoryStream ms = new MemoryStream(buffer);
|
||
currentContent.CopyToAsync(ms).Wait<CommunicationException>();
|
||
httpRequestMessage.Content = new ByteArrayContent(buffer, 0, length);
|
||
|
||
foreach (var header in currentContent.Headers)
|
||
{
|
||
httpRequestMessage.Content.Headers.Add(header.Key, header.Value);
|
||
}
|
||
|
||
//
|
||
|
||
message = this.messageEncoder.ReadMessage(new ArraySegment<byte>(buffer, 0, length), this.bufferManager, this.ContentType);
|
||
success = true;
|
||
}
|
||
finally
|
||
{
|
||
if (!success)
|
||
{
|
||
// We don't have to return it in success case since the buffer will be returned to bufferManager when the message is disposed.
|
||
this.bufferManager.ReturnBuffer(buffer);
|
||
}
|
||
}
|
||
}
|
||
return message;
|
||
}
|
||
|
||
void ThrowHttpProtocolException(string message, HttpStatusCode statusCode)
|
||
{
|
||
ThrowHttpProtocolException(message, statusCode, null);
|
||
}
|
||
|
||
void ThrowHttpProtocolException(string message, HttpStatusCode statusCode, string statusDescription)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateHttpProtocolException(message, statusCode, statusDescription, webException));
|
||
}
|
||
|
||
internal static ProtocolException CreateHttpProtocolException(string message, HttpStatusCode statusCode, string statusDescription, Exception innerException)
|
||
{
|
||
ProtocolException exception = new ProtocolException(message, innerException);
|
||
exception.Data.Add(HttpChannelUtilities.HttpStatusCodeExceptionKey, statusCode);
|
||
if (statusDescription != null && statusDescription.Length > 0)
|
||
{
|
||
exception.Data.Add(HttpChannelUtilities.HttpStatusDescriptionExceptionKey, statusDescription);
|
||
}
|
||
|
||
return exception;
|
||
}
|
||
|
||
protected virtual void Close()
|
||
{
|
||
}
|
||
|
||
ArraySegment<byte> GetMessageBuffer()
|
||
{
|
||
long count = ContentLength;
|
||
int bufferSize;
|
||
|
||
if (count > settings.MaxReceivedMessageSize)
|
||
{
|
||
ThrowMaxReceivedMessageSizeExceeded();
|
||
}
|
||
|
||
bufferSize = (int)count;
|
||
|
||
return new ArraySegment<byte>(bufferManager.TakeBuffer(bufferSize), 0, bufferSize);
|
||
}
|
||
|
||
class ParseMessageAsyncResult : TraceAsyncResult
|
||
{
|
||
ArraySegment<byte> buffer;
|
||
int count;
|
||
int offset;
|
||
HttpInput httpInput;
|
||
Stream inputStream;
|
||
Message message;
|
||
Exception requestException = null;
|
||
HttpRequestMessage httpRequestMessage;
|
||
static AsyncCallback onRead = Fx.ThunkCallback(new AsyncCallback(OnRead));
|
||
|
||
public ParseMessageAsyncResult(
|
||
HttpRequestMessage httpRequestMessage,
|
||
HttpInput httpInput,
|
||
AsyncCallback callback,
|
||
object state)
|
||
: base(callback, state)
|
||
{
|
||
this.httpInput = httpInput;
|
||
this.httpRequestMessage = httpRequestMessage;
|
||
this.BeginParse();
|
||
}
|
||
|
||
void BeginParse()
|
||
{
|
||
httpInput.ValidateContentType();
|
||
this.inputStream = httpInput.GetInputStream(true);
|
||
|
||
if (!httpInput.HasContent)
|
||
{
|
||
if (httpInput.messageEncoder.MessageVersion == MessageVersion.None)
|
||
{
|
||
this.message = new NullMessage();
|
||
}
|
||
else
|
||
{
|
||
base.Complete(true);
|
||
return;
|
||
}
|
||
}
|
||
else if (httpInput.streamed || httpInput.ContentLength == -1)
|
||
{
|
||
if (httpInput.streamed)
|
||
{
|
||
this.message = httpInput.ReadStreamedMessage(inputStream);
|
||
}
|
||
else
|
||
{
|
||
this.message = httpInput.ReadChunkedBufferedMessage(inputStream);
|
||
}
|
||
}
|
||
|
||
if (this.message != null)
|
||
{
|
||
this.requestException = httpInput.ProcessHttpAddressing(this.message);
|
||
base.Complete(true);
|
||
return;
|
||
}
|
||
|
||
AsyncCompletionResult result;
|
||
if (httpRequestMessage == null)
|
||
{
|
||
result = this.DecodeBufferedMessageAsync();
|
||
}
|
||
else
|
||
{
|
||
result = this.DecodeBufferedHttpRequestMessageAsync();
|
||
}
|
||
|
||
if (result == AsyncCompletionResult.Completed)
|
||
{
|
||
base.Complete(true);
|
||
}
|
||
}
|
||
|
||
AsyncCompletionResult DecodeBufferedMessageAsync()
|
||
{
|
||
this.buffer = this.httpInput.GetMessageBuffer();
|
||
this.count = this.buffer.Count;
|
||
this.offset = 0;
|
||
|
||
IAsyncResult result = inputStream.BeginRead(buffer.Array, offset, count, onRead, this);
|
||
if (result.CompletedSynchronously)
|
||
{
|
||
if (ContinueReading(inputStream.EndRead(result)))
|
||
{
|
||
return AsyncCompletionResult.Completed;
|
||
}
|
||
}
|
||
|
||
return AsyncCompletionResult.Queued;
|
||
}
|
||
|
||
bool ContinueReading(int bytesRead)
|
||
{
|
||
while (true)
|
||
{
|
||
if (bytesRead == 0) // EOF
|
||
{
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
offset += bytesRead;
|
||
count -= bytesRead;
|
||
if (count <= 0)
|
||
{
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
IAsyncResult result = inputStream.BeginRead(buffer.Array, offset, count, onRead, this);
|
||
if (!result.CompletedSynchronously)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
bytesRead = inputStream.EndRead(result);
|
||
}
|
||
}
|
||
}
|
||
|
||
using (DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.BoundOperation(this.CallbackActivity) : null)
|
||
{
|
||
using (ServiceModelActivity activity = DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.CreateBoundedActivity(true) : null)
|
||
{
|
||
if (DiagnosticUtility.ShouldUseActivity)
|
||
{
|
||
ServiceModelActivity.Start(activity, SR.GetString(SR.ActivityProcessingMessage, TraceUtility.RetrieveMessageNumber()), ActivityType.ProcessMessage);
|
||
}
|
||
|
||
this.message = this.httpInput.DecodeBufferedMessage(new ArraySegment<byte>(buffer.Array, 0, offset), inputStream);
|
||
this.requestException = this.httpInput.ProcessHttpAddressing(this.message);
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
static void OnRead(IAsyncResult result)
|
||
{
|
||
if (result.CompletedSynchronously)
|
||
return;
|
||
|
||
ParseMessageAsyncResult thisPtr = (ParseMessageAsyncResult)result.AsyncState;
|
||
|
||
Exception completionException = null;
|
||
bool completeSelf;
|
||
try
|
||
{
|
||
completeSelf = thisPtr.ContinueReading(thisPtr.inputStream.EndRead(result));
|
||
}
|
||
#pragma warning suppress 56500 // Microsoft, transferring exception to another thread
|
||
catch (Exception e)
|
||
{
|
||
if (Fx.IsFatal(e))
|
||
{
|
||
throw;
|
||
}
|
||
|
||
completeSelf = true;
|
||
completionException = e;
|
||
}
|
||
|
||
if (completeSelf)
|
||
{
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
public static Message End(IAsyncResult result, out Exception requestException)
|
||
{
|
||
ParseMessageAsyncResult thisPtr = AsyncResult.End<ParseMessageAsyncResult>(result);
|
||
requestException = thisPtr.requestException;
|
||
return thisPtr.message;
|
||
}
|
||
|
||
AsyncCompletionResult DecodeBufferedHttpRequestMessageAsync()
|
||
{
|
||
// Need to consider moving this to async implemenation for HttpContent reading.(CSDMAIN: 229108)
|
||
this.message = this.httpInput.ReadBufferedMessage(this.httpRequestMessage);
|
||
this.requestException = this.httpInput.ProcessHttpAddressing(this.message);
|
||
return AsyncCompletionResult.Completed;
|
||
}
|
||
}
|
||
|
||
class WebResponseHttpInput : HttpInput
|
||
{
|
||
HttpWebResponse httpWebResponse;
|
||
byte[] preReadBuffer;
|
||
ChannelBinding channelBinding;
|
||
bool hasContent;
|
||
|
||
public WebResponseHttpInput(HttpWebResponse httpWebResponse, IHttpTransportFactorySettings settings, ChannelBinding channelBinding)
|
||
: base(settings, false, channelBinding != null)
|
||
{
|
||
this.channelBinding = channelBinding;
|
||
this.httpWebResponse = httpWebResponse;
|
||
if (this.httpWebResponse.ContentLength == -1)
|
||
{
|
||
this.preReadBuffer = new byte[1];
|
||
|
||
if (this.httpWebResponse.GetResponseStream().Read(preReadBuffer, 0, 1) == 0)
|
||
{
|
||
this.preReadBuffer = null;
|
||
}
|
||
}
|
||
|
||
this.hasContent = (this.preReadBuffer != null || this.httpWebResponse.ContentLength > 0);
|
||
if (!this.hasContent)
|
||
{
|
||
// Close the response stream to avoid leaking the connection.
|
||
this.httpWebResponse.GetResponseStream().Close();
|
||
}
|
||
}
|
||
|
||
protected override ChannelBinding ChannelBinding
|
||
{
|
||
get
|
||
{
|
||
return this.channelBinding;
|
||
}
|
||
}
|
||
|
||
public override long ContentLength
|
||
{
|
||
get
|
||
{
|
||
return httpWebResponse.ContentLength;
|
||
}
|
||
}
|
||
|
||
protected override string ContentTypeCore
|
||
{
|
||
get
|
||
{
|
||
return httpWebResponse.ContentType;
|
||
}
|
||
}
|
||
|
||
protected override bool HasContent
|
||
{
|
||
get { return this.hasContent; }
|
||
}
|
||
|
||
protected override string SoapActionHeader
|
||
{
|
||
get
|
||
{
|
||
return httpWebResponse.Headers["SOAPAction"];
|
||
}
|
||
}
|
||
|
||
protected override void AddProperties(Message message)
|
||
{
|
||
HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty(httpWebResponse.Headers);
|
||
responseProperty.StatusCode = httpWebResponse.StatusCode;
|
||
responseProperty.StatusDescription = httpWebResponse.StatusDescription;
|
||
message.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
|
||
message.Properties.Via = message.Version.Addressing.AnonymousUri;
|
||
}
|
||
|
||
public override void ConfigureHttpRequestMessage(HttpRequestMessage message)
|
||
{
|
||
// HTTP pipeline for client side is not implemented yet
|
||
// DCR CSDMain 216853 is tracking this
|
||
// This API is never going to be called in current stack
|
||
Fx.Assert(false, "HTTP pipeline for client is not implemented yet. This method should not be called.");
|
||
throw FxTrace.Exception.AsError(new NotSupportedException());
|
||
}
|
||
|
||
protected override void Close()
|
||
{
|
||
try
|
||
{
|
||
httpWebResponse.Close();
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
if (Fx.IsFatal(exception))
|
||
throw;
|
||
|
||
DiagnosticUtility.TraceHandledException(exception, TraceEventType.Error);
|
||
}
|
||
}
|
||
|
||
protected override Stream GetInputStream()
|
||
{
|
||
Fx.Assert(this.HasContent, "this.HasContent must be true.");
|
||
if (this.preReadBuffer != null)
|
||
{
|
||
return new WebResponseInputStream(httpWebResponse, preReadBuffer);
|
||
}
|
||
else
|
||
{
|
||
return new WebResponseInputStream(httpWebResponse);
|
||
}
|
||
}
|
||
|
||
class WebResponseInputStream : DetectEofStream
|
||
{
|
||
// in order to avoid ----ing kernel buffers, we throttle our reads. http.sys
|
||
// deals with this fine, but System.Net doesn't do any such throttling.
|
||
const int maxSocketRead = 64 * 1024;
|
||
HttpWebResponse webResponse;
|
||
bool responseClosed;
|
||
|
||
public WebResponseInputStream(HttpWebResponse httpWebResponse)
|
||
: base(httpWebResponse.GetResponseStream())
|
||
{
|
||
this.webResponse = httpWebResponse;
|
||
}
|
||
|
||
public WebResponseInputStream(HttpWebResponse httpWebResponse, byte[] prereadBuffer)
|
||
: base(new PreReadStream(httpWebResponse.GetResponseStream(), prereadBuffer))
|
||
{
|
||
this.webResponse = httpWebResponse;
|
||
}
|
||
|
||
|
||
public override void Close()
|
||
{
|
||
base.Close();
|
||
CloseResponse();
|
||
}
|
||
|
||
protected override void OnReceivedEof()
|
||
{
|
||
base.OnReceivedEof();
|
||
CloseResponse();
|
||
}
|
||
|
||
void CloseResponse()
|
||
{
|
||
if (responseClosed)
|
||
{
|
||
return;
|
||
}
|
||
|
||
responseClosed = true;
|
||
this.webResponse.Close();
|
||
}
|
||
|
||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||
{
|
||
try
|
||
{
|
||
return BaseStream.BeginRead(buffer, offset, Math.Min(count, maxSocketRead), callback, state);
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw this.CreateResponseIOException(ioException);
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(objectDisposedException.Message, objectDisposedException));
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateResponseWebException(webException, this.webResponse));
|
||
}
|
||
}
|
||
|
||
public override int EndRead(IAsyncResult result)
|
||
{
|
||
try
|
||
{
|
||
return BaseStream.EndRead(result);
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw this.CreateResponseIOException(ioException);
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(objectDisposedException.Message, objectDisposedException));
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateResponseWebException(webException, this.webResponse));
|
||
}
|
||
}
|
||
|
||
public override int Read(byte[] buffer, int offset, int count)
|
||
{
|
||
try
|
||
{
|
||
return BaseStream.Read(buffer, offset, Math.Min(count, maxSocketRead));
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(objectDisposedException.Message, objectDisposedException));
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw this.CreateResponseIOException(ioException);
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateResponseWebException(webException, this.webResponse));
|
||
}
|
||
}
|
||
|
||
|
||
public override int ReadByte()
|
||
{
|
||
try
|
||
{
|
||
return BaseStream.ReadByte();
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(objectDisposedException.Message, objectDisposedException));
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw this.CreateResponseIOException(ioException);
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateResponseWebException(webException, this.webResponse));
|
||
}
|
||
}
|
||
|
||
private Exception CreateResponseIOException(IOException ioException)
|
||
{
|
||
TimeSpan timeSpan = this.CanTimeout ? TimeoutHelper.FromMilliseconds(this.ReadTimeout) : TimeSpan.MaxValue;
|
||
|
||
return DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateResponseIOException(ioException, timeSpan));
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
// abstract out the common functionality of an "HttpOutput"
|
||
abstract class HttpOutput
|
||
{
|
||
const string DefaultMimeVersion = "1.0";
|
||
|
||
HttpAbortReason abortReason;
|
||
bool isDisposed;
|
||
bool isRequest;
|
||
Message message;
|
||
IHttpTransportFactorySettings settings;
|
||
byte[] bufferToRecycle;
|
||
BufferManager bufferManager;
|
||
MessageEncoder messageEncoder;
|
||
bool streamed;
|
||
static Action<object> onStreamSendTimeout;
|
||
string mtomBoundary;
|
||
Stream outputStream;
|
||
bool supportsConcurrentIO;
|
||
EventTraceActivity eventTraceActivity;
|
||
bool canSendCompressedResponses;
|
||
|
||
protected HttpOutput(IHttpTransportFactorySettings settings, Message message, bool isRequest, bool supportsConcurrentIO)
|
||
{
|
||
this.settings = settings;
|
||
this.message = message;
|
||
this.isRequest = isRequest;
|
||
this.bufferManager = settings.BufferManager;
|
||
this.messageEncoder = settings.MessageEncoderFactory.Encoder;
|
||
ICompressedMessageEncoder compressedMessageEncoder = this.messageEncoder as ICompressedMessageEncoder;
|
||
this.canSendCompressedResponses = compressedMessageEncoder != null && compressedMessageEncoder.CompressionEnabled;
|
||
if (isRequest)
|
||
{
|
||
this.streamed = TransferModeHelper.IsRequestStreamed(settings.TransferMode);
|
||
}
|
||
else
|
||
{
|
||
this.streamed = TransferModeHelper.IsResponseStreamed(settings.TransferMode);
|
||
}
|
||
this.supportsConcurrentIO = supportsConcurrentIO;
|
||
|
||
if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled)
|
||
{
|
||
this.eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(message);
|
||
}
|
||
}
|
||
|
||
protected virtual bool IsChannelBindingSupportEnabled { get { return false; } }
|
||
protected virtual ChannelBinding ChannelBinding { get { return null; } }
|
||
|
||
protected void Abort()
|
||
{
|
||
Abort(HttpAbortReason.Aborted);
|
||
}
|
||
|
||
public virtual void Abort(HttpAbortReason reason)
|
||
{
|
||
if (isDisposed)
|
||
{
|
||
return;
|
||
}
|
||
|
||
this.abortReason = reason;
|
||
|
||
TraceRequestResponseAborted(reason);
|
||
|
||
CleanupBuffer();
|
||
}
|
||
|
||
private void TraceRequestResponseAborted(HttpAbortReason reason)
|
||
{
|
||
if (isRequest)
|
||
{
|
||
if (TD.HttpChannelRequestAbortedIsEnabled())
|
||
{
|
||
TD.HttpChannelRequestAborted(this.eventTraceActivity);
|
||
}
|
||
}
|
||
else if (TD.HttpChannelResponseAbortedIsEnabled())
|
||
{
|
||
TD.HttpChannelResponseAborted(this.eventTraceActivity);
|
||
}
|
||
|
||
if (DiagnosticUtility.ShouldTraceWarning)
|
||
{
|
||
TraceUtility.TraceEvent(TraceEventType.Warning,
|
||
isRequest ? TraceCode.HttpChannelRequestAborted : TraceCode.HttpChannelResponseAborted,
|
||
isRequest ? SR.GetString(SR.TraceCodeHttpChannelRequestAborted) : SR.GetString(SR.TraceCodeHttpChannelResponseAborted),
|
||
this.message);
|
||
}
|
||
}
|
||
|
||
public void Close()
|
||
{
|
||
if (isDisposed)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
if (this.outputStream != null)
|
||
{
|
||
outputStream.Close();
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
CleanupBuffer();
|
||
}
|
||
}
|
||
|
||
void CleanupBuffer()
|
||
{
|
||
byte[] bufferToRecycleSnapshot = Interlocked.Exchange<byte[]>(ref this.bufferToRecycle, null);
|
||
if (bufferToRecycleSnapshot != null)
|
||
{
|
||
bufferManager.ReturnBuffer(bufferToRecycleSnapshot);
|
||
}
|
||
|
||
isDisposed = true;
|
||
}
|
||
|
||
protected abstract void AddMimeVersion(string version);
|
||
protected abstract void AddHeader(string name, string value);
|
||
protected abstract void SetContentType(string contentType);
|
||
protected abstract void SetContentEncoding(string contentEncoding);
|
||
protected abstract void SetStatusCode(HttpStatusCode statusCode);
|
||
protected abstract void SetStatusDescription(string statusDescription);
|
||
protected virtual bool CleanupChannelBinding { get { return true; } }
|
||
protected virtual void SetContentLength(int contentLength)
|
||
{
|
||
}
|
||
|
||
protected virtual string HttpMethod { get { return null; } }
|
||
|
||
public virtual ChannelBinding TakeChannelBinding()
|
||
{
|
||
return null;
|
||
}
|
||
|
||
private void ApplyChannelBinding()
|
||
{
|
||
if (this.IsChannelBindingSupportEnabled)
|
||
{
|
||
ChannelBindingUtility.TryAddToMessage(this.ChannelBinding, this.message, this.CleanupChannelBinding);
|
||
}
|
||
}
|
||
|
||
protected abstract Stream GetOutputStream();
|
||
|
||
protected virtual bool WillGetOutputStreamCompleteSynchronously
|
||
{
|
||
get { return true; }
|
||
}
|
||
|
||
protected bool CanSendCompressedResponses
|
||
{
|
||
get { return this.canSendCompressedResponses; }
|
||
}
|
||
|
||
protected virtual IAsyncResult BeginGetOutputStream(AsyncCallback callback, object state)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
||
}
|
||
|
||
protected virtual Stream EndGetOutputStream(IAsyncResult result)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
||
}
|
||
|
||
public void ConfigureHttpResponseMessage(Message message, HttpResponseMessage httpResponseMessage, HttpResponseMessageProperty responseProperty)
|
||
{
|
||
HttpChannelUtilities.EnsureHttpResponseMessageContentNotNull(httpResponseMessage);
|
||
|
||
string action = message.Headers.Action;
|
||
|
||
if (message.Version.Addressing == AddressingVersion.None)
|
||
{
|
||
if (MessageLogger.LogMessagesAtTransportLevel)
|
||
{
|
||
message.Properties.Add(AddressingProperty.Name, new AddressingProperty(message.Headers));
|
||
}
|
||
|
||
message.Headers.Action = null;
|
||
message.Headers.To = null;
|
||
}
|
||
|
||
bool httpResponseMessagePropertyFound = responseProperty != null;
|
||
|
||
string contentType = null;
|
||
if (message.Version == MessageVersion.None && httpResponseMessagePropertyFound && !string.IsNullOrEmpty(responseProperty.Headers[HttpResponseHeader.ContentType]))
|
||
{
|
||
contentType = responseProperty.Headers[HttpResponseHeader.ContentType];
|
||
responseProperty.Headers.Remove(HttpResponseHeader.ContentType);
|
||
if (!messageEncoder.IsContentTypeSupported(contentType))
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.ResponseContentTypeNotSupported,
|
||
contentType)));
|
||
}
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(contentType))
|
||
{
|
||
MtomMessageEncoder mtomMessageEncoder = messageEncoder as MtomMessageEncoder;
|
||
if (mtomMessageEncoder == null)
|
||
{
|
||
contentType = messageEncoder.ContentType;
|
||
}
|
||
else
|
||
{
|
||
contentType = mtomMessageEncoder.GetContentType(out this.mtomBoundary);
|
||
// For MTOM messages, add a MIME version header
|
||
httpResponseMessage.Headers.Add(HttpChannelUtilities.MIMEVersionHeader, DefaultMimeVersion);
|
||
}
|
||
}
|
||
|
||
if (isRequest && FxTrace.Trace.IsEnd2EndActivityTracingEnabled)
|
||
{
|
||
EnsureEventTraceActivity(message);
|
||
}
|
||
|
||
if (this.CanSendCompressedResponses)
|
||
{
|
||
string contentEncoding;
|
||
string compressionContentType = contentType;
|
||
if (HttpChannelUtilities.GetHttpResponseTypeAndEncodingForCompression(ref compressionContentType, out contentEncoding))
|
||
{
|
||
contentType = compressionContentType;
|
||
this.SetContentEncoding(contentEncoding);
|
||
}
|
||
}
|
||
|
||
if (httpResponseMessage.Content != null && !string.IsNullOrEmpty(contentType))
|
||
{
|
||
MediaTypeHeaderValue mediaTypeHeaderValue;
|
||
if (!MediaTypeHeaderValue.TryParse(contentType, out mediaTypeHeaderValue))
|
||
{
|
||
throw FxTrace.Exception.Argument("contentType", SR.GetString(SR.InvalidContentTypeError, contentType));
|
||
}
|
||
httpResponseMessage.Content.Headers.ContentType = mediaTypeHeaderValue;
|
||
}
|
||
|
||
bool httpMethodIsHead = string.Compare(this.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase) == 0;
|
||
|
||
if (httpMethodIsHead ||
|
||
httpResponseMessagePropertyFound && responseProperty.SuppressEntityBody)
|
||
{
|
||
httpResponseMessage.Content.Headers.ContentLength = 0;
|
||
httpResponseMessage.Content.Headers.ContentType = null;
|
||
}
|
||
|
||
if (httpResponseMessagePropertyFound)
|
||
{
|
||
httpResponseMessage.StatusCode = responseProperty.StatusCode;
|
||
if (responseProperty.StatusDescription != null)
|
||
{
|
||
responseProperty.StatusDescription = responseProperty.StatusDescription;
|
||
}
|
||
|
||
foreach (string key in responseProperty.Headers.AllKeys)
|
||
{
|
||
httpResponseMessage.AddHeader(key, responseProperty.Headers[key]);
|
||
}
|
||
}
|
||
|
||
if (!message.IsEmpty)
|
||
{
|
||
using (HttpContent content = httpResponseMessage.Content)
|
||
{
|
||
if (this.streamed)
|
||
{
|
||
IStreamedMessageEncoder streamedMessageEncoder = this.messageEncoder as IStreamedMessageEncoder;
|
||
Stream stream = null;
|
||
if (streamedMessageEncoder != null)
|
||
{
|
||
stream = streamedMessageEncoder.GetResponseMessageStream(message);
|
||
}
|
||
|
||
if (stream != null)
|
||
{
|
||
httpResponseMessage.Content = new StreamContent(stream);
|
||
}
|
||
else
|
||
{
|
||
httpResponseMessage.Content = new OpaqueContent(this.messageEncoder, message, this.mtomBoundary);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// HttpOutputByteArrayContent assumes responsibility for returning the buffer to the bufferManager.
|
||
ArraySegment<byte> messageBytes = this.SerializeBufferedMessage(message, false);
|
||
httpResponseMessage.Content = new HttpOutputByteArrayContent(messageBytes.Array, messageBytes.Offset, messageBytes.Count, this.bufferManager);
|
||
}
|
||
|
||
httpResponseMessage.Content.Headers.Clear();
|
||
foreach (var header in content.Headers)
|
||
{
|
||
httpResponseMessage.Content.Headers.Add(header.Key, header.Value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
protected virtual bool PrepareHttpSend(Message message)
|
||
{
|
||
string action = message.Headers.Action;
|
||
|
||
if (message.Version.Addressing == AddressingVersion.None)
|
||
{
|
||
if (MessageLogger.LogMessagesAtTransportLevel)
|
||
{
|
||
message.Properties.Add(AddressingProperty.Name, new AddressingProperty(message.Headers));
|
||
}
|
||
|
||
message.Headers.Action = null;
|
||
message.Headers.To = null;
|
||
}
|
||
|
||
string contentType = null;
|
||
|
||
if (message.Version == MessageVersion.None)
|
||
{
|
||
object property = null;
|
||
if (message.Properties.TryGetValue(HttpResponseMessageProperty.Name, out property))
|
||
{
|
||
HttpResponseMessageProperty responseProperty = (HttpResponseMessageProperty)property;
|
||
if (!string.IsNullOrEmpty(responseProperty.Headers[HttpResponseHeader.ContentType]))
|
||
{
|
||
contentType = responseProperty.Headers[HttpResponseHeader.ContentType];
|
||
if (!messageEncoder.IsContentTypeSupported(contentType))
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.ResponseContentTypeNotSupported,
|
||
contentType)));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(contentType))
|
||
{
|
||
MtomMessageEncoder mtomMessageEncoder = messageEncoder as MtomMessageEncoder;
|
||
if (mtomMessageEncoder == null)
|
||
{
|
||
contentType = messageEncoder.ContentType;
|
||
}
|
||
else
|
||
{
|
||
contentType = mtomMessageEncoder.GetContentType(out this.mtomBoundary);
|
||
// For MTOM messages, add a MIME version header
|
||
AddMimeVersion("1.0");
|
||
}
|
||
}
|
||
|
||
if (isRequest && FxTrace.Trace.IsEnd2EndActivityTracingEnabled)
|
||
{
|
||
EnsureEventTraceActivity(message);
|
||
}
|
||
|
||
SetContentType(contentType);
|
||
return message is NullMessage;
|
||
}
|
||
|
||
protected bool PrepareHttpSend(HttpResponseMessage httpResponseMessage)
|
||
{
|
||
this.PrepareHttpSendCore(httpResponseMessage);
|
||
return HttpChannelUtilities.IsEmpty(httpResponseMessage);
|
||
}
|
||
|
||
protected abstract void PrepareHttpSendCore(HttpResponseMessage message);
|
||
|
||
private static void EnsureEventTraceActivity(Message message)
|
||
{
|
||
//We need to send this only if there is no message id.
|
||
if (message.Headers.MessageId == null)
|
||
{
|
||
EventTraceActivity eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(message);
|
||
if (eventTraceActivity == null)
|
||
{
|
||
//Whoops no activity on the message yet.
|
||
eventTraceActivity = new EventTraceActivity();
|
||
EventTraceActivityHelper.TryAttachActivity(message, eventTraceActivity);
|
||
}
|
||
|
||
HttpRequestMessageProperty httpProperties;
|
||
if (!message.Properties.TryGetValue<HttpRequestMessageProperty>(HttpRequestMessageProperty.Name, out httpProperties))
|
||
{
|
||
httpProperties = new HttpRequestMessageProperty();
|
||
message.Properties.Add(HttpRequestMessageProperty.Name, httpProperties);
|
||
}
|
||
httpProperties.Headers.Add(EventTraceActivity.Name, Convert.ToBase64String(eventTraceActivity.ActivityId.ToByteArray()));
|
||
}
|
||
}
|
||
|
||
ArraySegment<byte> SerializeBufferedMessage(Message message)
|
||
{
|
||
// by default, the HttpOutput should own the buffer and clean it up
|
||
return SerializeBufferedMessage(message, true);
|
||
}
|
||
|
||
ArraySegment<byte> SerializeBufferedMessage(Message message, bool shouldRecycleBuffer)
|
||
{
|
||
ArraySegment<byte> result;
|
||
|
||
MtomMessageEncoder mtomMessageEncoder = messageEncoder as MtomMessageEncoder;
|
||
if (mtomMessageEncoder == null)
|
||
{
|
||
result = messageEncoder.WriteMessage(message, int.MaxValue, bufferManager);
|
||
}
|
||
else
|
||
{
|
||
result = mtomMessageEncoder.WriteMessage(message, int.MaxValue, bufferManager, 0, this.mtomBoundary);
|
||
}
|
||
|
||
if (shouldRecycleBuffer)
|
||
{
|
||
// Only set this.bufferToRecycle if the HttpOutput owns the buffer, we will clean it up upon httpOutput.Close()
|
||
// Otherwise, caller of SerializeBufferedMessage assumes responsiblity for returning the buffer to the buffer pool
|
||
this.bufferToRecycle = result.Array;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
Stream GetWrappedOutputStream()
|
||
{
|
||
const int ChunkSize = 32768; // buffer size used for synchronous writes
|
||
const int BufferSize = 16384; // buffer size used for asynchronous writes
|
||
const int BufferCount = 4; // buffer count used for asynchronous writes
|
||
|
||
// Writing an HTTP request chunk has a high fixed cost, so use BufferedStream to avoid writing
|
||
// small ones.
|
||
return this.supportsConcurrentIO ? (Stream)new BufferedOutputAsyncStream(this.outputStream, BufferSize, BufferCount) : new BufferedStream(this.outputStream, ChunkSize);
|
||
}
|
||
|
||
void WriteStreamedMessage(TimeSpan timeout)
|
||
{
|
||
this.outputStream = GetWrappedOutputStream();
|
||
|
||
// Since HTTP streams don't support timeouts, we can't just use TimeoutStream here.
|
||
// Rather, we need to run a timer to bound the overall operation
|
||
if (onStreamSendTimeout == null)
|
||
{
|
||
onStreamSendTimeout = new Action<object>(OnStreamSendTimeout);
|
||
}
|
||
IOThreadTimer sendTimer = new IOThreadTimer(onStreamSendTimeout, this, true);
|
||
sendTimer.Set(timeout);
|
||
|
||
try
|
||
{
|
||
MtomMessageEncoder mtomMessageEncoder = messageEncoder as MtomMessageEncoder;
|
||
if (mtomMessageEncoder == null)
|
||
{
|
||
messageEncoder.WriteMessage(this.message, this.outputStream);
|
||
}
|
||
else
|
||
{
|
||
mtomMessageEncoder.WriteMessage(this.message, this.outputStream, this.mtomBoundary);
|
||
}
|
||
|
||
if (this.supportsConcurrentIO)
|
||
{
|
||
this.outputStream.Close();
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
sendTimer.Cancel();
|
||
}
|
||
}
|
||
|
||
static void OnStreamSendTimeout(object state)
|
||
{
|
||
HttpOutput thisPtr = (HttpOutput)state;
|
||
thisPtr.Abort(HttpAbortReason.TimedOut);
|
||
}
|
||
|
||
IAsyncResult BeginWriteStreamedMessage(HttpResponseMessage httpResponseMessage, TimeSpan timeout, AsyncCallback callback, object state)
|
||
{
|
||
return new WriteStreamedMessageAsyncResult(timeout, this, httpResponseMessage, callback, state);
|
||
}
|
||
|
||
void EndWriteStreamedMessage(IAsyncResult result)
|
||
{
|
||
WriteStreamedMessageAsyncResult.End(result);
|
||
}
|
||
|
||
class HttpOutputByteArrayContent : ByteArrayContent
|
||
{
|
||
BufferManager bufferManager;
|
||
volatile bool cleaned = false;
|
||
ArraySegment<byte> content;
|
||
|
||
public HttpOutputByteArrayContent(byte[] content, int offset, int count, BufferManager bufferManager)
|
||
: base(content, offset, count)
|
||
{
|
||
Fx.Assert(bufferManager != null, "bufferManager should not be null");
|
||
Fx.Assert(content != null, "content should not be null");
|
||
this.content = new ArraySegment<byte>(content, offset, count);
|
||
this.bufferManager = bufferManager;
|
||
}
|
||
|
||
public ArraySegment<byte> Content
|
||
{
|
||
get
|
||
{
|
||
return this.content;
|
||
}
|
||
}
|
||
|
||
protected override Task<Stream> CreateContentReadStreamAsync()
|
||
{
|
||
return base.CreateContentReadStreamAsync().ContinueWith<Stream>(t =>
|
||
new HttpOutputByteArrayContentStream(t.Result, this));
|
||
}
|
||
|
||
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
|
||
{
|
||
return base.SerializeToStreamAsync(stream, context).ContinueWith(t =>
|
||
{
|
||
this.Cleanup();
|
||
HttpChannelUtilities.HandleContinueWithTask(t);
|
||
});
|
||
}
|
||
|
||
void Cleanup()
|
||
{
|
||
if (!cleaned)
|
||
{
|
||
lock (this)
|
||
{
|
||
if (!cleaned)
|
||
{
|
||
cleaned = true;
|
||
this.bufferManager.ReturnBuffer(this.content.Array);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
class HttpOutputByteArrayContentStream : DelegatingStream
|
||
{
|
||
HttpOutputByteArrayContent content;
|
||
|
||
public HttpOutputByteArrayContentStream(Stream innerStream, HttpOutputByteArrayContent content)
|
||
: base(innerStream)
|
||
{
|
||
this.content = content;
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
base.Close();
|
||
this.content.Cleanup();
|
||
}
|
||
}
|
||
}
|
||
|
||
class WriteStreamedMessageAsyncResult : AsyncResult
|
||
{
|
||
HttpOutput httpOutput;
|
||
IOThreadTimer sendTimer;
|
||
static AsyncCallback onWriteStreamedMessage = Fx.ThunkCallback(OnWriteStreamedMessage);
|
||
HttpResponseMessage httpResponseMessage;
|
||
|
||
public WriteStreamedMessageAsyncResult(TimeSpan timeout, HttpOutput httpOutput, HttpResponseMessage httpResponseMessage, AsyncCallback callback, object state)
|
||
: base(callback, state)
|
||
{
|
||
this.httpResponseMessage = httpResponseMessage;
|
||
this.httpOutput = httpOutput;
|
||
httpOutput.outputStream = httpOutput.GetWrappedOutputStream();
|
||
|
||
// Since HTTP streams don't support timeouts, we can't just use TimeoutStream here.
|
||
// Rather, we need to run a timer to bound the overall operation
|
||
if (onStreamSendTimeout == null)
|
||
{
|
||
onStreamSendTimeout = new Action<object>(OnStreamSendTimeout);
|
||
}
|
||
this.SetTimer(timeout);
|
||
|
||
bool completeSelf = false;
|
||
bool throwing = true;
|
||
|
||
try
|
||
{
|
||
completeSelf = HandleWriteStreamedMessage(null);
|
||
throwing = false;
|
||
}
|
||
finally
|
||
{
|
||
if (completeSelf || throwing)
|
||
{
|
||
this.sendTimer.Cancel();
|
||
}
|
||
}
|
||
|
||
if (completeSelf)
|
||
{
|
||
this.Complete(true);
|
||
}
|
||
}
|
||
|
||
bool HandleWriteStreamedMessage(IAsyncResult result)
|
||
{
|
||
if (this.httpResponseMessage == null)
|
||
{
|
||
if (result == null)
|
||
{
|
||
MtomMessageEncoder mtomMessageEncoder = httpOutput.messageEncoder as MtomMessageEncoder;
|
||
if (mtomMessageEncoder == null)
|
||
{
|
||
result = httpOutput.messageEncoder.BeginWriteMessage(httpOutput.message, httpOutput.outputStream, onWriteStreamedMessage, this);
|
||
}
|
||
else
|
||
{
|
||
result = mtomMessageEncoder.BeginWriteMessage(httpOutput.message, httpOutput.outputStream, httpOutput.mtomBoundary, onWriteStreamedMessage, this);
|
||
}
|
||
|
||
if (!result.CompletedSynchronously)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
httpOutput.messageEncoder.EndWriteMessage(result);
|
||
|
||
if (this.httpOutput.supportsConcurrentIO)
|
||
{
|
||
httpOutput.outputStream.Close();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
OpaqueContent content = this.httpResponseMessage.Content as OpaqueContent;
|
||
if (result == null)
|
||
{
|
||
Fx.Assert(this.httpResponseMessage.Content != null, "httpOutput.httpResponseMessage.Content should not be null.");
|
||
|
||
if (content != null)
|
||
{
|
||
result = content.BeginWriteToStream(httpOutput.outputStream, onWriteStreamedMessage, this);
|
||
}
|
||
else
|
||
{
|
||
result = this.httpResponseMessage.Content.CopyToAsync(httpOutput.outputStream).AsAsyncResult(onWriteStreamedMessage, this);
|
||
}
|
||
|
||
if (!result.CompletedSynchronously)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (content != null)
|
||
{
|
||
content.EndWriteToStream(result);
|
||
}
|
||
|
||
if (this.httpOutput.supportsConcurrentIO)
|
||
{
|
||
httpOutput.outputStream.Close();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
static void OnWriteStreamedMessage(IAsyncResult result)
|
||
{
|
||
if (result.CompletedSynchronously)
|
||
{
|
||
return;
|
||
}
|
||
|
||
WriteStreamedMessageAsyncResult thisPtr = (WriteStreamedMessageAsyncResult)result.AsyncState;
|
||
Exception completionException = null;
|
||
bool completeSelf = false;
|
||
|
||
try
|
||
{
|
||
completeSelf = thisPtr.HandleWriteStreamedMessage(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (Fx.IsFatal(ex))
|
||
{
|
||
throw;
|
||
}
|
||
completeSelf = true;
|
||
completionException = ex;
|
||
}
|
||
|
||
if (completeSelf)
|
||
{
|
||
thisPtr.sendTimer.Cancel();
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
void SetTimer(TimeSpan timeout)
|
||
{
|
||
Fx.Assert(this.sendTimer == null, "SetTimer should only be called once");
|
||
|
||
this.sendTimer = new IOThreadTimer(onStreamSendTimeout, this.httpOutput, true);
|
||
this.sendTimer.Set(timeout);
|
||
}
|
||
|
||
public static void End(IAsyncResult result)
|
||
{
|
||
AsyncResult.End<WriteStreamedMessageAsyncResult>(result);
|
||
}
|
||
}
|
||
|
||
public IAsyncResult BeginSend(HttpResponseMessage httpResponseMessage, TimeSpan timeout, AsyncCallback callback, object state)
|
||
{
|
||
Fx.Assert(httpResponseMessage != null, "httpResponseMessage should not be null.");
|
||
return this.BeginSendCore(httpResponseMessage, timeout, callback, state);
|
||
}
|
||
|
||
public IAsyncResult BeginSend(TimeSpan timeout, AsyncCallback callback, object state)
|
||
{
|
||
return this.BeginSendCore(null, timeout, callback, state);
|
||
}
|
||
|
||
IAsyncResult BeginSendCore(HttpResponseMessage httpResponseMessage, TimeSpan timeout, AsyncCallback callback, object state)
|
||
{
|
||
bool throwing = true;
|
||
try
|
||
{
|
||
bool suppressEntityBody;
|
||
if (httpResponseMessage != null)
|
||
{
|
||
suppressEntityBody = this.PrepareHttpSend(httpResponseMessage);
|
||
}
|
||
else
|
||
{
|
||
suppressEntityBody = PrepareHttpSend(message);
|
||
}
|
||
|
||
this.TraceHttpSendStart();
|
||
IAsyncResult result = new SendAsyncResult(this, httpResponseMessage, suppressEntityBody, timeout, callback, state);
|
||
throwing = false;
|
||
return result;
|
||
}
|
||
finally
|
||
{
|
||
if (throwing)
|
||
{
|
||
Abort();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void TraceHttpSendStart()
|
||
{
|
||
if (TD.HttpSendMessageStartIsEnabled())
|
||
{
|
||
if (streamed)
|
||
{
|
||
TD.HttpSendStreamedMessageStart(this.eventTraceActivity);
|
||
}
|
||
else
|
||
{
|
||
TD.HttpSendMessageStart(this.eventTraceActivity);
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void EndSend(IAsyncResult result)
|
||
{
|
||
bool throwing = true;
|
||
try
|
||
{
|
||
SendAsyncResult.End(result);
|
||
throwing = false;
|
||
}
|
||
finally
|
||
{
|
||
if (throwing)
|
||
{
|
||
Abort();
|
||
}
|
||
}
|
||
}
|
||
|
||
void LogMessage()
|
||
{
|
||
if (MessageLogger.LogMessagesAtTransportLevel)
|
||
{
|
||
MessageLogger.LogMessage(ref message, MessageLoggingSource.TransportSend);
|
||
}
|
||
}
|
||
|
||
public void Send(HttpResponseMessage httpResponseMessage, TimeSpan timeout)
|
||
{
|
||
bool suppressEntityBody = this.PrepareHttpSend(httpResponseMessage);
|
||
|
||
TraceHttpSendStart();
|
||
|
||
if (suppressEntityBody)
|
||
{
|
||
// requests can't always support an output stream (for GET, etc)
|
||
if (!isRequest)
|
||
{
|
||
outputStream = GetOutputStream();
|
||
}
|
||
else
|
||
{
|
||
this.SetContentLength(0);
|
||
LogMessage();
|
||
}
|
||
}
|
||
else if (streamed)
|
||
{
|
||
outputStream = this.GetOutputStream();
|
||
ApplyChannelBinding();
|
||
|
||
OpaqueContent content = httpResponseMessage.Content as OpaqueContent;
|
||
if (content != null)
|
||
{
|
||
content.WriteToStream(this.outputStream);
|
||
}
|
||
else
|
||
{
|
||
if (!httpResponseMessage.Content.CopyToAsync(this.outputStream).Wait<CommunicationException>(timeout))
|
||
{
|
||
throw FxTrace.Exception.AsError(new TimeoutException(SR.GetString(SR.TimeoutOnSend, timeout)));
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (this.IsChannelBindingSupportEnabled)
|
||
{
|
||
//need to get the Channel binding token (CBT), apply channel binding info to the message and then write the message
|
||
//CBT is only enabled when message security is in the stack, which also requires an HTTP entity body, so we
|
||
//should be safe to always get the stream.
|
||
outputStream = this.GetOutputStream();
|
||
|
||
ApplyChannelBinding();
|
||
|
||
ArraySegment<byte> buffer = SerializeBufferedMessage(httpResponseMessage);
|
||
|
||
Fx.Assert(buffer.Count != 0, "We should always have an entity body in this case...");
|
||
outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||
}
|
||
else
|
||
{
|
||
ArraySegment<byte> buffer = SerializeBufferedMessage(httpResponseMessage);
|
||
SetContentLength(buffer.Count);
|
||
|
||
// requests can't always support an output stream (for GET, etc)
|
||
if (!isRequest || buffer.Count > 0)
|
||
{
|
||
outputStream = this.GetOutputStream();
|
||
outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||
}
|
||
}
|
||
}
|
||
|
||
TraceSend();
|
||
}
|
||
|
||
ArraySegment<byte> SerializeBufferedMessage(HttpResponseMessage httpResponseMessage)
|
||
{
|
||
HttpOutputByteArrayContent content = httpResponseMessage.Content as HttpOutputByteArrayContent;
|
||
if (content == null)
|
||
{
|
||
byte[] byteArray = httpResponseMessage.Content.ReadAsByteArrayAsync().Result;
|
||
return new ArraySegment<byte>(byteArray, 0, byteArray.Length);
|
||
}
|
||
else
|
||
{
|
||
return content.Content;
|
||
}
|
||
}
|
||
|
||
public void Send(TimeSpan timeout)
|
||
{
|
||
bool suppressEntityBody = PrepareHttpSend(message);
|
||
|
||
TraceHttpSendStart();
|
||
|
||
if (suppressEntityBody)
|
||
{
|
||
// requests can't always support an output stream (for GET, etc)
|
||
if (!isRequest)
|
||
{
|
||
outputStream = GetOutputStream();
|
||
}
|
||
else
|
||
{
|
||
this.SetContentLength(0);
|
||
LogMessage();
|
||
}
|
||
}
|
||
else if (streamed)
|
||
{
|
||
outputStream = GetOutputStream();
|
||
ApplyChannelBinding();
|
||
WriteStreamedMessage(timeout);
|
||
}
|
||
else
|
||
{
|
||
if (this.IsChannelBindingSupportEnabled)
|
||
{
|
||
//need to get the Channel binding token (CBT), apply channel binding info to the message and then write the message
|
||
//CBT is only enabled when message security is in the stack, which also requires an HTTP entity body, so we
|
||
//should be safe to always get the stream.
|
||
outputStream = GetOutputStream();
|
||
|
||
ApplyChannelBinding();
|
||
|
||
ArraySegment<byte> buffer = SerializeBufferedMessage(message);
|
||
|
||
Fx.Assert(buffer.Count != 0, "We should always have an entity body in this case...");
|
||
outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||
}
|
||
else
|
||
{
|
||
ArraySegment<byte> buffer = SerializeBufferedMessage(message);
|
||
SetContentLength(buffer.Count);
|
||
|
||
// requests can't always support an output stream (for GET, etc)
|
||
if (!isRequest || buffer.Count > 0)
|
||
{
|
||
outputStream = GetOutputStream();
|
||
outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||
}
|
||
}
|
||
}
|
||
|
||
TraceSend();
|
||
}
|
||
|
||
void TraceSend()
|
||
{
|
||
if (DiagnosticUtility.ShouldTraceInformation)
|
||
{
|
||
TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.MessageSent, SR.GetString(SR.TraceCodeMessageSent),
|
||
new MessageTraceRecord(this.message), this, null);
|
||
}
|
||
|
||
if (TD.HttpSendStopIsEnabled())
|
||
{
|
||
TD.HttpSendStop(this.eventTraceActivity);
|
||
}
|
||
}
|
||
|
||
class SendAsyncResult : AsyncResult
|
||
{
|
||
HttpOutput httpOutput;
|
||
static AsyncCallback onGetOutputStream;
|
||
static Action<object> onWriteStreamedMessageLater;
|
||
static AsyncCallback onWriteStreamedMessage;
|
||
static AsyncCallback onWriteBody;
|
||
bool suppressEntityBody;
|
||
ArraySegment<byte> buffer;
|
||
TimeoutHelper timeoutHelper;
|
||
HttpResponseMessage httpResponseMessage;
|
||
|
||
public SendAsyncResult(HttpOutput httpOutput, HttpResponseMessage httpResponseMessage, bool suppressEntityBody, TimeSpan timeout, AsyncCallback callback, object state)
|
||
: base(callback, state)
|
||
{
|
||
this.httpOutput = httpOutput;
|
||
this.httpResponseMessage = httpResponseMessage;
|
||
this.suppressEntityBody = suppressEntityBody;
|
||
|
||
if (suppressEntityBody)
|
||
{
|
||
if (httpOutput.isRequest)
|
||
{
|
||
httpOutput.SetContentLength(0);
|
||
this.httpOutput.TraceSend();
|
||
this.httpOutput.LogMessage();
|
||
base.Complete(true);
|
||
return;
|
||
}
|
||
}
|
||
|
||
this.timeoutHelper = new TimeoutHelper(timeout);
|
||
Send();
|
||
}
|
||
|
||
void Send()
|
||
{
|
||
if (httpOutput.IsChannelBindingSupportEnabled)
|
||
{
|
||
SendWithChannelBindingToken();
|
||
}
|
||
else
|
||
{
|
||
SendWithoutChannelBindingToken();
|
||
}
|
||
}
|
||
|
||
void SendWithoutChannelBindingToken()
|
||
{
|
||
if (!suppressEntityBody && !httpOutput.streamed)
|
||
{
|
||
if (this.httpResponseMessage != null)
|
||
{
|
||
buffer = httpOutput.SerializeBufferedMessage(this.httpResponseMessage);
|
||
}
|
||
else
|
||
{
|
||
buffer = httpOutput.SerializeBufferedMessage(httpOutput.message);
|
||
}
|
||
|
||
httpOutput.SetContentLength(buffer.Count);
|
||
}
|
||
|
||
|
||
if (this.httpOutput.WillGetOutputStreamCompleteSynchronously)
|
||
{
|
||
httpOutput.outputStream = httpOutput.GetOutputStream();
|
||
}
|
||
else
|
||
{
|
||
if (onGetOutputStream == null)
|
||
{
|
||
onGetOutputStream = Fx.ThunkCallback(new AsyncCallback(OnGetOutputStream));
|
||
}
|
||
|
||
IAsyncResult result = httpOutput.BeginGetOutputStream(onGetOutputStream, this);
|
||
|
||
if (!result.CompletedSynchronously)
|
||
return;
|
||
|
||
httpOutput.outputStream = httpOutput.EndGetOutputStream(result);
|
||
}
|
||
|
||
if (WriteMessage(true))
|
||
{
|
||
this.httpOutput.TraceSend();
|
||
base.Complete(true);
|
||
}
|
||
}
|
||
|
||
void SendWithChannelBindingToken()
|
||
{
|
||
if (this.httpOutput.WillGetOutputStreamCompleteSynchronously)
|
||
{
|
||
httpOutput.outputStream = httpOutput.GetOutputStream();
|
||
httpOutput.ApplyChannelBinding();
|
||
}
|
||
else
|
||
{
|
||
if (onGetOutputStream == null)
|
||
{
|
||
onGetOutputStream = Fx.ThunkCallback(new AsyncCallback(OnGetOutputStream));
|
||
}
|
||
|
||
IAsyncResult result = httpOutput.BeginGetOutputStream(onGetOutputStream, this);
|
||
|
||
if (!result.CompletedSynchronously)
|
||
return;
|
||
|
||
httpOutput.outputStream = httpOutput.EndGetOutputStream(result);
|
||
httpOutput.ApplyChannelBinding();
|
||
}
|
||
|
||
if (!httpOutput.streamed)
|
||
{
|
||
if (this.httpResponseMessage != null)
|
||
{
|
||
buffer = httpOutput.SerializeBufferedMessage(this.httpResponseMessage);
|
||
}
|
||
else
|
||
{
|
||
buffer = httpOutput.SerializeBufferedMessage(httpOutput.message);
|
||
}
|
||
|
||
httpOutput.SetContentLength(buffer.Count);
|
||
}
|
||
|
||
if (WriteMessage(true))
|
||
{
|
||
this.httpOutput.TraceSend();
|
||
base.Complete(true);
|
||
}
|
||
}
|
||
|
||
bool WriteMessage(bool isStillSynchronous)
|
||
{
|
||
if (suppressEntityBody)
|
||
{
|
||
return true;
|
||
}
|
||
if (httpOutput.streamed)
|
||
{
|
||
if (isStillSynchronous)
|
||
{
|
||
if (onWriteStreamedMessageLater == null)
|
||
{
|
||
onWriteStreamedMessageLater = new Action<object>(OnWriteStreamedMessageLater);
|
||
}
|
||
ActionItem.Schedule(onWriteStreamedMessageLater, this);
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
return WriteStreamedMessage();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (onWriteBody == null)
|
||
{
|
||
onWriteBody = Fx.ThunkCallback(new AsyncCallback(OnWriteBody));
|
||
}
|
||
|
||
IAsyncResult writeResult =
|
||
httpOutput.outputStream.BeginWrite(buffer.Array, buffer.Offset, buffer.Count, onWriteBody, this);
|
||
|
||
if (!writeResult.CompletedSynchronously)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
CompleteWriteBody(writeResult);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool WriteStreamedMessage()
|
||
{
|
||
// return a bool to determine if we are sync.
|
||
|
||
if (onWriteStreamedMessage == null)
|
||
{
|
||
onWriteStreamedMessage = Fx.ThunkCallback(OnWriteStreamedMessage);
|
||
}
|
||
|
||
return HandleWriteStreamedMessage(null); // completed synchronously
|
||
}
|
||
|
||
bool HandleWriteStreamedMessage(IAsyncResult result)
|
||
{
|
||
if (result == null)
|
||
{
|
||
result = httpOutput.BeginWriteStreamedMessage(this.httpResponseMessage, timeoutHelper.RemainingTime(), onWriteStreamedMessage, this);
|
||
if (!result.CompletedSynchronously)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
httpOutput.EndWriteStreamedMessage(result);
|
||
return true;
|
||
}
|
||
|
||
static void OnWriteStreamedMessage(IAsyncResult result)
|
||
{
|
||
if (result.CompletedSynchronously)
|
||
{
|
||
return;
|
||
}
|
||
|
||
SendAsyncResult thisPtr = (SendAsyncResult)result.AsyncState;
|
||
Exception completionException = null;
|
||
bool completeSelf = false;
|
||
|
||
try
|
||
{
|
||
completeSelf = thisPtr.HandleWriteStreamedMessage(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (Fx.IsFatal(ex))
|
||
{
|
||
throw;
|
||
}
|
||
completeSelf = true;
|
||
completionException = ex;
|
||
}
|
||
|
||
if (completeSelf)
|
||
{
|
||
if (completionException != null)
|
||
{
|
||
thisPtr.httpOutput.TraceSend();
|
||
}
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
void CompleteWriteBody(IAsyncResult result)
|
||
{
|
||
httpOutput.outputStream.EndWrite(result);
|
||
}
|
||
|
||
public static void End(IAsyncResult result)
|
||
{
|
||
AsyncResult.End<SendAsyncResult>(result);
|
||
}
|
||
|
||
static void OnGetOutputStream(IAsyncResult result)
|
||
{
|
||
if (result.CompletedSynchronously)
|
||
return;
|
||
|
||
SendAsyncResult thisPtr = (SendAsyncResult)result.AsyncState;
|
||
|
||
Exception completionException = null;
|
||
bool completeSelf = false;
|
||
try
|
||
{
|
||
thisPtr.httpOutput.outputStream = thisPtr.httpOutput.EndGetOutputStream(result);
|
||
thisPtr.httpOutput.ApplyChannelBinding();
|
||
|
||
if (!thisPtr.httpOutput.streamed && thisPtr.httpOutput.IsChannelBindingSupportEnabled)
|
||
{
|
||
thisPtr.buffer = thisPtr.httpOutput.SerializeBufferedMessage(thisPtr.httpOutput.message);
|
||
thisPtr.httpOutput.SetContentLength(thisPtr.buffer.Count);
|
||
}
|
||
|
||
if (thisPtr.WriteMessage(false))
|
||
{
|
||
thisPtr.httpOutput.TraceSend();
|
||
completeSelf = true;
|
||
}
|
||
}
|
||
#pragma warning suppress 56500 // Microsoft, transferring exception to another thread
|
||
catch (Exception e)
|
||
{
|
||
if (Fx.IsFatal(e))
|
||
{
|
||
throw;
|
||
}
|
||
completeSelf = true;
|
||
completionException = e;
|
||
}
|
||
if (completeSelf)
|
||
{
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
static void OnWriteStreamedMessageLater(object state)
|
||
{
|
||
SendAsyncResult thisPtr = (SendAsyncResult)state;
|
||
|
||
bool completeSelf = false;
|
||
Exception completionException = null;
|
||
try
|
||
{
|
||
completeSelf = thisPtr.WriteStreamedMessage();
|
||
}
|
||
#pragma warning suppress 56500 // Microsoft, transferring exception to another thread
|
||
catch (Exception e)
|
||
{
|
||
if (Fx.IsFatal(e))
|
||
{
|
||
throw;
|
||
}
|
||
completeSelf = true;
|
||
completionException = e;
|
||
}
|
||
|
||
if (completeSelf)
|
||
{
|
||
if (completionException != null)
|
||
{
|
||
thisPtr.httpOutput.TraceSend();
|
||
}
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
static void OnWriteBody(IAsyncResult result)
|
||
{
|
||
if (result.CompletedSynchronously)
|
||
return;
|
||
|
||
SendAsyncResult thisPtr = (SendAsyncResult)result.AsyncState;
|
||
|
||
Exception completionException = null;
|
||
try
|
||
{
|
||
thisPtr.CompleteWriteBody(result);
|
||
thisPtr.httpOutput.TraceSend();
|
||
}
|
||
#pragma warning suppress 56500 // Microsoft, transferring exception to another thread
|
||
catch (Exception e)
|
||
{
|
||
if (Fx.IsFatal(e))
|
||
{
|
||
throw;
|
||
}
|
||
completionException = e;
|
||
}
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
internal static HttpOutput CreateHttpOutput(HttpWebRequest httpWebRequest, IHttpTransportFactorySettings settings, Message message, bool enableChannelBindingSupport)
|
||
{
|
||
return new WebRequestHttpOutput(httpWebRequest, settings, message, enableChannelBindingSupport);
|
||
}
|
||
|
||
internal static HttpOutput CreateHttpOutput(HttpListenerResponse httpListenerResponse, IHttpTransportFactorySettings settings, Message message, string httpMethod)
|
||
{
|
||
return new ListenerResponseHttpOutput(httpListenerResponse, settings, message, httpMethod);
|
||
}
|
||
|
||
class WebRequestHttpOutput : HttpOutput
|
||
{
|
||
HttpWebRequest httpWebRequest;
|
||
ChannelBinding channelBindingToken;
|
||
bool enableChannelBindingSupport;
|
||
|
||
public WebRequestHttpOutput(HttpWebRequest httpWebRequest, IHttpTransportFactorySettings settings, Message message, bool enableChannelBindingSupport)
|
||
: base(settings, message, true, false)
|
||
{
|
||
this.httpWebRequest = httpWebRequest;
|
||
this.enableChannelBindingSupport = enableChannelBindingSupport;
|
||
}
|
||
|
||
public override void Abort(HttpAbortReason abortReason)
|
||
{
|
||
httpWebRequest.Abort();
|
||
base.Abort(abortReason);
|
||
}
|
||
|
||
protected override void AddMimeVersion(string version)
|
||
{
|
||
httpWebRequest.Headers[HttpChannelUtilities.MIMEVersionHeader] = version;
|
||
}
|
||
|
||
protected override void AddHeader(string name, string value)
|
||
{
|
||
httpWebRequest.Headers.Add(name, value);
|
||
}
|
||
|
||
protected override void SetContentType(string contentType)
|
||
{
|
||
httpWebRequest.ContentType = contentType;
|
||
}
|
||
|
||
protected override void SetContentEncoding(string contentEncoding)
|
||
{
|
||
this.httpWebRequest.Headers.Add(HttpChannelUtilities.ContentEncodingHeader, contentEncoding);
|
||
}
|
||
|
||
protected override void SetContentLength(int contentLength)
|
||
{
|
||
if (contentLength == 0 // work around whidbey issue with setting ContentLength - (see MB36881)
|
||
&& !this.enableChannelBindingSupport) //When ChannelBinding is enabled, content length isn't supported
|
||
{
|
||
httpWebRequest.ContentLength = contentLength;
|
||
}
|
||
}
|
||
|
||
protected override void SetStatusCode(HttpStatusCode statusCode)
|
||
{
|
||
}
|
||
|
||
protected override void SetStatusDescription(string statusDescription)
|
||
{
|
||
}
|
||
|
||
protected override bool WillGetOutputStreamCompleteSynchronously
|
||
{
|
||
get { return false; }
|
||
}
|
||
|
||
protected override bool IsChannelBindingSupportEnabled
|
||
{
|
||
get
|
||
{
|
||
return this.enableChannelBindingSupport;
|
||
}
|
||
}
|
||
|
||
protected override ChannelBinding ChannelBinding
|
||
{
|
||
get
|
||
{
|
||
return this.channelBindingToken;
|
||
}
|
||
}
|
||
|
||
protected override bool CleanupChannelBinding
|
||
{
|
||
get
|
||
{
|
||
//client side channel binding token will be attached to the inbound response message also, so
|
||
//we need to not clean up the CBT object for this HttpOutput object.
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//Used to allow the channel binding object to be transferred to the
|
||
//WebResponseHttpInput object.
|
||
public override ChannelBinding TakeChannelBinding()
|
||
{
|
||
ChannelBinding result = this.channelBindingToken;
|
||
this.channelBindingToken = null;
|
||
return result;
|
||
}
|
||
|
||
protected override Stream GetOutputStream()
|
||
{
|
||
try
|
||
{
|
||
Stream outputStream;
|
||
if (this.IsChannelBindingSupportEnabled)
|
||
{
|
||
TransportContext context;
|
||
outputStream = httpWebRequest.GetRequestStream(out context);
|
||
this.channelBindingToken = ChannelBindingUtility.GetToken(context);
|
||
}
|
||
else
|
||
{
|
||
outputStream = httpWebRequest.GetRequestStream();
|
||
}
|
||
|
||
outputStream = new WebRequestOutputStream(outputStream, httpWebRequest, this);
|
||
|
||
return outputStream;
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, abortReason));
|
||
}
|
||
}
|
||
|
||
protected override IAsyncResult BeginGetOutputStream(AsyncCallback callback, object state)
|
||
{
|
||
return new GetOutputStreamAsyncResult(httpWebRequest, this, callback, state);
|
||
}
|
||
|
||
protected override Stream EndGetOutputStream(IAsyncResult result)
|
||
{
|
||
return GetOutputStreamAsyncResult.End(result, out this.channelBindingToken);
|
||
}
|
||
|
||
protected override bool PrepareHttpSend(Message message)
|
||
{
|
||
bool wasContentTypeSet = false;
|
||
|
||
string action = message.Headers.Action;
|
||
|
||
if (action != null)
|
||
{
|
||
//This code is calling UrlPathEncode due to MessageBus bug 53362.
|
||
//After reviewing this decision, we
|
||
//feel that this was probably the wrong thing to do because UrlPathEncode
|
||
//doesn't escape some characters like '+', '%', etc. The real issue behind
|
||
//bug 53362 may have been as simple as being encoded multiple times on the client
|
||
//but being decoded one time on the server. Calling UrlEncode would correctly
|
||
//escape these characters, but since we don't want to break any customers and no
|
||
//customers have complained, we will leave this as is for now...
|
||
action = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", UrlUtility.UrlPathEncode(action));
|
||
}
|
||
|
||
bool suppressEntityBody = base.PrepareHttpSend(message);
|
||
|
||
object property;
|
||
if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out property))
|
||
{
|
||
HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)property;
|
||
httpWebRequest.Method = requestProperty.Method;
|
||
// Query string was applied in HttpChannelFactory.ApplyManualAddressing
|
||
WebHeaderCollection requestHeaders = requestProperty.Headers;
|
||
suppressEntityBody = suppressEntityBody || requestProperty.SuppressEntityBody;
|
||
for (int i = 0; i < requestHeaders.Count; i++)
|
||
{
|
||
string name = requestHeaders.Keys[i];
|
||
string value = requestHeaders[i];
|
||
if (string.Compare(name, "accept", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
httpWebRequest.Accept = value;
|
||
}
|
||
else if (string.Compare(name, "connection", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
if (value.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) != -1)
|
||
{
|
||
httpWebRequest.KeepAlive = true;
|
||
}
|
||
else
|
||
{
|
||
httpWebRequest.Connection = value;
|
||
}
|
||
}
|
||
else if (string.Compare(name, "SOAPAction", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
if (action == null)
|
||
{
|
||
action = value;
|
||
}
|
||
else
|
||
{
|
||
if (value.Length > 0 && string.Compare(value, action, StringComparison.Ordinal) != 0)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.HttpSoapActionMismatch, action, value)));
|
||
}
|
||
}
|
||
}
|
||
else if (string.Compare(name, "content-length", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
// this will be taken care of by System.Net when we write to the content
|
||
}
|
||
else if (string.Compare(name, "content-type", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
httpWebRequest.ContentType = value;
|
||
wasContentTypeSet = true;
|
||
}
|
||
else if (string.Compare(name, "expect", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
if (value.ToUpperInvariant().IndexOf("100-CONTINUE", StringComparison.OrdinalIgnoreCase) != -1)
|
||
{
|
||
httpWebRequest.ServicePoint.Expect100Continue = true;
|
||
}
|
||
else
|
||
{
|
||
httpWebRequest.Expect = value;
|
||
}
|
||
}
|
||
else if (string.Compare(name, "host", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
// this should be controlled through Via
|
||
}
|
||
else if (string.Compare(name, "referer", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
// referrer is proper spelling, but referer is the what is in the protocol.
|
||
httpWebRequest.Referer = value;
|
||
}
|
||
else if (string.Compare(name, "transfer-encoding", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
if (value.ToUpperInvariant().IndexOf("CHUNKED", StringComparison.OrdinalIgnoreCase) != -1)
|
||
{
|
||
httpWebRequest.SendChunked = true;
|
||
}
|
||
else
|
||
{
|
||
httpWebRequest.TransferEncoding = value;
|
||
}
|
||
}
|
||
else if (string.Compare(name, "user-agent", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
httpWebRequest.UserAgent = value;
|
||
}
|
||
else if (string.Compare(name, "if-modified-since", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
DateTime modifiedSinceDate;
|
||
if (DateTime.TryParse(value, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out modifiedSinceDate))
|
||
{
|
||
httpWebRequest.IfModifiedSince = modifiedSinceDate;
|
||
}
|
||
else
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.HttpIfModifiedSinceParseError, value)));
|
||
}
|
||
}
|
||
else if (string.Compare(name, "date", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
// this will be taken care of by System.Net when we make the request
|
||
}
|
||
else if (string.Compare(name, "proxy-connection", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
// set by System.Net if using a proxy.
|
||
}
|
||
else if (string.Compare(name, "range", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
// we don't support ranges in v1.
|
||
}
|
||
else
|
||
{
|
||
httpWebRequest.Headers.Add(name, value);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (action != null)
|
||
{
|
||
if (message.Version.Envelope == EnvelopeVersion.Soap11)
|
||
{
|
||
httpWebRequest.Headers["SOAPAction"] = action;
|
||
}
|
||
else if (message.Version.Envelope == EnvelopeVersion.Soap12)
|
||
{
|
||
if (message.Version.Addressing == AddressingVersion.None)
|
||
{
|
||
bool shouldSetContentType = true;
|
||
if (wasContentTypeSet)
|
||
{
|
||
if (httpWebRequest.ContentType.Contains("action")
|
||
|| httpWebRequest.ContentType.ToUpperInvariant().IndexOf("ACTION", StringComparison.OrdinalIgnoreCase) != -1)
|
||
{
|
||
try
|
||
{
|
||
ContentType parsedContentType = new ContentType(httpWebRequest.ContentType);
|
||
if (parsedContentType.Parameters.ContainsKey("action"))
|
||
{
|
||
string value = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", parsedContentType.Parameters["action"]);
|
||
if (string.Compare(value, action, StringComparison.Ordinal) != 0)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.HttpSoapActionMismatchContentType, action, value)));
|
||
}
|
||
shouldSetContentType = false;
|
||
}
|
||
}
|
||
catch (FormatException formatException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.HttpContentTypeFormatException, formatException.Message, httpWebRequest.ContentType), formatException));
|
||
}
|
||
}
|
||
}
|
||
|
||
if (shouldSetContentType)
|
||
{
|
||
httpWebRequest.ContentType = string.Format(CultureInfo.InvariantCulture, "{0}; action={1}", httpWebRequest.ContentType, action);
|
||
}
|
||
}
|
||
}
|
||
else if (message.Version.Envelope != EnvelopeVersion.None)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new ProtocolException(SR.GetString(SR.EnvelopeVersionUnknown,
|
||
message.Version.Envelope.ToString())));
|
||
}
|
||
}
|
||
|
||
// since we don't get the output stream in send when retVal == true,
|
||
// we need to disable chunking for some verbs (DELETE/PUT)
|
||
if (suppressEntityBody)
|
||
{
|
||
httpWebRequest.SendChunked = false;
|
||
}
|
||
else if (this.IsChannelBindingSupportEnabled)
|
||
{
|
||
//force chunked upload since the length of the message is unknown before encoding.
|
||
httpWebRequest.SendChunked = true;
|
||
}
|
||
|
||
return suppressEntityBody;
|
||
}
|
||
|
||
protected override void PrepareHttpSendCore(HttpResponseMessage message)
|
||
{
|
||
// HTTP pipeline for client side is not implemented yet
|
||
// DCR CSDMain 216853 is tracking this
|
||
Fx.Assert(false, "HTTP pipeline for client is not implemented yet. This method should not be called.");
|
||
}
|
||
|
||
class GetOutputStreamAsyncResult : AsyncResult
|
||
{
|
||
static AsyncCallback onGetRequestStream = Fx.ThunkCallback(new AsyncCallback(OnGetRequestStream));
|
||
HttpOutput httpOutput;
|
||
HttpWebRequest httpWebRequest;
|
||
Stream outputStream;
|
||
ChannelBinding channelBindingToken;
|
||
|
||
public GetOutputStreamAsyncResult(HttpWebRequest httpWebRequest, HttpOutput httpOutput, AsyncCallback callback, object state)
|
||
: base(callback, state)
|
||
{
|
||
this.httpWebRequest = httpWebRequest;
|
||
this.httpOutput = httpOutput;
|
||
|
||
IAsyncResult result = null;
|
||
try
|
||
{
|
||
result = httpWebRequest.BeginGetRequestStream(onGetRequestStream, this);
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
|
||
if (result.CompletedSynchronously)
|
||
{
|
||
CompleteGetRequestStream(result);
|
||
base.Complete(true);
|
||
}
|
||
}
|
||
|
||
void CompleteGetRequestStream(IAsyncResult result)
|
||
{
|
||
try
|
||
{
|
||
TransportContext context;
|
||
this.outputStream = new WebRequestOutputStream(httpWebRequest.EndGetRequestStream(result, out context), httpWebRequest, this.httpOutput);
|
||
this.channelBindingToken = ChannelBindingUtility.GetToken(context);
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
}
|
||
|
||
public static Stream End(IAsyncResult result, out ChannelBinding channelBindingToken)
|
||
{
|
||
GetOutputStreamAsyncResult thisPtr = AsyncResult.End<GetOutputStreamAsyncResult>(result);
|
||
channelBindingToken = thisPtr.channelBindingToken;
|
||
return thisPtr.outputStream;
|
||
}
|
||
|
||
static void OnGetRequestStream(IAsyncResult result)
|
||
{
|
||
if (result.CompletedSynchronously)
|
||
return;
|
||
|
||
GetOutputStreamAsyncResult thisPtr = (GetOutputStreamAsyncResult)result.AsyncState;
|
||
|
||
Exception completionException = null;
|
||
try
|
||
{
|
||
thisPtr.CompleteGetRequestStream(result);
|
||
}
|
||
#pragma warning suppress 56500 // Microsoft, transferring exception to another thread
|
||
catch (Exception e)
|
||
{
|
||
if (Fx.IsFatal(e))
|
||
{
|
||
throw;
|
||
}
|
||
completionException = e;
|
||
}
|
||
thisPtr.Complete(false, completionException);
|
||
}
|
||
}
|
||
|
||
class WebRequestOutputStream : BytesReadPositionStream
|
||
{
|
||
HttpWebRequest httpWebRequest;
|
||
HttpOutput httpOutput;
|
||
int bytesSent = 0;
|
||
|
||
public WebRequestOutputStream(Stream requestStream, HttpWebRequest httpWebRequest, HttpOutput httpOutput)
|
||
: base(requestStream)
|
||
{
|
||
this.httpWebRequest = httpWebRequest;
|
||
this.httpOutput = httpOutput;
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
try
|
||
{
|
||
base.Close();
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestCanceledException(objectDisposedException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestIOException(ioException, httpWebRequest));
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
}
|
||
|
||
public override long Position
|
||
{
|
||
get
|
||
{
|
||
return bytesSent;
|
||
}
|
||
set
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupported)));
|
||
}
|
||
}
|
||
|
||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||
{
|
||
this.bytesSent += count;
|
||
try
|
||
{
|
||
return base.BeginWrite(buffer, offset, count, callback, state);
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestCanceledException(objectDisposedException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestIOException(ioException, httpWebRequest));
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
}
|
||
|
||
public override void EndWrite(IAsyncResult result)
|
||
{
|
||
try
|
||
{
|
||
base.EndWrite(result);
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestCanceledException(objectDisposedException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestIOException(ioException, httpWebRequest));
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
}
|
||
|
||
public override void Write(byte[] buffer, int offset, int count)
|
||
{
|
||
try
|
||
{
|
||
base.Write(buffer, offset, count);
|
||
}
|
||
catch (ObjectDisposedException objectDisposedException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestCanceledException(objectDisposedException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
catch (IOException ioException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestIOException(ioException, httpWebRequest));
|
||
}
|
||
catch (WebException webException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(HttpChannelUtilities.CreateRequestWebException(webException, httpWebRequest, httpOutput.abortReason));
|
||
}
|
||
this.bytesSent += count;
|
||
}
|
||
}
|
||
}
|
||
|
||
class ListenerResponseHttpOutput : HttpOutput
|
||
{
|
||
HttpListenerResponse listenerResponse;
|
||
string httpMethod;
|
||
|
||
[System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.Usage, "CA2214", Justification = "No one else is inhiriting from this class.")]
|
||
public ListenerResponseHttpOutput(HttpListenerResponse listenerResponse, IHttpTransportFactorySettings settings, Message message, string httpMethod)
|
||
: base(settings, message, false, true)
|
||
{
|
||
this.listenerResponse = listenerResponse;
|
||
this.httpMethod = httpMethod;
|
||
|
||
if (message.IsFault)
|
||
{
|
||
this.SetStatusCode(HttpStatusCode.InternalServerError);
|
||
}
|
||
else
|
||
{
|
||
this.SetStatusCode(HttpStatusCode.OK);
|
||
}
|
||
}
|
||
|
||
protected override string HttpMethod
|
||
{
|
||
get { return this.httpMethod; }
|
||
}
|
||
|
||
public override void Abort(HttpAbortReason abortReason)
|
||
{
|
||
listenerResponse.Abort();
|
||
base.Abort(abortReason);
|
||
}
|
||
|
||
protected override void AddMimeVersion(string version)
|
||
{
|
||
listenerResponse.Headers[HttpChannelUtilities.MIMEVersionHeader] = version;
|
||
}
|
||
|
||
protected override bool PrepareHttpSend(Message message)
|
||
{
|
||
bool result = base.PrepareHttpSend(message);
|
||
|
||
if (this.CanSendCompressedResponses)
|
||
{
|
||
string contentType = this.listenerResponse.ContentType;
|
||
string contentEncoding;
|
||
if (HttpChannelUtilities.GetHttpResponseTypeAndEncodingForCompression(ref contentType, out contentEncoding))
|
||
{
|
||
if (contentType != this.listenerResponse.ContentType)
|
||
{
|
||
this.SetContentType(contentType);
|
||
}
|
||
this.SetContentEncoding(contentEncoding);
|
||
}
|
||
}
|
||
|
||
HttpResponseMessageProperty responseProperty = message.Properties.GetValue<HttpResponseMessageProperty>(HttpResponseMessageProperty.Name, true);
|
||
bool httpResponseMessagePropertyFound = responseProperty != null;
|
||
bool httpMethodIsHead = string.Compare(this.httpMethod, "HEAD", StringComparison.OrdinalIgnoreCase) == 0;
|
||
|
||
if (httpMethodIsHead ||
|
||
httpResponseMessagePropertyFound && responseProperty.SuppressEntityBody)
|
||
{
|
||
result = true;
|
||
this.SetContentLength(0);
|
||
this.SetContentType(null);
|
||
listenerResponse.SendChunked = false;
|
||
}
|
||
|
||
if (httpResponseMessagePropertyFound)
|
||
{
|
||
this.SetStatusCode(responseProperty.StatusCode);
|
||
if (responseProperty.StatusDescription != null)
|
||
{
|
||
this.SetStatusDescription(responseProperty.StatusDescription);
|
||
}
|
||
|
||
WebHeaderCollection responseHeaders = responseProperty.Headers;
|
||
for (int i = 0; i < responseHeaders.Count; i++)
|
||
{
|
||
string name = responseHeaders.Keys[i];
|
||
string value = responseHeaders[i];
|
||
if (string.Compare(name, "content-length", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
int contentLength = -1;
|
||
if (httpMethodIsHead &&
|
||
int.TryParse(value, out contentLength))
|
||
{
|
||
this.SetContentLength(contentLength);
|
||
}
|
||
// else
|
||
//this will be taken care of by System.Net when we write to the content
|
||
}
|
||
else if (string.Compare(name, "content-type", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
if (httpMethodIsHead ||
|
||
!responseProperty.SuppressEntityBody)
|
||
{
|
||
this.SetContentType(value);
|
||
}
|
||
}
|
||
else if (string.Compare(name, "Connection", StringComparison.OrdinalIgnoreCase) == 0 &&
|
||
value != null &&
|
||
string.Compare(value.Trim(), "close", StringComparison.OrdinalIgnoreCase) == 0 &&
|
||
!LocalAppContextSwitches.DisableExplicitConnectionCloseHeader)
|
||
{
|
||
// HttpListenerResponse will not serialize the Connection:close header
|
||
// if its KeepAlive is true. So in the case where a service has explicitly
|
||
// added Connection:close (not added by default) set KeepAlive to false.
|
||
// This will cause HttpListenerResponse to add its own Connection:close header
|
||
// and to serialize it properly. We do not add a redundant header here.
|
||
this.listenerResponse.KeepAlive = false;
|
||
}
|
||
else
|
||
{
|
||
this.AddHeader(name, value);
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
protected override void PrepareHttpSendCore(HttpResponseMessage message)
|
||
{
|
||
this.listenerResponse.StatusCode = (int)message.StatusCode;
|
||
if (message.ReasonPhrase != null)
|
||
{
|
||
this.listenerResponse.StatusDescription = message.ReasonPhrase;
|
||
}
|
||
HttpChannelUtilities.CopyHeaders(message, AddHeader);
|
||
}
|
||
|
||
protected override void AddHeader(string name, string value)
|
||
{
|
||
if (string.Compare(name, "WWW-Authenticate", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
listenerResponse.AddHeader(name, value);
|
||
}
|
||
else
|
||
{
|
||
listenerResponse.AppendHeader(name, value);
|
||
}
|
||
}
|
||
|
||
protected override void SetContentType(string contentType)
|
||
{
|
||
listenerResponse.ContentType = contentType;
|
||
}
|
||
|
||
protected override void SetContentEncoding(string contentEncoding)
|
||
{
|
||
this.listenerResponse.AddHeader(HttpChannelUtilities.ContentEncodingHeader, contentEncoding);
|
||
}
|
||
|
||
protected override void SetContentLength(int contentLength)
|
||
{
|
||
listenerResponse.ContentLength64 = contentLength;
|
||
}
|
||
|
||
protected override void SetStatusCode(HttpStatusCode statusCode)
|
||
{
|
||
listenerResponse.StatusCode = (int)statusCode;
|
||
}
|
||
|
||
protected override void SetStatusDescription(string statusDescription)
|
||
{
|
||
listenerResponse.StatusDescription = statusDescription;
|
||
}
|
||
|
||
protected override Stream GetOutputStream()
|
||
{
|
||
return new ListenerResponseOutputStream(listenerResponse);
|
||
}
|
||
|
||
class ListenerResponseOutputStream : BytesReadPositionStream
|
||
{
|
||
public ListenerResponseOutputStream(HttpListenerResponse listenerResponse)
|
||
: base(listenerResponse.OutputStream)
|
||
{
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
try
|
||
{
|
||
base.Close();
|
||
}
|
||
catch (HttpListenerException listenerException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
HttpChannelUtilities.CreateCommunicationException(listenerException));
|
||
}
|
||
}
|
||
|
||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||
{
|
||
try
|
||
{
|
||
return base.BeginWrite(buffer, offset, count, callback, state);
|
||
}
|
||
catch (HttpListenerException listenerException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
HttpChannelUtilities.CreateCommunicationException(listenerException));
|
||
}
|
||
catch (ApplicationException applicationException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new CommunicationObjectAbortedException(SR.GetString(SR.HttpResponseAborted),
|
||
applicationException));
|
||
}
|
||
}
|
||
|
||
public override void EndWrite(IAsyncResult result)
|
||
{
|
||
try
|
||
{
|
||
base.EndWrite(result);
|
||
}
|
||
catch (HttpListenerException listenerException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
HttpChannelUtilities.CreateCommunicationException(listenerException));
|
||
}
|
||
catch (ApplicationException applicationException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new CommunicationObjectAbortedException(SR.GetString(SR.HttpResponseAborted),
|
||
applicationException));
|
||
}
|
||
}
|
||
|
||
public override void Write(byte[] buffer, int offset, int count)
|
||
{
|
||
try
|
||
{
|
||
base.Write(buffer, offset, count);
|
||
}
|
||
catch (HttpListenerException listenerException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
HttpChannelUtilities.CreateCommunicationException(listenerException));
|
||
}
|
||
catch (ApplicationException applicationException)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new CommunicationObjectAbortedException(SR.GetString(SR.HttpResponseAborted),
|
||
applicationException));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
enum HttpAbortReason
|
||
{
|
||
None,
|
||
Aborted,
|
||
TimedOut
|
||
}
|
||
|
||
delegate void AddHeaderDelegate(string headerName, string headerValue);
|
||
|
||
static class HttpChannelUtilities
|
||
{
|
||
internal static class StatusDescriptionStrings
|
||
{
|
||
internal const string HttpContentTypeMissing = "Missing Content Type";
|
||
internal const string HttpContentTypeMismatch = "Cannot process the message because the content type '{0}' was not the expected type '{1}'.";
|
||
internal const string HttpStatusServiceActivationException = "System.ServiceModel.ServiceActivationException";
|
||
}
|
||
|
||
internal static class ObsoleteDescriptionStrings
|
||
{
|
||
internal const string PropertyObsoleteUseAllowCookies = "This property is obsolete. To enable Http CookieContainer, use the AllowCookies property instead.";
|
||
internal const string TypeObsoleteUseAllowCookies = "This type is obsolete. To enable the Http CookieContainer, use the AllowCookies property on the http binding or on the HttpTransportBindingElement.";
|
||
}
|
||
|
||
internal const string HttpStatusCodeKey = "HttpStatusCode";
|
||
internal const string HttpStatusCodeExceptionKey = "System.ServiceModel.Channels.HttpInput.HttpStatusCode";
|
||
internal const string HttpStatusDescriptionExceptionKey = "System.ServiceModel.Channels.HttpInput.HttpStatusDescription";
|
||
|
||
internal const int ResponseStreamExcerptSize = 1024;
|
||
|
||
internal const string MIMEVersionHeader = "MIME-Version";
|
||
|
||
internal const string ContentEncodingHeader = "Content-Encoding";
|
||
internal const string AcceptEncodingHeader = "Accept-Encoding";
|
||
|
||
private const string ContentLengthHeader = "Content-Length";
|
||
private static readonly HashSet<string> httpContentHeaders = new HashSet<string>()
|
||
{
|
||
"Allow", "Content-Encoding", "Content-Language", "Content-Location", "Content-MD5",
|
||
"Content-Range", "Expires", "Last-Modified", "Content-Type", ContentLengthHeader
|
||
};
|
||
|
||
static bool allReferencedAssembliesLoaded = false;
|
||
|
||
public static Exception CreateCommunicationException(HttpListenerException listenerException)
|
||
{
|
||
switch (listenerException.NativeErrorCode)
|
||
{
|
||
case UnsafeNativeMethods.ERROR_NO_TRACKING_SERVICE:
|
||
return new CommunicationException(SR.GetString(SR.HttpNoTrackingService, listenerException.Message), listenerException);
|
||
|
||
case UnsafeNativeMethods.ERROR_NETNAME_DELETED:
|
||
return new CommunicationException(SR.GetString(SR.HttpNetnameDeleted, listenerException.Message), listenerException);
|
||
|
||
case UnsafeNativeMethods.ERROR_INVALID_HANDLE:
|
||
return new CommunicationObjectAbortedException(SR.GetString(SR.HttpResponseAborted), listenerException);
|
||
|
||
case UnsafeNativeMethods.ERROR_NOT_ENOUGH_MEMORY:
|
||
case UnsafeNativeMethods.ERROR_OUTOFMEMORY:
|
||
case UnsafeNativeMethods.ERROR_NO_SYSTEM_RESOURCES:
|
||
return new InsufficientMemoryException(SR.GetString(SR.InsufficentMemory), listenerException);
|
||
|
||
default:
|
||
return new CommunicationException(listenerException.Message, listenerException);
|
||
}
|
||
}
|
||
|
||
public static void EnsureHttpRequestMessageContentNotNull(HttpRequestMessage httpRequestMessage)
|
||
{
|
||
if (httpRequestMessage.Content == null)
|
||
{
|
||
httpRequestMessage.Content = new ByteArrayContent(EmptyArray<byte>.Instance);
|
||
}
|
||
}
|
||
|
||
public static void EnsureHttpResponseMessageContentNotNull(HttpResponseMessage httpResponseMessage)
|
||
{
|
||
if (httpResponseMessage.Content == null)
|
||
{
|
||
httpResponseMessage.Content = new ByteArrayContent(EmptyArray<byte>.Instance);
|
||
}
|
||
}
|
||
|
||
public static bool IsEmpty(HttpResponseMessage httpResponseMessage)
|
||
{
|
||
return httpResponseMessage.Content == null
|
||
|| (httpResponseMessage.Content.Headers.ContentLength.HasValue && httpResponseMessage.Content.Headers.ContentLength.Value == 0);
|
||
}
|
||
|
||
internal static void HandleContinueWithTask(Task task)
|
||
{
|
||
HandleContinueWithTask(task, null);
|
||
}
|
||
|
||
internal static void HandleContinueWithTask(Task task, Action<Exception> exceptionHandler)
|
||
{
|
||
if (task.IsFaulted)
|
||
{
|
||
if (exceptionHandler == null)
|
||
{
|
||
throw FxTrace.Exception.AsError<FaultException>(task.Exception);
|
||
}
|
||
else
|
||
{
|
||
exceptionHandler.Invoke(task.Exception);
|
||
}
|
||
}
|
||
else if (task.IsCanceled)
|
||
{
|
||
throw FxTrace.Exception.AsError(new TimeoutException(SR.GetString(SR.TaskCancelledError)));
|
||
}
|
||
}
|
||
|
||
public static void AbortRequest(HttpWebRequest request)
|
||
{
|
||
request.Abort();
|
||
}
|
||
|
||
public static void SetRequestTimeout(HttpWebRequest request, TimeSpan timeout)
|
||
{
|
||
int millisecondsTimeout = TimeoutHelper.ToMilliseconds(timeout);
|
||
if (millisecondsTimeout == 0)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TimeoutException(SR.GetString(
|
||
SR.HttpRequestTimedOut, request.RequestUri, timeout)));
|
||
}
|
||
request.Timeout = millisecondsTimeout;
|
||
request.ReadWriteTimeout = millisecondsTimeout;
|
||
}
|
||
|
||
public static void AddReplySecurityProperty(HttpChannelFactory<IRequestChannel> factory, HttpWebRequest webRequest,
|
||
HttpWebResponse webResponse, Message replyMessage)
|
||
{
|
||
SecurityMessageProperty securityProperty = factory.CreateReplySecurityProperty(webRequest, webResponse);
|
||
if (securityProperty != null)
|
||
{
|
||
replyMessage.Properties.Security = securityProperty;
|
||
}
|
||
}
|
||
|
||
public static void CopyHeaders(HttpRequestMessage request, AddHeaderDelegate addHeader)
|
||
{
|
||
HttpChannelUtilities.CopyHeaders(request.Headers, addHeader);
|
||
if (request.Content != null)
|
||
{
|
||
HttpChannelUtilities.CopyHeaders(request.Content.Headers, addHeader);
|
||
}
|
||
}
|
||
|
||
public static void CopyHeaders(HttpResponseMessage response, AddHeaderDelegate addHeader)
|
||
{
|
||
HttpChannelUtilities.CopyHeaders(response.Headers, addHeader);
|
||
if (response.Content != null)
|
||
{
|
||
HttpChannelUtilities.CopyHeaders(response.Content.Headers, addHeader);
|
||
}
|
||
}
|
||
|
||
static void CopyHeaders(HttpHeaders headers, AddHeaderDelegate addHeader)
|
||
{
|
||
foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
|
||
{
|
||
foreach (string value in header.Value)
|
||
{
|
||
TryAddToCollection(addHeader, header.Key, value);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void CopyHeaders(NameValueCollection headers, AddHeaderDelegate addHeader)
|
||
{
|
||
//this nested loop logic was copied from NameValueCollection.Add(NameValueCollection)
|
||
int count = headers.Count;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
string key = headers.GetKey(i);
|
||
|
||
string[] values = headers.GetValues(i);
|
||
if (values != null)
|
||
{
|
||
for (int j = 0; j < values.Length; j++)
|
||
{
|
||
TryAddToCollection(addHeader, key, values[j]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
addHeader(key, null);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void CopyHeadersToNameValueCollection(NameValueCollection headers, NameValueCollection destination)
|
||
{
|
||
CopyHeaders(headers, destination.Add);
|
||
}
|
||
|
||
[System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability104",
|
||
Justification = "The exceptions are traced already.")]
|
||
static void TryAddToCollection(AddHeaderDelegate addHeader, string headerName, string value)
|
||
{
|
||
try
|
||
{
|
||
addHeader(headerName, value);
|
||
}
|
||
catch (ArgumentException ex)
|
||
{
|
||
string encodedValue = null;
|
||
if (TryEncodeHeaderValueAsUri(headerName, value, out encodedValue))
|
||
{
|
||
//note: if the hosthame of a referer header contains illegal chars, we will still throw from here
|
||
//because Uri will not fix this up for us, which is ok. The request will get rejected in the error code path.
|
||
addHeader(headerName, encodedValue);
|
||
}
|
||
else
|
||
{
|
||
// In self-hosted scenarios, some of the headers like Content-Length cannot be added directly.
|
||
// It will throw ArgumentException instead.
|
||
FxTrace.Exception.AsInformation(ex);
|
||
}
|
||
}
|
||
}
|
||
|
||
static bool TryEncodeHeaderValueAsUri(string headerName, string value, out string result)
|
||
{
|
||
result = null;
|
||
//Internet Explorer will send the referrer header on the wire in unicode without encoding it
|
||
//this will cause errors when added to a WebHeaderCollection. This is a workaround for sharepoint,
|
||
//but will only work for WebHosted Scenarios.
|
||
if (String.Compare(headerName, "Referer", StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
Uri uri;
|
||
if (Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out uri))
|
||
{
|
||
if (uri.IsAbsoluteUri)
|
||
{
|
||
result = uri.AbsoluteUri;
|
||
}
|
||
else
|
||
{
|
||
result = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//
|
||
internal static Type GetTypeFromAssembliesInCurrentDomain(string typeString)
|
||
{
|
||
Type type = Type.GetType(typeString, false);
|
||
if (null == type)
|
||
{
|
||
if (!allReferencedAssembliesLoaded)
|
||
{
|
||
allReferencedAssembliesLoaded = true;
|
||
AspNetEnvironment.Current.EnsureAllReferencedAssemblyLoaded();
|
||
}
|
||
|
||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||
for (int i = 0; i < assemblies.Length; i++)
|
||
{
|
||
type = assemblies[i].GetType(typeString, false);
|
||
if (null != type)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return type;
|
||
}
|
||
|
||
public static NetworkCredential GetCredential(AuthenticationSchemes authenticationScheme,
|
||
SecurityTokenProviderContainer credentialProvider, TimeSpan timeout,
|
||
out TokenImpersonationLevel impersonationLevel, out AuthenticationLevel authenticationLevel)
|
||
{
|
||
impersonationLevel = TokenImpersonationLevel.None;
|
||
authenticationLevel = AuthenticationLevel.None;
|
||
|
||
NetworkCredential result = null;
|
||
|
||
if (authenticationScheme != AuthenticationSchemes.Anonymous)
|
||
{
|
||
result = GetCredentialCore(authenticationScheme, credentialProvider, timeout, out impersonationLevel, out authenticationLevel);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||
static NetworkCredential GetCredentialCore(AuthenticationSchemes authenticationScheme,
|
||
SecurityTokenProviderContainer credentialProvider, TimeSpan timeout,
|
||
out TokenImpersonationLevel impersonationLevel, out AuthenticationLevel authenticationLevel)
|
||
{
|
||
impersonationLevel = TokenImpersonationLevel.None;
|
||
authenticationLevel = AuthenticationLevel.None;
|
||
|
||
NetworkCredential result = null;
|
||
|
||
switch (authenticationScheme)
|
||
{
|
||
case AuthenticationSchemes.Basic:
|
||
result = TransportSecurityHelpers.GetUserNameCredential(credentialProvider, timeout);
|
||
impersonationLevel = TokenImpersonationLevel.Delegation;
|
||
break;
|
||
|
||
case AuthenticationSchemes.Digest:
|
||
result = TransportSecurityHelpers.GetSspiCredential(credentialProvider, timeout,
|
||
out impersonationLevel, out authenticationLevel);
|
||
|
||
HttpChannelUtilities.ValidateDigestCredential(ref result, impersonationLevel);
|
||
break;
|
||
|
||
case AuthenticationSchemes.Negotiate:
|
||
result = TransportSecurityHelpers.GetSspiCredential(credentialProvider, timeout,
|
||
out impersonationLevel, out authenticationLevel);
|
||
break;
|
||
|
||
case AuthenticationSchemes.Ntlm:
|
||
result = TransportSecurityHelpers.GetSspiCredential(credentialProvider, timeout,
|
||
out impersonationLevel, out authenticationLevel);
|
||
if (authenticationLevel == AuthenticationLevel.MutualAuthRequired)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
new InvalidOperationException(SR.GetString(SR.CredentialDisallowsNtlm)));
|
||
}
|
||
break;
|
||
|
||
default:
|
||
// The setter for this property should prevent this.
|
||
throw Fx.AssertAndThrow("GetCredential: Invalid authentication scheme");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
public static HttpWebResponse ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
|
||
{
|
||
HttpWebResponse response = null;
|
||
|
||
if (webException.Status == WebExceptionStatus.Success ||
|
||
webException.Status == WebExceptionStatus.ProtocolError)
|
||
{
|
||
response = (HttpWebResponse)webException.Response;
|
||
}
|
||
|
||
if (response == null)
|
||
{
|
||
Exception convertedException = ConvertWebException(webException, request, abortReason);
|
||
|
||
if (convertedException != null)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(convertedException);
|
||
}
|
||
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(webException.Message,
|
||
webException));
|
||
}
|
||
|
||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new EndpointNotFoundException(SR.GetString(SR.EndpointNotFound, request.RequestUri.AbsoluteUri), webException));
|
||
}
|
||
|
||
if (response.StatusCode == HttpStatusCode.ServiceUnavailable)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ServerTooBusyException(SR.GetString(SR.HttpServerTooBusy, request.RequestUri.AbsoluteUri), webException));
|
||
}
|
||
|
||
if (response.StatusCode == HttpStatusCode.UnsupportedMediaType)
|
||
{
|
||
string statusDescription = response.StatusDescription;
|
||
if (!string.IsNullOrEmpty(statusDescription))
|
||
{
|
||
if (string.Compare(statusDescription, HttpChannelUtilities.StatusDescriptionStrings.HttpContentTypeMissing, StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.GetString(SR.MissingContentType, request.RequestUri), webException));
|
||
}
|
||
}
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.GetString(SR.FramingContentTypeMismatch, request.ContentType, request.RequestUri), webException));
|
||
}
|
||
|
||
if (response.StatusCode == HttpStatusCode.GatewayTimeout)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TimeoutException(webException.Message, webException));
|
||
}
|
||
|
||
// if http.sys has a request queue on the TCP port, then if the path fails to match it will send
|
||
// back "<h1>Bad Request (Invalid Hostname)</h1>" in the body of a 400 response.
|
||
// See code at \\index1\sddnsrv\net\http\sys\httprcv.c for details
|
||
if (response.StatusCode == HttpStatusCode.BadRequest)
|
||
{
|
||
const string httpSysRequestQueueNotFound = "<h1>Bad Request (Invalid Hostname)</h1>";
|
||
const string httpSysRequestQueueNotFoundVista = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\"http://www.w3.org/TR/html4/strict.dtd\">\r\n<HTML><HEAD><TITLE>Bad Request</TITLE>\r\n<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=us-ascii\"></HEAD>\r\n<BODY><h2>Bad Request - Invalid Hostname</h2>\r\n<hr><p>HTTP Error 400. The request hostname is invalid.</p>\r\n</BODY></HTML>\r\n";
|
||
string notFoundTestString = null;
|
||
|
||
if (response.ContentLength == httpSysRequestQueueNotFound.Length)
|
||
{
|
||
notFoundTestString = httpSysRequestQueueNotFound;
|
||
}
|
||
else if (response.ContentLength == httpSysRequestQueueNotFoundVista.Length)
|
||
{
|
||
notFoundTestString = httpSysRequestQueueNotFoundVista;
|
||
}
|
||
|
||
if (notFoundTestString != null)
|
||
{
|
||
Stream responseStream = response.GetResponseStream();
|
||
byte[] responseBytes = new byte[notFoundTestString.Length];
|
||
int bytesRead = responseStream.Read(responseBytes, 0, responseBytes.Length);
|
||
|
||
// since the response is buffered by System.Net (it's an error response), we should have read
|
||
// the amount we were expecting
|
||
if (bytesRead == notFoundTestString.Length
|
||
&& notFoundTestString == UTF8Encoding.ASCII.GetString(responseBytes))
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new EndpointNotFoundException(SR.GetString(SR.EndpointNotFound, request.RequestUri.AbsoluteUri), webException));
|
||
}
|
||
}
|
||
}
|
||
|
||
return response;
|
||
}
|
||
|
||
public static Exception ConvertWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
|
||
{
|
||
switch (webException.Status)
|
||
{
|
||
case WebExceptionStatus.ConnectFailure:
|
||
case WebExceptionStatus.NameResolutionFailure:
|
||
case WebExceptionStatus.ProxyNameResolutionFailure:
|
||
return new EndpointNotFoundException(SR.GetString(SR.EndpointNotFound, request.RequestUri.AbsoluteUri), webException);
|
||
case WebExceptionStatus.SecureChannelFailure:
|
||
return new SecurityNegotiationException(SR.GetString(SR.SecureChannelFailure, request.RequestUri.Authority), webException);
|
||
case WebExceptionStatus.TrustFailure:
|
||
return new SecurityNegotiationException(SR.GetString(SR.TrustFailure, request.RequestUri.Authority), webException);
|
||
case WebExceptionStatus.Timeout:
|
||
return new TimeoutException(CreateRequestTimedOutMessage(request), webException);
|
||
case WebExceptionStatus.ReceiveFailure:
|
||
return new CommunicationException(SR.GetString(SR.HttpReceiveFailure, request.RequestUri), webException);
|
||
case WebExceptionStatus.SendFailure:
|
||
return new CommunicationException(SR.GetString(SR.HttpSendFailure, request.RequestUri), webException);
|
||
case WebExceptionStatus.RequestCanceled:
|
||
return CreateRequestCanceledException(webException, request, abortReason);
|
||
case WebExceptionStatus.ProtocolError:
|
||
HttpWebResponse response = (HttpWebResponse)webException.Response;
|
||
Fx.Assert(response != null, "'response' MUST NOT be NULL for WebExceptionStatus=='ProtocolError'.");
|
||
if (response.StatusCode == HttpStatusCode.InternalServerError &&
|
||
string.Compare(response.StatusDescription, HttpChannelUtilities.StatusDescriptionStrings.HttpStatusServiceActivationException, StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
return new ServiceActivationException(SR.GetString(SR.Hosting_ServiceActivationFailed, request.RequestUri));
|
||
}
|
||
else
|
||
{
|
||
return null;
|
||
}
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public static Exception CreateResponseIOException(IOException ioException, TimeSpan receiveTimeout)
|
||
{
|
||
if (ioException.InnerException is SocketException)
|
||
{
|
||
return SocketConnection.ConvertTransferException((SocketException)ioException.InnerException, receiveTimeout, ioException);
|
||
}
|
||
|
||
return new CommunicationException(SR.GetString(SR.HttpTransferError, ioException.Message), ioException);
|
||
}
|
||
|
||
public static Exception CreateResponseWebException(WebException webException, HttpWebResponse response)
|
||
{
|
||
switch (webException.Status)
|
||
{
|
||
case WebExceptionStatus.RequestCanceled:
|
||
return TraceResponseException(new CommunicationObjectAbortedException(SR.GetString(SR.HttpRequestAborted, response.ResponseUri), webException));
|
||
case WebExceptionStatus.ConnectionClosed:
|
||
return TraceResponseException(new CommunicationException(webException.Message, webException));
|
||
case WebExceptionStatus.Timeout:
|
||
return TraceResponseException(new TimeoutException(SR.GetString(SR.HttpResponseTimedOut, response.ResponseUri,
|
||
TimeSpan.FromMilliseconds(response.GetResponseStream().ReadTimeout)), webException));
|
||
default:
|
||
return CreateUnexpectedResponseException(webException, response);
|
||
}
|
||
}
|
||
|
||
public static Exception CreateRequestCanceledException(Exception webException, HttpWebRequest request, HttpAbortReason abortReason)
|
||
{
|
||
switch (abortReason)
|
||
{
|
||
case HttpAbortReason.Aborted:
|
||
return new CommunicationObjectAbortedException(SR.GetString(SR.HttpRequestAborted, request.RequestUri), webException);
|
||
case HttpAbortReason.TimedOut:
|
||
return new TimeoutException(CreateRequestTimedOutMessage(request), webException);
|
||
default:
|
||
return new CommunicationException(SR.GetString(SR.HttpTransferError, webException.Message), webException);
|
||
}
|
||
}
|
||
|
||
public static Exception CreateRequestIOException(IOException ioException, HttpWebRequest request)
|
||
{
|
||
return CreateRequestIOException(ioException, request, null);
|
||
}
|
||
|
||
public static Exception CreateRequestIOException(IOException ioException, HttpWebRequest request, Exception originalException)
|
||
{
|
||
Exception exception = originalException == null ? ioException : originalException;
|
||
|
||
if (ioException.InnerException is SocketException)
|
||
{
|
||
return SocketConnection.ConvertTransferException((SocketException)ioException.InnerException, TimeSpan.FromMilliseconds(request.Timeout), exception);
|
||
}
|
||
|
||
return new CommunicationException(SR.GetString(SR.HttpTransferError, exception.Message), exception);
|
||
}
|
||
|
||
static string CreateRequestTimedOutMessage(HttpWebRequest request)
|
||
{
|
||
return SR.GetString(SR.HttpRequestTimedOut, request.RequestUri, TimeSpan.FromMilliseconds(request.Timeout));
|
||
}
|
||
|
||
public static Exception CreateRequestWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
|
||
{
|
||
Exception convertedException = ConvertWebException(webException, request, abortReason);
|
||
|
||
if (webException.Response != null)
|
||
{
|
||
//free the connection for use by another request
|
||
webException.Response.Close();
|
||
}
|
||
|
||
if (convertedException != null)
|
||
{
|
||
return convertedException;
|
||
}
|
||
|
||
if (webException.InnerException is IOException)
|
||
{
|
||
return CreateRequestIOException((IOException)webException.InnerException, request, webException);
|
||
}
|
||
|
||
if (webException.InnerException is SocketException)
|
||
{
|
||
return SocketConnectionInitiator.ConvertConnectException((SocketException)webException.InnerException, request.RequestUri, TimeSpan.MaxValue, webException);
|
||
}
|
||
|
||
return new EndpointNotFoundException(SR.GetString(SR.EndpointNotFound, request.RequestUri.AbsoluteUri), webException);
|
||
}
|
||
|
||
static Exception CreateUnexpectedResponseException(WebException responseException, HttpWebResponse response)
|
||
{
|
||
string statusDescription = response.StatusDescription;
|
||
if (string.IsNullOrEmpty(statusDescription))
|
||
statusDescription = response.StatusCode.ToString();
|
||
|
||
return TraceResponseException(
|
||
new ProtocolException(SR.GetString(SR.UnexpectedHttpResponseCode,
|
||
(int)response.StatusCode, statusDescription), responseException));
|
||
}
|
||
|
||
public static Exception CreateNullReferenceResponseException(NullReferenceException nullReferenceException)
|
||
{
|
||
return TraceResponseException(
|
||
new ProtocolException(SR.GetString(SR.NullReferenceOnHttpResponse), nullReferenceException));
|
||
}
|
||
|
||
static string GetResponseStreamString(HttpWebResponse webResponse, out int bytesRead)
|
||
{
|
||
Stream responseStream = webResponse.GetResponseStream();
|
||
|
||
long bufferSize = webResponse.ContentLength;
|
||
|
||
if (bufferSize < 0 || bufferSize > ResponseStreamExcerptSize)
|
||
{
|
||
bufferSize = ResponseStreamExcerptSize;
|
||
}
|
||
|
||
byte[] responseBuffer = DiagnosticUtility.Utility.AllocateByteArray(checked((int)bufferSize));
|
||
bytesRead = responseStream.Read(responseBuffer, 0, (int)bufferSize);
|
||
responseStream.Close();
|
||
|
||
return System.Text.Encoding.UTF8.GetString(responseBuffer, 0, bytesRead);
|
||
}
|
||
|
||
static Exception TraceResponseException(Exception exception)
|
||
{
|
||
if (DiagnosticUtility.ShouldTraceError)
|
||
{
|
||
TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.HttpChannelUnexpectedResponse, SR.GetString(SR.TraceCodeHttpChannelUnexpectedResponse), (object)null, exception);
|
||
}
|
||
|
||
return exception;
|
||
}
|
||
|
||
static bool ValidateEmptyContent(HttpWebResponse response)
|
||
{
|
||
bool responseIsEmpty = true;
|
||
|
||
if (response.ContentLength > 0)
|
||
{
|
||
responseIsEmpty = false;
|
||
}
|
||
else if (response.ContentLength == -1) // chunked
|
||
{
|
||
Stream responseStream = response.GetResponseStream();
|
||
byte[] testBuffer = new byte[1];
|
||
responseIsEmpty = (responseStream.Read(testBuffer, 0, 1) != 1);
|
||
}
|
||
|
||
return responseIsEmpty;
|
||
}
|
||
|
||
static void ValidateAuthentication(HttpWebRequest request, HttpWebResponse response,
|
||
WebException responseException, HttpChannelFactory<IRequestChannel> factory)
|
||
{
|
||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||
{
|
||
string message = SR.GetString(SR.HttpAuthorizationFailed, factory.AuthenticationScheme,
|
||
response.Headers[HttpResponseHeader.WwwAuthenticate]);
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
TraceResponseException(new MessageSecurityException(message, responseException)));
|
||
}
|
||
|
||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||
{
|
||
string message = SR.GetString(SR.HttpAuthorizationForbidden, factory.AuthenticationScheme);
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
TraceResponseException(new MessageSecurityException(message, responseException)));
|
||
}
|
||
|
||
if ((request.AuthenticationLevel == AuthenticationLevel.MutualAuthRequired) &&
|
||
!response.IsMutuallyAuthenticated)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
TraceResponseException(new SecurityNegotiationException(SR.GetString(SR.HttpMutualAuthNotSatisfied),
|
||
responseException)));
|
||
}
|
||
}
|
||
|
||
public static void ValidateDigestCredential(ref NetworkCredential credential, TokenImpersonationLevel impersonationLevel)
|
||
{
|
||
// this is a work-around to VSWhidbey#470545 (Since the service always uses Impersonation,
|
||
// we mitigate EOP by preemtively not allowing Identification)
|
||
if (!SecurityUtils.IsDefaultNetworkCredential(credential))
|
||
{
|
||
// With a non-default credential, Digest will not honor a client impersonation constraint of
|
||
// TokenImpersonationLevel.Identification.
|
||
if (!TokenImpersonationLevelHelper.IsGreaterOrEqual(impersonationLevel,
|
||
TokenImpersonationLevel.Impersonation))
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(
|
||
SR.DigestExplicitCredsImpersonationLevel, impersonationLevel)));
|
||
}
|
||
}
|
||
}
|
||
|
||
// only valid response codes are 500 (if it's a fault) or 200 (iff it's a response message)
|
||
public static HttpInput ValidateRequestReplyResponse(HttpWebRequest request, HttpWebResponse response,
|
||
HttpChannelFactory<IRequestChannel> factory, WebException responseException, ChannelBinding channelBinding)
|
||
{
|
||
ValidateAuthentication(request, response, responseException, factory);
|
||
|
||
HttpInput httpInput = null;
|
||
|
||
// We will close the HttpWebResponse if we got an error code betwen 200 and 300 and
|
||
// 1) an exception was thrown out or
|
||
// 2) it's an empty message and we are using SOAP.
|
||
// For responses with status code above 300, System.Net will close the underlying connection so we don't need to worry about that.
|
||
if ((200 <= (int)response.StatusCode && (int)response.StatusCode < 300) || response.StatusCode == HttpStatusCode.InternalServerError)
|
||
{
|
||
if (response.StatusCode == HttpStatusCode.InternalServerError
|
||
&& string.Compare(response.StatusDescription, HttpChannelUtilities.StatusDescriptionStrings.HttpStatusServiceActivationException, StringComparison.OrdinalIgnoreCase) == 0)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ServiceActivationException(SR.GetString(SR.Hosting_ServiceActivationFailed, request.RequestUri)));
|
||
}
|
||
else
|
||
{
|
||
bool throwing = true;
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(response.ContentType))
|
||
{
|
||
if (!ValidateEmptyContent(response))
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TraceResponseException(
|
||
new ProtocolException(
|
||
SR.GetString(SR.HttpContentTypeHeaderRequired),
|
||
responseException)));
|
||
}
|
||
}
|
||
else if (response.ContentLength != 0)
|
||
{
|
||
MessageEncoder encoder = factory.MessageEncoderFactory.Encoder;
|
||
if (!encoder.IsContentTypeSupported(response.ContentType))
|
||
{
|
||
int bytesRead;
|
||
String responseExcerpt = GetResponseStreamString(response, out bytesRead);
|
||
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TraceResponseException(
|
||
new ProtocolException(
|
||
SR.GetString(
|
||
SR.ResponseContentTypeMismatch,
|
||
response.ContentType,
|
||
encoder.ContentType,
|
||
bytesRead,
|
||
responseExcerpt), responseException)));
|
||
|
||
}
|
||
|
||
httpInput = HttpInput.CreateHttpInput(response, factory, channelBinding);
|
||
httpInput.WebException = responseException;
|
||
}
|
||
|
||
throwing = false;
|
||
}
|
||
finally
|
||
{
|
||
if (throwing)
|
||
{
|
||
response.Close();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (httpInput == null)
|
||
{
|
||
if (factory.MessageEncoderFactory.MessageVersion == MessageVersion.None)
|
||
{
|
||
httpInput = HttpInput.CreateHttpInput(response, factory, channelBinding);
|
||
httpInput.WebException = responseException;
|
||
}
|
||
else
|
||
{
|
||
// In this case, we got a response with
|
||
// 1) status code between 200 and 300
|
||
// 2) Non-empty Content Type string
|
||
// 3) Zero content length
|
||
// Since we are trying to use SOAP here, the message seems to be malicious and we should
|
||
// just close the response directly.
|
||
response.Close();
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateUnexpectedResponseException(responseException, response));
|
||
}
|
||
|
||
return httpInput;
|
||
}
|
||
|
||
public static bool GetHttpResponseTypeAndEncodingForCompression(ref string contentType, out string contentEncoding)
|
||
{
|
||
contentEncoding = null;
|
||
bool isSession = false;
|
||
bool isDeflate = false;
|
||
|
||
if (string.Equals(BinaryVersion.GZipVersion1.ContentType, contentType, StringComparison.OrdinalIgnoreCase) ||
|
||
(isSession = string.Equals(BinaryVersion.GZipVersion1.SessionContentType, contentType, StringComparison.OrdinalIgnoreCase)) ||
|
||
(isDeflate = (string.Equals(BinaryVersion.DeflateVersion1.ContentType, contentType, StringComparison.OrdinalIgnoreCase) ||
|
||
(isSession = string.Equals(BinaryVersion.DeflateVersion1.SessionContentType, contentType, StringComparison.OrdinalIgnoreCase)))))
|
||
{
|
||
contentType = isSession ? BinaryVersion.Version1.SessionContentType : BinaryVersion.Version1.ContentType;
|
||
contentEncoding = isDeflate ? MessageEncoderCompressionHandler.DeflateContentEncoding : MessageEncoderCompressionHandler.GZipContentEncoding;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
abstract class HttpDelayedAcceptStream : DetectEofStream
|
||
{
|
||
HttpOutput httpOutput;
|
||
bool isHttpOutputClosed;
|
||
|
||
/// <summary>
|
||
/// Indicates whether the HttpOutput should be closed when this stream is closed. In the streamed case,
|
||
/// we<77>ll leave the HttpOutput opened (and it will be closed by the HttpRequestContext, so we won't leak it).
|
||
/// </summary>
|
||
bool closeHttpOutput;
|
||
|
||
// sometimes we can't flush the HTTP output until we're done reading the end of the
|
||
// incoming stream of the HTTP input
|
||
protected HttpDelayedAcceptStream(Stream stream)
|
||
: base(stream)
|
||
{
|
||
}
|
||
|
||
public bool EnableDelayedAccept(HttpOutput output, bool closeHttpOutput)
|
||
{
|
||
if (IsAtEof)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
this.closeHttpOutput = closeHttpOutput;
|
||
this.httpOutput = output;
|
||
return true;
|
||
}
|
||
|
||
protected override void OnReceivedEof()
|
||
{
|
||
if (this.closeHttpOutput)
|
||
{
|
||
CloseHttpOutput();
|
||
}
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
if (this.closeHttpOutput)
|
||
{
|
||
CloseHttpOutput();
|
||
}
|
||
|
||
base.Close();
|
||
}
|
||
|
||
void CloseHttpOutput()
|
||
{
|
||
if (this.httpOutput != null && !this.isHttpOutputClosed)
|
||
{
|
||
this.httpOutput.Close();
|
||
this.isHttpOutputClosed = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
abstract class BytesReadPositionStream : DelegatingStream
|
||
{
|
||
int bytesSent = 0;
|
||
|
||
protected BytesReadPositionStream(Stream stream)
|
||
: base(stream)
|
||
{
|
||
}
|
||
|
||
public override long Position
|
||
{
|
||
get
|
||
{
|
||
return bytesSent;
|
||
}
|
||
set
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupported)));
|
||
}
|
||
}
|
||
|
||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||
{
|
||
this.bytesSent += count;
|
||
return BaseStream.BeginWrite(buffer, offset, count, callback, state);
|
||
}
|
||
|
||
public override void Write(byte[] buffer, int offset, int count)
|
||
{
|
||
BaseStream.Write(buffer, offset, count);
|
||
this.bytesSent += count;
|
||
}
|
||
|
||
public override void WriteByte(byte value)
|
||
{
|
||
BaseStream.WriteByte(value);
|
||
this.bytesSent++;
|
||
}
|
||
}
|
||
|
||
class PreReadStream : DelegatingStream
|
||
{
|
||
byte[] preReadBuffer;
|
||
|
||
public PreReadStream(Stream stream, byte[] preReadBuffer)
|
||
: base(stream)
|
||
{
|
||
this.preReadBuffer = preReadBuffer;
|
||
}
|
||
|
||
bool ReadFromBuffer(byte[] buffer, int offset, int count, out int bytesRead)
|
||
{
|
||
if (this.preReadBuffer != null)
|
||
{
|
||
if (buffer == null)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
|
||
}
|
||
|
||
if (offset >= buffer.Length)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", offset,
|
||
SR.GetString(SR.OffsetExceedsBufferBound, buffer.Length - 1)));
|
||
}
|
||
|
||
if (count < 0)
|
||
{
|
||
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", count,
|
||
SR.GetString(SR.ValueMustBeNonNegative)));
|
||
}
|
||
|
||
if (count == 0)
|
||
{
|
||
bytesRead = 0;
|
||
}
|
||
else
|
||
{
|
||
buffer[offset] = this.preReadBuffer[0];
|
||
this.preReadBuffer = null;
|
||
bytesRead = 1;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bytesRead = -1;
|
||
return false;
|
||
}
|
||
|
||
public override int Read(byte[] buffer, int offset, int count)
|
||
{
|
||
int bytesRead;
|
||
if (ReadFromBuffer(buffer, offset, count, out bytesRead))
|
||
{
|
||
return bytesRead;
|
||
}
|
||
|
||
return base.Read(buffer, offset, count);
|
||
}
|
||
|
||
public override int ReadByte()
|
||
{
|
||
if (this.preReadBuffer != null)
|
||
{
|
||
byte[] tempBuffer = new byte[1];
|
||
int bytesRead;
|
||
if (ReadFromBuffer(tempBuffer, 0, 1, out bytesRead))
|
||
{
|
||
return tempBuffer[0];
|
||
}
|
||
}
|
||
return base.ReadByte();
|
||
}
|
||
|
||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||
{
|
||
int bytesRead;
|
||
if (ReadFromBuffer(buffer, offset, count, out bytesRead))
|
||
{
|
||
return new CompletedAsyncResult<int>(bytesRead, callback, state);
|
||
}
|
||
|
||
return base.BeginRead(buffer, offset, count, callback, state);
|
||
}
|
||
|
||
public override int EndRead(IAsyncResult result)
|
||
{
|
||
if (result is CompletedAsyncResult<int>)
|
||
{
|
||
return CompletedAsyncResult<int>.End(result);
|
||
}
|
||
else
|
||
{
|
||
return base.EndRead(result);
|
||
}
|
||
}
|
||
}
|
||
|
||
class HttpRequestMessageHttpInput : HttpInput, HttpRequestMessageProperty.IHttpHeaderProvider
|
||
{
|
||
const string SoapAction = "SOAPAction";
|
||
HttpRequestMessage httpRequestMessage;
|
||
ChannelBinding channelBinding;
|
||
|
||
public HttpRequestMessageHttpInput(HttpRequestMessage httpRequestMessage, IHttpTransportFactorySettings settings, bool enableChannelBinding, ChannelBinding channelBinding)
|
||
: base(settings, true, enableChannelBinding)
|
||
{
|
||
this.httpRequestMessage = httpRequestMessage;
|
||
this.channelBinding = channelBinding;
|
||
}
|
||
|
||
public override long ContentLength
|
||
{
|
||
get
|
||
{
|
||
if (this.httpRequestMessage.Content.Headers.ContentLength == null)
|
||
{
|
||
// Chunked transfer mode
|
||
return -1;
|
||
}
|
||
|
||
return this.httpRequestMessage.Content.Headers.ContentLength.Value;
|
||
}
|
||
}
|
||
|
||
protected override ChannelBinding ChannelBinding
|
||
{
|
||
get
|
||
{
|
||
return this.channelBinding;
|
||
}
|
||
}
|
||
|
||
public HttpRequestMessage HttpRequestMessage
|
||
{
|
||
get { return this.httpRequestMessage; }
|
||
}
|
||
|
||
protected override bool HasContent
|
||
{
|
||
get
|
||
{
|
||
// In Chunked transfer mode, the ContentLength header is null
|
||
// Otherwise we just rely on the ContentLength header
|
||
return this.httpRequestMessage.Content.Headers.ContentLength == null || this.httpRequestMessage.Content.Headers.ContentLength.Value > 0;
|
||
}
|
||
}
|
||
|
||
protected override string ContentTypeCore
|
||
{
|
||
get
|
||
{
|
||
if (!this.HasContent)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
return this.httpRequestMessage.Content.Headers.ContentType == null ? null : this.httpRequestMessage.Content.Headers.ContentType.MediaType;
|
||
}
|
||
}
|
||
|
||
public override void ConfigureHttpRequestMessage(HttpRequestMessage message)
|
||
{
|
||
throw FxTrace.Exception.AsError(new InvalidOperationException());
|
||
}
|
||
|
||
protected override Stream GetInputStream()
|
||
{
|
||
if (this.httpRequestMessage.Content == null)
|
||
{
|
||
return Stream.Null;
|
||
}
|
||
|
||
return this.httpRequestMessage.Content.ReadAsStreamAsync().Result;
|
||
}
|
||
|
||
protected override void AddProperties(Message message)
|
||
{
|
||
HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty(this.httpRequestMessage);
|
||
message.Properties.Add(HttpRequestMessageProperty.Name, requestProperty);
|
||
message.Properties.Via = this.httpRequestMessage.RequestUri;
|
||
|
||
foreach (KeyValuePair<string, object> property in this.httpRequestMessage.Properties)
|
||
{
|
||
message.Properties.Add(property.Key, property.Value);
|
||
}
|
||
|
||
this.httpRequestMessage.Properties.Clear();
|
||
}
|
||
|
||
protected override string SoapActionHeader
|
||
{
|
||
get
|
||
{
|
||
IEnumerable<string> values;
|
||
if (this.httpRequestMessage.Headers.TryGetValues(SoapAction, out values))
|
||
{
|
||
foreach (string headerValue in values)
|
||
{
|
||
return headerValue;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public void CopyHeaders(WebHeaderCollection headers)
|
||
{
|
||
// No special-casing for the "WWW-Authenticate" header required here,
|
||
// because this method is only called for the incoming request
|
||
// and the WWW-Authenticate header is a header only applied to responses.
|
||
HttpChannelUtilities.CopyHeaders(this.httpRequestMessage, headers.Add);
|
||
}
|
||
|
||
internal void SetHttpRequestMessage(HttpRequestMessage httpRequestMessage)
|
||
{
|
||
Fx.Assert(httpRequestMessage != null, "httpRequestMessage should not be null.");
|
||
this.httpRequestMessage = httpRequestMessage;
|
||
}
|
||
}
|
||
}
|