├── korebuild-lock.txt ├── korebuild.json ├── samples ├── SelfHostServer │ ├── App.config │ ├── Properties │ │ └── launchSettings.json │ ├── SelfHostServer.csproj │ ├── Public │ │ └── 1kb.txt │ └── Startup.cs ├── TestClient │ ├── App.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TestClient.csproj │ └── Program.cs └── HotAddSample │ ├── Properties │ └── launchSettings.json │ ├── HotAddSample.csproj │ └── Startup.cs ├── CONTRIBUTING.md ├── NuGet.config ├── run.cmd ├── src ├── Directory.Build.props └── Microsoft.AspNetCore.Server.HttpSys │ ├── HttpSysDefaults.cs │ ├── NativeInterop │ ├── IntPtrHelper.cs │ ├── ComNetOS.cs │ ├── HttpRequestQueueV2Handle.cs │ ├── ServerSession.cs │ ├── HttpServerSessionHandle.cs │ ├── TokenBindingUtil.cs │ ├── HttpSysSettings.cs │ ├── UrlGroup.cs │ ├── RequestQueue.cs │ └── DisconnectListener.cs │ ├── RequestProcessing │ ├── BoundaryType.cs │ ├── HttpReasonPhrase.cs │ ├── OpaqueStream.cs │ └── RequestStreamAsyncResult.cs │ ├── Properties │ ├── AssemblyInfo.cs │ └── Resources.Designer.cs │ ├── AuthenticationSchemes.cs │ ├── Http503VerbosityLevel .cs │ ├── HttpSysException.cs │ ├── Microsoft.AspNetCore.Server.HttpSys.csproj │ ├── AuthenticationHandler.cs │ ├── WebHostBuilderHttpSysExtensions.cs │ ├── ValidationHelper.cs │ ├── LogHelper.cs │ ├── ResponseStream.cs │ ├── StandardFeatureCollection.cs │ ├── Helpers.cs │ ├── UrlPrefixCollection.cs │ ├── AuthenticationManager.cs │ └── UrlPrefix.cs ├── NuGetPackageVerifier.json ├── .vsts-pipelines └── builds │ ├── ci-internal.yml │ └── ci-public.yml ├── .appveyor.yml ├── test ├── Microsoft.AspNetCore.Server.HttpSys.Tests │ ├── Microsoft.AspNetCore.Server.HttpSys.Tests.csproj │ └── UrlPrefixTests.cs ├── Microsoft.AspNetCore.Server.HttpSys.FunctionalTests │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj │ ├── DummyApplication.cs │ ├── OSDontSkipConditionAttribute.cs │ ├── RequestHeaderTests.cs │ ├── Listener │ │ ├── Utilities.cs │ │ ├── RequestHeaderTests.cs │ │ └── RequestTests.cs │ ├── MessagePumpTests.cs │ └── HttpsTests.cs └── Directory.Build.props ├── shared └── Microsoft.AspNetCore.HttpSys.Sources │ ├── RequestProcessing │ ├── SslStatus.cs │ ├── HeaderEncoding.cs │ ├── HeaderParser.cs │ ├── HttpKnownHeaderNames.cs │ └── RawUrlHelper.cs │ ├── NativeInterop │ ├── NclUtilities.cs │ ├── SafeLocalMemHandle.cs │ ├── HeapAllocHandle.cs │ ├── SafeLocalFreeChannelBinding.cs │ ├── CookedUrl.cs │ ├── HttpSysResponseHeader.cs │ ├── SafeNativeOverlapped.cs │ ├── HttpSysRequestHeader.cs │ └── UnsafeNativeMethods.cs │ └── Constants.cs ├── .gitignore ├── README.md ├── .travis.yml ├── Directory.Build.targets ├── Directory.Build.props ├── version.props ├── .gitattributes └── run.ps1 /korebuild-lock.txt: -------------------------------------------------------------------------------- 1 | version:3.0.0-alpha1-20181011.3 2 | commithash:e7569d931e994629267ab2646e9926140962b4ac 3 | -------------------------------------------------------------------------------- /korebuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 3 | "channel": "master" 4 | } 5 | -------------------------------------------------------------------------------- /samples/SelfHostServer/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ====== 3 | 4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. 5 | -------------------------------------------------------------------------------- /samples/TestClient/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /run.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" 3 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NuGetPackageVerifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "adx-nonshipping": { 3 | "rules": [], 4 | "packages": { 5 | "Microsoft.AspNetCore.HttpSys.Sources": {} 6 | } 7 | }, 8 | "Default": { 9 | "rules": [ 10 | "DefaultCompositeRule" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-internal.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | resources: 6 | repositories: 7 | - repository: buildtools 8 | type: git 9 | name: aspnet-BuildTools 10 | ref: refs/heads/master 11 | 12 | phases: 13 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools 14 | -------------------------------------------------------------------------------- /samples/SelfHostServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SelfHostServer": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "http://localhost:5000/", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /samples/HotAddSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "HotAddSample": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "http://localhost:12345", 7 | "environmentVariables": { 8 | "ASPNETCORE_URLS": "http://localhost:12345", 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf true 3 | branches: 4 | only: 5 | - master 6 | - /^release\/.*$/ 7 | - /^(.*\/)?ci-.*$/ 8 | build_script: 9 | - ps: .\run.ps1 default-build 10 | clone_depth: 1 11 | environment: 12 | global: 13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 15 | test: 'off' 16 | deploy: 'off' 17 | os: Visual Studio 2017 18 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-public.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | # See https://github.com/aspnet/BuildTools 6 | resources: 7 | repositories: 8 | - repository: buildtools 9 | type: github 10 | endpoint: DotNet-Bot GitHub Connection 11 | name: aspnet/BuildTools 12 | ref: refs/heads/master 13 | 14 | phases: 15 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools 16 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.Tests/Microsoft.AspNetCore.Server.HttpSys.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/RequestProcessing/SslStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.HttpSys.Internal 5 | { 6 | internal enum SslStatus : byte 7 | { 8 | Insecure, 9 | NoClientCert, 10 | ClientCert 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | _ReSharper.*/ 6 | packages/ 7 | artifacts/ 8 | PublishProfiles/ 9 | *.user 10 | *.suo 11 | *.cache 12 | *.docstates 13 | _ReSharper.* 14 | nuget.exe 15 | *net45.csproj 16 | *net451.csproj 17 | *k10.csproj 18 | *.psess 19 | *.vsp 20 | *.pidb 21 | *.userprefs 22 | *DS_Store 23 | *.ncrunchsolution 24 | *.*sdf 25 | *.ipch 26 | *.sln.ide 27 | project.lock.json 28 | /.vs 29 | .vscode/ 30 | .build/ 31 | .testPublish/ 32 | global.json 33 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Testing.xunit; 5 | using Xunit; 6 | 7 | [assembly: OSSkipCondition(OperatingSystems.MacOSX)] 8 | [assembly: OSSkipCondition(OperatingSystems.Linux)] 9 | [assembly: CollectionBehavior(DisableTestParallelization = true)] -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/HttpSysDefaults.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.Server.HttpSys 5 | { 6 | public static class HttpSysDefaults 7 | { 8 | /// 9 | /// The name of the authentication scheme used. 10 | /// 11 | public static readonly string AuthenticationScheme = "Windows"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HttpSysServer [Archived] 2 | ======================== 3 | 4 | **This GitHub project has been archived.** Ongoing development on this project can be found in . 5 | 6 | This repo contains a web server for ASP.NET Core based on the Windows [Http Server API](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364510.aspx). 7 | 8 | This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. 9 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/HotAddSample/HotAddSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2;net461 5 | Exe 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/SelfHostServer/SelfHostServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2;net461 5 | Exe 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/NclUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.AspNetCore.HttpSys.Internal 7 | { 8 | internal static class NclUtilities 9 | { 10 | internal static bool HasShutdownStarted 11 | { 12 | get 13 | { 14 | return Environment.HasShutdownStarted 15 | || AppDomain.CurrentDomain.IsFinalizingForUnload(); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/IntPtrHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.AspNetCore.Server.HttpSys 7 | { 8 | internal static class IntPtrHelper 9 | { 10 | internal static IntPtr Add(IntPtr a, int b) 11 | { 12 | return (IntPtr)((long)a + (long)b); 13 | } 14 | 15 | internal static long Subtract(IntPtr a, IntPtr b) 16 | { 17 | return ((long)a - (long)b); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/RequestProcessing/BoundaryType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.Server.HttpSys 5 | { 6 | internal enum BoundaryType 7 | { 8 | None = 0, 9 | Chunked = 1, // Transfer-Encoding: chunked 10 | ContentLength = 2, // Content-Length: XXX 11 | Close = 3, // Connection: close 12 | PassThrough = 4, // The application is handling the boundary themselves (e.g. chunking themselves). 13 | Invalid = 5, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: false 3 | dist: trusty 4 | env: 5 | global: 6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 8 | mono: none 9 | os: 10 | - linux 11 | - osx 12 | osx_image: xcode8.2 13 | addons: 14 | apt: 15 | packages: 16 | - libunwind8 17 | branches: 18 | only: 19 | - master 20 | - /^release\/.*$/ 21 | - /^(.*\/)?ci-.*$/ 22 | before_install: 23 | - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s 24 | /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib 25 | /usr/local/lib/; fi 26 | script: 27 | - ./build.sh 28 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.HttpSys.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/AuthenticationSchemes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.AspNetCore.Server.HttpSys 7 | { 8 | // REVIEW: this appears to be very similar to System.Net.AuthenticationSchemes 9 | [Flags] 10 | public enum AuthenticationSchemes 11 | { 12 | None = 0x0, 13 | Basic = 0x1, 14 | // Digest = 0x2, // TODO: Verify this is no longer supported by Http.Sys 15 | NTLM = 0x4, 16 | Negotiate = 0x8, 17 | Kerberos = 0x10 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/ComNetOS.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Internal; 6 | 7 | namespace Microsoft.AspNetCore.Server.HttpSys 8 | { 9 | internal static class ComNetOS 10 | { 11 | // Windows is assumed based on HttpApi.Supported which is checked in the HttpSysListener constructor. 12 | // Minimum support for Windows 7 is assumed. 13 | internal static readonly bool IsWin8orLater; 14 | 15 | static ComNetOS() 16 | { 17 | var win8Version = new Version(6, 2); 18 | 19 | IsWin8orLater = (Environment.OSVersion.Version >= win8Version); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MicrosoftNETCoreApp20PackageVersion) 4 | $(MicrosoftNETCoreApp21PackageVersion) 5 | $(MicrosoftNETCoreApp22PackageVersion) 6 | $(NETStandardLibrary20PackageVersion) 7 | 8 | 99.9 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/HttpRequestQueueV2Handle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.HttpSys.Internal; 5 | using Microsoft.Win32.SafeHandles; 6 | 7 | namespace Microsoft.AspNetCore.Server.HttpSys 8 | { 9 | // This class is a wrapper for Http.sys V2 request queue handle. 10 | internal sealed class HttpRequestQueueV2Handle : SafeHandleZeroOrMinusOneIsInvalid 11 | { 12 | private HttpRequestQueueV2Handle() 13 | : base(true) 14 | { 15 | } 16 | 17 | protected override bool ReleaseHandle() 18 | { 19 | return (HttpApi.HttpCloseRequestQueue(handle) == 20 | UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/SafeLocalMemHandle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Win32.SafeHandles; 6 | 7 | namespace Microsoft.AspNetCore.HttpSys.Internal 8 | { 9 | internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid 10 | { 11 | internal SafeLocalMemHandle() 12 | : base(true) 13 | { 14 | } 15 | 16 | internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) 17 | : base(ownsHandle) 18 | { 19 | SetHandle(existingHandle); 20 | } 21 | 22 | protected override bool ReleaseHandle() 23 | { 24 | return UnsafeNclNativeMethods.SafeNetHandles.LocalFree(handle) == IntPtr.Zero; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.AspNetCore.HttpSys.Internal 7 | { 8 | internal static class Constants 9 | { 10 | internal const string HttpScheme = "http"; 11 | internal const string HttpsScheme = "https"; 12 | internal const string Chunked = "chunked"; 13 | internal const string Close = "close"; 14 | internal const string Zero = "0"; 15 | internal const string SchemeDelimiter = "://"; 16 | internal const string DefaultServerAddress = "http://localhost:5000"; 17 | 18 | internal static Version V1_0 = new Version(1, 0); 19 | internal static Version V1_1 = new Version(1, 1); 20 | internal static Version V2 = new Version(2, 0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Microsoft ASP.NET Core 12 | https://github.com/aspnet/HttpSysServer 13 | git 14 | $(MSBuildThisFileDirectory) 15 | $(MSBuildThisFileDirectory)build\Key.snk 16 | true 17 | true 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netcoreapp2.2 6 | $(DeveloperBuildTestTfms) 7 | $(StandardTestTfms) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/HeapAllocHandle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Win32.SafeHandles; 6 | 7 | namespace Microsoft.AspNetCore.HttpSys.Internal 8 | { 9 | internal sealed class HeapAllocHandle : SafeHandleZeroOrMinusOneIsInvalid 10 | { 11 | private static readonly IntPtr ProcessHeap = UnsafeNclNativeMethods.GetProcessHeap(); 12 | 13 | // Called by P/Invoke when returning SafeHandles 14 | private HeapAllocHandle() 15 | : base(ownsHandle: true) 16 | { 17 | } 18 | 19 | // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you. 20 | protected override bool ReleaseHandle() 21 | { 22 | return UnsafeNclNativeMethods.HeapFree(ProcessHeap, 0, handle); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/SelfHostServer/Public/1kb.txt: -------------------------------------------------------------------------------- 1 | asdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfqweruoiasdfnsngdfioenrglknsgilhasdgha;gu;agnaknusgnjkadfgnknjksdfk asdhfhasdf nklasdgnasg njagnjasdfasdfasdfasdfasdfasd -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3.0.0 4 | alpha1 5 | $(VersionPrefix) 6 | $(VersionPrefix)-$(VersionSuffix)-final 7 | t000 8 | a- 9 | $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) 10 | $(VersionSuffix)-$(BuildNumber) 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/Http503VerbosityLevel .cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.Server.HttpSys 5 | { 6 | /// 7 | /// Enum declaring the allowed values for the verbosity level when http.sys reject requests due to throttling. 8 | /// 9 | public enum Http503VerbosityLevel : long 10 | { 11 | /// 12 | /// A 503 response is not sent; the connection is reset. This is the default HTTP Server API behavior. 13 | /// 14 | Basic = 0, 15 | 16 | /// 17 | /// The HTTP Server API sends a 503 response with a "Service Unavailable" reason phrase. 18 | /// 19 | Limited = 1, 20 | 21 | /// 22 | /// The HTTP Server API sends a 503 response with a detailed reason phrase. 23 | /// 24 | Full = 2 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | 46 | *.csproj text=auto 47 | *.vbproj text=auto 48 | *.fsproj text=auto 49 | *.dbproj text=auto 50 | *.sln text=auto eol=crlf 51 | *.sh eol=lf 52 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/ServerSession.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using Microsoft.AspNetCore.HttpSys.Internal; 7 | 8 | namespace Microsoft.AspNetCore.Server.HttpSys 9 | { 10 | internal class ServerSession : IDisposable 11 | { 12 | internal unsafe ServerSession() 13 | { 14 | ulong serverSessionId = 0; 15 | var statusCode = HttpApi.HttpCreateServerSession( 16 | HttpApi.Version, &serverSessionId, 0); 17 | 18 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 19 | { 20 | throw new HttpSysException((int)statusCode); 21 | } 22 | 23 | Debug.Assert(serverSessionId != 0, "Invalid id returned by HttpCreateServerSession"); 24 | 25 | Id = new HttpServerSessionHandle(serverSessionId); 26 | } 27 | 28 | public HttpServerSessionHandle Id { get; private set; } 29 | 30 | public void Dispose() 31 | { 32 | Id.Dispose(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/HttpSysException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.ComponentModel; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace Microsoft.AspNetCore.Server.HttpSys 10 | { 11 | [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")] 12 | public class HttpSysException : Win32Exception 13 | { 14 | internal HttpSysException() 15 | : base(Marshal.GetLastWin32Error()) 16 | { 17 | } 18 | 19 | internal HttpSysException(int errorCode) 20 | : base(errorCode) 21 | { 22 | } 23 | 24 | internal HttpSysException(int errorCode, string message) 25 | : base(errorCode, message) 26 | { 27 | } 28 | 29 | // the base class returns the HResult with this property 30 | // we need the Win32 Error Code, hence the override. 31 | public override int ErrorCode 32 | { 33 | get 34 | { 35 | return NativeErrorCode; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/Microsoft.AspNetCore.Server.HttpSys.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ASP.NET Core HTTP server that uses the Windows HTTP Server API. 5 | netstandard2.0 6 | $(NoWarn);CS1591 7 | true 8 | true 9 | aspnetcore;weblistener;httpsys 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/DummyApplication.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting.Server; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Http.Features; 9 | 10 | namespace Microsoft.AspNetCore.Server.HttpSys 11 | { 12 | internal class DummyApplication : IHttpApplication 13 | { 14 | private readonly RequestDelegate _requestDelegate; 15 | 16 | public DummyApplication() : this(context => Task.CompletedTask) { } 17 | 18 | public DummyApplication(RequestDelegate requestDelegate) 19 | { 20 | _requestDelegate = requestDelegate; 21 | } 22 | 23 | public HttpContext CreateContext(IFeatureCollection contextFeatures) 24 | { 25 | return new DefaultHttpContext(contextFeatures); 26 | } 27 | 28 | public void DisposeContext(HttpContext httpContext, Exception exception) 29 | { 30 | 31 | } 32 | 33 | public async Task ProcessRequestAsync(HttpContext httpContext) 34 | { 35 | await _requestDelegate(httpContext); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/RequestProcessing/HeaderEncoding.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Text; 5 | 6 | namespace Microsoft.AspNetCore.HttpSys.Internal 7 | { 8 | internal static class HeaderEncoding 9 | { 10 | // It should just be ASCII or ANSI, but they break badly with un-expected values. We use UTF-8 because it's the same for 11 | // ASCII, and because some old client would send UTF8 Host headers and expect UTF8 Location responses 12 | // (e.g. IE and HttpWebRequest on intranets). 13 | private static Encoding Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false); 14 | 15 | internal static unsafe string GetString(byte* pBytes, int byteCount) 16 | { 17 | // net451: return new string(pBytes, 0, byteCount, Encoding); 18 | 19 | var charCount = Encoding.GetCharCount(pBytes, byteCount); 20 | var chars = new char[charCount]; 21 | fixed (char* pChars = chars) 22 | { 23 | var count = Encoding.GetChars(pBytes, byteCount, pChars, charCount); 24 | System.Diagnostics.Debug.Assert(count == charCount); 25 | } 26 | return new string(chars); 27 | } 28 | 29 | internal static byte[] GetBytes(string myString) 30 | { 31 | return Encoding.GetBytes(myString); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/SafeLocalFreeChannelBinding.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Security.Authentication.ExtendedProtection; 6 | 7 | namespace Microsoft.AspNetCore.HttpSys.Internal 8 | { 9 | internal class SafeLocalFreeChannelBinding : ChannelBinding 10 | { 11 | private const int LMEM_FIXED = 0; 12 | private int size; 13 | 14 | public override int Size 15 | { 16 | get { return size; } 17 | } 18 | 19 | public static SafeLocalFreeChannelBinding LocalAlloc(int cb) 20 | { 21 | SafeLocalFreeChannelBinding result; 22 | 23 | result = UnsafeNclNativeMethods.SafeNetHandles.LocalAllocChannelBinding(LMEM_FIXED, (UIntPtr)cb); 24 | if (result.IsInvalid) 25 | { 26 | result.SetHandleAsInvalid(); 27 | throw new OutOfMemoryException(); 28 | } 29 | 30 | result.size = cb; 31 | return result; 32 | } 33 | 34 | protected override bool ReleaseHandle() 35 | { 36 | return UnsafeNclNativeMethods.SafeNetHandles.LocalFree(handle) == IntPtr.Zero; 37 | } 38 | 39 | public override bool IsInvalid 40 | { 41 | get 42 | { 43 | return handle == IntPtr.Zero || handle.ToInt32() == -1; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/HttpServerSessionHandle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using Microsoft.AspNetCore.HttpSys.Internal; 7 | using Microsoft.Win32.SafeHandles; 8 | 9 | namespace Microsoft.AspNetCore.Server.HttpSys 10 | { 11 | internal sealed class HttpServerSessionHandle : CriticalHandleZeroOrMinusOneIsInvalid 12 | { 13 | private int disposed; 14 | private ulong serverSessionId; 15 | 16 | internal HttpServerSessionHandle(ulong id) 17 | : base() 18 | { 19 | serverSessionId = id; 20 | 21 | // This class uses no real handle so we need to set a dummy handle. Otherwise, IsInvalid always remains 22 | // true. 23 | 24 | SetHandle(new IntPtr(1)); 25 | } 26 | 27 | internal ulong DangerousGetServerSessionId() 28 | { 29 | return serverSessionId; 30 | } 31 | 32 | protected override bool ReleaseHandle() 33 | { 34 | if (!IsInvalid) 35 | { 36 | if (Interlocked.Increment(ref disposed) == 1) 37 | { 38 | // Closing server session also closes all open url groups under that server session. 39 | return (HttpApi.HttpCloseServerSession(serverSessionId) == 40 | UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS); 41 | } 42 | } 43 | return true; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/SelfHostServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Server.HttpSys; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace SelfHostServer 10 | { 11 | public class Startup 12 | { 13 | public void ConfigureServices(IServiceCollection services) 14 | { 15 | // Server options can be configured here instead of in Main. 16 | services.Configure(options => 17 | { 18 | options.Authentication.Schemes = AuthenticationSchemes.None; 19 | options.Authentication.AllowAnonymous = true; 20 | }); 21 | } 22 | 23 | public void Configure(IApplicationBuilder app) 24 | { 25 | app.Run(async context => 26 | { 27 | context.Response.ContentType = "text/plain"; 28 | await context.Response.WriteAsync("Hello world from " + context.Request.Host + " at " + DateTime.Now); 29 | }); 30 | } 31 | 32 | public static void Main(string[] args) 33 | { 34 | var host = new WebHostBuilder() 35 | .ConfigureLogging(factory => factory.AddConsole()) 36 | .UseStartup() 37 | .UseHttpSys(options => 38 | { 39 | options.UrlPrefixes.Add("http://localhost:5000"); 40 | options.Authentication.Schemes = AuthenticationSchemes.None; 41 | options.Authentication.AllowAnonymous = true; 42 | }) 43 | .Build(); 44 | 45 | host.Run(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/AuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Security.Claims; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Authentication; 9 | using Microsoft.AspNetCore.Http; 10 | 11 | namespace Microsoft.AspNetCore.Server.HttpSys 12 | { 13 | internal class AuthenticationHandler : IAuthenticationHandler 14 | { 15 | private RequestContext _requestContext; 16 | private AuthenticationScheme _scheme; 17 | 18 | public Task AuthenticateAsync() 19 | { 20 | var identity = _requestContext.User?.Identity; 21 | if (identity != null && identity.IsAuthenticated) 22 | { 23 | return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(_requestContext.User, properties: null, authenticationScheme: _scheme.Name))); 24 | } 25 | return Task.FromResult(AuthenticateResult.NoResult()); 26 | } 27 | 28 | public Task ChallengeAsync(AuthenticationProperties properties) 29 | { 30 | _requestContext.Response.StatusCode = 401; 31 | return Task.CompletedTask; 32 | } 33 | 34 | public Task ForbidAsync(AuthenticationProperties properties) 35 | { 36 | _requestContext.Response.StatusCode = 403; 37 | return Task.CompletedTask; 38 | } 39 | 40 | public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) 41 | { 42 | _scheme = scheme; 43 | _requestContext = context.Features.Get(); 44 | 45 | if (_requestContext == null) 46 | { 47 | throw new InvalidOperationException("No RequestContext found."); 48 | } 49 | 50 | return Task.CompletedTask; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/WebHostBuilderHttpSysExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Hosting.Server; 6 | using Microsoft.AspNetCore.Server.HttpSys; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Microsoft.AspNetCore.Hosting 10 | { 11 | public static class WebHostBuilderHttpSysExtensions 12 | { 13 | /// 14 | /// Specify HttpSys as the server to be used by the web host. 15 | /// 16 | /// 17 | /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. 18 | /// 19 | /// 20 | /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. 21 | /// 22 | public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder) 23 | { 24 | return hostBuilder.ConfigureServices(services => { 25 | services.AddSingleton(); 26 | services.AddAuthenticationCore(); 27 | }); 28 | } 29 | 30 | /// 31 | /// Specify HttpSys as the server to be used by the web host. 32 | /// 33 | /// 34 | /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. 35 | /// 36 | /// 37 | /// A callback to configure HttpSys options. 38 | /// 39 | /// 40 | /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. 41 | /// 42 | public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder, Action options) 43 | { 44 | return hostBuilder.UseHttpSys().ConfigureServices(services => 45 | { 46 | services.Configure(options); 47 | }); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/CookedUrl.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Microsoft.AspNetCore.HttpSys.Internal 8 | { 9 | // Note this type should only be used while the request buffer remains pinned 10 | internal class CookedUrl 11 | { 12 | private readonly HttpApiTypes.HTTP_COOKED_URL _nativeCookedUrl; 13 | 14 | internal CookedUrl(HttpApiTypes.HTTP_COOKED_URL nativeCookedUrl) 15 | { 16 | _nativeCookedUrl = nativeCookedUrl; 17 | } 18 | 19 | internal unsafe string GetFullUrl() 20 | { 21 | if (_nativeCookedUrl.pFullUrl != null && _nativeCookedUrl.FullUrlLength > 0) 22 | { 23 | return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pFullUrl, _nativeCookedUrl.FullUrlLength / 2); 24 | } 25 | return null; 26 | } 27 | 28 | internal unsafe string GetHost() 29 | { 30 | if (_nativeCookedUrl.pHost != null && _nativeCookedUrl.HostLength > 0) 31 | { 32 | return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pHost, _nativeCookedUrl.HostLength / 2); 33 | } 34 | return null; 35 | } 36 | 37 | internal unsafe string GetAbsPath() 38 | { 39 | if (_nativeCookedUrl.pAbsPath != null && _nativeCookedUrl.AbsPathLength > 0) 40 | { 41 | return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pAbsPath, _nativeCookedUrl.AbsPathLength / 2); 42 | } 43 | return null; 44 | } 45 | 46 | internal unsafe string GetQueryString() 47 | { 48 | if (_nativeCookedUrl.pQueryString != null && _nativeCookedUrl.QueryStringLength > 0) 49 | { 50 | return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pQueryString, _nativeCookedUrl.QueryStringLength / 2); 51 | } 52 | return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/ValidationHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | 7 | namespace Microsoft.AspNetCore.Server.HttpSys 8 | { 9 | internal static class ValidationHelper 10 | { 11 | public static string ExceptionMessage(Exception exception) 12 | { 13 | if (exception == null) 14 | { 15 | return string.Empty; 16 | } 17 | if (exception.InnerException == null) 18 | { 19 | return exception.Message; 20 | } 21 | return exception.Message + " (" + ExceptionMessage(exception.InnerException) + ")"; 22 | } 23 | 24 | public static string ToString(object objectValue) 25 | { 26 | if (objectValue == null) 27 | { 28 | return "(null)"; 29 | } 30 | else if (objectValue is string && ((string)objectValue).Length == 0) 31 | { 32 | return "(string.empty)"; 33 | } 34 | else if (objectValue is Exception) 35 | { 36 | return ExceptionMessage(objectValue as Exception); 37 | } 38 | else if (objectValue is IntPtr) 39 | { 40 | return "0x" + ((IntPtr)objectValue).ToString("x"); 41 | } 42 | else 43 | { 44 | return objectValue.ToString(); 45 | } 46 | } 47 | 48 | public static string HashString(object objectValue) 49 | { 50 | if (objectValue == null) 51 | { 52 | return "(null)"; 53 | } 54 | else if (objectValue is string && ((string)objectValue).Length == 0) 55 | { 56 | return "(string.empty)"; 57 | } 58 | else 59 | { 60 | return objectValue.GetHashCode().ToString(NumberFormatInfo.InvariantInfo); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/HttpSysResponseHeader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.HttpSys.Internal 5 | { 6 | internal enum HttpSysResponseHeader 7 | { 8 | CacheControl = 0, // general-header [section 4.5] 9 | Connection = 1, // general-header [section 4.5] 10 | Date = 2, // general-header [section 4.5] 11 | KeepAlive = 3, // general-header [not in rfc] 12 | Pragma = 4, // general-header [section 4.5] 13 | Trailer = 5, // general-header [section 4.5] 14 | TransferEncoding = 6, // general-header [section 4.5] 15 | Upgrade = 7, // general-header [section 4.5] 16 | Via = 8, // general-header [section 4.5] 17 | Warning = 9, // general-header [section 4.5] 18 | Allow = 10, // entity-header [section 7.1] 19 | ContentLength = 11, // entity-header [section 7.1] 20 | ContentType = 12, // entity-header [section 7.1] 21 | ContentEncoding = 13, // entity-header [section 7.1] 22 | ContentLanguage = 14, // entity-header [section 7.1] 23 | ContentLocation = 15, // entity-header [section 7.1] 24 | ContentMd5 = 16, // entity-header [section 7.1] 25 | ContentRange = 17, // entity-header [section 7.1] 26 | Expires = 18, // entity-header [section 7.1] 27 | LastModified = 19, // entity-header [section 7.1] 28 | 29 | AcceptRanges = 20, // response-header [section 6.2] 30 | Age = 21, // response-header [section 6.2] 31 | ETag = 22, // response-header [section 6.2] 32 | Location = 23, // response-header [section 6.2] 33 | ProxyAuthenticate = 24, // response-header [section 6.2] 34 | RetryAfter = 25, // response-header [section 6.2] 35 | Server = 26, // response-header [section 6.2] 36 | SetCookie = 27, // response-header [not in rfc] 37 | Vary = 28, // response-header [section 6.2] 38 | WwwAuthenticate = 29, // response-header [section 6.2] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/RequestProcessing/HeaderParser.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace Microsoft.AspNetCore.HttpSys.Internal 8 | { 9 | internal static class HeaderParser 10 | { 11 | internal static IEnumerable Empty = new string[0]; 12 | 13 | // Split on commas, except in quotes 14 | internal static IEnumerable SplitValues(StringValues values) 15 | { 16 | foreach (var value in values) 17 | { 18 | int start = 0; 19 | bool inQuotes = false; 20 | int current = 0; 21 | for ( ; current < value.Length; current++) 22 | { 23 | char ch = value[current]; 24 | if (inQuotes) 25 | { 26 | if (ch == '"') 27 | { 28 | inQuotes = false; 29 | } 30 | } 31 | else if (ch == '"') 32 | { 33 | inQuotes = true; 34 | } 35 | else if (ch == ',') 36 | { 37 | var subValue = value.Substring(start, current - start); 38 | if (!string.IsNullOrWhiteSpace(subValue)) 39 | { 40 | yield return subValue.Trim(); 41 | start = current + 1; 42 | } 43 | } 44 | } 45 | 46 | if (start < current) 47 | { 48 | var subValue = value.Substring(start, current - start); 49 | if (!string.IsNullOrWhiteSpace(subValue)) 50 | { 51 | yield return subValue.Trim(); 52 | start = current + 1; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /samples/TestClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Open Technologies, Inc. 2 | // All Rights Reserved 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR 11 | // CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING 12 | // WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF 13 | // TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR 14 | // NON-INFRINGEMENT. 15 | // See the Apache 2 License for the specific language governing 16 | // permissions and limitations under the License. 17 | 18 | using System.Reflection; 19 | using System.Runtime.CompilerServices; 20 | using System.Runtime.InteropServices; 21 | 22 | // General Information about an assembly is controlled through the following 23 | // set of attributes. Change these attribute values to modify the information 24 | // associated with an assembly. 25 | [assembly: AssemblyTitle("TestClient")] 26 | [assembly: AssemblyDescription("")] 27 | [assembly: AssemblyConfiguration("")] 28 | [assembly: AssemblyCompany("")] 29 | [assembly: AssemblyProduct("TestClient")] 30 | [assembly: AssemblyCopyright("Copyright © 2012")] 31 | [assembly: AssemblyTrademark("")] 32 | [assembly: AssemblyCulture("")] 33 | 34 | // Setting ComVisible to false makes the types in this assembly not visible 35 | // to COM components. If you need to access a type in this assembly from 36 | // COM, set the ComVisible attribute to true on that type. 37 | [assembly: ComVisible(false)] 38 | 39 | // The following GUID is for the ID of the typelib if this project is exposed to COM 40 | [assembly: Guid("8db62eb3-48c0-4049-b33e-271c738140a0")] 41 | 42 | // Version information for an assembly consists of the following four values: 43 | // 44 | // Major Version 45 | // Minor Version 46 | // Build Number 47 | // Revision 48 | // 49 | // You can specify all the values or you can default the Build and Revision Numbers 50 | // by using the '*' as shown below: 51 | // [assembly: AssemblyVersion("0.5")] 52 | [assembly: AssemblyVersion("0.5")] 53 | [assembly: AssemblyFileVersion("0.5.40117.0")] 54 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/SafeNativeOverlapped.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | 8 | namespace Microsoft.AspNetCore.HttpSys.Internal 9 | { 10 | internal class SafeNativeOverlapped : SafeHandle 11 | { 12 | internal static readonly SafeNativeOverlapped Zero = new SafeNativeOverlapped(); 13 | private ThreadPoolBoundHandle _boundHandle; 14 | 15 | internal SafeNativeOverlapped() 16 | : base(IntPtr.Zero, true) 17 | { 18 | } 19 | 20 | internal unsafe SafeNativeOverlapped(ThreadPoolBoundHandle boundHandle, NativeOverlapped* handle) 21 | : base(IntPtr.Zero, true) 22 | { 23 | SetHandle((IntPtr)handle); 24 | _boundHandle = boundHandle; 25 | } 26 | 27 | public override bool IsInvalid 28 | { 29 | get { return handle == IntPtr.Zero; } 30 | } 31 | 32 | public void ReinitializeNativeOverlapped() 33 | { 34 | IntPtr handleSnapshot = handle; 35 | 36 | if (handleSnapshot != IntPtr.Zero) 37 | { 38 | unsafe 39 | { 40 | ((NativeOverlapped*)handleSnapshot)->InternalHigh = IntPtr.Zero; 41 | ((NativeOverlapped*)handleSnapshot)->InternalLow = IntPtr.Zero; 42 | ((NativeOverlapped*)handleSnapshot)->EventHandle = IntPtr.Zero; 43 | } 44 | } 45 | } 46 | 47 | protected override bool ReleaseHandle() 48 | { 49 | IntPtr oldHandle = Interlocked.Exchange(ref handle, IntPtr.Zero); 50 | // Do not call free durring AppDomain shutdown, there may be an outstanding operation. 51 | // Overlapped will take care calling free when the native callback completes. 52 | if (oldHandle != IntPtr.Zero && !NclUtilities.HasShutdownStarted) 53 | { 54 | unsafe 55 | { 56 | _boundHandle.FreeNativeOverlapped((NativeOverlapped*)oldHandle); 57 | } 58 | } 59 | return true; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/HttpSysRequestHeader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.HttpSys.Internal 5 | { 6 | internal enum HttpSysRequestHeader 7 | { 8 | CacheControl = 0, // general-header [section 4.5] 9 | Connection = 1, // general-header [section 4.5] 10 | Date = 2, // general-header [section 4.5] 11 | KeepAlive = 3, // general-header [not in rfc] 12 | Pragma = 4, // general-header [section 4.5] 13 | Trailer = 5, // general-header [section 4.5] 14 | TransferEncoding = 6, // general-header [section 4.5] 15 | Upgrade = 7, // general-header [section 4.5] 16 | Via = 8, // general-header [section 4.5] 17 | Warning = 9, // general-header [section 4.5] 18 | Allow = 10, // entity-header [section 7.1] 19 | ContentLength = 11, // entity-header [section 7.1] 20 | ContentType = 12, // entity-header [section 7.1] 21 | ContentEncoding = 13, // entity-header [section 7.1] 22 | ContentLanguage = 14, // entity-header [section 7.1] 23 | ContentLocation = 15, // entity-header [section 7.1] 24 | ContentMd5 = 16, // entity-header [section 7.1] 25 | ContentRange = 17, // entity-header [section 7.1] 26 | Expires = 18, // entity-header [section 7.1] 27 | LastModified = 19, // entity-header [section 7.1] 28 | 29 | Accept = 20, // request-header [section 5.3] 30 | AcceptCharset = 21, // request-header [section 5.3] 31 | AcceptEncoding = 22, // request-header [section 5.3] 32 | AcceptLanguage = 23, // request-header [section 5.3] 33 | Authorization = 24, // request-header [section 5.3] 34 | Cookie = 25, // request-header [not in rfc] 35 | Expect = 26, // request-header [section 5.3] 36 | From = 27, // request-header [section 5.3] 37 | Host = 28, // request-header [section 5.3] 38 | IfMatch = 29, // request-header [section 5.3] 39 | IfModifiedSince = 30, // request-header [section 5.3] 40 | IfNoneMatch = 31, // request-header [section 5.3] 41 | IfRange = 32, // request-header [section 5.3] 42 | IfUnmodifiedSince = 33, // request-header [section 5.3] 43 | MaxForwards = 34, // request-header [section 5.3] 44 | ProxyAuthorization = 35, // request-header [section 5.3] 45 | Referer = 36, // request-header [section 5.3] 46 | Range = 37, // request-header [section 5.3] 47 | Te = 38, // request-header [section 5.3] 48 | Translate = 39, // request-header [webDAV, not in rfc 2518] 49 | UserAgent = 40, // request-header [section 5.3] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/TestClient/TestClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8B828433-B333-4C19-96AE-00BFFF9D8841} 8 | Exe 9 | Properties 10 | TestClient 11 | TestClient 12 | v4.6 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | -------------------------------------------------------------------------------- /samples/TestClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Net.WebSockets; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace TestClient 10 | { 11 | public class Program 12 | { 13 | private const string Address = 14 | // "http://localhost:5000/public/1kb.txt"; 15 | "https://localhost:9090/public/1kb.txt"; 16 | 17 | public static void Main(string[] args) 18 | { 19 | WebRequestHandler handler = new WebRequestHandler(); 20 | handler.ServerCertificateValidationCallback = (_, __, ___, ____) => true; 21 | // handler.UseDefaultCredentials = true; 22 | handler.Credentials = new NetworkCredential(@"redmond\chrross", "passwird"); 23 | HttpClient client = new HttpClient(handler); 24 | 25 | /* 26 | int completionCount = 0; 27 | int iterations = 30000; 28 | for (int i = 0; i < iterations; i++) 29 | { 30 | client.GetAsync(Address) 31 | .ContinueWith(t => Interlocked.Increment(ref completionCount)); 32 | } 33 | 34 | while (completionCount < iterations) 35 | { 36 | Thread.Sleep(10); 37 | }*/ 38 | 39 | while (true) 40 | { 41 | Console.WriteLine("Press any key to send request"); 42 | Console.ReadKey(); 43 | var result = client.GetAsync(Address).Result; 44 | Console.WriteLine(result); 45 | } 46 | 47 | // RunWebSocketClient().Wait(); 48 | // Console.WriteLine("Done"); 49 | // Console.ReadKey(); 50 | } 51 | 52 | public static async Task RunWebSocketClient() 53 | { 54 | ClientWebSocket websocket = new ClientWebSocket(); 55 | 56 | string url = "ws://localhost:5000/"; 57 | Console.WriteLine("Connecting to: " + url); 58 | await websocket.ConnectAsync(new Uri(url), CancellationToken.None); 59 | 60 | string message = "Hello World"; 61 | Console.WriteLine("Sending message: " + message); 62 | byte[] messageBytes = Encoding.UTF8.GetBytes(message); 63 | await websocket.SendAsync(new ArraySegment(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None); 64 | 65 | byte[] incomingData = new byte[1024]; 66 | WebSocketReceiveResult result = await websocket.ReceiveAsync(new ArraySegment(incomingData), CancellationToken.None); 67 | 68 | if (result.CloseStatus.HasValue) 69 | { 70 | Console.WriteLine("Closed; Status: " + result.CloseStatus + ", " + result.CloseStatusDescription); 71 | } 72 | else 73 | { 74 | Console.WriteLine("Received message: " + Encoding.UTF8.GetString(incomingData, 0, result.Count)); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/LogHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Microsoft.AspNetCore.Server.HttpSys 9 | { 10 | internal static class LogHelper 11 | { 12 | internal static ILogger CreateLogger(ILoggerFactory factory, Type type) 13 | { 14 | if (factory == null) 15 | { 16 | return null; 17 | } 18 | 19 | return factory.CreateLogger(type.FullName); 20 | } 21 | 22 | internal static void LogInfo(ILogger logger, string data) 23 | { 24 | if (logger == null) 25 | { 26 | Debug.WriteLine(data); 27 | } 28 | else 29 | { 30 | logger.LogInformation(data); 31 | } 32 | } 33 | 34 | internal static void LogWarning(ILogger logger, string data) 35 | { 36 | if (logger == null) 37 | { 38 | Debug.WriteLine(data); 39 | } 40 | else 41 | { 42 | logger.LogWarning(data); 43 | } 44 | } 45 | 46 | internal static void LogDebug(ILogger logger, string data) 47 | { 48 | if (logger == null) 49 | { 50 | Debug.WriteLine(data); 51 | } 52 | else 53 | { 54 | logger.LogDebug(data); 55 | } 56 | } 57 | 58 | internal static void LogDebug(ILogger logger, string location, string data) 59 | { 60 | if (logger == null) 61 | { 62 | Debug.WriteLine(data); 63 | } 64 | else 65 | { 66 | logger.LogDebug(location + "; " + data); 67 | } 68 | } 69 | 70 | internal static void LogDebug(ILogger logger, string location, Exception exception) 71 | { 72 | if (logger == null) 73 | { 74 | Console.WriteLine(location + Environment.NewLine + exception.ToString()); 75 | } 76 | else 77 | { 78 | logger.LogDebug(0, exception, location); 79 | } 80 | } 81 | 82 | internal static void LogException(ILogger logger, string location, Exception exception) 83 | { 84 | if (logger == null) 85 | { 86 | Debug.WriteLine(location + Environment.NewLine + exception.ToString()); 87 | } 88 | else 89 | { 90 | logger.LogError(0, exception, location); 91 | } 92 | } 93 | 94 | internal static void LogError(ILogger logger, string location, string message) 95 | { 96 | if (logger == null) 97 | { 98 | Debug.WriteLine(message); 99 | } 100 | else 101 | { 102 | logger.LogError(location + "; " + message); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/OSDontSkipConditionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace Microsoft.AspNetCore.Testing.xunit 10 | { 11 | // Skip except on a specific OS and version 12 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] 13 | public class OSDontSkipConditionAttribute : Attribute, ITestCondition 14 | { 15 | private readonly OperatingSystems _includedOperatingSystem; 16 | private readonly IEnumerable _includedVersions; 17 | private readonly OperatingSystems _osPlatform; 18 | private readonly string _osVersion; 19 | 20 | public OSDontSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) : 21 | this( 22 | operatingSystem, 23 | GetCurrentOS(), 24 | GetCurrentOSVersion(), 25 | versions) 26 | { 27 | } 28 | 29 | // to enable unit testing 30 | internal OSDontSkipConditionAttribute( 31 | OperatingSystems operatingSystem, OperatingSystems osPlatform, string osVersion, params string[] versions) 32 | { 33 | _includedOperatingSystem = operatingSystem; 34 | _includedVersions = versions ?? Enumerable.Empty(); 35 | _osPlatform = osPlatform; 36 | _osVersion = osVersion; 37 | } 38 | 39 | public bool IsMet 40 | { 41 | get 42 | { 43 | var currentOSInfo = new OSInfo() 44 | { 45 | OperatingSystem = _osPlatform, 46 | Version = _osVersion, 47 | }; 48 | 49 | var skip = (_includedOperatingSystem & currentOSInfo.OperatingSystem) != currentOSInfo.OperatingSystem; 50 | if (!skip && _includedVersions.Any()) 51 | { 52 | skip = !_includedVersions.Any(inc => _osVersion.StartsWith(inc, StringComparison.OrdinalIgnoreCase)); 53 | } 54 | 55 | // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip 56 | return !skip; 57 | } 58 | } 59 | 60 | public string SkipReason { get; set; } = "Test cannot run on this operating system."; 61 | 62 | static private OperatingSystems GetCurrentOS() 63 | { 64 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 65 | { 66 | return OperatingSystems.Windows; 67 | } 68 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 69 | { 70 | return OperatingSystems.Linux; 71 | } 72 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 73 | { 74 | return OperatingSystems.MacOSX; 75 | } 76 | throw new PlatformNotSupportedException(); 77 | } 78 | 79 | static private string GetCurrentOSVersion() 80 | { 81 | // currently not used on other OS's 82 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 83 | { 84 | return Environment.OSVersion.Version.ToString(); 85 | } 86 | else 87 | { 88 | return string.Empty; 89 | } 90 | } 91 | 92 | private class OSInfo 93 | { 94 | public OperatingSystems OperatingSystem { get; set; } 95 | 96 | public string Version { get; set; } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/RequestProcessing/HttpReasonPhrase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.Server.HttpSys 5 | { 6 | internal static class HttpReasonPhrase 7 | { 8 | private static readonly string[][] HttpReasonPhrases = new string[][] 9 | { 10 | null, 11 | 12 | new string[] 13 | { 14 | /* 100 */ "Continue", 15 | /* 101 */ "Switching Protocols", 16 | /* 102 */ "Processing" 17 | }, 18 | 19 | new string[] 20 | { 21 | /* 200 */ "OK", 22 | /* 201 */ "Created", 23 | /* 202 */ "Accepted", 24 | /* 203 */ "Non-Authoritative Information", 25 | /* 204 */ "No Content", 26 | /* 205 */ "Reset Content", 27 | /* 206 */ "Partial Content", 28 | /* 207 */ "Multi-Status" 29 | }, 30 | 31 | new string[] 32 | { 33 | /* 300 */ "Multiple Choices", 34 | /* 301 */ "Moved Permanently", 35 | /* 302 */ "Found", 36 | /* 303 */ "See Other", 37 | /* 304 */ "Not Modified", 38 | /* 305 */ "Use Proxy", 39 | /* 306 */ null, 40 | /* 307 */ "Temporary Redirect" 41 | }, 42 | 43 | new string[] 44 | { 45 | /* 400 */ "Bad Request", 46 | /* 401 */ "Unauthorized", 47 | /* 402 */ "Payment Required", 48 | /* 403 */ "Forbidden", 49 | /* 404 */ "Not Found", 50 | /* 405 */ "Method Not Allowed", 51 | /* 406 */ "Not Acceptable", 52 | /* 407 */ "Proxy Authentication Required", 53 | /* 408 */ "Request Timeout", 54 | /* 409 */ "Conflict", 55 | /* 410 */ "Gone", 56 | /* 411 */ "Length Required", 57 | /* 412 */ "Precondition Failed", 58 | /* 413 */ "Request Entity Too Large", 59 | /* 414 */ "Request-Uri Too Long", 60 | /* 415 */ "Unsupported Media Type", 61 | /* 416 */ "Requested Range Not Satisfiable", 62 | /* 417 */ "Expectation Failed", 63 | /* 418 */ null, 64 | /* 419 */ null, 65 | /* 420 */ null, 66 | /* 421 */ null, 67 | /* 422 */ "Unprocessable Entity", 68 | /* 423 */ "Locked", 69 | /* 424 */ "Failed Dependency", 70 | /* 425 */ null, 71 | /* 426 */ "Upgrade Required", // RFC 2817 72 | }, 73 | 74 | new string[] 75 | { 76 | /* 500 */ "Internal Server Error", 77 | /* 501 */ "Not Implemented", 78 | /* 502 */ "Bad Gateway", 79 | /* 503 */ "Service Unavailable", 80 | /* 504 */ "Gateway Timeout", 81 | /* 505 */ "Http Version Not Supported", 82 | /* 506 */ null, 83 | /* 507 */ "Insufficient Storage" 84 | } 85 | }; 86 | 87 | internal static string Get(int code) 88 | { 89 | if (code >= 100 && code < 600) 90 | { 91 | int i = code / 100; 92 | int j = code % 100; 93 | if (j < HttpReasonPhrases[i].Length) 94 | { 95 | return HttpReasonPhrases[i][j]; 96 | } 97 | } 98 | return null; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.Tests/UrlPrefixTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace Microsoft.AspNetCore.Server.HttpSys 9 | { 10 | public class UrlPrefixTests 11 | { 12 | [Theory] 13 | [InlineData("")] 14 | [InlineData("5000")] 15 | [InlineData("//noscheme")] 16 | public void CreateThrowsForUrlsWithoutSchemeDelimiter(string url) 17 | { 18 | Assert.Throws(() => UrlPrefix.Create(url)); 19 | } 20 | 21 | [Theory] 22 | [InlineData("://emptyscheme")] 23 | [InlineData("://")] 24 | [InlineData("://:5000")] 25 | public void CreateThrowsForUrlsWithEmptyScheme(string url) 26 | { 27 | Assert.Throws(() => UrlPrefix.Create(url)); 28 | } 29 | 30 | [Theory] 31 | [InlineData("http://")] 32 | [InlineData("http://:5000")] 33 | [InlineData("http:///")] 34 | [InlineData("http:///:5000")] 35 | [InlineData("http:////")] 36 | [InlineData("http:////:5000")] 37 | public void CreateThrowsForUrlsWithoutHost(string url) 38 | { 39 | Assert.Throws(() => UrlPrefix.Create(url)); 40 | } 41 | 42 | [Theory] 43 | [InlineData("http://www.example.com:NOTAPORT")] 44 | [InlineData("https://www.example.com:NOTAPORT")] 45 | [InlineData("http://www.example.com:NOTAPORT/")] 46 | [InlineData("http://foo:/tmp/httpsys-test.sock:5000/doesn't/matter")] 47 | public void CreateThrowsForUrlsWithInvalidPorts(string url) 48 | { 49 | Assert.Throws(() => UrlPrefix.Create(url)); 50 | } 51 | 52 | [Theory] 53 | [InlineData("http://+", "http", "+", "80", "/", "http://+:80/")] 54 | [InlineData("http://*", "http", "*", "80", "/", "http://*:80/")] 55 | [InlineData("http://localhost", "http", "localhost", "80", "/", "http://localhost:80/")] 56 | [InlineData("http://www.example.com", "http", "www.example.com", "80", "/", "http://www.example.com:80/")] 57 | [InlineData("https://www.example.com", "https", "www.example.com", "443", "/", "https://www.example.com:443/")] 58 | [InlineData("http://www.example.com/", "http", "www.example.com", "80", "/", "http://www.example.com:80/")] 59 | [InlineData("http://www.example.com/foo?bar=baz", "http", "www.example.com", "80", "/foo?bar=baz/", "http://www.example.com:80/foo?bar=baz/")] 60 | [InlineData("http://www.example.com:5000", "http", "www.example.com", "5000", "/", "http://www.example.com:5000/")] 61 | [InlineData("https://www.example.com:5000", "https", "www.example.com", "5000", "/", "https://www.example.com:5000/")] 62 | [InlineData("http://www.example.com:5000/", "http", "www.example.com", "5000", "/", "http://www.example.com:5000/")] 63 | [InlineData("http://www.example.com/foo:bar", "http", "www.example.com", "80", "/foo:bar/", "http://www.example.com:80/foo:bar/")] 64 | public void UrlsAreParsedCorrectly(string url, string scheme, string host, string port, string pathBase, string toString) 65 | { 66 | var urlPrefix = UrlPrefix.Create(url); 67 | 68 | Assert.Equal(scheme, urlPrefix.Scheme); 69 | Assert.Equal(host, urlPrefix.Host); 70 | Assert.Equal(port, urlPrefix.Port); 71 | Assert.Equal(pathBase, urlPrefix.Path); 72 | 73 | Assert.Equal(toString ?? url, urlPrefix.ToString()); 74 | } 75 | 76 | [Fact] 77 | public void PathBaseIsNotNormalized() 78 | { 79 | var urlPrefix = UrlPrefix.Create("http://localhost:8080/p\u0041\u030Athbase"); 80 | 81 | Assert.False(urlPrefix.Path.IsNormalized(NormalizationForm.FormC)); 82 | Assert.Equal("/p\u0041\u030Athbase/", urlPrefix.Path); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/TokenBindingUtil.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using Microsoft.AspNetCore.HttpSys.Internal; 7 | using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes; 8 | using static Microsoft.AspNetCore.HttpSys.Internal.UnsafeNclNativeMethods.TokenBinding; 9 | 10 | namespace Microsoft.AspNetCore.Server.HttpSys 11 | { 12 | /// 13 | /// Contains helpers for dealing with TLS token binding. 14 | /// 15 | // TODO: https://github.com/aspnet/HttpSysServer/issues/231 16 | internal unsafe static class TokenBindingUtil 17 | { 18 | private static byte[] ExtractIdentifierBlob(TOKENBINDING_RESULT_DATA* pTokenBindingResultData) 19 | { 20 | // Per http://tools.ietf.org/html/draft-ietf-tokbind-protocol-00, Sec. 4, 21 | // the identifier is a tuple which contains (token binding type, hash algorithm 22 | // signature algorithm, key data). We'll strip off the token binding type and 23 | // return the remainder (starting with the hash algorithm) as an opaque blob. 24 | byte[] retVal = new byte[checked(pTokenBindingResultData->identifierSize - 1)]; 25 | Marshal.Copy((IntPtr)(&pTokenBindingResultData->identifierData->hashAlgorithm), retVal, 0, retVal.Length); 26 | return retVal; 27 | } 28 | 29 | /// 30 | /// Returns the 'provided' token binding identifier, optionally also returning the 31 | /// 'referred' token binding identifier. Returns null on failure. 32 | /// 33 | public static byte[] GetProvidedTokenIdFromBindingInfo(HTTP_REQUEST_TOKEN_BINDING_INFO* pTokenBindingInfo, out byte[] referredId) 34 | { 35 | byte[] providedId = null; 36 | referredId = null; 37 | 38 | HeapAllocHandle handle = null; 39 | int status = UnsafeNclNativeMethods.TokenBindingVerifyMessage( 40 | pTokenBindingInfo->TokenBinding, 41 | pTokenBindingInfo->TokenBindingSize, 42 | pTokenBindingInfo->KeyType, 43 | pTokenBindingInfo->TlsUnique, 44 | pTokenBindingInfo->TlsUniqueSize, 45 | out handle); 46 | 47 | // No match found or there was an error? 48 | if (status != 0 || handle == null || handle.IsInvalid) 49 | { 50 | return null; 51 | } 52 | 53 | using (handle) 54 | { 55 | // Find the first 'provided' and 'referred' types. 56 | TOKENBINDING_RESULT_LIST* pResultList = (TOKENBINDING_RESULT_LIST*)handle.DangerousGetHandle(); 57 | for (int i = 0; i < pResultList->resultCount; i++) 58 | { 59 | TOKENBINDING_RESULT_DATA* pThisResultData = &pResultList->resultData[i]; 60 | if (pThisResultData->identifierData->bindingType == TOKENBINDING_TYPE.TOKENBINDING_TYPE_PROVIDED) 61 | { 62 | if (providedId != null) 63 | { 64 | return null; // It is invalid to have more than one 'provided' identifier. 65 | } 66 | providedId = ExtractIdentifierBlob(pThisResultData); 67 | } 68 | else if (pThisResultData->identifierData->bindingType == TOKENBINDING_TYPE.TOKENBINDING_TYPE_REFERRED) 69 | { 70 | if (referredId != null) 71 | { 72 | return null; // It is invalid to have more than one 'referred' identifier. 73 | } 74 | referredId = ExtractIdentifierBlob(pThisResultData); 75 | } 76 | } 77 | } 78 | 79 | return providedId; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/RequestProcessing/HttpKnownHeaderNames.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.HttpSys.Internal 5 | { 6 | internal static class HttpKnownHeaderNames 7 | { 8 | internal const string CacheControl = "Cache-Control"; 9 | internal const string Connection = "Connection"; 10 | internal const string Date = "Date"; 11 | internal const string KeepAlive = "Keep-Alive"; 12 | internal const string Pragma = "Pragma"; 13 | internal const string ProxyConnection = "Proxy-Connection"; 14 | internal const string Trailer = "Trailer"; 15 | internal const string TransferEncoding = "Transfer-Encoding"; 16 | internal const string Upgrade = "Upgrade"; 17 | internal const string Via = "Via"; 18 | internal const string Warning = "Warning"; 19 | internal const string ContentLength = "Content-Length"; 20 | internal const string ContentType = "Content-Type"; 21 | internal const string ContentDisposition = "Content-Disposition"; 22 | internal const string ContentEncoding = "Content-Encoding"; 23 | internal const string ContentLanguage = "Content-Language"; 24 | internal const string ContentLocation = "Content-Location"; 25 | internal const string ContentRange = "Content-Range"; 26 | internal const string Expires = "Expires"; 27 | internal const string LastModified = "Last-Modified"; 28 | internal const string Age = "Age"; 29 | internal const string Location = "Location"; 30 | internal const string ProxyAuthenticate = "Proxy-Authenticate"; 31 | internal const string RetryAfter = "Retry-After"; 32 | internal const string Server = "Server"; 33 | internal const string SetCookie = "Set-Cookie"; 34 | internal const string SetCookie2 = "Set-Cookie2"; 35 | internal const string Vary = "Vary"; 36 | internal const string WWWAuthenticate = "WWW-Authenticate"; 37 | internal const string Accept = "Accept"; 38 | internal const string AcceptCharset = "Accept-Charset"; 39 | internal const string AcceptEncoding = "Accept-Encoding"; 40 | internal const string AcceptLanguage = "Accept-Language"; 41 | internal const string Authorization = "Authorization"; 42 | internal const string Cookie = "Cookie"; 43 | internal const string Cookie2 = "Cookie2"; 44 | internal const string Expect = "Expect"; 45 | internal const string From = "From"; 46 | internal const string Host = "Host"; 47 | internal const string IfMatch = "If-Match"; 48 | internal const string IfModifiedSince = "If-Modified-Since"; 49 | internal const string IfNoneMatch = "If-None-Match"; 50 | internal const string IfRange = "If-Range"; 51 | internal const string IfUnmodifiedSince = "If-Unmodified-Since"; 52 | internal const string MaxForwards = "Max-Forwards"; 53 | internal const string ProxyAuthorization = "Proxy-Authorization"; 54 | internal const string Referer = "Referer"; 55 | internal const string Range = "Range"; 56 | internal const string UserAgent = "User-Agent"; 57 | internal const string ContentMD5 = "Content-MD5"; 58 | internal const string ETag = "ETag"; 59 | internal const string TE = "TE"; 60 | internal const string Allow = "Allow"; 61 | internal const string AcceptRanges = "Accept-Ranges"; 62 | internal const string P3P = "P3P"; 63 | internal const string XPoweredBy = "X-Powered-By"; 64 | internal const string XAspNetVersion = "X-AspNet-Version"; 65 | internal const string SecWebSocketKey = "Sec-WebSocket-Key"; 66 | internal const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; 67 | internal const string SecWebSocketAccept = "Sec-WebSocket-Accept"; 68 | internal const string Origin = "Origin"; 69 | internal const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; 70 | internal const string SecWebSocketVersion = "Sec-WebSocket-Version"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Net.Http; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Testing.xunit; 10 | using Microsoft.Extensions.Primitives; 11 | using Xunit; 12 | 13 | namespace Microsoft.AspNetCore.Server.HttpSys 14 | { 15 | public class RequestHeaderTests 16 | { 17 | [ConditionalFact] 18 | public async Task RequestHeaders_ClientSendsDefaultHeaders_Success() 19 | { 20 | string address; 21 | using (Utilities.CreateHttpServer(out address, httpContext => 22 | { 23 | var requestHeaders = httpContext.Request.Headers; 24 | // NOTE: The System.Net client only sends the Connection: keep-alive header on the first connection per service-point. 25 | // Assert.Equal(2, requestHeaders.Count); 26 | // Assert.Equal("Keep-Alive", requestHeaders.Get("Connection")); 27 | Assert.False(StringValues.IsNullOrEmpty(requestHeaders["Host"])); 28 | Assert.True(StringValues.IsNullOrEmpty(requestHeaders["Accept"])); 29 | return Task.FromResult(0); 30 | })) 31 | { 32 | string response = await SendRequestAsync(address); 33 | Assert.Equal(string.Empty, response); 34 | } 35 | } 36 | 37 | [ConditionalFact] 38 | public async Task RequestHeaders_ClientSendsCustomHeaders_Success() 39 | { 40 | string address; 41 | using (Utilities.CreateHttpServer(out address, httpContext => 42 | { 43 | var requestHeaders = httpContext.Request.Headers; 44 | Assert.Equal(4, requestHeaders.Count); 45 | Assert.False(StringValues.IsNullOrEmpty(requestHeaders["Host"])); 46 | Assert.Equal("close", requestHeaders["Connection"]); 47 | // Apparently Http.Sys squashes request headers together. 48 | Assert.Single(requestHeaders["Custom-Header"]); 49 | Assert.Equal("custom1, and custom2, custom3", requestHeaders["Custom-Header"]); 50 | Assert.Single(requestHeaders["Spacer-Header"]); 51 | Assert.Equal("spacervalue, spacervalue", requestHeaders["Spacer-Header"]); 52 | return Task.FromResult(0); 53 | })) 54 | { 55 | string[] customValues = new string[] { "custom1, and custom2", "custom3" }; 56 | 57 | await SendRequestAsync(address, "Custom-Header", customValues); 58 | } 59 | } 60 | 61 | private async Task SendRequestAsync(string uri) 62 | { 63 | using (HttpClient client = new HttpClient()) 64 | { 65 | return await client.GetStringAsync(uri); 66 | } 67 | } 68 | 69 | private async Task SendRequestAsync(string address, string customHeader, string[] customValues) 70 | { 71 | var uri = new Uri(address); 72 | StringBuilder builder = new StringBuilder(); 73 | builder.AppendLine("GET / HTTP/1.1"); 74 | builder.AppendLine("Connection: close"); 75 | builder.Append("HOST: "); 76 | builder.AppendLine(uri.Authority); 77 | foreach (string value in customValues) 78 | { 79 | builder.Append(customHeader); 80 | builder.Append(": "); 81 | builder.AppendLine(value); 82 | builder.AppendLine("Spacer-Header: spacervalue"); 83 | } 84 | builder.AppendLine(); 85 | 86 | byte[] request = Encoding.ASCII.GetBytes(builder.ToString()); 87 | 88 | Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); 89 | socket.Connect(uri.Host, uri.Port); 90 | 91 | socket.Send(request); 92 | 93 | byte[] response = new byte[1024 * 5]; 94 | await Task.Run(() => socket.Receive(response)); 95 | socket.Dispose(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/ResponseStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.AspNetCore.Server.HttpSys 10 | { 11 | internal class ResponseStream : Stream 12 | { 13 | private readonly Stream _innerStream; 14 | private readonly Func _onStart; 15 | 16 | internal ResponseStream(Stream innerStream, Func onStart) 17 | { 18 | _innerStream = innerStream; 19 | _onStart = onStart; 20 | } 21 | 22 | public override bool CanRead => _innerStream.CanRead; 23 | 24 | public override bool CanSeek => _innerStream.CanSeek; 25 | 26 | public override bool CanWrite => _innerStream.CanWrite; 27 | 28 | public override long Length => _innerStream.Length; 29 | 30 | public override long Position 31 | { 32 | get { return _innerStream.Position; } 33 | set { _innerStream.Position = value; } 34 | } 35 | 36 | public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin); 37 | 38 | public override void SetLength(long value) => _innerStream.SetLength(value); 39 | 40 | public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count); 41 | 42 | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) 43 | { 44 | return _innerStream.BeginRead(buffer, offset, count, callback, state); 45 | } 46 | 47 | public override int EndRead(IAsyncResult asyncResult) 48 | { 49 | return _innerStream.EndRead(asyncResult); 50 | } 51 | public override void Flush() 52 | { 53 | _onStart().GetAwaiter().GetResult(); 54 | _innerStream.Flush(); 55 | } 56 | 57 | public override async Task FlushAsync(CancellationToken cancellationToken) 58 | { 59 | await _onStart(); 60 | await _innerStream.FlushAsync(cancellationToken); 61 | } 62 | 63 | public override void Write(byte[] buffer, int offset, int count) 64 | { 65 | _onStart().GetAwaiter().GetResult(); 66 | _innerStream.Write(buffer, offset, count); 67 | } 68 | 69 | public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 70 | { 71 | await _onStart(); 72 | await _innerStream.WriteAsync(buffer, offset, count, cancellationToken); 73 | } 74 | 75 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) 76 | { 77 | return ToIAsyncResult(WriteAsync(buffer, offset, count), callback, state); 78 | } 79 | 80 | public override void EndWrite(IAsyncResult asyncResult) 81 | { 82 | if (asyncResult == null) 83 | { 84 | throw new ArgumentNullException(nameof(asyncResult)); 85 | } 86 | ((Task)asyncResult).GetAwaiter().GetResult(); 87 | } 88 | 89 | private static IAsyncResult ToIAsyncResult(Task task, AsyncCallback callback, object state) 90 | { 91 | var tcs = new TaskCompletionSource(state); 92 | task.ContinueWith(t => 93 | { 94 | if (t.IsFaulted) 95 | { 96 | tcs.TrySetException(t.Exception.InnerExceptions); 97 | } 98 | else if (t.IsCanceled) 99 | { 100 | tcs.TrySetCanceled(); 101 | } 102 | else 103 | { 104 | tcs.TrySetResult(0); 105 | } 106 | 107 | if (callback != null) 108 | { 109 | callback(tcs.Task); 110 | } 111 | }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); 112 | return tcs.Task; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/StandardFeatureCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using Microsoft.AspNetCore.Http.Features; 8 | using Microsoft.AspNetCore.Http.Features.Authentication; 9 | 10 | namespace Microsoft.AspNetCore.Server.HttpSys 11 | { 12 | internal sealed class StandardFeatureCollection : IFeatureCollection 13 | { 14 | private static readonly Func _identityFunc = ReturnIdentity; 15 | private static readonly Dictionary> _featureFuncLookup = new Dictionary>() 16 | { 17 | { typeof(IHttpRequestFeature), _identityFunc }, 18 | { typeof(IHttpConnectionFeature), _identityFunc }, 19 | { typeof(IHttpResponseFeature), _identityFunc }, 20 | { typeof(IHttpSendFileFeature), _identityFunc }, 21 | { typeof(ITlsConnectionFeature), ctx => ctx.GetTlsConnectionFeature() }, 22 | // { typeof(ITlsTokenBindingFeature), ctx => ctx.GetTlsTokenBindingFeature() }, TODO: https://github.com/aspnet/HttpSysServer/issues/231 23 | { typeof(IHttpBufferingFeature), _identityFunc }, 24 | { typeof(IHttpRequestLifetimeFeature), _identityFunc }, 25 | { typeof(IHttpAuthenticationFeature), _identityFunc }, 26 | { typeof(IHttpRequestIdentifierFeature), _identityFunc }, 27 | { typeof(RequestContext), ctx => ctx.RequestContext }, 28 | { typeof(IHttpMaxRequestBodySizeFeature), _identityFunc }, 29 | { typeof(IHttpBodyControlFeature), _identityFunc }, 30 | }; 31 | 32 | private readonly FeatureContext _featureContext; 33 | 34 | static StandardFeatureCollection() 35 | { 36 | if (ComNetOS.IsWin8orLater) 37 | { 38 | // Only add the upgrade feature if it stands a chance of working. 39 | // SignalR uses the presence of the feature to detect feature support. 40 | // https://github.com/aspnet/HttpSysServer/issues/427 41 | _featureFuncLookup[typeof(IHttpUpgradeFeature)] = _identityFunc; 42 | } 43 | } 44 | 45 | public StandardFeatureCollection(FeatureContext featureContext) 46 | { 47 | _featureContext = featureContext; 48 | } 49 | 50 | public bool IsReadOnly 51 | { 52 | get { return true; } 53 | } 54 | 55 | public int Revision 56 | { 57 | get { return 0; } 58 | } 59 | 60 | public object this[Type key] 61 | { 62 | get 63 | { 64 | Func lookupFunc; 65 | _featureFuncLookup.TryGetValue(key, out lookupFunc); 66 | return lookupFunc?.Invoke(_featureContext); 67 | } 68 | set 69 | { 70 | throw new InvalidOperationException("The collection is read-only"); 71 | } 72 | } 73 | 74 | private static object ReturnIdentity(FeatureContext featureContext) 75 | { 76 | return featureContext; 77 | } 78 | 79 | IEnumerator IEnumerable.GetEnumerator() 80 | { 81 | return ((IEnumerable>)this).GetEnumerator(); 82 | } 83 | 84 | IEnumerator> IEnumerable>.GetEnumerator() 85 | { 86 | foreach (var featureFunc in _featureFuncLookup) 87 | { 88 | var feature = featureFunc.Value(_featureContext); 89 | if (feature != null) 90 | { 91 | yield return new KeyValuePair(featureFunc.Key, feature); 92 | } 93 | } 94 | } 95 | 96 | public TFeature Get() 97 | { 98 | return (TFeature)this[typeof(TFeature)]; 99 | } 100 | 101 | public void Set(TFeature instance) 102 | { 103 | this[typeof(TFeature)] = instance; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/HttpSysSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Security; 9 | using Microsoft.Win32; 10 | 11 | namespace Microsoft.AspNetCore.Server.HttpSys 12 | { 13 | internal static class HttpSysSettings 14 | { 15 | private const string HttpSysParametersKey = @"System\CurrentControlSet\Services\HTTP\Parameters"; 16 | private const bool EnableNonUtf8Default = true; 17 | private const bool FavorUtf8Default = true; 18 | private const string EnableNonUtf8Name = "EnableNonUtf8"; 19 | private const string FavorUtf8Name = "FavorUtf8"; 20 | 21 | private static volatile bool enableNonUtf8 = EnableNonUtf8Default; 22 | private static volatile bool favorUtf8 = FavorUtf8Default; 23 | 24 | static HttpSysSettings() 25 | { 26 | ReadHttpSysRegistrySettings(); 27 | } 28 | 29 | internal static bool EnableNonUtf8 30 | { 31 | get { return enableNonUtf8; } 32 | } 33 | 34 | internal static bool FavorUtf8 35 | { 36 | get { return favorUtf8; } 37 | } 38 | 39 | private static void ReadHttpSysRegistrySettings() 40 | { 41 | try 42 | { 43 | RegistryKey httpSysParameters = Registry.LocalMachine.OpenSubKey(HttpSysParametersKey); 44 | 45 | if (httpSysParameters == null) 46 | { 47 | LogWarning("ReadHttpSysRegistrySettings", "The Http.Sys registry key is null.", 48 | HttpSysParametersKey); 49 | } 50 | else 51 | { 52 | using (httpSysParameters) 53 | { 54 | enableNonUtf8 = ReadRegistryValue(httpSysParameters, EnableNonUtf8Name, EnableNonUtf8Default); 55 | favorUtf8 = ReadRegistryValue(httpSysParameters, FavorUtf8Name, FavorUtf8Default); 56 | } 57 | } 58 | } 59 | catch (SecurityException e) 60 | { 61 | LogRegistryException("ReadHttpSysRegistrySettings", e); 62 | } 63 | catch (ObjectDisposedException e) 64 | { 65 | LogRegistryException("ReadHttpSysRegistrySettings", e); 66 | } 67 | } 68 | 69 | private static bool ReadRegistryValue(RegistryKey key, string valueName, bool defaultValue) 70 | { 71 | Debug.Assert(key != null, "'key' must not be null"); 72 | 73 | try 74 | { 75 | if (key.GetValue(valueName) != null && key.GetValueKind(valueName) == RegistryValueKind.DWord) 76 | { 77 | // At this point we know the Registry value exists and it must be valid (any DWORD value 78 | // can be converted to a bool). 79 | return Convert.ToBoolean(key.GetValue(valueName), CultureInfo.InvariantCulture); 80 | } 81 | } 82 | catch (UnauthorizedAccessException e) 83 | { 84 | LogRegistryException("ReadRegistryValue", e); 85 | } 86 | catch (IOException e) 87 | { 88 | LogRegistryException("ReadRegistryValue", e); 89 | } 90 | catch (SecurityException e) 91 | { 92 | LogRegistryException("ReadRegistryValue", e); 93 | } 94 | catch (ObjectDisposedException e) 95 | { 96 | LogRegistryException("ReadRegistryValue", e); 97 | } 98 | 99 | return defaultValue; 100 | } 101 | 102 | private static void LogRegistryException(string methodName, Exception e) 103 | { 104 | LogWarning(methodName, "Unable to access the Http.Sys registry value.", HttpSysParametersKey, e); 105 | } 106 | 107 | private static void LogWarning(string methodName, string message, params object[] args) 108 | { 109 | // TODO: log 110 | // Logging.PrintWarning(Logging.HttpListener, typeof(HttpSysSettings), methodName, SR.GetString(message, args)); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/Helpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Microsoft.AspNetCore.Server.HttpSys 11 | { 12 | internal static class Helpers 13 | { 14 | internal static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }; 15 | internal static readonly byte[] CRLF = new byte[] { (byte)'\r', (byte)'\n' }; 16 | 17 | internal static ConfiguredTaskAwaitable SupressContext(this Task task) 18 | { 19 | return task.ConfigureAwait(continueOnCapturedContext: false); 20 | } 21 | 22 | internal static ConfiguredTaskAwaitable SupressContext(this Task task) 23 | { 24 | return task.ConfigureAwait(continueOnCapturedContext: false); 25 | } 26 | 27 | internal static IAsyncResult ToIAsyncResult(this Task task, AsyncCallback callback, object state) 28 | { 29 | var tcs = new TaskCompletionSource(state); 30 | task.ContinueWith(t => 31 | { 32 | if (t.IsFaulted) 33 | { 34 | tcs.TrySetException(t.Exception.InnerExceptions); 35 | } 36 | else if (t.IsCanceled) 37 | { 38 | tcs.TrySetCanceled(); 39 | } 40 | else 41 | { 42 | tcs.TrySetResult(0); 43 | } 44 | 45 | if (callback != null) 46 | { 47 | callback(tcs.Task); 48 | } 49 | }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); 50 | return tcs.Task; 51 | } 52 | 53 | internal static ArraySegment GetChunkHeader(long size) 54 | { 55 | if (size < int.MaxValue) 56 | { 57 | return GetChunkHeader((int)size); 58 | } 59 | 60 | // Greater than 2gb, perf is no longer our concern 61 | return new ArraySegment(Encoding.ASCII.GetBytes(size.ToString("X") + "\r\n")); 62 | } 63 | 64 | /// 65 | /// A private utility routine to convert an integer to a chunk header, 66 | /// which is an ASCII hex number followed by a CRLF.The header is returned 67 | /// as a byte array. 68 | /// Generates a right-aligned hex string and returns the start offset. 69 | /// 70 | /// Chunk size to be encoded 71 | /// A byte array with the header in int. 72 | internal static ArraySegment GetChunkHeader(int size) 73 | { 74 | uint mask = 0xf0000000; 75 | byte[] header = new byte[10]; 76 | int i; 77 | int offset = -1; 78 | 79 | // Loop through the size, looking at each nibble. If it's not 0 80 | // convert it to hex. Save the index of the first non-zero 81 | // byte. 82 | 83 | for (i = 0; i < 8; i++, size <<= 4) 84 | { 85 | // offset == -1 means that we haven't found a non-zero nibble 86 | // yet. If we haven't found one, and the current one is zero, 87 | // don't do anything. 88 | 89 | if (offset == -1) 90 | { 91 | if ((size & mask) == 0) 92 | { 93 | continue; 94 | } 95 | } 96 | 97 | // Either we have a non-zero nibble or we're no longer skipping 98 | // leading zeros. Convert this nibble to ASCII and save it. 99 | 100 | uint temp = (uint)size >> 28; 101 | 102 | if (temp < 10) 103 | { 104 | header[i] = (byte)(temp + '0'); 105 | } 106 | else 107 | { 108 | header[i] = (byte)((temp - 10) + 'A'); 109 | } 110 | 111 | // If we haven't found a non-zero nibble yet, we've found one 112 | // now, so remember that. 113 | 114 | if (offset == -1) 115 | { 116 | offset = i; 117 | } 118 | } 119 | 120 | header[8] = (byte)'\r'; 121 | header[9] = (byte)'\n'; 122 | 123 | return new ArraySegment(header, offset, header.Length - offset); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/UrlPrefixCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace Microsoft.AspNetCore.Server.HttpSys 8 | { 9 | /// 10 | /// A collection or URL prefixes 11 | /// 12 | public class UrlPrefixCollection : ICollection 13 | { 14 | private readonly IDictionary _prefixes = new Dictionary(1); 15 | private UrlGroup _urlGroup; 16 | private int _nextId = 1; 17 | 18 | internal UrlPrefixCollection() 19 | { 20 | } 21 | 22 | public int Count 23 | { 24 | get 25 | { 26 | lock (_prefixes) 27 | { 28 | return _prefixes.Count; 29 | } 30 | } 31 | } 32 | 33 | public bool IsReadOnly 34 | { 35 | get { return false; } 36 | } 37 | 38 | public void Add(string prefix) 39 | { 40 | Add(UrlPrefix.Create(prefix)); 41 | } 42 | 43 | public void Add(UrlPrefix item) 44 | { 45 | lock (_prefixes) 46 | { 47 | var id = _nextId++; 48 | if (_urlGroup != null) 49 | { 50 | _urlGroup.RegisterPrefix(item.FullPrefix, id); 51 | } 52 | _prefixes.Add(id, item); 53 | } 54 | } 55 | 56 | internal UrlPrefix GetPrefix(int id) 57 | { 58 | lock (_prefixes) 59 | { 60 | return _prefixes[id]; 61 | } 62 | } 63 | 64 | public void Clear() 65 | { 66 | lock (_prefixes) 67 | { 68 | if (_urlGroup != null) 69 | { 70 | UnregisterAllPrefixes(); 71 | } 72 | _prefixes.Clear(); 73 | } 74 | } 75 | 76 | public bool Contains(UrlPrefix item) 77 | { 78 | lock (_prefixes) 79 | { 80 | return _prefixes.Values.Contains(item); 81 | } 82 | } 83 | 84 | public void CopyTo(UrlPrefix[] array, int arrayIndex) 85 | { 86 | lock (_prefixes) 87 | { 88 | _prefixes.Values.CopyTo(array, arrayIndex); 89 | } 90 | } 91 | 92 | public bool Remove(string prefix) 93 | { 94 | return Remove(UrlPrefix.Create(prefix)); 95 | } 96 | 97 | public bool Remove(UrlPrefix item) 98 | { 99 | lock (_prefixes) 100 | { 101 | int? id = null; 102 | foreach (var pair in _prefixes) 103 | { 104 | if (pair.Value.Equals(item)) 105 | { 106 | id = pair.Key; 107 | if (_urlGroup != null) 108 | { 109 | _urlGroup.UnregisterPrefix(pair.Value.FullPrefix); 110 | } 111 | } 112 | } 113 | if (id.HasValue) 114 | { 115 | _prefixes.Remove(id.Value); 116 | return true; 117 | } 118 | return false; 119 | } 120 | } 121 | 122 | public IEnumerator GetEnumerator() 123 | { 124 | lock (_prefixes) 125 | { 126 | return _prefixes.Values.GetEnumerator(); 127 | } 128 | } 129 | 130 | IEnumerator IEnumerable.GetEnumerator() 131 | { 132 | return GetEnumerator(); 133 | } 134 | 135 | internal void RegisterAllPrefixes(UrlGroup urlGroup) 136 | { 137 | lock (_prefixes) 138 | { 139 | _urlGroup = urlGroup; 140 | // go through the uri list and register for each one of them 141 | foreach (var pair in _prefixes) 142 | { 143 | // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. 144 | _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); 145 | } 146 | } 147 | } 148 | 149 | internal void UnregisterAllPrefixes() 150 | { 151 | lock (_prefixes) 152 | { 153 | // go through the uri list and unregister for each one of them 154 | foreach (var prefix in _prefixes.Values) 155 | { 156 | // ignore possible failures 157 | _urlGroup.UnregisterPrefix(prefix.FullPrefix); 158 | } 159 | } 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Listener/Utilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Microsoft.AspNetCore.Server.HttpSys.Listener 9 | { 10 | internal static class Utilities 11 | { 12 | internal static readonly int WriteRetryLimit = 1000; 13 | 14 | // When tests projects are run in parallel, overlapping port ranges can cause a race condition when looking for free 15 | // ports during dynamic port allocation. 16 | private const int BasePort = 8001; 17 | private const int MaxPort = 11000; 18 | private static int NextPort = BasePort; 19 | private static object PortLock = new object(); 20 | 21 | internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); 22 | // Minimum support for Windows 7 is assumed. 23 | internal static readonly bool IsWin8orLater; 24 | 25 | static Utilities() 26 | { 27 | var win8Version = new Version(6, 2); 28 | IsWin8orLater = (Environment.OSVersion.Version >= win8Version); 29 | } 30 | 31 | internal static HttpSysListener CreateHttpServer(out string baseAddress) 32 | { 33 | string root; 34 | return CreateDynamicHttpServer(string.Empty, out root, out baseAddress); 35 | } 36 | 37 | internal static HttpSysListener CreateHttpServerReturnRoot(string path, out string root) 38 | { 39 | string baseAddress; 40 | return CreateDynamicHttpServer(path, out root, out baseAddress); 41 | } 42 | 43 | internal static HttpSysListener CreateDynamicHttpServer(string basePath, out string root, out string baseAddress) 44 | { 45 | lock (PortLock) 46 | { 47 | while (NextPort < MaxPort) 48 | { 49 | var port = NextPort++; 50 | var prefix = UrlPrefix.Create("http", "localhost", port, basePath); 51 | root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; 52 | baseAddress = prefix.ToString(); 53 | var listener = new HttpSysListener(new HttpSysOptions(), new LoggerFactory()); 54 | listener.Options.UrlPrefixes.Add(prefix); 55 | try 56 | { 57 | listener.Start(); 58 | return listener; 59 | } 60 | catch (HttpSysException) 61 | { 62 | listener.Dispose(); 63 | } 64 | } 65 | NextPort = BasePort; 66 | } 67 | throw new Exception("Failed to locate a free port."); 68 | } 69 | 70 | internal static HttpSysListener CreateHttpsServer() 71 | { 72 | return CreateServer("https", "localhost", 9090, string.Empty); 73 | } 74 | 75 | internal static HttpSysListener CreateServer(string scheme, string host, int port, string path) 76 | { 77 | var listener = new HttpSysListener(new HttpSysOptions(), new LoggerFactory()); 78 | listener.Options.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path)); 79 | listener.Start(); 80 | return listener; 81 | } 82 | 83 | /// 84 | /// AcceptAsync extension with timeout. This extension should be used in all tests to prevent 85 | /// unexpected hangs when a request does not arrive. 86 | /// 87 | internal static async Task AcceptAsync(this HttpSysListener server, TimeSpan timeout) 88 | { 89 | var acceptTask = server.AcceptAsync(); 90 | var completedTask = await Task.WhenAny(acceptTask, Task.Delay(timeout)); 91 | 92 | if (completedTask == acceptTask) 93 | { 94 | return await acceptTask; 95 | } 96 | else 97 | { 98 | server.Dispose(); 99 | throw new TimeoutException("AcceptAsync has timed out."); 100 | } 101 | } 102 | 103 | // Fail if the given response task completes before the given accept task. 104 | internal static async Task Before(this Task acceptTask, Task responseTask) 105 | { 106 | var completedTask = await Task.WhenAny(acceptTask, responseTask); 107 | 108 | if (completedTask == acceptTask) 109 | { 110 | return await acceptTask; 111 | } 112 | else 113 | { 114 | var response = await responseTask; 115 | throw new InvalidOperationException("The response completed prematurely: " + response.ToString()); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /samples/HotAddSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Server.HttpSys; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace HotAddSample 10 | { 11 | // This sample shows how to dynamically add or remove prefixes for the underlying server. 12 | // Be careful not to remove the prefix you're currently accessing because the connection 13 | // will be reset before the end of the request. 14 | public class Startup 15 | { 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | services.Configure(options => 19 | { 20 | ServerOptions = options; 21 | }); 22 | } 23 | 24 | public HttpSysOptions ServerOptions { get; set; } 25 | 26 | public void Configure(IApplicationBuilder app) 27 | { 28 | var addresses = ServerOptions.UrlPrefixes; 29 | addresses.Add("http://localhost:12346/pathBase/"); 30 | 31 | app.Use(async (context, next) => 32 | { 33 | // Note: To add any prefix other than localhost you must run this sample as an administrator. 34 | var toAdd = context.Request.Query["add"]; 35 | if (!string.IsNullOrEmpty(toAdd)) 36 | { 37 | context.Response.ContentType = "text/html"; 38 | await context.Response.WriteAsync(""); 39 | try 40 | { 41 | addresses.Add(toAdd); 42 | await context.Response.WriteAsync("Added: " + toAdd + ""); 43 | } 44 | catch (Exception ex) 45 | { 46 | await context.Response.WriteAsync("Error adding: " + toAdd + "
"); 47 | await context.Response.WriteAsync(ex.ToString().Replace(Environment.NewLine, "
")); 48 | } 49 | await context.Response.WriteAsync("
back"); 50 | await context.Response.WriteAsync(""); 51 | return; 52 | } 53 | await next(); 54 | }); 55 | 56 | app.Use(async (context, next) => 57 | { 58 | // Be careful not to remove the prefix you're currently accessing because the connection 59 | // will be reset before the response is sent. 60 | var toRemove = context.Request.Query["remove"]; 61 | if (!string.IsNullOrEmpty(toRemove)) 62 | { 63 | context.Response.ContentType = "text/html"; 64 | await context.Response.WriteAsync(""); 65 | if (addresses.Remove(toRemove)) 66 | { 67 | await context.Response.WriteAsync("Removed: " + toRemove); 68 | } 69 | else 70 | { 71 | await context.Response.WriteAsync("Not found: " + toRemove); 72 | } 73 | await context.Response.WriteAsync("
back"); 74 | await context.Response.WriteAsync(""); 75 | return; 76 | } 77 | await next(); 78 | }); 79 | 80 | app.Run(async context => 81 | { 82 | context.Response.ContentType = "text/html"; 83 | await context.Response.WriteAsync(""); 84 | await context.Response.WriteAsync("Listening on these prefixes:
"); 85 | foreach (var prefix in addresses) 86 | { 87 | await context.Response.WriteAsync("" + prefix + " (remove)
"); 88 | } 89 | 90 | await context.Response.WriteAsync("
"); 91 | await context.Response.WriteAsync(""); 92 | await context.Response.WriteAsync(""); 93 | await context.Response.WriteAsync("
"); 94 | 95 | await context.Response.WriteAsync(""); 96 | }); 97 | } 98 | 99 | public static void Main(string[] args) 100 | { 101 | var host = new WebHostBuilder() 102 | .ConfigureLogging(factory => factory.AddConsole()) 103 | .UseStartup() 104 | .UseHttpSys() 105 | .Build(); 106 | 107 | host.Run(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/MessagePumpTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using System.Threading; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Hosting.Server.Features; 8 | using Microsoft.AspNetCore.HttpSys.Internal; 9 | using Microsoft.AspNetCore.Testing.xunit; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using Xunit; 13 | 14 | namespace Microsoft.AspNetCore.Server.HttpSys 15 | { 16 | public class MessagePumpTests 17 | { 18 | [ConditionalFact] 19 | public void OverridingDirectConfigurationWithIServerAddressesFeatureSucceeds() 20 | { 21 | var serverAddress = "http://localhost:11001/"; 22 | var overrideAddress = "http://localhost:11002/"; 23 | 24 | using (var server = Utilities.CreatePump()) 25 | { 26 | var serverAddressesFeature = server.Features.Get(); 27 | serverAddressesFeature.Addresses.Add(overrideAddress); 28 | serverAddressesFeature.PreferHostingUrls = true; 29 | server.Listener.Options.UrlPrefixes.Add(serverAddress); 30 | 31 | server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); 32 | 33 | Assert.Equal(overrideAddress, serverAddressesFeature.Addresses.Single()); 34 | } 35 | } 36 | 37 | [ConditionalTheory] 38 | [InlineData("http://localhost:11001/")] 39 | [InlineData("invalid address")] 40 | [InlineData("")] 41 | [InlineData(null)] 42 | public void DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_IfPreferHostinUrlsFalse(string overrideAddress) 43 | { 44 | var serverAddress = "http://localhost:11002/"; 45 | 46 | using (var server = Utilities.CreatePump()) 47 | { 48 | var serverAddressesFeature = server.Features.Get(); 49 | serverAddressesFeature.Addresses.Add(overrideAddress); 50 | server.Listener.Options.UrlPrefixes.Add(serverAddress); 51 | 52 | server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); 53 | 54 | Assert.Equal(serverAddress, serverAddressesFeature.Addresses.Single()); 55 | } 56 | } 57 | 58 | [ConditionalFact] 59 | public void DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_IfAddressesIsEmpty() 60 | { 61 | var serverAddress = "http://localhost:11002/"; 62 | 63 | using (var server = Utilities.CreatePump()) 64 | { 65 | var serverAddressesFeature = server.Features.Get(); 66 | serverAddressesFeature.PreferHostingUrls = true; 67 | server.Listener.Options.UrlPrefixes.Add(serverAddress); 68 | 69 | server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); 70 | 71 | Assert.Equal(serverAddress, serverAddressesFeature.Addresses.Single()); 72 | } 73 | } 74 | 75 | [ConditionalTheory] 76 | [InlineData("http://localhost:11001/")] 77 | [InlineData("invalid address")] 78 | [InlineData("")] 79 | [InlineData(null)] 80 | public void OverridingIServerAdressesFeatureWithDirectConfiguration_WarnsOnStart(string serverAddress) 81 | { 82 | var overrideAddress = "http://localhost:11002/"; 83 | 84 | using (var server = Utilities.CreatePump()) 85 | { 86 | var serverAddressesFeature = server.Features.Get(); 87 | serverAddressesFeature.Addresses.Add(serverAddress); 88 | server.Listener.Options.UrlPrefixes.Add(overrideAddress); 89 | 90 | server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); 91 | 92 | Assert.Equal(overrideAddress, serverAddressesFeature.Addresses.Single()); 93 | } 94 | } 95 | 96 | [ConditionalFact] 97 | public void UseIServerAdressesFeature_WhenNoDirectConfiguration() 98 | { 99 | var serverAddress = "http://localhost:11001/"; 100 | 101 | using (var server = Utilities.CreatePump()) 102 | { 103 | var serverAddressesFeature = server.Features.Get(); 104 | serverAddressesFeature.Addresses.Add(serverAddress); 105 | 106 | server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); 107 | } 108 | } 109 | 110 | [ConditionalFact] 111 | public void UseDefaultAddress_WhenNoServerAddressAndNoDirectConfiguration() 112 | { 113 | using (var server = Utilities.CreatePump()) 114 | { 115 | server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); 116 | 117 | Assert.Equal(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); 118 | } 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/UrlGroup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Runtime.InteropServices; 7 | using Microsoft.AspNetCore.HttpSys.Internal; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Microsoft.AspNetCore.Server.HttpSys 11 | { 12 | internal class UrlGroup : IDisposable 13 | { 14 | private static readonly int QosInfoSize = 15 | Marshal.SizeOf(); 16 | 17 | private ServerSession _serverSession; 18 | private ILogger _logger; 19 | private bool _disposed; 20 | 21 | internal unsafe UrlGroup(ServerSession serverSession, ILogger logger) 22 | { 23 | _serverSession = serverSession; 24 | _logger = logger; 25 | 26 | ulong urlGroupId = 0; 27 | var statusCode = HttpApi.HttpCreateUrlGroup( 28 | _serverSession.Id.DangerousGetServerSessionId(), &urlGroupId, 0); 29 | 30 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 31 | { 32 | throw new HttpSysException((int)statusCode); 33 | } 34 | 35 | Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup"); 36 | Id = urlGroupId; 37 | } 38 | 39 | internal ulong Id { get; private set; } 40 | 41 | internal unsafe void SetMaxConnections(long maxConnections) 42 | { 43 | var connectionLimit = new HttpApiTypes.HTTP_CONNECTION_LIMIT_INFO(); 44 | connectionLimit.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; 45 | connectionLimit.MaxConnections = (uint)maxConnections; 46 | 47 | var qosSettings = new HttpApiTypes.HTTP_QOS_SETTING_INFO(); 48 | qosSettings.QosType = HttpApiTypes.HTTP_QOS_SETTING_TYPE.HttpQosSettingTypeConnectionLimit; 49 | qosSettings.QosSetting = new IntPtr(&connectionLimit); 50 | 51 | SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize); 52 | } 53 | 54 | internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true) 55 | { 56 | Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer"); 57 | CheckDisposed(); 58 | 59 | var statusCode = HttpApi.HttpSetUrlGroupProperty(Id, property, info, infosize); 60 | 61 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 62 | { 63 | var exception = new HttpSysException((int)statusCode); 64 | LogHelper.LogException(_logger, "SetUrlGroupProperty", exception); 65 | if (throwOnError) 66 | { 67 | throw exception; 68 | } 69 | } 70 | } 71 | 72 | internal void RegisterPrefix(string uriPrefix, int contextId) 73 | { 74 | LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix); 75 | CheckDisposed(); 76 | 77 | var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0); 78 | 79 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 80 | { 81 | if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) 82 | { 83 | throw new HttpSysException((int)statusCode, string.Format(Resources.Exception_PrefixAlreadyRegistered, uriPrefix)); 84 | } 85 | else 86 | { 87 | throw new HttpSysException((int)statusCode); 88 | } 89 | } 90 | } 91 | 92 | internal bool UnregisterPrefix(string uriPrefix) 93 | { 94 | LogHelper.LogInfo(_logger, "Stop listening on prefix: " + uriPrefix); 95 | CheckDisposed(); 96 | 97 | var statusCode = HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0); 98 | 99 | if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND) 100 | { 101 | return false; 102 | } 103 | return true; 104 | } 105 | 106 | public void Dispose() 107 | { 108 | if (_disposed) 109 | { 110 | return; 111 | } 112 | 113 | _disposed = true; 114 | 115 | Debug.Assert(Id != 0, "HttpCloseUrlGroup called with invalid url group id"); 116 | 117 | uint statusCode = HttpApi.HttpCloseUrlGroup(Id); 118 | 119 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 120 | { 121 | LogHelper.LogError(_logger, "CleanupV2Config", "Result: " + statusCode); 122 | } 123 | Id = 0; 124 | } 125 | 126 | private void CheckDisposed() 127 | { 128 | if (_disposed) 129 | { 130 | throw new ObjectDisposedException(this.GetType().FullName); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/AuthenticationManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Security.Claims; 10 | using System.Security.Principal; 11 | using Microsoft.AspNetCore.HttpSys.Internal; 12 | using Microsoft.Extensions.Primitives; 13 | 14 | namespace Microsoft.AspNetCore.Server.HttpSys 15 | { 16 | // See the native HTTP_SERVER_AUTHENTICATION_INFO structure documentation for additional information. 17 | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364638(v=vs.85).aspx 18 | 19 | /// 20 | /// Exposes the Http.Sys authentication configurations. 21 | /// 22 | public sealed class AuthenticationManager 23 | { 24 | private static readonly int AuthInfoSize = 25 | Marshal.SizeOf(); 26 | 27 | private UrlGroup _urlGroup; 28 | private AuthenticationSchemes _authSchemes; 29 | private bool _allowAnonymous = true; 30 | 31 | internal AuthenticationManager() 32 | { 33 | } 34 | 35 | public AuthenticationSchemes Schemes 36 | { 37 | get { return _authSchemes; } 38 | set 39 | { 40 | _authSchemes = value; 41 | SetUrlGroupSecurity(); 42 | } 43 | } 44 | 45 | public bool AllowAnonymous 46 | { 47 | get { return _allowAnonymous; } 48 | set { _allowAnonymous = value; } 49 | } 50 | 51 | internal void SetUrlGroupSecurity(UrlGroup urlGroup) 52 | { 53 | Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once."); 54 | _urlGroup = urlGroup; 55 | SetUrlGroupSecurity(); 56 | } 57 | 58 | private unsafe void SetUrlGroupSecurity() 59 | { 60 | if (_urlGroup == null) 61 | { 62 | // Not started yet. 63 | return; 64 | } 65 | 66 | HttpApiTypes.HTTP_SERVER_AUTHENTICATION_INFO authInfo = 67 | new HttpApiTypes.HTTP_SERVER_AUTHENTICATION_INFO(); 68 | 69 | authInfo.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; 70 | var authSchemes = (HttpApiTypes.HTTP_AUTH_TYPES)_authSchemes; 71 | if (authSchemes != HttpApiTypes.HTTP_AUTH_TYPES.NONE) 72 | { 73 | authInfo.AuthSchemes = authSchemes; 74 | 75 | // TODO: 76 | // NTLM auth sharing (on by default?) DisableNTLMCredentialCaching 77 | // Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING 78 | // Mutual Auth - ReceiveMutualAuth 79 | // Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS 80 | // Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS 81 | 82 | IntPtr infoptr = new IntPtr(&authInfo); 83 | 84 | _urlGroup.SetProperty( 85 | HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty, 86 | infoptr, (uint)AuthInfoSize); 87 | } 88 | } 89 | 90 | internal static IList GenerateChallenges(AuthenticationSchemes authSchemes) 91 | { 92 | IList challenges = new List(); 93 | 94 | if (authSchemes == AuthenticationSchemes.None) 95 | { 96 | return challenges; 97 | } 98 | 99 | // Order by strength. 100 | if ((authSchemes & AuthenticationSchemes.Kerberos) == AuthenticationSchemes.Kerberos) 101 | { 102 | challenges.Add("Kerberos"); 103 | } 104 | if ((authSchemes & AuthenticationSchemes.Negotiate) == AuthenticationSchemes.Negotiate) 105 | { 106 | challenges.Add("Negotiate"); 107 | } 108 | if ((authSchemes & AuthenticationSchemes.NTLM) == AuthenticationSchemes.NTLM) 109 | { 110 | challenges.Add("NTLM"); 111 | } 112 | /*if ((_authSchemes & AuthenticationSchemes.Digest) == AuthenticationSchemes.Digest) 113 | { 114 | // TODO: 115 | throw new NotImplementedException("Digest challenge generation has not been implemented."); 116 | // challenges.Add("Digest"); 117 | }*/ 118 | if ((authSchemes & AuthenticationSchemes.Basic) == AuthenticationSchemes.Basic) 119 | { 120 | // TODO: Realm 121 | challenges.Add("Basic"); 122 | } 123 | return challenges; 124 | } 125 | 126 | internal void SetAuthenticationChallenge(RequestContext context) 127 | { 128 | IList challenges = GenerateChallenges(context.Response.AuthenticationChallenges); 129 | 130 | if (challenges.Count > 0) 131 | { 132 | context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate] 133 | = StringValues.Concat(context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate], challenges.ToArray()); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/RequestQueue.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | using Microsoft.AspNetCore.HttpSys.Internal; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Microsoft.AspNetCore.Server.HttpSys 11 | { 12 | internal class RequestQueue 13 | { 14 | private static readonly int BindingInfoSize = 15 | Marshal.SizeOf(); 16 | 17 | private readonly UrlGroup _urlGroup; 18 | private readonly ILogger _logger; 19 | private bool _disposed; 20 | 21 | internal RequestQueue(UrlGroup urlGroup, ILogger logger) 22 | { 23 | _urlGroup = urlGroup; 24 | _logger = logger; 25 | 26 | HttpRequestQueueV2Handle requestQueueHandle = null; 27 | var statusCode = HttpApi.HttpCreateRequestQueue( 28 | HttpApi.Version, null, null, 0, out requestQueueHandle); 29 | 30 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 31 | { 32 | throw new HttpSysException((int)statusCode); 33 | } 34 | 35 | // Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS) 36 | if (HttpSysListener.SkipIOCPCallbackOnSuccess && 37 | !UnsafeNclNativeMethods.SetFileCompletionNotificationModes( 38 | requestQueueHandle, 39 | UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipCompletionPortOnSuccess | 40 | UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipSetEventOnHandle)) 41 | { 42 | throw new HttpSysException(Marshal.GetLastWin32Error()); 43 | } 44 | 45 | Handle = requestQueueHandle; 46 | BoundHandle = ThreadPoolBoundHandle.BindHandle(Handle); 47 | } 48 | 49 | internal SafeHandle Handle { get; } 50 | internal ThreadPoolBoundHandle BoundHandle { get; } 51 | 52 | internal unsafe void AttachToUrlGroup() 53 | { 54 | CheckDisposed(); 55 | // Set the association between request queue and url group. After this, requests for registered urls will 56 | // get delivered to this request queue. 57 | 58 | var info = new HttpApiTypes.HTTP_BINDING_INFO(); 59 | info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; 60 | info.RequestQueueHandle = Handle.DangerousGetHandle(); 61 | 62 | var infoptr = new IntPtr(&info); 63 | 64 | _urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, 65 | infoptr, (uint)BindingInfoSize); 66 | } 67 | 68 | internal unsafe void DetachFromUrlGroup() 69 | { 70 | CheckDisposed(); 71 | // Break the association between request queue and url group. After this, requests for registered urls 72 | // will get 503s. 73 | // Note that this method may be called multiple times (Stop() and then Abort()). This 74 | // is fine since http.sys allows to set HttpServerBindingProperty multiple times for valid 75 | // Url groups. 76 | 77 | var info = new HttpApiTypes.HTTP_BINDING_INFO(); 78 | info.Flags = HttpApiTypes.HTTP_FLAGS.NONE; 79 | info.RequestQueueHandle = IntPtr.Zero; 80 | 81 | var infoptr = new IntPtr(&info); 82 | 83 | _urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, 84 | infoptr, (uint)BindingInfoSize, throwOnError: false); 85 | } 86 | 87 | // The listener must be active for this to work. 88 | internal unsafe void SetLengthLimit(long length) 89 | { 90 | CheckDisposed(); 91 | 92 | var result = HttpApi.HttpSetRequestQueueProperty(Handle, 93 | HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty, 94 | new IntPtr((void*)&length), (uint)Marshal.SizeOf(), 0, IntPtr.Zero); 95 | 96 | if (result != 0) 97 | { 98 | throw new HttpSysException((int)result); 99 | } 100 | } 101 | 102 | // The listener must be active for this to work. 103 | internal unsafe void SetRejectionVerbosity(Http503VerbosityLevel verbosity) 104 | { 105 | CheckDisposed(); 106 | 107 | var result = HttpApi.HttpSetRequestQueueProperty(Handle, 108 | HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServer503VerbosityProperty, 109 | new IntPtr((void*)&verbosity), (uint)Marshal.SizeOf(), 0, IntPtr.Zero); 110 | 111 | if (result != 0) 112 | { 113 | throw new HttpSysException((int)result); 114 | } 115 | } 116 | 117 | public void Dispose() 118 | { 119 | if (_disposed) 120 | { 121 | return; 122 | } 123 | 124 | _disposed = true; 125 | BoundHandle.Dispose(); 126 | Handle.Dispose(); 127 | } 128 | 129 | private void CheckDisposed() 130 | { 131 | if (_disposed) 132 | { 133 | throw new ObjectDisposedException(this.GetType().FullName); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/RequestProcessing/OpaqueStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.AspNetCore.Server.HttpSys 10 | { 11 | // A duplex wrapper around RequestStream and ResponseStream. 12 | // TODO: Consider merging RequestStream and ResponseStream instead. 13 | internal class OpaqueStream : Stream 14 | { 15 | private readonly Stream _requestStream; 16 | private readonly Stream _responseStream; 17 | 18 | internal OpaqueStream(Stream requestStream, Stream responseStream) 19 | { 20 | _requestStream = requestStream; 21 | _responseStream = responseStream; 22 | } 23 | 24 | #region Properties 25 | 26 | public override bool CanRead 27 | { 28 | get { return _requestStream.CanRead; } 29 | } 30 | 31 | public override bool CanSeek 32 | { 33 | get { return false; } 34 | } 35 | 36 | public override bool CanTimeout 37 | { 38 | get { return _requestStream.CanTimeout || _responseStream.CanTimeout; } 39 | } 40 | 41 | public override bool CanWrite 42 | { 43 | get { return _responseStream.CanWrite; } 44 | } 45 | 46 | public override long Length 47 | { 48 | get { throw new NotSupportedException(Resources.Exception_NoSeek); } 49 | } 50 | 51 | public override long Position 52 | { 53 | get { throw new NotSupportedException(Resources.Exception_NoSeek); } 54 | set { throw new NotSupportedException(Resources.Exception_NoSeek); } 55 | } 56 | 57 | public override int ReadTimeout 58 | { 59 | get { return _requestStream.ReadTimeout; } 60 | set { _requestStream.ReadTimeout = value; } 61 | } 62 | 63 | public override int WriteTimeout 64 | { 65 | get { return _responseStream.WriteTimeout; } 66 | set { _responseStream.WriteTimeout = value; } 67 | } 68 | 69 | #endregion Properties 70 | 71 | public override long Seek(long offset, SeekOrigin origin) 72 | { 73 | throw new NotSupportedException(Resources.Exception_NoSeek); 74 | } 75 | 76 | public override void SetLength(long value) 77 | { 78 | throw new NotSupportedException(Resources.Exception_NoSeek); 79 | } 80 | 81 | #region Read 82 | 83 | public override int Read(byte[] buffer, int offset, int count) 84 | { 85 | return _requestStream.Read(buffer, offset, count); 86 | } 87 | 88 | public override int ReadByte() 89 | { 90 | return _requestStream.ReadByte(); 91 | } 92 | 93 | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) 94 | { 95 | return _requestStream.BeginRead(buffer, offset, count, callback, state); 96 | } 97 | 98 | public override int EndRead(IAsyncResult asyncResult) 99 | { 100 | return _requestStream.EndRead(asyncResult); 101 | } 102 | 103 | public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 104 | { 105 | return _requestStream.ReadAsync(buffer, offset, count, cancellationToken); 106 | } 107 | 108 | public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) 109 | { 110 | return _requestStream.CopyToAsync(destination, bufferSize, cancellationToken); 111 | } 112 | 113 | #endregion Read 114 | 115 | #region Write 116 | 117 | public override void Write(byte[] buffer, int offset, int count) 118 | { 119 | _responseStream.Write(buffer, offset, count); 120 | } 121 | 122 | public override void WriteByte(byte value) 123 | { 124 | _responseStream.WriteByte(value); 125 | } 126 | 127 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) 128 | { 129 | return _responseStream.BeginWrite(buffer, offset, count, callback, state); 130 | } 131 | 132 | public override void EndWrite(IAsyncResult asyncResult) 133 | { 134 | _responseStream.EndWrite(asyncResult); 135 | } 136 | 137 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 138 | { 139 | return _responseStream.WriteAsync(buffer, offset, count, cancellationToken); 140 | } 141 | 142 | public override void Flush() 143 | { 144 | _responseStream.Flush(); 145 | } 146 | 147 | public override Task FlushAsync(CancellationToken cancellationToken) 148 | { 149 | return _responseStream.FlushAsync(cancellationToken); 150 | } 151 | 152 | #endregion Write 153 | 154 | protected override void Dispose(bool disposing) 155 | { 156 | // TODO: Suppress dispose? 157 | if (disposing) 158 | { 159 | _requestStream.Dispose(); 160 | _responseStream.Dispose(); 161 | } 162 | base.Dispose(disposing); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Listener/RequestHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Testing.xunit; 11 | using Microsoft.Extensions.Primitives; 12 | using Xunit; 13 | 14 | namespace Microsoft.AspNetCore.Server.HttpSys.Listener 15 | { 16 | public class RequestHeaderTests 17 | { 18 | 19 | [ConditionalFact] 20 | public async Task RequestHeaders_ClientSendsUtf8Headers_Success() 21 | { 22 | string address; 23 | using (var server = Utilities.CreateHttpServer(out address)) 24 | { 25 | string[] customValues = new string[] { "custom1, and custom测试2", "custom3" }; 26 | Task responseTask = SendRequestAsync(address, "Custom-Header", customValues); 27 | 28 | var context = await server.AcceptAsync(Utilities.DefaultTimeout); 29 | var requestHeaders = context.Request.Headers; 30 | Assert.Equal(4, requestHeaders.Count); 31 | Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]); 32 | Assert.Equal(new[] { new Uri(address).Authority }, requestHeaders.GetValues("Host")); 33 | Assert.Equal("close", requestHeaders["Connection"]); 34 | Assert.Equal(new[] { "close" }, requestHeaders.GetValues("Connection")); 35 | // Apparently Http.Sys squashes request headers together. 36 | Assert.Equal("custom1, and custom测试2, custom3", requestHeaders["Custom-Header"]); 37 | Assert.Equal(new[] { "custom1", "and custom测试2", "custom3" }, requestHeaders.GetValues("Custom-Header")); 38 | Assert.Equal("spacervalue, spacervalue", requestHeaders["Spacer-Header"]); 39 | Assert.Equal(new[] { "spacervalue", "spacervalue" }, requestHeaders.GetValues("Spacer-Header")); 40 | context.Dispose(); 41 | 42 | await responseTask; 43 | } 44 | } 45 | 46 | [ConditionalFact] 47 | public async Task RequestHeaders_ClientSendsKnownHeaderWithNoValue_Success() 48 | { 49 | string address; 50 | using (var server = Utilities.CreateHttpServer(out address)) 51 | { 52 | string[] customValues = new string[] { "" }; 53 | Task responseTask = SendRequestAsync(address, "If-None-Match", customValues); 54 | 55 | var context = await server.AcceptAsync(Utilities.DefaultTimeout); 56 | var requestHeaders = context.Request.Headers; 57 | Assert.Equal(3, requestHeaders.Count); 58 | Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]); 59 | Assert.Equal(new[] { new Uri(address).Authority }, requestHeaders.GetValues("Host")); 60 | Assert.Equal("close", requestHeaders["Connection"]); 61 | Assert.Equal(new[] { "close" }, requestHeaders.GetValues("Connection")); 62 | Assert.Equal(StringValues.Empty, requestHeaders["If-None-Match"]); 63 | Assert.Empty(requestHeaders.GetValues("If-None-Match")); 64 | Assert.Equal("spacervalue", requestHeaders["Spacer-Header"]); 65 | context.Dispose(); 66 | 67 | await responseTask; 68 | } 69 | } 70 | 71 | [ConditionalFact] 72 | public async Task RequestHeaders_ClientSendsUnknownHeaderWithNoValue_Success() 73 | { 74 | string address; 75 | using (var server = Utilities.CreateHttpServer(out address)) 76 | { 77 | string[] customValues = new string[] { "" }; 78 | Task responseTask = SendRequestAsync(address, "Custom-Header", customValues); 79 | 80 | var context = await server.AcceptAsync(Utilities.DefaultTimeout); 81 | var requestHeaders = context.Request.Headers; 82 | Assert.Equal(4, requestHeaders.Count); 83 | Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]); 84 | Assert.Equal(new[] { new Uri(address).Authority }, requestHeaders.GetValues("Host")); 85 | Assert.Equal("close", requestHeaders["Connection"]); 86 | Assert.Equal(new[] { "close" }, requestHeaders.GetValues("Connection")); 87 | Assert.Equal("", requestHeaders["Custom-Header"]); 88 | Assert.Empty(requestHeaders.GetValues("Custom-Header")); 89 | Assert.Equal("spacervalue", requestHeaders["Spacer-Header"]); 90 | context.Dispose(); 91 | 92 | await responseTask; 93 | } 94 | } 95 | 96 | private async Task SendRequestAsync(string address, string customHeader, string[] customValues) 97 | { 98 | var uri = new Uri(address); 99 | StringBuilder builder = new StringBuilder(); 100 | builder.AppendLine("GET / HTTP/1.1"); 101 | builder.AppendLine("Connection: close"); 102 | builder.Append("HOST: "); 103 | builder.AppendLine(uri.Authority); 104 | foreach (string value in customValues) 105 | { 106 | builder.Append(customHeader); 107 | builder.Append(": "); 108 | builder.AppendLine(value); 109 | builder.AppendLine("Spacer-Header: spacervalue"); 110 | } 111 | builder.AppendLine(); 112 | 113 | byte[] request = Encoding.UTF8.GetBytes(builder.ToString()); 114 | 115 | Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); 116 | socket.Connect(uri.Host, uri.Port); 117 | 118 | socket.Send(request); 119 | 120 | byte[] response = new byte[1024 * 5]; 121 | await Task.Run(() => socket.Receive(response)); 122 | socket.Dispose(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/RequestProcessing/RawUrlHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | 7 | namespace Microsoft.AspNetCore.HttpSys.Internal 8 | { 9 | internal static class RawUrlHelper 10 | { 11 | private static readonly byte[] _forwardSlashPath = Encoding.ASCII.GetBytes("/"); 12 | 13 | /// 14 | /// Find the segment of the URI byte array which represents the path. 15 | /// 16 | public static ArraySegment GetPath(byte[] raw) 17 | { 18 | // performance 19 | var pathStartIndex = 0; 20 | 21 | // Performance improvement: accept two cases upfront 22 | // 23 | // 1) Since nearly all strings are relative Uris, just look if the string starts with '/'. 24 | // If so, we have a relative Uri and the path starts at position 0. 25 | // (http.sys already trimmed leading whitespaces) 26 | // 27 | // 2) The URL is simply '*' 28 | if (raw[0] != '/' && !(raw.Length == 1 && raw[0] == '*')) 29 | { 30 | // We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to 31 | // use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the 32 | // Uri starts with either http:// or https://. 33 | var authorityStartIndex = FindHttpOrHttps(raw); 34 | if (authorityStartIndex > 0) 35 | { 36 | // we have an absolute Uri. Find out where the authority ends and the path begins. 37 | // Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616 38 | // and http.sys behavior: If the Uri contains a query, there must be at least one '/' 39 | // between the authority and the '?' character: It's safe to just look for the first 40 | // '/' after the authority to determine the beginning of the path. 41 | pathStartIndex = Find(raw, authorityStartIndex, '/'); 42 | if (pathStartIndex == -1) 43 | { 44 | // e.g. for request lines like: 'GET http://myserver' (no final '/') 45 | // At this point we can return a path with a slash. 46 | return new ArraySegment(_forwardSlashPath); 47 | } 48 | } 49 | else 50 | { 51 | // RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority 52 | // 'authority' can only be used with CONNECT which is never received by HttpListener. 53 | // I.e. if we don't have an absolute path (must start with '/') and we don't have 54 | // an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'. 55 | throw new InvalidOperationException("Invalid URI format"); 56 | } 57 | } 58 | 59 | // Find end of path: The path is terminated by 60 | // - the first '?' character 61 | // - the first '#' character: This is never the case here, since http.sys won't accept 62 | // Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris. 63 | // - end of Uri string 64 | var scan = pathStartIndex + 1; 65 | while (scan < raw.Length && raw[scan] != '?') 66 | { 67 | scan++; 68 | } 69 | 70 | return new ArraySegment(raw, pathStartIndex, scan - pathStartIndex); 71 | } 72 | 73 | /// 74 | /// Compare the beginning portion of the raw URL byte array to https:// and http:// 75 | /// 76 | /// The byte array represents the raw URI 77 | /// Length of the matched bytes, 0 if it is not matched. 78 | private static int FindHttpOrHttps(byte[] raw) 79 | { 80 | if (raw.Length < 7) 81 | { 82 | return 0; 83 | } 84 | 85 | if (raw[0] != 'h' && raw[0] != 'H') 86 | { 87 | return 0; 88 | } 89 | 90 | if (raw[1] != 't' && raw[1] != 'T') 91 | { 92 | return 0; 93 | } 94 | 95 | if (raw[2] != 't' && raw[2] != 'T') 96 | { 97 | return 0; 98 | } 99 | 100 | if (raw[3] != 'p' && raw[3] != 'P') 101 | { 102 | return 0; 103 | } 104 | 105 | if (raw[4] == ':') 106 | { 107 | if (raw[5] != '/' || raw[6] != '/') 108 | { 109 | return 0; 110 | } 111 | else 112 | { 113 | return 7; 114 | } 115 | } 116 | else if (raw[4] == 's' || raw[4] == 'S') 117 | { 118 | if (raw.Length < 8) 119 | { 120 | return 0; 121 | } 122 | 123 | if (raw[5] != ':' || raw[6] != '/' || raw[7] != '/') 124 | { 125 | return 0; 126 | } 127 | else 128 | { 129 | return 8; 130 | } 131 | } 132 | else 133 | { 134 | return 0; 135 | } 136 | } 137 | 138 | private static int Find(byte[] raw, int begin, char target) 139 | { 140 | for (var idx = begin; idx < raw.Length; ++idx) 141 | { 142 | if (raw[idx] == target) 143 | { 144 | return idx; 145 | } 146 | } 147 | 148 | return -1; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/DisconnectListener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.Threading; 9 | using Microsoft.AspNetCore.HttpSys.Internal; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace Microsoft.AspNetCore.Server.HttpSys 13 | { 14 | internal class DisconnectListener 15 | { 16 | private readonly ConcurrentDictionary _connectionCancellationTokens 17 | = new ConcurrentDictionary(); 18 | 19 | private readonly RequestQueue _requestQueue; 20 | private readonly ILogger _logger; 21 | 22 | internal DisconnectListener(RequestQueue requestQueue, ILogger logger) 23 | { 24 | _requestQueue = requestQueue; 25 | _logger = logger; 26 | } 27 | 28 | internal CancellationToken GetTokenForConnection(ulong connectionId) 29 | { 30 | try 31 | { 32 | // Create exactly one CancellationToken per connection. 33 | return GetOrCreateDisconnectToken(connectionId); 34 | } 35 | catch (Win32Exception exception) 36 | { 37 | LogHelper.LogException(_logger, "GetConnectionToken", exception); 38 | return CancellationToken.None; 39 | } 40 | } 41 | 42 | private CancellationToken GetOrCreateDisconnectToken(ulong connectionId) 43 | { 44 | // Read case is performance sensitive 45 | ConnectionCancellation cancellation; 46 | if (!_connectionCancellationTokens.TryGetValue(connectionId, out cancellation)) 47 | { 48 | cancellation = GetCreatedConnectionCancellation(connectionId); 49 | } 50 | return cancellation.GetCancellationToken(connectionId); 51 | } 52 | 53 | private ConnectionCancellation GetCreatedConnectionCancellation(ulong connectionId) 54 | { 55 | // Race condition on creation has no side effects 56 | var cancellation = new ConnectionCancellation(this); 57 | return _connectionCancellationTokens.GetOrAdd(connectionId, cancellation); 58 | } 59 | 60 | private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) 61 | { 62 | LogHelper.LogDebug(_logger, "CreateDisconnectToken", "Registering connection for disconnect for connection ID: " + connectionId); 63 | 64 | // Create a nativeOverlapped callback so we can register for disconnect callback 65 | var cts = new CancellationTokenSource(); 66 | var returnToken = cts.Token; 67 | 68 | SafeNativeOverlapped nativeOverlapped = null; 69 | var boundHandle = _requestQueue.BoundHandle; 70 | nativeOverlapped = new SafeNativeOverlapped(boundHandle, boundHandle.AllocateNativeOverlapped( 71 | (errorCode, numBytes, overlappedPtr) => 72 | { 73 | LogHelper.LogDebug(_logger, "CreateDisconnectToken", "http.sys disconnect callback fired for connection ID: " + connectionId); 74 | 75 | // Free the overlapped 76 | nativeOverlapped.Dispose(); 77 | 78 | // Pull the token out of the list and Cancel it. 79 | ConnectionCancellation token; 80 | _connectionCancellationTokens.TryRemove(connectionId, out token); 81 | try 82 | { 83 | cts.Cancel(); 84 | } 85 | catch (AggregateException exception) 86 | { 87 | LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception); 88 | } 89 | }, 90 | null, null)); 91 | 92 | uint statusCode; 93 | try 94 | { 95 | statusCode = HttpApi.HttpWaitForDisconnectEx(requestQueueHandle: _requestQueue.Handle, 96 | connectionId: connectionId, reserved: 0, overlapped: nativeOverlapped); 97 | } 98 | catch (Win32Exception exception) 99 | { 100 | statusCode = (uint)exception.NativeErrorCode; 101 | LogHelper.LogException(_logger, "CreateDisconnectToken", exception); 102 | } 103 | 104 | if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING && 105 | statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 106 | { 107 | // We got an unknown result, assume the connection has been closed. 108 | nativeOverlapped.Dispose(); 109 | ConnectionCancellation ignored; 110 | _connectionCancellationTokens.TryRemove(connectionId, out ignored); 111 | LogHelper.LogDebug(_logger, "HttpWaitForDisconnectEx", new Win32Exception((int)statusCode)); 112 | cts.Cancel(); 113 | } 114 | 115 | if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess) 116 | { 117 | // IO operation completed synchronously - callback won't be called to signal completion 118 | nativeOverlapped.Dispose(); 119 | ConnectionCancellation ignored; 120 | _connectionCancellationTokens.TryRemove(connectionId, out ignored); 121 | cts.Cancel(); 122 | } 123 | 124 | return returnToken; 125 | } 126 | 127 | private class ConnectionCancellation 128 | { 129 | private readonly DisconnectListener _parent; 130 | private volatile bool _initialized; // Must be volatile because initialization is synchronized 131 | private CancellationToken _cancellationToken; 132 | 133 | public ConnectionCancellation(DisconnectListener parent) 134 | { 135 | _parent = parent; 136 | } 137 | 138 | internal CancellationToken GetCancellationToken(ulong connectionId) 139 | { 140 | // Initialized case is performance sensitive 141 | if (_initialized) 142 | { 143 | return _cancellationToken; 144 | } 145 | return InitializeCancellationToken(connectionId); 146 | } 147 | 148 | private CancellationToken InitializeCancellationToken(ulong connectionId) 149 | { 150 | object syncObject = this; 151 | #pragma warning disable 420 // Disable warning about volatile by reference since EnsureInitialized does volatile operations 152 | return LazyInitializer.EnsureInitialized(ref _cancellationToken, ref _initialized, ref syncObject, () => _parent.CreateDisconnectToken(connectionId)); 153 | #pragma warning restore 420 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Listener/RequestTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Net.Http; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Testing.xunit; 11 | using Microsoft.Extensions.Logging; 12 | using Xunit; 13 | 14 | namespace Microsoft.AspNetCore.Server.HttpSys.Listener 15 | { 16 | public class RequestTests 17 | { 18 | 19 | [ConditionalTheory] 20 | [InlineData("/path%")] 21 | [InlineData("/path%XY")] 22 | [InlineData("/path%F")] 23 | [InlineData("/path with spaces")] 24 | public async Task Request_MalformedPathReturns400StatusCode(string requestPath) 25 | { 26 | string root; 27 | using (var server = Utilities.CreateHttpServerReturnRoot("/", out root)) 28 | { 29 | var responseTask = SendSocketRequestAsync(root, requestPath); 30 | var contextTask = server.AcceptAsync(Utilities.DefaultTimeout); 31 | var response = await responseTask; 32 | var responseStatusCode = response.Substring(9); // Skip "HTTP/1.1 " 33 | Assert.Equal("400", responseStatusCode); 34 | } 35 | } 36 | 37 | [ConditionalFact] 38 | public async Task Request_OptionsStar_EmptyPath() 39 | { 40 | string root; 41 | using (var server = Utilities.CreateHttpServerReturnRoot("/", out root)) 42 | { 43 | var responseTask = SendSocketRequestAsync(root, "*", "OPTIONS"); 44 | var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask); 45 | Assert.Equal("", context.Request.PathBase); 46 | Assert.Equal("", context.Request.Path); 47 | Assert.Equal("*", context.Request.RawUrl); 48 | context.Dispose(); 49 | } 50 | } 51 | 52 | [ConditionalTheory] 53 | [InlineData("%D0%A4", "Ф")] 54 | [InlineData("%d0%a4", "Ф")] 55 | [InlineData("%E0%A4%AD", "भ")] 56 | [InlineData("%e0%A4%Ad", "भ")] 57 | [InlineData("%F0%A4%AD%A2", "𤭢")] 58 | [InlineData("%F0%a4%Ad%a2", "𤭢")] 59 | [InlineData("%48%65%6C%6C%6F%20%57%6F%72%6C%64", "Hello World")] 60 | [InlineData("%48%65%6C%6C%6F%2D%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", "Hello-µ@ßöäüàá")] 61 | // Test the borderline cases of overlong UTF8. 62 | [InlineData("%C2%80", "\u0080")] 63 | [InlineData("%E0%A0%80", "\u0800")] 64 | [InlineData("%F0%90%80%80", "\U00010000")] 65 | [InlineData("%63", "c")] 66 | [InlineData("%32", "2")] 67 | [InlineData("%20", " ")] 68 | // Internationalized 69 | [InlineData("%C3%84ra%20Benetton", "Ära Benetton")] 70 | [InlineData("%E6%88%91%E8%87%AA%E6%A8%AA%E5%88%80%E5%90%91%E5%A4%A9%E7%AC%91%E5%8E%BB%E7%95%99%E8%82%9D%E8%83%86%E4%B8%A4%E6%98%86%E4%BB%91", "我自横刀向天笑去留肝胆两昆仑")] 71 | // Skip forward slash 72 | [InlineData("%2F", "%2F")] 73 | [InlineData("foo%2Fbar", "foo%2Fbar")] 74 | [InlineData("foo%2F%20bar", "foo%2F bar")] 75 | public async Task Request_PathDecodingValidUTF8(string requestPath, string expect) 76 | { 77 | string root; 78 | string actualPath; 79 | using (var server = Utilities.CreateHttpServerReturnRoot("/", out root)) 80 | { 81 | var responseTask = SendSocketRequestAsync(root, "/" + requestPath); 82 | var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask); 83 | actualPath = context.Request.Path; 84 | context.Dispose(); 85 | 86 | var response = await responseTask; 87 | Assert.Equal("200", response.Substring(9)); 88 | } 89 | 90 | Assert.Equal(expect, actualPath.TrimStart('/')); 91 | } 92 | 93 | [ConditionalTheory] 94 | [InlineData("/%%32")] 95 | [InlineData("/%%20")] 96 | [InlineData("/%F0%8F%8F%BF")] 97 | [InlineData("/%")] 98 | [InlineData("/%%")] 99 | [InlineData("/%A")] 100 | [InlineData("/%Y")] 101 | public async Task Request_PathDecodingInvalidUTF8(string requestPath) 102 | { 103 | string root; 104 | using (var server = Utilities.CreateHttpServerReturnRoot("/", out root)) 105 | { 106 | var responseTask = SendSocketRequestAsync(root, requestPath); 107 | var contextTask = server.AcceptAsync(Utilities.DefaultTimeout); 108 | 109 | var response = await responseTask; 110 | Assert.Equal("400", response.Substring(9)); 111 | } 112 | } 113 | 114 | [ConditionalTheory] 115 | // Overlong ASCII 116 | [InlineData("/%C0%A4", "/%C0%A4")] 117 | [InlineData("/%C1%BF", "/%C1%BF")] 118 | [InlineData("/%E0%80%AF", "/%E0%80%AF")] 119 | [InlineData("/%E0%9F%BF", "/%E0%9F%BF")] 120 | [InlineData("/%F0%80%80%AF", "/%F0%80%80%AF")] 121 | [InlineData("/%F0%80%BF%BF", "/%F0%80%BF%BF")] 122 | // Mixed 123 | [InlineData("/%C0%A4%32", "/%C0%A42")] 124 | [InlineData("/%32%C0%A4%32", "/2%C0%A42")] 125 | [InlineData("/%C0%32%A4", "/%C02%A4")] 126 | public async Task Request_OverlongUTF8Path(string requestPath, string expectedPath) 127 | { 128 | string root; 129 | using (var server = Utilities.CreateHttpServerReturnRoot("/", out root)) 130 | { 131 | var responseTask = SendSocketRequestAsync(root, requestPath); 132 | var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask); 133 | Assert.Equal(expectedPath, context.Request.Path); 134 | context.Dispose(); 135 | 136 | var response = await responseTask; 137 | Assert.Equal("200", response.Substring(9)); 138 | } 139 | } 140 | 141 | private async Task SendSocketRequestAsync(string address, string path, string method = "GET") 142 | { 143 | var uri = new Uri(address); 144 | StringBuilder builder = new StringBuilder(); 145 | builder.AppendLine($"{method} {path} HTTP/1.1"); 146 | builder.AppendLine("Connection: close"); 147 | builder.Append("HOST: "); 148 | builder.AppendLine(uri.Authority); 149 | builder.AppendLine(); 150 | 151 | byte[] request = Encoding.ASCII.GetBytes(builder.ToString()); 152 | 153 | using (var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) 154 | { 155 | socket.Connect(uri.Host, uri.Port); 156 | socket.Send(request); 157 | var response = new byte[12]; 158 | await Task.Run(() => socket.Receive(response)); 159 | return Encoding.ASCII.GetString(response); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/HttpsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.IO; 5 | using System.Net.Http; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Http.Features; 11 | using Microsoft.AspNetCore.Testing.xunit; 12 | using Xunit; 13 | 14 | namespace Microsoft.AspNetCore.Server.HttpSys 15 | { 16 | public class HttpsTests 17 | { 18 | private const string Address = "https://localhost:9090/"; 19 | 20 | [ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")] 21 | public async Task Https_200OK_Success() 22 | { 23 | using (Utilities.CreateHttpsServer(httpContext => 24 | { 25 | return Task.FromResult(0); 26 | })) 27 | { 28 | string response = await SendRequestAsync(Address); 29 | Assert.Equal(string.Empty, response); 30 | } 31 | } 32 | 33 | [ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")] 34 | public async Task Https_SendHelloWorld_Success() 35 | { 36 | using (Utilities.CreateHttpsServer(httpContext => 37 | { 38 | byte[] body = Encoding.UTF8.GetBytes("Hello World"); 39 | httpContext.Response.ContentLength = body.Length; 40 | return httpContext.Response.Body.WriteAsync(body, 0, body.Length); 41 | })) 42 | { 43 | string response = await SendRequestAsync(Address); 44 | Assert.Equal("Hello World", response); 45 | } 46 | } 47 | 48 | [ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")] 49 | public async Task Https_EchoHelloWorld_Success() 50 | { 51 | using (Utilities.CreateHttpsServer(httpContext => 52 | { 53 | string input = new StreamReader(httpContext.Request.Body).ReadToEnd(); 54 | Assert.Equal("Hello World", input); 55 | byte[] body = Encoding.UTF8.GetBytes("Hello World"); 56 | httpContext.Response.ContentLength = body.Length; 57 | httpContext.Response.Body.Write(body, 0, body.Length); 58 | return Task.FromResult(0); 59 | })) 60 | { 61 | string response = await SendRequestAsync(Address, "Hello World"); 62 | Assert.Equal("Hello World", response); 63 | } 64 | } 65 | 66 | [ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")] 67 | public async Task Https_ClientCertNotSent_ClientCertNotPresent() 68 | { 69 | using (Utilities.CreateHttpsServer(async httpContext => 70 | { 71 | var tls = httpContext.Features.Get(); 72 | Assert.NotNull(tls); 73 | var cert = await tls.GetClientCertificateAsync(CancellationToken.None); 74 | Assert.Null(cert); 75 | Assert.Null(tls.ClientCertificate); 76 | })) 77 | { 78 | string response = await SendRequestAsync(Address); 79 | Assert.Equal(string.Empty, response); 80 | } 81 | } 82 | 83 | [ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")] 84 | public async Task Https_ClientCertRequested_ClientCertPresent() 85 | { 86 | using (Utilities.CreateHttpsServer(async httpContext => 87 | { 88 | var tls = httpContext.Features.Get(); 89 | Assert.NotNull(tls); 90 | var cert = await tls.GetClientCertificateAsync(CancellationToken.None); 91 | Assert.NotNull(cert); 92 | Assert.NotNull(tls.ClientCertificate); 93 | })) 94 | { 95 | X509Certificate2 cert = FindClientCert(); 96 | Assert.NotNull(cert); 97 | string response = await SendRequestAsync(Address, cert); 98 | Assert.Equal(string.Empty, response); 99 | } 100 | } 101 | 102 | private async Task SendRequestAsync(string uri, 103 | X509Certificate cert = null) 104 | { 105 | var handler = new WinHttpHandler(); 106 | handler.ServerCertificateValidationCallback = (a, b, c, d) => true; 107 | if (cert != null) 108 | { 109 | handler.ClientCertificates.Add(cert); 110 | } 111 | using (HttpClient client = new HttpClient(handler)) 112 | { 113 | return await client.GetStringAsync(uri); 114 | } 115 | } 116 | 117 | private async Task SendRequestAsync(string uri, string upload) 118 | { 119 | var handler = new WinHttpHandler(); 120 | handler.ServerCertificateValidationCallback = (a, b, c, d) => true; 121 | using (HttpClient client = new HttpClient(handler)) 122 | { 123 | HttpResponseMessage response = await client.PostAsync(uri, new StringContent(upload)); 124 | response.EnsureSuccessStatusCode(); 125 | return await response.Content.ReadAsStringAsync(); 126 | } 127 | } 128 | 129 | private X509Certificate2 FindClientCert() 130 | { 131 | var store = new X509Store(); 132 | store.Open(OpenFlags.ReadOnly); 133 | 134 | foreach (var cert in store.Certificates) 135 | { 136 | bool isClientAuth = false; 137 | bool isSmartCard = false; 138 | foreach (var extension in cert.Extensions) 139 | { 140 | var eku = extension as X509EnhancedKeyUsageExtension; 141 | if (eku != null) 142 | { 143 | foreach (var oid in eku.EnhancedKeyUsages) 144 | { 145 | if (oid.FriendlyName == "Client Authentication") 146 | { 147 | isClientAuth = true; 148 | } 149 | else if (oid.FriendlyName == "Smart Card Logon") 150 | { 151 | isSmartCard = true; 152 | break; 153 | } 154 | } 155 | } 156 | } 157 | 158 | if (isClientAuth && !isSmartCard) 159 | { 160 | return cert; 161 | } 162 | } 163 | return null; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /run.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | #requires -version 4 3 | 4 | <# 5 | .SYNOPSIS 6 | Executes KoreBuild commands. 7 | 8 | .DESCRIPTION 9 | Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. 10 | 11 | .PARAMETER Command 12 | The KoreBuild command to run. 13 | 14 | .PARAMETER Path 15 | The folder to build. Defaults to the folder containing this script. 16 | 17 | .PARAMETER Channel 18 | The channel of KoreBuild to download. Overrides the value from the config file. 19 | 20 | .PARAMETER DotNetHome 21 | The directory where .NET Core tools will be stored. 22 | 23 | .PARAMETER ToolsSource 24 | The base url where build tools can be downloaded. Overrides the value from the config file. 25 | 26 | .PARAMETER Update 27 | Updates KoreBuild to the latest version even if a lock file is present. 28 | 29 | .PARAMETER Reinstall 30 | Re-installs KoreBuild 31 | 32 | .PARAMETER ConfigFile 33 | The path to the configuration file that stores values. Defaults to korebuild.json. 34 | 35 | .PARAMETER ToolsSourceSuffix 36 | The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. 37 | 38 | .PARAMETER CI 39 | Sets up CI specific settings and variables. 40 | 41 | .PARAMETER Arguments 42 | Arguments to be passed to the command 43 | 44 | .NOTES 45 | This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. 46 | When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. 47 | 48 | The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set 49 | in the file are overridden by command line parameters. 50 | 51 | .EXAMPLE 52 | Example config file: 53 | ```json 54 | { 55 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 56 | "channel": "master", 57 | "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" 58 | } 59 | ``` 60 | #> 61 | [CmdletBinding(PositionalBinding = $false)] 62 | param( 63 | [Parameter(Mandatory = $true, Position = 0)] 64 | [string]$Command, 65 | [string]$Path = $PSScriptRoot, 66 | [Alias('c')] 67 | [string]$Channel, 68 | [Alias('d')] 69 | [string]$DotNetHome, 70 | [Alias('s')] 71 | [string]$ToolsSource, 72 | [Alias('u')] 73 | [switch]$Update, 74 | [switch]$Reinstall, 75 | [string]$ToolsSourceSuffix, 76 | [string]$ConfigFile = $null, 77 | [switch]$CI, 78 | [Parameter(ValueFromRemainingArguments = $true)] 79 | [string[]]$Arguments 80 | ) 81 | 82 | Set-StrictMode -Version 2 83 | $ErrorActionPreference = 'Stop' 84 | 85 | # 86 | # Functions 87 | # 88 | 89 | function Get-KoreBuild { 90 | 91 | $lockFile = Join-Path $Path 'korebuild-lock.txt' 92 | 93 | if (!(Test-Path $lockFile) -or $Update) { 94 | Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix 95 | } 96 | 97 | $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 98 | if (!$version) { 99 | Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" 100 | } 101 | $version = $version.TrimStart('version:').Trim() 102 | $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) 103 | 104 | if ($Reinstall -and (Test-Path $korebuildPath)) { 105 | Remove-Item -Force -Recurse $korebuildPath 106 | } 107 | 108 | if (!(Test-Path $korebuildPath)) { 109 | Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" 110 | New-Item -ItemType Directory -Path $korebuildPath | Out-Null 111 | $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" 112 | 113 | try { 114 | $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" 115 | Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix 116 | if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { 117 | # Use built-in commands where possible as they are cross-plat compatible 118 | Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath 119 | } 120 | else { 121 | # Fallback to old approach for old installations of PowerShell 122 | Add-Type -AssemblyName System.IO.Compression.FileSystem 123 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) 124 | } 125 | } 126 | catch { 127 | Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore 128 | throw 129 | } 130 | finally { 131 | Remove-Item $tmpfile -ErrorAction Ignore 132 | } 133 | } 134 | 135 | return $korebuildPath 136 | } 137 | 138 | function Join-Paths([string]$path, [string[]]$childPaths) { 139 | $childPaths | ForEach-Object { $path = Join-Path $path $_ } 140 | return $path 141 | } 142 | 143 | function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { 144 | if ($RemotePath -notlike 'http*') { 145 | Copy-Item $RemotePath $LocalPath 146 | return 147 | } 148 | 149 | $retries = 10 150 | while ($retries -gt 0) { 151 | $retries -= 1 152 | try { 153 | Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath 154 | return 155 | } 156 | catch { 157 | Write-Verbose "Request failed. $retries retries remaining" 158 | } 159 | } 160 | 161 | Write-Error "Download failed: '$RemotePath'." 162 | } 163 | 164 | # 165 | # Main 166 | # 167 | 168 | # Load configuration or set defaults 169 | 170 | $Path = Resolve-Path $Path 171 | if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } 172 | 173 | if (Test-Path $ConfigFile) { 174 | try { 175 | $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json 176 | if ($config) { 177 | if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } 178 | if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} 179 | } 180 | } 181 | catch { 182 | Write-Host -ForegroundColor Red $Error[0] 183 | Write-Error "$ConfigFile contains invalid JSON." 184 | exit 1 185 | } 186 | } 187 | 188 | if (!$DotNetHome) { 189 | $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` 190 | elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` 191 | elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` 192 | else { Join-Path $PSScriptRoot '.dotnet'} 193 | } 194 | 195 | if (!$Channel) { $Channel = 'master' } 196 | if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } 197 | 198 | # Execute 199 | 200 | $korebuildPath = Get-KoreBuild 201 | Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') 202 | 203 | try { 204 | Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI 205 | Invoke-KoreBuildCommand $Command @Arguments 206 | } 207 | finally { 208 | Remove-Module 'KoreBuild' -ErrorAction Ignore 209 | } 210 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | namespace Microsoft.AspNetCore.Server.HttpSys 3 | { 4 | using System.Globalization; 5 | using System.Reflection; 6 | using System.Resources; 7 | 8 | internal static class Resources 9 | { 10 | private static readonly ResourceManager _resourceManager 11 | = new ResourceManager("Microsoft.AspNetCore.Server.HttpSys.Resources", typeof(Resources).GetTypeInfo().Assembly); 12 | 13 | /// 14 | /// The destination array is too small. 15 | /// 16 | internal static string Exception_ArrayTooSmall 17 | { 18 | get { return GetString("Exception_ArrayTooSmall"); } 19 | } 20 | 21 | /// 22 | /// The destination array is too small. 23 | /// 24 | internal static string FormatException_ArrayTooSmall() 25 | { 26 | return GetString("Exception_ArrayTooSmall"); 27 | } 28 | 29 | /// 30 | /// End has already been called. 31 | /// 32 | internal static string Exception_EndCalledMultipleTimes 33 | { 34 | get { return GetString("Exception_EndCalledMultipleTimes"); } 35 | } 36 | 37 | /// 38 | /// End has already been called. 39 | /// 40 | internal static string FormatException_EndCalledMultipleTimes() 41 | { 42 | return GetString("Exception_EndCalledMultipleTimes"); 43 | } 44 | 45 | /// 46 | /// The status code '{0}' is not supported. 47 | /// 48 | internal static string Exception_InvalidStatusCode 49 | { 50 | get { return GetString("Exception_InvalidStatusCode"); } 51 | } 52 | 53 | /// 54 | /// The status code '{0}' is not supported. 55 | /// 56 | internal static string FormatException_InvalidStatusCode(object p0) 57 | { 58 | return string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvalidStatusCode"), p0); 59 | } 60 | 61 | /// 62 | /// The stream is not seekable. 63 | /// 64 | internal static string Exception_NoSeek 65 | { 66 | get { return GetString("Exception_NoSeek"); } 67 | } 68 | 69 | /// 70 | /// The stream is not seekable. 71 | /// 72 | internal static string FormatException_NoSeek() 73 | { 74 | return GetString("Exception_NoSeek"); 75 | } 76 | 77 | /// 78 | /// The prefix '{0}' is already registered. 79 | /// 80 | internal static string Exception_PrefixAlreadyRegistered 81 | { 82 | get { return GetString("Exception_PrefixAlreadyRegistered"); } 83 | } 84 | 85 | /// 86 | /// The prefix '{0}' is already registered. 87 | /// 88 | internal static string FormatException_PrefixAlreadyRegistered(object p0) 89 | { 90 | return string.Format(CultureInfo.CurrentCulture, GetString("Exception_PrefixAlreadyRegistered"), p0); 91 | } 92 | 93 | /// 94 | /// This stream only supports read operations. 95 | /// 96 | internal static string Exception_ReadOnlyStream 97 | { 98 | get { return GetString("Exception_ReadOnlyStream"); } 99 | } 100 | 101 | /// 102 | /// This stream only supports read operations. 103 | /// 104 | internal static string FormatException_ReadOnlyStream() 105 | { 106 | return GetString("Exception_ReadOnlyStream"); 107 | } 108 | 109 | /// 110 | /// More data written than specified in the Content-Length header. 111 | /// 112 | internal static string Exception_TooMuchWritten 113 | { 114 | get { return GetString("Exception_TooMuchWritten"); } 115 | } 116 | 117 | /// 118 | /// More data written than specified in the Content-Length header. 119 | /// 120 | internal static string FormatException_TooMuchWritten() 121 | { 122 | return GetString("Exception_TooMuchWritten"); 123 | } 124 | 125 | /// 126 | /// Only the http and https schemes are supported. 127 | /// 128 | internal static string Exception_UnsupportedScheme 129 | { 130 | get { return GetString("Exception_UnsupportedScheme"); } 131 | } 132 | 133 | /// 134 | /// Only the http and https schemes are supported. 135 | /// 136 | internal static string FormatException_UnsupportedScheme() 137 | { 138 | return GetString("Exception_UnsupportedScheme"); 139 | } 140 | 141 | /// 142 | /// This stream only supports write operations. 143 | /// 144 | internal static string Exception_WriteOnlyStream 145 | { 146 | get { return GetString("Exception_WriteOnlyStream"); } 147 | } 148 | 149 | /// 150 | /// This stream only supports write operations. 151 | /// 152 | internal static string FormatException_WriteOnlyStream() 153 | { 154 | return GetString("Exception_WriteOnlyStream"); 155 | } 156 | 157 | /// 158 | /// The given IAsyncResult does not match this opperation. 159 | /// 160 | internal static string Exception_WrongIAsyncResult 161 | { 162 | get { return GetString("Exception_WrongIAsyncResult"); } 163 | } 164 | 165 | /// 166 | /// The given IAsyncResult does not match this opperation. 167 | /// 168 | internal static string FormatException_WrongIAsyncResult() 169 | { 170 | return GetString("Exception_WrongIAsyncResult"); 171 | } 172 | 173 | /// 174 | /// An exception occured while running an action registered with {0}. 175 | /// 176 | internal static string Warning_ExceptionInOnResponseCompletedAction 177 | { 178 | get { return GetString("Warning_ExceptionInOnResponseCompletedAction"); } 179 | } 180 | 181 | /// 182 | /// An exception occured while running an action registered with {0}. 183 | /// 184 | internal static string FormatWarning_ExceptionInOnResponseCompletedAction(object p0) 185 | { 186 | return string.Format(CultureInfo.CurrentCulture, GetString("Warning_ExceptionInOnResponseCompletedAction"), p0); 187 | } 188 | 189 | private static string GetString(string name, params string[] formatterNames) 190 | { 191 | var value = _resourceManager.GetString(name); 192 | 193 | System.Diagnostics.Debug.Assert(value != null); 194 | 195 | if (formatterNames != null) 196 | { 197 | for (var i = 0; i < formatterNames.Length; i++) 198 | { 199 | value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); 200 | } 201 | } 202 | 203 | return value; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /shared/Microsoft.AspNetCore.HttpSys.Sources/NativeInterop/UnsafeNativeMethods.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace Microsoft.AspNetCore.HttpSys.Internal 9 | { 10 | internal static unsafe class UnsafeNclNativeMethods 11 | { 12 | private const string sspicli_LIB = "sspicli.dll"; 13 | private const string api_ms_win_core_processthreads_LIB = "api-ms-win-core-processthreads-l1-1-1.dll"; 14 | private const string api_ms_win_core_io_LIB = "api-ms-win-core-io-l1-1-0.dll"; 15 | private const string api_ms_win_core_handle_LIB = "api-ms-win-core-handle-l1-1-0.dll"; 16 | private const string api_ms_win_core_libraryloader_LIB = "api-ms-win-core-libraryloader-l1-1-0.dll"; 17 | private const string api_ms_win_core_heap_LIB = "api-ms-win-core-heap-L1-2-0.dll"; 18 | private const string api_ms_win_core_heap_obsolete_LIB = "api-ms-win-core-heap-obsolete-L1-1-0.dll"; 19 | private const string api_ms_win_core_kernel32_legacy_LIB = "api-ms-win-core-kernel32-legacy-l1-1-0.dll"; 20 | 21 | private const string TOKENBINDING = "tokenbinding.dll"; 22 | 23 | // CONSIDER: Make this an enum, requires changing a lot of types from uint to ErrorCodes. 24 | internal static class ErrorCodes 25 | { 26 | internal const uint ERROR_SUCCESS = 0; 27 | internal const uint ERROR_HANDLE_EOF = 38; 28 | internal const uint ERROR_NOT_SUPPORTED = 50; 29 | internal const uint ERROR_INVALID_PARAMETER = 87; 30 | internal const uint ERROR_ALREADY_EXISTS = 183; 31 | internal const uint ERROR_MORE_DATA = 234; 32 | internal const uint ERROR_OPERATION_ABORTED = 995; 33 | internal const uint ERROR_IO_PENDING = 997; 34 | internal const uint ERROR_NOT_FOUND = 1168; 35 | internal const uint ERROR_CONNECTION_INVALID = 1229; 36 | } 37 | 38 | [DllImport(api_ms_win_core_io_LIB, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] 39 | internal static unsafe extern uint CancelIoEx(SafeHandle handle, SafeNativeOverlapped overlapped); 40 | 41 | [DllImport(api_ms_win_core_kernel32_legacy_LIB, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] 42 | internal static unsafe extern bool SetFileCompletionNotificationModes(SafeHandle handle, FileCompletionNotificationModes modes); 43 | 44 | [Flags] 45 | internal enum FileCompletionNotificationModes : byte 46 | { 47 | None = 0, 48 | SkipCompletionPortOnSuccess = 1, 49 | SkipSetEventOnHandle = 2 50 | } 51 | 52 | [DllImport(TOKENBINDING, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] 53 | public static extern int TokenBindingVerifyMessage( 54 | [In] byte* tokenBindingMessage, 55 | [In] uint tokenBindingMessageSize, 56 | [In] char* keyType, 57 | [In] byte* tlsUnique, 58 | [In] uint tlsUniqueSize, 59 | [Out] out HeapAllocHandle resultList); 60 | 61 | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366569(v=vs.85).aspx 62 | [DllImport(api_ms_win_core_heap_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 63 | internal static extern IntPtr GetProcessHeap(); 64 | 65 | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366701(v=vs.85).aspx 66 | [DllImport(api_ms_win_core_heap_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 67 | internal static extern bool HeapFree( 68 | [In] IntPtr hHeap, 69 | [In] uint dwFlags, 70 | [In] IntPtr lpMem); 71 | 72 | internal static class SafeNetHandles 73 | { 74 | [DllImport(sspicli_LIB, ExactSpelling = true, SetLastError = true)] 75 | internal static extern int FreeContextBuffer( 76 | [In] IntPtr contextBuffer); 77 | 78 | [DllImport(api_ms_win_core_handle_LIB, ExactSpelling = true, SetLastError = true)] 79 | internal static extern bool CloseHandle(IntPtr handle); 80 | 81 | [DllImport(api_ms_win_core_heap_obsolete_LIB, EntryPoint = "LocalAlloc", SetLastError = true)] 82 | internal static extern SafeLocalFreeChannelBinding LocalAllocChannelBinding(int uFlags, UIntPtr sizetdwBytes); 83 | 84 | [DllImport(api_ms_win_core_heap_obsolete_LIB, ExactSpelling = true, SetLastError = true)] 85 | internal static extern IntPtr LocalFree(IntPtr handle); 86 | } 87 | 88 | // from tokenbinding.h 89 | internal static class TokenBinding 90 | { 91 | [StructLayout(LayoutKind.Sequential)] 92 | internal unsafe struct TOKENBINDING_RESULT_DATA 93 | { 94 | public uint identifierSize; 95 | public TOKENBINDING_IDENTIFIER* identifierData; 96 | public TOKENBINDING_EXTENSION_FORMAT extensionFormat; 97 | public uint extensionSize; 98 | public IntPtr extensionData; 99 | } 100 | 101 | [StructLayout(LayoutKind.Sequential)] 102 | internal struct TOKENBINDING_IDENTIFIER 103 | { 104 | // Note: If the layout of these fields changes, be sure to make the 105 | // corresponding change to TokenBindingUtil.ExtractIdentifierBlob. 106 | 107 | public TOKENBINDING_TYPE bindingType; 108 | public TOKENBINDING_HASH_ALGORITHM hashAlgorithm; 109 | public TOKENBINDING_SIGNATURE_ALGORITHM signatureAlgorithm; 110 | } 111 | 112 | internal enum TOKENBINDING_TYPE : byte 113 | { 114 | TOKENBINDING_TYPE_PROVIDED = 0, 115 | TOKENBINDING_TYPE_REFERRED = 1, 116 | } 117 | 118 | internal enum TOKENBINDING_HASH_ALGORITHM : byte 119 | { 120 | TOKENBINDING_HASH_ALGORITHM_SHA256 = 4, 121 | } 122 | 123 | internal enum TOKENBINDING_SIGNATURE_ALGORITHM : byte 124 | { 125 | TOKENBINDING_SIGNATURE_ALGORITHM_RSA = 1, 126 | TOKENBINDING_SIGNATURE_ALGORITHM_ECDSAP256 = 3, 127 | } 128 | 129 | internal enum TOKENBINDING_EXTENSION_FORMAT 130 | { 131 | TOKENBINDING_EXTENSION_FORMAT_UNDEFINED = 0, 132 | } 133 | 134 | [StructLayout(LayoutKind.Sequential)] 135 | internal unsafe struct TOKENBINDING_RESULT_LIST 136 | { 137 | public uint resultCount; 138 | public TOKENBINDING_RESULT_DATA* resultData; 139 | } 140 | } 141 | 142 | // DACL related stuff 143 | 144 | [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Instantiated natively")] 145 | [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", 146 | Justification = "Does not own the resource.")] 147 | [StructLayout(LayoutKind.Sequential)] 148 | internal class SECURITY_ATTRIBUTES 149 | { 150 | public int nLength = 12; 151 | public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false); 152 | public bool bInheritHandle = false; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/UrlPrefix.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using Microsoft.AspNetCore.HttpSys.Internal; 7 | 8 | namespace Microsoft.AspNetCore.Server.HttpSys 9 | { 10 | public class UrlPrefix 11 | { 12 | private UrlPrefix(bool isHttps, string scheme, string host, string port, int portValue, string path) 13 | { 14 | IsHttps = isHttps; 15 | Scheme = scheme; 16 | Host = host; 17 | Port = port; 18 | PortValue = portValue; 19 | Path = path; 20 | FullPrefix = string.Format(CultureInfo.InvariantCulture, "{0}://{1}:{2}{3}", Scheme, Host, Port, Path); 21 | } 22 | 23 | /// 24 | /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364698(v=vs.85).aspx 25 | /// 26 | /// http or https. Will be normalized to lower case. 27 | /// +, *, IPv4, [IPv6], or a dns name. Http.Sys does not permit punycode (xn--), use Unicode instead. 28 | /// If empty, the default port for the given scheme will be used (80 or 443). 29 | /// Should start and end with a '/', though a missing trailing slash will be added. This value must be un-escaped. 30 | public static UrlPrefix Create(string scheme, string host, string port, string path) 31 | { 32 | int? portValue = null; 33 | if (!string.IsNullOrWhiteSpace(port)) 34 | { 35 | portValue = int.Parse(port, NumberStyles.None, CultureInfo.InvariantCulture); 36 | } 37 | 38 | return UrlPrefix.Create(scheme, host, portValue, path); 39 | } 40 | 41 | /// 42 | /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364698(v=vs.85).aspx 43 | /// 44 | /// http or https. Will be normalized to lower case. 45 | /// +, *, IPv4, [IPv6], or a dns name. Http.Sys does not permit punycode (xn--), use Unicode instead. 46 | /// If empty, the default port for the given scheme will be used (80 or 443). 47 | /// Should start and end with a '/', though a missing trailing slash will be added. This value must be un-escaped. 48 | public static UrlPrefix Create(string scheme, string host, int? portValue, string path) 49 | { 50 | bool isHttps; 51 | if (string.Equals(Constants.HttpScheme, scheme, StringComparison.OrdinalIgnoreCase)) 52 | { 53 | scheme = Constants.HttpScheme; // Always use a lower case scheme 54 | isHttps = false; 55 | } 56 | else if (string.Equals(Constants.HttpsScheme, scheme, StringComparison.OrdinalIgnoreCase)) 57 | { 58 | scheme = Constants.HttpsScheme; // Always use a lower case scheme 59 | isHttps = true; 60 | } 61 | else 62 | { 63 | throw new ArgumentOutOfRangeException("scheme", scheme, Resources.Exception_UnsupportedScheme); 64 | } 65 | 66 | if (string.IsNullOrWhiteSpace(host)) 67 | { 68 | throw new ArgumentNullException("host"); 69 | } 70 | 71 | string port; 72 | if (!portValue.HasValue) 73 | { 74 | port = isHttps ? "443" : "80"; 75 | portValue = isHttps ? 443 : 80; 76 | } 77 | else 78 | { 79 | port = portValue.Value.ToString(CultureInfo.InvariantCulture); 80 | } 81 | 82 | // Http.Sys requires the path end with a slash. 83 | if (string.IsNullOrWhiteSpace(path)) 84 | { 85 | path = "/"; 86 | } 87 | else if (!path.EndsWith("/", StringComparison.Ordinal)) 88 | { 89 | path += "/"; 90 | } 91 | 92 | return new UrlPrefix(isHttps, scheme, host, port, portValue.Value, path); 93 | } 94 | 95 | public static UrlPrefix Create(string prefix) 96 | { 97 | string scheme = null; 98 | string host = null; 99 | int? port = null; 100 | string path = null; 101 | var whole = prefix ?? string.Empty; 102 | 103 | var schemeDelimiterEnd = whole.IndexOf("://", StringComparison.Ordinal); 104 | if (schemeDelimiterEnd < 0) 105 | { 106 | throw new FormatException("Invalid prefix, missing scheme separator: " + prefix); 107 | } 108 | var hostDelimiterStart = schemeDelimiterEnd + "://".Length; 109 | 110 | var pathDelimiterStart = whole.IndexOf("/", hostDelimiterStart, StringComparison.Ordinal); 111 | if (pathDelimiterStart < 0) 112 | { 113 | pathDelimiterStart = whole.Length; 114 | } 115 | var hostDelimiterEnd = whole.LastIndexOf(":", pathDelimiterStart - 1, pathDelimiterStart - hostDelimiterStart, StringComparison.Ordinal); 116 | if (hostDelimiterEnd < 0) 117 | { 118 | hostDelimiterEnd = pathDelimiterStart; 119 | } 120 | 121 | scheme = whole.Substring(0, schemeDelimiterEnd); 122 | var portString = whole.Substring(hostDelimiterEnd, pathDelimiterStart - hostDelimiterEnd); // The leading ":" is included 123 | int portValue; 124 | if (!string.IsNullOrEmpty(portString)) 125 | { 126 | var portValueString = portString.Substring(1); // Trim the leading ":" 127 | if (int.TryParse(portValueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out portValue)) 128 | { 129 | host = whole.Substring(hostDelimiterStart, hostDelimiterEnd - hostDelimiterStart); 130 | port = portValue; 131 | } 132 | else 133 | { 134 | // This means a port was specified but was invalid or empty. 135 | throw new FormatException("Invalid prefix, invalid port specified: " + prefix); 136 | } 137 | } 138 | else 139 | { 140 | host = whole.Substring(hostDelimiterStart, pathDelimiterStart - hostDelimiterStart); 141 | } 142 | path = whole.Substring(pathDelimiterStart); 143 | 144 | return Create(scheme, host, port, path); 145 | } 146 | 147 | public bool IsHttps { get; private set; } 148 | public string Scheme { get; private set; } 149 | public string Host { get; private set; } 150 | public string Port { get; private set; } 151 | public int PortValue { get; private set; } 152 | public string Path { get; private set; } 153 | public string FullPrefix { get; private set; } 154 | 155 | public override bool Equals(object obj) 156 | { 157 | return string.Equals(FullPrefix, Convert.ToString(obj), StringComparison.OrdinalIgnoreCase); 158 | } 159 | 160 | public override int GetHashCode() 161 | { 162 | return StringComparer.OrdinalIgnoreCase.GetHashCode(FullPrefix); 163 | } 164 | 165 | public override string ToString() 166 | { 167 | return FullPrefix; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Server.HttpSys/RequestProcessing/RequestStreamAsyncResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.HttpSys.Internal; 11 | 12 | namespace Microsoft.AspNetCore.Server.HttpSys 13 | { 14 | internal unsafe class RequestStreamAsyncResult : IAsyncResult, IDisposable 15 | { 16 | private static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(Callback); 17 | 18 | private SafeNativeOverlapped _overlapped; 19 | private IntPtr _pinnedBuffer; 20 | private uint _dataAlreadyRead; 21 | private TaskCompletionSource _tcs; 22 | private RequestStream _requestStream; 23 | private AsyncCallback _callback; 24 | private CancellationTokenRegistration _cancellationRegistration; 25 | 26 | internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback) 27 | { 28 | _requestStream = requestStream; 29 | _tcs = new TaskCompletionSource(userState); 30 | _callback = callback; 31 | } 32 | 33 | internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback, uint dataAlreadyRead) 34 | : this(requestStream, userState, callback) 35 | { 36 | _dataAlreadyRead = dataAlreadyRead; 37 | } 38 | 39 | internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback, byte[] buffer, int offset, uint dataAlreadyRead) 40 | : this(requestStream, userState, callback, buffer, offset, dataAlreadyRead, new CancellationTokenRegistration()) 41 | { 42 | } 43 | 44 | internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback, byte[] buffer, int offset, uint dataAlreadyRead, CancellationTokenRegistration cancellationRegistration) 45 | : this(requestStream, userState, callback) 46 | { 47 | _dataAlreadyRead = dataAlreadyRead; 48 | var boundHandle = requestStream.RequestContext.Server.RequestQueue.BoundHandle; 49 | _overlapped = new SafeNativeOverlapped(boundHandle, 50 | boundHandle.AllocateNativeOverlapped(IOCallback, this, buffer)); 51 | _pinnedBuffer = (Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset)); 52 | _cancellationRegistration = cancellationRegistration; 53 | } 54 | 55 | internal RequestStream RequestStream 56 | { 57 | get { return _requestStream; } 58 | } 59 | 60 | internal SafeNativeOverlapped NativeOverlapped 61 | { 62 | get { return _overlapped; } 63 | } 64 | 65 | internal IntPtr PinnedBuffer 66 | { 67 | get { return _pinnedBuffer; } 68 | } 69 | 70 | internal uint DataAlreadyRead 71 | { 72 | get { return _dataAlreadyRead; } 73 | } 74 | 75 | internal Task Task 76 | { 77 | get { return _tcs.Task; } 78 | } 79 | 80 | internal bool EndCalled { get; set; } 81 | 82 | internal void IOCompleted(uint errorCode, uint numBytes) 83 | { 84 | IOCompleted(this, errorCode, numBytes); 85 | } 86 | 87 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirecting to callback")] 88 | private static void IOCompleted(RequestStreamAsyncResult asyncResult, uint errorCode, uint numBytes) 89 | { 90 | try 91 | { 92 | if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) 93 | { 94 | asyncResult.Fail(new IOException(string.Empty, new HttpSysException((int)errorCode))); 95 | } 96 | else 97 | { 98 | // TODO: Verbose log dump data read 99 | asyncResult.Complete((int)numBytes, errorCode); 100 | } 101 | } 102 | catch (Exception e) 103 | { 104 | asyncResult.Fail(new IOException(string.Empty, e)); 105 | } 106 | } 107 | 108 | private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) 109 | { 110 | var asyncResult = (RequestStreamAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped); 111 | IOCompleted(asyncResult, errorCode, numBytes); 112 | } 113 | 114 | internal void Complete(int read, uint errorCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) 115 | { 116 | if (_requestStream.TryCheckSizeLimit(read + (int)DataAlreadyRead, out var exception)) 117 | { 118 | _tcs.TrySetException(exception); 119 | } 120 | else if (_tcs.TrySetResult(read + (int)DataAlreadyRead)) 121 | { 122 | RequestStream.UpdateAfterRead((uint)errorCode, (uint)(read + DataAlreadyRead)); 123 | if (_callback != null) 124 | { 125 | try 126 | { 127 | _callback(this); 128 | } 129 | catch (Exception) 130 | { 131 | // TODO: Exception handling? This may be an IO callback thread and throwing here could crash the app. 132 | } 133 | } 134 | } 135 | Dispose(); 136 | } 137 | 138 | internal void Fail(Exception ex) 139 | { 140 | if (_tcs.TrySetException(ex) && _callback != null) 141 | { 142 | try 143 | { 144 | _callback(this); 145 | } 146 | catch (Exception) 147 | { 148 | // TODO: Exception handling? This may be an IO callback thread and throwing here could crash the app. 149 | // TODO: Log 150 | } 151 | } 152 | Dispose(); 153 | _requestStream.Abort(); 154 | } 155 | 156 | [SuppressMessage("Microsoft.Usage", "CA2216:DisposableTypesShouldDeclareFinalizer", Justification = "The disposable resource referenced does have a finalizer.")] 157 | public void Dispose() 158 | { 159 | Dispose(true); 160 | } 161 | 162 | protected virtual void Dispose(bool disposing) 163 | { 164 | if (disposing) 165 | { 166 | if (_overlapped != null) 167 | { 168 | _overlapped.Dispose(); 169 | } 170 | _cancellationRegistration.Dispose(); 171 | } 172 | } 173 | 174 | public object AsyncState 175 | { 176 | get { return _tcs.Task.AsyncState; } 177 | } 178 | 179 | public WaitHandle AsyncWaitHandle 180 | { 181 | get { return ((IAsyncResult)_tcs.Task).AsyncWaitHandle; } 182 | } 183 | 184 | public bool CompletedSynchronously 185 | { 186 | get { return ((IAsyncResult)_tcs.Task).CompletedSynchronously; } 187 | } 188 | 189 | public bool IsCompleted 190 | { 191 | get { return _tcs.Task.IsCompleted; } 192 | } 193 | } 194 | } --------------------------------------------------------------------------------