├── 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("");
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 | }
--------------------------------------------------------------------------------