e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1027 lines
44 KiB
C#
1027 lines
44 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
namespace System.ServiceModel.Activation
|
|
{
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Runtime;
|
|
using System.Runtime.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Security.Authentication.ExtendedProtection;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Security.Permissions;
|
|
using System.Security.Principal;
|
|
using System.ServiceModel;
|
|
using System.ServiceModel.Channels;
|
|
using System.ServiceModel.Activation.Configuration;
|
|
using System.ServiceModel.Activation.Diagnostics;
|
|
using System.Threading;
|
|
using System.Web;
|
|
using System.Web.Management;
|
|
using System.Web.Routing;
|
|
using TD2 = System.ServiceModel.Diagnostics.Application.TD;
|
|
|
|
class HostedHttpRequestAsyncResult : AsyncResult, HttpChannelListener.IHttpAuthenticationContext
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Stores the securitycritical callback values, we need to protect these values")]
|
|
[SecurityCritical]
|
|
static WindowsIdentity anonymousIdentity;
|
|
[SecurityCritical]
|
|
static Action<object> waitOnBeginRequest;
|
|
[SecurityCritical]
|
|
static Action<object> waitOnBeginRequestWithFlow;
|
|
[SecurityCritical]
|
|
static ContextCallback contextOnBeginRequest;
|
|
[SecurityCritical]
|
|
static AsyncCallback processRequestCompleteCallback;
|
|
|
|
|
|
[ThreadStatic]
|
|
static AutoResetEvent waitObject;
|
|
|
|
static Nullable<bool> iisSupportsExtendedProtection;
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Keeps track of impersonated user, caller must use with care")]
|
|
[SecurityCritical]
|
|
HostedImpersonationContext impersonationContext;
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Keeps track of thread static data (HttpContext, CurrentCulture, CurrentUICulture) that is used for AspNetCompatibility mode, caller must use with care")]
|
|
[SecurityCritical]
|
|
HostedThreadData hostedThreadData;
|
|
|
|
[Fx.Tag.SecurityNote(Critical =
|
|
"This field is used to manipulate request/responses using APIs protected by LinkDemand." +
|
|
"It is critical because we use it to determine whether we believe we're being hosted in ASP.NET or not." +
|
|
"The field is set in the constructor of this class and we deem it safe because:" +
|
|
" 1) all paths that lead to the .ctor are SecurityCritical and" +
|
|
" 2) those paths have called ServiceHostingEnvironment.EnsureInitialized (which is also critical)" +
|
|
"So if the field is non-null, it's safe to say that we're hosted in ASP.NET, hence all the helper methods in this class that touch this field can be SecurityTreatAsSafe")]
|
|
[SecurityCritical]
|
|
HttpApplication context;
|
|
|
|
int state;
|
|
|
|
int streamedReadState;
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Determines whether to set the HttpContext on the outgoing thread.")]
|
|
[SecurityCritical]
|
|
bool flowContext;
|
|
bool ensureWFService;
|
|
string configurationBasedServiceVirtualPath;
|
|
EventTraceActivity eventTraceActivity;
|
|
|
|
readonly bool isWebSocketRequest;
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Captures HostedImpersonationContext which must be done in the right place, and calls unsafe" +
|
|
"ScheduleCallbackLowPriNoFlow and ScriptTimeout. Called outside of user security context.")]
|
|
[SecurityCritical]
|
|
public HostedHttpRequestAsyncResult(HttpApplication context, bool flowContext, bool ensureWFService, AsyncCallback callback, object state) :
|
|
this(context, null, flowContext, ensureWFService, callback, state)
|
|
{
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Captures HostedImpersonationContext which must be done in the right place, and calls unsafe" +
|
|
"ScheduleCallbackLowPriNoFlow and ScriptTimeout. Called outside of user security context.")]
|
|
[SecurityCritical]
|
|
public HostedHttpRequestAsyncResult(HttpApplication context, string aspNetRouteServiceVirtualPath, bool flowContext, bool ensureWFService, AsyncCallback callback, object state) :
|
|
base(callback, state)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("context");
|
|
}
|
|
|
|
AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
|
|
|
|
HostedAspNetEnvironment.TrySetWebSocketVersion(context);
|
|
this.context = context;
|
|
// WebSockets require the integrated pipeline mode and the WebSocket IIS module to be loaded. If these conditions
|
|
// are not met, the HttpContext.IsWebSocketRequest property throws. Also, if these conditions are not met,
|
|
// we do not let WebSocket listeners to be started (we fail the service activation), so setting the 'isWebSocketRequest' flag
|
|
// to false in this case will not create confusion (or make troubleshooting difficult).
|
|
this.isWebSocketRequest = HttpRuntime.UsingIntegratedPipeline && AspNetEnvironment.Current.IsWebSocketModuleLoaded && this.context.Context.IsWebSocketRequest;
|
|
this.flowContext = flowContext;
|
|
if (ensureWFService)
|
|
{
|
|
// check for CBA scenario. if true, service should be handled by WCF instead of WF,
|
|
// set this.ensureWFservice to false
|
|
if (ServiceHostingEnvironment.IsConfigurationBasedService(context, out this.configurationBasedServiceVirtualPath))
|
|
{
|
|
this.ensureWFService = false;
|
|
}
|
|
else
|
|
{
|
|
this.ensureWFService = true;
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(aspNetRouteServiceVirtualPath))
|
|
{
|
|
// aspnet routing can hijack CBA request as we append {*pathInfo} to urlpattern and there is no real file for CBA
|
|
// check for CBA scenario. if the request is hijacked. i.e.,
|
|
// 1) route maps to a virtual directory:
|
|
// aspNetRouteServiceVirtualPath <> context.Request.AppRelativeCurrentExecutionFilePath == configurationBasedServiceVirtualPath
|
|
// if RouteExistingFiles <> true, set aspnetRouteServiceVirtualPath to null so that the request will be treated as CBA
|
|
// if RouteExistingFiles == true, this hijack is by-design, do nothing
|
|
// 2) route maps to a CBA entry:
|
|
// aspNetRouteServiceVirtualPath == context.Request.AppRelativeCurrentExecutionFilePath == configurationBasedServiceVirtualPath
|
|
// we will use RouteExistingFiles to decide which service should be activated. We do it in ServiceHostingEnviroment.HostingManager,
|
|
// as we cannot pass this info to the latter.
|
|
if (!RouteTable.Routes.RouteExistingFiles &&
|
|
ServiceHostingEnvironment.IsConfigurationBasedService(context, out this.configurationBasedServiceVirtualPath))
|
|
{
|
|
this.AspNetRouteServiceVirtualPath = null;
|
|
}
|
|
else
|
|
{
|
|
this.AspNetRouteServiceVirtualPath = aspNetRouteServiceVirtualPath;
|
|
}
|
|
}
|
|
|
|
// If this is a DEBUG request, complete right away and let ASP.NET handle it.
|
|
string method = context.Request.HttpMethod ?? "";
|
|
char firstMethodChar = method.Length == 5 ? method[0] : '\0';
|
|
if ((firstMethodChar == 'd' || firstMethodChar == 'D') &&
|
|
string.Compare(method, "DEBUG", StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
if (DiagnosticUtility.ShouldTraceVerbose)
|
|
{
|
|
TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.WebHostDebugRequest, SR.TraceCodeWebHostDebugRequest, this);
|
|
}
|
|
|
|
this.state = State.Completed;
|
|
Complete(true, null);
|
|
return;
|
|
}
|
|
|
|
this.impersonationContext = new HostedImpersonationContext();
|
|
|
|
if (flowContext)
|
|
{
|
|
if (ServiceHostingEnvironment.AspNetCompatibilityEnabled)
|
|
{
|
|
// Capture HttpContext/culture context if necessary. Can be used later by HostedHttpInput to re-apply
|
|
// the culture during dispatch. Also flowed here.
|
|
hostedThreadData = new HostedThreadData();
|
|
}
|
|
}
|
|
|
|
// Set this up before calling IncrementRequestCount so if it fails, we don't leak a count.
|
|
Action<object> iotsCallback = (AspNetPartialTrustHelpers.NeedPartialTrustInvoke || flowContext) ?
|
|
WaitOnBeginRequestWithFlow : WaitOnBeginRequest;
|
|
|
|
// Tell ASPNET to by-pass all the other events so no other http modules will
|
|
// be invoked, Indigo basically takes over the request completely. This should
|
|
// only be called in non-AspNetCompatibilityEnabled mode.
|
|
if (!ServiceHostingEnvironment.AspNetCompatibilityEnabled && !this.ensureWFService)
|
|
{
|
|
context.CompleteRequest();
|
|
}
|
|
|
|
// Prevent ASP.NET from generating thread aborts in relation to this request.
|
|
context.Server.ScriptTimeout = int.MaxValue;
|
|
|
|
ServiceHostingEnvironment.IncrementRequestCount(ref this.eventTraceActivity, context.Request.AppRelativeCurrentExecutionFilePath);
|
|
|
|
IOThreadScheduler.ScheduleCallbackLowPriNoFlow(iotsCallback, this);
|
|
}
|
|
|
|
public static WindowsIdentity AnonymousIdentity
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Access the value of corresponding static field and prevent someone from changing its value")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
if (anonymousIdentity == null)
|
|
{
|
|
anonymousIdentity = WindowsIdentity.GetAnonymous();
|
|
}
|
|
return anonymousIdentity;
|
|
}
|
|
}
|
|
|
|
public static Action<object> WaitOnBeginRequest
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Access the value of corresponding static field and prevent someone from changing its value")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
if (waitOnBeginRequest == null)
|
|
{
|
|
waitOnBeginRequest = new Action<object>(OnBeginRequest);
|
|
}
|
|
return waitOnBeginRequest;
|
|
}
|
|
}
|
|
|
|
public static Action<object> WaitOnBeginRequestWithFlow
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Access the value of corresponding static field and prevent someone from changing its value")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
if (waitOnBeginRequestWithFlow == null)
|
|
{
|
|
waitOnBeginRequestWithFlow = new Action<object>(OnBeginRequestWithFlow);
|
|
}
|
|
return waitOnBeginRequestWithFlow;
|
|
}
|
|
}
|
|
|
|
public static ContextCallback ContextOnBeginRequest
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Access the value of corresponding static field and prevent someone from changing its value")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
if (contextOnBeginRequest == null)
|
|
{
|
|
contextOnBeginRequest = new ContextCallback(OnBeginRequest);
|
|
}
|
|
return contextOnBeginRequest;
|
|
}
|
|
}
|
|
|
|
public static AsyncCallback ProcessRequestCompleteCallback
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Access the value of corresponding static field and prevent someone from changing its value")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
if (processRequestCompleteCallback == null)
|
|
{
|
|
processRequestCompleteCallback = Fx.ThunkCallback(new AsyncCallback(ProcessRequestComplete));
|
|
}
|
|
return processRequestCompleteCallback;
|
|
}
|
|
}
|
|
|
|
public bool IISSupportsExtendedProtection
|
|
{
|
|
get
|
|
{
|
|
if (HostedHttpRequestAsyncResult.iisSupportsExtendedProtection == null)
|
|
{
|
|
HostedHttpRequestAsyncResult.iisSupportsExtendedProtection = this.IISSupportsExtendedProtectionInternal();
|
|
}
|
|
return HostedHttpRequestAsyncResult.iisSupportsExtendedProtection.Value;
|
|
}
|
|
}
|
|
|
|
public bool IsWebSocketRequest
|
|
{
|
|
get { return this.isWebSocketRequest; }
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Touches critical field context.", Safe = "Does not leak control or data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
[PermissionSetAttribute(SecurityAction.Assert, Unrestricted = true)]
|
|
private bool IISSupportsExtendedProtectionInternal()
|
|
{
|
|
DiagnosticUtility.DebugAssert(ExtendedProtectionPolicy.OSSupportsExtendedProtection, "OS must support ExtendedProtection");
|
|
|
|
try
|
|
{
|
|
ChannelBinding cbt = this.context.Request.HttpChannelBinding;
|
|
return true;
|
|
}
|
|
catch (PlatformNotSupportedException)
|
|
{
|
|
// contract with Asp.Net is that they will always throw a PlatformNotSupportedException if IIS is not patched for CBT yet
|
|
return false;
|
|
}
|
|
catch (COMException)
|
|
{
|
|
// If IIS is patched for CBT and an error occurs when trying to retrieve the token a COMException is thrown. Even in this
|
|
// case we know that IIS is patched for CBT.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Captures HostedImpersonationContext which must be done in the right place, and calls unsafe" +
|
|
"ScheduleCallbackLowPriNoFlow and ScriptTimeout. Called outside of user security context." +
|
|
"Callers of this function must call ServiceHostingEnvironment.EnsureInitialized")]
|
|
[SecurityCritical]
|
|
public static void ExecuteSynchronous(HttpApplication context, bool flowContext, bool ensureWFService)
|
|
{
|
|
ExecuteSynchronous(context, null, flowContext, ensureWFService);
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Captures HostedImpersonationContext which must be done in the right place, and calls unsafe" +
|
|
"ScheduleCallbackLowPriNoFlow and ScriptTimeout. Called outside of user security context." +
|
|
"Callers of this function must call ServiceHostingEnvironment.EnsureInitialized")]
|
|
[SecurityCritical]
|
|
public static void ExecuteSynchronous(HttpApplication context, string routeServiceVirtualPath, bool flowContext, bool ensureWFService)
|
|
{
|
|
AutoResetEvent wait = HostedHttpRequestAsyncResult.waitObject;
|
|
if (wait == null)
|
|
{
|
|
wait = new AutoResetEvent(false);
|
|
HostedHttpRequestAsyncResult.waitObject = wait;
|
|
}
|
|
|
|
HostedHttpRequestAsyncResult result;
|
|
try
|
|
{
|
|
result = new HostedHttpRequestAsyncResult(context, routeServiceVirtualPath, flowContext, ensureWFService, ProcessRequestCompleteCallback, wait);
|
|
if (!result.CompletedSynchronously)
|
|
{
|
|
wait.WaitOne();
|
|
}
|
|
wait = null;
|
|
}
|
|
finally
|
|
{
|
|
if (wait != null)
|
|
{
|
|
// Not sure of the state anymore.
|
|
HostedHttpRequestAsyncResult.waitObject = null;
|
|
wait.Close();
|
|
}
|
|
}
|
|
|
|
HostedHttpRequestAsyncResult.End(result);
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Can be called outside of a user context.")]
|
|
static void ProcessRequestComplete(IAsyncResult result)
|
|
{
|
|
if (!result.CompletedSynchronously)
|
|
{
|
|
try
|
|
{
|
|
((AutoResetEvent)result.AsyncState).Set();
|
|
}
|
|
catch (ObjectDisposedException exception)
|
|
{
|
|
DiagnosticUtility.TraceHandledException(exception, TraceEventType.Warning);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Can be called outside of user context, accesses hostedThreadData.",
|
|
Safe = "Uses hostedThreadData to set HttpContext.Current, cultures to the one attached to this async-result instance.")]
|
|
[SecuritySafeCritical]
|
|
static void OnBeginRequestWithFlow(object state)
|
|
{
|
|
HostedHttpRequestAsyncResult self = (HostedHttpRequestAsyncResult)state;
|
|
|
|
IDisposable hostedThreadContext = null;
|
|
try
|
|
{
|
|
if (self.flowContext)
|
|
{
|
|
// In AspCompat case, these are the three things that need to be flowed. See HostedHttpInput.
|
|
if (self.hostedThreadData != null)
|
|
{
|
|
hostedThreadContext = self.hostedThreadData.CreateContext();
|
|
}
|
|
}
|
|
|
|
// In full-trust, this simply calls the delegate.
|
|
AspNetPartialTrustHelpers.PartialTrustInvoke(ContextOnBeginRequest, self);
|
|
}
|
|
finally
|
|
{
|
|
if (hostedThreadContext != null)
|
|
{
|
|
hostedThreadContext.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void OnBeginRequest(object state)
|
|
{
|
|
HostedHttpRequestAsyncResult self = (HostedHttpRequestAsyncResult)state;
|
|
|
|
Exception completionException = null;
|
|
try
|
|
{
|
|
self.BeginRequest();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
completionException = e;
|
|
}
|
|
|
|
if (completionException != null)
|
|
{
|
|
self.CompleteOperation(completionException);
|
|
}
|
|
}
|
|
|
|
void BeginRequest()
|
|
{
|
|
try
|
|
{
|
|
HandleRequest();
|
|
}
|
|
catch (EndpointNotFoundException exception)
|
|
{
|
|
// HTTP-GET is special cased to avoid that the ServiceActivation-HTTP-response is treated as service response.
|
|
// For WebSocket requests we treat the ServiceActivation in the same way like for SOAP (HTTP-POST) requests.
|
|
if (string.Compare(GetHttpMethod(), "GET", StringComparison.OrdinalIgnoreCase) == 0 &&
|
|
!this.isWebSocketRequest)
|
|
{
|
|
// Wrap the exception into HttpException
|
|
throw FxTrace.Exception.AsError(new HttpException((int)HttpStatusCode.NotFound, exception.Message, exception));
|
|
}
|
|
|
|
SetStatusCode((int)HttpStatusCode.NotFound);
|
|
CompleteOperation(null);
|
|
}
|
|
catch (ServiceActivationException exception)
|
|
{
|
|
// HTTP-GET is special cased to avoid that the ServiceActivation-HTTP-response is treated as service response.
|
|
// For WebSocket requests we treat the ServiceActivation in the same way like for SOAP (HTTP-POST) requests.
|
|
if (string.Compare(GetHttpMethod(), "GET", StringComparison.OrdinalIgnoreCase) == 0 &&
|
|
!this.isWebSocketRequest)
|
|
{
|
|
if (exception.InnerException is HttpException)
|
|
{
|
|
throw exception.InnerException;
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
SetStatusCode((int)HttpStatusCode.InternalServerError);
|
|
SetStatusDescription(
|
|
HttpChannelUtilities.StatusDescriptionStrings.HttpStatusServiceActivationException);
|
|
CompleteOperation(null);
|
|
}
|
|
finally
|
|
{
|
|
ReleaseImpersonation();
|
|
}
|
|
}
|
|
|
|
public WindowsIdentity LogonUserIdentity
|
|
{
|
|
get
|
|
{
|
|
IPrincipal user = this.Application.User;
|
|
if (user == null)
|
|
{
|
|
return AnonymousIdentity;
|
|
}
|
|
|
|
WindowsIdentity identity = user.Identity as WindowsIdentity;
|
|
if (identity == null)
|
|
{
|
|
return AnonymousIdentity;
|
|
}
|
|
|
|
return identity;
|
|
}
|
|
}
|
|
|
|
WindowsIdentity HttpChannelListener.IHttpAuthenticationContext.LogonUserIdentity
|
|
{
|
|
get
|
|
{
|
|
return this.LogonUserIdentity;
|
|
}
|
|
}
|
|
|
|
public HostedImpersonationContext ImpersonationContext
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Keeps track of impersonated user, caller must use with care.",
|
|
Safe = "Safe for Get, individual members of HostedImpersonationContext are protected.")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
return this.impersonationContext;
|
|
}
|
|
}
|
|
|
|
public HostedThreadData HostedThreadData
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Keeps track of impersonated user, caller must use with care.",
|
|
Safe = "Safe for Get, individual members of HostedThreadData are protected.")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
return this.hostedThreadData;
|
|
}
|
|
}
|
|
|
|
public EventTraceActivity EventTraceActivity
|
|
{
|
|
get
|
|
{
|
|
return this.eventTraceActivity;
|
|
}
|
|
}
|
|
|
|
public Uri OriginalRequestUri
|
|
{
|
|
get;
|
|
private set;
|
|
|
|
}
|
|
|
|
public Uri RequestUri
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public HttpApplication Application
|
|
{
|
|
[Fx.Tag.SecurityNote(Critical = "Touches critical field context.", Safe = "Does not leak control or data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
return this.context;
|
|
}
|
|
}
|
|
|
|
public string AspNetRouteServiceVirtualPath
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.", Safe = "Does not leak control or data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
public Stream GetInputStream()
|
|
{
|
|
try
|
|
{
|
|
// CSDMain #133228: "Consume GetBufferlessInputStream"
|
|
// The ReadEntityBodyMode property on the HttpRequest keeps track of whether the request stream has already been accessed, and if so, what API was used to access the request.
|
|
// - "None" means that the request stream hasn't been accessed.
|
|
// - "Bufferless" means that GetBufferlessInputStream() was used to access it.
|
|
// - "Buffered" means GetBufferedInputStream() was used to access it.
|
|
// - "Classic" means that either the InputStream, Form, Files, or BinaryRead APIs were invoked already.
|
|
// In general, these values are incompatible with one another, meaning that once the request was accessed in a "Classic" way, only "Classic" APIs can be invoked on the HttpRequest.
|
|
// If incompatible APIs are invoked, an HttpException is thrown.
|
|
// In order to prevent HttpExceptions from being thrown for this reason, we will check the ReadEntityBodyMode, and access the request stream with the corresponding API
|
|
// If the request stream hasn't been accessed yet (eg, by an HttpModule which executed earlier), then we will use GetBufferlessInputStream by default.
|
|
ReadEntityBodyMode mode = this.context.Request.ReadEntityBodyMode;
|
|
Fx.Assert(mode == ReadEntityBodyMode.None || mode == ReadEntityBodyMode.Bufferless || mode == ReadEntityBodyMode.Buffered || mode == ReadEntityBodyMode.Classic,
|
|
"Unknown value for System.Web.ReadEntityBodyMode enum");
|
|
|
|
if (mode == ReadEntityBodyMode.None && ServiceHostingEnvironment.AspNetCompatibilityEnabled && AppSettings.UseClassicReadEntityMode)
|
|
{
|
|
mode = ReadEntityBodyMode.Classic;
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case ReadEntityBodyMode.None:
|
|
case ReadEntityBodyMode.Bufferless:
|
|
return this.context.Request.GetBufferlessInputStream(true); // ignores system.web/httpRuntime/maxRequestLength
|
|
case ReadEntityBodyMode.Buffered:
|
|
return this.context.Request.GetBufferedInputStream();
|
|
default:
|
|
// ReadEntityBodyMode.Classic:
|
|
return this.context.Request.InputStream;
|
|
}
|
|
}
|
|
catch (HttpException hostedException)
|
|
{
|
|
if (hostedException.WebEventCode == WebEventCodes.RuntimeErrorPostTooLarge)
|
|
{
|
|
throw FxTrace.Exception.AsError(HttpInput.CreateHttpProtocolException(SR.Hosting_MaxRequestLengthExceeded, HttpStatusCode.RequestEntityTooLarge, null, hostedException));
|
|
}
|
|
else
|
|
{
|
|
throw FxTrace.Exception.AsError(new CommunicationException(hostedException.Message, hostedException));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnReplySent()
|
|
{
|
|
CompleteOperation(null);
|
|
}
|
|
|
|
internal void CompleteOperation(Exception exception)
|
|
{
|
|
if (this.state == State.Running &&
|
|
Interlocked.CompareExchange(ref this.state, State.Completed, State.Running) == State.Running)
|
|
{
|
|
this.CompleteAsynchronously(exception);
|
|
}
|
|
}
|
|
|
|
public void Abort()
|
|
{
|
|
if (this.state == State.Running &&
|
|
Interlocked.CompareExchange(ref this.state, State.Aborted, State.Running) == State.Running)
|
|
{
|
|
int currentStreamedReadState = Interlocked.Exchange(ref this.streamedReadState, StreamedReadState.AbortStarted);
|
|
// Closes the socket connection to the client
|
|
if (HttpRuntime.UsingIntegratedPipeline)
|
|
{
|
|
Application.Request.Abort();
|
|
}
|
|
else
|
|
{
|
|
Application.Response.Close();
|
|
}
|
|
|
|
if (currentStreamedReadState == StreamedReadState.None)
|
|
{
|
|
this.CompleteAsynchronously(null);
|
|
}
|
|
else
|
|
{
|
|
Fx.Assert(currentStreamedReadState == StreamedReadState.ReceiveStarted, string.Format(CultureInfo.InvariantCulture, "currentStramedReadState is not ReceivedStarted: {0}", currentStreamedReadState));
|
|
if (Interlocked.CompareExchange(ref this.streamedReadState, StreamedReadState.Aborted, StreamedReadState.AbortStarted) == StreamedReadState.AbortStarted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Fx.Assert(this.streamedReadState == StreamedReadState.ReceiveFinishedAfterAbortStarted, string.Format(CultureInfo.InvariantCulture, "currentStramedReadState is not ReceiveFinished: {0}", this.streamedReadState));
|
|
this.CompleteAsynchronously(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CompleteAsynchronously(Exception ex)
|
|
{
|
|
Complete(false, ex);
|
|
ServiceHostingEnvironment.DecrementRequestCount(this.eventTraceActivity);
|
|
}
|
|
|
|
internal bool TryStartStreamedRead()
|
|
{
|
|
return Interlocked.CompareExchange(ref this.streamedReadState, StreamedReadState.ReceiveStarted, StreamedReadState.None) == StreamedReadState.None;
|
|
}
|
|
|
|
internal void SetStreamedReadFinished()
|
|
{
|
|
if (Interlocked.CompareExchange(ref this.streamedReadState, StreamedReadState.None, StreamedReadState.ReceiveStarted) == StreamedReadState.ReceiveStarted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Interlocked.CompareExchange(ref this.streamedReadState, StreamedReadState.ReceiveFinishedAfterAbortStarted, StreamedReadState.AbortStarted) == StreamedReadState.AbortStarted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Fx.Assert(this.streamedReadState == StreamedReadState.Aborted, string.Format(CultureInfo.InvariantCulture, "currentStramedReadState is not Aborted: {0}", this.streamedReadState));
|
|
|
|
this.CompleteAsynchronously(null);
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Can be called outside of a user context.")]
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
try
|
|
{
|
|
AsyncResult.End<HostedHttpRequestAsyncResult>(result);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if (!Fx.IsFatal(exception))
|
|
{
|
|
// Log the exception.
|
|
DiagnosticUtility.EventLog.LogEvent(TraceEventType.Error, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost,
|
|
(uint)System.Runtime.Diagnostics.EventLogEventId.WebHostFailedToProcessRequest,
|
|
TraceUtility.CreateSourceString(result),
|
|
exception == null ? string.Empty : exception.ToString());
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
X509Certificate2 HttpChannelListener.IHttpAuthenticationContext.GetClientCertificate(out bool isValidCertificate)
|
|
{
|
|
HttpClientCertificate certificateInfo = this.Application.Request.ClientCertificate;
|
|
isValidCertificate = certificateInfo.IsValid;
|
|
if (certificateInfo.IsPresent)
|
|
{
|
|
return new X509Certificate2(certificateInfo.Certificate);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
TraceRecord HttpChannelListener.IHttpAuthenticationContext.CreateTraceRecord()
|
|
{
|
|
return new System.ServiceModel.Diagnostics.HttpRequestTraceRecord(this.Application.Request);
|
|
}
|
|
|
|
void HandleRequest()
|
|
{
|
|
this.OriginalRequestUri = GetUrl();
|
|
string relativeVirtualPath;
|
|
if (!string.IsNullOrEmpty(this.AspNetRouteServiceVirtualPath))
|
|
{
|
|
relativeVirtualPath = this.AspNetRouteServiceVirtualPath;
|
|
}
|
|
else if (!string.IsNullOrEmpty(this.configurationBasedServiceVirtualPath))
|
|
{
|
|
relativeVirtualPath = this.configurationBasedServiceVirtualPath;
|
|
|
|
}
|
|
else
|
|
{
|
|
relativeVirtualPath = GetAppRelativeCurrentExecutionFilePath();
|
|
}
|
|
|
|
if (ensureWFService)
|
|
{
|
|
bool bypass = false;
|
|
try
|
|
{
|
|
if (!ServiceHostingEnvironment.EnsureWorkflowService(relativeVirtualPath))
|
|
{
|
|
CompleteOperation(null);
|
|
bypass = true;
|
|
return;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (!bypass)
|
|
{
|
|
CompleteRequest();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Support for Cassini.
|
|
if (ServiceHostingEnvironment.IsSimpleApplicationHost)
|
|
{
|
|
HostedTransportConfigurationManager.EnsureInitializedForSimpleApplicationHost(this);
|
|
}
|
|
|
|
HttpHostedTransportConfiguration transportConfiguration = HostedTransportConfigurationManager.GetConfiguration(this.OriginalRequestUri.Scheme)
|
|
as HttpHostedTransportConfiguration;
|
|
HostedHttpTransportManager transportManager = null;
|
|
|
|
// There must be a transport binding that matches the request.
|
|
if (transportConfiguration != null)
|
|
{
|
|
transportManager = transportConfiguration.GetHttpTransportManager(this.OriginalRequestUri);
|
|
}
|
|
|
|
if (transportManager == null)
|
|
{
|
|
InvalidOperationException invalidOpException = new InvalidOperationException(SR.Hosting_TransportBindingNotFound(OriginalRequestUri.ToString()));
|
|
|
|
ServiceActivationException activationException = new ServiceActivationException(invalidOpException.Message, invalidOpException);
|
|
|
|
LogServiceActivationException(activationException);
|
|
|
|
throw FxTrace.Exception.AsError(activationException);
|
|
}
|
|
|
|
this.RequestUri = new Uri(transportManager.ListenUri, this.OriginalRequestUri.PathAndQuery);
|
|
Fx.Assert(
|
|
object.ReferenceEquals(this.RequestUri.Scheme, Uri.UriSchemeHttp) || object.ReferenceEquals(this.RequestUri.Scheme, Uri.UriSchemeHttps),
|
|
"Scheme must be Http or Https.");
|
|
|
|
ServiceHostingEnvironment.EnsureServiceAvailableFast(relativeVirtualPath, this.eventTraceActivity);
|
|
|
|
transportManager.HttpContextReceived(this);
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls into an unsafe UnsafeLogEvent method",
|
|
Safe = "Event identities cannot be spoofed as they are constants determined inside the method")]
|
|
[SecuritySafeCritical]
|
|
void LogServiceActivationException(ServiceActivationException activationException)
|
|
{
|
|
if (TD2.ServiceExceptionIsEnabled())
|
|
{
|
|
TD2.ServiceException(this.eventTraceActivity, activationException.ToString(), typeof(ServiceActivationException).FullName);
|
|
}
|
|
|
|
if (TD.ServiceActivationExceptionIsEnabled())
|
|
{
|
|
TD.ServiceActivationException(activationException != null ? activationException.ToString() : string.Empty, activationException);
|
|
}
|
|
DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost,
|
|
(uint)System.Runtime.Diagnostics.EventLogEventId.WebHostFailedToProcessRequest, true,
|
|
TraceUtility.CreateSourceString(this), activationException.ToString());
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "manipulates impersonation object",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm except memory leak.")]
|
|
[SecuritySafeCritical]
|
|
internal void AddRefForImpersonation()
|
|
{
|
|
if (this.impersonationContext != null)
|
|
{
|
|
this.impersonationContext.AddRef();
|
|
}
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "manipulates impersonation object",
|
|
Safe = "Releasing the SafeHandle early could only cause a future impersonation attempt to fail. We have to handle impersonation failures well already.")]
|
|
[SecuritySafeCritical]
|
|
internal void ReleaseImpersonation()
|
|
{
|
|
if (this.impersonationContext != null)
|
|
{
|
|
this.impersonationContext.Release();
|
|
}
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void SetContentType(string contentType)
|
|
{
|
|
this.context.Response.ContentType = contentType;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void SetContentEncoding(string contentEncoding)
|
|
{
|
|
this.context.Response.AddHeader(HttpChannelUtilities.ContentEncodingHeader, contentEncoding);
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, completes the request.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void CompleteRequest()
|
|
{
|
|
this.context.CompleteRequest();
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void SetTransferModeToStreaming()
|
|
{
|
|
this.context.Response.BufferOutput = false;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void AppendHeader(string name, string value)
|
|
{
|
|
this.context.Response.AppendHeader(name, value);
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void SetStatusCode(int statusCode)
|
|
{
|
|
this.context.Response.TrySkipIisCustomErrors = true;
|
|
this.context.Response.StatusCode = statusCode;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void SetStatusDescription(string statusDescription)
|
|
{
|
|
this.context.Response.StatusDescription = statusDescription;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects, changes properties of the HTTP response.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal void SetConnectionClose()
|
|
{
|
|
this.context.Response.AppendHeader("Connection", "close");
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal byte[] GetPrereadBuffer(ref int contentLength)
|
|
{
|
|
byte[] preReadBuffer = new byte[1];
|
|
if (this.GetInputStream().Read(preReadBuffer, 0, 1) > 0)
|
|
{
|
|
contentLength = -1;
|
|
return preReadBuffer;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal Stream GetOutputStream()
|
|
{
|
|
return this.context.Response.OutputStream;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal string GetHttpMethod()
|
|
{
|
|
return this.context.Request.HttpMethod;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal string GetContentType()
|
|
{
|
|
const string ContentTypeHeaderName = "Content-Type";
|
|
return this.context.Request.Headers[ContentTypeHeaderName];
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal string GetAcceptEncoding()
|
|
{
|
|
return this.context.Request.Headers[HttpChannelUtilities.AcceptEncodingHeader];
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal string GetContentTypeFast()
|
|
{
|
|
return this.context.Request.ContentType;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal int GetContentLength()
|
|
{
|
|
return this.context.Request.ContentLength;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal string GetSoapAction()
|
|
{
|
|
const string SoapActionHeaderName = "SOAPAction";
|
|
return this.context.Request.Headers[SoapActionHeaderName];
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
internal ChannelBinding GetChannelBinding()
|
|
{
|
|
if (!this.IISSupportsExtendedProtection)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return this.context.Request.HttpChannelBinding;
|
|
}
|
|
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
string GetAppRelativeCurrentExecutionFilePath()
|
|
{
|
|
return this.context.Request.AppRelativeCurrentExecutionFilePath;
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Calls getters with LinkDemands in ASP .NET objects.",
|
|
Safe = "Does not leak control or mutable/harmful data, no potential for harm.")]
|
|
[SecuritySafeCritical]
|
|
Uri GetUrl()
|
|
{
|
|
return this.context.Request.Url;
|
|
}
|
|
|
|
static class State
|
|
{
|
|
internal const int Running = 0;
|
|
internal const int Completed = 1;
|
|
internal const int Aborted = 2;
|
|
}
|
|
|
|
static class StreamedReadState
|
|
{
|
|
internal const int None = 0;
|
|
internal const int ReceiveStarted = 1;
|
|
internal const int ReceiveFinishedAfterAbortStarted = 2;
|
|
internal const int AbortStarted = 3;
|
|
internal const int Aborted = 4;
|
|
}
|
|
}
|
|
}
|