// 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.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Microsoft.Net.Http.Server { public sealed class Request { private RequestContext _requestContext; private NativeRequestContext _nativeRequestContext; private ulong _requestId; private ulong _connectionId; private ulong _contextId; private SslStatus _sslStatus; private string _httpMethod; private Version _httpVersion; // private Uri _requestUri; private string _rawUrl; private string _cookedUrlHost; private string _cookedUrlPath; private string _cookedUrlQuery; private string _pathBase; private string _path; private X509Certificate2 _clientCert; private byte[] _providedTokenBindingId; private byte[] _referredTokenBindingId; private HeaderCollection _headers; private BoundaryType _contentBoundaryType; private long? _contentLength; private Stream _nativeStream; private SocketAddress _localEndPoint; private SocketAddress _remoteEndPoint; private ClaimsPrincipal _user; private bool _isDisposed = false; internal unsafe Request(RequestContext httpContext, NativeRequestContext memoryBlob) { // TODO: Verbose log _requestContext = httpContext; _nativeRequestContext = memoryBlob; _contentBoundaryType = BoundaryType.None; // Set up some of these now to avoid refcounting on memory blob later. _requestId = memoryBlob.RequestBlob->RequestId; _connectionId = memoryBlob.RequestBlob->ConnectionId; _contextId = memoryBlob.RequestBlob->UrlContext; _sslStatus = memoryBlob.RequestBlob->pSslInfo == null ? SslStatus.Insecure : memoryBlob.RequestBlob->pSslInfo->SslClientCertNegotiated == 0 ? SslStatus.NoClientCert : SslStatus.ClientCert; if (memoryBlob.RequestBlob->pRawUrl != null && memoryBlob.RequestBlob->RawUrlLength > 0) { _rawUrl = Marshal.PtrToStringAnsi((IntPtr)memoryBlob.RequestBlob->pRawUrl, memoryBlob.RequestBlob->RawUrlLength); } UnsafeNclNativeMethods.HttpApi.HTTP_COOKED_URL cookedUrl = memoryBlob.RequestBlob->CookedUrl; if (cookedUrl.pHost != null && cookedUrl.HostLength > 0) { _cookedUrlHost = Marshal.PtrToStringUni((IntPtr)cookedUrl.pHost, cookedUrl.HostLength / 2); } if (cookedUrl.pAbsPath != null && cookedUrl.AbsPathLength > 0) { _cookedUrlPath = Marshal.PtrToStringUni((IntPtr)cookedUrl.pAbsPath, cookedUrl.AbsPathLength / 2); } if (cookedUrl.pQueryString != null && cookedUrl.QueryStringLength > 0) { _cookedUrlQuery = Marshal.PtrToStringUni((IntPtr)cookedUrl.pQueryString, cookedUrl.QueryStringLength / 2); } UrlPrefix prefix = httpContext.Server.UrlPrefixes.GetPrefix((int)_contextId); string originalPath = RequestPath; // These paths are both unescaped already. if (originalPath.Length == prefix.Path.Length - 1) { // They matched exactly except for the trailing slash. _pathBase = originalPath; _path = string.Empty; } else { // url: /base/path, prefix: /base/, base: /base, path: /path // url: /, prefix: /, base: , path: / _pathBase = originalPath.Substring(0, prefix.Path.Length - 1); _path = originalPath.Substring(prefix.Path.Length - 1); } int major = memoryBlob.RequestBlob->Version.MajorVersion; int minor = memoryBlob.RequestBlob->Version.MinorVersion; if (major == 1 && minor == 1) { _httpVersion = Constants.V1_1; } else if (major == 1 && minor == 0) { _httpVersion = Constants.V1_0; } else { _httpVersion = new Version(major, minor); } _httpMethod = UnsafeNclNativeMethods.HttpApi.GetVerb(RequestBuffer, OriginalBlobAddress); _headers = new HeaderCollection(new RequestHeaders(_nativeRequestContext)); var requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob; _user = AuthenticationManager.GetUser(requestV2->pRequestInfo, requestV2->RequestInfoCount); GetTlsTokenBindingInfo(); // TODO: Verbose log parameters } internal SslStatus SslStatus { get { return _sslStatus; } } internal ulong ConnectionId { get { return _connectionId; } } internal ulong ContextId { get { return _contextId; } } internal RequestContext RequestContext { get { return _requestContext; } } internal byte[] RequestBuffer { get { CheckDisposed(); return _nativeRequestContext.RequestBuffer; } } internal IntPtr OriginalBlobAddress { get { CheckDisposed(); return _nativeRequestContext.OriginalBlobAddress; } } // With the leading ?, if any public string QueryString { get { return _cookedUrlQuery ?? string.Empty; } set { _cookedUrlQuery = value; } } internal ulong RequestId { get { return _requestId; } } public long? ContentLength { get { if (_contentBoundaryType == BoundaryType.None) { string transferEncoding = Headers[HttpKnownHeaderNames.TransferEncoding]; if (string.Equals("chunked", transferEncoding?.Trim(), StringComparison.OrdinalIgnoreCase)) { _contentBoundaryType = BoundaryType.Chunked; } else { string length = Headers[HttpKnownHeaderNames.ContentLength]; long value; if (length != null && long.TryParse(length.Trim(), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out value)) { _contentBoundaryType = BoundaryType.ContentLength; _contentLength = value; } else { _contentBoundaryType = BoundaryType.Invalid; } } } return _contentLength; } } public HeaderCollection Headers { get { return _headers; } } public string Method { get { return _httpMethod; } } public bool IsHeadMethod { get { return string.Equals(_httpMethod, "HEAD", StringComparison.OrdinalIgnoreCase); } } public Stream Body { get { if (_nativeStream == null) { _nativeStream = HasEntityBody ? new RequestStream(RequestContext) : Stream.Null; } return _nativeStream; } } public string PathBase { get { return _pathBase; } } public string Path { get { return _path; } } public bool IsSecureConnection { get { return _sslStatus != SslStatus.Insecure; } } /* internal string RawUrl { get { return _rawUrl; } } */ public Version ProtocolVersion { get { return _httpVersion; } } public bool HasEntityBody { get { // accessing the ContentLength property delay creates _contentBoundaryType return (ContentLength.HasValue && ContentLength.Value > 0 && _contentBoundaryType == BoundaryType.ContentLength) || _contentBoundaryType == BoundaryType.Chunked; } } private SocketAddress RemoteEndPoint { get { if (_remoteEndPoint == null) { _remoteEndPoint = UnsafeNclNativeMethods.HttpApi.GetRemoteEndPoint(RequestBuffer, OriginalBlobAddress); } return _remoteEndPoint; } } private SocketAddress LocalEndPoint { get { if (_localEndPoint == null) { _localEndPoint = UnsafeNclNativeMethods.HttpApi.GetLocalEndPoint(RequestBuffer, OriginalBlobAddress); } return _localEndPoint; } } public IPAddress RemoteIpAddress { get { return RemoteEndPoint.GetIPAddress(); } } public IPAddress LocalIpAddress { get { return LocalEndPoint.GetIPAddress(); } } public int RemotePort { get { return RemoteEndPoint.GetPort(); } } public int LocalPort { get { return LocalEndPoint.GetPort(); } } public string Scheme { get { return IsSecureConnection ? Constants.HttpsScheme : Constants.HttpScheme; } } internal string RequestPath { get { return RequestUriBuilder.GetRequestPath(_rawUrl, _cookedUrlPath, RequestContext.Logger); } } internal bool IsUpgradable { get { // HTTP.Sys allows you to upgrade anything to opaque unless content-length > 0 or chunked are specified. return !HasEntityBody; } } public string ContentType { get { return Headers[HttpKnownHeaderNames.ContentType]; } } internal ClaimsPrincipal User { get { return _user; } } internal UnsafeNclNativeMethods.HttpApi.HTTP_VERB GetKnownMethod() { return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress); } // Populates the client certificate. The result may be null if there is no client cert. // TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to // enable this, but it's unclear what Http.Sys would do. public async Task GetClientCertificateAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (SslStatus == SslStatus.Insecure) { // Non-SSL return null; } // TODO: Verbose log if (_clientCert != null) { return _clientCert; } cancellationToken.ThrowIfCancellationRequested(); ClientCertLoader certLoader = new ClientCertLoader(RequestContext, cancellationToken); try { await certLoader.LoadClientCertificateAsync().SupressContext(); // Populate the environment. if (certLoader.ClientCert != null) { _clientCert = certLoader.ClientCert; } // TODO: Expose errors and exceptions? } catch (Exception) { if (certLoader != null) { certLoader.Dispose(); } throw; } return _clientCert; } public byte[] GetProvidedTokenBindingId() { return _providedTokenBindingId; } public byte[] GetReferredTokenBindingId() { return _referredTokenBindingId; } // Only call from the constructor so we can directly access the native request blob. // This requires Windows 10 and the following reg key: // Set Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP\Parameters to Value: EnableSslTokenBinding = 1 [DWORD] // Then for IE to work you need to set these: // Key: HKLM\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING // Value: "iexplore.exe"=dword:0x00000001 // Key: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING // Value: "iexplore.exe"=dword:00000001 private unsafe void GetTlsTokenBindingInfo() { var nativeRequest = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)_nativeRequestContext.RequestBlob; for (int i = 0; i < nativeRequest->RequestInfoCount; i++) { var pThisInfo = &nativeRequest->pRequestInfo[i]; if (pThisInfo->InfoType == UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeSslTokenBinding) { var pTokenBindingInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_TOKEN_BINDING_INFO*)pThisInfo->pInfo; _providedTokenBindingId = TokenBindingUtil.GetProvidedTokenIdFromBindingInfo(pTokenBindingInfo, out _referredTokenBindingId); } } } // Use this to save the blob from dispose if this object was never used (never given to a user) and is about to be // disposed. internal void DetachBlob(NativeRequestContext memoryBlob) { if (memoryBlob != null && (object)memoryBlob == (object)_nativeRequestContext) { _nativeRequestContext = null; } } // Finalizes ownership of the memory blob. DetachBlob can't be called after this. internal void ReleasePins() { _nativeRequestContext.ReleasePins(); } // should only be called from RequestContext internal void Dispose() { // TODO: Verbose log _isDisposed = true; NativeRequestContext memoryBlob = _nativeRequestContext; if (memoryBlob != null) { memoryBlob.Dispose(); _nativeRequestContext = null; } if (_nativeStream != null) { _nativeStream.Dispose(); } } internal void CheckDisposed() { if (_isDisposed) { throw new ObjectDisposedException(this.GetType().FullName); } } internal void SwitchToOpaqueMode() { if (_nativeStream == null || _nativeStream == Stream.Null) { _nativeStream = new RequestStream(RequestContext); } } } }