From b31ec7d7a723d111ba565f4fd8d6836c5ad26961 Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 31 Aug 2016 14:19:56 -0700 Subject: [PATCH] #204 Consolidate unsafe buffer access into NativeRequestContext --- .../AsyncAcceptContext.cs | 14 +- .../AuthenticationManager.cs | 46 -- .../NativeInterop/CookedUrl.cs | 75 +++ .../NativeInterop/HttpApi.cs | 200 -------- .../RequestProcessing/NativeRequestContext.cs | 431 +++++++++++++----- .../RequestProcessing/Request.cs | 99 +--- .../RequestProcessing/RequestHeaders.cs | 6 +- .../RequestProcessing/RequestStream.cs | 10 +- src/Microsoft.Net.Http.Server/WebListener.cs | 9 +- .../ResponseCachingTests.cs | 4 +- 10 files changed, 442 insertions(+), 452 deletions(-) create mode 100644 src/Microsoft.Net.Http.Server/NativeInterop/CookedUrl.cs diff --git a/src/Microsoft.Net.Http.Server/AsyncAcceptContext.cs b/src/Microsoft.Net.Http.Server/AsyncAcceptContext.cs index d42a66e605..58a5f7f186 100644 --- a/src/Microsoft.Net.Http.Server/AsyncAcceptContext.cs +++ b/src/Microsoft.Net.Http.Server/AsyncAcceptContext.cs @@ -108,13 +108,13 @@ namespace Microsoft.Net.Http.Server } else { - asyncResult._nativeRequestContext.Reset(0, 0); + asyncResult._nativeRequestContext.Reset(); } } } else { - asyncResult._nativeRequestContext.Reset(asyncResult._nativeRequestContext.RequestBlob->RequestId, numBytes); + asyncResult._nativeRequestContext.Reset(asyncResult._nativeRequestContext.RequestId, numBytes); } // We need to issue a new request, either because auth failed, or because our buffer was too small the first time. @@ -166,26 +166,26 @@ namespace Microsoft.Net.Http.Server uint bytesTransferred = 0; statusCode = HttpApi.HttpReceiveHttpRequest( Server.RequestQueue.Handle, - _nativeRequestContext.RequestBlob->RequestId, + _nativeRequestContext.RequestId, (uint)HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, - _nativeRequestContext.RequestBlob, + _nativeRequestContext.NativeRequest, _nativeRequestContext.Size, &bytesTransferred, _nativeRequestContext.NativeOverlapped); - if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && _nativeRequestContext.RequestBlob->RequestId != 0) + if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && _nativeRequestContext.RequestId != 0) { // we might get this if somebody stole our RequestId, // set RequestId to 0 and start all over again with the buffer we just allocated // BUGBUG: how can someone steal our request ID? seems really bad and in need of fix. - _nativeRequestContext.RequestBlob->RequestId = 0; + _nativeRequestContext.RequestId = 0; retry = true; } 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 - _nativeRequestContext.Reset(_nativeRequestContext.RequestBlob->RequestId, bytesTransferred); + _nativeRequestContext.Reset(_nativeRequestContext.RequestId, bytesTransferred); retry = true; } else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS diff --git a/src/Microsoft.Net.Http.Server/AuthenticationManager.cs b/src/Microsoft.Net.Http.Server/AuthenticationManager.cs index fb8766996a..14153b285b 100644 --- a/src/Microsoft.Net.Http.Server/AuthenticationManager.cs +++ b/src/Microsoft.Net.Http.Server/AuthenticationManager.cs @@ -152,51 +152,5 @@ namespace Microsoft.Net.Http.Server = StringValues.Concat(context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate], challenges.ToArray()); } } - - internal static unsafe bool CheckAuthenticated(HttpApi.HTTP_REQUEST_INFO* requestInfo) - { - if (requestInfo != null - && requestInfo->InfoType == HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth - && requestInfo->pInfo->AuthStatus == HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess) - { - return true; - } - return false; - } - - internal static unsafe ClaimsPrincipal GetUser(HttpApi.HTTP_REQUEST_INFO* requestInfo, int infoCount) - { - for (int i = 0; i < infoCount; i++) - { - var info = &requestInfo[i]; - if (requestInfo != null - && info->InfoType == HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth - && info->pInfo->AuthStatus == HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess) - { - return new WindowsPrincipal(new WindowsIdentity(info->pInfo->AccessToken, - GetAuthTypeFromRequest(info->pInfo->AuthType).ToString())); - } - } - return new ClaimsPrincipal(new ClaimsIdentity()); // Anonymous / !IsAuthenticated - } - - private static AuthenticationSchemes GetAuthTypeFromRequest(HttpApi.HTTP_REQUEST_AUTH_TYPE input) - { - switch (input) - { - case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeBasic: - return AuthenticationSchemes.Basic; - // case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeDigest: - // return AuthenticationSchemes.Digest; - case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeNTLM: - return AuthenticationSchemes.NTLM; - case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeNegotiate: - return AuthenticationSchemes.Negotiate; - case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeKerberos: - return AuthenticationSchemes.Kerberos; - default: - throw new NotImplementedException(input.ToString()); - } - } } } diff --git a/src/Microsoft.Net.Http.Server/NativeInterop/CookedUrl.cs b/src/Microsoft.Net.Http.Server/NativeInterop/CookedUrl.cs new file mode 100644 index 0000000000..ed702b7406 --- /dev/null +++ b/src/Microsoft.Net.Http.Server/NativeInterop/CookedUrl.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Net.Http.Server +{ + // Note this type should only be used while the request buffer remains pinned + internal class CookedUrl + { + private readonly HttpApi.HTTP_COOKED_URL _nativeCookedUrl; + + internal CookedUrl(HttpApi.HTTP_COOKED_URL nativeCookedUrl) + { + _nativeCookedUrl = nativeCookedUrl; + } + + internal unsafe string GetFullUrl() + { + if (_nativeCookedUrl.pFullUrl != null && _nativeCookedUrl.FullUrlLength > 0) + { + return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pFullUrl, _nativeCookedUrl.FullUrlLength / 2); + } + return null; + } + + internal unsafe string GetHost() + { + if (_nativeCookedUrl.pHost != null && _nativeCookedUrl.HostLength > 0) + { + return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pHost, _nativeCookedUrl.HostLength / 2); + } + return null; + } + + internal unsafe string GetAbsPath() + { + if (_nativeCookedUrl.pAbsPath != null && _nativeCookedUrl.AbsPathLength > 0) + { + return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pAbsPath, _nativeCookedUrl.AbsPathLength / 2); + } + return null; + } + + internal unsafe string GetQueryString() + { + if (_nativeCookedUrl.pQueryString != null && _nativeCookedUrl.QueryStringLength > 0) + { + return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pQueryString, _nativeCookedUrl.QueryStringLength / 2); + } + return null; + } + } +} diff --git a/src/Microsoft.Net.Http.Server/NativeInterop/HttpApi.cs b/src/Microsoft.Net.Http.Server/NativeInterop/HttpApi.cs index b50c36a54e..10c736af80 100644 --- a/src/Microsoft.Net.Http.Server/NativeInterop/HttpApi.cs +++ b/src/Microsoft.Net.Http.Server/NativeInterop/HttpApi.cs @@ -868,205 +868,5 @@ namespace Microsoft.Net.Http.Server return supported; } } - - // Server API - - internal static void GetUnknownHeaders(IDictionary unknownHeaders, byte[] memoryBlob, - int requestOffset, IntPtr originalAddress) - { - // Return value. - fixed (byte* pMemoryBlob = memoryBlob) - { - HTTP_REQUEST* request = (HTTP_REQUEST*)(pMemoryBlob + requestOffset); - long fixup = pMemoryBlob - (byte*)originalAddress; - int index; - - // unknown headers - if (request->Headers.UnknownHeaderCount != 0) - { - HTTP_UNKNOWN_HEADER* pUnknownHeader = (HTTP_UNKNOWN_HEADER*)(fixup + (byte*)request->Headers.pUnknownHeaders); - for (index = 0; index < request->Headers.UnknownHeaderCount; index++) - { - // For unknown headers, when header value is empty, RawValueLength will be 0 and - // pRawValue will be null. - if (pUnknownHeader->pName != null && pUnknownHeader->NameLength > 0) - { - string headerName = HeaderEncoding.GetString(pUnknownHeader->pName + fixup, pUnknownHeader->NameLength); - string headerValue; - if (pUnknownHeader->pRawValue != null && pUnknownHeader->RawValueLength > 0) - { - headerValue = HeaderEncoding.GetString(pUnknownHeader->pRawValue + fixup, pUnknownHeader->RawValueLength); - } - else - { - headerValue = string.Empty; - } - // Note that Http.Sys currently collapses all headers of the same name to a single coma seperated string, - // so we can just call Set. - unknownHeaders[headerName] = headerValue; - } - pUnknownHeader++; - } - } - } - } - - private static string GetKnownHeader(HTTP_REQUEST* request, long fixup, int headerIndex) - { - string header = null; - - HTTP_KNOWN_HEADER* pKnownHeader = (&request->Headers.KnownHeaders) + headerIndex; - // For known headers, when header value is empty, RawValueLength will be 0 and - // pRawValue will point to empty string ("\0") - if (pKnownHeader->pRawValue != null) - { - header = HeaderEncoding.GetString(pKnownHeader->pRawValue + fixup, pKnownHeader->RawValueLength); - } - - return header; - } - - internal static string GetKnownHeader(byte[] memoryBlob, int requestOffset, IntPtr originalAddress, int headerIndex) - { - fixed (byte* pMemoryBlob = memoryBlob) - { - return GetKnownHeader( - (HTTP_REQUEST*)(pMemoryBlob + requestOffset), pMemoryBlob - (byte*)originalAddress, headerIndex); - } - } - - // This requires the HTTP_REQUEST to still be pinned in its original location. - internal static unsafe string GetVerb(HTTP_REQUEST* request) - { - string verb = null; - - if ((int)request->Verb > (int)HTTP_VERB.HttpVerbUnknown && (int)request->Verb < (int)HTTP_VERB.HttpVerbMaximum) - { - verb = HttpVerbs[(int)request->Verb]; - } - else if (request->Verb == HTTP_VERB.HttpVerbUnknown && request->pUnknownVerb != null) - { - verb = HeaderEncoding.GetString(request->pUnknownVerb, request->UnknownVerbLength); - } - - return verb; - } - - internal static uint GetChunks(byte[] memoryBlob, int requestOffset, IntPtr originalAddress, - ref int dataChunkIndex, ref uint dataChunkOffset, byte[] buffer, int offset, int size) - { - // Return value. - uint dataRead = 0; - fixed (byte* pMemoryBlob = memoryBlob) - { - HTTP_REQUEST* request = (HTTP_REQUEST*)(pMemoryBlob + requestOffset); - long fixup = pMemoryBlob - (byte*)originalAddress; - - if (request->EntityChunkCount > 0 && dataChunkIndex < request->EntityChunkCount && dataChunkIndex != -1) - { - HTTP_DATA_CHUNK* pDataChunk = (HTTP_DATA_CHUNK*)(fixup + (byte*)&request->pEntityChunks[dataChunkIndex]); - - fixed (byte* pReadBuffer = buffer) - { - byte* pTo = &pReadBuffer[offset]; - - while (dataChunkIndex < request->EntityChunkCount && dataRead < size) - { - if (dataChunkOffset >= pDataChunk->fromMemory.BufferLength) - { - dataChunkOffset = 0; - dataChunkIndex++; - pDataChunk++; - } - else - { - byte* pFrom = (byte*)pDataChunk->fromMemory.pBuffer + dataChunkOffset + fixup; - - uint bytesToRead = pDataChunk->fromMemory.BufferLength - (uint)dataChunkOffset; - if (bytesToRead > (uint)size) - { - bytesToRead = (uint)size; - } - for (uint i = 0; i < bytesToRead; i++) - { - *(pTo++) = *(pFrom++); - } - dataRead += bytesToRead; - dataChunkOffset += bytesToRead; - } - } - } - } - // we're finished. - if (dataChunkIndex == request->EntityChunkCount) - { - dataChunkIndex = -1; - } - } - - return dataRead; - } - - internal static SocketAddress GetRemoteEndPoint(byte[] memoryBlob, int requestOffset, IntPtr originalAddress) - { - fixed (byte* pMemoryBlob = memoryBlob) - { - HTTP_REQUEST* request = (HTTP_REQUEST*)(pMemoryBlob + requestOffset); - return GetEndPoint(memoryBlob, requestOffset, originalAddress, (byte*)request->Address.pRemoteAddress); - } - } - - internal static SocketAddress GetLocalEndPoint(byte[] memoryBlob, int requestOffset, IntPtr originalAddress) - { - fixed (byte* pMemoryBlob = memoryBlob) - { - HTTP_REQUEST* request = (HTTP_REQUEST*)(pMemoryBlob + requestOffset); - return GetEndPoint(memoryBlob, requestOffset, originalAddress, (byte*)request->Address.pLocalAddress); - } - } - - internal static SocketAddress GetEndPoint(byte[] memoryBlob, int requestOffset, IntPtr originalAddress, byte* source) - { - fixed (byte* pMemoryBlob = memoryBlob) - { - IntPtr address = source != null ? - (IntPtr)(pMemoryBlob + requestOffset - (byte*)originalAddress + source) : IntPtr.Zero; - return CopyOutAddress(address); - } - } - - private static SocketAddress CopyOutAddress(IntPtr address) - { - if (address != IntPtr.Zero) - { - ushort addressFamily = *((ushort*)address); - if (addressFamily == (ushort)AddressFamily.InterNetwork) - { - SocketAddress v4address = new SocketAddress(AddressFamily.InterNetwork, SocketAddress.IPv4AddressSize); - fixed (byte* pBuffer = v4address.Buffer) - { - for (int index = 2; index < SocketAddress.IPv4AddressSize; index++) - { - pBuffer[index] = ((byte*)address)[index]; - } - } - return v4address; - } - if (addressFamily == (ushort)AddressFamily.InterNetworkV6) - { - SocketAddress v6address = new SocketAddress(AddressFamily.InterNetworkV6, SocketAddress.IPv6AddressSize); - fixed (byte* pBuffer = v6address.Buffer) - { - for (int index = 2; index < SocketAddress.IPv6AddressSize; index++) - { - pBuffer[index] = ((byte*)address)[index]; - } - } - return v6address; - } - } - - return null; - } } } diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/NativeRequestContext.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/NativeRequestContext.cs index 5072342c42..32a93e8ae6 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/NativeRequestContext.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/NativeRequestContext.cs @@ -22,9 +22,12 @@ //------------------------------------------------------------------------------ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Threading; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.Extensions.Primitives; namespace Microsoft.Net.Http.Server { @@ -32,8 +35,8 @@ namespace Microsoft.Net.Http.Server { private const int DefaultBufferSize = 4096; private const int AlignmentPadding = 8; - private HttpApi.HTTP_REQUEST* _memoryBlob; - private IntPtr _originalBlobAddress; + private HttpApi.HTTP_REQUEST* _nativeRequest; + private IntPtr _originalBufferAddress; private byte[] _backingBuffer; private int _bufferAlignment; private SafeNativeOverlapped _nativeOverlapped; @@ -42,125 +45,73 @@ namespace Microsoft.Net.Http.Server internal NativeRequestContext(AsyncAcceptContext result) { _acceptResult = result; - HttpApi.HTTP_REQUEST* requestBlob = Allocate(0); - if (requestBlob == null) - { - GC.SuppressFinalize(this); - } - else - { - _memoryBlob = requestBlob; - } + AllocateNativeRequest(); } - internal SafeNativeOverlapped NativeOverlapped + internal SafeNativeOverlapped NativeOverlapped => _nativeOverlapped; + + internal HttpApi.HTTP_REQUEST* NativeRequest { get { - return _nativeOverlapped; + Debug.Assert(_nativeRequest != null || _backingBuffer == null, "native request accessed after ReleasePins()."); + return _nativeRequest; } } - internal HttpApi.HTTP_REQUEST* RequestBlob + private HttpApi.HTTP_REQUEST_V2* NativeRequestV2 { get { - Debug.Assert(_memoryBlob != null || _backingBuffer == null, "RequestBlob requested after ReleasePins()."); - return _memoryBlob; + Debug.Assert(_nativeRequest != null || _backingBuffer == null, "native request accessed after ReleasePins()."); + return (HttpApi.HTTP_REQUEST_V2*)_nativeRequest; } } - internal byte[] RequestBuffer + internal ulong RequestId + { + get { return NativeRequest->RequestId; } + set { NativeRequest->RequestId = value; } + } + + internal ulong ConnectionId => NativeRequest->ConnectionId; + + internal HttpApi.HTTP_VERB VerbId => NativeRequest->Verb; + + internal ulong UrlContext => NativeRequest->UrlContext; + + internal ushort UnknownHeaderCount => NativeRequest->Headers.UnknownHeaderCount; + + internal SslStatus SslStatus { get { - return _backingBuffer; + return NativeRequest->pSslInfo == null ? SslStatus.Insecure : + NativeRequest->pSslInfo->SslClientCertNegotiated == 0 ? SslStatus.NoClientCert : + SslStatus.ClientCert; } } internal uint Size { - get - { - return (uint)_backingBuffer.Length - AlignmentPadding; - } - } - - internal int BufferAlignment - { - get - { - return _bufferAlignment; - } - } - - internal IntPtr OriginalBlobAddress - { - get - { - HttpApi.HTTP_REQUEST* blob = _memoryBlob; - return blob == null ? _originalBlobAddress : (IntPtr)blob; - } + get { return (uint)_backingBuffer.Length - AlignmentPadding; } } // ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called // before an object (Request) which closes the RequestContext on demand is returned to the application. internal void ReleasePins() { - Debug.Assert(_memoryBlob != null || _backingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice."); - _originalBlobAddress = (IntPtr)_memoryBlob; - UnsetBlob(); - OnReleasePins(); - } - - private void OnReleasePins() - { - if (_nativeOverlapped != null) - { - SafeNativeOverlapped nativeOverlapped = _nativeOverlapped; - _nativeOverlapped = null; - nativeOverlapped.Dispose(); - } + Debug.Assert(_nativeRequest != null || _backingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice."); + _originalBufferAddress = (IntPtr)_nativeRequest; + _nativeRequest = null; + _nativeOverlapped?.Dispose(); + _nativeOverlapped = null; } public void Dispose() { - Debug.Assert(_memoryBlob == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins()."); - Dispose(true); - } - - protected void Dispose(bool disposing) - { - if (_nativeOverlapped != null) - { - Debug.Assert(!disposing, "AsyncRequestContext::Dispose()|Must call ReleasePins() before calling Dispose()."); - _nativeOverlapped.Dispose(); - } - } - - private void SetBlob(HttpApi.HTTP_REQUEST* requestBlob) - { - Debug.Assert(_memoryBlob != null || _backingBuffer == null, "RequestContextBase::Dispose()|SetBlob() called after ReleasePins()."); - if (requestBlob == null) - { - UnsetBlob(); - return; - } - - if (_memoryBlob == null) - { - GC.ReRegisterForFinalize(this); - } - _memoryBlob = requestBlob; - } - - private void UnsetBlob() - { - if (_memoryBlob != null) - { - GC.SuppressFinalize(this); - } - _memoryBlob = null; + Debug.Assert(_nativeRequest == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins()."); + _nativeOverlapped?.Dispose(); } private void SetBuffer(int size) @@ -170,34 +121,306 @@ namespace Microsoft.Net.Http.Server _backingBuffer = new byte[size + AlignmentPadding]; } - private HttpApi.HTTP_REQUEST* Allocate(uint size) + private void AllocateNativeRequest(uint? size = null) { // We can't reuse overlapped objects - if (_nativeOverlapped != null) - { - SafeNativeOverlapped nativeOverlapped = _nativeOverlapped; - _nativeOverlapped = null; - nativeOverlapped.Dispose(); - } + _nativeOverlapped?.Dispose(); - uint newSize = size != 0 ? size : RequestBuffer == null ? DefaultBufferSize : Size; + uint newSize = size.HasValue ? size.Value : _backingBuffer == null ? DefaultBufferSize : Size; SetBuffer(checked((int)newSize)); var boundHandle = _acceptResult.Server.RequestQueue.BoundHandle; _nativeOverlapped = new SafeNativeOverlapped(boundHandle, - boundHandle.AllocateNativeOverlapped(AsyncAcceptContext.IOCallback, _acceptResult, RequestBuffer)); + boundHandle.AllocateNativeOverlapped(AsyncAcceptContext.IOCallback, _acceptResult, _backingBuffer)); // HttpReceiveHttpRequest expects the request pointer to be 8-byte-aligned or it fails. On ARM // CLR creates buffers that are 4-byte-aligned so we need force 8-byte alignment. - var requestAddress = Marshal.UnsafeAddrOfPinnedArrayElement(RequestBuffer, 0); + var requestAddress = Marshal.UnsafeAddrOfPinnedArrayElement(_backingBuffer, 0); _bufferAlignment = (int)(requestAddress.ToInt64() & 0x07); - return (HttpApi.HTTP_REQUEST*)(requestAddress + _bufferAlignment); + _nativeRequest = (HttpApi.HTTP_REQUEST*)(requestAddress + _bufferAlignment); } - internal void Reset(ulong requestId, uint size) + internal void Reset(ulong requestId = 0, uint? size = null) { - SetBlob(Allocate(size)); - RequestBlob->RequestId = requestId; + Debug.Assert(_nativeRequest != null || _backingBuffer == null, "RequestContextBase::Dispose()|SetNativeRequest() called after ReleasePins()."); + AllocateNativeRequest(size); + RequestId = requestId; + } + + // These methods require the HTTP_REQUEST to still be pinned in its original location. + + internal string GetVerb() + { + var verb = NativeRequest->Verb; + if (verb > HttpApi.HTTP_VERB.HttpVerbUnknown && verb < HttpApi.HTTP_VERB.HttpVerbMaximum) + { + return HttpApi.HttpVerbs[(int)verb]; + } + else if (verb == HttpApi.HTTP_VERB.HttpVerbUnknown && NativeRequest->pUnknownVerb != null) + { + return HeaderEncoding.GetString(NativeRequest->pUnknownVerb, NativeRequest->UnknownVerbLength); + } + + return null; + } + + internal string GetRawUrl() + { + if (NativeRequest->pRawUrl != null && NativeRequest->RawUrlLength > 0) + { + return Marshal.PtrToStringAnsi((IntPtr)NativeRequest->pRawUrl, NativeRequest->RawUrlLength); + } + return null; + } + + internal CookedUrl GetCookedUrl() + { + return new CookedUrl(NativeRequest->CookedUrl); + } + + internal Version GetVersion() + { + var major = NativeRequest->Version.MajorVersion; + var minor = NativeRequest->Version.MinorVersion; + if (major == 1 && minor == 1) + { + return Constants.V1_1; + } + else if (major == 1 && minor == 0) + { + return Constants.V1_0; + } + return new Version(major, minor); + } + + internal bool CheckAuthenticated() + { + var requestInfo = NativeRequestV2->pRequestInfo; + var infoCount = NativeRequestV2->RequestInfoCount; + + for (int i = 0; i < infoCount; i++) + { + var info = &requestInfo[i]; + if (info != null + && info->InfoType == HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth + && info->pInfo->AuthStatus == HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess) + { + return true; + } + } + return false; + } + + internal ClaimsPrincipal GetUser() + { + var requestInfo = NativeRequestV2->pRequestInfo; + var infoCount = NativeRequestV2->RequestInfoCount; + + for (int i = 0; i < infoCount; i++) + { + var info = &requestInfo[i]; + if (info != null + && info->InfoType == HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth + && info->pInfo->AuthStatus == HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess) + { + return new WindowsPrincipal(new WindowsIdentity(info->pInfo->AccessToken, + GetAuthTypeFromRequest(info->pInfo->AuthType).ToString())); + } + } + return new ClaimsPrincipal(new ClaimsIdentity()); // Anonymous / !IsAuthenticated + } + + private static AuthenticationSchemes GetAuthTypeFromRequest(HttpApi.HTTP_REQUEST_AUTH_TYPE input) + { + switch (input) + { + case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeBasic: + return AuthenticationSchemes.Basic; + // case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeDigest: + // return AuthenticationSchemes.Digest; + case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeNTLM: + return AuthenticationSchemes.NTLM; + case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeNegotiate: + return AuthenticationSchemes.Negotiate; + case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeKerberos: + return AuthenticationSchemes.Kerberos; + default: + throw new NotImplementedException(input.ToString()); + } + } + + // These methods are for accessing the request structure after it has been unpinned. They need to adjust addresses + // in case GC has moved the original object. + + internal string GetKnownHeader(HttpSysRequestHeader header) + { + fixed (byte* pMemoryBlob = _backingBuffer) + { + var request = (HttpApi.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); + long fixup = pMemoryBlob - (byte*)_originalBufferAddress; + int headerIndex = (int)header; + string value = null; + + HttpApi.HTTP_KNOWN_HEADER* pKnownHeader = (&request->Headers.KnownHeaders) + headerIndex; + // For known headers, when header value is empty, RawValueLength will be 0 and + // pRawValue will point to empty string ("\0") + if (pKnownHeader->pRawValue != null) + { + value = HeaderEncoding.GetString(pKnownHeader->pRawValue + fixup, pKnownHeader->RawValueLength); + } + + return value; + } + } + + internal void GetUnknownHeaders(IDictionary unknownHeaders) + { + // Return value. + fixed (byte* pMemoryBlob = _backingBuffer) + { + var request = (HttpApi.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); + long fixup = pMemoryBlob - (byte*)_originalBufferAddress; + int index; + + // unknown headers + if (request->Headers.UnknownHeaderCount != 0) + { + var pUnknownHeader = (HttpApi.HTTP_UNKNOWN_HEADER*)(fixup + (byte*)request->Headers.pUnknownHeaders); + for (index = 0; index < request->Headers.UnknownHeaderCount; index++) + { + // For unknown headers, when header value is empty, RawValueLength will be 0 and + // pRawValue will be null. + if (pUnknownHeader->pName != null && pUnknownHeader->NameLength > 0) + { + var headerName = HeaderEncoding.GetString(pUnknownHeader->pName + fixup, pUnknownHeader->NameLength); + string headerValue; + if (pUnknownHeader->pRawValue != null && pUnknownHeader->RawValueLength > 0) + { + headerValue = HeaderEncoding.GetString(pUnknownHeader->pRawValue + fixup, pUnknownHeader->RawValueLength); + } + else + { + headerValue = string.Empty; + } + // Note that Http.Sys currently collapses all headers of the same name to a single coma separated string, + // so we can just call Set. + unknownHeaders[headerName] = headerValue; + } + pUnknownHeader++; + } + } + } + } + + internal SocketAddress GetRemoteEndPoint() + { + return GetEndPoint(localEndpoint: false); + } + + internal SocketAddress GetLocalEndPoint() + { + return GetEndPoint(localEndpoint: true); + } + + private SocketAddress GetEndPoint(bool localEndpoint) + { + fixed (byte* pMemoryBlob = _backingBuffer) + { + var request = (HttpApi.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); + var source = localEndpoint ? (byte*)request->Address.pLocalAddress : (byte*)request->Address.pRemoteAddress; + + if (source == null) + { + return null; + } + var address = (IntPtr)(pMemoryBlob + _bufferAlignment - (byte*)_originalBufferAddress + source); + return CopyOutAddress(address); + } + } + + private static SocketAddress CopyOutAddress(IntPtr address) + { + ushort addressFamily = *((ushort*)address); + if (addressFamily == (ushort)AddressFamily.InterNetwork) + { + var v4address = new SocketAddress(AddressFamily.InterNetwork, SocketAddress.IPv4AddressSize); + fixed (byte* pBuffer = v4address.Buffer) + { + for (int index = 2; index < SocketAddress.IPv4AddressSize; index++) + { + pBuffer[index] = ((byte*)address)[index]; + } + } + return v4address; + } + if (addressFamily == (ushort)AddressFamily.InterNetworkV6) + { + var v6address = new SocketAddress(AddressFamily.InterNetworkV6, SocketAddress.IPv6AddressSize); + fixed (byte* pBuffer = v6address.Buffer) + { + for (int index = 2; index < SocketAddress.IPv6AddressSize; index++) + { + pBuffer[index] = ((byte*)address)[index]; + } + } + return v6address; + } + + return null; + } + + internal uint GetChunks(ref int dataChunkIndex, ref uint dataChunkOffset, byte[] buffer, int offset, int size) + { + // Return value. + uint dataRead = 0; + fixed (byte* pMemoryBlob = _backingBuffer) + { + var request = (HttpApi.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment); + long fixup = pMemoryBlob - (byte*)_originalBufferAddress; + + if (request->EntityChunkCount > 0 && dataChunkIndex < request->EntityChunkCount && dataChunkIndex != -1) + { + var pDataChunk = (HttpApi.HTTP_DATA_CHUNK*)(fixup + (byte*)&request->pEntityChunks[dataChunkIndex]); + + fixed (byte* pReadBuffer = buffer) + { + byte* pTo = &pReadBuffer[offset]; + + while (dataChunkIndex < request->EntityChunkCount && dataRead < size) + { + if (dataChunkOffset >= pDataChunk->fromMemory.BufferLength) + { + dataChunkOffset = 0; + dataChunkIndex++; + pDataChunk++; + } + else + { + byte* pFrom = (byte*)pDataChunk->fromMemory.pBuffer + dataChunkOffset + fixup; + + uint bytesToRead = pDataChunk->fromMemory.BufferLength - (uint)dataChunkOffset; + if (bytesToRead > (uint)size) + { + bytesToRead = (uint)size; + } + for (uint i = 0; i < bytesToRead; i++) + { + *(pTo++) = *(pFrom++); + } + dataRead += bytesToRead; + dataChunkOffset += bytesToRead; + } + } + } + } + // we're finished. + if (dataChunkIndex == request->EntityChunkCount) + { + dataChunkIndex = -1; + } + } + + return dataRead; } } } diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs index 924b7eef2c..d7fce4acb0 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs @@ -25,7 +25,6 @@ using System; using System.Globalization; using System.IO; using System.Net; -using System.Runtime.InteropServices; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -51,46 +50,27 @@ namespace Microsoft.Net.Http.Server private bool _isDisposed = false; - internal unsafe Request(RequestContext requestContext, NativeRequestContext memoryBlob) + internal Request(RequestContext requestContext, NativeRequestContext nativeRequestContext) { // TODO: Verbose log RequestContext = requestContext; - _nativeRequestContext = memoryBlob; + _nativeRequestContext = nativeRequestContext; _contentBoundaryType = BoundaryType.None; - // Set up some of these now to avoid refcounting on memory blob later. - RequestId = memoryBlob.RequestBlob->RequestId; - UConnectionId = memoryBlob.RequestBlob->ConnectionId; - SslStatus = memoryBlob.RequestBlob->pSslInfo == null ? SslStatus.Insecure : - memoryBlob.RequestBlob->pSslInfo->SslClientCertNegotiated == 0 ? SslStatus.NoClientCert : - SslStatus.ClientCert; + RequestId = nativeRequestContext.RequestId; + UConnectionId = nativeRequestContext.ConnectionId; + SslStatus = nativeRequestContext.SslStatus; - KnownMethod = memoryBlob.RequestBlob->Verb; - Method = HttpApi.GetVerb(memoryBlob.RequestBlob); + KnownMethod = nativeRequestContext.VerbId; + Method = _nativeRequestContext.GetVerb(); - if (memoryBlob.RequestBlob->pRawUrl != null && memoryBlob.RequestBlob->RawUrlLength > 0) - { - RawUrl = Marshal.PtrToStringAnsi((IntPtr)memoryBlob.RequestBlob->pRawUrl, memoryBlob.RequestBlob->RawUrlLength); - } + RawUrl = nativeRequestContext.GetRawUrl(); - HttpApi.HTTP_COOKED_URL cookedUrl = memoryBlob.RequestBlob->CookedUrl; - if (cookedUrl.pHost != null && cookedUrl.HostLength > 0) - { - // TODO: Unused - // _cookedUrlHost = Marshal.PtrToStringUni((IntPtr)cookedUrl.pHost, cookedUrl.HostLength / 2); - } - var cookedUrlPath = string.Empty; - if (cookedUrl.pAbsPath != null && cookedUrl.AbsPathLength > 0) - { - cookedUrlPath = Marshal.PtrToStringUni((IntPtr)cookedUrl.pAbsPath, cookedUrl.AbsPathLength / 2); - } - QueryString = string.Empty; - if (cookedUrl.pQueryString != null && cookedUrl.QueryStringLength > 0) - { - QueryString = Marshal.PtrToStringUni((IntPtr)cookedUrl.pQueryString, cookedUrl.QueryStringLength / 2); - } + var cookedUrl = nativeRequestContext.GetCookedUrl(); + var cookedUrlPath = cookedUrl.GetAbsPath() ?? string.Empty; + QueryString = cookedUrl.GetQueryString() ?? string.Empty; - var prefix = requestContext.Server.Settings.UrlPrefixes.GetPrefix((int)memoryBlob.RequestBlob->UrlContext); + var prefix = requestContext.Server.Settings.UrlPrefixes.GetPrefix((int)nativeRequestContext.UrlContext); var originalPath = RequestUriBuilder.GetRequestPath(RawUrl, cookedUrlPath, RequestContext.Logger); // 'OPTIONS * HTTP/1.1' @@ -114,25 +94,11 @@ namespace Microsoft.Net.Http.Server Path = originalPath.Substring(prefix.Path.Length - 1); } - int major = memoryBlob.RequestBlob->Version.MajorVersion; - int minor = memoryBlob.RequestBlob->Version.MinorVersion; - if (major == 1 && minor == 1) - { - ProtocolVersion = Constants.V1_1; - } - else if (major == 1 && minor == 0) - { - ProtocolVersion = Constants.V1_0; - } - else - { - ProtocolVersion = new Version(major, minor); - } + ProtocolVersion = _nativeRequestContext.GetVersion(); Headers = new HeaderCollection(new RequestHeaders(_nativeRequestContext)); - var requestV2 = (HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob; - User = AuthenticationManager.GetUser(requestV2->pRequestInfo, requestV2->RequestInfoCount); + User = nativeRequestContext.GetUser(); // GetTlsTokenBindingInfo(); TODO: https://github.com/aspnet/WebListener/issues/231 @@ -141,33 +107,6 @@ namespace Microsoft.Net.Http.Server // TODO: Verbose log parameters } - internal byte[] RequestBuffer - { - get - { - CheckDisposed(); - return _nativeRequestContext.RequestBuffer; - } - } - - internal int BufferAlignment - { - get - { - CheckDisposed(); - return _nativeRequestContext.BufferAlignment; - } - } - - internal IntPtr OriginalBlobAddress - { - get - { - CheckDisposed(); - return _nativeRequestContext.OriginalBlobAddress; - } - } - internal ulong UConnectionId { get; } // No ulongs in public APIs... @@ -260,7 +199,7 @@ namespace Microsoft.Net.Http.Server { if (_remoteEndPoint == null) { - _remoteEndPoint = HttpApi.GetRemoteEndPoint(RequestBuffer, BufferAlignment, OriginalBlobAddress); + _remoteEndPoint = _nativeRequestContext.GetRemoteEndPoint(); } return _remoteEndPoint; @@ -273,7 +212,7 @@ namespace Microsoft.Net.Http.Server { if (_localEndPoint == null) { - _localEndPoint = HttpApi.GetLocalEndPoint(RequestBuffer, BufferAlignment, OriginalBlobAddress); + _localEndPoint = _nativeRequestContext.GetLocalEndPoint(); } return _localEndPoint; @@ -356,6 +295,7 @@ namespace Microsoft.Net.Http.Server // Key: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING // Value: "iexplore.exe"=dword:00000001 // TODO: https://github.com/aspnet/WebListener/issues/231 + // TODO: https://github.com/aspnet/WebListener/issues/204 Move to NativeRequestContext /* private unsafe void GetTlsTokenBindingInfo() { @@ -371,6 +311,11 @@ namespace Microsoft.Net.Http.Server } } */ + internal uint GetChunks(ref int dataChunkIndex, ref uint dataChunkOffset, byte[] buffer, int offset, int size) + { + return _nativeRequestContext.GetChunks(ref dataChunkIndex, ref dataChunkOffset, buffer, offset, size); + } + // should only be called from RequestContext internal void Dispose() { diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestHeaders.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestHeaders.cs index a1c526d68b..2b3f1dcafb 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestHeaders.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestHeaders.cs @@ -85,14 +85,12 @@ namespace Microsoft.Net.Http.Server private string GetKnownHeader(HttpSysRequestHeader header) { - return HttpApi.GetKnownHeader(_requestMemoryBlob.RequestBuffer, - _requestMemoryBlob.BufferAlignment, _requestMemoryBlob.OriginalBlobAddress, (int)header); + return _requestMemoryBlob.GetKnownHeader(header); } private void GetUnknownHeaders(IDictionary extra) { - HttpApi.GetUnknownHeaders(extra, _requestMemoryBlob.RequestBuffer, - _requestMemoryBlob.BufferAlignment, _requestMemoryBlob.OriginalBlobAddress); + _requestMemoryBlob.GetUnknownHeaders(extra); } void IDictionary.Add(string key, StringValues value) diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestStream.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestStream.cs index 16324aa487..9ad00ca84d 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestStream.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestStream.cs @@ -154,9 +154,7 @@ namespace Microsoft.Net.Http.Server if (_dataChunkIndex != -1) { - dataRead = HttpApi.GetChunks(_requestContext.Request.RequestBuffer, - _requestContext.Request.BufferAlignment, _requestContext.Request.OriginalBlobAddress, - ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); + dataRead = _requestContext.Request.GetChunks(ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); } if (_dataChunkIndex == -1 && dataRead < size) @@ -232,8 +230,7 @@ namespace Microsoft.Net.Http.Server uint dataRead = 0; if (_dataChunkIndex != -1) { - dataRead = HttpApi.GetChunks(_requestContext.Request.RequestBuffer, _requestContext.Request.BufferAlignment, - _requestContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); + dataRead = _requestContext.Request.GetChunks(ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); if (_dataChunkIndex != -1 && dataRead == size) { @@ -350,8 +347,7 @@ namespace Microsoft.Net.Http.Server uint dataRead = 0; if (_dataChunkIndex != -1) { - dataRead = HttpApi.GetChunks(_requestContext.Request.RequestBuffer, _requestContext.Request.BufferAlignment, - _requestContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); + dataRead = _requestContext.Request.GetChunks(ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); if (_dataChunkIndex != -1 && dataRead == size) { UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead); diff --git a/src/Microsoft.Net.Http.Server/WebListener.cs b/src/Microsoft.Net.Http.Server/WebListener.cs index cb72439299..2e2f0cb1f8 100644 --- a/src/Microsoft.Net.Http.Server/WebListener.cs +++ b/src/Microsoft.Net.Http.Server/WebListener.cs @@ -319,9 +319,9 @@ namespace Microsoft.Net.Http.Server internal unsafe bool ValidateRequest(NativeRequestContext requestMemory) { // Block potential DOS attacks - if (requestMemory.RequestBlob->Headers.UnknownHeaderCount > UnknownHeaderLimit) + if (requestMemory.UnknownHeaderCount > UnknownHeaderLimit) { - SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.BadRequest, authChallenges: null); + SendError(requestMemory.RequestId, HttpStatusCode.BadRequest, authChallenges: null); return false; } return true; @@ -329,10 +329,9 @@ namespace Microsoft.Net.Http.Server internal unsafe bool ValidateAuth(NativeRequestContext requestMemory) { - var requestV2 = (HttpApi.HTTP_REQUEST_V2*)requestMemory.RequestBlob; - if (!Settings.Authentication.AllowAnonymous && !AuthenticationManager.CheckAuthenticated(requestV2->pRequestInfo)) + if (!Settings.Authentication.AllowAnonymous && !requestMemory.CheckAuthenticated()) { - SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.Unauthorized, + SendError(requestMemory.RequestId, HttpStatusCode.Unauthorized, AuthenticationManager.GenerateChallenges(Settings.Authentication.Schemes)); return false; } diff --git a/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/ResponseCachingTests.cs b/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/ResponseCachingTests.cs index ff48f77414..0075622980 100644 --- a/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/ResponseCachingTests.cs +++ b/test/Microsoft.AspNetCore.Server.WebListener.FunctionalTests/ResponseCachingTests.cs @@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests } } - [Theory] + [Theory(Skip = "https://github.com/aspnet/WebListener/issues/210")] [InlineData("Set-cookie")] [InlineData("vary")] [InlineData("pragma")] @@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests } } - [Fact] + [Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] public async Task Caching_ExpiresWithoutPublic_NotCached() { var requestCount = 1;