Code:
/ FX-1434 / FX-1434 / 1.0 / untmp / whidbey / REDBITS / ndp / fx / src / Net / System / Net / HttpListener.cs / 1 / HttpListener.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- //disabled until BindHandle has an overload that accepts Criticalhandles #pragma warning disable 618 namespace System.Net { using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Threading; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Security; using System.Security.Principal; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32; public class HttpListenerBasicIdentity : GenericIdentity { private string m_Password; public HttpListenerBasicIdentity(string username, string password) : base(username, BasicClient.AuthType) { m_Password = password; } public virtual string Password { get { return m_Password; } } } internal abstract unsafe class RequestContextBase : IDisposable { private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* m_MemoryBlob; private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* m_OriginalBlobAddress; private byte[] m_BackingBuffer; // Must call this from derived class' constructors. protected void BaseConstruction(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* requestBlob) { if (requestBlob == null) { GC.SuppressFinalize(this); } else { m_MemoryBlob = requestBlob; } } // ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called // before an object (HttpListenerReqeust) which closes the RequestContext on demand is returned to the application. internal void ReleasePins() { GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice."); m_OriginalBlobAddress = m_MemoryBlob; UnsetBlob(); OnReleasePins(); } protected abstract void OnReleasePins(); public void Close() { Dispose(); } public void Dispose() { GlobalLog.Assert(m_MemoryBlob == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins()."); Dispose(true); } protected virtual void Dispose(bool disposing) { } ~RequestContextBase() { Dispose(false); } internal UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* RequestBlob { get { GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::Dispose()|RequestBlob requested after ReleasePins()."); return m_MemoryBlob; } } internal byte[] RequestBuffer { get { return m_BackingBuffer; } } internal uint Size { get { return (uint) m_BackingBuffer.Length; } } internal IntPtr OriginalBlobAddress { get { UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* blob = m_MemoryBlob; return (IntPtr) (blob == null ? m_OriginalBlobAddress : blob); } } protected void SetBlob(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* requestBlob) { GlobalLog.Assert(m_MemoryBlob != null || m_BackingBuffer == null, "RequestContextBase::Dispose()|SetBlob() called after ReleasePins()."); if (requestBlob == null) { UnsetBlob(); return; } if (m_MemoryBlob == null) { GC.ReRegisterForFinalize(this); } m_MemoryBlob = requestBlob; } protected void UnsetBlob() { if (m_MemoryBlob != null) { GC.SuppressFinalize(this); } m_MemoryBlob = null; } protected void SetBuffer(int size) { m_BackingBuffer = size == 0 ? null : new byte[size]; } } internal unsafe class AsyncRequestContext : RequestContextBase { private NativeOverlapped* m_NativeOverlapped; private ListenerAsyncResult m_Result; internal AsyncRequestContext(ListenerAsyncResult result) { m_Result = result; BaseConstruction(Allocate(0)); } private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* Allocate(uint size) { uint newSize = size != 0 ? size : RequestBuffer == null ? 4096 : Size; if (m_NativeOverlapped != null && newSize != RequestBuffer.Length) { NativeOverlapped* nativeOverlapped = m_NativeOverlapped; m_NativeOverlapped = null; Overlapped.Free(nativeOverlapped); } if (m_NativeOverlapped == null) { SetBuffer(checked((int) newSize)); Overlapped overlapped = new Overlapped(); overlapped.AsyncResult = m_Result; m_NativeOverlapped = overlapped.Pack(ListenerAsyncResult.IOCallback, RequestBuffer); return (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST*) Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0); } return RequestBlob; } internal void Reset(ulong requestId, uint size) { SetBlob(Allocate(size)); RequestBlob->RequestId = requestId; } protected override void OnReleasePins() { if (m_NativeOverlapped != null) { NativeOverlapped* nativeOverlapped = m_NativeOverlapped; m_NativeOverlapped = null; Overlapped.Free(nativeOverlapped); } } protected override void Dispose(bool disposing) { if (m_NativeOverlapped != null) { GlobalLog.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose()."); if (!NclUtilities.HasShutdownStarted || disposing) { Overlapped.Free(m_NativeOverlapped); } } base.Dispose(disposing); } internal NativeOverlapped* NativeOverlapped { get { return m_NativeOverlapped; } } } internal unsafe class SyncRequestContext : RequestContextBase { private GCHandle m_PinnedHandle; internal SyncRequestContext(int size) { BaseConstruction(Allocate(size)); } private UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST* Allocate(int size) { if (m_PinnedHandle.IsAllocated) { if (RequestBuffer.Length == size) { return RequestBlob; } m_PinnedHandle.Free(); } SetBuffer(size); if (RequestBuffer == null) { return null; } m_PinnedHandle = GCHandle.Alloc(RequestBuffer, GCHandleType.Pinned); return (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST*) Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0); } internal void Reset(int size) { SetBlob(Allocate(size)); } protected override void OnReleasePins() { if (m_PinnedHandle.IsAllocated) { m_PinnedHandle.Free(); } } protected override void Dispose(bool disposing) { if (m_PinnedHandle.IsAllocated) { GlobalLog.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose()."); if (!NclUtilities.HasShutdownStarted || disposing) { m_PinnedHandle.Free(); } } base.Dispose(disposing); } } public sealed unsafe class HttpListener : IDisposable { private static byte[] s_WwwAuthenticateBytes = new byte[] { (byte) 'W', (byte) 'W', (byte) 'W', (byte) '-', (byte) 'A', (byte) 'u', (byte) 't', (byte) 'h', (byte) 'e', (byte) 'n', (byte) 't', (byte) 'i', (byte) 'c', (byte) 'a', (byte) 't', (byte) 'e' }; private class AuthenticationSelectorInfo { private AuthenticationSchemeSelector m_SelectorDelegate; private bool m_CanUseAdvancedAuth; internal AuthenticationSelectorInfo(AuthenticationSchemeSelector selectorDelegate, bool canUseAdvancedAuth) { m_SelectorDelegate = selectorDelegate; m_CanUseAdvancedAuth = canUseAdvancedAuth; } internal AuthenticationSchemeSelector Delegate { get { return m_SelectorDelegate; } } internal bool AdvancedAuth { get { return m_CanUseAdvancedAuth; } } } private AuthenticationSelectorInfo m_AuthenticationDelegate; private AuthenticationSchemes m_AuthenticationScheme = AuthenticationSchemes.Anonymous; private SecurityException m_SecurityException; private string m_Realm; private SafeCloseHandle m_RequestQueueHandle; private bool m_RequestHandleBound; private State m_State; private HttpListenerPrefixCollection m_Prefixes; private bool m_IgnoreWriteExceptions; private bool m_UnsafeConnectionNtlmAuthentication; private Hashtable m_DisconnectResults; // ulong -> DisconnectAsyncResult private object m_InternalLock; internal Hashtable m_UriPrefixes = new Hashtable(); public HttpListener() { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "HttpListener", ""); if (!UnsafeNclNativeMethods.HttpApi.Supported) { throw new PlatformNotSupportedException(); } m_State = State.Stopped; m_InternalLock = new object(); if(Logging.On)Logging.Exit(Logging.HttpListener, this, "HttpListener", ""); } internal SafeCloseHandle RequestQueueHandle { get { return m_RequestQueueHandle; } } public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate { get { AuthenticationSelectorInfo selector = m_AuthenticationDelegate; return selector == null ? null : selector.Delegate; } set { CheckDisposed(); try { new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Demand(); m_AuthenticationDelegate = new AuthenticationSelectorInfo(value, true); } catch (SecurityException exception) { m_SecurityException = exception; m_AuthenticationDelegate = new AuthenticationSelectorInfo(value, false); } } } public AuthenticationSchemes AuthenticationSchemes { get { return m_AuthenticationScheme; } set { CheckDisposed(); // Enabling certain schemes requires special permissions. if ((value & (AuthenticationSchemes.Digest | AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm)) != 0) { new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Demand(); } m_AuthenticationScheme = value; } } public string Realm { get { return m_Realm; } set { CheckDisposed(); m_Realm = value; } } public static bool IsSupported { get { return UnsafeNclNativeMethods.HttpApi.Supported; } } public bool IsListening { get { return m_State==State.Started; } } public bool IgnoreWriteExceptions { get { return m_IgnoreWriteExceptions; } set { CheckDisposed(); m_IgnoreWriteExceptions = value; } } public bool UnsafeConnectionNtlmAuthentication { get { return m_UnsafeConnectionNtlmAuthentication; } set { CheckDisposed(); if (m_UnsafeConnectionNtlmAuthentication==value) { return; } lock (DisconnectResults.SyncRoot) { if (m_UnsafeConnectionNtlmAuthentication == value) { return; } m_UnsafeConnectionNtlmAuthentication = value; if (!value) { foreach (DisconnectAsyncResult result in DisconnectResults.Values) { result.AuthenticatedConnection = null; } } } } } private Hashtable DisconnectResults { get { if (m_DisconnectResults == null) { lock (m_InternalLock) { if (m_DisconnectResults == null) { m_DisconnectResults = Hashtable.Synchronized(new Hashtable()); } } } return m_DisconnectResults; } } internal void AddPrefix(string uriPrefix) { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "AddPrefix", "uriPrefix:" + uriPrefix); string registeredPrefix = null; try { if (uriPrefix==null) { throw new ArgumentNullException("uriPrefix"); } (new WebPermission(NetworkAccess.Accept, uriPrefix)).Demand(); CheckDisposed(); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::AddPrefix() uriPrefix:" + uriPrefix); int i; if (string.Compare(uriPrefix, 0, "http://", 0, 7, StringComparison.OrdinalIgnoreCase)==0) { i = 7; } else if (string.Compare(uriPrefix, 0, "https://", 0, 8, StringComparison.OrdinalIgnoreCase)==0) { i = 8; } else { throw new ArgumentException(SR.GetString(SR.net_listener_scheme), "uriPrefix"); } bool inSquareBrakets = false; int j = i; while (j0) { if (m_State==State.Started) { foreach (string registeredPrefix in m_UriPrefixes.Values) { // ignore possible failures InternalRemovePrefix(registeredPrefix); } } if(clear) m_UriPrefixes.Clear(); } } finally { if(Logging.On)Logging.Exit(Logging.HttpListener, this, "RemoveAll", ""); } } [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)] internal void EnsureBoundHandle() { if (!m_RequestHandleBound) { lock (m_InternalLock) { if (!m_RequestHandleBound) { ThreadPool.BindHandle(m_RequestQueueHandle.DangerousGetHandle()); m_RequestHandleBound = true; } } } } public void Start() { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Start", ""); try { CheckDisposed(); if (m_State==State.Started) { return; } m_RequestQueueHandle = SafeCloseHandle.CreateRequestQueueHandle(); AddAll(); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::.ctor() SafeCloseHandle.CreateRequestQueueHandle() returned m_RequestQueueHandle:" + m_RequestQueueHandle); m_State = State.Started; } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Start", exception); throw; } finally { if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Start", ""); } } public void Stop() { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Stop", ""); try { CheckDisposed(); if (m_State==State.Stopped) { return; } RemoveAll(false); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Stop() calling SafeCloseHandle.Close()"); m_RequestQueueHandle.Close(); m_RequestHandleBound = false; m_State = State.Stopped; ClearDigestCache(); } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Stop", exception); throw; } finally { if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Stop", ""); } } public void Abort() { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Abort", ""); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Abort()"); try { if (m_RequestQueueHandle!=null) { m_RequestQueueHandle.Abort(); } m_RequestHandleBound = false; m_State = State.Closed; ClearDigestCache(); } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Abort", exception); throw; } finally { if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Abort", ""); } } public void Close() { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Close", ""); try { GlobalLog.Print("HttpListenerRequest::Close()"); ((IDisposable)this).Dispose(); } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Close", exception); throw; } finally { if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", ""); } } // old API, now private, and helper methods private void Dispose(bool disposing) { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Dispose", ""); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::Dispose()"); try { if (m_State == State.Closed){ return; } Stop(); m_RequestHandleBound = false; m_State = State.Closed; } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Dispose", exception); throw; } finally { if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Dispose", ""); } } /// void IDisposable.Dispose() { Dispose(true); } private uint InternalAddPrefix(string uriPrefix) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalAddPrefix() uriPrefix:" + uriPrefix + " calling UnsafeNclNativeMethods.HttpApi.HttpAddUrl"); uint statusCode = 0; fixed (char* pUriPrefix = uriPrefix) { statusCode = UnsafeNclNativeMethods.HttpApi.HttpAddUrl( m_RequestQueueHandle, (ushort*)pUriPrefix, null); } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalAddPrefix() call to UnsafeNclNativeMethods.HttpApi.HttpAddUrl returned:" + statusCode); return statusCode; } private bool InternalRemovePrefix(string uriPrefix) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalRemovePrefix() uriPrefix:" + uriPrefix + " calling UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl"); uint statusCode = 0; fixed (char* pUriPrefix = uriPrefix) { statusCode = UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl( m_RequestQueueHandle, (ushort*)pUriPrefix); } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::InternalRemovePrefix() call to UnsafeNclNativeMethods.HttpApi.HttpRemoveUrl returned:" + statusCode); if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND) { return false; } return true; } private void AddAll() { // go through the uri list and register for each one of them if (m_UriPrefixes.Count>0) { foreach (string registeredPrefix in m_UriPrefixes.Values) { uint statusCode = InternalAddPrefix(registeredPrefix); if (statusCode!=UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { Abort(); if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) throw new HttpListenerException((int)statusCode, SR.GetString(SR.net_listener_already, registeredPrefix)); else throw new HttpListenerException((int)statusCode); } } } } public HttpListenerContext GetContext() { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "GetContext", ""); SyncRequestContext memoryBlob = null; HttpListenerContext httpContext = null; bool stoleBlob = false; try { CheckDisposed(); if (m_State==State.Stopped) { throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "Start()")); } if (m_UriPrefixes.Count==0) { throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "AddPrefix()")); } uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS; uint size = 4096; ulong requestId = 0; memoryBlob = new SyncRequestContext((int) size); for (;;) { for (;;) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() calling UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest RequestId:" + requestId); uint bytesTransferred = 0; statusCode = UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest( m_RequestQueueHandle, requestId, (uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, memoryBlob.RequestBlob, size, &bytesTransferred, null); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() call to UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest returned:" + statusCode); if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && requestId != 0) { // we might get this if somebody stole our RequestId, // we need to start all over again but we can reuse the buffer we just allocated requestId = 0; continue; } else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA) { // the buffer was not big enough to fit the headers, we need // to read the RequestId returned, allocate a new buffer of the required size size = bytesTransferred; requestId = memoryBlob.RequestBlob->RequestId; memoryBlob.Reset(checked((int) size)); continue; } break; } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { // someother bad error, possible(?) return values are: // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED throw new HttpListenerException((int)statusCode); } // We need to hook up our authentication handling code here. httpContext = HandleAuthentication(memoryBlob, out stoleBlob); if (stoleBlob) { // The request has been handed to the user, which means this code can't reuse the blob. Reset it here. memoryBlob = null; stoleBlob = false; } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::GetContext() HandleAuthentication() returned httpContext#" + ValidationHelper.HashString(httpContext)); // if the request survived authentication, return it to the user if (httpContext!=null) { return httpContext; } // HandleAuthentication may have cleaned this up. if (memoryBlob == null) { memoryBlob = new SyncRequestContext(checked((int) size)); } requestId = 0; } } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "GetContext", exception); throw; } finally { if (memoryBlob != null && !stoleBlob) { memoryBlob.ReleasePins(); memoryBlob.Close(); } if(Logging.On)Logging.Exit(Logging.HttpListener, this, "GetContext", "HttpListenerContext#" + ValidationHelper.HashString(httpContext) + " RequestTraceIdentifier#"+httpContext.Request.RequestTraceIdentifier); } } [HostProtection(ExternalThreading=true)] public IAsyncResult BeginGetContext(AsyncCallback callback, object state) { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "BeginGetContext", ""); ListenerAsyncResult asyncResult = null; try { CheckDisposed(); if (m_State==State.Stopped) { throw new InvalidOperationException(SR.GetString(SR.net_listener_mustcall, "Start()")); } // prepare the ListenerAsyncResult object (this will have it's own // event that the user can wait on for IO completion - which means we // need to signal it when IO completes) asyncResult = new ListenerAsyncResult(this, state, callback); uint statusCode = asyncResult.QueueBeginGetContext(); if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) { // someother bad error, possible(?) return values are: // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED throw new HttpListenerException((int)statusCode); } } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "BeginGetContext", exception); throw; } finally { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "BeginGetContext", "IAsyncResult#" + ValidationHelper.HashString(asyncResult)); } return asyncResult; } public HttpListenerContext EndGetContext(IAsyncResult asyncResult) { if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndGetContext", "IAsyncResult#" + ValidationHelper.HashString(asyncResult)); HttpListenerContext httpContext = null; try { CheckDisposed(); if (asyncResult==null) { throw new ArgumentNullException("asyncResult"); } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::EndGetContext() asyncResult:" + ValidationHelper.ToString(asyncResult)); ListenerAsyncResult castedAsyncResult = asyncResult as ListenerAsyncResult; if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) { throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult"); } if (castedAsyncResult.EndCalled) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetContext")); } castedAsyncResult.EndCalled = true; httpContext = castedAsyncResult.InternalWaitForCompletion() as HttpListenerContext; if (httpContext == null) { GlobalLog.Assert(castedAsyncResult.Result is Exception, "EndGetContext|The result is neither a HttpListenerContext nor an Exception."); throw castedAsyncResult.Result as Exception; } } catch (Exception exception) { if(Logging.On)Logging.Exception(Logging.HttpListener, this, "EndGetContext", exception); throw; } finally { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::EndGetContext() returning HttpListenerContext#" + ValidationHelper.ToString(httpContext)); if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndGetContext", "HttpListenerContext#" + ValidationHelper.HashString(httpContext)+ " RequestTraceIdentifier#"+httpContext.Request.RequestTraceIdentifier); } return httpContext; } [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)] private WindowsIdentity CreateWindowsIdentity(IntPtr userToken, string type, WindowsAccountType acctType, bool isAuthenticated) { return new WindowsIdentity(userToken, type, acctType, isAuthenticated); } internal HttpListenerContext HandleAuthentication(RequestContextBase memoryBlob, out bool stoleBlob) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() memoryBlob:0x" + ((IntPtr) memoryBlob.RequestBlob).ToString("x")); string challenge = null; stoleBlob = false; // Some things we need right away. Lift them out now while it's convenient. string verb = UnsafeNclNativeMethods.HttpApi.GetVerb(memoryBlob.RequestBlob); string authorizationHeader = UnsafeNclNativeMethods.HttpApi.GetKnownHeader(memoryBlob.RequestBlob, (int) HttpRequestHeader.Authorization); ulong connectionId = memoryBlob.RequestBlob->ConnectionId; ulong requestId = memoryBlob.RequestBlob->RequestId; GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() authorizationHeader:" + ValidationHelper.ToString(authorizationHeader)); // if the app has turned on AuthPersistence, an anonymous request might // be authenticated by virtue of it coming on a connection that was // previously authenticated. // assurance that we do this only for NTLM/Negotiate is not here, but in the // code that caches WindowsIdentity instances in the Dictionary. DisconnectAsyncResult disconnectResult = (DisconnectAsyncResult) DisconnectResults[connectionId]; if (UnsafeConnectionNtlmAuthentication) { if (authorizationHeader == null) { WindowsPrincipal principal = disconnectResult == null ? null : disconnectResult.AuthenticatedConnection; if (principal != null) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() got principal:" + ValidationHelper.ToString(principal) + " principal.Identity.Name:" + ValidationHelper.ToString(principal.Identity.Name) + " creating request"); stoleBlob = true; HttpListenerContext ntlmContext = new HttpListenerContext(this, memoryBlob); ntlmContext.SetIdentity(principal, null); ntlmContext.Request.ReleasePins(); return ntlmContext; } } else { // They sent an authorization - destroy their previous credentials. GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() clearing principal cache"); if (disconnectResult != null) { disconnectResult.AuthenticatedConnection = null; } } } // Figure out what schemes we're allowing, what context we have. stoleBlob = true; HttpListenerContext httpContext = null; NTAuthentication oldContext = null; NTAuthentication newContext = null; NTAuthentication context = null; AuthenticationSchemes headerScheme = AuthenticationSchemes.None; AuthenticationSchemes authenticationScheme = AuthenticationSchemes; try { // Take over handling disconnects for now. if (disconnectResult != null && !disconnectResult.StartOwningDisconnectHandling()) { // Oops! Just disconnected just then. Pretend we didn't see the disconnectResult. disconnectResult = null; } // Pick out the old context now. By default, it'll be removed in the finally, unless context is set somewhere. if (disconnectResult != null) { oldContext = disconnectResult.Session; } httpContext = new HttpListenerContext(this, memoryBlob); AuthenticationSelectorInfo authenticationSelector = m_AuthenticationDelegate; if (authenticationSelector != null) { try { httpContext.Request.ReleasePins(); authenticationScheme = authenticationSelector.Delegate(httpContext.Request); if (!authenticationSelector.AdvancedAuth && (authenticationScheme & (AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm | AuthenticationSchemes.Digest)) != 0) { throw m_SecurityException; } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() AuthenticationSchemeSelectorDelegate() returned authenticationScheme:" + authenticationScheme); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; if (Logging.On) Logging.PrintError(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_delegate_exception, exception)); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() AuthenticationSchemeSelectorDelegate() returned authenticationScheme:" + authenticationScheme); SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Close(); return null; } } else { // We didn't give the request to the user yet, so we haven't lost control of the unmanaged blob and can // continue to reuse the buffer. stoleBlob = false; } // Then figure out what scheme they're trying (if any are allowed) int index = -1; if (authorizationHeader != null && (authenticationScheme & ~AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { // Find the end of the scheme name. Trust that HTTP.SYS parsed out just our header ok. for (index = 0; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] == ' ' || authorizationHeader[index] == '\t' || authorizationHeader[index] == '\r' || authorizationHeader[index] == '\n') { break; } } // Currently only allow one Authorization scheme/header per request. if (index < authorizationHeader.Length) { if ((authenticationScheme & AuthenticationSchemes.Negotiate) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, NegotiateClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Negotiate; } else if ((authenticationScheme & AuthenticationSchemes.Ntlm) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, NtlmClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Ntlm; } else if ((authenticationScheme & AuthenticationSchemes.Digest) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, DigestClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Digest; } else if ((authenticationScheme & AuthenticationSchemes.Basic) != AuthenticationSchemes.None && string.Compare(authorizationHeader, 0, BasicClient.AuthType, 0, index, StringComparison.OrdinalIgnoreCase) == 0) { headerScheme = AuthenticationSchemes.Basic; } else { if (Logging.On) Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_unsupported_authentication_scheme, authorizationHeader , authenticationScheme)); } } } // httpError holds the error we will return if an Authorization header is present but can't be authenticated HttpStatusCode httpError = HttpStatusCode.InternalServerError; bool error = false; // See if we found an acceptable auth header if (headerScheme == AuthenticationSchemes.None) { if (Logging.On) Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_unmatched_authentication_scheme, ValidationHelper.ToString(authenticationScheme), (authorizationHeader == null ? " " : authorizationHeader))); // If anonymous is allowed, just return the context. Otherwise go for the 401. if ((authenticationScheme & AuthenticationSchemes.Anonymous) != AuthenticationSchemes.None) { if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } httpError = HttpStatusCode.Unauthorized; httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } else { // Perform Authentication byte[] bytes = null; byte[] decodedOutgoingBlob = null; string outBlob = null; // Find the beginning of the blob. Trust that HTTP.SYS parsed out just our header ok. for (index++; index < authorizationHeader.Length; index++) { if (authorizationHeader[index] != ' ' && authorizationHeader[index] != '\t' && authorizationHeader[index] != '\r' && authorizationHeader[index] != '\n') { break; } } string inBlob = index < authorizationHeader.Length ? authorizationHeader.Substring(index) : ""; IPrincipal principal = null; SecurityStatus statusCodeNew; GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() Performing Authentication headerScheme:" + ValidationHelper.ToString(headerScheme)); switch (headerScheme) { case AuthenticationSchemes.Digest: GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() package:WDigest headerScheme:" + headerScheme); // WDigest had some weird behavior. This is what I have discovered: // Local accounts don't work, only domain accounts. The domain (i.e. REDMOND) is implied. Not sure how it is chosen. // If the domain is specified and the credentials are correct, it works. If they're not (domain, username or password): // AcceptSecurityContext (GetOutgoingDigestBlob) returns success but with a bogus 4k challenge, and // QuerySecurityContextToken (GetContextToken) fails with NoImpersonation. // If the domain isn't specified, AcceptSecurityContext returns NoAuthenticatingAuthority for a bad username, // and LogonDenied for a bad password. // Also interesting is that WDigest requires us to keep a reference to the previous context, but fails if we // actually pass it in! (It't ok to pass it in for the first request, but not if nc > 1.) For Whidbey, // we create a new context and associate it with the connection, just like NTLM, but instead of using it for // the next request on the connection, we always create a new context and swap the old one out. As long // as we keep the old one around until after we authenticate with the new one, it works. For this reason, // we also keep these contexts around past the lifetime of the connection, so that KeepAlive=false works. context = new NTAuthentication(true, NegotiationInfoClass.WDigest, null, ContextFlags.Connection); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() verb:" + verb + " context.IsValidContext:" + context.IsValidContext.ToString()); outBlob = context.GetOutgoingDigestBlob(inBlob, verb, null, Realm, false, false, out statusCodeNew); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetOutgoingDigestBlob() returned IsCompleted:" + context.IsCompleted + " statusCodeNew:" + statusCodeNew + " outBlob:[" + outBlob + "]"); // WDigest bug: sometimes when AcceptSecurityContext returns success, it provides a bogus, empty 4k buffer. // Ignore it. (Should find out what's going on here from WDigest people.) if (statusCodeNew == SecurityStatus.OK) { outBlob = null; } if (context.IsValidContext) { SafeCloseHandle userContext = null; try { userContext = context.GetContextToken(out statusCodeNew); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetContextToken() returned:" + statusCodeNew.ToString()); if (statusCodeNew != SecurityStatus.OK) { httpError = HttpStatusFromSecurityStatus(statusCodeNew); } else if (userContext == null) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() error: GetContextToken() returned:null statusCodeNew:" + statusCodeNew.ToString()); httpError = HttpStatusCode.Unauthorized; } else { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() creating new WindowsIdentity() from userContext:" + userContext.DangerousGetHandle().ToString("x8")); principal = new WindowsPrincipal(CreateWindowsIdentity(userContext.DangerousGetHandle(), DigestClient.AuthType, WindowsAccountType.Normal, true)); } } finally { if (userContext!=null) { userContext.Close(); } } newContext = context; if (outBlob != null) { challenge = DigestClient.AuthType + " " + outBlob; } } else { httpError = HttpStatusFromSecurityStatus(statusCodeNew); } break; case AuthenticationSchemes.Negotiate: case AuthenticationSchemes.Ntlm: GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() headerScheme:" + headerScheme); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() returned context#" + ValidationHelper.HashString(oldContext) + " for connectionId:" + connectionId); string package = headerScheme == AuthenticationSchemes.Ntlm ? NtlmClient.AuthType : NegotiateClient.AuthType; if (oldContext != null && oldContext.Package == package) { context = oldContext; } else { context = new NTAuthentication(true, package, null, ContextFlags.Connection); } try { bytes = Convert.FromBase64String(inBlob); } catch (FormatException) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() FromBase64String threw a FormatException."); httpError = HttpStatusCode.BadRequest; error = true; } if (!error) { decodedOutgoingBlob = context.GetOutgoingBlob(bytes, false, out statusCodeNew); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetOutgoingBlob() returned IsCompleted:" + context.IsCompleted + " statusCodeNew:" + statusCodeNew); error = !context.IsValidContext; if (error) { // Bug #474228: SSPI Workaround // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE // when it should return SEC_E_INVALID_TOKEN. if (statusCodeNew == SecurityStatus.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) { statusCodeNew = SecurityStatus.InvalidToken; } httpError = HttpStatusFromSecurityStatus(statusCodeNew); } } if (decodedOutgoingBlob!=null) { outBlob = Convert.ToBase64String(decodedOutgoingBlob); } if (!error) { if (context.IsCompleted) { SafeCloseHandle userContext = null; try { userContext = context.GetContextToken(out statusCodeNew); if (statusCodeNew != SecurityStatus.OK) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() GetContextToken() failed with statusCodeNew:" + statusCodeNew.ToString()); httpError = HttpStatusFromSecurityStatus(statusCodeNew); } else { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() creating new WindowsIdentity() from userContext:" + userContext.DangerousGetHandle().ToString("x8")); WindowsPrincipal windowsPrincipal = new WindowsPrincipal(CreateWindowsIdentity(userContext.DangerousGetHandle(), context.ProtocolName, WindowsAccountType.Normal, true)); principal = windowsPrincipal; // if appropriate, cache this credential on this connection if (UnsafeConnectionNtlmAuthentication && context.ProtocolName == NegotiationInfoClass.NTLM) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() inserting principal#" + ValidationHelper.HashString(principal) + " for connectionId:" + connectionId); // We may need to call WaitForDisconnect. if (disconnectResult == null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); } if (disconnectResult != null) { lock (DisconnectResults.SyncRoot) { if (UnsafeConnectionNtlmAuthentication) { disconnectResult.AuthenticatedConnection = windowsPrincipal; } } } else { // Registration failed - UnsafeConnectionNtlmAuthentication ignored. GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() RegisterForDisconnectNotification() failed."); } } } } finally { if (userContext!=null) { userContext.Close(); } } } else { // auth incomplete newContext = context; challenge = (headerScheme==AuthenticationSchemes.Ntlm ? NtlmClient.AuthType : NegotiateClient.AuthType); if (!String.IsNullOrEmpty(outBlob)) { challenge += " " + outBlob; } } } break; case AuthenticationSchemes.Basic: try { bytes = Convert.FromBase64String(inBlob); inBlob = WebHeaderCollection.HeaderEncoding.GetString(bytes, 0, bytes.Length); index = inBlob.IndexOf(':'); if (index!=-1) { string userName = inBlob.Substring(0, index); string password = inBlob.Substring(index+1); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() basic identity found, userName:" + userName); principal = new GenericPrincipal(new HttpListenerBasicIdentity(userName, password), null); } else { httpError = HttpStatusCode.BadRequest; } } catch (FormatException) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() FromBase64String threw a FormatException."); } break; } if (principal != null) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() got principal:" + ValidationHelper.ToString(principal) + " principal.Identity.Name:" + ValidationHelper.ToString(principal.Identity.Name) + " creating request"); httpContext.SetIdentity(principal, outBlob); } else { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() handshake has failed"); if(Logging.On)Logging.PrintWarning(Logging.HttpListener, this, "HandleAuthentication", SR.GetString(SR.net_log_listener_create_valid_identity_failed)); httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); httpContext = null; } } // if we're not giving a request to the application, we need to send an error ArrayList challenges = null; if (httpContext == null) { // If we already have a challenge, just use it. Otherwise put a challenge for each acceptable scheme. if (challenge != null) { AddChallenge(ref challenges, challenge); } else { // We're starting over. Any context SSPI might have wanted us to keep is useless. if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } // If we're sending something besides 401, do it here. if (httpError != HttpStatusCode.Unauthorized) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() failed context#" + ValidationHelper.HashString(context) + " for connectionId:" + connectionId + " because of error:" + httpError.ToString()); SendError(requestId, httpError, null); return null; } challenges = BuildChallenge(authenticationScheme, connectionId, out newContext); } } // Check if we need to call WaitForDisconnect, because if we do and it fails, we want to send a 500 instead. if (disconnectResult == null && newContext != null) { RegisterForDisconnectNotification(connectionId, ref disconnectResult); // Failed - send 500. if (disconnectResult == null) { if (newContext != null) { if (newContext == context) { context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() failed context#" + ValidationHelper.HashString(context) + " for connectionId:" + connectionId + " because of failed HttpWaitForDisconnect"); SendError(requestId, HttpStatusCode.InternalServerError, null); httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); return null; } } // Update Session if necessary. if (oldContext != newContext) { if (oldContext == context) { // Prevent the finally from closing this twice. context = null; } NTAuthentication toClose = oldContext; oldContext = newContext; disconnectResult.Session = newContext; if (toClose != null) { // Save digest context in digest cache, we may need it later because of // subsequest responses to the same req on the same/diff connection if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { SaveDigestContext(toClose); } else { toClose.CloseContext(); } } } // Send the 401 here. if (httpContext == null) { SendError(requestId, challenges != null && challenges.Count > 0 ? HttpStatusCode.Unauthorized : HttpStatusCode.Forbidden, challenges); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() SendUnauthorized(Scheme:" + authenticationScheme + ")"); return null; } if (!stoleBlob) { stoleBlob = true; httpContext.Request.ReleasePins(); } return httpContext; } catch { if (httpContext != null) { httpContext.Request.DetachBlob(memoryBlob); httpContext.Close(); } if (newContext != null) { if (newContext == context) { // Prevent the finally from closing this twice. context = null; } if (newContext != oldContext) { NTAuthentication toClose = newContext; newContext = null; toClose.CloseContext(); } else { newContext = null; } } throw; } finally { try { // Clean up the previous context if necessary. if (oldContext != null && oldContext != newContext) { // Clear out Session if it wasn't already. if (newContext == null && disconnectResult != null) { disconnectResult.Session = null; } // Save digest context in digest cache, we may need it later because of // subsequest responses to the same req on the same/diff connection if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { SaveDigestContext(oldContext); } else { oldContext.CloseContext(); } } // Delete any context created but not stored. if (context != null && oldContext != context && newContext != context) { context.CloseContext(); } } finally { // Check if the connection got deleted while in this method, and clear out the hashtables if it did. // In a nested finally because if this doesn't happen, we leak. if (disconnectResult != null) { disconnectResult.FinishOwningDisconnectHandling(); } } } } private static void AddChallenge(ref ArrayList challenges, string challenge) { if (challenge!=null) { challenge = challenge.Trim(); if (challenge.Length>0) { GlobalLog.Print("HttpListener:AddChallenge() challenge:" + challenge); if (challenges == null) { challenges = new ArrayList(4); } challenges.Add(challenge); } } } private ArrayList BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId, out NTAuthentication newContext) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() authenticationScheme:" + authenticationScheme.ToString()); ArrayList challenges = null; newContext = null; if ((authenticationScheme & AuthenticationSchemes.Negotiate) != 0) { AddChallenge(ref challenges, NegotiateClient.AuthType); } if ((authenticationScheme & AuthenticationSchemes.Ntlm) != 0) { AddChallenge(ref challenges, NtlmClient.AuthType); } if ((authenticationScheme & AuthenticationSchemes.Digest) != 0) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() package:WDigest"); NTAuthentication context = null; try { context = new NTAuthentication(true, NegotiationInfoClass.WDigest, null, ContextFlags.Connection); SecurityStatus statusCode; string outBlob = context.GetOutgoingDigestBlob(null, null, null, Realm, false, false, out statusCode); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::BuildChallenge() GetOutgoingDigestBlob() returned IsCompleted:" + context.IsCompleted + " statusCode:" + statusCode + " outBlob:[" + outBlob + "]"); if (context.IsValidContext) { newContext = context; } AddChallenge(ref challenges, DigestClient.AuthType + (string.IsNullOrEmpty(outBlob) ? "" : " " + outBlob)); } finally { if (context != null && newContext != context) { context.CloseContext(); } } } if ((authenticationScheme & AuthenticationSchemes.Basic) != 0) { AddChallenge(ref challenges, BasicClient.AuthType + " realm=\"" + Realm + "\""); } return challenges; } private void RegisterForDisconnectNotification(ulong connectionId, ref DisconnectAsyncResult disconnectResult) { GlobalLog.Assert(disconnectResult == null, "HttpListener#{0}::RegisterForDisconnectNotification()|Called with a disconnectResult.", ValidationHelper.HashString(this)); try { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() calling UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect"); DisconnectAsyncResult result = new DisconnectAsyncResult(this, connectionId); EnsureBoundHandle(); uint statusCode = UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect( m_RequestQueueHandle, connectionId, result.NativeOverlapped); GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() call to UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect returned:" + statusCode); if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS || statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) { // Need to make sure it's going to get returned before adding it to the hash. That way it'll be handled // correctly in HandleAuthentication's finally. disconnectResult = result; DisconnectResults[connectionId] = disconnectResult; } } catch (Win32Exception exception) { uint statusCode = (uint) exception.NativeErrorCode; GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::RegisterForDisconnectNotification() call to UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnect threw. statusCode:" + statusCode); } } private void SendError(ulong requestId, HttpStatusCode httpStatusCode, ArrayList challenges) { GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() requestId:" + ValidationHelper.ToString(requestId)); UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE httpResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE(); httpResponse.Version = new UnsafeNclNativeMethods.HttpApi.HTTP_VERSION(); httpResponse.Version.MajorVersion = (ushort)1; httpResponse.Version.MinorVersion = (ushort)1; httpResponse.StatusCode = (ushort)httpStatusCode; string statusDescription = HttpListenerResponse.GetStatusDescription((int)httpStatusCode); uint DataWritten = 0; uint statusCode; byte[] byteReason = Encoding.Default.GetBytes(statusDescription); fixed (byte* pReason = byteReason) { httpResponse.pReason = (sbyte*)pReason; httpResponse.ReasonLength = (ushort)byteReason.Length; byte[] byteContentLength = Encoding.Default.GetBytes("0"); fixed (byte* pContentLength = byteContentLength) { (&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength; (&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length; httpResponse.Headers.UnknownHeaderCount = checked((ushort) (challenges == null ? 0 : challenges.Count)); GCHandle[] challengeHandles = null; UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[] headersArray = null; GCHandle headersArrayHandle = new GCHandle(); GCHandle wwwAuthenticateHandle = new GCHandle(); if (httpResponse.Headers.UnknownHeaderCount > 0) { challengeHandles = new GCHandle[httpResponse.Headers.UnknownHeaderCount]; headersArray = new UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[httpResponse.Headers.UnknownHeaderCount]; } try { if (httpResponse.Headers.UnknownHeaderCount > 0) { headersArrayHandle = GCHandle.Alloc(headersArray, GCHandleType.Pinned); httpResponse.Headers.pUnknownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER*) Marshal.UnsafeAddrOfPinnedArrayElement(headersArray, 0); wwwAuthenticateHandle = GCHandle.Alloc(s_WwwAuthenticateBytes, GCHandleType.Pinned); sbyte* wwwAuthenticate = (sbyte*) Marshal.UnsafeAddrOfPinnedArrayElement(s_WwwAuthenticateBytes, 0); for (int i = 0; i < challengeHandles.Length; i++) { byte[] byteChallenge = Encoding.Default.GetBytes((string) challenges[i]); challengeHandles[i] = GCHandle.Alloc(byteChallenge, GCHandleType.Pinned); headersArray[i].pName = wwwAuthenticate; headersArray[i].NameLength = (ushort) s_WwwAuthenticateBytes.Length; headersArray[i].pRawValue = (sbyte*) Marshal.UnsafeAddrOfPinnedArrayElement(byteChallenge, 0); headersArray[i].RawValueLength = checked((ushort) byteChallenge.Length); } } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() calling UnsafeNclNativeMethods.HttpApi.HttpSendHtthttpResponse"); statusCode = UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse( m_RequestQueueHandle, requestId, 0, &httpResponse, null, &DataWritten, SafeLocalFree.Zero, 0, null, null ); } finally { if (headersArrayHandle.IsAllocated) { headersArrayHandle.Free(); } if (wwwAuthenticateHandle.IsAllocated) { wwwAuthenticateHandle.Free(); } if (challengeHandles != null) { for (int i = 0; i < challengeHandles.Length; i++) { if (challengeHandles[i].IsAllocated) { challengeHandles[i].Free(); } } } } } } GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::SendInternalError() call to UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse returned:" + statusCode); if (statusCode!=UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { // if we fail to send a 401 something's seriously wrong, abort the request GlobalLog.Print("HttpListener#" + ValidationHelper.HashString(this) + "::HandleAuthentication() SendUnauthorized() returned:" + statusCode); HttpListenerContext.CancelRequest(m_RequestQueueHandle, requestId); } } internal void CheckDisposed() { if (m_State==State.Closed) { throw new ObjectDisposedException(this.GetType().FullName); } } // This only works for context-destroying errors. private HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatus status) { if (NclUtilities.IsCredentialFailure(status)) { return HttpStatusCode.Unauthorized; } if (NclUtilities.IsClientFault(status)) { return HttpStatusCode.BadRequest; } return HttpStatusCode.InternalServerError; } enum State { Stopped, Started, Closed, } private const int DigestLifetimeSeconds = 300; private const int MaximumDigests = 1024; // Must be a power of two. private const int MinimumDigestLifetimeSeconds = 10; private struct DigestContext { internal NTAuthentication context; internal int timestamp; } private DigestContext[] m_SavedDigests; private ArrayList m_ExtraSavedDigests; private ArrayList m_ExtraSavedDigestsBaking; private int m_ExtraSavedDigestsTimestamp; private int m_NewestContext; private int m_OldestContext; private void SaveDigestContext(NTAuthentication digestContext) { if (m_SavedDigests == null) { Interlocked.CompareExchange (ref m_SavedDigests, new DigestContext[MaximumDigests], null); } // We want to actually close the contexts outside the lock. NTAuthentication oldContext = null; ArrayList digestsToClose = null; lock (m_SavedDigests) { // If we're stopped, just throw it away. if (!IsListening) { digestContext.CloseContext(); return; } int now = ((now = Environment.TickCount) == 0 ? 1 : now); m_NewestContext = (m_NewestContext + 1) & (MaximumDigests - 1); int oldTimestamp = m_SavedDigests[m_NewestContext].timestamp; oldContext = m_SavedDigests[m_NewestContext].context; m_SavedDigests[m_NewestContext].timestamp = now; m_SavedDigests[m_NewestContext].context = digestContext; // May need to move this up. if (m_OldestContext == m_NewestContext) { m_OldestContext = (m_NewestContext + 1) & (MaximumDigests - 1); } // Delete additional contexts older than five minutes. while (unchecked(now - m_SavedDigests[m_OldestContext].timestamp) >= DigestLifetimeSeconds && m_SavedDigests[m_OldestContext].context != null) { if (digestsToClose == null) { digestsToClose = new ArrayList(); } digestsToClose.Add(m_SavedDigests[m_OldestContext].context); m_SavedDigests[m_OldestContext].context = null; m_OldestContext = (m_OldestContext + 1) & (MaximumDigests - 1); } // If the old context is younger than 10 seconds, put it in the backup pile. if (oldContext != null && unchecked(now - oldTimestamp) <= MinimumDigestLifetimeSeconds * 1000) { // Use a two-tier ArrayList system to guarantee each entry lives at least 10 seconds. if (m_ExtraSavedDigests == null || unchecked(now - m_ExtraSavedDigestsTimestamp) > MinimumDigestLifetimeSeconds * 1000) { digestsToClose = m_ExtraSavedDigestsBaking; m_ExtraSavedDigestsBaking = m_ExtraSavedDigests; m_ExtraSavedDigestsTimestamp = now; m_ExtraSavedDigests = new ArrayList(); } m_ExtraSavedDigests.Add(oldContext); oldContext = null; } } if (oldContext != null) { oldContext.CloseContext(); } if (digestsToClose != null) { for (int i = 0; i < digestsToClose.Count; i++) { ((NTAuthentication)digestsToClose[i]).CloseContext(); } } } private void ClearDigestCache() { if (m_SavedDigests == null) { return; } ArrayList[] toClose = new ArrayList[3]; lock (m_SavedDigests) { toClose[0] = m_ExtraSavedDigestsBaking; m_ExtraSavedDigestsBaking = null; toClose[1] = m_ExtraSavedDigests; m_ExtraSavedDigests = null; m_NewestContext = 0; m_OldestContext = 0; toClose[2] = new ArrayList(); for (int i = 0; i < MaximumDigests; i++) { if (m_SavedDigests[i].context != null) { toClose[2].Add(m_SavedDigests[i].context); m_SavedDigests[i].context = null; } m_SavedDigests[i].timestamp = 0; } } for (int j = 0; j < toClose.Length; j++) { if (toClose[j] != null) { for (int k = 0; k < toClose[j].Count; k++) { ((NTAuthentication) toClose[j][k]).CloseContext(); } } } } class DisconnectAsyncResult : IAsyncResult { private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(WaitCallback); private ulong m_ConnectionId; private HttpListener m_HttpListener; NativeOverlapped* m_NativeOverlapped; private int m_OwnershipState; // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up private WindowsPrincipal m_AuthenticatedConnection; private NTAuthentication m_Session; internal const string NTLM = "NTLM"; internal NativeOverlapped* NativeOverlapped{ get{ return m_NativeOverlapped; } } public object AsyncState { get { throw ExceptionHelper.PropertyNotImplementedException; } } public WaitHandle AsyncWaitHandle { get { throw ExceptionHelper.PropertyNotImplementedException; } } public bool CompletedSynchronously { get { throw ExceptionHelper.PropertyNotImplementedException; } } public bool IsCompleted { get { throw ExceptionHelper.PropertyNotImplementedException; } } internal unsafe DisconnectAsyncResult(HttpListener httpListener, ulong connectionId) { GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() httpListener#" + ValidationHelper.HashString(httpListener) + " connectionId:" + connectionId); m_OwnershipState = 1; m_HttpListener = httpListener; m_ConnectionId = connectionId; Overlapped overlapped = new Overlapped(); overlapped.AsyncResult = this; // we can call the Unsafe API here, we won't ever call user code m_NativeOverlapped = overlapped.UnsafePack(s_IOCallback, null); GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() overlapped#" + ValidationHelper.HashString(overlapped) + " nativeOverlapped:" + ((IntPtr)m_NativeOverlapped).ToString("x")); } internal bool StartOwningDisconnectHandling() { int oldValue; while ((oldValue = Interlocked.CompareExchange(ref m_OwnershipState, 1, 0)) == 2) { // Must block until it equals 3 - we must be in the callback right now. Thread.SpinWait(1); } GlobalLog.Assert(oldValue != 1, "DisconnectAsyncResult#{0}::HandleDisconnect()|StartOwningDisconnectHandling() called twice.", ValidationHelper.HashString(this)); return oldValue < 2; } internal void FinishOwningDisconnectHandling() { // If it got disconnected, run the disconnect code. if (Interlocked.CompareExchange(ref m_OwnershipState, 0, 1) == 2) { HandleDisconnect(); } } private static unsafe void WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) { GlobalLog.Print("DisconnectAsyncResult::WaitCallback() errorCode:" + errorCode + " numBytes:" + numBytes + " nativeOverlapped:" + ((IntPtr)nativeOverlapped).ToString("x")); // take the DisconnectAsyncResult object from the state Overlapped callbackOverlapped = Overlapped.Unpack(nativeOverlapped); DisconnectAsyncResult asyncResult = (DisconnectAsyncResult) callbackOverlapped.AsyncResult; GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::WaitCallback() callbackOverlapped#" + ValidationHelper.HashString(callbackOverlapped) + " m_ConnectionId:" + asyncResult.m_ConnectionId); Overlapped.Free(nativeOverlapped); if (Interlocked.Exchange(ref asyncResult.m_OwnershipState, 2) == 0) { asyncResult.HandleDisconnect(); } } private void HandleDisconnect() { GlobalLog.Print("DisconnectAsyncResult#" + ValidationHelper.HashString(this) + "::HandleDisconnect() DisconnectResults#" + ValidationHelper.HashString(m_HttpListener.DisconnectResults) + " removing for m_ConnectionId:" + m_ConnectionId); m_HttpListener.DisconnectResults.Remove(m_ConnectionId); if (m_Session != null) { if (m_Session.Package == NegotiationInfoClass.WDigest) { // VSWhidbey #497767 // WDigest doesn't like having the context passed back in on the next request on a connection, but it does want // the server to keep a reference to it for as long as a client might reuse the nonce. The heuristic we use is, // keep contexts for five minutes, up to a maximum of 1024, except also keep all contexts at least 10 seconds to avoid // total DoS (where no handshakes can be completed in time). m_HttpListener.SaveDigestContext(m_Session); } else { m_Session.CloseContext(); } } // Clean up the identity. This is for scenarios where identity was not cleaned up before due to // identity caching for unsafe ntlm authentication IDisposable identity = m_AuthenticatedConnection == null ? null : m_AuthenticatedConnection.Identity as IDisposable; if ((identity != null) && (m_AuthenticatedConnection.Identity.AuthenticationType == NTLM) && (m_HttpListener.UnsafeConnectionNtlmAuthentication)) { identity.Dispose(); } int oldValue = Interlocked.Exchange(ref m_OwnershipState, 3); GlobalLog.Assert(oldValue == 2, "DisconnectAsyncResult#{0}::HandleDisconnect()|Expected OwnershipState of 2, saw {1}.", ValidationHelper.HashString(this), oldValue); } internal WindowsPrincipal AuthenticatedConnection { get { return m_AuthenticatedConnection; } set { // The previous value can't be disposed because it may be in use by the app. m_AuthenticatedConnection = value; } } internal NTAuthentication Session { get { return m_Session; } set { m_Session = value; } } } } /* Proposed Future HTTP Base Classes see \ndp\mb\docs\specs\NetworkFramework\HTTPSYS\ASP.NET\stub.cs // System.Net exposes base abstract classes that System.Web will inherit from public abstract class BaseHttpContext { public virtual IPrincipal User { get; set; } // it doesn't make sense to make these virtual because we can't override // and change the returned type for System.Web or people would break. // the only thing we can do is to declare them normally and hide the // base implementation like "public new System.Web.HttpRequest Request" public BaseHttpRequest Request { get; } public BaseHttpResponse Response { get; } // these provide plumbing to make the above two methods callable with a BaseHttpContext reference protected virtual BaseHttpRequest GetRequest(); protected virtual BaseHttpResponse GetResponse(); } public abstract class BaseHttpRequest { public virtual string[] AcceptTypes { get; } public virtual Encoding ContentEncoding { get; set; } public virtual string ContentType { get; set; } public virtual NameValueCollection Headers { get; } public virtual string HttpMethod { get; } public virtual Stream InputStream { get; } public virtual bool IsAuthenticated { get; } public virtual bool IsLocal { get; } public virtual bool IsSecureConnection { get; } public virtual NameValueCollection QueryString { get; } public virtual string RawUrl { get; } public virtual Uri Url { get; } public virtual Uri UrlReferrer { get; } public virtual string UserAgent { get; } public virtual string UserHostAddress { get; } public virtual string UserHostName { get; } public virtual string[] UserLanguages { get; } // APIs that are in the base class but are new to ASP .NET public virtual long ContentLengthLong { get; } public virtual bool HasEntityBody { get; } public virtual bool KeepAlive { get; } public virtual IPEndPoint RemoteEndPoint { get; } public virtual IPEndPoint LocalEndPoint { get; } } public abstract class BaseHttpResponse { public virtual void AppendHeader(string name, string value); public virtual void Close(); public virtual Encoding ContentEncoding { get; set; } public virtual string ContentType { get; set; } public virtual Stream OutputStream { get; } public virtual string RedirectLocation { get; set; } public virtual int StatusCode { get; set; } public virtual string StatusDescription { get; set; } // APIs that are in the base class but are new to ASP .NET public virtual bool KeepAlive { get; set; } } */ }
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- DataGridViewMethods.cs
- CqlLexer.cs
- UdpDuplexChannel.cs
- DocumentXPathNavigator.cs
- WinFormsSecurity.cs
- TypeHelpers.cs
- ServicePoint.cs
- Collection.cs
- DataTablePropertyDescriptor.cs
- WeakReference.cs
- SymbolEqualComparer.cs
- KnownTypesHelper.cs
- connectionpool.cs
- HtmlPanelAdapter.cs
- ImageCodecInfoPrivate.cs
- CounterSample.cs
- ISAPIApplicationHost.cs
- HtmlTextArea.cs
- PipeConnection.cs
- TypeToken.cs
- Effect.cs
- CellConstant.cs
- IPHostEntry.cs
- CapabilitiesAssignment.cs
- SrgsRulesCollection.cs
- SystemWebExtensionsSectionGroup.cs
- CalendarDay.cs
- OdbcConnectionHandle.cs
- RequestSecurityTokenResponse.cs
- BindingGroup.cs
- HashLookup.cs
- ReplyAdapterChannelListener.cs
- NamespaceCollection.cs
- ObjectAnimationBase.cs
- TreeViewHitTestInfo.cs
- XmlDomTextWriter.cs
- CompModSwitches.cs
- Span.cs
- SocketException.cs
- XsltQilFactory.cs
- SubqueryRules.cs
- OrderByExpression.cs
- DrawingGroup.cs
- Rect3DConverter.cs
- ClientConfigurationHost.cs
- SchemaTypeEmitter.cs
- SqlProviderServices.cs
- shaperfactoryquerycacheentry.cs
- CharUnicodeInfo.cs
- CompositionTarget.cs
- DictionaryBase.cs
- Publisher.cs
- TextBox.cs
- DataServiceRequestException.cs
- TextEffectResolver.cs
- ResizeGrip.cs
- WaveHeader.cs
- CodeGotoStatement.cs
- DataServiceException.cs
- DeviceContext2.cs
- BitmapEffectInput.cs
- ListBindableAttribute.cs
- XPathNodeHelper.cs
- GridViewEditEventArgs.cs
- RequestResizeEvent.cs
- IgnoreFileBuildProvider.cs
- BinaryNode.cs
- AsymmetricSignatureFormatter.cs
- ActivationArguments.cs
- SafeNativeMethods.cs
- ScriptComponentDescriptor.cs
- webclient.cs
- CorrelationValidator.cs
- FunctionCommandText.cs
- IndependentAnimationStorage.cs
- RegistryExceptionHelper.cs
- RichTextBoxConstants.cs
- EventSetter.cs
- WriterOutput.cs
- ClassGenerator.cs
- CodeExporter.cs
- PolicyLevel.cs
- FactoryRecord.cs
- GridViewColumnHeader.cs
- CompatibleComparer.cs
- Hash.cs
- ErrorInfoXmlDocument.cs
- WinCategoryAttribute.cs
- DataReceivedEventArgs.cs
- DelegateSerializationHolder.cs
- DataGridViewRowCollection.cs
- SerialPort.cs
- CheckBoxRenderer.cs
- SecurityTokenSerializer.cs
- ServicePoint.cs
- Sql8ExpressionRewriter.cs
- ControlEvent.cs
- SystemWebExtensionsSectionGroup.cs
- RuleSettingsCollection.cs
- SmiConnection.cs