1 //----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------------------
4 namespace System.ServiceModel.Activation
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
11 using System.Net.Http;
12 using System.Net.Http.Headers;
13 using System.Net.WebSockets;
15 using System.Security;
16 using System.Security.Authentication.ExtendedProtection;
17 using System.Security.Permissions;
18 using System.ServiceModel;
19 using System.ServiceModel.Channels;
20 using System.ServiceModel.Diagnostics;
21 using System.ServiceModel.Security;
22 using System.Threading;
23 using System.Threading.Tasks;
25 using System.Web.Management;
26 using System.Web.WebSockets;
28 class HostedHttpContext : HttpRequestContext
30 HostedRequestContainer requestContainer;
31 HostedHttpRequestAsyncResult result;
32 TaskCompletionSource<object> webSocketWaitingTask;
33 RemoteEndpointMessageProperty remoteEndpointMessageProperty;
34 TaskCompletionSource<WebSocketContext> webSocketContextTaskSource;
36 int impersonationReleased = 0;
38 public HostedHttpContext(HttpChannelListener listener, HostedHttpRequestAsyncResult result)
39 : base(listener, null, result.EventTraceActivity)
41 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
44 result.AddRefForImpersonation();
47 public override string HttpMethod
51 return result.GetHttpMethod();
55 internal void CompleteWithException(Exception ex)
57 Fx.Assert(ex != null, "ex should not be null.");
58 this.result.CompleteOperation(ex);
61 protected override SecurityMessageProperty OnProcessAuthentication()
63 return Listener.ProcessAuthentication(this.result);
66 protected override HttpStatusCode ValidateAuthentication()
68 return Listener.ValidateAuthentication(this.result);
71 // Accessing the headers of an already replied HttpRequest instance causes an Access Violation in hosted mode.
72 // In one-way scenarios, reply happens before the user gets the message. That's why we are disabling all access
73 // to HostedRequestContainer (CSDMain 34014).
74 void CloseHostedRequestContainer()
76 // RequestContext.RequestMessage property can throw rather than create a message.
77 // This means we never created a message and never cached the IIS properties.
78 // At this point the user may return a reply, so requestContainer is allowed to be null.
79 if (this.requestContainer != null)
81 this.requestContainer.Close();
82 this.requestContainer = null;
86 protected override Task<WebSocketContext> AcceptWebSocketCore(HttpResponseMessage response, string protocol)
88 this.BeforeAcceptWebSocket(response);
89 this.webSocketContextTaskSource = new TaskCompletionSource<WebSocketContext>();
90 this.result.Application.Context.AcceptWebSocketRequest(PostAcceptWebSocket, new AspNetWebSocketOptions() { SubProtocol = protocol });
91 this.result.OnReplySent();
92 return this.webSocketContextTaskSource.Task;
95 protected override void OnAcceptWebSocketSuccess(WebSocketContext context, HttpRequestMessage requestMessage)
97 // ASP.NET owns the WebSocket object and needs it during the cleanup process. We should not dispose the WebSocket in WCF layer.
98 base.OnAcceptWebSocketSuccess(context, this.remoteEndpointMessageProperty, null, false, requestMessage);
101 void BeforeAcceptWebSocket(HttpResponseMessage response)
103 this.SetRequestContainer(new HostedRequestContainer(this.result));
104 string address = string.Empty;
107 if (this.requestContainer.TryGetAddressAndPort(out address, out port))
109 this.remoteEndpointMessageProperty = new RemoteEndpointMessageProperty(address, port);
112 this.CloseHostedRequestContainer();
114 HostedHttpContext.AppendHeaderFromHttpResponseMessageToResponse(response, this.result);
117 Task PostAcceptWebSocket(AspNetWebSocketContext context)
119 this.webSocketWaitingTask = new TaskCompletionSource<object>();
120 this.WebSocketChannel.Closed += new EventHandler(this.FinishWebSocketWaitingTask);
121 this.webSocketContextTaskSource.SetResult(context);
122 return webSocketWaitingTask.Task;
125 static void AppendHeaderFromHttpResponseMessageToResponse(HttpResponseMessage response, HostedHttpRequestAsyncResult result)
127 HostedHttpContext.AppendHeaderToResponse(response.Headers, result);
128 if (response.Content != null)
130 HostedHttpContext.AppendHeaderToResponse(response.Content.Headers, result);
134 static void AppendHeaderToResponse(HttpHeaders headers, HostedHttpRequestAsyncResult result)
136 foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
138 foreach (string value in header.Value)
140 result.AppendHeader(header.Key, value);
145 void FinishWebSocketWaitingTask(object sender, EventArgs args)
147 this.webSocketWaitingTask.TrySetResult(null);
150 public override bool IsWebSocketRequest
154 return this.result.IsWebSocketRequest;
158 protected override void OnReply(Message message, TimeSpan timeout)
160 this.CloseHostedRequestContainer();
161 base.OnReply(message, timeout);
164 protected override IAsyncResult OnBeginReply(
165 Message message, TimeSpan timeout, AsyncCallback callback, object state)
167 this.CloseHostedRequestContainer();
168 return base.OnBeginReply(message, timeout, callback, state);
171 protected override void OnAbort()
177 protected override void Cleanup()
181 if (Interlocked.Increment(ref this.impersonationReleased) == 1)
183 this.result.ReleaseImpersonation();
187 protected override HttpInput GetHttpInput()
189 return new HostedHttpInput(this);
192 public override HttpOutput GetHttpOutput(Message message)
194 HttpInput httpInput = this.GetHttpInput(false);
196 // work around http.sys keep alive bug with chunked requests, see MB 49676, this is fixed in Vista
197 if ((httpInput != null && httpInput.ContentLength == -1 && !OSEnvironmentHelper.IsVistaOrGreater) || !this.KeepAliveEnabled)
199 result.SetConnectionClose();
202 ICompressedMessageEncoder compressedMessageEncoder = this.Listener.MessageEncoderFactory.Encoder as ICompressedMessageEncoder;
203 if (compressedMessageEncoder != null && compressedMessageEncoder.CompressionEnabled)
205 string acceptEncoding = this.result.GetAcceptEncoding();
206 compressedMessageEncoder.AddCompressedMessageProperties(message, acceptEncoding);
209 return new HostedRequestHttpOutput(result, Listener, message, this);
212 protected override void OnClose(TimeSpan timeout)
214 base.OnClose(timeout);
215 result.OnReplySent();
218 void SetRequestContainer(HostedRequestContainer requestContainer)
220 this.requestContainer = requestContainer;
223 class HostedHttpInput : HttpInput
227 HostedHttpContext hostedHttpContext;
228 byte[] preReadBuffer;
230 public HostedHttpInput(HostedHttpContext hostedHttpContext)
231 : base(hostedHttpContext.Listener, true, hostedHttpContext.Listener.IsChannelBindingSupportEnabled)
233 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
235 this.hostedHttpContext = hostedHttpContext;
237 EnvelopeVersion envelopeVersion = hostedHttpContext.Listener.MessageEncoderFactory.Encoder.MessageVersion.Envelope;
239 // MB#29602, perf optimization
240 if (envelopeVersion == EnvelopeVersion.Soap11)
242 // For soap 1.1, use headers collection to get content-type since we need to pull in the headers
243 // collection for SOAP-Action anyways
244 this.contentType = hostedHttpContext.result.GetContentType();
248 // For soap 1.2, the we pull the action header from the content-type, so don't access the headers
249 // and just use the typed property. For other versions, we shouldn't need the headers up front.
250 this.contentType = hostedHttpContext.result.GetContentTypeFast();
253 this.contentLength = hostedHttpContext.result.GetContentLength();
255 // MB#34947: System.Web signals chunked as 0 as well so the only way we can
256 // differentiate is by reading ahead
257 if (this.contentLength == 0)
259 preReadBuffer = hostedHttpContext.result.GetPrereadBuffer(ref this.contentLength);
263 public override long ContentLength
267 return this.contentLength;
271 protected override string ContentTypeCore
275 return this.contentType;
279 protected override bool HasContent
281 get { return (this.preReadBuffer != null || this.ContentLength > 0); }
284 protected override string SoapActionHeader
288 return hostedHttpContext.result.GetSoapAction();
292 protected override ChannelBinding ChannelBinding
296 return ChannelBindingUtility.DuplicateToken(hostedHttpContext.result.GetChannelBinding());
300 protected override void AddProperties(Message message)
302 HostedRequestContainer requestContainer = new HostedRequestContainer(this.hostedHttpContext.result);
304 HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty(requestContainer);
306 requestProperty.Method = this.hostedHttpContext.HttpMethod;
308 // Uri.Query always includes the '?'
309 if (this.hostedHttpContext.result.RequestUri.Query.Length > 1)
311 requestProperty.QueryString = this.hostedHttpContext.result.RequestUri.Query.Substring(1);
314 message.Properties.Add(HttpRequestMessageProperty.Name, requestProperty);
316 message.Properties.Add(HostingMessageProperty.Name, CreateMessagePropertyFromHostedResult(this.hostedHttpContext.result));
317 message.Properties.Via = this.hostedHttpContext.result.RequestUri;
319 RemoteEndpointMessageProperty remoteEndpointProperty = new RemoteEndpointMessageProperty(requestContainer);
320 message.Properties.Add(RemoteEndpointMessageProperty.Name, remoteEndpointProperty);
322 this.hostedHttpContext.SetRequestContainer(requestContainer);
325 public override void ConfigureHttpRequestMessage(HttpRequestMessage message)
327 message.Method = new HttpMethod(this.hostedHttpContext.result.GetHttpMethod());
328 message.RequestUri = this.hostedHttpContext.result.RequestUri;
329 foreach (string webHeaderKey in this.hostedHttpContext.result.Application.Context.Request.Headers.Keys)
331 message.AddHeader(webHeaderKey, this.hostedHttpContext.result.Application.Context.Request.Headers[webHeaderKey]);
334 HostedRequestContainer requestContainer = new HostedRequestContainer(this.hostedHttpContext.result);
335 RemoteEndpointMessageProperty remoteEndpointProperty = new RemoteEndpointMessageProperty(requestContainer);
336 message.Properties.Add(RemoteEndpointMessageProperty.Name, remoteEndpointProperty);
339 [Fx.Tag.SecurityNote(Critical = "Calls critical .ctor(HostedImpersonationContext)",
340 Safe = "Only accepts the incoming context from HostedHttpRequestAsyncResult which stores the context in a critical field")]
341 [SecuritySafeCritical]
342 static HostingMessageProperty CreateMessagePropertyFromHostedResult(HostedHttpRequestAsyncResult result)
344 return new HostingMessageProperty(result);
347 protected override Stream GetInputStream()
349 if (this.preReadBuffer != null)
351 return new HostedInputStream(this.hostedHttpContext, this.preReadBuffer);
355 return new HostedInputStream(this.hostedHttpContext);
359 class HostedInputStream : HttpDelayedAcceptStream
361 HostedHttpRequestAsyncResult result;
363 public HostedInputStream(HostedHttpContext hostedContext)
364 : base(hostedContext.result.GetInputStream())
366 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
367 this.result = hostedContext.result;
370 public HostedInputStream(HostedHttpContext hostedContext, byte[] preReadBuffer)
371 : base(new PreReadStream(hostedContext.result.GetInputStream(), preReadBuffer))
373 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
374 this.result = hostedContext.result;
377 public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
379 if (!this.result.TryStartStreamedRead())
381 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.RequestContextAborted));
384 bool throwing = true;
388 IAsyncResult result = base.BeginRead(buffer, offset, count, callback, state);
392 catch (HttpException hostedException)
394 throw FxTrace.Exception.AsError(CreateCommunicationException(hostedException));
400 this.result.SetStreamedReadFinished();
405 public override int EndRead(IAsyncResult result)
409 return base.EndRead(result);
411 catch (HttpException hostedException)
413 throw FxTrace.Exception.AsError(CreateCommunicationException(hostedException));
417 this.result.SetStreamedReadFinished();
421 public override int Read(byte[] buffer, int offset, int count)
423 if (!this.result.TryStartStreamedRead())
425 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.RequestContextAborted));
430 return base.Read(buffer, offset, count);
432 catch (HttpException hostedException)
434 throw FxTrace.Exception.AsError(CreateCommunicationException(hostedException));
438 this.result.SetStreamedReadFinished();
442 // Wraps HttpException as inner exception in CommunicationException or ProtocolException (which derives from CommunicationException)
443 static Exception CreateCommunicationException(HttpException hostedException)
445 if (hostedException.WebEventCode == WebEventCodes.RuntimeErrorPostTooLarge)
447 // This HttpException is thrown if greater than httpRuntime/maxRequestLength bytes have been read from the stream.
448 // Note that this code path can only be hit when GetBufferedInputStream() is called in HostedHttpRequestAsyncResult.GetInputStream(), which only
449 // happens when an Http Module which is executed before the WCF Http Handler has accessed the request stream via GetBufferedInputStream().
450 // This is the only case that throws because GetBufferlessInputStream(true) ignores maxRequestLength, and InputStream property throws when invoked, not when stream is read.
451 return HttpInput.CreateHttpProtocolException(SR.Hosting_MaxRequestLengthExceeded, HttpStatusCode.RequestEntityTooLarge, null, hostedException);
455 // This HttpException is thrown if client disconnects and a read operation is invoked on the stream.
456 return new CommunicationException(hostedException.Message, hostedException);
463 class HostedRequestHttpOutput : HttpOutput
465 HostedHttpRequestAsyncResult result;
466 HostedHttpContext context;
470 bool isSettingMimeHeader = false;
471 bool isSettingContentType = false;
473 public HostedRequestHttpOutput(HostedHttpRequestAsyncResult result, IHttpTransportFactorySettings settings,
474 Message message, HostedHttpContext context)
475 : base(settings, message, false, false)
477 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
479 this.result = result;
480 this.context = context;
482 if (TransferModeHelper.IsResponseStreamed(settings.TransferMode))
483 result.SetTransferModeToStreaming();
487 this.statusCode = (int)HttpStatusCode.InternalServerError;
491 this.statusCode = (int)HttpStatusCode.OK;
495 protected override Stream GetOutputStream()
497 return new HostedResponseOutputStream(this.result, this.context);
500 protected override void AddHeader(string name, string value)
502 this.result.AppendHeader(name, value);
505 protected override void AddMimeVersion(string version)
507 if (!isSettingMimeHeader)
509 this.mimeVersion = version;
513 this.result.AppendHeader(HttpChannelUtilities.MIMEVersionHeader, this.mimeVersion);
517 protected override void SetContentType(string contentType)
519 if (!this.isSettingContentType)
521 this.contentType = contentType;
525 this.result.SetContentType(contentType);
529 protected override void SetContentEncoding(string contentEncoding)
531 this.result.AppendHeader(HttpChannelUtilities.ContentEncodingHeader, contentEncoding);
534 protected override void SetContentLength(int contentLength)
536 this.result.AppendHeader("content-length", contentLength.ToString(CultureInfo.InvariantCulture));
539 protected override void SetStatusCode(HttpStatusCode statusCode)
541 this.result.SetStatusCode((int)statusCode);
544 protected override void SetStatusDescription(string statusDescription)
546 this.result.SetStatusDescription(statusDescription);
549 protected override bool PrepareHttpSend(Message message)
551 bool retValue = base.PrepareHttpSend(message);
554 bool httpMethodIsHead = string.Compare(this.context.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase) == 0;
555 if (httpMethodIsHead)
560 if (message.Properties.TryGetValue(HttpResponseMessageProperty.Name, out property))
562 HttpResponseMessageProperty responseProperty = (HttpResponseMessageProperty)property;
564 if (responseProperty.SuppressPreamble)
566 return retValue || responseProperty.SuppressEntityBody;
569 this.SetStatusCode(responseProperty.StatusCode);
570 if (responseProperty.StatusDescription != null)
572 this.SetStatusDescription(responseProperty.StatusDescription);
575 WebHeaderCollection responseHeaders = responseProperty.Headers;
576 for (int i = 0; i < responseHeaders.Count; i++)
578 string name = responseHeaders.Keys[i];
579 string value = responseHeaders[i];
580 if (string.Compare(name, "content-type", StringComparison.OrdinalIgnoreCase) == 0)
582 this.contentType = value;
584 else if (string.Compare(name, HttpChannelUtilities.MIMEVersionHeader, StringComparison.OrdinalIgnoreCase) == 0)
586 this.mimeVersion = value;
588 else if (string.Compare(name, "content-length", StringComparison.OrdinalIgnoreCase) == 0)
590 int contentLength = -1;
591 if (httpMethodIsHead &&
592 int.TryParse(value, out contentLength))
594 this.SetContentLength(contentLength);
599 this.AddHeader(name, value);
602 if (responseProperty.SuppressEntityBody)
611 this.SetStatusCode((HttpStatusCode)statusCode);
614 if (contentType != null && contentType.Length != 0)
616 if (this.CanSendCompressedResponses)
618 string contentEncoding;
619 if (HttpChannelUtilities.GetHttpResponseTypeAndEncodingForCompression(ref contentType, out contentEncoding))
621 result.SetContentEncoding(contentEncoding);
625 this.isSettingContentType = true;
626 this.SetContentType(contentType);
629 if (this.mimeVersion != null)
631 this.isSettingMimeHeader = true;
632 this.AddMimeVersion(this.mimeVersion);
638 protected override void PrepareHttpSendCore(HttpResponseMessage message)
640 result.SetStatusCode((int)message.StatusCode);
641 if (message.ReasonPhrase != null)
643 result.SetStatusDescription(message.ReasonPhrase);
645 HostedHttpContext.AppendHeaderFromHttpResponseMessageToResponse(message, this.result);
648 class HostedResponseOutputStream : BytesReadPositionStream
650 HostedHttpContext context;
651 HostedHttpRequestAsyncResult result;
653 public HostedResponseOutputStream(HostedHttpRequestAsyncResult result, HostedHttpContext context)
654 : base(result.GetOutputStream())
656 this.context = context;
657 this.result = result;
660 public override void Close()
673 result.OnReplySent();
677 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
681 return base.BeginWrite(buffer, offset, count, callback, state);
690 public override void EndWrite(IAsyncResult result)
694 base.EndWrite(result);
703 public override void Write(byte[] buffer, int offset, int count)
707 base.Write(buffer, offset, count);
716 void CheckWrapThrow(Exception e)
720 if (e is HttpException)
722 if (this.context.Aborted)
724 throw FxTrace.Exception.AsError(
725 new CommunicationObjectAbortedException(SR.RequestContextAborted, e));
729 throw FxTrace.Exception.AsError(new CommunicationException(e.Message, e));
732 else if (this.context.Aborted)
734 // See VsWhidbey (594450)
735 if (DiagnosticUtility.ShouldTraceError)
737 TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.RequestContextAbort, SR.TraceCodeRequestContextAbort, this, e);
740 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.RequestContextAborted));
747 class HostedRequestContainer : RemoteEndpointMessageProperty.IRemoteEndpointProvider, HttpRequestMessageProperty.IHttpHeaderProvider
749 volatile bool isClosed;
750 HostedHttpRequestAsyncResult result;
753 public HostedRequestContainer(HostedHttpRequestAsyncResult result)
755 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
757 this.result = result;
758 this.thisLock = new object();
765 return this.thisLock;
769 // IIS properties are not valid once the reply occurs.
770 // Close invalidates all access to these properties.
775 this.isClosed = true;
780 [Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects",
781 Safe = "Does not leak control or mutable/harmful data, no potential for harm")]
782 [SecuritySafeCritical]
783 void HttpRequestMessageProperty.IHttpHeaderProvider.CopyHeaders(WebHeaderCollection headers)
791 HttpChannelUtilities.CopyHeadersToNameValueCollection(this.result.Application.Request.Headers, headers);
797 [Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects",
798 Safe = "Does not leak control or mutable/harmful data, no potential for harm")]
799 [SecuritySafeCritical]
800 string RemoteEndpointMessageProperty.IRemoteEndpointProvider.GetAddress()
808 return this.result.Application.Request.UserHostAddress;
816 [Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects",
817 Safe = "Does not leak control or mutable/harmful data, no potential for harm")]
818 [SecuritySafeCritical]
819 int RemoteEndpointMessageProperty.IRemoteEndpointProvider.GetPort()
829 string remotePort = this.result.Application.Request.ServerVariables["REMOTE_PORT"];
830 if (string.IsNullOrEmpty(remotePort) || !int.TryParse(remotePort, out port))
841 [Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects",
842 Safe = "Does not leak control or mutable/harmful data, no potential for harm")]
843 [SecuritySafeCritical]
844 public bool TryGetAddressAndPort(out string address, out int port)
846 address = string.Empty;
855 address = this.result.Application.Request.UserHostAddress;
857 IServiceProvider provider = (IServiceProvider)result.Application.Context;
858 port = GetRemotePort(provider);
866 [Fx.Tag.SecurityNote(Critical = "Asserts UnmanagedCode to get the HttpWorkerRequest.", Safe = "Only returns the remote port, doesn't leak the HttpWorkerRequest.")]
867 [SecuritySafeCritical]
868 [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
869 static int GetRemotePort(IServiceProvider provider)
871 return ((HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest))).GetRemotePort();