Merge remote-tracking branch 'HttpSysServer/rybrande/release21ToSrc' into rybrande/Mondo2.1

This commit is contained in:
Ryan Brandenburg 2018-11-21 15:17:11 -08:00
commit 5c0097bf69
129 changed files with 25584 additions and 0 deletions

32
src/HttpSysServer/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
[Oo]bj/
[Bb]in/
TestResults/
.nuget/
_ReSharper.*/
packages/
artifacts/
PublishProfiles/
*.user
*.suo
*.cache
*.docstates
_ReSharper.*
nuget.exe
*net45.csproj
*net451.csproj
*k10.csproj
*.psess
*.vsp
*.pidb
*.userprefs
*DS_Store
*.ncrunchsolution
*.*sdf
*.ipch
*.sln.ide
project.lock.json
/.vs
.vscode/
.build/
.testPublish/
global.json

View File

@ -0,0 +1,20 @@
<Project>
<Import
Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
Condition=" '$(CI)' != 'true' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))' != '' " />
<Import Project="version.props" />
<Import Project="build\dependencies.props" />
<Import Project="build\sources.props" />
<PropertyGroup>
<Product>Microsoft ASP.NET Core</Product>
<RepositoryUrl>https://github.com/aspnet/HttpSysServer</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,176 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.10
MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{99D5E5F3-88F5-4CCF-8D8C-717C8925DF09}"
ProjectSection(SolutionItems) = preProject
src\Directory.Build.props = src\Directory.Build.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E183C826-1360-4DFF-9994-F33CED5C8525}"
ProjectSection(SolutionItems) = preProject
test\Directory.Build.props = test\Directory.Build.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3A1E31E3-2794-4CA3-B8E2-253E96BDE514}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5E9B546C-17AC-4BDF-BCB3-5955D4755ED8}"
ProjectSection(SolutionItems) = preProject
.appveyor.yml = .appveyor.yml
.travis.yml = .travis.yml
build.cmd = build.cmd
build.ps1 = build.ps1
build.sh = build.sh
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
NuGet.config = NuGet.config
version.xml = version.xml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfHostServer", "samples\SelfHostServer\SelfHostServer.csproj", "{1236F93A-AC5C-4A77-9477-C88F040151CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys.FunctionalTests", "test\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj", "{4492FF4C-9032-411D-853F-46B01755E504}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys", "src\Microsoft.AspNetCore.Server.HttpSys\Microsoft.AspNetCore.Server.HttpSys.csproj", "{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotAddSample", "samples\HotAddSample\HotAddSample.csproj", "{8BFA392A-8B67-4454-916B-67C545EDFAEF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys.Tests", "test\Microsoft.AspNetCore.Server.HttpSys.Tests\Microsoft.AspNetCore.Server.HttpSys.Tests.csproj", "{E837249E-E666-4DF2-AFC3-7A4D70234F9F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{85914BA9-4168-48C5-9C3F-E2E8B1479A6E}"
ProjectSection(SolutionItems) = preProject
build\dependencies.props = build\dependencies.props
build\Key.snk = build\Key.snk
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{AB6964C9-A7AF-4FAC-BEA1-C8A538EC989E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.HttpSys.Sources", "Microsoft.AspNetCore.HttpSys.Sources", "{4AB1E069-2A8A-4D46-98AE-CC82E3497038}"
ProjectSection(SolutionItems) = preProject
shared\Microsoft.AspNetCore.HttpSys.Sources\Constants.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\Constants.cs
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NativeInterop", "NativeInterop", "{94AD33C9-1BDD-4385-A850-4B24FD5D5012}"
ProjectSection(SolutionItems) = preProject
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\CookedUrl.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\CookedUrl.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HeapAllocHandle.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HeapAllocHandle.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HttpApiTypes.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HttpApiTypes.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HttpSysRequestHeader.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HttpSysRequestHeader.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HttpSysResponseHeader.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\HttpSysResponseHeader.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\NativeRequestInput.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\NativeRequestInput.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\NclUtilities.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\NclUtilities.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SafeLocalFreeChannelBinding.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SafeLocalFreeChannelBinding.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SafeLocalMemHandle.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SafeLocalMemHandle.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SafeNativeOverlapped.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SafeNativeOverlapped.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SocketAddress.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\SocketAddress.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\UnsafeNativeMethods.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\NativeInterop\UnsafeNativeMethods.cs
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RequestProcessing", "RequestProcessing", "{AA8C91BD-D558-468B-9258-1E186884F78D}"
ProjectSection(SolutionItems) = preProject
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HeaderCollection.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HeaderCollection.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HeaderEncoding.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HeaderEncoding.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HeaderParser.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HeaderParser.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HttpKnownHeaderNames.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\HttpKnownHeaderNames.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\NativeRequestContext.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\NativeRequestContext.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\RequestHeaders.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\RequestHeaders.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\RequestHeaders.Generated.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\RequestHeaders.Generated.cs
shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\SslStatus.cs = shared\Microsoft.AspNetCore.HttpSys.Sources\RequestProcessing\SslStatus.cs
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Debug|x86.ActiveCfg = Debug|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Release|Any CPU.Build.0 = Release|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{8B828433-B333-4C19-96AE-00BFFF9D8841}.Release|x86.ActiveCfg = Release|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Debug|x86.ActiveCfg = Debug|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|Any CPU.Build.0 = Release|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1236F93A-AC5C-4A77-9477-C88F040151CA}.Release|x86.ActiveCfg = Release|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Debug|x86.ActiveCfg = Debug|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Release|Any CPU.Build.0 = Release|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4492FF4C-9032-411D-853F-46B01755E504}.Release|x86.ActiveCfg = Release|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Debug|x86.ActiveCfg = Debug|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|Any CPU.Build.0 = Release|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|x86.ActiveCfg = Release|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Debug|x86.ActiveCfg = Debug|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Debug|x86.Build.0 = Debug|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Release|Any CPU.Build.0 = Release|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Release|x86.ActiveCfg = Release|Any CPU
{8BFA392A-8B67-4454-916B-67C545EDFAEF}.Release|x86.Build.0 = Release|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Debug|x86.ActiveCfg = Debug|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Debug|x86.Build.0 = Debug|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Release|Any CPU.Build.0 = Release|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Release|x86.ActiveCfg = Release|Any CPU
{E837249E-E666-4DF2-AFC3-7A4D70234F9F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8B828433-B333-4C19-96AE-00BFFF9D8841} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514}
{1236F93A-AC5C-4A77-9477-C88F040151CA} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514}
{4492FF4C-9032-411D-853F-46B01755E504} = {E183C826-1360-4DFF-9994-F33CED5C8525}
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92} = {99D5E5F3-88F5-4CCF-8D8C-717C8925DF09}
{8BFA392A-8B67-4454-916B-67C545EDFAEF} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514}
{E837249E-E666-4DF2-AFC3-7A4D70234F9F} = {E183C826-1360-4DFF-9994-F33CED5C8525}
{85914BA9-4168-48C5-9C3F-E2E8B1479A6E} = {5E9B546C-17AC-4BDF-BCB3-5955D4755ED8}
{4AB1E069-2A8A-4D46-98AE-CC82E3497038} = {AB6964C9-A7AF-4FAC-BEA1-C8A538EC989E}
{94AD33C9-1BDD-4385-A850-4B24FD5D5012} = {4AB1E069-2A8A-4D46-98AE-CC82E3497038}
{AA8C91BD-D558-468B-9258-1E186884F78D} = {4AB1E069-2A8A-4D46-98AE-CC82E3497038}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {34B42B42-FA09-41AB-9216-14073990C504}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,13 @@
{
"adx-nonshipping": {
"rules": [],
"packages": {
"Microsoft.AspNetCore.HttpSys.Sources": {}
}
},
"Default": {
"rules": [
"DefaultCompositeRule"
]
}
}

View File

@ -0,0 +1,10 @@
HttpSysServer
=================
| AppVeyor | Travis |
| ---- | ----
| [![AppVeyor](https://ci.appveyor.com/api/projects/status/47fv9qoe862xlr25/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/HttpSysServer/branch/dev) | [![Travis](https://travis-ci.org/aspnet/HttpSysServer.svg?branch=dev)](https://travis-ci.org/aspnet/HttpSysServer) |
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).
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.

Binary file not shown.

View File

@ -0,0 +1,31 @@
<Project>
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<!-- These package versions may be overridden or updated by automation. -->
<PropertyGroup Label="Package Versions: Auto">
<InternalAspNetCoreSdkPackageVersion>2.1.3-rtm-15802</InternalAspNetCoreSdkPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MicrosoftWin32RegistryPackageVersion>4.5.0</MicrosoftWin32RegistryPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<SystemNetHttpWinHttpHandlerPackageVersion>4.5.0</SystemNetHttpWinHttpHandlerPackageVersion>
<SystemSecurityPrincipalWindowsPackageVersion>4.5.0</SystemSecurityPrincipalWindowsPackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>
</PropertyGroup>
<!-- This may import a generated file which may override the variables above. -->
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
<!-- These are package versions that should not be overridden or updated by automation. -->
<PropertyGroup Label="Package Versions: Pinned">
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.1.1</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.1.1</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.1</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.1.1</MicrosoftNetHttpHeadersPackageVersion>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,15 @@
<Project>
<Import Project="dependencies.props" />
<PropertyGroup>
<!-- These properties are use by the automation that updates dependencies.props -->
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
<LineupPackageVersion>2.1.0-rc1-*</LineupPackageVersion>
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
</PropertyGroup>
<ItemGroup>
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
<Project>
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
<PropertyGroup Label="RestoreSources">
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
$(RestoreSources);
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
$(RestoreSources);
https://api.nuget.org/v3/index.json;
</RestoreSources>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<OutputType>Exe</OutputType>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.HttpSys\Microsoft.AspNetCore.Server.HttpSys.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
{
"profiles": {
"HotAddSample": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:12345",
"environmentVariables": {
"ASPNETCORE_URLS": "http://localhost:12345",
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,110 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace HotAddSample
{
// This sample shows how to dynamically add or remove prefixes for the underlying server.
// Be careful not to remove the prefix you're currently accessing because the connection
// will be reset before the end of the request.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.Configure<HttpSysOptions>(options =>
{
ServerOptions = options;
});
}
public HttpSysOptions ServerOptions { get; set; }
public void Configure(IApplicationBuilder app)
{
var addresses = ServerOptions.UrlPrefixes;
addresses.Add("http://localhost:12346/pathBase/");
app.Use(async (context, next) =>
{
// Note: To add any prefix other than localhost you must run this sample as an administrator.
var toAdd = context.Request.Query["add"];
if (!string.IsNullOrEmpty(toAdd))
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
try
{
addresses.Add(toAdd);
await context.Response.WriteAsync("Added: <a href=\"" + toAdd + "\">" + toAdd + "</a>");
}
catch (Exception ex)
{
await context.Response.WriteAsync("Error adding: " + toAdd + "<br>");
await context.Response.WriteAsync(ex.ToString().Replace(Environment.NewLine, "<br>"));
}
await context.Response.WriteAsync("<br><a href=\"" + context.Request.PathBase.ToUriComponent() + "\">back</a>");
await context.Response.WriteAsync("</body></html>");
return;
}
await next();
});
app.Use(async (context, next) =>
{
// Be careful not to remove the prefix you're currently accessing because the connection
// will be reset before the response is sent.
var toRemove = context.Request.Query["remove"];
if (!string.IsNullOrEmpty(toRemove))
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
if (addresses.Remove(toRemove))
{
await context.Response.WriteAsync("Removed: " + toRemove);
}
else
{
await context.Response.WriteAsync("Not found: " + toRemove);
}
await context.Response.WriteAsync("<br><a href=\"" + context.Request.PathBase.ToUriComponent() + "\">back</a>");
await context.Response.WriteAsync("</body></html>");
return;
}
await next();
});
app.Run(async context =>
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
await context.Response.WriteAsync("Listening on these prefixes: <br>");
foreach (var prefix in addresses)
{
await context.Response.WriteAsync("<a href=\"" + prefix + "\">" + prefix + "</a> <a href=\"?remove=" + prefix + "\">(remove)</a><br>");
}
await context.Response.WriteAsync("<form action=\"" + context.Request.PathBase.ToUriComponent() + "\" method=\"GET\">");
await context.Response.WriteAsync("<input type=\"text\" name=\"add\" value=\"http://localhost:12348\" >");
await context.Response.WriteAsync("<input type=\"submit\" value=\"Add\">");
await context.Response.WriteAsync("</form>");
await context.Response.WriteAsync("</body></html>");
});
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.ConfigureLogging(factory => factory.AddConsole())
.UseStartup<Startup>()
.UseHttpSys()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<gcServer enabled="true"/>
</runtime>
</configuration>

View File

@ -0,0 +1,12 @@
{
"profiles": {
"SelfHostServer": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +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

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<OutputType>Exe</OutputType>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.HttpSys\Microsoft.AspNetCore.Server.HttpSys.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SelfHostServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Server options can be configured here instead of in Main.
services.Configure<HttpSysOptions>(options =>
{
options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;
});
}
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello world from " + context.Request.Host + " at " + DateTime.Now);
});
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.ConfigureLogging(factory => factory.AddConsole())
.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.UrlPrefixes.Add("http://localhost:5000");
options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;
})
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>
</configuration>

View File

@ -0,0 +1,78 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TestClient
{
public class Program
{
private const string Address =
// "http://localhost:5000/public/1kb.txt";
"https://localhost:9090/public/1kb.txt";
public static void Main(string[] args)
{
WebRequestHandler handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = (_, __, ___, ____) => true;
// handler.UseDefaultCredentials = true;
handler.Credentials = new NetworkCredential(@"redmond\chrross", "passwird");
HttpClient client = new HttpClient(handler);
/*
int completionCount = 0;
int iterations = 30000;
for (int i = 0; i < iterations; i++)
{
client.GetAsync(Address)
.ContinueWith(t => Interlocked.Increment(ref completionCount));
}
while (completionCount < iterations)
{
Thread.Sleep(10);
}*/
while (true)
{
Console.WriteLine("Press any key to send request");
Console.ReadKey();
var result = client.GetAsync(Address).Result;
Console.WriteLine(result);
}
// RunWebSocketClient().Wait();
// Console.WriteLine("Done");
// Console.ReadKey();
}
public static async Task RunWebSocketClient()
{
ClientWebSocket websocket = new ClientWebSocket();
string url = "ws://localhost:5000/";
Console.WriteLine("Connecting to: " + url);
await websocket.ConnectAsync(new Uri(url), CancellationToken.None);
string message = "Hello World";
Console.WriteLine("Sending message: " + message);
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
await websocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None);
byte[] incomingData = new byte[1024];
WebSocketReceiveResult result = await websocket.ReceiveAsync(new ArraySegment<byte>(incomingData), CancellationToken.None);
if (result.CloseStatus.HasValue)
{
Console.WriteLine("Closed; Status: " + result.CloseStatus + ", " + result.CloseStatusDescription);
}
else
{
Console.WriteLine("Received message: " + Encoding.UTF8.GetString(incomingData, 0, result.Count));
}
}
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestClient")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TestClient")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8db62eb3-48c0-4049-b33e-271c738140a0")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("0.5")]
[assembly: AssemblyVersion("0.5")]
[assembly: AssemblyFileVersion("0.5.40117.0")]

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8B828433-B333-4C19-96AE-00BFFF9D8841}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestClient</RootNamespace>
<AssemblyName>TestClient</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class Constants
{
internal const string HttpScheme = "http";
internal const string HttpsScheme = "https";
internal const string Chunked = "chunked";
internal const string Close = "close";
internal const string Zero = "0";
internal const string SchemeDelimiter = "://";
internal const string DefaultServerAddress = "http://localhost:5000";
internal static Version V1_0 = new Version(1, 0);
internal static Version V1_1 = new Version(1, 1);
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
// Note this type should only be used while the request buffer remains pinned
internal class CookedUrl
{
private readonly HttpApiTypes.HTTP_COOKED_URL _nativeCookedUrl;
internal CookedUrl(HttpApiTypes.HTTP_COOKED_URL nativeCookedUrl)
{
_nativeCookedUrl = nativeCookedUrl;
}
internal unsafe string GetFullUrl()
{
if (_nativeCookedUrl.pFullUrl != null && _nativeCookedUrl.FullUrlLength > 0)
{
return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pFullUrl, _nativeCookedUrl.FullUrlLength / 2);
}
return null;
}
internal unsafe string GetHost()
{
if (_nativeCookedUrl.pHost != null && _nativeCookedUrl.HostLength > 0)
{
return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pHost, _nativeCookedUrl.HostLength / 2);
}
return null;
}
internal unsafe string GetAbsPath()
{
if (_nativeCookedUrl.pAbsPath != null && _nativeCookedUrl.AbsPathLength > 0)
{
return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pAbsPath, _nativeCookedUrl.AbsPathLength / 2);
}
return null;
}
internal unsafe string GetQueryString()
{
if (_nativeCookedUrl.pQueryString != null && _nativeCookedUrl.QueryStringLength > 0)
{
return Marshal.PtrToStringUni((IntPtr)_nativeCookedUrl.pQueryString, _nativeCookedUrl.QueryStringLength / 2);
}
return null;
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal sealed class HeapAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private static readonly IntPtr ProcessHeap = UnsafeNclNativeMethods.GetProcessHeap();
// Called by P/Invoke when returning SafeHandles
private HeapAllocHandle()
: base(ownsHandle: true)
{
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
return UnsafeNclNativeMethods.HeapFree(ProcessHeap, 0, handle);
}
}
}

View File

@ -0,0 +1,694 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static unsafe class HttpApiTypes
{
internal enum HTTP_API_VERSION
{
Invalid,
Version10,
Version20,
}
// see http.w for definitions
internal enum HTTP_SERVER_PROPERTY
{
HttpServerAuthenticationProperty,
HttpServerLoggingProperty,
HttpServerQosProperty,
HttpServerTimeoutsProperty,
HttpServerQueueLengthProperty,
HttpServerStateProperty,
HttpServer503VerbosityProperty,
HttpServerBindingProperty,
HttpServerExtendedAuthenticationProperty,
HttpServerListenEndpointProperty,
HttpServerChannelBindProperty,
HttpServerProtectionLevelProperty,
}
// Currently only one request info type is supported but the enum is for future extensibility.
internal enum HTTP_REQUEST_INFO_TYPE
{
HttpRequestInfoTypeAuth,
HttpRequestInfoTypeChannelBind,
HttpRequestInfoTypeSslProtocol,
HttpRequestInfoTypeSslTokenBinding
}
internal enum HTTP_RESPONSE_INFO_TYPE
{
HttpResponseInfoTypeMultipleKnownHeaders,
HttpResponseInfoTypeAuthenticationProperty,
HttpResponseInfoTypeQosProperty,
}
internal enum HTTP_TIMEOUT_TYPE
{
EntityBody,
DrainEntityBody,
RequestQueue,
IdleConnection,
HeaderWait,
MinSendRate,
}
internal const int MaxTimeout = 6;
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_VERSION
{
internal ushort MajorVersion;
internal ushort MinorVersion;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_KNOWN_HEADER
{
internal ushort RawValueLength;
internal byte* pRawValue;
}
[StructLayout(LayoutKind.Explicit)]
internal struct HTTP_DATA_CHUNK
{
[FieldOffset(0)]
internal HTTP_DATA_CHUNK_TYPE DataChunkType;
[FieldOffset(8)]
internal FromMemory fromMemory;
[FieldOffset(8)]
internal FromFileHandle fromFile;
}
[StructLayout(LayoutKind.Sequential)]
internal struct FromMemory
{
// 4 bytes for 32bit, 8 bytes for 64bit
internal IntPtr pBuffer;
internal uint BufferLength;
}
[StructLayout(LayoutKind.Sequential)]
internal struct FromFileHandle
{
internal ulong offset;
internal ulong count;
internal IntPtr fileHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTPAPI_VERSION
{
internal ushort HttpApiMajorVersion;
internal ushort HttpApiMinorVersion;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_COOKED_URL
{
internal ushort FullUrlLength;
internal ushort HostLength;
internal ushort AbsPathLength;
internal ushort QueryStringLength;
internal ushort* pFullUrl;
internal ushort* pHost;
internal ushort* pAbsPath;
internal ushort* pQueryString;
}
// Only cache unauthorized GETs + HEADs.
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_CACHE_POLICY
{
internal HTTP_CACHE_POLICY_TYPE Policy;
internal uint SecondsToLive;
}
internal enum HTTP_CACHE_POLICY_TYPE : int
{
HttpCachePolicyNocache = 0,
HttpCachePolicyUserInvalidates = 1,
HttpCachePolicyTimeToLive = 2,
}
[StructLayout(LayoutKind.Sequential)]
internal struct SOCKADDR
{
internal ushort sa_family;
internal byte sa_data;
internal byte sa_data_02;
internal byte sa_data_03;
internal byte sa_data_04;
internal byte sa_data_05;
internal byte sa_data_06;
internal byte sa_data_07;
internal byte sa_data_08;
internal byte sa_data_09;
internal byte sa_data_10;
internal byte sa_data_11;
internal byte sa_data_12;
internal byte sa_data_13;
internal byte sa_data_14;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_TRANSPORT_ADDRESS
{
internal SOCKADDR* pRemoteAddress;
internal SOCKADDR* pLocalAddress;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SSL_CLIENT_CERT_INFO
{
internal uint CertFlags;
internal uint CertEncodedSize;
internal byte* pCertEncoded;
internal void* Token;
internal byte CertDeniedByMapper;
}
internal enum HTTP_SERVICE_BINDING_TYPE : uint
{
HttpServiceBindingTypeNone = 0,
HttpServiceBindingTypeW,
HttpServiceBindingTypeA
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SERVICE_BINDING_BASE
{
internal HTTP_SERVICE_BINDING_TYPE Type;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST_CHANNEL_BIND_STATUS
{
internal IntPtr ServiceName;
internal IntPtr ChannelToken;
internal uint ChannelTokenSize;
internal uint Flags;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_UNKNOWN_HEADER
{
internal ushort NameLength;
internal ushort RawValueLength;
internal byte* pName;
internal byte* pRawValue;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SSL_INFO
{
internal ushort ServerCertKeySize;
internal ushort ConnectionKeySize;
internal uint ServerCertIssuerSize;
internal uint ServerCertSubjectSize;
internal byte* pServerCertIssuer;
internal byte* pServerCertSubject;
internal HTTP_SSL_CLIENT_CERT_INFO* pClientCertInfo;
internal uint SslClientCertNegotiated;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_RESPONSE_HEADERS
{
internal ushort UnknownHeaderCount;
internal HTTP_UNKNOWN_HEADER* pUnknownHeaders;
internal ushort TrailerCount;
internal HTTP_UNKNOWN_HEADER* pTrailers;
internal HTTP_KNOWN_HEADER KnownHeaders;
internal HTTP_KNOWN_HEADER KnownHeaders_02;
internal HTTP_KNOWN_HEADER KnownHeaders_03;
internal HTTP_KNOWN_HEADER KnownHeaders_04;
internal HTTP_KNOWN_HEADER KnownHeaders_05;
internal HTTP_KNOWN_HEADER KnownHeaders_06;
internal HTTP_KNOWN_HEADER KnownHeaders_07;
internal HTTP_KNOWN_HEADER KnownHeaders_08;
internal HTTP_KNOWN_HEADER KnownHeaders_09;
internal HTTP_KNOWN_HEADER KnownHeaders_10;
internal HTTP_KNOWN_HEADER KnownHeaders_11;
internal HTTP_KNOWN_HEADER KnownHeaders_12;
internal HTTP_KNOWN_HEADER KnownHeaders_13;
internal HTTP_KNOWN_HEADER KnownHeaders_14;
internal HTTP_KNOWN_HEADER KnownHeaders_15;
internal HTTP_KNOWN_HEADER KnownHeaders_16;
internal HTTP_KNOWN_HEADER KnownHeaders_17;
internal HTTP_KNOWN_HEADER KnownHeaders_18;
internal HTTP_KNOWN_HEADER KnownHeaders_19;
internal HTTP_KNOWN_HEADER KnownHeaders_20;
internal HTTP_KNOWN_HEADER KnownHeaders_21;
internal HTTP_KNOWN_HEADER KnownHeaders_22;
internal HTTP_KNOWN_HEADER KnownHeaders_23;
internal HTTP_KNOWN_HEADER KnownHeaders_24;
internal HTTP_KNOWN_HEADER KnownHeaders_25;
internal HTTP_KNOWN_HEADER KnownHeaders_26;
internal HTTP_KNOWN_HEADER KnownHeaders_27;
internal HTTP_KNOWN_HEADER KnownHeaders_28;
internal HTTP_KNOWN_HEADER KnownHeaders_29;
internal HTTP_KNOWN_HEADER KnownHeaders_30;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST_HEADERS
{
internal ushort UnknownHeaderCount;
internal HTTP_UNKNOWN_HEADER* pUnknownHeaders;
internal ushort TrailerCount;
internal HTTP_UNKNOWN_HEADER* pTrailers;
internal HTTP_KNOWN_HEADER KnownHeaders;
internal HTTP_KNOWN_HEADER KnownHeaders_02;
internal HTTP_KNOWN_HEADER KnownHeaders_03;
internal HTTP_KNOWN_HEADER KnownHeaders_04;
internal HTTP_KNOWN_HEADER KnownHeaders_05;
internal HTTP_KNOWN_HEADER KnownHeaders_06;
internal HTTP_KNOWN_HEADER KnownHeaders_07;
internal HTTP_KNOWN_HEADER KnownHeaders_08;
internal HTTP_KNOWN_HEADER KnownHeaders_09;
internal HTTP_KNOWN_HEADER KnownHeaders_10;
internal HTTP_KNOWN_HEADER KnownHeaders_11;
internal HTTP_KNOWN_HEADER KnownHeaders_12;
internal HTTP_KNOWN_HEADER KnownHeaders_13;
internal HTTP_KNOWN_HEADER KnownHeaders_14;
internal HTTP_KNOWN_HEADER KnownHeaders_15;
internal HTTP_KNOWN_HEADER KnownHeaders_16;
internal HTTP_KNOWN_HEADER KnownHeaders_17;
internal HTTP_KNOWN_HEADER KnownHeaders_18;
internal HTTP_KNOWN_HEADER KnownHeaders_19;
internal HTTP_KNOWN_HEADER KnownHeaders_20;
internal HTTP_KNOWN_HEADER KnownHeaders_21;
internal HTTP_KNOWN_HEADER KnownHeaders_22;
internal HTTP_KNOWN_HEADER KnownHeaders_23;
internal HTTP_KNOWN_HEADER KnownHeaders_24;
internal HTTP_KNOWN_HEADER KnownHeaders_25;
internal HTTP_KNOWN_HEADER KnownHeaders_26;
internal HTTP_KNOWN_HEADER KnownHeaders_27;
internal HTTP_KNOWN_HEADER KnownHeaders_28;
internal HTTP_KNOWN_HEADER KnownHeaders_29;
internal HTTP_KNOWN_HEADER KnownHeaders_30;
internal HTTP_KNOWN_HEADER KnownHeaders_31;
internal HTTP_KNOWN_HEADER KnownHeaders_32;
internal HTTP_KNOWN_HEADER KnownHeaders_33;
internal HTTP_KNOWN_HEADER KnownHeaders_34;
internal HTTP_KNOWN_HEADER KnownHeaders_35;
internal HTTP_KNOWN_HEADER KnownHeaders_36;
internal HTTP_KNOWN_HEADER KnownHeaders_37;
internal HTTP_KNOWN_HEADER KnownHeaders_38;
internal HTTP_KNOWN_HEADER KnownHeaders_39;
internal HTTP_KNOWN_HEADER KnownHeaders_40;
internal HTTP_KNOWN_HEADER KnownHeaders_41;
}
internal enum HTTP_VERB : int
{
HttpVerbUnparsed = 0,
HttpVerbUnknown = 1,
HttpVerbInvalid = 2,
HttpVerbOPTIONS = 3,
HttpVerbGET = 4,
HttpVerbHEAD = 5,
HttpVerbPOST = 6,
HttpVerbPUT = 7,
HttpVerbDELETE = 8,
HttpVerbTRACE = 9,
HttpVerbCONNECT = 10,
HttpVerbTRACK = 11,
HttpVerbMOVE = 12,
HttpVerbCOPY = 13,
HttpVerbPROPFIND = 14,
HttpVerbPROPPATCH = 15,
HttpVerbMKCOL = 16,
HttpVerbLOCK = 17,
HttpVerbUNLOCK = 18,
HttpVerbSEARCH = 19,
HttpVerbMaximum = 20,
}
internal static readonly string[] HttpVerbs = new string[]
{
null,
"Unknown",
"Invalid",
"OPTIONS",
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"TRACE",
"CONNECT",
"TRACK",
"MOVE",
"COPY",
"PROPFIND",
"PROPPATCH",
"MKCOL",
"LOCK",
"UNLOCK",
"SEARCH",
};
internal enum HTTP_DATA_CHUNK_TYPE : int
{
HttpDataChunkFromMemory = 0,
HttpDataChunkFromFileHandle = 1,
HttpDataChunkFromFragmentCache = 2,
HttpDataChunkMaximum = 3,
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_RESPONSE_INFO
{
internal HTTP_RESPONSE_INFO_TYPE Type;
internal uint Length;
internal HTTP_MULTIPLE_KNOWN_HEADERS* pInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_RESPONSE
{
internal uint Flags;
internal HTTP_VERSION Version;
internal ushort StatusCode;
internal ushort ReasonLength;
internal byte* pReason;
internal HTTP_RESPONSE_HEADERS Headers;
internal ushort EntityChunkCount;
internal HTTP_DATA_CHUNK* pEntityChunks;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_RESPONSE_V2
{
internal HTTP_RESPONSE Response_V1;
internal ushort ResponseInfoCount;
internal HTTP_RESPONSE_INFO* pResponseInfo;
}
internal enum HTTP_RESPONSE_INFO_FLAGS : uint
{
None = 0,
PreserveOrder = 1,
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_MULTIPLE_KNOWN_HEADERS
{
internal HTTP_RESPONSE_HEADER_ID.Enum HeaderId;
internal HTTP_RESPONSE_INFO_FLAGS Flags;
internal ushort KnownHeaderCount;
internal HTTP_KNOWN_HEADER* KnownHeaders;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST_AUTH_INFO
{
internal HTTP_AUTH_STATUS AuthStatus;
internal uint SecStatus;
internal uint Flags;
internal HTTP_REQUEST_AUTH_TYPE AuthType;
internal IntPtr AccessToken;
internal uint ContextAttributes;
internal uint PackedContextLength;
internal uint PackedContextType;
internal IntPtr PackedContext;
internal uint MutualAuthDataLength;
internal char* pMutualAuthData;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST_INFO
{
internal HTTP_REQUEST_INFO_TYPE InfoType;
internal uint InfoLength;
internal HTTP_REQUEST_AUTH_INFO* pInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST
{
internal uint Flags;
internal ulong ConnectionId;
internal ulong RequestId;
internal ulong UrlContext;
internal HTTP_VERSION Version;
internal HTTP_VERB Verb;
internal ushort UnknownVerbLength;
internal ushort RawUrlLength;
internal byte* pUnknownVerb;
internal byte* pRawUrl;
internal HTTP_COOKED_URL CookedUrl;
internal HTTP_TRANSPORT_ADDRESS Address;
internal HTTP_REQUEST_HEADERS Headers;
internal ulong BytesReceived;
internal ushort EntityChunkCount;
internal HTTP_DATA_CHUNK* pEntityChunks;
internal ulong RawConnectionId;
internal HTTP_SSL_INFO* pSslInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST_V2
{
internal HTTP_REQUEST Request;
internal ushort RequestInfoCount;
internal HTTP_REQUEST_INFO* pRequestInfo;
}
internal enum HTTP_AUTH_STATUS
{
HttpAuthStatusSuccess,
HttpAuthStatusNotAuthenticated,
HttpAuthStatusFailure,
}
internal enum HTTP_REQUEST_AUTH_TYPE
{
HttpRequestAuthTypeNone = 0,
HttpRequestAuthTypeBasic,
HttpRequestAuthTypeDigest,
HttpRequestAuthTypeNTLM,
HttpRequestAuthTypeNegotiate,
HttpRequestAuthTypeKerberos
}
internal enum HTTP_QOS_SETTING_TYPE
{
HttpQosSettingTypeBandwidth,
HttpQosSettingTypeConnectionLimit,
HttpQosSettingTypeFlowRate
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SERVER_AUTHENTICATION_INFO
{
internal HTTP_FLAGS Flags;
internal HTTP_AUTH_TYPES AuthSchemes;
internal bool ReceiveMutualAuth;
internal bool ReceiveContextHandle;
internal bool DisableNTLMCredentialCaching;
internal ulong ExFlags;
HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS DigestParams;
HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS BasicParams;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS
{
internal ushort DomainNameLength;
internal char* DomainName;
internal ushort RealmLength;
internal char* Realm;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS
{
ushort RealmLength;
char* Realm;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_REQUEST_TOKEN_BINDING_INFO
{
public byte* TokenBinding;
public uint TokenBindingSize;
public byte* TlsUnique;
public uint TlsUniqueSize;
public char* KeyType;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_TIMEOUT_LIMIT_INFO
{
internal HTTP_FLAGS Flags;
internal ushort EntityBody;
internal ushort DrainEntityBody;
internal ushort RequestQueue;
internal ushort IdleConnection;
internal ushort HeaderWait;
internal uint MinSendRate;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_BINDING_INFO
{
internal HTTP_FLAGS Flags;
internal IntPtr RequestQueueHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_CONNECTION_LIMIT_INFO
{
internal HTTP_FLAGS Flags;
internal uint MaxConnections;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_QOS_SETTING_INFO
{
internal HTTP_QOS_SETTING_TYPE QosType;
internal IntPtr QosSetting;
}
// see http.w for definitions
[Flags]
internal enum HTTP_FLAGS : uint
{
NONE = 0x00000000,
HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY = 0x00000001,
HTTP_RECEIVE_SECURE_CHANNEL_TOKEN = 0x00000001,
HTTP_SEND_RESPONSE_FLAG_DISCONNECT = 0x00000001,
HTTP_SEND_RESPONSE_FLAG_MORE_DATA = 0x00000002,
HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA = 0x00000004,
HTTP_SEND_RESPONSE_FLAG_RAW_HEADER = 0x00000004,
HTTP_SEND_REQUEST_FLAG_MORE_DATA = 0x00000001,
HTTP_PROPERTY_FLAG_PRESENT = 0x00000001,
HTTP_INITIALIZE_SERVER = 0x00000001,
HTTP_INITIALIZE_CBT = 0x00000004,
HTTP_SEND_RESPONSE_FLAG_OPAQUE = 0x00000040,
}
[Flags]
internal enum HTTP_AUTH_TYPES : uint
{
NONE = 0x00000000,
HTTP_AUTH_ENABLE_BASIC = 0x00000001,
HTTP_AUTH_ENABLE_DIGEST = 0x00000002,
HTTP_AUTH_ENABLE_NTLM = 0x00000004,
HTTP_AUTH_ENABLE_NEGOTIATE = 0x00000008,
HTTP_AUTH_ENABLE_KERBEROS = 0x00000010,
}
internal static class HTTP_RESPONSE_HEADER_ID
{
private static string[] _strings =
{
"Cache-Control",
"Connection",
"Date",
"Keep-Alive",
"Pragma",
"Trailer",
"Transfer-Encoding",
"Upgrade",
"Via",
"Warning",
"Allow",
"Content-Length",
"Content-Type",
"Content-Encoding",
"Content-Language",
"Content-Location",
"Content-MD5",
"Content-Range",
"Expires",
"Last-Modified",
"Accept-Ranges",
"Age",
"ETag",
"Location",
"Proxy-Authenticate",
"Retry-After",
"Server",
"Set-Cookie",
"Vary",
"WWW-Authenticate",
};
private static Dictionary<string, int> _lookupTable = CreateLookupTable();
private static Dictionary<string, int> CreateLookupTable()
{
Dictionary<string, int> lookupTable = new Dictionary<string, int>((int)Enum.HttpHeaderResponseMaximum, StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < (int)Enum.HttpHeaderResponseMaximum; i++)
{
lookupTable.Add(_strings[i], i);
}
return lookupTable;
}
internal static int IndexOfKnownHeader(string HeaderName)
{
int index;
return _lookupTable.TryGetValue(HeaderName, out index) ? index : -1;
}
internal enum Enum
{
HttpHeaderCacheControl = 0, // general-header [section 4.5]
HttpHeaderConnection = 1, // general-header [section 4.5]
HttpHeaderDate = 2, // general-header [section 4.5]
HttpHeaderKeepAlive = 3, // general-header [not in rfc]
HttpHeaderPragma = 4, // general-header [section 4.5]
HttpHeaderTrailer = 5, // general-header [section 4.5]
HttpHeaderTransferEncoding = 6, // general-header [section 4.5]
HttpHeaderUpgrade = 7, // general-header [section 4.5]
HttpHeaderVia = 8, // general-header [section 4.5]
HttpHeaderWarning = 9, // general-header [section 4.5]
HttpHeaderAllow = 10, // entity-header [section 7.1]
HttpHeaderContentLength = 11, // entity-header [section 7.1]
HttpHeaderContentType = 12, // entity-header [section 7.1]
HttpHeaderContentEncoding = 13, // entity-header [section 7.1]
HttpHeaderContentLanguage = 14, // entity-header [section 7.1]
HttpHeaderContentLocation = 15, // entity-header [section 7.1]
HttpHeaderContentMd5 = 16, // entity-header [section 7.1]
HttpHeaderContentRange = 17, // entity-header [section 7.1]
HttpHeaderExpires = 18, // entity-header [section 7.1]
HttpHeaderLastModified = 19, // entity-header [section 7.1]
// Response Headers
HttpHeaderAcceptRanges = 20, // response-header [section 6.2]
HttpHeaderAge = 21, // response-header [section 6.2]
HttpHeaderEtag = 22, // response-header [section 6.2]
HttpHeaderLocation = 23, // response-header [section 6.2]
HttpHeaderProxyAuthenticate = 24, // response-header [section 6.2]
HttpHeaderRetryAfter = 25, // response-header [section 6.2]
HttpHeaderServer = 26, // response-header [section 6.2]
HttpHeaderSetCookie = 27, // response-header [not in rfc]
HttpHeaderVary = 28, // response-header [section 6.2]
HttpHeaderWwwAuthenticate = 29, // response-header [section 6.2]
HttpHeaderResponseMaximum = 30,
HttpHeaderMaximum = 41
}
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal enum HttpSysRequestHeader
{
CacheControl = 0, // general-header [section 4.5]
Connection = 1, // general-header [section 4.5]
Date = 2, // general-header [section 4.5]
KeepAlive = 3, // general-header [not in rfc]
Pragma = 4, // general-header [section 4.5]
Trailer = 5, // general-header [section 4.5]
TransferEncoding = 6, // general-header [section 4.5]
Upgrade = 7, // general-header [section 4.5]
Via = 8, // general-header [section 4.5]
Warning = 9, // general-header [section 4.5]
Allow = 10, // entity-header [section 7.1]
ContentLength = 11, // entity-header [section 7.1]
ContentType = 12, // entity-header [section 7.1]
ContentEncoding = 13, // entity-header [section 7.1]
ContentLanguage = 14, // entity-header [section 7.1]
ContentLocation = 15, // entity-header [section 7.1]
ContentMd5 = 16, // entity-header [section 7.1]
ContentRange = 17, // entity-header [section 7.1]
Expires = 18, // entity-header [section 7.1]
LastModified = 19, // entity-header [section 7.1]
Accept = 20, // request-header [section 5.3]
AcceptCharset = 21, // request-header [section 5.3]
AcceptEncoding = 22, // request-header [section 5.3]
AcceptLanguage = 23, // request-header [section 5.3]
Authorization = 24, // request-header [section 5.3]
Cookie = 25, // request-header [not in rfc]
Expect = 26, // request-header [section 5.3]
From = 27, // request-header [section 5.3]
Host = 28, // request-header [section 5.3]
IfMatch = 29, // request-header [section 5.3]
IfModifiedSince = 30, // request-header [section 5.3]
IfNoneMatch = 31, // request-header [section 5.3]
IfRange = 32, // request-header [section 5.3]
IfUnmodifiedSince = 33, // request-header [section 5.3]
MaxForwards = 34, // request-header [section 5.3]
ProxyAuthorization = 35, // request-header [section 5.3]
Referer = 36, // request-header [section 5.3]
Range = 37, // request-header [section 5.3]
Te = 38, // request-header [section 5.3]
Translate = 39, // request-header [webDAV, not in rfc 2518]
UserAgent = 40, // request-header [section 5.3]
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal enum HttpSysResponseHeader
{
CacheControl = 0, // general-header [section 4.5]
Connection = 1, // general-header [section 4.5]
Date = 2, // general-header [section 4.5]
KeepAlive = 3, // general-header [not in rfc]
Pragma = 4, // general-header [section 4.5]
Trailer = 5, // general-header [section 4.5]
TransferEncoding = 6, // general-header [section 4.5]
Upgrade = 7, // general-header [section 4.5]
Via = 8, // general-header [section 4.5]
Warning = 9, // general-header [section 4.5]
Allow = 10, // entity-header [section 7.1]
ContentLength = 11, // entity-header [section 7.1]
ContentType = 12, // entity-header [section 7.1]
ContentEncoding = 13, // entity-header [section 7.1]
ContentLanguage = 14, // entity-header [section 7.1]
ContentLocation = 15, // entity-header [section 7.1]
ContentMd5 = 16, // entity-header [section 7.1]
ContentRange = 17, // entity-header [section 7.1]
Expires = 18, // entity-header [section 7.1]
LastModified = 19, // entity-header [section 7.1]
AcceptRanges = 20, // response-header [section 6.2]
Age = 21, // response-header [section 6.2]
ETag = 22, // response-header [section 6.2]
Location = 23, // response-header [section 6.2]
ProxyAuthenticate = 24, // response-header [section 6.2]
RetryAfter = 25, // response-header [section 6.2]
Server = 26, // response-header [section 6.2]
SetCookie = 27, // response-header [not in rfc]
Vary = 28, // response-header [section 6.2]
WwwAuthenticate = 29, // response-header [section 6.2]
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class NclUtilities
{
internal static bool HasShutdownStarted
{
get
{
return Environment.HasShutdownStarted
|| AppDomain.CurrentDomain.IsFinalizingForUnload();
}
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Authentication.ExtendedProtection;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal class SafeLocalFreeChannelBinding : ChannelBinding
{
private const int LMEM_FIXED = 0;
private int size;
public override int Size
{
get { return size; }
}
public static SafeLocalFreeChannelBinding LocalAlloc(int cb)
{
SafeLocalFreeChannelBinding result;
result = UnsafeNclNativeMethods.SafeNetHandles.LocalAllocChannelBinding(LMEM_FIXED, (UIntPtr)cb);
if (result.IsInvalid)
{
result.SetHandleAsInvalid();
throw new OutOfMemoryException();
}
result.size = cb;
return result;
}
protected override bool ReleaseHandle()
{
return UnsafeNclNativeMethods.SafeNetHandles.LocalFree(handle) == IntPtr.Zero;
}
public override bool IsInvalid
{
get
{
return handle == IntPtr.Zero || handle.ToInt32() == -1;
}
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
{
internal SafeLocalMemHandle()
: base(true)
{
}
internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(existingHandle);
}
protected override bool ReleaseHandle()
{
return UnsafeNclNativeMethods.SafeNetHandles.LocalFree(handle) == IntPtr.Zero;
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal class SafeNativeOverlapped : SafeHandle
{
internal static readonly SafeNativeOverlapped Zero = new SafeNativeOverlapped();
private ThreadPoolBoundHandle _boundHandle;
internal SafeNativeOverlapped()
: base(IntPtr.Zero, true)
{
}
internal unsafe SafeNativeOverlapped(ThreadPoolBoundHandle boundHandle, NativeOverlapped* handle)
: base(IntPtr.Zero, true)
{
SetHandle((IntPtr)handle);
_boundHandle = boundHandle;
}
public override bool IsInvalid
{
get { return handle == IntPtr.Zero; }
}
public void ReinitializeNativeOverlapped()
{
IntPtr handleSnapshot = handle;
if (handleSnapshot != IntPtr.Zero)
{
unsafe
{
((NativeOverlapped*)handleSnapshot)->InternalHigh = IntPtr.Zero;
((NativeOverlapped*)handleSnapshot)->InternalLow = IntPtr.Zero;
((NativeOverlapped*)handleSnapshot)->EventHandle = IntPtr.Zero;
}
}
}
protected override bool ReleaseHandle()
{
IntPtr oldHandle = Interlocked.Exchange(ref handle, IntPtr.Zero);
// Do not call free durring AppDomain shutdown, there may be an outstanding operation.
// Overlapped will take care calling free when the native callback completes.
if (oldHandle != IntPtr.Zero && !NclUtilities.HasShutdownStarted)
{
unsafe
{
_boundHandle.FreeNativeOverlapped((NativeOverlapped*)oldHandle);
}
}
return true;
}
}
}

View File

@ -0,0 +1,371 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
// a little perf app measured these times when comparing the internal
// buffer implemented as a managed byte[] or unmanaged memory IntPtr
// that's why we use byte[]
// byte[] total ms:19656
// IntPtr total ms:25671
/// <devdoc>
/// <para>
/// This class is used when subclassing EndPoint, and provides indication
/// on how to format the memory buffers that winsock uses for network addresses.
/// </para>
/// </devdoc>
internal class SocketAddress
{
private const int NumberOfIPv6Labels = 8;
// Lower case hex, no leading zeros
private const string IPv6NumberFormat = "{0:x}";
private const string IPv6StringSeparator = ":";
private const string IPv4StringFormat = "{0:d}.{1:d}.{2:d}.{3:d}";
internal const int IPv6AddressSize = 28;
internal const int IPv4AddressSize = 16;
private const int WriteableOffset = 2;
private int _size;
private byte[] _buffer;
private int _hash;
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public SocketAddress(AddressFamily family, int size)
{
if (size < WriteableOffset)
{
// it doesn't make sense to create a socket address with less tha
// 2 bytes, that's where we store the address family.
throw new ArgumentOutOfRangeException("size");
}
_size = size;
_buffer = new byte[((size / IntPtr.Size) + 2) * IntPtr.Size]; // sizeof DWORD
#if BIGENDIAN
m_Buffer[0] = unchecked((byte)((int)family>>8));
m_Buffer[1] = unchecked((byte)((int)family ));
#else
_buffer[0] = unchecked((byte)((int)family));
_buffer[1] = unchecked((byte)((int)family >> 8));
#endif
}
internal byte[] Buffer
{
get { return _buffer; }
}
internal AddressFamily Family
{
get
{
int family;
#if BIGENDIAN
family = ((int)m_Buffer[0]<<8) | m_Buffer[1];
#else
family = _buffer[0] | ((int)_buffer[1] << 8);
#endif
return (AddressFamily)family;
}
}
internal int Size
{
get
{
return _size;
}
}
// access to unmanaged serialized data. this doesn't
// allow access to the first 2 bytes of unmanaged memory
// that are supposed to contain the address family which
// is readonly.
//
// <SECREVIEW> you can still use negative offsets as a back door in case
// winsock changes the way it uses SOCKADDR. maybe we want to prohibit it?
// maybe we should make the class sealed to avoid potentially dangerous calls
// into winsock with unproperly formatted data? </SECREVIEW>
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
private byte this[int offset]
{
get
{
// access
if (offset < 0 || offset >= Size)
{
throw new ArgumentOutOfRangeException("offset");
}
return _buffer[offset];
}
}
internal int GetPort()
{
return (int)((_buffer[2] << 8 & 0xFF00) | (_buffer[3]));
}
public override bool Equals(object comparand)
{
SocketAddress castedComparand = comparand as SocketAddress;
if (castedComparand == null || this.Size != castedComparand.Size)
{
return false;
}
for (int i = 0; i < this.Size; i++)
{
if (this[i] != castedComparand[i])
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
if (_hash == 0)
{
int i;
int size = Size & ~3;
for (i = 0; i < size; i += 4)
{
_hash ^= (int)_buffer[i]
| ((int)_buffer[i + 1] << 8)
| ((int)_buffer[i + 2] << 16)
| ((int)_buffer[i + 3] << 24);
}
if ((Size & 3) != 0)
{
int remnant = 0;
int shift = 0;
for (; i < Size; ++i)
{
remnant |= ((int)_buffer[i]) << shift;
shift += 8;
}
_hash ^= remnant;
}
}
return _hash;
}
internal IPAddress GetIPAddress()
{
if (Family == AddressFamily.InterNetworkV6)
{
return GetIpv6Address();
}
else if (Family == AddressFamily.InterNetwork)
{
return GetIPv4Address();
}
else
{
return null;
}
}
private IPAddress GetIpv6Address()
{
Contract.Assert(Size >= IPv6AddressSize);
byte[] bytes = new byte[NumberOfIPv6Labels * 2];
Array.Copy(_buffer, 8, bytes, 0, NumberOfIPv6Labels * 2);
return new IPAddress(bytes); // TODO: Does scope id matter?
}
private IPAddress GetIPv4Address()
{
Contract.Assert(Size >= IPv4AddressSize);
return new IPAddress(new byte[] { _buffer[4], _buffer[5], _buffer[6], _buffer[7] });
}
public override string ToString()
{
StringBuilder bytes = new StringBuilder();
for (int i = WriteableOffset; i < this.Size; i++)
{
if (i > WriteableOffset)
{
bytes.Append(",");
}
bytes.Append(this[i].ToString(NumberFormatInfo.InvariantInfo));
}
return Family.ToString() + ":" + Size.ToString(NumberFormatInfo.InvariantInfo) + ":{" + bytes.ToString() + "}";
}
internal string GetIPAddressString()
{
if (Family == AddressFamily.InterNetworkV6)
{
return GetIpv6AddressString();
}
else if (Family == AddressFamily.InterNetwork)
{
return GetIPv4AddressString();
}
else
{
return null;
}
}
private string GetIPv4AddressString()
{
Contract.Assert(Size >= IPv4AddressSize);
return string.Format(CultureInfo.InvariantCulture, IPv4StringFormat,
_buffer[4], _buffer[5], _buffer[6], _buffer[7]);
}
// TODO: Does scope ID ever matter?
private unsafe string GetIpv6AddressString()
{
Contract.Assert(Size >= IPv6AddressSize);
fixed (byte* rawBytes = _buffer)
{
// Convert from bytes to shorts.
ushort* rawShorts = stackalloc ushort[NumberOfIPv6Labels];
int numbersOffset = 0;
// The address doesn't start at the beginning of the buffer.
for (int i = 8; i < ((NumberOfIPv6Labels * 2) + 8); i += 2)
{
rawShorts[numbersOffset++] = (ushort)(rawBytes[i] << 8 | rawBytes[i + 1]);
}
return GetIPv6AddressString(rawShorts);
}
}
private static unsafe string GetIPv6AddressString(ushort* numbers)
{
// RFC 5952 Sections 4 & 5 - Compressed, lower case, with possible embedded IPv4 addresses.
// Start to finish, inclusive. <-1, -1> for no compression
KeyValuePair<int, int> range = FindCompressionRange(numbers);
bool ipv4Embedded = ShouldHaveIpv4Embedded(numbers);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < NumberOfIPv6Labels; i++)
{
if (ipv4Embedded && i == (NumberOfIPv6Labels - 2))
{
// Write the remaining digits as an IPv4 address
builder.Append(IPv6StringSeparator);
builder.Append(string.Format(CultureInfo.InvariantCulture, IPv4StringFormat,
numbers[i] >> 8, numbers[i] & 0xFF, numbers[i + 1] >> 8, numbers[i + 1] & 0xFF));
break;
}
// Compression; 1::1, ::1, 1::
if (range.Key == i)
{
// Start compression, add :
builder.Append(IPv6StringSeparator);
}
if (range.Key <= i && range.Value == (NumberOfIPv6Labels - 1))
{
// Remainder compressed; 1::
builder.Append(IPv6StringSeparator);
break;
}
if (range.Key <= i && i <= range.Value)
{
continue; // Compressed
}
if (i != 0)
{
builder.Append(IPv6StringSeparator);
}
builder.Append(string.Format(CultureInfo.InvariantCulture, IPv6NumberFormat, numbers[i]));
}
return builder.ToString();
}
// RFC 5952 Section 4.2.3
// Longest consecutive sequence of zero segments, minimum 2.
// On equal, first sequence wins.
// <-1, -1> for no compression.
private static unsafe KeyValuePair<int, int> FindCompressionRange(ushort* numbers)
{
int longestSequenceLength = 0;
int longestSequenceStart = -1;
int currentSequenceLength = 0;
for (int i = 0; i < NumberOfIPv6Labels; i++)
{
if (numbers[i] == 0)
{
// In a sequence
currentSequenceLength++;
if (currentSequenceLength > longestSequenceLength)
{
longestSequenceLength = currentSequenceLength;
longestSequenceStart = i - currentSequenceLength + 1;
}
}
else
{
currentSequenceLength = 0;
}
}
if (longestSequenceLength >= 2)
{
return new KeyValuePair<int, int>(longestSequenceStart,
longestSequenceStart + longestSequenceLength - 1);
}
return new KeyValuePair<int, int>(-1, -1); // No compression
}
// Returns true if the IPv6 address should be formated with an embedded IPv4 address:
// ::192.168.1.1
private static unsafe bool ShouldHaveIpv4Embedded(ushort* numbers)
{
// 0:0 : 0:0 : x:x : x.x.x.x
if (numbers[0] == 0 && numbers[1] == 0 && numbers[2] == 0 && numbers[3] == 0 && numbers[6] != 0)
{
// RFC 5952 Section 5 - 0:0 : 0:0 : 0:[0 | FFFF] : x.x.x.x
if (numbers[4] == 0 && (numbers[5] == 0 || numbers[5] == 0xFFFF))
{
return true;
// SIIT - 0:0 : 0:0 : FFFF:0 : x.x.x.x
}
else if (numbers[4] == 0xFFFF && numbers[5] == 0)
{
return true;
}
}
// ISATAP
if (numbers[4] == 0 && numbers[5] == 0x5EFE)
{
return true;
}
return false;
}
} // class SocketAddress
} // namespace System.Net

View File

@ -0,0 +1,155 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static unsafe class UnsafeNclNativeMethods
{
private const string sspicli_LIB = "sspicli.dll";
private const string api_ms_win_core_processthreads_LIB = "api-ms-win-core-processthreads-l1-1-1.dll";
private const string api_ms_win_core_io_LIB = "api-ms-win-core-io-l1-1-0.dll";
private const string api_ms_win_core_handle_LIB = "api-ms-win-core-handle-l1-1-0.dll";
private const string api_ms_win_core_libraryloader_LIB = "api-ms-win-core-libraryloader-l1-1-0.dll";
private const string api_ms_win_core_heap_LIB = "api-ms-win-core-heap-L1-2-0.dll";
private const string api_ms_win_core_heap_obsolete_LIB = "api-ms-win-core-heap-obsolete-L1-1-0.dll";
private const string api_ms_win_core_kernel32_legacy_LIB = "api-ms-win-core-kernel32-legacy-l1-1-0.dll";
private const string TOKENBINDING = "tokenbinding.dll";
// CONSIDER: Make this an enum, requires changing a lot of types from uint to ErrorCodes.
internal static class ErrorCodes
{
internal const uint ERROR_SUCCESS = 0;
internal const uint ERROR_HANDLE_EOF = 38;
internal const uint ERROR_NOT_SUPPORTED = 50;
internal const uint ERROR_INVALID_PARAMETER = 87;
internal const uint ERROR_ALREADY_EXISTS = 183;
internal const uint ERROR_MORE_DATA = 234;
internal const uint ERROR_OPERATION_ABORTED = 995;
internal const uint ERROR_IO_PENDING = 997;
internal const uint ERROR_NOT_FOUND = 1168;
internal const uint ERROR_CONNECTION_INVALID = 1229;
}
[DllImport(api_ms_win_core_io_LIB, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static unsafe extern uint CancelIoEx(SafeHandle handle, SafeNativeOverlapped overlapped);
[DllImport(api_ms_win_core_kernel32_legacy_LIB, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static unsafe extern bool SetFileCompletionNotificationModes(SafeHandle handle, FileCompletionNotificationModes modes);
[Flags]
internal enum FileCompletionNotificationModes : byte
{
None = 0,
SkipCompletionPortOnSuccess = 1,
SkipSetEventOnHandle = 2
}
[DllImport(TOKENBINDING, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern int TokenBindingVerifyMessage(
[In] byte* tokenBindingMessage,
[In] uint tokenBindingMessageSize,
[In] char* keyType,
[In] byte* tlsUnique,
[In] uint tlsUniqueSize,
[Out] out HeapAllocHandle resultList);
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa366569(v=vs.85).aspx
[DllImport(api_ms_win_core_heap_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
internal static extern IntPtr GetProcessHeap();
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa366701(v=vs.85).aspx
[DllImport(api_ms_win_core_heap_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
internal static extern bool HeapFree(
[In] IntPtr hHeap,
[In] uint dwFlags,
[In] IntPtr lpMem);
internal static class SafeNetHandles
{
[DllImport(sspicli_LIB, ExactSpelling = true, SetLastError = true)]
internal static extern int FreeContextBuffer(
[In] IntPtr contextBuffer);
[DllImport(api_ms_win_core_handle_LIB, ExactSpelling = true, SetLastError = true)]
internal static extern bool CloseHandle(IntPtr handle);
[DllImport(api_ms_win_core_heap_obsolete_LIB, EntryPoint = "LocalAlloc", SetLastError = true)]
internal static extern SafeLocalFreeChannelBinding LocalAllocChannelBinding(int uFlags, UIntPtr sizetdwBytes);
[DllImport(api_ms_win_core_heap_obsolete_LIB, ExactSpelling = true, SetLastError = true)]
internal static extern IntPtr LocalFree(IntPtr handle);
}
// from tokenbinding.h
internal static class TokenBinding
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct TOKENBINDING_RESULT_DATA
{
public uint identifierSize;
public TOKENBINDING_IDENTIFIER* identifierData;
public TOKENBINDING_EXTENSION_FORMAT extensionFormat;
public uint extensionSize;
public IntPtr extensionData;
}
[StructLayout(LayoutKind.Sequential)]
internal struct TOKENBINDING_IDENTIFIER
{
// Note: If the layout of these fields changes, be sure to make the
// corresponding change to TokenBindingUtil.ExtractIdentifierBlob.
public TOKENBINDING_TYPE bindingType;
public TOKENBINDING_HASH_ALGORITHM hashAlgorithm;
public TOKENBINDING_SIGNATURE_ALGORITHM signatureAlgorithm;
}
internal enum TOKENBINDING_TYPE : byte
{
TOKENBINDING_TYPE_PROVIDED = 0,
TOKENBINDING_TYPE_REFERRED = 1,
}
internal enum TOKENBINDING_HASH_ALGORITHM : byte
{
TOKENBINDING_HASH_ALGORITHM_SHA256 = 4,
}
internal enum TOKENBINDING_SIGNATURE_ALGORITHM : byte
{
TOKENBINDING_SIGNATURE_ALGORITHM_RSA = 1,
TOKENBINDING_SIGNATURE_ALGORITHM_ECDSAP256 = 3,
}
internal enum TOKENBINDING_EXTENSION_FORMAT
{
TOKENBINDING_EXTENSION_FORMAT_UNDEFINED = 0,
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct TOKENBINDING_RESULT_LIST
{
public uint resultCount;
public TOKENBINDING_RESULT_DATA* resultData;
}
}
// DACL related stuff
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Instantiated natively")]
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
Justification = "Does not own the resource.")]
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
public int nLength = 12;
public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false);
public bool bInheritHandle = false;
}
}
}

View File

@ -0,0 +1,243 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal class HeaderCollection : IHeaderDictionary
{
private long? _contentLength;
private StringValues _contentLengthText;
public HeaderCollection()
: this(new Dictionary<string, StringValues>(4, StringComparer.OrdinalIgnoreCase))
{
}
public HeaderCollection(IDictionary<string, StringValues> store)
{
Store = store;
}
private IDictionary<string, StringValues> Store { get; set; }
// Readonly after the response has been started.
public bool IsReadOnly { get; internal set; }
public StringValues this[string key]
{
get
{
StringValues values;
return TryGetValue(key, out values) ? values : StringValues.Empty;
}
set
{
ThrowIfReadOnly();
if (StringValues.IsNullOrEmpty(value))
{
Remove(key);
}
else
{
ValidateHeaderCharacters(key);
ValidateHeaderCharacters(value);
Store[key] = value;
}
}
}
StringValues IDictionary<string, StringValues>.this[string key]
{
get { return Store[key]; }
set
{
ThrowIfReadOnly();
ValidateHeaderCharacters(key);
ValidateHeaderCharacters(value);
Store[key] = value;
}
}
public int Count
{
get { return Store.Count; }
}
public ICollection<string> Keys
{
get { return Store.Keys; }
}
public ICollection<StringValues> Values
{
get { return Store.Values; }
}
public long? ContentLength
{
get
{
long value;
var rawValue = this[HttpKnownHeaderNames.ContentLength];
if (_contentLengthText.Equals(rawValue))
{
return _contentLength;
}
if (rawValue.Count == 1 &&
!string.IsNullOrWhiteSpace(rawValue[0]) &&
HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
{
_contentLengthText = rawValue;
_contentLength = value;
return value;
}
return null;
}
set
{
ThrowIfReadOnly();
if (value.HasValue)
{
if (value.Value < 0)
{
throw new ArgumentOutOfRangeException("value", value.Value, "Cannot be negative.");
}
_contentLengthText = HeaderUtilities.FormatNonNegativeInt64(value.Value);
this[HttpKnownHeaderNames.ContentLength] = _contentLengthText;
_contentLength = value;
}
else
{
Remove(HttpKnownHeaderNames.ContentLength);
_contentLengthText = StringValues.Empty;
_contentLength = null;
}
}
}
public void Add(KeyValuePair<string, StringValues> item)
{
ThrowIfReadOnly();
ValidateHeaderCharacters(item.Key);
ValidateHeaderCharacters(item.Value);
Store.Add(item);
}
public void Add(string key, StringValues value)
{
ThrowIfReadOnly();
ValidateHeaderCharacters(key);
ValidateHeaderCharacters(value);
Store.Add(key, value);
}
public void Append(string key, string value)
{
ThrowIfReadOnly();
ValidateHeaderCharacters(key);
ValidateHeaderCharacters(value);
StringValues values;
Store.TryGetValue(key, out values);
Store[key] = StringValues.Concat(values, value);
}
public void Clear()
{
ThrowIfReadOnly();
Store.Clear();
}
public bool Contains(KeyValuePair<string, StringValues> item)
{
return Store.Contains(item);
}
public bool ContainsKey(string key)
{
return Store.ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{
Store.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator()
{
return Store.GetEnumerator();
}
public IEnumerable<string> GetValues(string key)
{
StringValues values;
if (Store.TryGetValue(key, out values))
{
return HeaderParser.SplitValues(values);
}
return HeaderParser.Empty;
}
public bool Remove(KeyValuePair<string, StringValues> item)
{
ThrowIfReadOnly();
return Store.Remove(item);
}
public bool Remove(string key)
{
ThrowIfReadOnly();
return Store.Remove(key);
}
public bool TryGetValue(string key, out StringValues value)
{
return Store.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("The response headers cannot be modified because the response has already started.");
}
}
public static void ValidateHeaderCharacters(StringValues headerValues)
{
foreach (var value in headerValues)
{
ValidateHeaderCharacters(value);
}
}
public static void ValidateHeaderCharacters(string headerCharacters)
{
if (headerCharacters != null)
{
foreach (var ch in headerCharacters)
{
if (ch < 0x20)
{
throw new InvalidOperationException(string.Format("Invalid control character in header: 0x{0:X2}", (byte)ch));
}
}
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class HeaderEncoding
{
// 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
// ASCII, and because some old client would send UTF8 Host headers and expect UTF8 Location responses
// (e.g. IE and HttpWebRequest on intranets).
private static Encoding Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
internal static unsafe string GetString(byte* pBytes, int byteCount)
{
// net451: return new string(pBytes, 0, byteCount, Encoding);
var charCount = Encoding.GetCharCount(pBytes, byteCount);
var chars = new char[charCount];
fixed (char* pChars = chars)
{
var count = Encoding.GetChars(pBytes, byteCount, pChars, charCount);
System.Diagnostics.Debug.Assert(count == charCount);
}
return new string(chars);
}
internal static byte[] GetBytes(string myString)
{
return Encoding.GetBytes(myString);
}
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class HeaderParser
{
internal static IEnumerable<string> Empty = new string[0];
// Split on commas, except in quotes
internal static IEnumerable<string> SplitValues(StringValues values)
{
foreach (var value in values)
{
int start = 0;
bool inQuotes = false;
int current = 0;
for ( ; current < value.Length; current++)
{
char ch = value[current];
if (inQuotes)
{
if (ch == '"')
{
inQuotes = false;
}
}
else if (ch == '"')
{
inQuotes = true;
}
else if (ch == ',')
{
var subValue = value.Substring(start, current - start);
if (!string.IsNullOrWhiteSpace(subValue))
{
yield return subValue.Trim();
start = current + 1;
}
}
}
if (start < current)
{
var subValue = value.Substring(start, current - start);
if (!string.IsNullOrWhiteSpace(subValue))
{
yield return subValue.Trim();
start = current + 1;
}
}
}
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class HttpKnownHeaderNames
{
internal const string CacheControl = "Cache-Control";
internal const string Connection = "Connection";
internal const string Date = "Date";
internal const string KeepAlive = "Keep-Alive";
internal const string Pragma = "Pragma";
internal const string ProxyConnection = "Proxy-Connection";
internal const string Trailer = "Trailer";
internal const string TransferEncoding = "Transfer-Encoding";
internal const string Upgrade = "Upgrade";
internal const string Via = "Via";
internal const string Warning = "Warning";
internal const string ContentLength = "Content-Length";
internal const string ContentType = "Content-Type";
internal const string ContentDisposition = "Content-Disposition";
internal const string ContentEncoding = "Content-Encoding";
internal const string ContentLanguage = "Content-Language";
internal const string ContentLocation = "Content-Location";
internal const string ContentRange = "Content-Range";
internal const string Expires = "Expires";
internal const string LastModified = "Last-Modified";
internal const string Age = "Age";
internal const string Location = "Location";
internal const string ProxyAuthenticate = "Proxy-Authenticate";
internal const string RetryAfter = "Retry-After";
internal const string Server = "Server";
internal const string SetCookie = "Set-Cookie";
internal const string SetCookie2 = "Set-Cookie2";
internal const string Vary = "Vary";
internal const string WWWAuthenticate = "WWW-Authenticate";
internal const string Accept = "Accept";
internal const string AcceptCharset = "Accept-Charset";
internal const string AcceptEncoding = "Accept-Encoding";
internal const string AcceptLanguage = "Accept-Language";
internal const string Authorization = "Authorization";
internal const string Cookie = "Cookie";
internal const string Cookie2 = "Cookie2";
internal const string Expect = "Expect";
internal const string From = "From";
internal const string Host = "Host";
internal const string IfMatch = "If-Match";
internal const string IfModifiedSince = "If-Modified-Since";
internal const string IfNoneMatch = "If-None-Match";
internal const string IfRange = "If-Range";
internal const string IfUnmodifiedSince = "If-Unmodified-Since";
internal const string MaxForwards = "Max-Forwards";
internal const string ProxyAuthorization = "Proxy-Authorization";
internal const string Referer = "Referer";
internal const string Range = "Range";
internal const string UserAgent = "User-Agent";
internal const string ContentMD5 = "Content-MD5";
internal const string ETag = "ETag";
internal const string TE = "TE";
internal const string Allow = "Allow";
internal const string AcceptRanges = "Accept-Ranges";
internal const string P3P = "P3P";
internal const string XPoweredBy = "X-Powered-By";
internal const string XAspNetVersion = "X-AspNet-Version";
internal const string SecWebSocketKey = "Sec-WebSocket-Key";
internal const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
internal const string SecWebSocketAccept = "Sec-WebSocket-Accept";
internal const string Origin = "Origin";
internal const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
internal const string SecWebSocketVersion = "Sec-WebSocket-Version";
}
}

View File

@ -0,0 +1,455 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal unsafe class NativeRequestContext : IDisposable
{
private const int AlignmentPadding = 8;
private IntPtr _originalBufferAddress;
private HttpApiTypes.HTTP_REQUEST* _nativeRequest;
private byte[] _backingBuffer;
private int _bufferAlignment;
private SafeNativeOverlapped _nativeOverlapped;
private bool _permanentlyPinned;
// To be used by HttpSys
internal NativeRequestContext(SafeNativeOverlapped nativeOverlapped,
int bufferAlignment,
HttpApiTypes.HTTP_REQUEST* nativeRequest,
byte[] backingBuffer,
ulong requestId)
{
_nativeOverlapped = nativeOverlapped;
_bufferAlignment = bufferAlignment;
_nativeRequest = nativeRequest;
_backingBuffer = backingBuffer;
RequestId = requestId;
}
// To be used by IIS Integration.
internal NativeRequestContext(HttpApiTypes.HTTP_REQUEST* request)
{
_nativeRequest = request;
_bufferAlignment = 0;
_permanentlyPinned = true;
}
internal SafeNativeOverlapped NativeOverlapped => _nativeOverlapped;
internal HttpApiTypes.HTTP_REQUEST* NativeRequest
{
get
{
Debug.Assert(_nativeRequest != null || _backingBuffer == null, "native request accessed after ReleasePins().");
return _nativeRequest;
}
}
internal HttpApiTypes.HTTP_REQUEST_V2* NativeRequestV2
{
get
{
Debug.Assert(_nativeRequest != null || _backingBuffer == null, "native request accessed after ReleasePins().");
return (HttpApiTypes.HTTP_REQUEST_V2*)_nativeRequest;
}
}
internal ulong RequestId
{
get { return NativeRequest->RequestId; }
set { NativeRequest->RequestId = value; }
}
internal ulong ConnectionId => NativeRequest->ConnectionId;
internal HttpApiTypes.HTTP_VERB VerbId => NativeRequest->Verb;
internal ulong UrlContext => NativeRequest->UrlContext;
internal ushort UnknownHeaderCount => NativeRequest->Headers.UnknownHeaderCount;
internal SslStatus SslStatus
{
get
{
return NativeRequest->pSslInfo == null ? SslStatus.Insecure :
NativeRequest->pSslInfo->SslClientCertNegotiated == 0 ? SslStatus.NoClientCert :
SslStatus.ClientCert;
}
}
internal uint Size
{
get { return (uint)_backingBuffer.Length - AlignmentPadding; }
}
// ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called
// before an object (Request) which closes the RequestContext on demand is returned to the application.
internal void ReleasePins()
{
Debug.Assert(_nativeRequest != null || _backingBuffer == null, "RequestContextBase::ReleasePins()|ReleasePins() called twice.");
_originalBufferAddress = (IntPtr)_nativeRequest;
_nativeRequest = null;
_nativeOverlapped?.Dispose();
_nativeOverlapped = null;
}
public virtual void Dispose()
{
Debug.Assert(_nativeRequest == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins().");
_nativeOverlapped?.Dispose();
}
// These methods require the HTTP_REQUEST to still be pinned in its original location.
internal string GetVerb()
{
var verb = NativeRequest->Verb;
if (verb > HttpApiTypes.HTTP_VERB.HttpVerbUnknown && verb < HttpApiTypes.HTTP_VERB.HttpVerbMaximum)
{
return HttpApiTypes.HttpVerbs[(int)verb];
}
else if (verb == HttpApiTypes.HTTP_VERB.HttpVerbUnknown && NativeRequest->pUnknownVerb != null)
{
return HeaderEncoding.GetString(NativeRequest->pUnknownVerb, NativeRequest->UnknownVerbLength);
}
return null;
}
internal string GetRawUrl()
{
if (NativeRequest->pRawUrl != null && NativeRequest->RawUrlLength > 0)
{
return Marshal.PtrToStringAnsi((IntPtr)NativeRequest->pRawUrl, NativeRequest->RawUrlLength);
}
return null;
}
internal byte[] GetRawUrlInBytes()
{
if (NativeRequest->pRawUrl != null && NativeRequest->RawUrlLength > 0)
{
var result = new byte[NativeRequest->RawUrlLength];
Marshal.Copy((IntPtr)NativeRequest->pRawUrl, result, 0, NativeRequest->RawUrlLength);
return result;
}
return null;
}
internal CookedUrl GetCookedUrl()
{
return new CookedUrl(NativeRequest->CookedUrl);
}
internal Version GetVersion()
{
var major = NativeRequest->Version.MajorVersion;
var minor = NativeRequest->Version.MinorVersion;
if (major == 1 && minor == 1)
{
return Constants.V1_1;
}
else if (major == 1 && minor == 0)
{
return Constants.V1_0;
}
return new Version(major, minor);
}
internal bool CheckAuthenticated()
{
var requestInfo = NativeRequestV2->pRequestInfo;
var infoCount = NativeRequestV2->RequestInfoCount;
for (int i = 0; i < infoCount; i++)
{
var info = &requestInfo[i];
if (info != null
&& info->InfoType == HttpApiTypes.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth
&& info->pInfo->AuthStatus == HttpApiTypes.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
{
return true;
}
}
return false;
}
internal WindowsPrincipal GetUser()
{
var requestInfo = NativeRequestV2->pRequestInfo;
var infoCount = NativeRequestV2->RequestInfoCount;
for (int i = 0; i < infoCount; i++)
{
var info = &requestInfo[i];
if (info != null
&& info->InfoType == HttpApiTypes.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth
&& info->pInfo->AuthStatus == HttpApiTypes.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
{
// Duplicates AccessToken
var identity = new WindowsIdentity(info->pInfo->AccessToken, GetAuthTypeFromRequest(info->pInfo->AuthType));
// Close the original
UnsafeNclNativeMethods.SafeNetHandles.CloseHandle(info->pInfo->AccessToken);
return new WindowsPrincipal(identity);
}
}
return new WindowsPrincipal(WindowsIdentity.GetAnonymous()); // Anonymous / !IsAuthenticated
}
private static string GetAuthTypeFromRequest(HttpApiTypes.HTTP_REQUEST_AUTH_TYPE input)
{
switch (input)
{
case HttpApiTypes.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeBasic:
return "Basic";
case HttpApiTypes.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeNTLM:
return "NTLM";
// case HttpApi.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeDigest:
// return "Digest";
case HttpApiTypes.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeNegotiate:
return "Negotiate";
case HttpApiTypes.HTTP_REQUEST_AUTH_TYPE.HttpRequestAuthTypeKerberos:
return "Kerberos";
default:
throw new NotImplementedException(input.ToString());
}
}
// These methods are for accessing the request structure after it has been unpinned. They need to adjust addresses
// in case GC has moved the original object.
internal string GetKnownHeader(HttpSysRequestHeader header)
{
if (_permanentlyPinned)
{
return GetKnowHeaderHelper(header, 0, _nativeRequest);
}
else
{
fixed (byte* pMemoryBlob = _backingBuffer)
{
var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment);
long fixup = pMemoryBlob - (byte*)_originalBufferAddress;
return GetKnowHeaderHelper(header, fixup, request);
}
}
}
private string GetKnowHeaderHelper(HttpSysRequestHeader header, long fixup, HttpApiTypes.HTTP_REQUEST* request)
{
int headerIndex = (int)header;
string value = null;
HttpApiTypes.HTTP_KNOWN_HEADER* pKnownHeader = (&request->Headers.KnownHeaders) + headerIndex;
// For known headers, when header value is empty, RawValueLength will be 0 and
// pRawValue will point to empty string ("\0")
if (pKnownHeader->RawValueLength > 0)
{
value = HeaderEncoding.GetString(pKnownHeader->pRawValue + fixup, pKnownHeader->RawValueLength);
}
return value;
}
internal void GetUnknownHeaders(IDictionary<string, StringValues> unknownHeaders)
{
if (_permanentlyPinned)
{
GetUnknownHeadersHelper(unknownHeaders, 0, _nativeRequest);
}
else
{
// Return value.
fixed (byte* pMemoryBlob = _backingBuffer)
{
var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment);
long fixup = pMemoryBlob - (byte*)_originalBufferAddress;
GetUnknownHeadersHelper(unknownHeaders, fixup, request);
}
}
}
private void GetUnknownHeadersHelper(IDictionary<string, StringValues> unknownHeaders, long fixup, HttpApiTypes.HTTP_REQUEST* request)
{
int index;
// unknown headers
if (request->Headers.UnknownHeaderCount != 0)
{
var pUnknownHeader = (HttpApiTypes.HTTP_UNKNOWN_HEADER*)(fixup + (byte*)request->Headers.pUnknownHeaders);
for (index = 0; index < request->Headers.UnknownHeaderCount; index++)
{
// For unknown headers, when header value is empty, RawValueLength will be 0 and
// pRawValue will be null.
if (pUnknownHeader->pName != null && pUnknownHeader->NameLength > 0)
{
var headerName = HeaderEncoding.GetString(pUnknownHeader->pName + fixup, pUnknownHeader->NameLength);
string headerValue;
if (pUnknownHeader->pRawValue != null && pUnknownHeader->RawValueLength > 0)
{
headerValue = HeaderEncoding.GetString(pUnknownHeader->pRawValue + fixup, pUnknownHeader->RawValueLength);
}
else
{
headerValue = string.Empty;
}
// Note that Http.Sys currently collapses all headers of the same name to a single coma separated string,
// so we can just call Set.
unknownHeaders[headerName] = headerValue;
}
pUnknownHeader++;
}
}
}
internal SocketAddress GetRemoteEndPoint()
{
return GetEndPoint(localEndpoint: false);
}
internal SocketAddress GetLocalEndPoint()
{
return GetEndPoint(localEndpoint: true);
}
private SocketAddress GetEndPoint(bool localEndpoint)
{
if (_permanentlyPinned)
{
return GetEndPointHelper(localEndpoint, _nativeRequest, (byte *)0);
}
else
{
fixed (byte* pMemoryBlob = _backingBuffer)
{
var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment);
return GetEndPointHelper(localEndpoint, request, pMemoryBlob);
}
}
}
private SocketAddress GetEndPointHelper(bool localEndpoint, HttpApiTypes.HTTP_REQUEST* request, byte* pMemoryBlob)
{
var source = localEndpoint ? (byte*)request->Address.pLocalAddress : (byte*)request->Address.pRemoteAddress;
if (source == null)
{
return null;
}
var address = (IntPtr)(pMemoryBlob + _bufferAlignment - (byte*)_originalBufferAddress + source);
return CopyOutAddress(address);
}
private static SocketAddress CopyOutAddress(IntPtr address)
{
ushort addressFamily = *((ushort*)address);
if (addressFamily == (ushort)AddressFamily.InterNetwork)
{
var v4address = new SocketAddress(AddressFamily.InterNetwork, SocketAddress.IPv4AddressSize);
fixed (byte* pBuffer = v4address.Buffer)
{
for (int index = 2; index < SocketAddress.IPv4AddressSize; index++)
{
pBuffer[index] = ((byte*)address)[index];
}
}
return v4address;
}
if (addressFamily == (ushort)AddressFamily.InterNetworkV6)
{
var v6address = new SocketAddress(AddressFamily.InterNetworkV6, SocketAddress.IPv6AddressSize);
fixed (byte* pBuffer = v6address.Buffer)
{
for (int index = 2; index < SocketAddress.IPv6AddressSize; index++)
{
pBuffer[index] = ((byte*)address)[index];
}
}
return v6address;
}
return null;
}
internal uint GetChunks(ref int dataChunkIndex, ref uint dataChunkOffset, byte[] buffer, int offset, int size)
{
// Return value.
if (_permanentlyPinned)
{
return GetChunksHelper(ref dataChunkIndex, ref dataChunkOffset, buffer, offset, size, 0, _nativeRequest);
}
else
{
fixed (byte* pMemoryBlob = _backingBuffer)
{
var request = (HttpApiTypes.HTTP_REQUEST*)(pMemoryBlob + _bufferAlignment);
long fixup = pMemoryBlob - (byte*)_originalBufferAddress;
return GetChunksHelper(ref dataChunkIndex, ref dataChunkOffset, buffer, offset, size, fixup, request);
}
}
}
private uint GetChunksHelper(ref int dataChunkIndex, ref uint dataChunkOffset, byte[] buffer, int offset, int size, long fixup, HttpApiTypes.HTTP_REQUEST* request)
{
uint dataRead = 0;
if (request->EntityChunkCount > 0 && dataChunkIndex < request->EntityChunkCount && dataChunkIndex != -1)
{
var pDataChunk = (HttpApiTypes.HTTP_DATA_CHUNK*)(fixup + (byte*)&request->pEntityChunks[dataChunkIndex]);
fixed (byte* pReadBuffer = buffer)
{
byte* pTo = &pReadBuffer[offset];
while (dataChunkIndex < request->EntityChunkCount && dataRead < size)
{
if (dataChunkOffset >= pDataChunk->fromMemory.BufferLength)
{
dataChunkOffset = 0;
dataChunkIndex++;
pDataChunk++;
}
else
{
byte* pFrom = (byte*)pDataChunk->fromMemory.pBuffer + dataChunkOffset + fixup;
uint bytesToRead = pDataChunk->fromMemory.BufferLength - (uint)dataChunkOffset;
if (bytesToRead > (uint)size)
{
bytesToRead = (uint)size;
}
for (uint i = 0; i < bytesToRead; i++)
{
*(pTo++) = *(pFrom++);
}
dataRead += bytesToRead;
dataChunkOffset += bytesToRead;
}
}
}
}
// we're finished.
if (dataChunkIndex == request->EntityChunkCount)
{
dataChunkIndex = -1;
}
return dataRead;
}
}
}

View File

@ -0,0 +1,151 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class RawUrlHelper
{
private static readonly byte[] _forwardSlashPath = Encoding.ASCII.GetBytes("/");
/// <summary>
/// Find the segment of the URI byte array which represents the path.
/// </summary>
public static ArraySegment<byte> GetPath(byte[] raw)
{
// performance
var pathStartIndex = 0;
// Performance improvement: accept two cases upfront
//
// 1) Since nearly all strings are relative Uris, just look if the string starts with '/'.
// If so, we have a relative Uri and the path starts at position 0.
// (http.sys already trimmed leading whitespaces)
//
// 2) The URL is simply '*'
if (raw[0] != '/' && !(raw.Length == 1 && raw[0] == '*'))
{
// We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to
// use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the
// Uri starts with either http:// or https://.
var authorityStartIndex = FindHttpOrHttps(raw);
if (authorityStartIndex > 0)
{
// we have an absolute Uri. Find out where the authority ends and the path begins.
// Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616
// and http.sys behavior: If the Uri contains a query, there must be at least one '/'
// between the authority and the '?' character: It's safe to just look for the first
// '/' after the authority to determine the beginning of the path.
pathStartIndex = Find(raw, authorityStartIndex, '/');
if (pathStartIndex == -1)
{
// e.g. for request lines like: 'GET http://myserver' (no final '/')
// At this point we can return a path with a slash.
return new ArraySegment<byte>(_forwardSlashPath);
}
}
else
{
// RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority
// 'authority' can only be used with CONNECT which is never received by HttpListener.
// I.e. if we don't have an absolute path (must start with '/') and we don't have
// an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'.
throw new InvalidOperationException("Invalid URI format");
}
}
// Find end of path: The path is terminated by
// - the first '?' character
// - the first '#' character: This is never the case here, since http.sys won't accept
// Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris.
// - end of Uri string
var scan = pathStartIndex + 1;
while (scan < raw.Length && raw[scan] != '?')
{
scan++;
}
return new ArraySegment<byte>(raw, pathStartIndex, scan - pathStartIndex);
}
/// <summary>
/// Compare the beginning portion of the raw URL byte array to https:// and http://
/// </summary>
/// <param name="raw">The byte array represents the raw URI</param>
/// <returns>Length of the matched bytes, 0 if it is not matched.</returns>
private static int FindHttpOrHttps(byte[] raw)
{
if (raw.Length < 7)
{
return 0;
}
if (raw[0] != 'h' && raw[0] != 'H')
{
return 0;
}
if (raw[1] != 't' && raw[1] != 'T')
{
return 0;
}
if (raw[2] != 't' && raw[2] != 'T')
{
return 0;
}
if (raw[3] != 'p' && raw[3] != 'P')
{
return 0;
}
if (raw[4] == ':')
{
if (raw[5] != '/' || raw[6] != '/')
{
return 0;
}
else
{
return 7;
}
}
else if (raw[4] == 's' || raw[4] == 'S')
{
if (raw.Length < 8)
{
return 0;
}
if (raw[5] != ':' || raw[6] != '/' || raw[7] != '/')
{
return 0;
}
else
{
return 8;
}
}
else
{
return 0;
}
}
private static int Find(byte[] raw, int begin, char target)
{
for (var idx = begin; idx < raw.Length; ++idx)
{
if (raw[idx] == target)
{
return idx;
}
}
return -1;
}
}
}

View File

@ -0,0 +1,265 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal partial class RequestHeaders : IHeaderDictionary
{
private IDictionary<string, StringValues> _extra;
private NativeRequestContext _requestMemoryBlob;
private long? _contentLength;
private StringValues _contentLengthText;
internal RequestHeaders(NativeRequestContext requestMemoryBlob)
{
_requestMemoryBlob = requestMemoryBlob;
}
public bool IsReadOnly { get; internal set; }
private IDictionary<string, StringValues> Extra
{
get
{
if (_extra == null)
{
var newDict = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
GetUnknownHeaders(newDict);
Interlocked.CompareExchange(ref _extra, newDict, null);
}
return _extra;
}
}
StringValues IDictionary<string, StringValues>.this[string key]
{
get
{
StringValues value;
return PropertiesTryGetValue(key, out value) ? value : Extra[key];
}
set
{
ThrowIfReadOnly();
if (!PropertiesTrySetValue(key, value))
{
Extra[key] = value;
}
}
}
private string GetKnownHeader(HttpSysRequestHeader header)
{
return _requestMemoryBlob.GetKnownHeader(header);
}
private void GetUnknownHeaders(IDictionary<string, StringValues> extra)
{
_requestMemoryBlob.GetUnknownHeaders(extra);
}
void IDictionary<string, StringValues>.Add(string key, StringValues value)
{
if (!PropertiesTrySetValue(key, value))
{
Extra.Add(key, value);
}
}
public bool ContainsKey(string key)
{
return PropertiesContainsKey(key) || Extra.ContainsKey(key);
}
public ICollection<string> Keys
{
get { return PropertiesKeys().Concat(Extra.Keys).ToArray(); }
}
ICollection<StringValues> IDictionary<string, StringValues>.Values
{
get { return PropertiesValues().Concat(Extra.Values).ToArray(); }
}
public int Count
{
get { return PropertiesKeys().Count() + Extra.Count; }
}
public bool Remove(string key)
{
// Although this is a mutating operation, Extra is used instead of StrongExtra,
// because if a real dictionary has not been allocated the default behavior of the
// nil dictionary is perfectly fine.
return PropertiesTryRemove(key) || Extra.Remove(key);
}
public bool TryGetValue(string key, out StringValues value)
{
return PropertiesTryGetValue(key, out value) || Extra.TryGetValue(key, out value);
}
void ICollection<KeyValuePair<string, StringValues>>.Add(KeyValuePair<string, StringValues> item)
{
((IDictionary<string, object>)this).Add(item.Key, item.Value);
}
void ICollection<KeyValuePair<string, StringValues>>.Clear()
{
foreach (var key in PropertiesKeys())
{
PropertiesTryRemove(key);
}
Extra.Clear();
}
bool ICollection<KeyValuePair<string, StringValues>>.Contains(KeyValuePair<string, StringValues> item)
{
object value;
return ((IDictionary<string, object>)this).TryGetValue(item.Key, out value) && Object.Equals(value, item.Value);
}
void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{
PropertiesEnumerable().Concat(Extra).ToArray().CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly
{
get { return false; }
}
long? IHeaderDictionary.ContentLength
{
get
{
long value;
var rawValue = this[HttpKnownHeaderNames.ContentLength];
if (_contentLengthText.Equals(rawValue))
{
return _contentLength;
}
if (rawValue.Count == 1 &&
!string.IsNullOrWhiteSpace(rawValue[0]) &&
HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
{
_contentLengthText = rawValue;
_contentLength = value;
return value;
}
return null;
}
set
{
ThrowIfReadOnly();
if (value.HasValue)
{
if (value.Value < 0)
{
throw new ArgumentOutOfRangeException("value", value.Value, "Cannot be negative.");
}
_contentLengthText = HeaderUtilities.FormatNonNegativeInt64(value.Value);
this[HttpKnownHeaderNames.ContentLength] = _contentLengthText;
_contentLength = value;
}
else
{
Remove(HttpKnownHeaderNames.ContentLength);
_contentLengthText = StringValues.Empty;
_contentLength = null;
}
}
}
public StringValues this[string key]
{
get
{
StringValues values;
return TryGetValue(key, out values) ? values : StringValues.Empty;
}
set
{
if (StringValues.IsNullOrEmpty(value))
{
Remove(key);
}
else
{
Extra[key] = value;
}
}
}
StringValues IHeaderDictionary.this[string key]
{
get
{
if (PropertiesTryGetValue(key, out var value))
{
return value;
}
if (Extra.TryGetValue(key, out value))
{
return value;
}
return StringValues.Empty;
}
set
{
if (!PropertiesTrySetValue(key, value))
{
Extra[key] = value;
}
}
}
bool ICollection<KeyValuePair<string, StringValues>>.Remove(KeyValuePair<string, StringValues> item)
{
return ((IDictionary<string, StringValues>)this).Contains(item) &&
((IDictionary<string, StringValues>)this).Remove(item.Key);
}
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
{
return PropertiesEnumerable().Concat(Extra).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<string, StringValues>)this).GetEnumerator();
}
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("The response headers cannot be modified because the response has already started.");
}
}
public IEnumerable<string> GetValues(string key)
{
StringValues values;
if (TryGetValue(key, out values))
{
return HeaderParser.SplitValues(values);
}
return HeaderParser.Empty;
}
}
}

View File

@ -0,0 +1,345 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
// We don't use the cooked URL because http.sys unescapes all percent-encoded values. However,
// we also can't just use the raw Uri, since http.sys supports not only UTF-8, but also ANSI/DBCS and
// Unicode code points. System.Uri only supports UTF-8.
// The purpose of this class is to decode all UTF-8 percent encoded characters, with the
// exception of %2F ('/'), which is left encoded
internal static class RequestUriBuilder
{
private static readonly Encoding UTF8 = new UTF8Encoding(
encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true);
public static string DecodeAndUnescapePath(byte[] rawUrlBytes)
{
if (rawUrlBytes == null)
{
throw new ArgumentNullException(nameof(rawUrlBytes));
}
if (rawUrlBytes.Length == 0)
{
throw new ArgumentException("Length of the URL cannot be zero.", nameof(rawUrlBytes));
}
var rawPath = RawUrlHelper.GetPath(rawUrlBytes);
var unescapedPath = Unescape(rawPath);
return UTF8.GetString(unescapedPath.Array, unescapedPath.Offset, unescapedPath.Count);
}
/// <summary>
/// Unescape a given path string in place. The given path string may contain escaped char.
/// </summary>
/// <param name="rawPath">The raw path string to be unescaped</param>
/// <returns>The unescaped path string</returns>
private static ArraySegment<byte> Unescape(ArraySegment<byte> rawPath)
{
// the slot to read the input
var reader = rawPath.Offset;
// the slot to write the unescaped byte
var writer = rawPath.Offset;
// the end of the path
var end = rawPath.Offset + rawPath.Count;
// the byte array
var buffer = rawPath.Array;
while (true)
{
if (reader == end)
{
break;
}
if (rawPath.Array[reader] == '%')
{
var decodeReader = reader;
// If decoding process succeeds, the writer iterator will be moved
// to the next write-ready location. On the other hand if the scanned
// percent-encodings cannot be interpreted as sequence of UTF-8 octets,
// these bytes should be copied to output as is.
// The decodeReader iterator is always moved to the first byte not yet
// be scanned after the process. A failed decoding means the chars
// between the reader and decodeReader can be copied to output untouched.
if (!DecodeCore(ref decodeReader, ref writer, end, buffer))
{
Copy(reader, decodeReader, ref writer, buffer);
}
reader = decodeReader;
}
else
{
buffer[writer++] = buffer[reader++];
}
}
return new ArraySegment<byte>(buffer, rawPath.Offset, writer - rawPath.Offset);
}
/// <summary>
/// Unescape the percent-encodings
/// </summary>
/// <param name="reader">The iterator point to the first % char</param>
/// <param name="writer">The place to write to</param>
/// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param>
private static bool DecodeCore(ref int reader, ref int writer, int end, byte[] buffer)
{
// preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets,
// bytes from this till the last scanned one will be copied to the memory pointed by writer.
var byte1 = UnescapePercentEncoding(ref reader, end, buffer);
if (!byte1.HasValue)
{
return false;
}
if (byte1 == 0)
{
throw new InvalidOperationException("The path contains null characters.");
}
if (byte1 <= 0x7F)
{
// first byte < U+007f, it is a single byte ASCII
buffer[writer++] = (byte)byte1;
return true;
}
int byte2 = 0, byte3 = 0, byte4 = 0;
// anticipate more bytes
var currentDecodeBits = 0;
var byteCount = 1;
var expectValueMin = 0;
if ((byte1 & 0xE0) == 0xC0)
{
// 110x xxxx, expect one more byte
currentDecodeBits = byte1.Value & 0x1F;
byteCount = 2;
expectValueMin = 0x80;
}
else if ((byte1 & 0xF0) == 0xE0)
{
// 1110 xxxx, expect two more bytes
currentDecodeBits = byte1.Value & 0x0F;
byteCount = 3;
expectValueMin = 0x800;
}
else if ((byte1 & 0xF8) == 0xF0)
{
// 1111 0xxx, expect three more bytes
currentDecodeBits = byte1.Value & 0x07;
byteCount = 4;
expectValueMin = 0x10000;
}
else
{
// invalid first byte
return false;
}
var remainingBytes = byteCount - 1;
while (remainingBytes > 0)
{
// read following three chars
if (reader == buffer.Length)
{
return false;
}
var nextItr = reader;
var nextByte = UnescapePercentEncoding(ref nextItr, end, buffer);
if (!nextByte.HasValue)
{
return false;
}
if ((nextByte & 0xC0) != 0x80)
{
// the follow up byte is not in form of 10xx xxxx
return false;
}
currentDecodeBits = (currentDecodeBits << 6) | (nextByte.Value & 0x3F);
remainingBytes--;
if (remainingBytes == 1 && currentDecodeBits >= 0x360 && currentDecodeBits <= 0x37F)
{
// this is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that
// are not allowed in UTF-8;
return false;
}
if (remainingBytes == 2 && currentDecodeBits >= 0x110)
{
// this is going to be out of the upper Unicode bound 0x10FFFF.
return false;
}
reader = nextItr;
if (byteCount - remainingBytes == 2)
{
byte2 = nextByte.Value;
}
else if (byteCount - remainingBytes == 3)
{
byte3 = nextByte.Value;
}
else if (byteCount - remainingBytes == 4)
{
byte4 = nextByte.Value;
}
}
if (currentDecodeBits < expectValueMin)
{
// overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
return false;
}
// all bytes are verified, write to the output
if (byteCount > 0)
{
buffer[writer++] = (byte)byte1;
}
if (byteCount > 1)
{
buffer[writer++] = (byte)byte2;
}
if (byteCount > 2)
{
buffer[writer++] = (byte)byte3;
}
if (byteCount > 3)
{
buffer[writer++] = (byte)byte4;
}
return true;
}
private static void Copy(int begin, int end, ref int writer, byte[] buffer)
{
while (begin != end)
{
buffer[writer++] = buffer[begin++];
}
}
/// <summary>
/// Read the percent-encoding and try unescape it.
///
/// The operation first peek at the character the <paramref name="scan"/>
/// iterator points at. If it is % the <paramref name="scan"/> is then
/// moved on to scan the following to characters. If the two following
/// characters are hexadecimal literals they will be unescaped and the
/// value will be returned.
///
/// If the first character is not % the <paramref name="scan"/> iterator
/// will be removed beyond the location of % and -1 will be returned.
///
/// If the following two characters can't be successfully unescaped the
/// <paramref name="scan"/> iterator will be move behind the % and -1
/// will be returned.
/// </summary>
/// <param name="scan">The value to read</param>
/// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param>
/// <returns>The unescaped byte if success. Otherwise return -1.</returns>
private static int? UnescapePercentEncoding(ref int scan, int end, byte[] buffer)
{
if (buffer[scan++] != '%')
{
return -1;
}
var probe = scan;
var value1 = ReadHex(ref probe, end, buffer);
if (!value1.HasValue)
{
return null;
}
var value2 = ReadHex(ref probe, end, buffer);
if (!value2.HasValue)
{
return null;
}
if (SkipUnescape(value1.Value, value2.Value))
{
return null;
}
scan = probe;
return (value1.Value << 4) + value2.Value;
}
/// <summary>
/// Read the next char and convert it into hexadecimal value.
///
/// The <paramref name="scan"/> iterator will be moved to the next
/// byte no matter no matter whether the operation successes.
/// </summary>
/// <param name="scan">The value to read</param>
/// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param>
/// <returns>The hexadecimal value if successes, otherwise -1.</returns>
private static int? ReadHex(ref int scan, int end, byte[] buffer)
{
if (scan == end)
{
return null;
}
var value = buffer[scan++];
var isHead = (((value >= '0') && (value <= '9')) ||
((value >= 'A') && (value <= 'F')) ||
((value >= 'a') && (value <= 'f')));
if (!isHead)
{
return null;
}
if (value <= '9')
{
return value - '0';
}
else if (value <= 'F')
{
return (value - 'A') + 10;
}
else // a - f
{
return (value - 'a') + 10;
}
}
private static bool SkipUnescape(int value1, int value2)
{
// skip %2F - '/'
if (value1 == 2 && value2 == 15)
{
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal enum SslStatus : byte
{
Insecure,
NoClientCert,
ClientCert
}
}

View File

@ -0,0 +1,7 @@
<Project>
<Import Project="..\Directory.Build.props" />
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,260 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal unsafe class AsyncAcceptContext : IAsyncResult, IDisposable
{
internal static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(IOWaitCallback);
private TaskCompletionSource<RequestContext> _tcs;
private HttpSysListener _server;
private NativeRequestContext _nativeRequestContext;
private const int DefaultBufferSize = 4096;
private const int AlignmentPadding = 8;
internal AsyncAcceptContext(HttpSysListener server)
{
_server = server;
_tcs = new TaskCompletionSource<RequestContext>();
AllocateNativeRequest();
}
internal Task<RequestContext> Task
{
get
{
return _tcs.Task;
}
}
private TaskCompletionSource<RequestContext> Tcs
{
get
{
return _tcs;
}
}
internal HttpSysListener Server
{
get
{
return _server;
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirecting to callback")]
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposed by callback")]
private static void IOCompleted(AsyncAcceptContext asyncResult, uint errorCode, uint numBytes)
{
bool complete = false;
try
{
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
asyncResult.Tcs.TrySetException(new HttpSysException((int)errorCode));
complete = true;
}
else
{
HttpSysListener server = asyncResult.Server;
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// at this point we have received an unmanaged HTTP_REQUEST and memoryBlob
// points to it we need to hook up our authentication handling code here.
try
{
if (server.ValidateRequest(asyncResult._nativeRequestContext) && server.ValidateAuth(asyncResult._nativeRequestContext))
{
RequestContext requestContext = new RequestContext(server, asyncResult._nativeRequestContext);
asyncResult.Tcs.TrySetResult(requestContext);
complete = true;
}
}
catch (Exception)
{
server.SendError(asyncResult._nativeRequestContext.RequestId, StatusCodes.Status400BadRequest);
throw;
}
finally
{
// The request has been handed to the user, which means this code can't reuse the blob. Reset it here.
if (complete)
{
asyncResult._nativeRequestContext = null;
}
else
{
asyncResult.AllocateNativeRequest(size: asyncResult._nativeRequestContext.Size);
}
}
}
else
{
// (uint)backingBuffer.Length - AlignmentPadding
asyncResult.AllocateNativeRequest(numBytes, asyncResult._nativeRequestContext.RequestId);
}
// We need to issue a new request, either because auth failed, or because our buffer was too small the first time.
if (!complete)
{
uint statusCode = asyncResult.QueueBeginGetContext();
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// someother bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
asyncResult.Tcs.TrySetException(new HttpSysException((int)statusCode));
complete = true;
}
}
if (!complete)
{
return;
}
}
if (complete)
{
asyncResult.Dispose();
}
}
catch (Exception exception)
{
// Logged by caller
asyncResult.Tcs.TrySetException(exception);
asyncResult.Dispose();
}
}
private static unsafe void IOWaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
{
// take the ListenerAsyncResult object from the state
var asyncResult = (AsyncAcceptContext)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
IOCompleted(asyncResult, errorCode, numBytes);
}
internal uint QueueBeginGetContext()
{
uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
bool retry;
do
{
retry = false;
uint bytesTransferred = 0;
statusCode = HttpApi.HttpReceiveHttpRequest(
Server.RequestQueue.Handle,
_nativeRequestContext.RequestId,
(uint)HttpApiTypes.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
_nativeRequestContext.NativeRequest,
_nativeRequestContext.Size,
&bytesTransferred,
_nativeRequestContext.NativeOverlapped);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && _nativeRequestContext.RequestId != 0)
{
// we might get this if somebody stole our RequestId,
// set RequestId to 0 and start all over again with the buffer we just allocated
// BUGBUG: how can someone steal our request ID? seems really bad and in need of fix.
_nativeRequestContext.RequestId = 0;
retry = true;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
// the buffer was not big enough to fit the headers, we need
// to read the RequestId returned, allocate a new buffer of the required size
// (uint)backingBuffer.Length - AlignmentPadding
AllocateNativeRequest(bytesTransferred);
retry = true;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS
&& HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
IOCompleted(this, statusCode, bytesTransferred);
}
}
while (retry);
return statusCode;
}
internal void AllocateNativeRequest(uint? size = null, ulong requestId = 0)
{
_nativeRequestContext?.ReleasePins();
_nativeRequestContext?.Dispose();
//Debug.Assert(size != 0, "unexpected size");
// We can't reuse overlapped objects
uint newSize = size.HasValue ? size.Value : DefaultBufferSize;
var backingBuffer = new byte[newSize + AlignmentPadding];
var boundHandle = Server.RequestQueue.BoundHandle;
var nativeOverlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, backingBuffer));
var requestAddress = Marshal.UnsafeAddrOfPinnedArrayElement(backingBuffer, 0);
// TODO:
// Apparently the HttpReceiveHttpRequest memory alignment requirements for non - ARM processors
// are different than for ARM processors. We have seen 4 - byte - aligned buffers allocated on
// virtual x64/x86 machines which were accepted by HttpReceiveHttpRequest without errors. In
// these cases the buffer alignment may cause reading values at invalid offset. Setting buffer
// alignment to 0 for now.
//
// _bufferAlignment = (int)(requestAddress.ToInt64() & 0x07);
var bufferAlignment = 0;
var nativeRequest = (HttpApiTypes.HTTP_REQUEST*)(requestAddress + bufferAlignment);
// nativeRequest
_nativeRequestContext = new NativeRequestContext(nativeOverlapped, bufferAlignment, nativeRequest, backingBuffer, requestId);
}
public object AsyncState
{
get { return _tcs.Task.AsyncState; }
}
public WaitHandle AsyncWaitHandle
{
get { return ((IAsyncResult)_tcs.Task).AsyncWaitHandle; }
}
public bool CompletedSynchronously
{
get { return ((IAsyncResult)_tcs.Task).CompletedSynchronously; }
}
public bool IsCompleted
{
get { return _tcs.Task.IsCompleted; }
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_nativeRequestContext != null)
{
_nativeRequestContext.ReleasePins();
_nativeRequestContext.Dispose();
}
}
}
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class AuthenticationHandler : IAuthenticationHandler
{
private RequestContext _requestContext;
private AuthenticationScheme _scheme;
public Task<AuthenticateResult> AuthenticateAsync()
{
var identity = _requestContext.User?.Identity;
if (identity != null && identity.IsAuthenticated)
{
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(_requestContext.User, properties: null, authenticationScheme: _scheme.Name)));
}
return Task.FromResult(AuthenticateResult.NoResult());
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_requestContext.Response.StatusCode = 401;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
_requestContext.Response.StatusCode = 403;
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_scheme = scheme;
_requestContext = context.Features.Get<RequestContext>();
if (_requestContext == null)
{
throw new InvalidOperationException("No RequestContext found.");
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,137 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Principal;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// See the native HTTP_SERVER_AUTHENTICATION_INFO structure documentation for additional information.
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364638(v=vs.85).aspx
/// <summary>
/// Exposes the Http.Sys authentication configurations.
/// </summary>
public sealed class AuthenticationManager
{
private static readonly int AuthInfoSize =
Marshal.SizeOf<HttpApiTypes.HTTP_SERVER_AUTHENTICATION_INFO>();
private UrlGroup _urlGroup;
private AuthenticationSchemes _authSchemes;
private bool _allowAnonymous = true;
internal AuthenticationManager()
{
}
public AuthenticationSchemes Schemes
{
get { return _authSchemes; }
set
{
_authSchemes = value;
SetUrlGroupSecurity();
}
}
public bool AllowAnonymous
{
get { return _allowAnonymous; }
set { _allowAnonymous = value; }
}
internal void SetUrlGroupSecurity(UrlGroup urlGroup)
{
Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once.");
_urlGroup = urlGroup;
SetUrlGroupSecurity();
}
private unsafe void SetUrlGroupSecurity()
{
if (_urlGroup == null)
{
// Not started yet.
return;
}
HttpApiTypes.HTTP_SERVER_AUTHENTICATION_INFO authInfo =
new HttpApiTypes.HTTP_SERVER_AUTHENTICATION_INFO();
authInfo.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
var authSchemes = (HttpApiTypes.HTTP_AUTH_TYPES)_authSchemes;
if (authSchemes != HttpApiTypes.HTTP_AUTH_TYPES.NONE)
{
authInfo.AuthSchemes = authSchemes;
// TODO:
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
// Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING
// Mutual Auth - ReceiveMutualAuth
// Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS
// Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS
IntPtr infoptr = new IntPtr(&authInfo);
_urlGroup.SetProperty(
HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
infoptr, (uint)AuthInfoSize);
}
}
internal static IList<string> GenerateChallenges(AuthenticationSchemes authSchemes)
{
IList<string> challenges = new List<string>();
if (authSchemes == AuthenticationSchemes.None)
{
return challenges;
}
// Order by strength.
if ((authSchemes & AuthenticationSchemes.Kerberos) == AuthenticationSchemes.Kerberos)
{
challenges.Add("Kerberos");
}
if ((authSchemes & AuthenticationSchemes.Negotiate) == AuthenticationSchemes.Negotiate)
{
challenges.Add("Negotiate");
}
if ((authSchemes & AuthenticationSchemes.NTLM) == AuthenticationSchemes.NTLM)
{
challenges.Add("NTLM");
}
/*if ((_authSchemes & AuthenticationSchemes.Digest) == AuthenticationSchemes.Digest)
{
// TODO:
throw new NotImplementedException("Digest challenge generation has not been implemented.");
// challenges.Add("Digest");
}*/
if ((authSchemes & AuthenticationSchemes.Basic) == AuthenticationSchemes.Basic)
{
// TODO: Realm
challenges.Add("Basic");
}
return challenges;
}
internal void SetAuthenticationChallenge(RequestContext context)
{
IList<string> challenges = GenerateChallenges(context.Response.AuthenticationChallenges);
if (challenges.Count > 0)
{
context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate]
= StringValues.Concat(context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate], challenges.ToArray());
}
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// REVIEW: this appears to be very similar to System.Net.AuthenticationSchemes
[Flags]
public enum AuthenticationSchemes
{
None = 0x0,
Basic = 0x1,
// Digest = 0x2, // TODO: Verify this is no longer supported by Http.Sys
NTLM = 0x4,
Negotiate = 0x8,
Kerberos = 0x10
}
}

View File

@ -0,0 +1,594 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class FeatureContext :
IHttpRequestFeature,
IHttpConnectionFeature,
IHttpResponseFeature,
IHttpSendFileFeature,
ITlsConnectionFeature,
// ITlsTokenBindingFeature, TODO: https://github.com/aspnet/HttpSysServer/issues/231
IHttpBufferingFeature,
IHttpRequestLifetimeFeature,
IHttpAuthenticationFeature,
IHttpUpgradeFeature,
IHttpRequestIdentifierFeature,
IHttpMaxRequestBodySizeFeature,
IHttpBodyControlFeature
{
private RequestContext _requestContext;
private IFeatureCollection _features;
private bool _enableResponseCaching;
private Stream _requestBody;
private IHeaderDictionary _requestHeaders;
private string _scheme;
private string _httpMethod;
private string _httpProtocolVersion;
private string _query;
private string _pathBase;
private string _path;
private string _rawTarget;
private IPAddress _remoteIpAddress;
private IPAddress _localIpAddress;
private int _remotePort;
private int _localPort;
private string _connectionId;
private string _traceIdentitfier;
private X509Certificate2 _clientCert;
private ClaimsPrincipal _user;
private CancellationToken _disconnectToken;
private Stream _responseStream;
private IHeaderDictionary _responseHeaders;
private Fields _initializedFields;
private List<Tuple<Func<object, Task>, object>> _onStartingActions = new List<Tuple<Func<object, Task>, object>>();
private List<Tuple<Func<object, Task>, object>> _onCompletedActions = new List<Tuple<Func<object, Task>, object>>();
private bool _responseStarted;
private bool _completed;
internal FeatureContext(RequestContext requestContext)
{
_requestContext = requestContext;
_features = new FeatureCollection(new StandardFeatureCollection(this));
_enableResponseCaching = _requestContext.Server.Options.EnableResponseCaching;
// Pre-initialize any fields that are not lazy at the lower level.
_requestHeaders = Request.Headers;
_httpMethod = Request.Method;
_path = Request.Path;
_pathBase = Request.PathBase;
_query = Request.QueryString;
_rawTarget = Request.RawUrl;
_scheme = Request.Scheme;
_user = _requestContext.User;
_responseStream = new ResponseStream(requestContext.Response.Body, OnResponseStart);
_responseHeaders = Response.Headers;
}
internal IFeatureCollection Features => _features;
internal object RequestContext => _requestContext;
private Request Request => _requestContext.Request;
private Response Response => _requestContext.Response;
[Flags]
// Fields that may be lazy-initialized
private enum Fields
{
None = 0x0,
Protocol = 0x1,
RequestBody = 0x2,
RequestAborted = 0x4,
LocalIpAddress = 0x8,
RemoteIpAddress = 0x10,
LocalPort = 0x20,
RemotePort = 0x40,
ConnectionId = 0x80,
ClientCertificate = 0x100,
TraceIdentifier = 0x200,
}
private bool IsNotInitialized(Fields field)
{
return (_initializedFields & field) != field;
}
private void SetInitialized(Fields field)
{
_initializedFields |= field;
}
Stream IHttpRequestFeature.Body
{
get
{
if (IsNotInitialized(Fields.RequestBody))
{
_requestBody = Request.Body;
SetInitialized(Fields.RequestBody);
}
return _requestBody;
}
set
{
_requestBody = value;
SetInitialized(Fields.RequestBody);
}
}
IHeaderDictionary IHttpRequestFeature.Headers
{
get { return _requestHeaders; }
set { _requestHeaders = value; }
}
string IHttpRequestFeature.Method
{
get { return _httpMethod; }
set { _httpMethod = value; }
}
string IHttpRequestFeature.Path
{
get { return _path; }
set { _path = value; }
}
string IHttpRequestFeature.PathBase
{
get { return _pathBase; }
set { _pathBase = value; }
}
string IHttpRequestFeature.Protocol
{
get
{
if (IsNotInitialized(Fields.Protocol))
{
var protocol = Request.ProtocolVersion;
if (protocol.Major == 1 && protocol.Minor == 1)
{
_httpProtocolVersion = "HTTP/1.1";
}
else if (protocol.Major == 1 && protocol.Minor == 0)
{
_httpProtocolVersion = "HTTP/1.0";
}
else
{
_httpProtocolVersion = "HTTP/" + protocol.ToString(2);
}
SetInitialized(Fields.Protocol);
}
return _httpProtocolVersion;
}
set
{
_httpProtocolVersion = value;
SetInitialized(Fields.Protocol);
}
}
string IHttpRequestFeature.QueryString
{
get { return _query; }
set { _query = value; }
}
string IHttpRequestFeature.RawTarget
{
get { return _rawTarget; }
set { _rawTarget = value; }
}
string IHttpRequestFeature.Scheme
{
get { return _scheme; }
set { _scheme = value; }
}
IPAddress IHttpConnectionFeature.LocalIpAddress
{
get
{
if (IsNotInitialized(Fields.LocalIpAddress))
{
_localIpAddress = Request.LocalIpAddress;
SetInitialized(Fields.LocalIpAddress);
}
return _localIpAddress;
}
set
{
_localIpAddress = value;
SetInitialized(Fields.LocalIpAddress);
}
}
IPAddress IHttpConnectionFeature.RemoteIpAddress
{
get
{
if (IsNotInitialized(Fields.RemoteIpAddress))
{
_remoteIpAddress = Request.RemoteIpAddress;
SetInitialized(Fields.RemoteIpAddress);
}
return _remoteIpAddress;
}
set
{
_remoteIpAddress = value;
SetInitialized(Fields.RemoteIpAddress);
}
}
int IHttpConnectionFeature.LocalPort
{
get
{
if (IsNotInitialized(Fields.LocalPort))
{
_localPort = Request.LocalPort;
SetInitialized(Fields.LocalPort);
}
return _localPort;
}
set
{
_localPort = value;
SetInitialized(Fields.LocalPort);
}
}
int IHttpConnectionFeature.RemotePort
{
get
{
if (IsNotInitialized(Fields.RemotePort))
{
_remotePort = Request.RemotePort;
SetInitialized(Fields.RemotePort);
}
return _remotePort;
}
set
{
_remotePort = value;
SetInitialized(Fields.RemotePort);
}
}
string IHttpConnectionFeature.ConnectionId
{
get
{
if (IsNotInitialized(Fields.ConnectionId))
{
_connectionId = Request.ConnectionId.ToString(CultureInfo.InvariantCulture);
SetInitialized(Fields.ConnectionId);
}
return _connectionId;
}
set
{
_connectionId = value;
SetInitialized(Fields.ConnectionId);
}
}
X509Certificate2 ITlsConnectionFeature.ClientCertificate
{
get
{
if (IsNotInitialized(Fields.ClientCertificate))
{
_clientCert = Request.GetClientCertificateAsync().Result; // TODO: Sync;
SetInitialized(Fields.ClientCertificate);
}
return _clientCert;
}
set
{
_clientCert = value;
SetInitialized(Fields.ClientCertificate);
}
}
async Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
{
if (IsNotInitialized(Fields.ClientCertificate))
{
_clientCert = await Request.GetClientCertificateAsync(cancellationToken);
SetInitialized(Fields.ClientCertificate);
}
return _clientCert;
}
internal ITlsConnectionFeature GetTlsConnectionFeature()
{
return Request.IsHttps ? this : null;
}
/* TODO: https://github.com/aspnet/HttpSysServer/issues/231
byte[] ITlsTokenBindingFeature.GetProvidedTokenBindingId() => Request.GetProvidedTokenBindingId();
byte[] ITlsTokenBindingFeature.GetReferredTokenBindingId() => Request.GetReferredTokenBindingId();
internal ITlsTokenBindingFeature GetTlsTokenBindingFeature()
{
return Request.IsHttps ? this : null;
}
*/
void IHttpBufferingFeature.DisableRequestBuffering()
{
// There is no request buffering.
}
void IHttpBufferingFeature.DisableResponseBuffering()
{
// TODO: What about native buffering?
}
Stream IHttpResponseFeature.Body
{
get { return _responseStream; }
set { _responseStream = value; }
}
IHeaderDictionary IHttpResponseFeature.Headers
{
get { return _responseHeaders; }
set { _responseHeaders = value; }
}
bool IHttpResponseFeature.HasStarted => Response.HasStarted;
void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
if (_onStartingActions == null)
{
throw new InvalidOperationException("Cannot register new callbacks, the response has already started.");
}
_onStartingActions.Add(new Tuple<Func<object, Task>, object>(callback, state));
}
void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
if (_onCompletedActions == null)
{
throw new InvalidOperationException("Cannot register new callbacks, the response has already completed.");
}
_onCompletedActions.Add(new Tuple<Func<object, Task>, object>(callback, state));
}
string IHttpResponseFeature.ReasonPhrase
{
get { return Response.ReasonPhrase; }
set { Response.ReasonPhrase = value; }
}
int IHttpResponseFeature.StatusCode
{
get { return Response.StatusCode; }
set { Response.StatusCode = value; }
}
async Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
{
await OnResponseStart();
await Response.SendFileAsync(path, offset, length, cancellation);
}
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
{
get
{
if (IsNotInitialized(Fields.RequestAborted))
{
_disconnectToken = _requestContext.DisconnectToken;
SetInitialized(Fields.RequestAborted);
}
return _disconnectToken;
}
set
{
_disconnectToken = value;
SetInitialized(Fields.RequestAborted);
}
}
void IHttpRequestLifetimeFeature.Abort() => _requestContext.Abort();
bool IHttpUpgradeFeature.IsUpgradableRequest => _requestContext.IsUpgradableRequest;
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
{
await OnResponseStart();
return await _requestContext.UpgradeAsync();
}
ClaimsPrincipal IHttpAuthenticationFeature.User
{
get { return _user; }
set { _user = value; }
}
IAuthenticationHandler IHttpAuthenticationFeature.Handler { get; set; }
string IHttpRequestIdentifierFeature.TraceIdentifier
{
get
{
if (IsNotInitialized(Fields.TraceIdentifier))
{
_traceIdentitfier = _requestContext.TraceIdentifier.ToString();
SetInitialized(Fields.TraceIdentifier);
}
return _traceIdentitfier;
}
set
{
_traceIdentitfier = value;
SetInitialized(Fields.TraceIdentifier);
}
}
bool IHttpBodyControlFeature.AllowSynchronousIO
{
get => _requestContext.AllowSynchronousIO;
set => _requestContext.AllowSynchronousIO = value;
}
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => Request.HasRequestBodyStarted;
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
{
get => Request.MaxRequestBodySize;
set => Request.MaxRequestBodySize = value;
}
internal async Task OnResponseStart()
{
if (_responseStarted)
{
return;
}
_responseStarted = true;
await NotifiyOnStartingAsync();
ConsiderEnablingResponseCache();
}
private async Task NotifiyOnStartingAsync()
{
var actions = _onStartingActions;
_onStartingActions = null;
if (actions == null)
{
return;
}
actions.Reverse();
// Execute last to first. This mimics a stack unwind.
foreach (var actionPair in actions)
{
await actionPair.Item1(actionPair.Item2);
}
}
private void ConsiderEnablingResponseCache()
{
if (_enableResponseCaching)
{
// We don't have to worry too much about what Http.Sys supports, caching is a best-effort feature.
// If there's something about the request or response that prevents it from caching then the response
// will complete normally without caching.
_requestContext.Response.CacheTtl = GetCacheTtl(_requestContext);
}
}
private static TimeSpan? GetCacheTtl(RequestContext requestContext)
{
var response = requestContext.Response;
// Only consider kernel-mode caching if the Cache-Control response header is present.
var cacheControlHeader = response.Headers[HeaderNames.CacheControl];
if (string.IsNullOrEmpty(cacheControlHeader))
{
return null;
}
// Before we check the header value, check for the existence of other headers which would
// make us *not* want to cache the response.
if (response.Headers.ContainsKey(HeaderNames.SetCookie)
|| response.Headers.ContainsKey(HeaderNames.Vary)
|| response.Headers.ContainsKey(HeaderNames.Pragma))
{
return null;
}
// We require 'public' and 's-max-age' or 'max-age' or the Expires header.
CacheControlHeaderValue cacheControl;
if (CacheControlHeaderValue.TryParse(cacheControlHeader.ToString(), out cacheControl) && cacheControl.Public)
{
if (cacheControl.SharedMaxAge.HasValue)
{
return cacheControl.SharedMaxAge;
}
else if (cacheControl.MaxAge.HasValue)
{
return cacheControl.MaxAge;
}
DateTimeOffset expirationDate;
if (HeaderUtilities.TryParseDate(response.Headers[HeaderNames.Expires].ToString(), out expirationDate))
{
var expiresOffset = expirationDate - DateTimeOffset.UtcNow;
if (expiresOffset > TimeSpan.Zero)
{
return expiresOffset;
}
}
}
return null;
}
internal Task OnCompleted()
{
if (_completed)
{
return Task.CompletedTask;
}
_completed = true;
return NotifyOnCompletedAsync();
}
private async Task NotifyOnCompletedAsync()
{
var actions = _onCompletedActions;
_onCompletedActions = null;
if (actions == null)
{
return;
}
actions.Reverse();
// Execute last to first. This mimics a stack unwind.
foreach (var actionPair in actions)
{
await actionPair.Item1(actionPair.Item2);
}
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class Helpers
{
internal static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
internal static readonly byte[] CRLF = new byte[] { (byte)'\r', (byte)'\n' };
internal static ConfiguredTaskAwaitable SupressContext(this Task task)
{
return task.ConfigureAwait(continueOnCapturedContext: false);
}
internal static ConfiguredTaskAwaitable<T> SupressContext<T>(this Task<T> task)
{
return task.ConfigureAwait(continueOnCapturedContext: false);
}
internal static IAsyncResult ToIAsyncResult(this Task task, AsyncCallback callback, object state)
{
var tcs = new TaskCompletionSource<int>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception.InnerExceptions);
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(0);
}
if (callback != null)
{
callback(tcs.Task);
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
return tcs.Task;
}
internal static ArraySegment<byte> GetChunkHeader(long size)
{
if (size < int.MaxValue)
{
return GetChunkHeader((int)size);
}
// Greater than 2gb, perf is no longer our concern
return new ArraySegment<byte>(Encoding.ASCII.GetBytes(size.ToString("X") + "\r\n"));
}
/// <summary>
/// A private utility routine to convert an integer to a chunk header,
/// which is an ASCII hex number followed by a CRLF.The header is returned
/// as a byte array.
/// Generates a right-aligned hex string and returns the start offset.
/// </summary>
/// <param name="size">Chunk size to be encoded</param>
/// <returns>A byte array with the header in int.</returns>
internal static ArraySegment<byte> GetChunkHeader(int size)
{
uint mask = 0xf0000000;
byte[] header = new byte[10];
int i;
int offset = -1;
// Loop through the size, looking at each nibble. If it's not 0
// convert it to hex. Save the index of the first non-zero
// byte.
for (i = 0; i < 8; i++, size <<= 4)
{
// offset == -1 means that we haven't found a non-zero nibble
// yet. If we haven't found one, and the current one is zero,
// don't do anything.
if (offset == -1)
{
if ((size & mask) == 0)
{
continue;
}
}
// Either we have a non-zero nibble or we're no longer skipping
// leading zeros. Convert this nibble to ASCII and save it.
uint temp = (uint)size >> 28;
if (temp < 10)
{
header[i] = (byte)(temp + '0');
}
else
{
header[i] = (byte)((temp - 10) + 'A');
}
// If we haven't found a non-zero nibble yet, we've found one
// now, so remember that.
if (offset == -1)
{
offset = i;
}
}
header[8] = (byte)'\r';
header[9] = (byte)'\n';
return new ArraySegment<byte>(header, offset, header.Length - offset);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Server.HttpSys
{
/// <summary>
/// Enum declaring the allowed values for the verbosity level when http.sys reject requests due to throttling.
/// </summary>
public enum Http503VerbosityLevel : long
{
/// <summary>
/// A 503 response is not sent; the connection is reset. This is the default HTTP Server API behavior.
/// </summary>
Basic = 0,
/// <summary>
/// The HTTP Server API sends a 503 response with a "Service Unavailable" reason phrase.
/// </summary>
Limited = 1,
/// <summary>
/// The HTTP Server API sends a 503 response with a detailed reason phrase.
/// </summary>
Full = 2
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Server.HttpSys
{
public static class HttpSysDefaults
{
/// <summary>
/// The name of the authentication scheme used.
/// </summary>
public static readonly string AuthenticationScheme = "Windows";
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.HttpSys
{
[SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]
public class HttpSysException : Win32Exception
{
internal HttpSysException()
: base(Marshal.GetLastWin32Error())
{
}
internal HttpSysException(int errorCode)
: base(errorCode)
{
}
internal HttpSysException(int errorCode, string message)
: base(errorCode, message)
{
}
// the base class returns the HResult with this property
// we need the Win32 Error Code, hence the override.
public override int ErrorCode
{
get
{
return NativeErrorCode;
}
}
}
}

View File

@ -0,0 +1,435 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
/// <summary>
/// An HTTP server wrapping the Http.Sys APIs that accepts requests.
/// </summary>
internal class HttpSysListener : IDisposable
{
// Win8# 559317 fixed a bug in Http.sys's HttpReceiveClientCertificate method.
// Without this fix IOCP callbacks were not being called although ERROR_IO_PENDING was
// returned from HttpReceiveClientCertificate when using the
// FileCompletionNotificationModes.SkipCompletionPortOnSuccess flag.
// This bug was only hit when the buffer passed into HttpReceiveClientCertificate
// (1500 bytes initially) is too small for the certificate.
// Due to this bug in downlevel operating systems the FileCompletionNotificationModes.SkipCompletionPortOnSuccess
// flag is only used on Win8 and later.
internal static readonly bool SkipIOCPCallbackOnSuccess = ComNetOS.IsWin8orLater;
// Mitigate potential DOS attacks by limiting the number of unknown headers we accept. Numerous header names
// with hash collisions will cause the server to consume excess CPU. 1000 headers limits CPU time to under
// 0.5 seconds per request. Respond with a 400 Bad Request.
private const int UnknownHeaderLimit = 1000;
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
private ServerSession _serverSession;
private UrlGroup _urlGroup;
private RequestQueue _requestQueue;
private DisconnectListener _disconnectListener;
private object _internalLock;
public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (!HttpApi.Supported)
{
throw new PlatformNotSupportedException();
}
Debug.Assert(HttpApi.ApiVersion == HttpApiTypes.HTTP_API_VERSION.Version20, "Invalid Http api version");
Options = options;
Logger = LogHelper.CreateLogger(loggerFactory, typeof(HttpSysListener));
_state = State.Stopped;
_internalLock = new object();
// V2 initialization sequence:
// 1. Create server session
// 2. Create url group
// 3. Create request queue - Done in Start()
// 4. Add urls to url group - Done in Start()
// 5. Attach request queue to url group - Done in Start()
try
{
_serverSession = new ServerSession();
_urlGroup = new UrlGroup(_serverSession, Logger);
_requestQueue = new RequestQueue(_urlGroup, Logger);
_disconnectListener = new DisconnectListener(_requestQueue, Logger);
}
catch (Exception exception)
{
// If Url group or request queue creation failed, close server session before throwing.
_requestQueue?.Dispose();
_urlGroup?.Dispose();
_serverSession?.Dispose();
LogHelper.LogException(Logger, ".Ctor", exception);
throw;
}
}
internal enum State
{
Stopped,
Started,
Disposed,
}
internal ILogger Logger { get; private set; }
internal UrlGroup UrlGroup
{
get { return _urlGroup; }
}
internal RequestQueue RequestQueue
{
get { return _requestQueue; }
}
internal DisconnectListener DisconnectListener
{
get { return _disconnectListener; }
}
public HttpSysOptions Options { get; }
public bool IsListening
{
get { return _state == State.Started; }
}
/// <summary>
/// Start accepting incoming requests.
/// </summary>
public void Start()
{
CheckDisposed();
LogHelper.LogInfo(Logger, "Start");
// Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose.
// Start needs to setup all resources. Abort/Stop must not interfere while Start is
// allocating those resources.
lock (_internalLock)
{
try
{
CheckDisposed();
if (_state == State.Started)
{
return;
}
Options.Apply(UrlGroup, RequestQueue);
_requestQueue.AttachToUrlGroup();
// All resources are set up correctly. Now add all prefixes.
try
{
Options.UrlPrefixes.RegisterAllPrefixes(UrlGroup);
}
catch (HttpSysException)
{
// If an error occurred while adding prefixes, free all resources allocated by previous steps.
_requestQueue.DetachFromUrlGroup();
throw;
}
_state = State.Started;
}
catch (Exception exception)
{
// Make sure the HttpListener instance can't be used if Start() failed.
_state = State.Disposed;
DisposeInternal();
LogHelper.LogException(Logger, "Start", exception);
throw;
}
}
}
private void Stop()
{
try
{
lock (_internalLock)
{
CheckDisposed();
if (_state == State.Stopped)
{
return;
}
Options.UrlPrefixes.UnregisterAllPrefixes();
_state = State.Stopped;
_requestQueue.DetachFromUrlGroup();
}
}
catch (Exception exception)
{
LogHelper.LogException(Logger, "Stop", exception);
throw;
}
}
/// <summary>
/// Stop the server and clean up.
/// </summary>
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
lock (_internalLock)
{
try
{
if (_state == State.Disposed)
{
return;
}
LogHelper.LogInfo(Logger, "Dispose");
Stop();
DisposeInternal();
}
catch (Exception exception)
{
LogHelper.LogException(Logger, "Dispose", exception);
throw;
}
finally
{
_state = State.Disposed;
}
}
}
private void DisposeInternal()
{
// V2 stopping sequence:
// 1. Detach request queue from url group - Done in Stop()/Abort()
// 2. Remove urls from url group - Done in Stop()
// 3. Close request queue - Done in Stop()/Abort()
// 4. Close Url group.
// 5. Close server session.
_requestQueue.Dispose();
_urlGroup.Dispose();
Debug.Assert(_serverSession != null, "ServerSessionHandle is null in CloseV2Config");
Debug.Assert(!_serverSession.Id.IsInvalid, "ServerSessionHandle is invalid in CloseV2Config");
_serverSession.Dispose();
}
/// <summary>
/// Accept a request from the incoming request queue.
/// </summary>
public Task<RequestContext> AcceptAsync()
{
AsyncAcceptContext asyncResult = null;
try
{
CheckDisposed();
Debug.Assert(_state != State.Stopped, "Listener has been stopped.");
// prepare the ListenerAsyncResult object (this will have it's own
// event that the user can wait on for IO completion - which means we
// need to signal it when IO completes)
asyncResult = new AsyncAcceptContext(this);
uint statusCode = asyncResult.QueueBeginGetContext();
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// some other bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
asyncResult.Dispose();
throw new HttpSysException((int)statusCode);
}
}
catch (Exception exception)
{
LogHelper.LogException(Logger, "GetContextAsync", exception);
throw;
}
return asyncResult.Task;
}
internal unsafe bool ValidateRequest(NativeRequestContext requestMemory)
{
// Block potential DOS attacks
if (requestMemory.UnknownHeaderCount > UnknownHeaderLimit)
{
SendError(requestMemory.RequestId, StatusCodes.Status400BadRequest, authChallenges: null);
return false;
}
return true;
}
internal unsafe bool ValidateAuth(NativeRequestContext requestMemory)
{
if (!Options.Authentication.AllowAnonymous && !requestMemory.CheckAuthenticated())
{
SendError(requestMemory.RequestId, StatusCodes.Status401Unauthorized,
AuthenticationManager.GenerateChallenges(Options.Authentication.Schemes));
return false;
}
return true;
}
internal unsafe void SendError(ulong requestId, int httpStatusCode, IList<string> authChallenges = null)
{
HttpApiTypes.HTTP_RESPONSE_V2 httpResponse = new HttpApiTypes.HTTP_RESPONSE_V2();
httpResponse.Response_V1.Version = new HttpApiTypes.HTTP_VERSION();
httpResponse.Response_V1.Version.MajorVersion = (ushort)1;
httpResponse.Response_V1.Version.MinorVersion = (ushort)1;
List<GCHandle> pinnedHeaders = null;
GCHandle gcHandle;
try
{
// Copied from the multi-value headers section of SerializeHeaders
if (authChallenges != null && authChallenges.Count > 0)
{
pinnedHeaders = new List<GCHandle>();
HttpApiTypes.HTTP_RESPONSE_INFO[] knownHeaderInfo = null;
knownHeaderInfo = new HttpApiTypes.HTTP_RESPONSE_INFO[1];
gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
httpResponse.pResponseInfo = (HttpApiTypes.HTTP_RESPONSE_INFO*)gcHandle.AddrOfPinnedObject();
knownHeaderInfo[httpResponse.ResponseInfoCount].Type = HttpApiTypes.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[httpResponse.ResponseInfoCount].Length =
(uint)Marshal.SizeOf<HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS>();
HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS header = new HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS();
header.HeaderId = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderWwwAuthenticate;
header.Flags = HttpApiTypes.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // The docs say this is for www-auth only.
HttpApiTypes.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApiTypes.HTTP_KNOWN_HEADER[authChallenges.Count];
gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
header.KnownHeaders = (HttpApiTypes.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
for (int headerValueIndex = 0; headerValueIndex < authChallenges.Count; headerValueIndex++)
{
// Add Value
string headerValue = authChallenges[headerValueIndex];
byte[] bytes = HeaderEncoding.GetBytes(headerValue);
nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
nativeHeaderValues[header.KnownHeaderCount].pRawValue = (byte*)gcHandle.AddrOfPinnedObject();
header.KnownHeaderCount++;
}
// This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set.
gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
knownHeaderInfo[0].pInfo = (HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS*)gcHandle.AddrOfPinnedObject();
httpResponse.ResponseInfoCount = 1;
}
httpResponse.Response_V1.StatusCode = (ushort)httpStatusCode;
string statusDescription = HttpReasonPhrase.Get(httpStatusCode);
uint dataWritten = 0;
uint statusCode;
byte[] byteReason = HeaderEncoding.GetBytes(statusDescription);
fixed (byte* pReason = byteReason)
{
httpResponse.Response_V1.pReason = (byte*)pReason;
httpResponse.Response_V1.ReasonLength = (ushort)byteReason.Length;
byte[] byteContentLength = new byte[] { (byte)'0' };
fixed (byte* pContentLength = byteContentLength)
{
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].pRawValue = (byte*)pContentLength;
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
httpResponse.Response_V1.Headers.UnknownHeaderCount = 0;
statusCode =
HttpApi.HttpSendHttpResponse(
_requestQueue.Handle,
requestId,
0,
&httpResponse,
null,
&dataWritten,
IntPtr.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// if we fail to send a 401 something's seriously wrong, abort the request
HttpApi.HttpCancelHttpRequest(_requestQueue.Handle, requestId, IntPtr.Zero);
}
}
finally
{
if (pinnedHeaders != null)
{
foreach (GCHandle handle in pinnedHeaders)
{
if (handle.IsAllocated)
{
handle.Free();
}
}
}
}
}
private void CheckDisposed()
{
if (_state == State.Disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
}
}

View File

@ -0,0 +1,199 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.HttpSys
{
public class HttpSysOptions
{
private const Http503VerbosityLevel DefaultRejectionVerbosityLevel = Http503VerbosityLevel.Basic; // Http.sys default.
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount;
// Matches the default maxAllowedContentLength in IIS (~28.6 MB)
// https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
private const long DefaultMaxRequestBodySize = 30000000;
private Http503VerbosityLevel _rejectionVebosityLevel = DefaultRejectionVerbosityLevel;
// The native request queue
private long _requestQueueLength = DefaultRequestQueueLength;
private long? _maxConnections;
private RequestQueue _requestQueue;
private UrlGroup _urlGroup;
private long? _maxRequestBodySize = DefaultMaxRequestBodySize;
public HttpSysOptions()
{
}
/// <summary>
/// The maximum number of concurrent accepts.
/// </summary>
public int MaxAccepts { get; set; } = DefaultMaxAccepts;
/// <summary>
/// Attempts kernel mode caching for responses with eligible headers. The response may not include
/// Set-Cookie, Vary, or Pragma headers. It must include a Cache-Control header with Public and
/// either a Shared-Max-Age or Max-Age value, or an Expires header.
/// </summary>
public bool EnableResponseCaching { get; set; } = true;
/// <summary>
/// The url prefixes to register with Http.Sys. These may be modified at any time prior to disposing
/// the listener.
/// </summary>
public UrlPrefixCollection UrlPrefixes { get; } = new UrlPrefixCollection();
/// <summary>
/// Http.Sys authentication settings. These may be modified at any time prior to disposing
/// the listener.
/// </summary>
public AuthenticationManager Authentication { get; } = new AuthenticationManager();
/// <summary>
/// Exposes the Http.Sys timeout configurations. These may also be configured in the registry.
/// These may be modified at any time prior to disposing the listener.
/// </summary>
public TimeoutManager Timeouts { get; } = new TimeoutManager();
/// <summary>
/// Gets or Sets if response body writes that fail due to client disconnects should throw exceptions or
/// complete normally. The default is false.
/// </summary>
public bool ThrowWriteExceptions { get; set; }
/// <summary>
/// Gets or sets the maximum number of concurrent connections to accept, -1 for infinite, or null to
/// use the machine wide setting from the registry. The default value is null.
/// </summary>
public long? MaxConnections
{
get => _maxConnections;
set
{
if (value.HasValue && value < -1)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "The value must be positive, or -1 for infiniate.");
}
if (value.HasValue && _urlGroup != null)
{
_urlGroup.SetMaxConnections(value.Value);
}
_maxConnections = value;
}
}
/// <summary>
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
/// </summary>
public long RequestQueueLimit
{
get
{
return _requestQueueLength;
}
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "The value must be greater than zero.");
}
if (_requestQueue != null)
{
_requestQueue.SetLengthLimit(_requestQueueLength);
}
// Only store it if it succeeds or hasn't started yet
_requestQueueLength = value;
}
}
/// <summary>
/// Gets or sets the maximum allowed size of any request body in bytes.
/// When set to null, the maximum request body size is unlimited.
/// This limit has no effect on upgraded connections which are always unlimited.
/// This can be overridden per-request via <see cref="IHttpMaxRequestBodySizeFeature"/>.
/// </summary>
/// <remarks>
/// Defaults to 30,000,000 bytes, which is approximately 28.6MB.
/// </remarks>
public long? MaxRequestBodySize
{
get => _maxRequestBodySize;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "The value must be greater or equal to zero.");
}
_maxRequestBodySize = value;
}
}
/// <summary>
/// Gets or sets a value that controls whether synchronous IO is allowed for the HttpContext.Request.Body and HttpContext.Response.Body.
/// The default is `true`.
/// </summary>
public bool AllowSynchronousIO { get; set; } = true;
/// <summary>
/// Gets or sets a value that controls how http.sys reacts when rejecting requests due to throttling conditions - like when the request
/// queue limit is reached. The default in http.sys is "Basic" which means http.sys is just resetting the TCP connection. IIS uses Limited
/// as its default behavior which will result in sending back a 503 - Service Unavailable back to the client.
/// </summary>
public Http503VerbosityLevel Http503Verbosity
{
get
{
return _rejectionVebosityLevel;
}
set
{
if (value < Http503VerbosityLevel.Basic || value > Http503VerbosityLevel.Full)
{
string message = String.Format(
CultureInfo.InvariantCulture,
"The value must be one of the values defined in the '{0}' enum.",
typeof(Http503VerbosityLevel).Name);
throw new ArgumentOutOfRangeException(nameof(value), value, message);
}
if (_requestQueue != null)
{
_requestQueue.SetRejectionVerbosity(value);
}
// Only store it if it succeeds or hasn't started yet
_rejectionVebosityLevel = value;
}
}
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
{
_urlGroup = urlGroup;
_requestQueue = requestQueue;
if (_maxConnections.HasValue)
{
_urlGroup.SetMaxConnections(_maxConnections.Value);
}
if (_requestQueueLength != DefaultRequestQueueLength)
{
_requestQueue.SetLengthLimit(_requestQueueLength);
}
if (_rejectionVebosityLevel != DefaultRejectionVerbosityLevel)
{
_requestQueue.SetRejectionVerbosity(_rejectionVebosityLevel);
}
Authentication.SetUrlGroupSecurity(urlGroup);
Timeouts.SetUrlGroupTimeouts(urlGroup);
}
}
}

View File

@ -0,0 +1,106 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class LogHelper
{
internal static ILogger CreateLogger(ILoggerFactory factory, Type type)
{
if (factory == null)
{
return null;
}
return factory.CreateLogger(type.FullName);
}
internal static void LogInfo(ILogger logger, string data)
{
if (logger == null)
{
Debug.WriteLine(data);
}
else
{
logger.LogInformation(data);
}
}
internal static void LogWarning(ILogger logger, string data)
{
if (logger == null)
{
Debug.WriteLine(data);
}
else
{
logger.LogWarning(data);
}
}
internal static void LogDebug(ILogger logger, string data)
{
if (logger == null)
{
Debug.WriteLine(data);
}
else
{
logger.LogDebug(data);
}
}
internal static void LogDebug(ILogger logger, string location, string data)
{
if (logger == null)
{
Debug.WriteLine(data);
}
else
{
logger.LogDebug(location + "; " + data);
}
}
internal static void LogDebug(ILogger logger, string location, Exception exception)
{
if (logger == null)
{
Console.WriteLine(location + Environment.NewLine + exception.ToString());
}
else
{
logger.LogDebug(0, exception, location);
}
}
internal static void LogException(ILogger logger, string location, Exception exception)
{
if (logger == null)
{
Debug.WriteLine(location + Environment.NewLine + exception.ToString());
}
else
{
logger.LogError(0, exception, location);
}
}
internal static void LogError(ILogger logger, string location, string message)
{
if (logger == null)
{
Debug.WriteLine(message);
}
else
{
logger.LogError(location + "; " + message);
}
}
}
}

View File

@ -0,0 +1,329 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.Contracts;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class MessagePump : IServer
{
private readonly ILogger _logger;
private readonly HttpSysOptions _options;
private IHttpApplication<object> _application;
private int _maxAccepts;
private int _acceptorCounts;
private Action<object> _processRequest;
private volatile int _stopping;
private int _outstandingRequests;
private readonly TaskCompletionSource<object> _shutdownSignal = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private int _shutdownSignalCompleted;
private readonly ServerAddressesFeature _serverAddresses;
public MessagePump(IOptions<HttpSysOptions> options, ILoggerFactory loggerFactory, IAuthenticationSchemeProvider authentication)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_options = options.Value;
Listener = new HttpSysListener(_options, loggerFactory);
_logger = LogHelper.CreateLogger(loggerFactory, typeof(MessagePump));
if (_options.Authentication.Schemes != AuthenticationSchemes.None)
{
authentication.AddScheme(new AuthenticationScheme(HttpSysDefaults.AuthenticationScheme, displayName: null, handlerType: typeof(AuthenticationHandler)));
}
Features = new FeatureCollection();
_serverAddresses = new ServerAddressesFeature();
Features.Set<IServerAddressesFeature>(_serverAddresses);
_processRequest = new Action<object>(ProcessRequestAsync);
_maxAccepts = _options.MaxAccepts;
}
internal HttpSysListener Listener { get; }
public IFeatureCollection Features { get; }
private bool Stopping => _stopping == 1;
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var hostingUrlsPresent = _serverAddresses.Addresses.Count > 0;
if (_serverAddresses.PreferHostingUrls && hostingUrlsPresent)
{
if (_options.UrlPrefixes.Count > 0)
{
LogHelper.LogWarning(_logger, $"Overriding endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} since {nameof(IServerAddressesFeature.PreferHostingUrls)} is set to true." +
$" Binding to address(es) '{string.Join(", ", _serverAddresses.Addresses)}' instead. ");
Listener.Options.UrlPrefixes.Clear();
}
foreach (var value in _serverAddresses.Addresses)
{
Listener.Options.UrlPrefixes.Add(value);
}
}
else if (_options.UrlPrefixes.Count > 0)
{
if (hostingUrlsPresent)
{
LogHelper.LogWarning(_logger, $"Overriding address(es) '{string.Join(", ", _serverAddresses.Addresses)}'. " +
$"Binding to endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} instead.");
_serverAddresses.Addresses.Clear();
}
foreach (var prefix in _options.UrlPrefixes)
{
_serverAddresses.Addresses.Add(prefix.FullPrefix);
}
}
else if (hostingUrlsPresent)
{
foreach (var value in _serverAddresses.Addresses)
{
Listener.Options.UrlPrefixes.Add(value);
}
}
else
{
LogHelper.LogDebug(_logger, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");
_serverAddresses.Addresses.Add(Constants.DefaultServerAddress);
Listener.Options.UrlPrefixes.Add(Constants.DefaultServerAddress);
}
// Can't call Start twice
Contract.Assert(_application == null);
Contract.Assert(application != null);
_application = new ApplicationWrapper<TContext>(application);
Listener.Start();
ActivateRequestProcessingLimits();
return Task.CompletedTask;
}
private void ActivateRequestProcessingLimits()
{
for (int i = _acceptorCounts; i < _maxAccepts; i++)
{
ProcessRequestsWorker();
}
}
// The message pump.
// When we start listening for the next request on one thread, we may need to be sure that the
// completion continues on another thread as to not block the current request processing.
// The awaits will manage stack depth for us.
private async void ProcessRequestsWorker()
{
int workerIndex = Interlocked.Increment(ref _acceptorCounts);
while (!Stopping && workerIndex <= _maxAccepts)
{
// Receive a request
RequestContext requestContext;
try
{
requestContext = await Listener.AcceptAsync().SupressContext();
}
catch (Exception exception)
{
Contract.Assert(Stopping);
if (Stopping)
{
LogHelper.LogDebug(_logger, "ListenForNextRequestAsync-Stopping", exception);
}
else
{
LogHelper.LogException(_logger, "ListenForNextRequestAsync", exception);
}
continue;
}
try
{
Task ignored = Task.Factory.StartNew(_processRequest, requestContext);
}
catch (Exception ex)
{
// Request processing failed to be queued in threadpool
// Log the error message, release throttle and move on
LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
}
}
Interlocked.Decrement(ref _acceptorCounts);
}
private async void ProcessRequestAsync(object requestContextObj)
{
var requestContext = requestContextObj as RequestContext;
try
{
if (Stopping)
{
SetFatalResponse(requestContext, 503);
return;
}
object context = null;
Interlocked.Increment(ref _outstandingRequests);
try
{
var featureContext = new FeatureContext(requestContext);
context = _application.CreateContext(featureContext.Features);
try
{
await _application.ProcessRequestAsync(context).SupressContext();
await featureContext.OnResponseStart();
}
finally
{
await featureContext.OnCompleted();
}
_application.DisposeContext(context, null);
requestContext.Dispose();
}
catch (Exception ex)
{
LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
_application.DisposeContext(context, ex);
if (requestContext.Response.HasStarted)
{
requestContext.Abort();
}
else
{
// We haven't sent a response yet, try to send a 500 Internal Server Error
requestContext.Response.Headers.Clear();
SetFatalResponse(requestContext, 500);
}
}
finally
{
if (Interlocked.Decrement(ref _outstandingRequests) == 0 && Stopping)
{
LogHelper.LogInfo(_logger, "All requests drained.");
_shutdownSignal.TrySetResult(0);
}
}
}
catch (Exception ex)
{
LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
requestContext.Abort();
}
}
private static void SetFatalResponse(RequestContext context, int status)
{
context.Response.StatusCode = status;
context.Response.ContentLength = 0;
context.Dispose();
}
public Task StopAsync(CancellationToken cancellationToken)
{
void RegisterCancelation()
{
cancellationToken.Register(() =>
{
if (Interlocked.Exchange(ref _shutdownSignalCompleted, 1) == 0)
{
LogHelper.LogInfo(_logger, "Canceled, terminating " + _outstandingRequests + " request(s).");
_shutdownSignal.TrySetResult(null);
}
});
}
if (Interlocked.Exchange(ref _stopping, 1) == 1)
{
RegisterCancelation();
return _shutdownSignal.Task;
}
try
{
// Wait for active requests to drain
if (_outstandingRequests > 0)
{
LogHelper.LogInfo(_logger, "Stopping, waiting for " + _outstandingRequests + " request(s) to drain.");
RegisterCancelation();
}
else
{
_shutdownSignal.TrySetResult(null);
}
}
catch (Exception ex)
{
_shutdownSignal.TrySetException(ex);
}
return _shutdownSignal.Task;
}
public void Dispose()
{
_stopping = 1;
_shutdownSignal.TrySetResult(null);
Listener.Dispose();
}
private class ApplicationWrapper<TContext> : IHttpApplication<object>
{
private readonly IHttpApplication<TContext> _application;
public ApplicationWrapper(IHttpApplication<TContext> application)
{
_application = application;
}
public object CreateContext(IFeatureCollection contextFeatures)
{
return _application.CreateContext(contextFeatures);
}
public void DisposeContext(object context, Exception exception)
{
_application.DisposeContext((TContext)context, exception);
}
public Task ProcessRequestAsync(object context)
{
return _application.ProcessRequestAsync((TContext)context);
}
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core HTTP server that uses the Windows HTTP Server API.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;weblistener;httpsys</PackageTags>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\shared\Microsoft.AspNetCore.HttpSys.Sources\**\*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Core" Version="$(MicrosoftAspNetCoreAuthenticationCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="$(MicrosoftNetHttpHeadersPackageVersion)" />
<PackageReference Include="Microsoft.Win32.Registry" Version="$(MicrosoftWin32RegistryPackageVersion)" />
<PackageReference Include="System.Security.Principal.Windows" Version="$(SystemSecurityPrincipalWindowsPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class ComNetOS
{
// Windows is assumed based on HttpApi.Supported which is checked in the HttpSysListener constructor.
// Minimum support for Windows 7 is assumed.
internal static readonly bool IsWin8orLater;
static ComNetOS()
{
var win8Version = new Version(6, 2);
IsWin8orLater = (Environment.OSVersion.Version >= win8Version);
}
}
}

View File

@ -0,0 +1,157 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class DisconnectListener
{
private readonly ConcurrentDictionary<ulong, ConnectionCancellation> _connectionCancellationTokens
= new ConcurrentDictionary<ulong, ConnectionCancellation>();
private readonly RequestQueue _requestQueue;
private readonly ILogger _logger;
internal DisconnectListener(RequestQueue requestQueue, ILogger logger)
{
_requestQueue = requestQueue;
_logger = logger;
}
internal CancellationToken GetTokenForConnection(ulong connectionId)
{
try
{
// Create exactly one CancellationToken per connection.
return GetOrCreateDisconnectToken(connectionId);
}
catch (Win32Exception exception)
{
LogHelper.LogException(_logger, "GetConnectionToken", exception);
return CancellationToken.None;
}
}
private CancellationToken GetOrCreateDisconnectToken(ulong connectionId)
{
// Read case is performance sensitive
ConnectionCancellation cancellation;
if (!_connectionCancellationTokens.TryGetValue(connectionId, out cancellation))
{
cancellation = GetCreatedConnectionCancellation(connectionId);
}
return cancellation.GetCancellationToken(connectionId);
}
private ConnectionCancellation GetCreatedConnectionCancellation(ulong connectionId)
{
// Race condition on creation has no side effects
var cancellation = new ConnectionCancellation(this);
return _connectionCancellationTokens.GetOrAdd(connectionId, cancellation);
}
private unsafe CancellationToken CreateDisconnectToken(ulong connectionId)
{
LogHelper.LogDebug(_logger, "CreateDisconnectToken", "Registering connection for disconnect for connection ID: " + connectionId);
// Create a nativeOverlapped callback so we can register for disconnect callback
var cts = new CancellationTokenSource();
var returnToken = cts.Token;
SafeNativeOverlapped nativeOverlapped = null;
var boundHandle = _requestQueue.BoundHandle;
nativeOverlapped = new SafeNativeOverlapped(boundHandle, boundHandle.AllocateNativeOverlapped(
(errorCode, numBytes, overlappedPtr) =>
{
LogHelper.LogDebug(_logger, "CreateDisconnectToken", "http.sys disconnect callback fired for connection ID: " + connectionId);
// Free the overlapped
nativeOverlapped.Dispose();
// Pull the token out of the list and Cancel it.
ConnectionCancellation token;
_connectionCancellationTokens.TryRemove(connectionId, out token);
try
{
cts.Cancel();
}
catch (AggregateException exception)
{
LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception);
}
},
null, null));
uint statusCode;
try
{
statusCode = HttpApi.HttpWaitForDisconnectEx(requestQueueHandle: _requestQueue.Handle,
connectionId: connectionId, reserved: 0, overlapped: nativeOverlapped);
}
catch (Win32Exception exception)
{
statusCode = (uint)exception.NativeErrorCode;
LogHelper.LogException(_logger, "CreateDisconnectToken", exception);
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// We got an unknown result, assume the connection has been closed.
nativeOverlapped.Dispose();
ConnectionCancellation ignored;
_connectionCancellationTokens.TryRemove(connectionId, out ignored);
LogHelper.LogDebug(_logger, "HttpWaitForDisconnectEx", new Win32Exception((int)statusCode));
cts.Cancel();
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion
nativeOverlapped.Dispose();
ConnectionCancellation ignored;
_connectionCancellationTokens.TryRemove(connectionId, out ignored);
cts.Cancel();
}
return returnToken;
}
private class ConnectionCancellation
{
private readonly DisconnectListener _parent;
private volatile bool _initialized; // Must be volatile because initialization is synchronized
private CancellationToken _cancellationToken;
public ConnectionCancellation(DisconnectListener parent)
{
_parent = parent;
}
internal CancellationToken GetCancellationToken(ulong connectionId)
{
// Initialized case is performance sensitive
if (_initialized)
{
return _cancellationToken;
}
return InitializeCancellationToken(connectionId);
}
private CancellationToken InitializeCancellationToken(ulong connectionId)
{
object syncObject = this;
#pragma warning disable 420 // Disable warning about volatile by reference since EnsureInitialized does volatile operations
return LazyInitializer.EnsureInitialized(ref _cancellationToken, ref _initialized, ref syncObject, () => _parent.CreateDisconnectToken(connectionId));
#pragma warning restore 420
}
}
}
}

View File

@ -0,0 +1,133 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.HttpSys.Internal;
using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static unsafe class HttpApi
{
private const string HTTPAPI = "httpapi.dll";
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpInitialize(HTTPAPI_VERSION version, uint flags, void* pReserved);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpReceiveRequestEntityBody(SafeHandle requestQueueHandle, ulong requestId, uint flags, IntPtr pEntityBuffer, uint entityBufferLength, out uint bytesReturned, SafeNativeOverlapped pOverlapped);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpReceiveClientCertificate(SafeHandle requestQueueHandle, ulong connectionId, uint flags, HTTP_SSL_CLIENT_CERT_INFO* pSslClientCertInfo, uint sslClientCertInfoSize, uint* pBytesReceived, SafeNativeOverlapped pOverlapped);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpReceiveClientCertificate(SafeHandle requestQueueHandle, ulong connectionId, uint flags, byte* pSslClientCertInfo, uint sslClientCertInfoSize, uint* pBytesReceived, SafeNativeOverlapped pOverlapped);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpReceiveHttpRequest(SafeHandle requestQueueHandle, ulong requestId, uint flags, HTTP_REQUEST* pRequestBuffer, uint requestBufferLength, uint* pBytesReturned, SafeNativeOverlapped pOverlapped);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpSendHttpResponse(SafeHandle requestQueueHandle, ulong requestId, uint flags, HTTP_RESPONSE_V2* pHttpResponse, HTTP_CACHE_POLICY* pCachePolicy, uint* pBytesSent, IntPtr pReserved1, uint Reserved2, SafeNativeOverlapped pOverlapped, IntPtr pLogData);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpSendResponseEntityBody(SafeHandle requestQueueHandle, ulong requestId, uint flags, ushort entityChunkCount, HTTP_DATA_CHUNK* pEntityChunks, uint* pBytesSent, IntPtr pReserved1, uint Reserved2, SafeNativeOverlapped pOverlapped, IntPtr pLogData);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpCancelHttpRequest(SafeHandle requestQueueHandle, ulong requestId, IntPtr pOverlapped);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpWaitForDisconnectEx(SafeHandle requestQueueHandle, ulong connectionId, uint reserved, SafeNativeOverlapped overlapped);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpCreateServerSession(HTTPAPI_VERSION version, ulong* serverSessionId, uint reserved);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpCreateUrlGroup(ulong serverSessionId, ulong* urlGroupId, uint reserved);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint HttpAddUrlToUrlGroup(ulong urlGroupId, string pFullyQualifiedUrl, ulong context, uint pReserved);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpSetUrlGroupProperty(ulong urlGroupId, HTTP_SERVER_PROPERTY serverProperty, IntPtr pPropertyInfo, uint propertyInfoLength);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint HttpRemoveUrlFromUrlGroup(ulong urlGroupId, string pFullyQualifiedUrl, uint flags);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpCloseServerSession(ulong serverSessionId);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpCloseUrlGroup(ulong urlGroupId);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern uint HttpSetRequestQueueProperty(SafeHandle requestQueueHandle, HTTP_SERVER_PROPERTY serverProperty, IntPtr pPropertyInfo, uint propertyInfoLength, uint reserved, IntPtr pReserved);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern unsafe uint HttpCreateRequestQueue(HTTPAPI_VERSION version, string pName,
UnsafeNclNativeMethods.SECURITY_ATTRIBUTES pSecurityAttributes, uint flags, out HttpRequestQueueV2Handle pReqQueueHandle);
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern unsafe uint HttpCloseRequestQueue(IntPtr pReqQueueHandle);
private static HTTPAPI_VERSION version;
// This property is used by HttpListener to pass the version structure to the native layer in API
// calls.
internal static HTTPAPI_VERSION Version
{
get
{
return version;
}
}
// This property is used by HttpListener to get the Api version in use so that it uses appropriate
// Http APIs.
internal static HTTP_API_VERSION ApiVersion
{
get
{
if (version.HttpApiMajorVersion == 2 && version.HttpApiMinorVersion == 0)
{
return HTTP_API_VERSION.Version20;
}
else if (version.HttpApiMajorVersion == 1 && version.HttpApiMinorVersion == 0)
{
return HTTP_API_VERSION.Version10;
}
else
{
return HTTP_API_VERSION.Invalid;
}
}
}
static HttpApi()
{
InitHttpApi(2, 0);
}
private static void InitHttpApi(ushort majorVersion, ushort minorVersion)
{
version.HttpApiMajorVersion = majorVersion;
version.HttpApiMinorVersion = minorVersion;
var statusCode = HttpInitialize(version, (uint)HTTP_FLAGS.HTTP_INITIALIZE_SERVER, null);
supported = statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
}
private static volatile bool supported;
internal static bool Supported
{
get
{
return supported;
}
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// This class is a wrapper for Http.sys V2 request queue handle.
internal sealed class HttpRequestQueueV2Handle : SafeHandleZeroOrMinusOneIsInvalid
{
private HttpRequestQueueV2Handle()
: base(true)
{
}
protected override bool ReleaseHandle()
{
return (HttpApi.HttpCloseRequestQueue(handle) ==
UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal sealed class HttpServerSessionHandle : CriticalHandleZeroOrMinusOneIsInvalid
{
private int disposed;
private ulong serverSessionId;
internal HttpServerSessionHandle(ulong id)
: base()
{
serverSessionId = id;
// This class uses no real handle so we need to set a dummy handle. Otherwise, IsInvalid always remains
// true.
SetHandle(new IntPtr(1));
}
internal ulong DangerousGetServerSessionId()
{
return serverSessionId;
}
protected override bool ReleaseHandle()
{
if (!IsInvalid)
{
if (Interlocked.Increment(ref disposed) == 1)
{
// Closing server session also closes all open url groups under that server session.
return (HttpApi.HttpCloseServerSession(serverSessionId) ==
UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
}
}
return true;
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security;
using Microsoft.Win32;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class HttpSysSettings
{
private const string HttpSysParametersKey = @"System\CurrentControlSet\Services\HTTP\Parameters";
private const bool EnableNonUtf8Default = true;
private const bool FavorUtf8Default = true;
private const string EnableNonUtf8Name = "EnableNonUtf8";
private const string FavorUtf8Name = "FavorUtf8";
private static volatile bool enableNonUtf8 = EnableNonUtf8Default;
private static volatile bool favorUtf8 = FavorUtf8Default;
static HttpSysSettings()
{
ReadHttpSysRegistrySettings();
}
internal static bool EnableNonUtf8
{
get { return enableNonUtf8; }
}
internal static bool FavorUtf8
{
get { return favorUtf8; }
}
private static void ReadHttpSysRegistrySettings()
{
try
{
RegistryKey httpSysParameters = Registry.LocalMachine.OpenSubKey(HttpSysParametersKey);
if (httpSysParameters == null)
{
LogWarning("ReadHttpSysRegistrySettings", "The Http.Sys registry key is null.",
HttpSysParametersKey);
}
else
{
using (httpSysParameters)
{
enableNonUtf8 = ReadRegistryValue(httpSysParameters, EnableNonUtf8Name, EnableNonUtf8Default);
favorUtf8 = ReadRegistryValue(httpSysParameters, FavorUtf8Name, FavorUtf8Default);
}
}
}
catch (SecurityException e)
{
LogRegistryException("ReadHttpSysRegistrySettings", e);
}
catch (ObjectDisposedException e)
{
LogRegistryException("ReadHttpSysRegistrySettings", e);
}
}
private static bool ReadRegistryValue(RegistryKey key, string valueName, bool defaultValue)
{
Debug.Assert(key != null, "'key' must not be null");
try
{
if (key.GetValue(valueName) != null && key.GetValueKind(valueName) == RegistryValueKind.DWord)
{
// At this point we know the Registry value exists and it must be valid (any DWORD value
// can be converted to a bool).
return Convert.ToBoolean(key.GetValue(valueName), CultureInfo.InvariantCulture);
}
}
catch (UnauthorizedAccessException e)
{
LogRegistryException("ReadRegistryValue", e);
}
catch (IOException e)
{
LogRegistryException("ReadRegistryValue", e);
}
catch (SecurityException e)
{
LogRegistryException("ReadRegistryValue", e);
}
catch (ObjectDisposedException e)
{
LogRegistryException("ReadRegistryValue", e);
}
return defaultValue;
}
private static void LogRegistryException(string methodName, Exception e)
{
LogWarning(methodName, "Unable to access the Http.Sys registry value.", HttpSysParametersKey, e);
}
private static void LogWarning(string methodName, string message, params object[] args)
{
// TODO: log
// Logging.PrintWarning(Logging.HttpListener, typeof(HttpSysSettings), methodName, SR.GetString(message, args));
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class IntPtrHelper
{
internal static IntPtr Add(IntPtr a, int b)
{
return (IntPtr)((long)a + (long)b);
}
internal static long Subtract(IntPtr a, IntPtr b)
{
return ((long)a - (long)b);
}
}
}

View File

@ -0,0 +1,137 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class RequestQueue
{
private static readonly int BindingInfoSize =
Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>();
private readonly UrlGroup _urlGroup;
private readonly ILogger _logger;
private bool _disposed;
internal RequestQueue(UrlGroup urlGroup, ILogger logger)
{
_urlGroup = urlGroup;
_logger = logger;
HttpRequestQueueV2Handle requestQueueHandle = null;
var statusCode = HttpApi.HttpCreateRequestQueue(
HttpApi.Version, null, null, 0, out requestQueueHandle);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new HttpSysException((int)statusCode);
}
// Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS)
if (HttpSysListener.SkipIOCPCallbackOnSuccess &&
!UnsafeNclNativeMethods.SetFileCompletionNotificationModes(
requestQueueHandle,
UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipCompletionPortOnSuccess |
UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipSetEventOnHandle))
{
throw new HttpSysException(Marshal.GetLastWin32Error());
}
Handle = requestQueueHandle;
BoundHandle = ThreadPoolBoundHandle.BindHandle(Handle);
}
internal SafeHandle Handle { get; }
internal ThreadPoolBoundHandle BoundHandle { get; }
internal unsafe void AttachToUrlGroup()
{
CheckDisposed();
// Set the association between request queue and url group. After this, requests for registered urls will
// get delivered to this request queue.
var info = new HttpApiTypes.HTTP_BINDING_INFO();
info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
info.RequestQueueHandle = Handle.DangerousGetHandle();
var infoptr = new IntPtr(&info);
_urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
infoptr, (uint)BindingInfoSize);
}
internal unsafe void DetachFromUrlGroup()
{
CheckDisposed();
// Break the association between request queue and url group. After this, requests for registered urls
// will get 503s.
// Note that this method may be called multiple times (Stop() and then Abort()). This
// is fine since http.sys allows to set HttpServerBindingProperty multiple times for valid
// Url groups.
var info = new HttpApiTypes.HTTP_BINDING_INFO();
info.Flags = HttpApiTypes.HTTP_FLAGS.NONE;
info.RequestQueueHandle = IntPtr.Zero;
var infoptr = new IntPtr(&info);
_urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
infoptr, (uint)BindingInfoSize, throwOnError: false);
}
// The listener must be active for this to work.
internal unsafe void SetLengthLimit(long length)
{
CheckDisposed();
var result = HttpApi.HttpSetRequestQueueProperty(Handle,
HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty,
new IntPtr((void*)&length), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero);
if (result != 0)
{
throw new HttpSysException((int)result);
}
}
// The listener must be active for this to work.
internal unsafe void SetRejectionVerbosity(Http503VerbosityLevel verbosity)
{
CheckDisposed();
var result = HttpApi.HttpSetRequestQueueProperty(Handle,
HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServer503VerbosityProperty,
new IntPtr((void*)&verbosity), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero);
if (result != 0)
{
throw new HttpSysException((int)result);
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
BoundHandle.Dispose();
Handle.Dispose();
}
private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class ServerSession : IDisposable
{
internal unsafe ServerSession()
{
ulong serverSessionId = 0;
var statusCode = HttpApi.HttpCreateServerSession(
HttpApi.Version, &serverSessionId, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new HttpSysException((int)statusCode);
}
Debug.Assert(serverSessionId != 0, "Invalid id returned by HttpCreateServerSession");
Id = new HttpServerSessionHandle(serverSessionId);
}
public HttpServerSessionHandle Id { get; private set; }
public void Dispose()
{
Id.Dispose();
}
}
}

View File

@ -0,0 +1,82 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.HttpSys.Internal;
using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes;
using static Microsoft.AspNetCore.HttpSys.Internal.UnsafeNclNativeMethods.TokenBinding;
namespace Microsoft.AspNetCore.Server.HttpSys
{
/// <summary>
/// Contains helpers for dealing with TLS token binding.
/// </summary>
// TODO: https://github.com/aspnet/HttpSysServer/issues/231
internal unsafe static class TokenBindingUtil
{
private static byte[] ExtractIdentifierBlob(TOKENBINDING_RESULT_DATA* pTokenBindingResultData)
{
// Per http://tools.ietf.org/html/draft-ietf-tokbind-protocol-00, Sec. 4,
// the identifier is a tuple which contains (token binding type, hash algorithm
// signature algorithm, key data). We'll strip off the token binding type and
// return the remainder (starting with the hash algorithm) as an opaque blob.
byte[] retVal = new byte[checked(pTokenBindingResultData->identifierSize - 1)];
Marshal.Copy((IntPtr)(&pTokenBindingResultData->identifierData->hashAlgorithm), retVal, 0, retVal.Length);
return retVal;
}
/// <summary>
/// Returns the 'provided' token binding identifier, optionally also returning the
/// 'referred' token binding identifier. Returns null on failure.
/// </summary>
public static byte[] GetProvidedTokenIdFromBindingInfo(HTTP_REQUEST_TOKEN_BINDING_INFO* pTokenBindingInfo, out byte[] referredId)
{
byte[] providedId = null;
referredId = null;
HeapAllocHandle handle = null;
int status = UnsafeNclNativeMethods.TokenBindingVerifyMessage(
pTokenBindingInfo->TokenBinding,
pTokenBindingInfo->TokenBindingSize,
pTokenBindingInfo->KeyType,
pTokenBindingInfo->TlsUnique,
pTokenBindingInfo->TlsUniqueSize,
out handle);
// No match found or there was an error?
if (status != 0 || handle == null || handle.IsInvalid)
{
return null;
}
using (handle)
{
// Find the first 'provided' and 'referred' types.
TOKENBINDING_RESULT_LIST* pResultList = (TOKENBINDING_RESULT_LIST*)handle.DangerousGetHandle();
for (int i = 0; i < pResultList->resultCount; i++)
{
TOKENBINDING_RESULT_DATA* pThisResultData = &pResultList->resultData[i];
if (pThisResultData->identifierData->bindingType == TOKENBINDING_TYPE.TOKENBINDING_TYPE_PROVIDED)
{
if (providedId != null)
{
return null; // It is invalid to have more than one 'provided' identifier.
}
providedId = ExtractIdentifierBlob(pThisResultData);
}
else if (pThisResultData->identifierData->bindingType == TOKENBINDING_TYPE.TOKENBINDING_TYPE_REFERRED)
{
if (referredId != null)
{
return null; // It is invalid to have more than one 'referred' identifier.
}
referredId = ExtractIdentifierBlob(pThisResultData);
}
}
}
return providedId;
}
}
}

View File

@ -0,0 +1,134 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class UrlGroup : IDisposable
{
private static readonly int QosInfoSize =
Marshal.SizeOf<HttpApiTypes.HTTP_QOS_SETTING_INFO>();
private ServerSession _serverSession;
private ILogger _logger;
private bool _disposed;
internal unsafe UrlGroup(ServerSession serverSession, ILogger logger)
{
_serverSession = serverSession;
_logger = logger;
ulong urlGroupId = 0;
var statusCode = HttpApi.HttpCreateUrlGroup(
_serverSession.Id.DangerousGetServerSessionId(), &urlGroupId, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new HttpSysException((int)statusCode);
}
Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup");
Id = urlGroupId;
}
internal ulong Id { get; private set; }
internal unsafe void SetMaxConnections(long maxConnections)
{
var connectionLimit = new HttpApiTypes.HTTP_CONNECTION_LIMIT_INFO();
connectionLimit.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
connectionLimit.MaxConnections = (uint)maxConnections;
var qosSettings = new HttpApiTypes.HTTP_QOS_SETTING_INFO();
qosSettings.QosType = HttpApiTypes.HTTP_QOS_SETTING_TYPE.HttpQosSettingTypeConnectionLimit;
qosSettings.QosSetting = new IntPtr(&connectionLimit);
SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize);
}
internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
{
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
CheckDisposed();
var statusCode = HttpApi.HttpSetUrlGroupProperty(Id, property, info, infosize);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
var exception = new HttpSysException((int)statusCode);
LogHelper.LogException(_logger, "SetUrlGroupProperty", exception);
if (throwOnError)
{
throw exception;
}
}
}
internal void RegisterPrefix(string uriPrefix, int contextId)
{
LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix);
CheckDisposed();
var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS)
{
throw new HttpSysException((int)statusCode, string.Format(Resources.Exception_PrefixAlreadyRegistered, uriPrefix));
}
else
{
throw new HttpSysException((int)statusCode);
}
}
}
internal bool UnregisterPrefix(string uriPrefix)
{
LogHelper.LogInfo(_logger, "Stop listening on prefix: " + uriPrefix);
CheckDisposed();
var statusCode = HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
return false;
}
return true;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
Debug.Assert(Id != 0, "HttpCloseUrlGroup called with invalid url group id");
uint statusCode = HttpApi.HttpCloseUrlGroup(Id);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
LogHelper.LogError(_logger, "CleanupV2Config", "Result: " + statusCode);
}
Id = 0;
}
private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
}
}

View File

@ -0,0 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.HttpSys.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,206 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Server.HttpSys
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Server.HttpSys.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The destination array is too small.
/// </summary>
internal static string Exception_ArrayTooSmall
{
get { return GetString("Exception_ArrayTooSmall"); }
}
/// <summary>
/// The destination array is too small.
/// </summary>
internal static string FormatException_ArrayTooSmall()
{
return GetString("Exception_ArrayTooSmall");
}
/// <summary>
/// End has already been called.
/// </summary>
internal static string Exception_EndCalledMultipleTimes
{
get { return GetString("Exception_EndCalledMultipleTimes"); }
}
/// <summary>
/// End has already been called.
/// </summary>
internal static string FormatException_EndCalledMultipleTimes()
{
return GetString("Exception_EndCalledMultipleTimes");
}
/// <summary>
/// The status code '{0}' is not supported.
/// </summary>
internal static string Exception_InvalidStatusCode
{
get { return GetString("Exception_InvalidStatusCode"); }
}
/// <summary>
/// The status code '{0}' is not supported.
/// </summary>
internal static string FormatException_InvalidStatusCode(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvalidStatusCode"), p0);
}
/// <summary>
/// The stream is not seekable.
/// </summary>
internal static string Exception_NoSeek
{
get { return GetString("Exception_NoSeek"); }
}
/// <summary>
/// The stream is not seekable.
/// </summary>
internal static string FormatException_NoSeek()
{
return GetString("Exception_NoSeek");
}
/// <summary>
/// The prefix '{0}' is already registered.
/// </summary>
internal static string Exception_PrefixAlreadyRegistered
{
get { return GetString("Exception_PrefixAlreadyRegistered"); }
}
/// <summary>
/// The prefix '{0}' is already registered.
/// </summary>
internal static string FormatException_PrefixAlreadyRegistered(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_PrefixAlreadyRegistered"), p0);
}
/// <summary>
/// This stream only supports read operations.
/// </summary>
internal static string Exception_ReadOnlyStream
{
get { return GetString("Exception_ReadOnlyStream"); }
}
/// <summary>
/// This stream only supports read operations.
/// </summary>
internal static string FormatException_ReadOnlyStream()
{
return GetString("Exception_ReadOnlyStream");
}
/// <summary>
/// More data written than specified in the Content-Length header.
/// </summary>
internal static string Exception_TooMuchWritten
{
get { return GetString("Exception_TooMuchWritten"); }
}
/// <summary>
/// More data written than specified in the Content-Length header.
/// </summary>
internal static string FormatException_TooMuchWritten()
{
return GetString("Exception_TooMuchWritten");
}
/// <summary>
/// Only the http and https schemes are supported.
/// </summary>
internal static string Exception_UnsupportedScheme
{
get { return GetString("Exception_UnsupportedScheme"); }
}
/// <summary>
/// Only the http and https schemes are supported.
/// </summary>
internal static string FormatException_UnsupportedScheme()
{
return GetString("Exception_UnsupportedScheme");
}
/// <summary>
/// This stream only supports write operations.
/// </summary>
internal static string Exception_WriteOnlyStream
{
get { return GetString("Exception_WriteOnlyStream"); }
}
/// <summary>
/// This stream only supports write operations.
/// </summary>
internal static string FormatException_WriteOnlyStream()
{
return GetString("Exception_WriteOnlyStream");
}
/// <summary>
/// The given IAsyncResult does not match this opperation.
/// </summary>
internal static string Exception_WrongIAsyncResult
{
get { return GetString("Exception_WrongIAsyncResult"); }
}
/// <summary>
/// The given IAsyncResult does not match this opperation.
/// </summary>
internal static string FormatException_WrongIAsyncResult()
{
return GetString("Exception_WrongIAsyncResult");
}
/// <summary>
/// An exception occured while running an action registered with {0}.
/// </summary>
internal static string Warning_ExceptionInOnResponseCompletedAction
{
get { return GetString("Warning_ExceptionInOnResponseCompletedAction"); }
}
/// <summary>
/// An exception occured while running an action registered with {0}.
/// </summary>
internal static string FormatWarning_ExceptionInOnResponseCompletedAction(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Warning_ExceptionInOnResponseCompletedAction"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal enum BoundaryType
{
None = 0,
Chunked = 1, // Transfer-Encoding: chunked
ContentLength = 2, // Content-Length: XXX
Close = 3, // Connection: close
PassThrough = 4, // The application is handling the boundary themselves (e.g. chunking themselves).
Invalid = 5,
}
}

View File

@ -0,0 +1,433 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// This class is used to load the client certificate on-demand. Because client certs are optional, all
// failures are handled internally and reported via ClientCertException or ClientCertError.
internal unsafe sealed class ClientCertLoader : IAsyncResult, IDisposable
{
private const uint CertBoblSize = 1500;
private static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(WaitCallback);
private static readonly int RequestChannelBindStatusSize =
Marshal.SizeOf<HttpApiTypes.HTTP_REQUEST_CHANNEL_BIND_STATUS>();
private SafeNativeOverlapped _overlapped;
private byte[] _backingBuffer;
private HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO* _memoryBlob;
private uint _size;
private TaskCompletionSource<object> _tcs;
private RequestContext _requestContext;
private int _clientCertError;
private X509Certificate2 _clientCert;
private Exception _clientCertException;
private CancellationTokenRegistration _cancellationRegistration;
internal ClientCertLoader(RequestContext requestContext, CancellationToken cancellationToken)
{
_requestContext = requestContext;
_tcs = new TaskCompletionSource<object>();
// we will use this overlapped structure to issue async IO to ul
// the event handle will be put in by the BeginHttpApi2.ERROR_SUCCESS() method
Reset(CertBoblSize);
if (cancellationToken.CanBeCanceled)
{
_cancellationRegistration = RequestContext.RegisterForCancellation(cancellationToken);
}
}
internal SafeHandle RequestQueueHandle => _requestContext.Server.RequestQueue.Handle;
internal X509Certificate2 ClientCert
{
get
{
Contract.Assert(Task.IsCompleted);
return _clientCert;
}
}
internal int ClientCertError
{
get
{
Contract.Assert(Task.IsCompleted);
return _clientCertError;
}
}
internal Exception ClientCertException
{
get
{
Contract.Assert(Task.IsCompleted);
return _clientCertException;
}
}
private RequestContext RequestContext
{
get
{
return _requestContext;
}
}
private Task Task
{
get
{
return _tcs.Task;
}
}
private SafeNativeOverlapped NativeOverlapped
{
get
{
return _overlapped;
}
}
private HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO* RequestBlob
{
get
{
return _memoryBlob;
}
}
private void Reset(uint size)
{
if (size == _size)
{
return;
}
if (_size != 0)
{
_overlapped.Dispose();
}
_size = size;
if (size == 0)
{
_overlapped = null;
_memoryBlob = null;
_backingBuffer = null;
return;
}
_backingBuffer = new byte[checked((int)size)];
var boundHandle = RequestContext.Server.RequestQueue.BoundHandle;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, _backingBuffer));
_memoryBlob = (HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO*)Marshal.UnsafeAddrOfPinnedArrayElement(_backingBuffer, 0);
}
// When you use netsh to configure HTTP.SYS with clientcertnegotiation = enable
// which means negotiate client certificates, when the client makes the
// initial SSL connection, the server (HTTP.SYS) requests the client certificate.
//
// Some apps may not want to negotiate the client cert at the beginning,
// perhaps serving the default.htm. In this case the HTTP.SYS is configured
// with clientcertnegotiation = disabled, which means that the client certificate is
// optional so initially when SSL is established HTTP.SYS won't ask for client
// certificate. This works fine for the default.htm in the case above,
// however, if the app wants to demand a client certificate at a later time
// perhaps showing "YOUR ORDERS" page, then the server wants to negotiate
// Client certs. This will in turn makes HTTP.SYS to do the
// SEC_I_RENOGOTIATE through which the client cert demand is made
//
// NOTE: When calling HttpReceiveClientCertificate you can get
// ERROR_NOT_FOUND - which means the client did not provide the cert
// If this is important, the server should respond with 403 forbidden
// HTTP.SYS will not do this for you automatically
internal Task LoadClientCertificateAsync()
{
uint size = CertBoblSize;
bool retry;
do
{
retry = false;
uint bytesReceived = 0;
uint statusCode =
HttpApi.HttpReceiveClientCertificate(
RequestQueueHandle,
RequestContext.Request.UConnectionId,
(uint)HttpApiTypes.HTTP_FLAGS.NONE,
RequestBlob,
size,
&bytesReceived,
NativeOverlapped);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO* pClientCertInfo = RequestBlob;
size = bytesReceived + pClientCertInfo->CertEncodedSize;
Reset(size);
retry = true;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
// The client did not send a cert.
Complete(0, null);
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
HttpSysListener.SkipIOCPCallbackOnSuccess)
{
IOCompleted(statusCode, bytesReceived);
}
else if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// Some other bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
// Also ERROR_BAD_DATA if we got it twice or it reported smaller size buffer required.
Fail(new HttpSysException((int)statusCode));
}
}
while (retry);
return Task;
}
private void Complete(int certErrors, X509Certificate2 cert)
{
// May be null
_clientCert = cert;
_clientCertError = certErrors;
Dispose();
_tcs.TrySetResult(null);
}
private void Fail(Exception ex)
{
// TODO: Log
_clientCertException = ex;
Dispose();
_tcs.TrySetResult(null);
}
private unsafe void IOCompleted(uint errorCode, uint numBytes)
{
IOCompleted(this, errorCode, numBytes);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirected to callback")]
private static unsafe void IOCompleted(ClientCertLoader asyncResult, uint errorCode, uint numBytes)
{
RequestContext requestContext = asyncResult.RequestContext;
try
{
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
// There is a bug that has existed in http.sys since w2k3. Bytesreceived will only
// return the size of the initial cert structure. To get the full size,
// we need to add the certificate encoding size as well.
HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO* pClientCertInfo = asyncResult.RequestBlob;
asyncResult.Reset(numBytes + pClientCertInfo->CertEncodedSize);
uint bytesReceived = 0;
errorCode =
HttpApi.HttpReceiveClientCertificate(
requestContext.Server.RequestQueue.Handle,
requestContext.Request.UConnectionId,
(uint)HttpApiTypes.HTTP_FLAGS.NONE,
asyncResult._memoryBlob,
asyncResult._size,
&bytesReceived,
asyncResult._overlapped);
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING ||
(errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && !HttpSysListener.SkipIOCPCallbackOnSuccess))
{
return;
}
}
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
// The client did not send a cert.
asyncResult.Complete(0, null);
}
else if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
asyncResult.Fail(new HttpSysException((int)errorCode));
}
else
{
HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO* pClientCertInfo = asyncResult._memoryBlob;
if (pClientCertInfo == null)
{
asyncResult.Complete(0, null);
}
else
{
if (pClientCertInfo->pCertEncoded != null)
{
try
{
byte[] certEncoded = new byte[pClientCertInfo->CertEncodedSize];
Marshal.Copy((IntPtr)pClientCertInfo->pCertEncoded, certEncoded, 0, certEncoded.Length);
asyncResult.Complete((int)pClientCertInfo->CertFlags, new X509Certificate2(certEncoded));
}
catch (CryptographicException exception)
{
// TODO: Log
asyncResult.Fail(exception);
}
catch (SecurityException exception)
{
// TODO: Log
asyncResult.Fail(exception);
}
}
}
}
}
catch (Exception exception)
{
asyncResult.Fail(exception);
}
}
private static unsafe void WaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
{
var asyncResult = (ClientCertLoader)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
IOCompleted(asyncResult, errorCode, numBytes);
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (disposing)
{
_cancellationRegistration.Dispose();
if (_overlapped != null)
{
_memoryBlob = null;
_overlapped.Dispose();
}
}
}
public object AsyncState
{
get { return _tcs.Task.AsyncState; }
}
public WaitHandle AsyncWaitHandle
{
get { return ((IAsyncResult)_tcs.Task).AsyncWaitHandle; }
}
public bool CompletedSynchronously
{
get { return ((IAsyncResult)_tcs.Task).CompletedSynchronously; }
}
public bool IsCompleted
{
get { return _tcs.Task.IsCompleted; }
}
internal static unsafe ChannelBinding GetChannelBindingFromTls(RequestQueue requestQueue, ulong connectionId, ILogger logger)
{
// +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT
// is >128 we will get ERROR_MORE_DATA and call again
int size = RequestChannelBindStatusSize + 128;
Debug.Assert(size >= 0);
byte[] blob = null;
SafeLocalFreeChannelBinding token = null;
uint bytesReceived = 0; ;
uint statusCode;
do
{
blob = new byte[size];
fixed (byte* blobPtr = blob)
{
// Http.sys team: ServiceName will always be null if
// HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set.
statusCode = HttpApi.HttpReceiveClientCertificate(
requestQueue.Handle,
connectionId,
(uint)HttpApiTypes.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN,
blobPtr,
(uint)size,
&bytesReceived,
SafeNativeOverlapped.Zero);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr);
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
token = SafeLocalFreeChannelBinding.LocalAlloc(tokenSize);
Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize);
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
size = RequestChannelBindStatusSize + tokenSize;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
LogHelper.LogError(logger, "GetChannelBindingFromTls", "Channel binding is not supported.");
return null; // old schannel library which doesn't support CBT
}
else
{
// It's up to the consumer to fail if the missing ChannelBinding matters to them.
LogHelper.LogException(logger, "GetChannelBindingFromTls", new HttpSysException((int)statusCode));
break;
}
}
}
while (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
return token;
}
private static int GetTokenOffsetFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
IntPtr tokenPointer = Marshal.ReadIntPtr(blob, (int)Marshal.OffsetOf<HttpApiTypes.HTTP_REQUEST_CHANNEL_BIND_STATUS>("ChannelToken"));
Debug.Assert(tokenPointer != IntPtr.Zero);
return (int)IntPtrHelper.Subtract(tokenPointer, blob);
}
private static int GetTokenSizeFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
return Marshal.ReadInt32(blob, (int)Marshal.OffsetOf<HttpApiTypes.HTTP_REQUEST_CHANNEL_BIND_STATUS>("ChannelTokenSize"));
}
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class HttpReasonPhrase
{
private static readonly string[][] HttpReasonPhrases = new string[][]
{
null,
new string[]
{
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
},
new string[]
{
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status"
},
new string[]
{
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ null,
/* 307 */ "Temporary Redirect"
},
new string[]
{
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Request Entity Too Large",
/* 414 */ "Request-Uri Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Requested Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ null,
/* 419 */ null,
/* 420 */ null,
/* 421 */ null,
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ null,
/* 426 */ "Upgrade Required", // RFC 2817
},
new string[]
{
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "Http Version Not Supported",
/* 506 */ null,
/* 507 */ "Insufficient Storage"
}
};
internal static string Get(int code)
{
if (code >= 100 && code < 600)
{
int i = code / 100;
int j = code % 100;
if (j < HttpReasonPhrases[i].Length)
{
return HttpReasonPhrases[i][j];
}
}
return null;
}
}
}

View File

@ -0,0 +1,165 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// A duplex wrapper around RequestStream and ResponseStream.
// TODO: Consider merging RequestStream and ResponseStream instead.
internal class OpaqueStream : Stream
{
private readonly Stream _requestStream;
private readonly Stream _responseStream;
internal OpaqueStream(Stream requestStream, Stream responseStream)
{
_requestStream = requestStream;
_responseStream = responseStream;
}
#region Properties
public override bool CanRead
{
get { return _requestStream.CanRead; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanTimeout
{
get { return _requestStream.CanTimeout || _responseStream.CanTimeout; }
}
public override bool CanWrite
{
get { return _responseStream.CanWrite; }
}
public override long Length
{
get { throw new NotSupportedException(Resources.Exception_NoSeek); }
}
public override long Position
{
get { throw new NotSupportedException(Resources.Exception_NoSeek); }
set { throw new NotSupportedException(Resources.Exception_NoSeek); }
}
public override int ReadTimeout
{
get { return _requestStream.ReadTimeout; }
set { _requestStream.ReadTimeout = value; }
}
public override int WriteTimeout
{
get { return _responseStream.WriteTimeout; }
set { _responseStream.WriteTimeout = value; }
}
#endregion Properties
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override void SetLength(long value)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
#region Read
public override int Read(byte[] buffer, int offset, int count)
{
return _requestStream.Read(buffer, offset, count);
}
public override int ReadByte()
{
return _requestStream.ReadByte();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _requestStream.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return _requestStream.EndRead(asyncResult);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _requestStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
return _requestStream.CopyToAsync(destination, bufferSize, cancellationToken);
}
#endregion Read
#region Write
public override void Write(byte[] buffer, int offset, int count)
{
_responseStream.Write(buffer, offset, count);
}
public override void WriteByte(byte value)
{
_responseStream.WriteByte(value);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _responseStream.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
_responseStream.EndWrite(asyncResult);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _responseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
public override void Flush()
{
_responseStream.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _responseStream.FlushAsync(cancellationToken);
}
#endregion Write
protected override void Dispose(bool disposing)
{
// TODO: Suppress dispose?
if (disposing)
{
_requestStream.Dispose();
_responseStream.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -0,0 +1,344 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal sealed class Request
{
private NativeRequestContext _nativeRequestContext;
private X509Certificate2 _clientCert;
// TODO: https://github.com/aspnet/HttpSysServer/issues/231
// private byte[] _providedTokenBindingId;
// private byte[] _referredTokenBindingId;
private BoundaryType _contentBoundaryType;
private long? _contentLength;
private RequestStream _nativeStream;
private AspNetCore.HttpSys.Internal.SocketAddress _localEndPoint;
private AspNetCore.HttpSys.Internal.SocketAddress _remoteEndPoint;
private bool _isDisposed = false;
internal Request(RequestContext requestContext, NativeRequestContext nativeRequestContext)
{
// TODO: Verbose log
RequestContext = requestContext;
_nativeRequestContext = nativeRequestContext;
_contentBoundaryType = BoundaryType.None;
RequestId = nativeRequestContext.RequestId;
UConnectionId = nativeRequestContext.ConnectionId;
SslStatus = nativeRequestContext.SslStatus;
KnownMethod = nativeRequestContext.VerbId;
Method = _nativeRequestContext.GetVerb();
RawUrl = nativeRequestContext.GetRawUrl();
var cookedUrl = nativeRequestContext.GetCookedUrl();
QueryString = cookedUrl.GetQueryString() ?? string.Empty;
var prefix = requestContext.Server.Options.UrlPrefixes.GetPrefix((int)nativeRequestContext.UrlContext);
var rawUrlInBytes = _nativeRequestContext.GetRawUrlInBytes();
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes);
// 'OPTIONS * HTTP/1.1'
if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawUrl, "*", StringComparison.Ordinal))
{
PathBase = string.Empty;
Path = string.Empty;
}
// These paths are both unescaped already.
else if (originalPath.Length == prefix.Path.Length - 1)
{
// They matched exactly except for the trailing slash.
PathBase = originalPath;
Path = string.Empty;
}
else
{
// url: /base/path, prefix: /base/, base: /base, path: /path
// url: /, prefix: /, base: , path: /
PathBase = originalPath.Substring(0, prefix.Path.Length - 1);
Path = originalPath.Substring(prefix.Path.Length - 1);
}
ProtocolVersion = _nativeRequestContext.GetVersion();
Headers = new RequestHeaders(_nativeRequestContext);
User = _nativeRequestContext.GetUser();
// GetTlsTokenBindingInfo(); TODO: https://github.com/aspnet/HttpSysServer/issues/231
// Finished directly accessing the HTTP_REQUEST structure.
_nativeRequestContext.ReleasePins();
// TODO: Verbose log parameters
}
internal ulong UConnectionId { get; }
// No ulongs in public APIs...
public long ConnectionId => (long)UConnectionId;
internal ulong RequestId { get; }
private SslStatus SslStatus { get; }
private RequestContext RequestContext { get; }
// With the leading ?, if any
public string QueryString { get; }
public long? ContentLength
{
get
{
if (_contentBoundaryType == BoundaryType.None)
{
string transferEncoding = Headers[HttpKnownHeaderNames.TransferEncoding];
if (string.Equals("chunked", transferEncoding?.Trim(), StringComparison.OrdinalIgnoreCase))
{
_contentBoundaryType = BoundaryType.Chunked;
}
else
{
string length = Headers[HttpKnownHeaderNames.ContentLength];
long value;
if (length != null && long.TryParse(length.Trim(), NumberStyles.None,
CultureInfo.InvariantCulture.NumberFormat, out value))
{
_contentBoundaryType = BoundaryType.ContentLength;
_contentLength = value;
}
else
{
_contentBoundaryType = BoundaryType.Invalid;
}
}
}
return _contentLength;
}
}
public RequestHeaders Headers { get; }
internal HttpApiTypes.HTTP_VERB KnownMethod { get; }
internal bool IsHeadMethod => KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbHEAD;
public string Method { get; }
public Stream Body => EnsureRequestStream() ?? Stream.Null;
private RequestStream EnsureRequestStream()
{
if (_nativeStream == null && HasEntityBody)
{
_nativeStream = new RequestStream(RequestContext);
}
return _nativeStream;
}
public bool HasRequestBodyStarted => _nativeStream?.HasStarted ?? false;
public long? MaxRequestBodySize
{
get => EnsureRequestStream()?.MaxSize;
set
{
EnsureRequestStream();
if (_nativeStream != null)
{
_nativeStream.MaxSize = value;
}
}
}
public string PathBase { get; }
public string Path { get; }
public bool IsHttps => SslStatus != SslStatus.Insecure;
public string RawUrl { get; }
public Version ProtocolVersion { get; }
public bool HasEntityBody
{
get
{
// accessing the ContentLength property delay creates _contentBoundaryType
return (ContentLength.HasValue && ContentLength.Value > 0 && _contentBoundaryType == BoundaryType.ContentLength)
|| _contentBoundaryType == BoundaryType.Chunked;
}
}
private AspNetCore.HttpSys.Internal.SocketAddress RemoteEndPoint
{
get
{
if (_remoteEndPoint == null)
{
_remoteEndPoint = _nativeRequestContext.GetRemoteEndPoint();
}
return _remoteEndPoint;
}
}
private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint
{
get
{
if (_localEndPoint == null)
{
_localEndPoint = _nativeRequestContext.GetLocalEndPoint();
}
return _localEndPoint;
}
}
// TODO: Lazy cache?
public IPAddress RemoteIpAddress => RemoteEndPoint.GetIPAddress();
public IPAddress LocalIpAddress => LocalEndPoint.GetIPAddress();
public int RemotePort => RemoteEndPoint.GetPort();
public int LocalPort => LocalEndPoint.GetPort();
public string Scheme => IsHttps ? Constants.HttpsScheme : Constants.HttpScheme;
// HTTP.Sys allows you to upgrade anything to opaque unless content-length > 0 or chunked are specified.
internal bool IsUpgradable => !HasEntityBody && ComNetOS.IsWin8orLater;
internal WindowsPrincipal User { get; }
// Populates the client certificate. The result may be null if there is no client cert.
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
// enable this, but it's unclear what Http.Sys would do.
public async Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken = default(CancellationToken))
{
if (SslStatus == SslStatus.Insecure)
{
// Non-SSL
return null;
}
// TODO: Verbose log
if (_clientCert != null)
{
return _clientCert;
}
cancellationToken.ThrowIfCancellationRequested();
var certLoader = new ClientCertLoader(RequestContext, cancellationToken);
try
{
await certLoader.LoadClientCertificateAsync().SupressContext();
// Populate the environment.
if (certLoader.ClientCert != null)
{
_clientCert = certLoader.ClientCert;
}
// TODO: Expose errors and exceptions?
}
catch (Exception)
{
if (certLoader != null)
{
certLoader.Dispose();
}
throw;
}
return _clientCert;
}
/* TODO: https://github.com/aspnet/WebListener/issues/231
private byte[] GetProvidedTokenBindingId()
{
return _providedTokenBindingId;
}
private byte[] GetReferredTokenBindingId()
{
return _referredTokenBindingId;
}
*/
// Only call from the constructor so we can directly access the native request blob.
// This requires Windows 10 and the following reg key:
// Set Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP\Parameters to Value: EnableSslTokenBinding = 1 [DWORD]
// Then for IE to work you need to set these:
// Key: HKLM\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING
// Value: "iexplore.exe"=dword:0x00000001
// Key: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING
// Value: "iexplore.exe"=dword:00000001
// TODO: https://github.com/aspnet/WebListener/issues/231
// TODO: https://github.com/aspnet/WebListener/issues/204 Move to NativeRequestContext
/*
private unsafe void GetTlsTokenBindingInfo()
{
var nativeRequest = (HttpApi.HTTP_REQUEST_V2*)_nativeRequestContext.RequestBlob;
for (int i = 0; i < nativeRequest->RequestInfoCount; i++)
{
var pThisInfo = &nativeRequest->pRequestInfo[i];
if (pThisInfo->InfoType == HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeSslTokenBinding)
{
var pTokenBindingInfo = (HttpApi.HTTP_REQUEST_TOKEN_BINDING_INFO*)pThisInfo->pInfo;
_providedTokenBindingId = TokenBindingUtil.GetProvidedTokenIdFromBindingInfo(pTokenBindingInfo, out _referredTokenBindingId);
}
}
}
*/
internal uint GetChunks(ref int dataChunkIndex, ref uint dataChunkOffset, byte[] buffer, int offset, int size)
{
return _nativeRequestContext.GetChunks(ref dataChunkIndex, ref dataChunkOffset, buffer, offset, size);
}
// should only be called from RequestContext
internal void Dispose()
{
// TODO: Verbose log
_isDisposed = true;
_nativeRequestContext.Dispose();
(User?.Identity as WindowsIdentity)?.Dispose();
if (_nativeStream != null)
{
_nativeStream.Dispose();
}
}
private void CheckDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
internal void SwitchToOpaqueMode()
{
if (_nativeStream == null)
{
_nativeStream = new RequestStream(RequestContext);
}
_nativeStream.SwitchToOpaqueMode();
}
}
}

View File

@ -0,0 +1,222 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal sealed class RequestContext : IDisposable
{
private static readonly Action<object> AbortDelegate = Abort;
private NativeRequestContext _memoryBlob;
private CancellationTokenSource _requestAbortSource;
private CancellationToken? _disconnectToken;
private bool _disposed;
internal RequestContext(HttpSysListener server, NativeRequestContext memoryBlob)
{
// TODO: Verbose log
Server = server;
_memoryBlob = memoryBlob;
Request = new Request(this, _memoryBlob);
Response = new Response(this);
AllowSynchronousIO = server.Options.AllowSynchronousIO;
}
internal HttpSysListener Server { get; }
internal ILogger Logger => Server.Logger;
public Request Request { get; }
public Response Response { get; }
public WindowsPrincipal User => Request.User;
public CancellationToken DisconnectToken
{
get
{
// Create a new token per request, but link it to a single connection token.
// We need to be able to dispose of the registrations each request to prevent leaks.
if (!_disconnectToken.HasValue)
{
if (_disposed || Response.BodyIsFinished)
{
// We cannot register for disconnect notifications after the response has finished sending.
_disconnectToken = CancellationToken.None;
}
else
{
var connectionDisconnectToken = Server.DisconnectListener.GetTokenForConnection(Request.UConnectionId);
if (connectionDisconnectToken.CanBeCanceled)
{
_requestAbortSource = CancellationTokenSource.CreateLinkedTokenSource(connectionDisconnectToken);
_disconnectToken = _requestAbortSource.Token;
}
else
{
_disconnectToken = CancellationToken.None;
}
}
}
return _disconnectToken.Value;
}
}
public unsafe Guid TraceIdentifier
{
get
{
// This is the base GUID used by HTTP.SYS for generating the activity ID.
// HTTP.SYS overwrites the first 8 bytes of the base GUID with RequestId to generate ETW activity ID.
var guid = new Guid(0xffcb4c93, 0xa57f, 0x453c, 0xb6, 0x3f, 0x84, 0x71, 0xc, 0x79, 0x67, 0xbb);
*((ulong*)&guid) = Request.RequestId;
return guid;
}
}
public bool IsUpgradableRequest => Request.IsUpgradable;
internal bool AllowSynchronousIO { get; set; }
public Task<Stream> UpgradeAsync()
{
if (!IsUpgradableRequest)
{
throw new InvalidOperationException("This request cannot be upgraded, it is incompatible.");
}
if (Response.HasStarted)
{
throw new InvalidOperationException("This request cannot be upgraded, the response has already started.");
}
// Set the status code and reason phrase
Response.StatusCode = StatusCodes.Status101SwitchingProtocols;
Response.ReasonPhrase = HttpReasonPhrase.Get(StatusCodes.Status101SwitchingProtocols);
Response.SendOpaqueUpgrade(); // TODO: Async
Request.SwitchToOpaqueMode();
Response.SwitchToOpaqueMode();
var opaqueStream = new OpaqueStream(Request.Body, Response.Body);
return Task.FromResult<Stream>(opaqueStream);
}
// TODO: Public when needed
internal bool TryGetChannelBinding(ref ChannelBinding value)
{
if (!Request.IsHttps)
{
LogHelper.LogDebug(Logger, "TryGetChannelBinding", "Channel binding requires HTTPS.");
return false;
}
value = ClientCertLoader.GetChannelBindingFromTls(Server.RequestQueue, Request.UConnectionId, Logger);
Debug.Assert(value != null, "GetChannelBindingFromTls returned null even though OS supposedly supports Extended Protection");
LogHelper.LogInfo(Logger, "Channel binding retrieved.");
return value != null;
}
/// <summary>
/// Flushes and completes the response.
/// </summary>
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
// TODO: Verbose log
try
{
_requestAbortSource?.Dispose();
Response.Dispose();
}
catch
{
Abort();
}
finally
{
Request.Dispose();
}
}
/// <summary>
/// Forcibly terminate and dispose the request, closing the connection if necessary.
/// </summary>
public void Abort()
{
// May be called from Dispose() code path, don't check _disposed.
// TODO: Verbose log
_disposed = true;
if (_requestAbortSource != null)
{
try
{
_requestAbortSource.Cancel();
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
LogHelper.LogDebug(Logger, "Abort", ex);
}
_requestAbortSource.Dispose();
}
ForceCancelRequest();
Request.Dispose();
// Only Abort, Response.Dispose() tries a graceful flush
Response.Abort();
}
private static void Abort(object state)
{
var context = (RequestContext)state;
context.Abort();
}
internal CancellationTokenRegistration RegisterForCancellation(CancellationToken cancellationToken)
{
return cancellationToken.Register(AbortDelegate, this);
}
// The request is being aborted, but large writes may be in progress. Cancel them.
internal void ForceCancelRequest()
{
try
{
var statusCode = HttpApi.HttpCancelHttpRequest(Server.RequestQueue.Handle,
Request.RequestId, IntPtr.Zero);
// Either the connection has already dropped, or the last write is in progress.
// The requestId becomes invalid as soon as the last Content-Length write starts.
// The only way to cancel now is with CancelIoEx.
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_CONNECTION_INVALID)
{
Response.CancelLastWrite();
}
}
catch (ObjectDisposedException)
{
// RequestQueueHandle may have been closed
}
}
}
}

View File

@ -0,0 +1,216 @@
<#@ template language="C#" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System.Linq" #>
<#
var props = new[]
{
new { Key = "Accept", Name = "Accept", ID = "HttpSysRequestHeader.Accept" },
new { Key = "Accept-Charset", Name = "AcceptCharset", ID = "HttpSysRequestHeader.AcceptCharset" },
new { Key = "Accept-Encoding", Name = "AcceptEncoding", ID = "HttpSysRequestHeader.AcceptEncoding" },
new { Key = "Accept-Language", Name = "AcceptLanguage", ID = "HttpSysRequestHeader.AcceptLanguage" },
new { Key = "Allow", Name = "Allow", ID = "HttpSysRequestHeader.Allow" },
new { Key = "Authorization", Name = "Authorization", ID = "HttpSysRequestHeader.Authorization" },
new { Key = "Cache-Control", Name = "CacheControl", ID = "HttpSysRequestHeader.CacheControl" },
new { Key = "Connection", Name = "Connection", ID = "HttpSysRequestHeader.Connection" },
new { Key = "Content-Encoding", Name = "ContentEncoding", ID = "HttpSysRequestHeader.ContentEncoding" },
new { Key = "Content-Language", Name = "ContentLanguage", ID = "HttpSysRequestHeader.ContentLanguage" },
new { Key = "Content-Length", Name = "ContentLength", ID = "HttpSysRequestHeader.ContentLength" },
new { Key = "Content-Location", Name = "ContentLocation", ID = "HttpSysRequestHeader.ContentLocation" },
new { Key = "Content-Md5", Name = "ContentMd5", ID = "HttpSysRequestHeader.ContentMd5" },
new { Key = "Content-Range", Name = "ContentRange", ID = "HttpSysRequestHeader.ContentRange" },
new { Key = "Content-Type", Name = "ContentType", ID = "HttpSysRequestHeader.ContentType" },
new { Key = "Cookie", Name = "Cookie", ID = "HttpSysRequestHeader.Cookie" },
new { Key = "Date", Name = "Date", ID = "HttpSysRequestHeader.Date" },
new { Key = "Expect", Name = "Expect", ID = "HttpSysRequestHeader.Expect" },
new { Key = "Expires", Name = "Expires", ID = "HttpSysRequestHeader.Expires" },
new { Key = "From", Name = "From", ID = "HttpSysRequestHeader.From" },
new { Key = "Host", Name = "Host", ID = "HttpSysRequestHeader.Host" },
new { Key = "If-Match", Name = "IfMatch", ID = "HttpSysRequestHeader.IfMatch" },
new { Key = "If-Modified-Since", Name = "IfModifiedSince", ID = "HttpSysRequestHeader.IfModifiedSince" },
new { Key = "If-None-Match", Name = "IfNoneMatch", ID = "HttpSysRequestHeader.IfNoneMatch" },
new { Key = "If-Range", Name = "IfRange", ID = "HttpSysRequestHeader.IfRange" },
new { Key = "If-Unmodified-Since", Name = "IfUnmodifiedSince", ID = "HttpSysRequestHeader.IfUnmodifiedSince" },
new { Key = "Keep-Alive", Name = "KeepAlive", ID = "HttpSysRequestHeader.KeepAlive" },
new { Key = "Last-Modified", Name = "LastModified", ID = "HttpSysRequestHeader.LastModified" },
new { Key = "Max-Forwards", Name = "MaxForwards", ID = "HttpSysRequestHeader.MaxForwards" },
new { Key = "Pragma", Name = "Pragma", ID = "HttpSysRequestHeader.Pragma" },
new { Key = "Proxy-Authorization", Name = "ProxyAuthorization", ID = "HttpSysRequestHeader.ProxyAuthorization" },
new { Key = "Range", Name = "Range", ID = "HttpSysRequestHeader.Range" },
new { Key = "Referer", Name = "Referer", ID = "HttpSysRequestHeader.Referer" },
new { Key = "Te", Name = "Te", ID = "HttpSysRequestHeader.Te" },
new { Key = "Trailer", Name = "Trailer", ID = "HttpSysRequestHeader.Trailer" },
new { Key = "Transfer-Encoding", Name = "TransferEncoding", ID = "HttpSysRequestHeader.TransferEncoding" },
new { Key = "Translate", Name = "Translate", ID = "HttpSysRequestHeader.Translate" },
new { Key = "Upgrade", Name = "Upgrade", ID = "HttpSysRequestHeader.Upgrade" },
new { Key = "User-Agent", Name = "UserAgent", ID = "HttpSysRequestHeader.UserAgent" },
new { Key = "Via", Name = "Via", ID = "HttpSysRequestHeader.Via" },
new { Key = "Warning", Name = "Warning", ID = "HttpSysRequestHeader.Warning" },
}.Select((prop, Index)=>new {prop.Key, prop.Name, prop.ID, Index});
var lengths = props.GroupBy(prop=>prop.Key.Length).OrderBy(prop=>prop.Key);
Func<int,string> IsRead = Index => "((_flag" + (Index / 32) + " & 0x" + (1<<(Index % 32)).ToString("x") + "u) != 0)";
Func<int,string> MarkRead = Index => "_flag" + (Index / 32) + " |= 0x" + (1<<(Index % 32)).ToString("x") + "u";
Func<int,string> Clear = Index => "_flag" + (Index / 32) + " &= ~0x" + (1<<(Index % 32)).ToString("x") + "u";
#>
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// <auto-generated />
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.HttpSys
{
[GeneratedCode("TextTemplatingFileGenerator", "")]
internal partial class RequestHeaders
{
// Tracks if individual fields have been read from native or set directly.
// Once read or set, their presence in the collection is marked by if their StringValues is null or not.
private UInt32 _flag0, _flag1;
<# foreach(var prop in props) { #>
private StringValues _<#=prop.Name#>;
<# } #>
<# foreach(var prop in props) { #>
internal StringValues <#=prop.Name#>
{
get
{
if (!<#=IsRead(prop.Index)#>)
{
string nativeValue = GetKnownHeader(<#=prop.ID#>);
if (nativeValue != null)
{
_<#=prop.Name#> = nativeValue;
}
<#=MarkRead(prop.Index)#>;
}
return _<#=prop.Name#>;
}
set
{
<#=MarkRead(prop.Index)#>;
_<#=prop.Name#> = value;
}
}
<# } #>
private bool PropertiesContainsKey(string key)
{
switch (key.Length)
{
<# foreach(var length in lengths) { #>
case <#=length.Key#>:
<# foreach(var prop in length) { #>
if (string.Equals(key, "<#=prop.Key#>", StringComparison.OrdinalIgnoreCase))
{
return <#=prop.Name#>.Count > 0;
}
<# } #>
break;
<# } #>
}
return false;
}
private bool PropertiesTryGetValue(string key, out StringValues value)
{
switch (key.Length)
{
<# foreach(var length in lengths) { #>
case <#=length.Key#>:
<# foreach(var prop in length) { #>
if (string.Equals(key, "<#=prop.Key#>", StringComparison.OrdinalIgnoreCase))
{
value = <#=prop.Name#>;
return value.Count > 0;
}
<# } #>
break;
<# } #>
}
value = StringValues.Empty;
return false;
}
private bool PropertiesTrySetValue(string key, StringValues value)
{
switch (key.Length)
{
<# foreach(var length in lengths) { #>
case <#=length.Key#>:
<# foreach(var prop in length) { #>
if (string.Equals(key, "<#=prop.Key#>", StringComparison.OrdinalIgnoreCase))
{
<#=MarkRead(prop.Index)#>;
<#=prop.Name#> = value;
return true;
}
<# } #>
break;
<# } #>
}
return false;
}
private bool PropertiesTryRemove(string key)
{
switch (key.Length)
{
<# foreach(var length in lengths) { #>
case <#=length.Key#>:
<# foreach(var prop in length) { #>
if (_<#=prop.Name#>.Count > 0
&& string.Equals(key, "<#=prop.Key#>", StringComparison.Ordinal))
{
bool wasSet = <#=IsRead(prop.Index)#>;
<#=prop.Name#> = StringValues.Empty;
return wasSet;
}
<# } #>
break;
<# } #>
}
return false;
}
private IEnumerable<string> PropertiesKeys()
{
<# foreach(var prop in props) { #>
if (<#=prop.Name#>.Count > 0)
{
yield return "<#=prop.Key#>";
}
<# } #>
}
private IEnumerable<StringValues> PropertiesValues()
{
<# foreach(var prop in props) { #>
if (<#=prop.Name#>.Count > 0)
{
yield return <#=prop.Name#>;
}
<# } #>
}
private IEnumerable<KeyValuePair<string, StringValues>> PropertiesEnumerable()
{
<# foreach(var prop in props) { #>
if (<#=prop.Name#>.Count > 0)
{
yield return new KeyValuePair<string, StringValues>("<#=prop.Key#>", <#=prop.Name#>);
}
<# } #>
}
}
}

View File

@ -0,0 +1,472 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class RequestStream : Stream
{
private const int MaxReadSize = 0x20000; // http.sys recommends we limit reads to 128k
private RequestContext _requestContext;
private uint _dataChunkOffset;
private int _dataChunkIndex;
private long? _maxSize;
private long _totalRead;
private bool _closed;
internal RequestStream(RequestContext httpContext)
{
_requestContext = httpContext;
_maxSize = _requestContext.Server.Options.MaxRequestBodySize;
}
internal RequestContext RequestContext
{
get { return _requestContext; }
}
private SafeHandle RequestQueueHandle => RequestContext.Server.RequestQueue.Handle;
private ulong RequestId => RequestContext.Request.RequestId;
private ILogger Logger => RequestContext.Server.Logger;
public bool HasStarted { get; private set; }
public long? MaxSize
{
get => _maxSize;
set
{
if (HasStarted)
{
throw new InvalidOperationException("The maximum request size cannot be changed after the request body has started reading.");
}
if (value.HasValue && value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "The value must be greater or equal to zero.");
}
_maxSize = value;
}
}
public override bool CanSeek => false;
public override bool CanWrite => false;
public override bool CanRead => true;
public override long Length => throw new NotSupportedException(Resources.Exception_NoSeek);
public override long Position
{
get => throw new NotSupportedException(Resources.Exception_NoSeek);
set => throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException(Resources.Exception_NoSeek);
public override void SetLength(long value) => throw new NotSupportedException(Resources.Exception_NoSeek);
public override void Flush() => throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
public override Task FlushAsync(CancellationToken cancellationToken)
=> throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
internal void SwitchToOpaqueMode()
{
HasStarted = true;
_maxSize = null;
}
internal void Abort()
{
_closed = true;
_requestContext.Abort();
}
private void ValidateReadBuffer(byte[] buffer, int offset, int size)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
}
if (size <= 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size", size, string.Empty);
}
}
public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
{
if (!RequestContext.AllowSynchronousIO)
{
throw new InvalidOperationException("Synchronous IO APIs are disabled, see AllowSynchronousIO.");
}
ValidateReadBuffer(buffer, offset, size);
CheckSizeLimit();
if (_closed)
{
return 0;
}
// TODO: Verbose log parameters
uint dataRead = 0;
if (_dataChunkIndex != -1)
{
dataRead = _requestContext.Request.GetChunks(ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size);
}
if (_dataChunkIndex == -1 && dataRead == 0)
{
uint statusCode = 0;
uint extraDataRead = 0;
// the http.sys team recommends that we limit the size to 128kb
if (size > MaxReadSize)
{
size = MaxReadSize;
}
fixed (byte* pBuffer = buffer)
{
// issue unmanaged blocking call
uint flags = 0;
statusCode =
HttpApi.HttpReceiveRequestEntityBody(
RequestQueueHandle,
RequestId,
flags,
(IntPtr)(pBuffer + offset),
(uint)size,
out extraDataRead,
SafeNativeOverlapped.Zero);
dataRead += extraDataRead;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
LogHelper.LogException(Logger, "Read", exception);
Abort();
throw exception;
}
UpdateAfterRead(statusCode, dataRead);
}
if (TryCheckSizeLimit((int)dataRead, out var ex))
{
throw ex;
}
// TODO: Verbose log dump data read
return (int)dataRead;
}
internal void UpdateAfterRead(uint statusCode, uint dataRead)
{
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF || dataRead == 0)
{
Dispose();
}
}
public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
ValidateReadBuffer(buffer, offset, size);
CheckSizeLimit();
if (_closed)
{
RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback);
result.Complete(0);
return result;
}
// TODO: Verbose log parameters
RequestStreamAsyncResult asyncResult = null;
uint dataRead = 0;
if (_dataChunkIndex != -1)
{
dataRead = _requestContext.Request.GetChunks(ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size);
if (dataRead > 0)
{
asyncResult = new RequestStreamAsyncResult(this, state, callback, buffer, offset, 0);
asyncResult.Complete((int)dataRead);
return asyncResult;
}
}
uint statusCode = 0;
// the http.sys team recommends that we limit the size to 128kb
if (size > MaxReadSize)
{
size = MaxReadSize;
}
asyncResult = new RequestStreamAsyncResult(this, state, callback, buffer, offset, dataRead);
uint bytesReturned;
try
{
uint flags = 0;
statusCode =
HttpApi.HttpReceiveRequestEntityBody(
RequestQueueHandle,
RequestId,
flags,
asyncResult.PinnedBuffer,
(uint)size,
out bytesReturned,
asyncResult.NativeOverlapped);
}
catch (Exception e)
{
LogHelper.LogException(Logger, "BeginRead", e);
asyncResult.Dispose();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
asyncResult = new RequestStreamAsyncResult(this, state, callback, dataRead);
asyncResult.Complete((int)bytesReturned);
}
else
{
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
LogHelper.LogException(Logger, "BeginRead", exception);
Abort();
throw exception;
}
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesReturned);
}
return asyncResult;
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
throw new ArgumentNullException("asyncResult");
}
RequestStreamAsyncResult castedAsyncResult = asyncResult as RequestStreamAsyncResult;
if (castedAsyncResult == null || castedAsyncResult.RequestStream != this)
{
throw new ArgumentException(Resources.Exception_WrongIAsyncResult, "asyncResult");
}
if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(Resources.Exception_EndCalledMultipleTimes);
}
castedAsyncResult.EndCalled = true;
// wait & then check for errors
// Throws on failure
var dataRead = castedAsyncResult.Task.GetAwaiter().GetResult();
// TODO: Verbose log #dataRead.
return dataRead;
}
public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
{
ValidateReadBuffer(buffer, offset, size);
CheckSizeLimit();
if (_closed)
{
return Task.FromResult<int>(0);
}
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
// TODO: Verbose log parameters
RequestStreamAsyncResult asyncResult = null;
uint dataRead = 0;
if (_dataChunkIndex != -1)
{
dataRead = _requestContext.Request.GetChunks(ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size);
if (dataRead > 0)
{
UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead);
if (TryCheckSizeLimit((int)dataRead, out var exception))
{
return Task.FromException<int>(exception);
}
// TODO: Verbose log #dataRead
return Task.FromResult<int>((int)dataRead);
}
}
uint statusCode = 0;
offset += (int)dataRead;
size -= (int)dataRead;
// the http.sys team recommends that we limit the size to 128kb
if (size > MaxReadSize)
{
size = MaxReadSize;
}
var cancellationRegistration = default(CancellationTokenRegistration);
if (cancellationToken.CanBeCanceled)
{
cancellationRegistration = RequestContext.RegisterForCancellation(cancellationToken);
}
asyncResult = new RequestStreamAsyncResult(this, null, null, buffer, offset, dataRead, cancellationRegistration);
uint bytesReturned;
try
{
uint flags = 0;
statusCode =
HttpApi.HttpReceiveRequestEntityBody(
RequestQueueHandle,
RequestId,
flags,
asyncResult.PinnedBuffer,
(uint)size,
out bytesReturned,
asyncResult.NativeOverlapped);
}
catch (Exception e)
{
asyncResult.Dispose();
Abort();
LogHelper.LogException(Logger, "ReadAsync", e);
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
uint totalRead = dataRead + bytesReturned;
UpdateAfterRead(statusCode, totalRead);
if (TryCheckSizeLimit((int)totalRead, out var exception))
{
return Task.FromException<int>(exception);
}
// TODO: Verbose log totalRead
return Task.FromResult<int>((int)totalRead);
}
else
{
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
LogHelper.LogException(Logger, "ReadAsync", exception);
Abort();
throw exception;
}
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.Dispose();
uint totalRead = dataRead + bytesReturned;
UpdateAfterRead(statusCode, totalRead);
if (TryCheckSizeLimit((int)totalRead, out var exception))
{
return Task.FromException<int>(exception);
}
// TODO: Verbose log
return Task.FromResult<int>((int)totalRead);
}
return asyncResult.Task;
}
public override void Write(byte[] buffer, int offset, int size)
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
public override void EndWrite(IAsyncResult asyncResult)
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
// Called before each read
private void CheckSizeLimit()
{
// Note SwitchToOpaqueMode sets HasStarted and clears _maxSize, so these limits don't apply.
if (!HasStarted)
{
var contentLength = RequestContext.Request.ContentLength;
if (contentLength.HasValue && _maxSize.HasValue && contentLength.Value > _maxSize.Value)
{
throw new IOException(
$"The request's Content-Length {contentLength.Value} is larger than the request body size limit {_maxSize.Value}.");
}
HasStarted = true;
}
else if (TryCheckSizeLimit(0, out var exception))
{
throw exception;
}
}
// Called after each read.
internal bool TryCheckSizeLimit(int bytesRead, out Exception exception)
{
_totalRead += bytesRead;
if (_maxSize.HasValue && _totalRead > _maxSize.Value)
{
exception = new IOException($"The total number of bytes read {_totalRead} has exceeded the request body size limit {_maxSize.Value}.");
return true;
}
exception = null;
return false;
}
protected override void Dispose(bool disposing)
{
try
{
_closed = true;
}
finally
{
base.Dispose(disposing);
}
}
}
}

View File

@ -0,0 +1,194 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal unsafe class RequestStreamAsyncResult : IAsyncResult, IDisposable
{
private static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(Callback);
private SafeNativeOverlapped _overlapped;
private IntPtr _pinnedBuffer;
private uint _dataAlreadyRead;
private TaskCompletionSource<int> _tcs;
private RequestStream _requestStream;
private AsyncCallback _callback;
private CancellationTokenRegistration _cancellationRegistration;
internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback)
{
_requestStream = requestStream;
_tcs = new TaskCompletionSource<int>(userState);
_callback = callback;
}
internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback, uint dataAlreadyRead)
: this(requestStream, userState, callback)
{
_dataAlreadyRead = dataAlreadyRead;
}
internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback, byte[] buffer, int offset, uint dataAlreadyRead)
: this(requestStream, userState, callback, buffer, offset, dataAlreadyRead, new CancellationTokenRegistration())
{
}
internal RequestStreamAsyncResult(RequestStream requestStream, object userState, AsyncCallback callback, byte[] buffer, int offset, uint dataAlreadyRead, CancellationTokenRegistration cancellationRegistration)
: this(requestStream, userState, callback)
{
_dataAlreadyRead = dataAlreadyRead;
var boundHandle = requestStream.RequestContext.Server.RequestQueue.BoundHandle;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, buffer));
_pinnedBuffer = (Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
_cancellationRegistration = cancellationRegistration;
}
internal RequestStream RequestStream
{
get { return _requestStream; }
}
internal SafeNativeOverlapped NativeOverlapped
{
get { return _overlapped; }
}
internal IntPtr PinnedBuffer
{
get { return _pinnedBuffer; }
}
internal uint DataAlreadyRead
{
get { return _dataAlreadyRead; }
}
internal Task<int> Task
{
get { return _tcs.Task; }
}
internal bool EndCalled { get; set; }
internal void IOCompleted(uint errorCode, uint numBytes)
{
IOCompleted(this, errorCode, numBytes);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirecting to callback")]
private static void IOCompleted(RequestStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
{
try
{
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
asyncResult.Fail(new IOException(string.Empty, new HttpSysException((int)errorCode)));
}
else
{
// TODO: Verbose log dump data read
asyncResult.Complete((int)numBytes, errorCode);
}
}
catch (Exception e)
{
asyncResult.Fail(new IOException(string.Empty, e));
}
}
private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
{
var asyncResult = (RequestStreamAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
IOCompleted(asyncResult, errorCode, numBytes);
}
internal void Complete(int read, uint errorCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
if (_requestStream.TryCheckSizeLimit(read + (int)DataAlreadyRead, out var exception))
{
_tcs.TrySetException(exception);
}
else if (_tcs.TrySetResult(read + (int)DataAlreadyRead))
{
RequestStream.UpdateAfterRead((uint)errorCode, (uint)(read + DataAlreadyRead));
if (_callback != null)
{
try
{
_callback(this);
}
catch (Exception)
{
// TODO: Exception handling? This may be an IO callback thread and throwing here could crash the app.
}
}
}
Dispose();
}
internal void Fail(Exception ex)
{
if (_tcs.TrySetException(ex) && _callback != null)
{
try
{
_callback(this);
}
catch (Exception)
{
// TODO: Exception handling? This may be an IO callback thread and throwing here could crash the app.
// TODO: Log
}
}
Dispose();
_requestStream.Abort();
}
[SuppressMessage("Microsoft.Usage", "CA2216:DisposableTypesShouldDeclareFinalizer", Justification = "The disposable resource referenced does have a finalizer.")]
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_overlapped != null)
{
_overlapped.Dispose();
}
_cancellationRegistration.Dispose();
}
}
public object AsyncState
{
get { return _tcs.Task.AsyncState; }
}
public WaitHandle AsyncWaitHandle
{
get { return ((IAsyncResult)_tcs.Task).AsyncWaitHandle; }
}
public bool CompletedSynchronously
{
get { return ((IAsyncResult)_tcs.Task).CompletedSynchronously; }
}
public bool IsCompleted
{
get { return _tcs.Task.IsCompleted; }
}
}
}

View File

@ -0,0 +1,627 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Primitives;
using static Microsoft.AspNetCore.HttpSys.Internal.UnsafeNclNativeMethods;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal sealed class Response
{
private ResponseState _responseState;
private string _reasonPhrase;
private ResponseBody _nativeStream;
private AuthenticationSchemes _authChallenges;
private TimeSpan? _cacheTtl;
private long _expectedBodyLength;
private BoundaryType _boundaryType;
private HttpApiTypes.HTTP_RESPONSE_V2 _nativeResponse;
internal Response(RequestContext requestContext)
{
// TODO: Verbose log
RequestContext = requestContext;
Headers = new HeaderCollection();
// We haven't started yet, or we're just buffered, we can clear any data, headers, and state so
// that we can start over (e.g. to write an error message).
_nativeResponse = new HttpApiTypes.HTTP_RESPONSE_V2();
Headers.IsReadOnly = false;
Headers.Clear();
_reasonPhrase = null;
_boundaryType = BoundaryType.None;
_nativeResponse.Response_V1.StatusCode = (ushort)StatusCodes.Status200OK;
_nativeResponse.Response_V1.Version.MajorVersion = 1;
_nativeResponse.Response_V1.Version.MinorVersion = 1;
_responseState = ResponseState.Created;
_expectedBodyLength = 0;
_nativeStream = null;
_cacheTtl = null;
_authChallenges = RequestContext.Server.Options.Authentication.Schemes;
}
private enum ResponseState
{
Created,
ComputedHeaders,
Started,
Closed,
}
private RequestContext RequestContext { get; }
private Request Request => RequestContext.Request;
public int StatusCode
{
get { return _nativeResponse.Response_V1.StatusCode; }
set
{
// Http.Sys automatically sends 100 Continue responses when you read from the request body.
if (value <= 100 || 999 < value)
{
throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(Resources.Exception_InvalidStatusCode, value));
}
CheckResponseStarted();
_nativeResponse.Response_V1.StatusCode = (ushort)value;
}
}
public string ReasonPhrase
{
get { return _reasonPhrase; }
set
{
// TODO: Validate user input for illegal chars, length limit, etc.?
CheckResponseStarted();
_reasonPhrase = value;
}
}
public Stream Body
{
get
{
EnsureResponseStream();
return _nativeStream;
}
}
internal bool BodyIsFinished => _nativeStream?.IsDisposed ?? _responseState >= ResponseState.Closed;
/// <summary>
/// The authentication challenges that will be added to the response if the status code is 401.
/// This must be a subset of the AuthenticationSchemes enabled on the server.
/// </summary>
public AuthenticationSchemes AuthenticationChallenges
{
get { return _authChallenges; }
set
{
CheckResponseStarted();
_authChallenges = value;
}
}
private string GetReasonPhrase(int statusCode)
{
string reasonPhrase = ReasonPhrase;
if (string.IsNullOrWhiteSpace(reasonPhrase))
{
// If the user hasn't set this then it is generated on the fly if possible.
reasonPhrase = HttpReasonPhrase.Get(statusCode) ?? string.Empty;
}
return reasonPhrase;
}
// We MUST NOT send message-body when we send responses with these Status codes
private static readonly int[] StatusWithNoResponseBody = { 100, 101, 204, 205, 304 };
private static bool CanSendResponseBody(int responseCode)
{
for (int i = 0; i < StatusWithNoResponseBody.Length; i++)
{
if (responseCode == StatusWithNoResponseBody[i])
{
return false;
}
}
return true;
}
public HeaderCollection Headers { get; }
internal long ExpectedBodyLength
{
get { return _expectedBodyLength; }
}
// Header accessors
public long? ContentLength
{
get { return Headers.ContentLength; }
set { Headers.ContentLength = value; }
}
/// <summary>
/// Enable kernel caching for the response with the given timeout. Http.Sys determines if the response
/// can be cached.
/// </summary>
public TimeSpan? CacheTtl
{
get { return _cacheTtl; }
set
{
CheckResponseStarted();
_cacheTtl = value;
}
}
internal void Abort()
{
// Update state for HasStarted. Do not attempt a graceful Dispose.
_responseState = ResponseState.Closed;
}
// should only be called from RequestContext
internal void Dispose()
{
if (_responseState >= ResponseState.Closed)
{
return;
}
// TODO: Verbose log
EnsureResponseStream();
_nativeStream.Dispose();
_responseState = ResponseState.Closed;
}
internal BoundaryType BoundaryType
{
get { return _boundaryType; }
}
internal bool HasComputedHeaders
{
get { return _responseState >= ResponseState.ComputedHeaders; }
}
/// <summary>
/// Indicates if the response status, reason, and headers are prepared to send and can
/// no longer be modified. This is caused by the first write or flush to the response body.
/// </summary>
public bool HasStarted
{
get { return _responseState >= ResponseState.Started; }
}
private void CheckResponseStarted()
{
if (HasStarted)
{
throw new InvalidOperationException("Headers already sent.");
}
}
private void EnsureResponseStream()
{
if (_nativeStream == null)
{
_nativeStream = new ResponseBody(RequestContext);
}
}
/*
12.3
HttpSendHttpResponse() and HttpSendResponseEntityBody() Flag Values.
The following flags can be used on calls to HttpSendHttpResponse() and HttpSendResponseEntityBody() API calls:
#define HTTP_SEND_RESPONSE_FLAG_DISCONNECT 0x00000001
#define HTTP_SEND_RESPONSE_FLAG_MORE_DATA 0x00000002
#define HTTP_SEND_RESPONSE_FLAG_RAW_HEADER 0x00000004
#define HTTP_SEND_RESPONSE_FLAG_VALID 0x00000007
HTTP_SEND_RESPONSE_FLAG_DISCONNECT:
specifies that the network connection should be disconnected immediately after
sending the response, overriding the HTTP protocol's persistent connection features.
HTTP_SEND_RESPONSE_FLAG_MORE_DATA:
specifies that additional entity body data will be sent by the caller. Thus,
the last call HttpSendResponseEntityBody for a RequestId, will have this flag reset.
HTTP_SEND_RESPONSE_RAW_HEADER:
specifies that a caller of HttpSendResponseEntityBody() is intentionally omitting
a call to HttpSendHttpResponse() in order to bypass normal header processing. The
actual HTTP header will be generated by the application and sent as entity body.
This flag should be passed on the first call to HttpSendResponseEntityBody, and
not after. Thus, flag is not applicable to HttpSendHttpResponse.
*/
// TODO: Consider using HTTP_SEND_RESPONSE_RAW_HEADER with HttpSendResponseEntityBody instead of calling HttpSendHttpResponse.
// This will give us more control of the bytes that hit the wire, including encodings, HTTP 1.0, etc..
// It may also be faster to do this work in managed code and then pass down only one buffer.
// What would we loose by bypassing HttpSendHttpResponse?
//
// TODO: Consider using the HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA flag for most/all responses rather than just Opaque.
internal unsafe uint SendHeaders(HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks,
ResponseStreamAsyncResult asyncResult,
HttpApiTypes.HTTP_FLAGS flags,
bool isOpaqueUpgrade)
{
Debug.Assert(!HasStarted, "HttpListenerResponse::SendHeaders()|SentHeaders is true.");
_responseState = ResponseState.Started;
var reasonPhrase = GetReasonPhrase(StatusCode);
uint statusCode;
uint bytesSent;
List<GCHandle> pinnedHeaders = SerializeHeaders(isOpaqueUpgrade);
try
{
if (dataChunks != null)
{
if (pinnedHeaders == null)
{
pinnedHeaders = new List<GCHandle>();
}
var handle = GCHandle.Alloc(dataChunks, GCHandleType.Pinned);
pinnedHeaders.Add(handle);
_nativeResponse.Response_V1.EntityChunkCount = (ushort)dataChunks.Length;
_nativeResponse.Response_V1.pEntityChunks = (HttpApiTypes.HTTP_DATA_CHUNK*)handle.AddrOfPinnedObject();
}
else if (asyncResult != null && asyncResult.DataChunks != null)
{
_nativeResponse.Response_V1.EntityChunkCount = asyncResult.DataChunkCount;
_nativeResponse.Response_V1.pEntityChunks = asyncResult.DataChunks;
}
else
{
_nativeResponse.Response_V1.EntityChunkCount = 0;
_nativeResponse.Response_V1.pEntityChunks = null;
}
var cachePolicy = new HttpApiTypes.HTTP_CACHE_POLICY();
if (_cacheTtl.HasValue && _cacheTtl.Value > TimeSpan.Zero)
{
cachePolicy.Policy = HttpApiTypes.HTTP_CACHE_POLICY_TYPE.HttpCachePolicyTimeToLive;
cachePolicy.SecondsToLive = (uint)Math.Min(_cacheTtl.Value.Ticks / TimeSpan.TicksPerSecond, Int32.MaxValue);
}
byte[] reasonPhraseBytes = HeaderEncoding.GetBytes(reasonPhrase);
fixed (byte* pReasonPhrase = reasonPhraseBytes)
{
_nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length;
_nativeResponse.Response_V1.pReason = (byte*)pReasonPhrase;
fixed (HttpApiTypes.HTTP_RESPONSE_V2* pResponse = &_nativeResponse)
{
statusCode =
HttpApi.HttpSendHttpResponse(
RequestContext.Server.RequestQueue.Handle,
Request.RequestId,
(uint)flags,
pResponse,
&cachePolicy,
&bytesSent,
IntPtr.Zero,
0,
asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped,
IntPtr.Zero);
if (asyncResult != null &&
statusCode == ErrorCodes.ERROR_SUCCESS &&
HttpSysListener.SkipIOCPCallbackOnSuccess)
{
asyncResult.BytesSent = bytesSent;
// The caller will invoke IOCompleted
}
}
}
}
finally
{
FreePinnedHeaders(pinnedHeaders);
}
return statusCode;
}
internal HttpApiTypes.HTTP_FLAGS ComputeHeaders(long writeCount, bool endOfRequest = false)
{
if (StatusCode == (ushort)StatusCodes.Status401Unauthorized)
{
RequestContext.Server.Options.Authentication.SetAuthenticationChallenge(RequestContext);
}
var flags = HttpApiTypes.HTTP_FLAGS.NONE;
Debug.Assert(!HasComputedHeaders, nameof(HasComputedHeaders) + " is true.");
_responseState = ResponseState.ComputedHeaders;
// Gather everything from the request that affects the response:
var requestVersion = Request.ProtocolVersion;
var requestConnectionString = Request.Headers[HttpKnownHeaderNames.Connection];
var isHeadRequest = Request.IsHeadMethod;
var requestCloseSet = Matches(Constants.Close, requestConnectionString);
// Gather everything the app may have set on the response:
// Http.Sys does not allow us to specify the response protocol version, assume this is a HTTP/1.1 response when making decisions.
var responseConnectionString = Headers[HttpKnownHeaderNames.Connection];
var transferEncodingString = Headers[HttpKnownHeaderNames.TransferEncoding];
var responseContentLength = ContentLength;
var responseCloseSet = Matches(Constants.Close, responseConnectionString);
var responseChunkedSet = Matches(Constants.Chunked, transferEncodingString);
var statusCanHaveBody = CanSendResponseBody(RequestContext.Response.StatusCode);
// Determine if the connection will be kept alive or closed.
var keepConnectionAlive = true;
if (requestVersion <= Constants.V1_0 // Http.Sys does not support "Keep-Alive: true" or "Connection: Keep-Alive"
|| (requestVersion == Constants.V1_1 && requestCloseSet)
|| responseCloseSet)
{
keepConnectionAlive = false;
}
// Determine the body format. If the user asks to do something, let them, otherwise choose a good default for the scenario.
if (responseContentLength.HasValue)
{
_boundaryType = BoundaryType.ContentLength;
// ComputeLeftToWrite checks for HEAD requests when setting _leftToWrite
_expectedBodyLength = responseContentLength.Value;
if (_expectedBodyLength == writeCount && !isHeadRequest)
{
// A single write with the whole content-length. Http.Sys will set the content-length for us in this scenario.
// If we don't remove it then range requests served from cache will have two.
// https://github.com/aspnet/HttpSysServer/issues/167
ContentLength = null;
}
}
else if (responseChunkedSet)
{
// The application is performing it's own chunking.
_boundaryType = BoundaryType.PassThrough;
}
else if (endOfRequest)
{
if (!isHeadRequest && statusCanHaveBody)
{
Headers[HttpKnownHeaderNames.ContentLength] = Constants.Zero;
}
_boundaryType = BoundaryType.ContentLength;
_expectedBodyLength = 0;
}
else if (requestVersion == Constants.V1_1)
{
_boundaryType = BoundaryType.Chunked;
Headers[HttpKnownHeaderNames.TransferEncoding] = Constants.Chunked;
}
else
{
// v1.0 and the length cannot be determined, so we must close the connection after writing data
keepConnectionAlive = false;
_boundaryType = BoundaryType.Close;
}
// Managed connection lifetime
if (!keepConnectionAlive)
{
// All Http.Sys responses are v1.1, so use 1.1 response headers
// Note that if we don't add this header, Http.Sys will often do it for us.
if (!responseCloseSet)
{
Headers.Append(HttpKnownHeaderNames.Connection, Constants.Close);
}
flags = HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
return flags;
}
private static bool Matches(string knownValue, string input)
{
return string.Equals(knownValue, input?.Trim(), StringComparison.OrdinalIgnoreCase);
}
private unsafe List<GCHandle> SerializeHeaders(bool isOpaqueUpgrade)
{
Headers.IsReadOnly = true; // Prohibit further modifications.
HttpApiTypes.HTTP_UNKNOWN_HEADER[] unknownHeaders = null;
HttpApiTypes.HTTP_RESPONSE_INFO[] knownHeaderInfo = null;
List<GCHandle> pinnedHeaders;
GCHandle gcHandle;
if (Headers.Count == 0)
{
return null;
}
string headerName;
string headerValue;
int lookup;
byte[] bytes = null;
pinnedHeaders = new List<GCHandle>();
int numUnknownHeaders = 0;
int numKnownMultiHeaders = 0;
foreach (var headerPair in Headers)
{
if (headerPair.Value.Count == 0)
{
continue;
}
// See if this is an unknown header
lookup = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key);
// Http.Sys doesn't let us send the Connection: Upgrade header as a Known header.
if (lookup == -1 ||
(isOpaqueUpgrade && lookup == (int)HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
{
numUnknownHeaders += headerPair.Value.Count;
}
else if (headerPair.Value.Count > 1)
{
numKnownMultiHeaders++;
}
// else known single-value header.
}
try
{
fixed (HttpApiTypes.HTTP_KNOWN_HEADER* pKnownHeaders = &_nativeResponse.Response_V1.Headers.KnownHeaders)
{
foreach (var headerPair in Headers)
{
if (headerPair.Value.Count == 0)
{
continue;
}
headerName = headerPair.Key;
StringValues headerValues = headerPair.Value;
lookup = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName);
// Http.Sys doesn't let us send the Connection: Upgrade header as a Known header.
if (lookup == -1 ||
(isOpaqueUpgrade && lookup == (int)HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
{
if (unknownHeaders == null)
{
unknownHeaders = new HttpApiTypes.HTTP_UNKNOWN_HEADER[numUnknownHeaders];
gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
_nativeResponse.Response_V1.Headers.pUnknownHeaders = (HttpApiTypes.HTTP_UNKNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
}
for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++)
{
// Add Name
bytes = HeaderEncoding.GetBytes(headerName);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pName = (byte*)gcHandle.AddrOfPinnedObject();
// Add Value
headerValue = headerValues[headerValueIndex] ?? string.Empty;
bytes = HeaderEncoding.GetBytes(headerValue);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pRawValue = (byte*)gcHandle.AddrOfPinnedObject();
_nativeResponse.Response_V1.Headers.UnknownHeaderCount++;
}
}
else if (headerPair.Value.Count == 1)
{
headerValue = headerValues[0] ?? string.Empty;
bytes = HeaderEncoding.GetBytes(headerValue);
pKnownHeaders[lookup].RawValueLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
pKnownHeaders[lookup].pRawValue = (byte*)gcHandle.AddrOfPinnedObject();
}
else
{
if (knownHeaderInfo == null)
{
knownHeaderInfo = new HttpApiTypes.HTTP_RESPONSE_INFO[numKnownMultiHeaders];
gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
_nativeResponse.pResponseInfo = (HttpApiTypes.HTTP_RESPONSE_INFO*)gcHandle.AddrOfPinnedObject();
}
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Type = HttpApiTypes.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Length = (uint)Marshal.SizeOf<HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS>();
HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS header = new HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS();
header.HeaderId = (HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum)lookup;
header.Flags = HttpApiTypes.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only.
HttpApiTypes.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApiTypes.HTTP_KNOWN_HEADER[headerValues.Count];
gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
header.KnownHeaders = (HttpApiTypes.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++)
{
// Add Value
headerValue = headerValues[headerValueIndex] ?? string.Empty;
bytes = HeaderEncoding.GetBytes(headerValue);
nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
nativeHeaderValues[header.KnownHeaderCount].pRawValue = (byte*)gcHandle.AddrOfPinnedObject();
header.KnownHeaderCount++;
}
// This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set.
gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
knownHeaderInfo[_nativeResponse.ResponseInfoCount].pInfo = (HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS*)gcHandle.AddrOfPinnedObject();
_nativeResponse.ResponseInfoCount++;
}
}
}
}
catch
{
FreePinnedHeaders(pinnedHeaders);
throw;
}
return pinnedHeaders;
}
private static void FreePinnedHeaders(List<GCHandle> pinnedHeaders)
{
if (pinnedHeaders != null)
{
foreach (GCHandle gcHandle in pinnedHeaders)
{
if (gcHandle.IsAllocated)
{
gcHandle.Free();
}
}
}
}
// Subset of ComputeHeaders
internal void SendOpaqueUpgrade()
{
_boundaryType = BoundaryType.Close;
// TODO: Send headers async?
ulong errorCode = SendHeaders(null, null,
HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_OPAQUE |
HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA |
HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA,
true);
if (errorCode != ErrorCodes.ERROR_SUCCESS)
{
throw new HttpSysException((int)errorCode);
}
}
internal void CancelLastWrite()
{
_nativeStream?.CancelLastWrite();
}
public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancel)
{
EnsureResponseStream();
return _nativeStream.SendFileAsync(path, offset, count, cancel);
}
internal void SwitchToOpaqueMode()
{
EnsureResponseStream();
_nativeStream.SwitchToOpaqueMode();
}
}
}

View File

@ -0,0 +1,705 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
using static Microsoft.AspNetCore.HttpSys.Internal.UnsafeNclNativeMethods;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class ResponseBody : Stream
{
private RequestContext _requestContext;
private long _leftToWrite = long.MinValue;
private bool _skipWrites;
private bool _disposed;
// The last write needs special handling to cancel.
private ResponseStreamAsyncResult _lastWrite;
internal ResponseBody(RequestContext requestContext)
{
_requestContext = requestContext;
}
internal RequestContext RequestContext
{
get { return _requestContext; }
}
private SafeHandle RequestQueueHandle => RequestContext.Server.RequestQueue.Handle;
private ulong RequestId => RequestContext.Request.RequestId;
private ILogger Logger => RequestContext.Server.Logger;
internal bool ThrowWriteExceptions => RequestContext.Server.Options.ThrowWriteExceptions;
internal bool IsDisposed => _disposed;
public override bool CanSeek
{
get
{
return false;
}
}
public override bool CanWrite
{
get
{
return true;
}
}
public override bool CanRead
{
get
{
return false;
}
}
public override long Length
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
public override long Position
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
set
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
// Send headers
public override void Flush()
{
if (!RequestContext.AllowSynchronousIO)
{
throw new InvalidOperationException("Synchronous IO APIs are disabled, see AllowSynchronousIO.");
}
if (_disposed)
{
return;
}
FlushInternal(endOfRequest: false);
}
// We never expect endOfRequest and data at the same time
private unsafe void FlushInternal(bool endOfRequest, ArraySegment<byte> data = new ArraySegment<byte>())
{
Debug.Assert(!(endOfRequest && data.Count > 0), "Data is not supported at the end of the request.");
if (_skipWrites)
{
return;
}
var started = _requestContext.Response.HasStarted;
if (data.Count == 0 && started && !endOfRequest)
{
// No data to send and we've already sent the headers
return;
}
// Make sure all validation is performed before this computes the headers
var flags = ComputeLeftToWrite(data.Count, endOfRequest);
if (endOfRequest && _leftToWrite > 0)
{
_requestContext.Abort();
// This is logged rather than thrown because it is too late for an exception to be visible in user code.
LogHelper.LogError(Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length.");
return;
}
uint statusCode = 0;
HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks;
var pinnedBuffers = PinDataBuffers(endOfRequest, data, out dataChunks);
try
{
if (!started)
{
statusCode = _requestContext.Response.SendHeaders(dataChunks, null, flags, false);
}
else
{
fixed (HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks = dataChunks)
{
statusCode = HttpApi.HttpSendResponseEntityBody(
RequestQueueHandle,
RequestId,
(uint)flags,
(ushort)dataChunks.Length,
pDataChunks,
null,
IntPtr.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
}
}
}
finally
{
FreeDataBuffers(pinnedBuffers);
}
if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_HANDLE_EOF
// Don't throw for disconnects, we were already finished with the response.
&& (!endOfRequest || (statusCode != ErrorCodes.ERROR_CONNECTION_INVALID && statusCode != ErrorCodes.ERROR_INVALID_PARAMETER)))
{
if (ThrowWriteExceptions)
{
var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
LogHelper.LogException(Logger, "Flush", exception);
Abort();
throw exception;
}
else
{
// Abort the request but do not close the stream, let future writes complete silently
LogHelper.LogDebug(Logger, "Flush", $"Ignored write exception: {statusCode}");
Abort(dispose: false);
}
}
}
private List<GCHandle> PinDataBuffers(bool endOfRequest, ArraySegment<byte> data, out HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks)
{
var pins = new List<GCHandle>();
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
var currentChunk = 0;
// Figure out how many data chunks
if (chunked && data.Count == 0 && endOfRequest)
{
dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[1];
SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment<byte>(Helpers.ChunkTerminator));
return pins;
}
else if (data.Count == 0)
{
// No data
dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[0];
return pins;
}
var chunkCount = 1;
if (chunked)
{
// Chunk framing
chunkCount += 2;
if (endOfRequest)
{
// Chunk terminator
chunkCount += 1;
}
}
dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[chunkCount];
if (chunked)
{
var chunkHeaderBuffer = Helpers.GetChunkHeader(data.Count);
SetDataChunk(dataChunks, ref currentChunk, pins, chunkHeaderBuffer);
}
SetDataChunk(dataChunks, ref currentChunk, pins, data);
if (chunked)
{
SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment<byte>(Helpers.CRLF));
if (endOfRequest)
{
SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment<byte>(Helpers.ChunkTerminator));
}
}
return pins;
}
private static void SetDataChunk(HttpApiTypes.HTTP_DATA_CHUNK[] chunks, ref int chunkIndex, List<GCHandle> pins, ArraySegment<byte> buffer)
{
var handle = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned);
pins.Add(handle);
chunks[chunkIndex].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
chunks[chunkIndex].fromMemory.pBuffer = handle.AddrOfPinnedObject() + buffer.Offset;
chunks[chunkIndex].fromMemory.BufferLength = (uint)buffer.Count;
chunkIndex++;
}
private void FreeDataBuffers(List<GCHandle> pinnedBuffers)
{
foreach (var pin in pinnedBuffers)
{
if (pin.IsAllocated)
{
pin.Free();
}
}
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (_disposed)
{
return Task.CompletedTask;
}
return FlushInternalAsync(new ArraySegment<byte>(), cancellationToken);
}
// Simpler than Flush because it will never be called at the end of the request from Dispose.
private unsafe Task FlushInternalAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
{
if (_skipWrites)
{
return Task.CompletedTask;
}
var started = _requestContext.Response.HasStarted;
if (data.Count == 0 && started)
{
// No data to send and we've already sent the headers
return Task.CompletedTask;
}
if (cancellationToken.IsCancellationRequested)
{
Abort(ThrowWriteExceptions);
return Task.FromCanceled<int>(cancellationToken);
}
// Make sure all validation is performed before this computes the headers
var flags = ComputeLeftToWrite(data.Count);
uint statusCode = 0;
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
var asyncResult = new ResponseStreamAsyncResult(this, data, chunked, cancellationToken);
uint bytesSent = 0;
try
{
if (!started)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
else
{
statusCode = HttpApi.HttpSendResponseEntityBody(
RequestQueueHandle,
RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
IntPtr.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
{
LogHelper.LogException(Logger, "FlushAsync", e);
asyncResult.Dispose();
Abort();
throw;
}
if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING)
{
if (cancellationToken.IsCancellationRequested)
{
LogHelper.LogDebug(Logger, "FlushAsync", $"Write cancelled with error code: {statusCode}");
asyncResult.Cancel(ThrowWriteExceptions);
}
else if (ThrowWriteExceptions)
{
asyncResult.Dispose();
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
LogHelper.LogException(Logger, "FlushAsync", exception);
Abort();
throw exception;
}
else
{
// Abort the request but do not close the stream, let future writes complete silently
LogHelper.LogDebug(Logger, "FlushAsync", $"Ignored write exception: {statusCode}");
asyncResult.FailSilently();
}
}
if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult.Task;
}
#region NotSupported Read/Seek
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override void SetLength(long value)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override int Read([In, Out] byte[] buffer, int offset, int count)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
public override int EndRead(IAsyncResult asyncResult)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
#endregion
internal void Abort(bool dispose = true)
{
if (dispose)
{
_disposed = true;
}
else
{
_skipWrites = true;
}
_requestContext.Abort();
}
private HttpApiTypes.HTTP_FLAGS ComputeLeftToWrite(long writeCount, bool endOfRequest = false)
{
var flags = HttpApiTypes.HTTP_FLAGS.NONE;
if (!_requestContext.Response.HasComputedHeaders)
{
flags = _requestContext.Response.ComputeHeaders(writeCount, endOfRequest);
}
if (_leftToWrite == long.MinValue)
{
if (_requestContext.Request.IsHeadMethod)
{
_leftToWrite = 0;
}
else if (_requestContext.Response.BoundaryType == BoundaryType.ContentLength)
{
_leftToWrite = _requestContext.Response.ExpectedBodyLength;
}
else
{
_leftToWrite = -1; // unlimited
}
}
if (endOfRequest && _requestContext.Response.BoundaryType == BoundaryType.Close)
{
flags |= HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
else if (!endOfRequest && _leftToWrite != writeCount)
{
flags |= HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
}
// Update _leftToWrite now so we can queue up additional async writes.
if (_leftToWrite > 0)
{
// keep track of the data transferred
_leftToWrite -= writeCount;
}
if (_leftToWrite == 0)
{
// in this case we already passed 0 as the flag, so we don't need to call HttpSendResponseEntityBody() when we Close()
_disposed = true;
}
// else -1 unlimited
return flags;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!RequestContext.AllowSynchronousIO)
{
throw new InvalidOperationException("Synchronous IO APIs are disabled, see AllowSynchronousIO.");
}
// Validates for null and bounds. Allows count == 0.
// TODO: Verbose log parameters
var data = new ArraySegment<byte>(buffer, offset, count);
CheckDisposed();
CheckWriteCount(count);
FlushInternal(endOfRequest: false, data: data);
}
private void CheckWriteCount(long? count)
{
var contentLength = _requestContext.Response.ContentLength;
// First write with more bytes written than the entire content-length
if (!_requestContext.Response.HasComputedHeaders && contentLength < count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
// A write in a response that has already started where the count exceeds the remainder of the content-length
else if (_requestContext.Response.HasComputedHeaders && _requestContext.Response.BoundaryType == BoundaryType.ContentLength
&& _leftToWrite < count)
{
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return WriteAsync(buffer, offset, count).ToIAsyncResult(callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
throw new ArgumentNullException(nameof(asyncResult));
}
((Task)asyncResult).GetAwaiter().GetResult();
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
// Validates for null and bounds. Allows count == 0.
// TODO: Verbose log parameters
var data = new ArraySegment<byte>(buffer, offset, count);
CheckDisposed();
CheckWriteCount(count);
return FlushInternalAsync(data, cancellationToken);
}
internal async Task SendFileAsync(string fileName, long offset, long? count, CancellationToken cancellationToken)
{
// It's too expensive to validate the file attributes before opening the file. Open the file and then check the lengths.
// This all happens inside of ResponseStreamAsyncResult.
// TODO: Verbose log parameters
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException("fileName");
}
CheckDisposed();
CheckWriteCount(count);
// We can't mix await and unsafe so separate the unsafe code into another method.
await SendFileAsyncCore(fileName, offset, count, cancellationToken);
}
internal unsafe Task SendFileAsyncCore(string fileName, long offset, long? count, CancellationToken cancellationToken)
{
if (_skipWrites)
{
return Task.CompletedTask;
}
var started = _requestContext.Response.HasStarted;
if (count == 0 && started)
{
// No data to send and we've already sent the headers
return Task.CompletedTask;
}
if (cancellationToken.IsCancellationRequested)
{
Abort(ThrowWriteExceptions);
return Task.FromCanceled<int>(cancellationToken);
}
// We are setting buffer size to 1 to prevent FileStream from allocating it's internal buffer
// It's too expensive to validate anything before opening the file. Open the file and then check the lengths.
var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 1,
options: FileOptions.Asynchronous | FileOptions.SequentialScan); // Extremely expensive.
try
{
var length = fileStream.Length; // Expensive, only do it once
if (!count.HasValue)
{
count = length - offset;
}
if (offset < 0 || offset > length)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
}
if (count < 0 || count > length - offset)
{
throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
}
CheckWriteCount(count);
}
catch
{
fileStream.Dispose();
throw;
}
// Make sure all validation is performed before this computes the headers
var flags = ComputeLeftToWrite(count.Value);
uint statusCode;
uint bytesSent = 0;
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
var asyncResult = new ResponseStreamAsyncResult(this, fileStream, offset, count.Value, chunked, cancellationToken);
try
{
if (!started)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
else
{
// TODO: If opaque then include the buffer data flag.
statusCode = HttpApi.HttpSendResponseEntityBody(
RequestQueueHandle,
RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
IntPtr.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
{
LogHelper.LogException(Logger, "SendFileAsync", e);
asyncResult.Dispose();
Abort();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
if (cancellationToken.IsCancellationRequested)
{
LogHelper.LogDebug(Logger, "SendFileAsync", $"Write cancelled with error code: {statusCode}");
asyncResult.Cancel(ThrowWriteExceptions);
}
else if (ThrowWriteExceptions)
{
asyncResult.Dispose();
var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
LogHelper.LogException(Logger, "SendFileAsync", exception);
Abort();
throw exception;
}
else
{
// Abort the request but do not close the stream, let future writes complete silently
LogHelper.LogDebug(Logger, "SendFileAsync", $"Ignored write exception: {statusCode}");
asyncResult.FailSilently();
}
}
if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult.Task;
}
protected override unsafe void Dispose(bool disposing)
{
try
{
if (disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
FlushInternal(endOfRequest: true);
}
}
finally
{
base.Dispose(disposing);
}
}
internal void SwitchToOpaqueMode()
{
_leftToWrite = -1;
}
// The final Content-Length async write can only be Canceled by CancelIoEx.
// Sync can only be Canceled by CancelSynchronousIo, but we don't attempt this right now.
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification =
"It is safe to ignore the return value on a cancel operation because the connection is being closed")]
internal unsafe void CancelLastWrite()
{
ResponseStreamAsyncResult asyncState = _lastWrite;
if (asyncState != null && !asyncState.IsCompleted)
{
UnsafeNclNativeMethods.CancelIoEx(RequestQueueHandle, asyncState.NativeOverlapped);
}
}
private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}
}

View File

@ -0,0 +1,341 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal unsafe class ResponseStreamAsyncResult : IAsyncResult, IDisposable
{
private static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(Callback);
private SafeNativeOverlapped _overlapped;
private HttpApiTypes.HTTP_DATA_CHUNK[] _dataChunks;
private FileStream _fileStream;
private ResponseBody _responseStream;
private TaskCompletionSource<object> _tcs;
private uint _bytesSent;
private CancellationToken _cancellationToken;
private CancellationTokenRegistration _cancellationRegistration;
internal ResponseStreamAsyncResult(ResponseBody responseStream, CancellationToken cancellationToken)
{
_responseStream = responseStream;
_tcs = new TaskCompletionSource<object>();
var cancellationRegistration = default(CancellationTokenRegistration);
if (cancellationToken.CanBeCanceled)
{
cancellationRegistration = _responseStream.RequestContext.RegisterForCancellation(cancellationToken);
}
_cancellationToken = cancellationToken;
_cancellationRegistration = cancellationRegistration;
}
internal ResponseStreamAsyncResult(ResponseBody responseStream, ArraySegment<byte> data, bool chunked,
CancellationToken cancellationToken)
: this(responseStream, cancellationToken)
{
var boundHandle = _responseStream.RequestContext.Server.RequestQueue.BoundHandle;
object[] objectsToPin;
if (data.Count == 0)
{
_dataChunks = null;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, null));
return;
}
_dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[1 + (chunked ? 2 : 0)];
objectsToPin = new object[_dataChunks.Length + 1];
objectsToPin[0] = _dataChunks;
var currentChunk = 0;
var currentPin = 1;
var chunkHeaderBuffer = new ArraySegment<byte>();
if (chunked)
{
chunkHeaderBuffer = Helpers.GetChunkHeader(data.Count);
SetDataChunk(_dataChunks, ref currentChunk, objectsToPin, ref currentPin, chunkHeaderBuffer);
}
SetDataChunk(_dataChunks, ref currentChunk, objectsToPin, ref currentPin, data);
if (chunked)
{
SetDataChunk(_dataChunks, ref currentChunk, objectsToPin, ref currentPin, new ArraySegment<byte>(Helpers.CRLF));
}
// This call will pin needed memory
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, objectsToPin));
currentChunk = 0;
if (chunked)
{
_dataChunks[currentChunk].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer.Array, chunkHeaderBuffer.Offset);
currentChunk++;
}
_dataChunks[currentChunk].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(data.Array, data.Offset);
currentChunk++;
if (chunked)
{
_dataChunks[currentChunk].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(Helpers.CRLF, 0);
currentChunk++;
}
}
internal ResponseStreamAsyncResult(ResponseBody responseStream, FileStream fileStream, long offset,
long count, bool chunked, CancellationToken cancellationToken)
: this(responseStream, cancellationToken)
{
var boundHandle = responseStream.RequestContext.Server.RequestQueue.BoundHandle;
_fileStream = fileStream;
if (count == 0)
{
_dataChunks = null;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, null));
}
else
{
_dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[chunked ? 3 : 1];
object[] objectsToPin = new object[_dataChunks.Length];
objectsToPin[_dataChunks.Length - 1] = _dataChunks;
var chunkHeaderBuffer = new ArraySegment<byte>();
if (chunked)
{
chunkHeaderBuffer = Helpers.GetChunkHeader(count);
_dataChunks[0].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[0].fromMemory.BufferLength = (uint)chunkHeaderBuffer.Count;
objectsToPin[0] = chunkHeaderBuffer.Array;
_dataChunks[1].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[1].fromFile.offset = (ulong)offset;
_dataChunks[1].fromFile.count = (ulong)count;
_dataChunks[1].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
// Nothing to pin for the file handle.
_dataChunks[2].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[2].fromMemory.BufferLength = (uint)Helpers.CRLF.Length;
objectsToPin[1] = Helpers.CRLF;
}
else
{
_dataChunks[0].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[0].fromFile.offset = (ulong)offset;
_dataChunks[0].fromFile.count = (ulong)count;
_dataChunks[0].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
}
// This call will pin needed memory
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, objectsToPin));
if (chunked)
{
// These must be set after pinning with Overlapped.
_dataChunks[0].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer.Array, chunkHeaderBuffer.Offset);
_dataChunks[2].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(Helpers.CRLF, 0);
}
}
}
private static void SetDataChunk(HttpApiTypes.HTTP_DATA_CHUNK[] chunks, ref int chunkIndex, object[] objectsToPin, ref int pinIndex, ArraySegment<byte> segment)
{
objectsToPin[pinIndex] = segment.Array;
pinIndex++;
chunks[chunkIndex].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
// The address is not set until after we pin it with Overlapped
chunks[chunkIndex].fromMemory.BufferLength = (uint)segment.Count;
chunkIndex++;
}
internal SafeNativeOverlapped NativeOverlapped
{
get { return _overlapped; }
}
internal Task Task
{
get { return _tcs.Task; }
}
internal uint BytesSent
{
get { return _bytesSent; }
set { _bytesSent = value; }
}
internal ushort DataChunkCount
{
get
{
if (_dataChunks == null)
{
return 0;
}
else
{
return (ushort)_dataChunks.Length;
}
}
}
internal HttpApiTypes.HTTP_DATA_CHUNK* DataChunks
{
get
{
if (_dataChunks == null)
{
return null;
}
else
{
return (HttpApiTypes.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(_dataChunks, 0));
}
}
}
internal bool EndCalled { get; set; }
internal void IOCompleted(uint errorCode)
{
IOCompleted(this, errorCode, BytesSent);
}
internal void IOCompleted(uint errorCode, uint numBytes)
{
IOCompleted(this, errorCode, numBytes);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirecting to callback")]
private static void IOCompleted(ResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
{
var logger = asyncResult._responseStream.RequestContext.Logger;
try
{
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
if (asyncResult._cancellationToken.IsCancellationRequested)
{
LogHelper.LogDebug(logger, "FlushAsync.IOCompleted", $"Write cancelled with error code: {errorCode}");
asyncResult.Cancel(asyncResult._responseStream.ThrowWriteExceptions);
}
else if (asyncResult._responseStream.ThrowWriteExceptions)
{
var exception = new IOException(string.Empty, new HttpSysException((int)errorCode));
LogHelper.LogException(logger, "FlushAsync.IOCompleted", exception);
asyncResult.Fail(exception);
}
else
{
LogHelper.LogDebug(logger, "FlushAsync.IOCompleted", $"Ignored write exception: {errorCode}");
asyncResult.FailSilently();
}
}
else
{
if (asyncResult._dataChunks == null)
{
// TODO: Verbose log data written
}
else
{
// TODO: Verbose log
// for (int i = 0; i < asyncResult._dataChunks.Length; i++)
// {
// Logging.Dump(Logging.HttpListener, asyncResult, "Callback", (IntPtr)asyncResult._dataChunks[0].fromMemory.pBuffer, (int)asyncResult._dataChunks[0].fromMemory.BufferLength);
// }
}
asyncResult.Complete();
}
}
catch (Exception e)
{
LogHelper.LogException(logger, "FlushAsync.IOCompleted", e);
asyncResult.Fail(e);
}
}
private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
{
var asyncResult = (ResponseStreamAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
IOCompleted(asyncResult, errorCode, numBytes);
}
internal void Complete()
{
Dispose();
_tcs.TrySetResult(null);
}
internal void FailSilently()
{
Dispose();
// Abort the request but do not close the stream, let future writes complete silently
_responseStream.Abort(dispose: false);
_tcs.TrySetResult(null);
}
internal void Cancel(bool dispose)
{
Dispose();
_responseStream.Abort(dispose);
_tcs.TrySetCanceled();
}
internal void Fail(Exception ex)
{
Dispose();
_responseStream.Abort();
_tcs.TrySetException(ex);
}
public object AsyncState
{
get { return _tcs.Task.AsyncState; }
}
public WaitHandle AsyncWaitHandle
{
get { return ((IAsyncResult)_tcs.Task).AsyncWaitHandle; }
}
public bool CompletedSynchronously
{
get { return ((IAsyncResult)_tcs.Task).CompletedSynchronously; }
}
public bool IsCompleted
{
get { return _tcs.Task.IsCompleted; }
}
public void Dispose()
{
if (_overlapped != null)
{
_overlapped.Dispose();
}
if (_fileStream != null)
{
_fileStream.Dispose();
}
_cancellationRegistration.Dispose();
}
}
}

View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Exception_ArrayTooSmall" xml:space="preserve">
<value>The destination array is too small.</value>
</data>
<data name="Exception_EndCalledMultipleTimes" xml:space="preserve">
<value>End has already been called.</value>
</data>
<data name="Exception_InvalidStatusCode" xml:space="preserve">
<value>The status code '{0}' is not supported.</value>
</data>
<data name="Exception_NoSeek" xml:space="preserve">
<value>The stream is not seekable.</value>
</data>
<data name="Exception_PrefixAlreadyRegistered" xml:space="preserve">
<value>The prefix '{0}' is already registered.</value>
</data>
<data name="Exception_ReadOnlyStream" xml:space="preserve">
<value>This stream only supports read operations.</value>
</data>
<data name="Exception_TooMuchWritten" xml:space="preserve">
<value>More data written than specified in the Content-Length header.</value>
</data>
<data name="Exception_UnsupportedScheme" xml:space="preserve">
<value>Only the http and https schemes are supported.</value>
</data>
<data name="Exception_WriteOnlyStream" xml:space="preserve">
<value>This stream only supports write operations.</value>
</data>
<data name="Exception_WrongIAsyncResult" xml:space="preserve">
<value>The given IAsyncResult does not match this opperation.</value>
</data>
<data name="Warning_ExceptionInOnResponseCompletedAction" xml:space="preserve">
<value>An exception occured while running an action registered with {0}.</value>
</data>
</root>

View File

@ -0,0 +1,115 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class ResponseStream : Stream
{
private readonly Stream _innerStream;
private readonly Func<Task> _onStart;
internal ResponseStream(Stream innerStream, Func<Task> onStart)
{
_innerStream = innerStream;
_onStart = onStart;
}
public override bool CanRead => _innerStream.CanRead;
public override bool CanSeek => _innerStream.CanSeek;
public override bool CanWrite => _innerStream.CanWrite;
public override long Length => _innerStream.Length;
public override long Position
{
get { return _innerStream.Position; }
set { _innerStream.Position = value; }
}
public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
public override void SetLength(long value) => _innerStream.SetLength(value);
public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count);
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _innerStream.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return _innerStream.EndRead(asyncResult);
}
public override void Flush()
{
_onStart().GetAwaiter().GetResult();
_innerStream.Flush();
}
public override async Task FlushAsync(CancellationToken cancellationToken)
{
await _onStart();
await _innerStream.FlushAsync(cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
_onStart().GetAwaiter().GetResult();
_innerStream.Write(buffer, offset, count);
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await _onStart();
await _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return ToIAsyncResult(WriteAsync(buffer, offset, count), callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
throw new ArgumentNullException(nameof(asyncResult));
}
((Task)asyncResult).GetAwaiter().GetResult();
}
private static IAsyncResult ToIAsyncResult(Task task, AsyncCallback callback, object state)
{
var tcs = new TaskCompletionSource<int>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception.InnerExceptions);
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(0);
}
if (callback != null)
{
callback(tcs.Task);
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
return tcs.Task;
}
}
}

View File

@ -0,0 +1,106 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal sealed class StandardFeatureCollection : IFeatureCollection
{
private static readonly Func<FeatureContext, object> _identityFunc = ReturnIdentity;
private static readonly Dictionary<Type, Func<FeatureContext, object>> _featureFuncLookup = new Dictionary<Type, Func<FeatureContext, object>>()
{
{ typeof(IHttpRequestFeature), _identityFunc },
{ typeof(IHttpConnectionFeature), _identityFunc },
{ typeof(IHttpResponseFeature), _identityFunc },
{ typeof(IHttpSendFileFeature), _identityFunc },
{ typeof(ITlsConnectionFeature), ctx => ctx.GetTlsConnectionFeature() },
// { typeof(ITlsTokenBindingFeature), ctx => ctx.GetTlsTokenBindingFeature() }, TODO: https://github.com/aspnet/HttpSysServer/issues/231
{ typeof(IHttpBufferingFeature), _identityFunc },
{ typeof(IHttpRequestLifetimeFeature), _identityFunc },
{ typeof(IHttpAuthenticationFeature), _identityFunc },
{ typeof(IHttpRequestIdentifierFeature), _identityFunc },
{ typeof(RequestContext), ctx => ctx.RequestContext },
{ typeof(IHttpMaxRequestBodySizeFeature), _identityFunc },
{ typeof(IHttpBodyControlFeature), _identityFunc },
};
private readonly FeatureContext _featureContext;
static StandardFeatureCollection()
{
if (ComNetOS.IsWin8orLater)
{
// Only add the upgrade feature if it stands a chance of working.
// SignalR uses the presence of the feature to detect feature support.
// https://github.com/aspnet/HttpSysServer/issues/427
_featureFuncLookup[typeof(IHttpUpgradeFeature)] = _identityFunc;
}
}
public StandardFeatureCollection(FeatureContext featureContext)
{
_featureContext = featureContext;
}
public bool IsReadOnly
{
get { return true; }
}
public int Revision
{
get { return 0; }
}
public object this[Type key]
{
get
{
Func<FeatureContext, object> lookupFunc;
_featureFuncLookup.TryGetValue(key, out lookupFunc);
return lookupFunc?.Invoke(_featureContext);
}
set
{
throw new InvalidOperationException("The collection is read-only");
}
}
private static object ReturnIdentity(FeatureContext featureContext)
{
return featureContext;
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<KeyValuePair<Type, object>>)this).GetEnumerator();
}
IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator()
{
foreach (var featureFunc in _featureFuncLookup)
{
var feature = featureFunc.Value(_featureContext);
if (feature != null)
{
yield return new KeyValuePair<Type, object>(featureFunc.Key, feature);
}
}
}
public TFeature Get<TFeature>()
{
return (TFeature)this[typeof(TFeature)];
}
public void Set<TFeature>(TFeature instance)
{
this[typeof(TFeature)] = instance;
}
}
}

View File

@ -0,0 +1,235 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// See the native HTTP_TIMEOUT_LIMIT_INFO structure documentation for additional information.
// http://msdn.microsoft.com/en-us/library/aa364661.aspx
/// <summary>
/// Exposes the Http.Sys timeout configurations. These may also be configured in the registry.
/// </summary>
public sealed class TimeoutManager
{
private static readonly int TimeoutLimitSize =
Marshal.SizeOf<HttpApiTypes.HTTP_TIMEOUT_LIMIT_INFO>();
private UrlGroup _urlGroup;
private int[] _timeouts;
private uint _minSendBytesPerSecond;
internal TimeoutManager()
{
// We have to maintain local state since we allow applications to set individual timeouts. Native Http
// API for setting timeouts expects all timeout values in every call so we have remember timeout values
// to fill in the blanks. Except MinSendBytesPerSecond, local state for remaining five timeouts is
// maintained in timeouts array.
//
// No initialization is required because a value of zero indicates that system defaults should be used.
_timeouts = new int[5];
}
#region Properties
/// <summary>
/// The time, in seconds, allowed for the request entity body to arrive. The default timer is 2 minutes.
///
/// The HTTP Server API turns on this timer when the request has an entity body. The timer expiration is
/// initially set to the configured value. When the HTTP Server API receives additional data indications on the
/// request, it resets the timer to give the connection another interval.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan EntityBody
{
get
{
return GetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.EntityBody);
}
set
{
SetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.EntityBody, value);
}
}
/// <summary>
/// The time, in seconds, allowed for the HTTP Server API to drain the entity body on a Keep-Alive connection.
/// The default timer is 2 minutes.
///
/// On a Keep-Alive connection, after the application has sent a response for a request and before the request
/// entity body has completely arrived, the HTTP Server API starts draining the remainder of the entity body to
/// reach another potentially pipelined request from the client. If the time to drain the remaining entity body
/// exceeds the allowed period the connection is timed out.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan DrainEntityBody
{
get
{
return GetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.DrainEntityBody);
}
set
{
SetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.DrainEntityBody, value);
}
}
/// <summary>
/// The time, in seconds, allowed for the request to remain in the request queue before the application picks
/// it up. The default timer is 2 minutes.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan RequestQueue
{
get
{
return GetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.RequestQueue);
}
set
{
SetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.RequestQueue, value);
}
}
/// <summary>
/// The time, in seconds, allowed for an idle connection. The default timer is 2 minutes.
///
/// This timeout is only enforced after the first request on the connection is routed to the application.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan IdleConnection
{
get
{
return GetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.IdleConnection);
}
set
{
SetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.IdleConnection, value);
}
}
/// <summary>
/// The time, in seconds, allowed for the HTTP Server API to parse the request header. The default timer is
/// 2 minutes.
///
/// This timeout is only enforced after the first request on the connection is routed to the application.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan HeaderWait
{
get
{
return GetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.HeaderWait);
}
set
{
SetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE.HeaderWait, value);
}
}
/// <summary>
/// The minimum send rate, in bytes-per-second, for the response. The default response send rate is 150
/// bytes-per-second.
///
/// Use 0 to indicate that system defaults should be used.
///
/// To disable this timer set it to UInt32.MaxValue
/// </summary>
public long MinSendBytesPerSecond
{
get
{
// Since we maintain local state, GET is local.
return _minSendBytesPerSecond;
}
set
{
// MinSendRate value is ULONG in native layer.
if (value < 0 || value > uint.MaxValue)
{
throw new ArgumentOutOfRangeException("value");
}
SetUrlGroupTimeouts(_timeouts, (uint)value);
_minSendBytesPerSecond = (uint)value;
}
}
#endregion Properties
#region Helpers
private TimeSpan GetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE type)
{
// Since we maintain local state, GET is local.
return new TimeSpan(0, 0, (int)_timeouts[(int)type]);
}
private void SetTimeSpanTimeout(HttpApiTypes.HTTP_TIMEOUT_TYPE type, TimeSpan value)
{
// All timeouts are defined as USHORT in native layer (except MinSendRate, which is ULONG). Make sure that
// timeout value is within range.
var timeoutValue = Convert.ToInt64(value.TotalSeconds);
if (timeoutValue < 0 || timeoutValue > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException("value");
}
// Use local state to get values for other timeouts. Call into the native layer and if that
// call succeeds, update local state.
var newTimeouts = (int[])_timeouts.Clone();
newTimeouts[(int)type] = (int)timeoutValue;
SetUrlGroupTimeouts(newTimeouts, _minSendBytesPerSecond);
_timeouts[(int)type] = (int)timeoutValue;
}
internal void SetUrlGroupTimeouts(UrlGroup urlGroup)
{
_urlGroup = urlGroup;
SetUrlGroupTimeouts(_timeouts, _minSendBytesPerSecond);
}
private unsafe void SetUrlGroupTimeouts(int[] timeouts, uint minSendBytesPerSecond)
{
if (_urlGroup == null)
{
// Not started yet
return;
}
var timeoutinfo = new HttpApiTypes.HTTP_TIMEOUT_LIMIT_INFO();
timeoutinfo.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
timeoutinfo.DrainEntityBody =
(ushort)timeouts[(int)HttpApiTypes.HTTP_TIMEOUT_TYPE.DrainEntityBody];
timeoutinfo.EntityBody =
(ushort)timeouts[(int)HttpApiTypes.HTTP_TIMEOUT_TYPE.EntityBody];
timeoutinfo.RequestQueue =
(ushort)timeouts[(int)HttpApiTypes.HTTP_TIMEOUT_TYPE.RequestQueue];
timeoutinfo.IdleConnection =
(ushort)timeouts[(int)HttpApiTypes.HTTP_TIMEOUT_TYPE.IdleConnection];
timeoutinfo.HeaderWait =
(ushort)timeouts[(int)HttpApiTypes.HTTP_TIMEOUT_TYPE.HeaderWait];
timeoutinfo.MinSendRate = minSendBytesPerSecond;
var infoptr = new IntPtr(&timeoutinfo);
_urlGroup.SetProperty(
HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerTimeoutsProperty,
infoptr, (uint)TimeoutLimitSize);
}
#endregion Helpers
}
}

View File

@ -0,0 +1,170 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNetCore.HttpSys.Internal;
namespace Microsoft.AspNetCore.Server.HttpSys
{
public class UrlPrefix
{
private UrlPrefix(bool isHttps, string scheme, string host, string port, int portValue, string path)
{
IsHttps = isHttps;
Scheme = scheme;
Host = host;
Port = port;
PortValue = portValue;
Path = path;
FullPrefix = string.Format(CultureInfo.InvariantCulture, "{0}://{1}:{2}{3}", Scheme, Host, Port, Path);
}
/// <summary>
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364698(v=vs.85).aspx
/// </summary>
/// <param name="scheme">http or https. Will be normalized to lower case.</param>
/// <param name="host">+, *, IPv4, [IPv6], or a dns name. Http.Sys does not permit punycode (xn--), use Unicode instead.</param>
/// <param name="port">If empty, the default port for the given scheme will be used (80 or 443).</param>
/// <param name="path">Should start and end with a '/', though a missing trailing slash will be added. This value must be un-escaped.</param>
public static UrlPrefix Create(string scheme, string host, string port, string path)
{
int? portValue = null;
if (!string.IsNullOrWhiteSpace(port))
{
portValue = int.Parse(port, NumberStyles.None, CultureInfo.InvariantCulture);
}
return UrlPrefix.Create(scheme, host, portValue, path);
}
/// <summary>
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364698(v=vs.85).aspx
/// </summary>
/// <param name="scheme">http or https. Will be normalized to lower case.</param>
/// <param name="host">+, *, IPv4, [IPv6], or a dns name. Http.Sys does not permit punycode (xn--), use Unicode instead.</param>
/// <param name="portValue">If empty, the default port for the given scheme will be used (80 or 443).</param>
/// <param name="path">Should start and end with a '/', though a missing trailing slash will be added. This value must be un-escaped.</param>
public static UrlPrefix Create(string scheme, string host, int? portValue, string path)
{
bool isHttps;
if (string.Equals(Constants.HttpScheme, scheme, StringComparison.OrdinalIgnoreCase))
{
scheme = Constants.HttpScheme; // Always use a lower case scheme
isHttps = false;
}
else if (string.Equals(Constants.HttpsScheme, scheme, StringComparison.OrdinalIgnoreCase))
{
scheme = Constants.HttpsScheme; // Always use a lower case scheme
isHttps = true;
}
else
{
throw new ArgumentOutOfRangeException("scheme", scheme, Resources.Exception_UnsupportedScheme);
}
if (string.IsNullOrWhiteSpace(host))
{
throw new ArgumentNullException("host");
}
string port;
if (!portValue.HasValue)
{
port = isHttps ? "443" : "80";
portValue = isHttps ? 443 : 80;
}
else
{
port = portValue.Value.ToString(CultureInfo.InvariantCulture);
}
// Http.Sys requires the path end with a slash.
if (string.IsNullOrWhiteSpace(path))
{
path = "/";
}
else if (!path.EndsWith("/", StringComparison.Ordinal))
{
path += "/";
}
return new UrlPrefix(isHttps, scheme, host, port, portValue.Value, path);
}
public static UrlPrefix Create(string prefix)
{
string scheme = null;
string host = null;
int? port = null;
string path = null;
var whole = prefix ?? string.Empty;
var schemeDelimiterEnd = whole.IndexOf("://", StringComparison.Ordinal);
if (schemeDelimiterEnd < 0)
{
throw new FormatException("Invalid prefix, missing scheme separator: " + prefix);
}
var hostDelimiterStart = schemeDelimiterEnd + "://".Length;
var pathDelimiterStart = whole.IndexOf("/", hostDelimiterStart, StringComparison.Ordinal);
if (pathDelimiterStart < 0)
{
pathDelimiterStart = whole.Length;
}
var hostDelimiterEnd = whole.LastIndexOf(":", pathDelimiterStart - 1, pathDelimiterStart - hostDelimiterStart, StringComparison.Ordinal);
if (hostDelimiterEnd < 0)
{
hostDelimiterEnd = pathDelimiterStart;
}
scheme = whole.Substring(0, schemeDelimiterEnd);
var portString = whole.Substring(hostDelimiterEnd, pathDelimiterStart - hostDelimiterEnd); // The leading ":" is included
int portValue;
if (!string.IsNullOrEmpty(portString))
{
var portValueString = portString.Substring(1); // Trim the leading ":"
if (int.TryParse(portValueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out portValue))
{
host = whole.Substring(hostDelimiterStart, hostDelimiterEnd - hostDelimiterStart);
port = portValue;
}
else
{
// This means a port was specified but was invalid or empty.
throw new FormatException("Invalid prefix, invalid port specified: " + prefix);
}
}
else
{
host = whole.Substring(hostDelimiterStart, pathDelimiterStart - hostDelimiterStart);
}
path = whole.Substring(pathDelimiterStart);
return Create(scheme, host, port, path);
}
public bool IsHttps { get; private set; }
public string Scheme { get; private set; }
public string Host { get; private set; }
public string Port { get; private set; }
public int PortValue { get; private set; }
public string Path { get; private set; }
public string FullPrefix { get; private set; }
public override bool Equals(object obj)
{
return string.Equals(FullPrefix, Convert.ToString(obj), StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()
{
return StringComparer.OrdinalIgnoreCase.GetHashCode(FullPrefix);
}
public override string ToString()
{
return FullPrefix;
}
}
}

View File

@ -0,0 +1,162 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Server.HttpSys
{
/// <summary>
/// A collection or URL prefixes
/// </summary>
public class UrlPrefixCollection : ICollection<UrlPrefix>
{
private readonly IDictionary<int, UrlPrefix> _prefixes = new Dictionary<int, UrlPrefix>(1);
private UrlGroup _urlGroup;
private int _nextId = 1;
internal UrlPrefixCollection()
{
}
public int Count
{
get
{
lock (_prefixes)
{
return _prefixes.Count;
}
}
}
public bool IsReadOnly
{
get { return false; }
}
public void Add(string prefix)
{
Add(UrlPrefix.Create(prefix));
}
public void Add(UrlPrefix item)
{
lock (_prefixes)
{
var id = _nextId++;
if (_urlGroup != null)
{
_urlGroup.RegisterPrefix(item.FullPrefix, id);
}
_prefixes.Add(id, item);
}
}
internal UrlPrefix GetPrefix(int id)
{
lock (_prefixes)
{
return _prefixes[id];
}
}
public void Clear()
{
lock (_prefixes)
{
if (_urlGroup != null)
{
UnregisterAllPrefixes();
}
_prefixes.Clear();
}
}
public bool Contains(UrlPrefix item)
{
lock (_prefixes)
{
return _prefixes.Values.Contains(item);
}
}
public void CopyTo(UrlPrefix[] array, int arrayIndex)
{
lock (_prefixes)
{
_prefixes.Values.CopyTo(array, arrayIndex);
}
}
public bool Remove(string prefix)
{
return Remove(UrlPrefix.Create(prefix));
}
public bool Remove(UrlPrefix item)
{
lock (_prefixes)
{
int? id = null;
foreach (var pair in _prefixes)
{
if (pair.Value.Equals(item))
{
id = pair.Key;
if (_urlGroup != null)
{
_urlGroup.UnregisterPrefix(pair.Value.FullPrefix);
}
}
}
if (id.HasValue)
{
_prefixes.Remove(id.Value);
return true;
}
return false;
}
}
public IEnumerator<UrlPrefix> GetEnumerator()
{
lock (_prefixes)
{
return _prefixes.Values.GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal void RegisterAllPrefixes(UrlGroup urlGroup)
{
lock (_prefixes)
{
_urlGroup = urlGroup;
// go through the uri list and register for each one of them
foreach (var pair in _prefixes)
{
// We'll get this index back on each request and use it to look up the prefix to calculate PathBase.
_urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key);
}
}
}
internal void UnregisterAllPrefixes()
{
lock (_prefixes)
{
// go through the uri list and unregister for each one of them
foreach (var prefix in _prefixes.Values)
{
// ignore possible failures
_urlGroup.UnregisterPrefix(prefix.FullPrefix);
}
}
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal static class ValidationHelper
{
public static string ExceptionMessage(Exception exception)
{
if (exception == null)
{
return string.Empty;
}
if (exception.InnerException == null)
{
return exception.Message;
}
return exception.Message + " (" + ExceptionMessage(exception.InnerException) + ")";
}
public static string ToString(object objectValue)
{
if (objectValue == null)
{
return "(null)";
}
else if (objectValue is string && ((string)objectValue).Length == 0)
{
return "(string.empty)";
}
else if (objectValue is Exception)
{
return ExceptionMessage(objectValue as Exception);
}
else if (objectValue is IntPtr)
{
return "0x" + ((IntPtr)objectValue).ToString("x");
}
else
{
return objectValue.ToString();
}
}
public static string HashString(object objectValue)
{
if (objectValue == null)
{
return "(null)";
}
else if (objectValue is string && ((string)objectValue).Length == 0)
{
return "(string.empty)";
}
else
{
return objectValue.GetHashCode().ToString(NumberFormatInfo.InvariantInfo);
}
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Hosting
{
public static class WebHostBuilderHttpSysExtensions
{
/// <summary>
/// Specify HttpSys as the server to be used by the web host.
/// </summary>
/// <param name="hostBuilder">
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
/// </param>
/// <returns>
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
/// </returns>
public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices(services => {
services.AddSingleton<IServer, MessagePump>();
services.AddAuthenticationCore();
});
}
/// <summary>
/// Specify HttpSys as the server to be used by the web host.
/// </summary>
/// <param name="hostBuilder">
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
/// </param>
/// <param name="options">
/// A callback to configure HttpSys options.
/// </param>
/// <returns>
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
/// </returns>
public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder, Action<HttpSysOptions> options)
{
return hostBuilder.UseHttpSys().ConfigureServices(services =>
{
services.Configure(options);
});
}
}
}

View File

@ -0,0 +1,881 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.HttpSys, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderHttpSysExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseHttpSys",
"Parameters": [
{
"Name": "hostBuilder",
"Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
}
],
"ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttpSys",
"Parameters": [
{
"Name": "hostBuilder",
"Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
},
{
"Name": "options",
"Type": "System.Action<Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager",
"Visibility": "Public",
"Kind": "Class",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Schemes",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_Schemes",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_AllowAnonymous",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_AllowAnonymous",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes",
"Visibility": "Public",
"Kind": "Enumeration",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Field",
"Name": "None",
"Parameters": [],
"GenericParameter": [],
"Literal": "0"
},
{
"Kind": "Field",
"Name": "Basic",
"Parameters": [],
"GenericParameter": [],
"Literal": "1"
},
{
"Kind": "Field",
"Name": "NTLM",
"Parameters": [],
"GenericParameter": [],
"Literal": "4"
},
{
"Kind": "Field",
"Name": "Negotiate",
"Parameters": [],
"GenericParameter": [],
"Literal": "8"
},
{
"Kind": "Field",
"Name": "Kerberos",
"Parameters": [],
"GenericParameter": [],
"Literal": "16"
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.Http503VerbosityLevel",
"Visibility": "Public",
"Kind": "Enumeration",
"Sealed": true,
"BaseType": "System.Int64",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Field",
"Name": "Basic",
"Parameters": [],
"GenericParameter": [],
"Literal": "0"
},
{
"Kind": "Field",
"Name": "Limited",
"Parameters": [],
"GenericParameter": [],
"Literal": "1"
},
{
"Kind": "Field",
"Name": "Full",
"Parameters": [],
"GenericParameter": [],
"Literal": "2"
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.HttpSysDefaults",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Field",
"Name": "AuthenticationScheme",
"Parameters": [],
"ReturnType": "System.String",
"Static": true,
"ReadOnly": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.HttpSysException",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "System.ComponentModel.Win32Exception",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_ErrorCode",
"Parameters": [],
"ReturnType": "System.Int32",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_MaxAccepts",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MaxAccepts",
"Parameters": [
{
"Name": "value",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_EnableResponseCaching",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_EnableResponseCaching",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_UrlPrefixes",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefixCollection",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Authentication",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Timeouts",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.TimeoutManager",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ThrowWriteExceptions",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ThrowWriteExceptions",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_MaxConnections",
"Parameters": [],
"ReturnType": "System.Nullable<System.Int64>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MaxConnections",
"Parameters": [
{
"Name": "value",
"Type": "System.Nullable<System.Int64>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_RequestQueueLimit",
"Parameters": [],
"ReturnType": "System.Int64",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_RequestQueueLimit",
"Parameters": [
{
"Name": "value",
"Type": "System.Int64"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_MaxRequestBodySize",
"Parameters": [],
"ReturnType": "System.Nullable<System.Int64>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MaxRequestBodySize",
"Parameters": [
{
"Name": "value",
"Type": "System.Nullable<System.Int64>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_AllowSynchronousIO",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_AllowSynchronousIO",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Http503Verbosity",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.Http503VerbosityLevel",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_Http503Verbosity",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Server.HttpSys.Http503VerbosityLevel"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.TimeoutManager",
"Visibility": "Public",
"Kind": "Class",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_EntityBody",
"Parameters": [],
"ReturnType": "System.TimeSpan",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_EntityBody",
"Parameters": [
{
"Name": "value",
"Type": "System.TimeSpan"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_DrainEntityBody",
"Parameters": [],
"ReturnType": "System.TimeSpan",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_DrainEntityBody",
"Parameters": [
{
"Name": "value",
"Type": "System.TimeSpan"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_RequestQueue",
"Parameters": [],
"ReturnType": "System.TimeSpan",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_RequestQueue",
"Parameters": [
{
"Name": "value",
"Type": "System.TimeSpan"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_IdleConnection",
"Parameters": [],
"ReturnType": "System.TimeSpan",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_IdleConnection",
"Parameters": [
{
"Name": "value",
"Type": "System.TimeSpan"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_HeaderWait",
"Parameters": [],
"ReturnType": "System.TimeSpan",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_HeaderWait",
"Parameters": [
{
"Name": "value",
"Type": "System.TimeSpan"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_MinSendBytesPerSecond",
"Parameters": [],
"ReturnType": "System.Int64",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MinSendBytesPerSecond",
"Parameters": [
{
"Name": "value",
"Type": "System.Int64"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "scheme",
"Type": "System.String"
},
{
"Name": "host",
"Type": "System.String"
},
{
"Name": "port",
"Type": "System.String"
},
{
"Name": "path",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "scheme",
"Type": "System.String"
},
{
"Name": "host",
"Type": "System.String"
},
{
"Name": "portValue",
"Type": "System.Nullable<System.Int32>"
},
{
"Name": "path",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "prefix",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_IsHttps",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Scheme",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Host",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Port",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_PortValue",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Path",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_FullPrefix",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Equals",
"Parameters": [
{
"Name": "obj",
"Type": "System.Object"
}
],
"ReturnType": "System.Boolean",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetHashCode",
"Parameters": [],
"ReturnType": "System.Int32",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "ToString",
"Parameters": [],
"ReturnType": "System.String",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefixCollection",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>"
],
"Members": [
{
"Kind": "Method",
"Name": "GetEnumerator",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IEnumerator<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Count",
"Parameters": [],
"ReturnType": "System.Int32",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_IsReadOnly",
"Parameters": [],
"ReturnType": "System.Boolean",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Add",
"Parameters": [
{
"Name": "prefix",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Add",
"Parameters": [
{
"Name": "item",
"Type": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix"
}
],
"ReturnType": "System.Void",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Clear",
"Parameters": [],
"ReturnType": "System.Void",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Contains",
"Parameters": [
{
"Name": "item",
"Type": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix"
}
],
"ReturnType": "System.Boolean",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CopyTo",
"Parameters": [
{
"Name": "array",
"Type": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix[]"
},
{
"Name": "arrayIndex",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Remove",
"Parameters": [
{
"Name": "prefix",
"Type": "System.String"
}
],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Remove",
"Parameters": [
{
"Name": "item",
"Type": "Microsoft.AspNetCore.Server.HttpSys.UrlPrefix"
}
],
"ReturnType": "System.Boolean",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "System.Collections.Generic.ICollection<Microsoft.AspNetCore.Server.HttpSys.UrlPrefix>",
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,18 @@
<Project>
<Import Project="..\Directory.Build.props" />
<PropertyGroup>
<DeveloperBuildTestTfms>netcoreapp2.1</DeveloperBuildTestTfms>
<StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms);netcoreapp2.0</StandardTestTfms>
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,383 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.HttpSys
{
public class AuthenticationTests
{
private static bool AllowAnoymous = true;
private static bool DenyAnoymous = false;
[ConditionalTheory]
[InlineData(AuthenticationSchemes.None)]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_AllowAnonymous_NoChallenge(AuthenticationSchemes authType)
{
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Empty(response.Headers.WwwAuthenticate);
}
}
#if !NETCOREAPP2_0
// https://github.com/aspnet/ServerTests/issues/82
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
[InlineData(AuthenticationSchemes.Basic)]
public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationSchemes authType)
{
using (var server = Utilities.CreateDynamicHost(authType, DenyAnoymous, out var address, httpContext =>
{
throw new NotImplementedException();
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
[InlineData(AuthenticationSchemes.Basic)]
public async Task AuthType_AllowAnonymousButSpecify401_ChallengesAdded(AuthenticationSchemes authType)
{
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
httpContext.Response.StatusCode = 401;
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[ConditionalFact]
public async Task MultipleAuthTypes_AllowAnonymousButSpecify401_ChallengesAdded()
{
string address;
using (Utilities.CreateHttpAuthServer(
AuthenticationSchemes.Negotiate
| AuthenticationSchemes.NTLM
/* | AuthenticationSchemes.Digest TODO: Not implemented */
| AuthenticationSchemes.Basic,
true,
out address,
httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
httpContext.Response.StatusCode = 401;
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal("Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
#endif
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationSchemes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /* AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationSchemes authType)
{
int requestId = 0;
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
if (requestId == 0)
{
Assert.False(httpContext.User.Identity.IsAuthenticated);
httpContext.Response.StatusCode = 401;
}
else if (requestId == 1)
{
Assert.True(httpContext.User.Identity.IsAuthenticated);
}
else
{
throw new NotImplementedException();
}
requestId++;
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, useDefaultCredentials: true);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationSchemes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /* AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_RequireAuth_Success(AuthenticationSchemes authType)
{
using (var server = Utilities.CreateDynamicHost(authType, DenyAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.True(httpContext.User.Identity.IsAuthenticated);
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, useDefaultCredentials: true);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
// https://github.com/aspnet/Logging/issues/543#issuecomment-321907828
[ConditionalFact]
public async Task AuthTypes_AccessUserInOnCompleted_Success()
{
var completed = new ManualResetEvent(false);
string userName = null;
var authTypes = AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM;
using (var server = Utilities.CreateDynamicHost(authTypes, DenyAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.True(httpContext.User.Identity.IsAuthenticated);
httpContext.Response.OnCompleted(() =>
{
userName = httpContext.User.Identity.Name;
completed.Set();
return Task.FromResult(0);
});
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(address, useDefaultCredentials: true);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.True(completed.WaitOne(TimeSpan.FromSeconds(5)));
Assert.False(string.IsNullOrEmpty(userName));
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_AuthenticateWithNoUser_NoResults(AuthenticationSchemes authType)
{
var authTypeList = authType.ToString().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, async httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
var authResults = await httpContext.AuthenticateAsync(HttpSysDefaults.AuthenticationScheme);
Assert.False(authResults.Succeeded);
Assert.True(authResults.None);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Empty(response.Headers.WwwAuthenticate);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
// [InlineData(AuthenticationSchemes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_AuthenticateWithUser_OneResult(AuthenticationSchemes authType)
{
using (var server = Utilities.CreateDynamicHost(authType, DenyAnoymous, out var address, async httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.True(httpContext.User.Identity.IsAuthenticated);
var authResults = await httpContext.AuthenticateAsync(HttpSysDefaults.AuthenticationScheme);
Assert.True(authResults.Succeeded);
}))
{
var response = await SendRequestAsync(address, useDefaultCredentials: true);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
#if !NETCOREAPP2_0
// https://github.com/aspnet/ServerTests/issues/82
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_ChallengeWithoutAuthTypes_AllChallengesSent(AuthenticationSchemes authType)
{
var authTypeList = authType.ToString().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
return httpContext.ChallengeAsync(HttpSysDefaults.AuthenticationScheme);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authTypeList.Count(), response.Headers.WwwAuthenticate.Count);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_ChallengeWithAllAuthTypes_AllChallengesSent(AuthenticationSchemes authType)
{
var authTypeList = authType.ToString().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, async httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
await httpContext.ChallengeAsync(HttpSysDefaults.AuthenticationScheme);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authTypeList.Count(), response.Headers.WwwAuthenticate.Count);
}
}
[ConditionalFact]
public async Task AuthTypes_OneChallengeSent()
{
var authTypes = AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic;
using (var server = Utilities.CreateDynamicHost(authTypes, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
return httpContext.ChallengeAsync(HttpSysDefaults.AuthenticationScheme);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(3, response.Headers.WwwAuthenticate.Count);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.NTLM | AuthenticationSchemes.Basic)]
public async Task AuthTypes_ChallengeWillAskForAllEnabledSchemes(AuthenticationSchemes authType)
{
var authTypeList = authType.ToString().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
using (var server = Utilities.CreateDynamicHost(authType, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
return httpContext.ChallengeAsync(HttpSysDefaults.AuthenticationScheme);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authTypeList.Count(), response.Headers.WwwAuthenticate.Count);
}
}
#endif
[ConditionalFact]
public async Task AuthTypes_Forbid_Forbidden()
{
var authTypes = AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic;
using (var server = Utilities.CreateDynamicHost(authTypes, AllowAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.False(httpContext.User.Identity.IsAuthenticated);
return httpContext.ForbidAsync(HttpSysDefaults.AuthenticationScheme);
}))
{
var response = await SendRequestAsync(address);
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
Assert.Empty(response.Headers.WwwAuthenticate);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // Not implemented
// [InlineData(AuthenticationSchemes.Basic)] // Can't log in with UseDefaultCredentials
public async Task AuthTypes_UnathorizedAuthenticatedAuthType_Unauthorized(AuthenticationSchemes authType)
{
using (var server = Utilities.CreateDynamicHost(authType, DenyAnoymous, out var address, httpContext =>
{
Assert.NotNull(httpContext.User);
Assert.NotNull(httpContext.User.Identity);
Assert.True(httpContext.User.Identity.IsAuthenticated);
return httpContext.ChallengeAsync(HttpSysDefaults.AuthenticationScheme, null);
}))
{
var response = await SendRequestAsync(address, useDefaultCredentials: true);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Single(response.Headers.WwwAuthenticate);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.First().Scheme);
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
{
HttpClientHandler handler = new HttpClientHandler();
handler.UseDefaultCredentials = useDefaultCredentials;
using (HttpClient client = new HttpClient(handler))
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class DummyApplication : IHttpApplication<HttpContext>
{
private readonly RequestDelegate _requestDelegate;
public DummyApplication() : this(context => Task.CompletedTask) { }
public DummyApplication(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
}
public HttpContext CreateContext(IFeatureCollection contextFeatures)
{
return new DefaultHttpContext(contextFeatures);
}
public void DisposeContext(HttpContext httpContext, Exception exception)
{
}
public async Task ProcessRequestAsync(HttpContext httpContext)
{
await _requestDelegate(httpContext);
}
}
}

View File

@ -0,0 +1,166 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.HttpSys
{
public class HttpsTests
{
private const string Address = "https://localhost:9090/";
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_200OK_Success()
{
using (Utilities.CreateHttpsServer(httpContext =>
{
return Task.FromResult(0);
}))
{
string response = await SendRequestAsync(Address);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_SendHelloWorld_Success()
{
using (Utilities.CreateHttpsServer(httpContext =>
{
byte[] body = Encoding.UTF8.GetBytes("Hello World");
httpContext.Response.ContentLength = body.Length;
return httpContext.Response.Body.WriteAsync(body, 0, body.Length);
}))
{
string response = await SendRequestAsync(Address);
Assert.Equal("Hello World", response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_EchoHelloWorld_Success()
{
using (Utilities.CreateHttpsServer(httpContext =>
{
string input = new StreamReader(httpContext.Request.Body).ReadToEnd();
Assert.Equal("Hello World", input);
byte[] body = Encoding.UTF8.GetBytes("Hello World");
httpContext.Response.ContentLength = body.Length;
httpContext.Response.Body.Write(body, 0, body.Length);
return Task.FromResult(0);
}))
{
string response = await SendRequestAsync(Address, "Hello World");
Assert.Equal("Hello World", response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_ClientCertNotSent_ClientCertNotPresent()
{
using (Utilities.CreateHttpsServer(async httpContext =>
{
var tls = httpContext.Features.Get<ITlsConnectionFeature>();
Assert.NotNull(tls);
var cert = await tls.GetClientCertificateAsync(CancellationToken.None);
Assert.Null(cert);
Assert.Null(tls.ClientCertificate);
}))
{
string response = await SendRequestAsync(Address);
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_ClientCertRequested_ClientCertPresent()
{
using (Utilities.CreateHttpsServer(async httpContext =>
{
var tls = httpContext.Features.Get<ITlsConnectionFeature>();
Assert.NotNull(tls);
var cert = await tls.GetClientCertificateAsync(CancellationToken.None);
Assert.NotNull(cert);
Assert.NotNull(tls.ClientCertificate);
}))
{
X509Certificate2 cert = FindClientCert();
Assert.NotNull(cert);
string response = await SendRequestAsync(Address, cert);
Assert.Equal(string.Empty, response);
}
}
private async Task<string> SendRequestAsync(string uri,
X509Certificate cert = null)
{
var handler = new WinHttpHandler();
handler.ServerCertificateValidationCallback = (a, b, c, d) => true;
if (cert != null)
{
handler.ClientCertificates.Add(cert);
}
using (HttpClient client = new HttpClient(handler))
{
return await client.GetStringAsync(uri);
}
}
private async Task<string> SendRequestAsync(string uri, string upload)
{
var handler = new WinHttpHandler();
handler.ServerCertificateValidationCallback = (a, b, c, d) => true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(upload));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private X509Certificate2 FindClientCert()
{
var store = new X509Store();
store.Open(OpenFlags.ReadOnly);
foreach (var cert in store.Certificates)
{
bool isClientAuth = false;
bool isSmartCard = false;
foreach (var extension in cert.Extensions)
{
var eku = extension as X509EnhancedKeyUsageExtension;
if (eku != null)
{
foreach (var oid in eku.EnhancedKeyUsages)
{
if (oid.FriendlyName == "Client Authentication")
{
isClientAuth = true;
}
else if (oid.FriendlyName == "Smart Card Logon")
{
isSmartCard = true;
break;
}
}
}
}
if (isClientAuth && !isSmartCard)
{
return cert;
}
}
return null;
}
}
}

View File

@ -0,0 +1,220 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.HttpSys.Listener
{
public class AuthenticationTests
{
private static bool AllowAnoymous = true;
private static bool DenyAnoymous = false;
[ConditionalTheory]
[InlineData(AuthenticationSchemes.None)]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_AllowAnonymous_NoChallenge(AuthenticationSchemes authType)
{
string address;
using (var server = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
Assert.Equal(authType, context.Response.AuthenticationChallenges);
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Empty(response.Headers.WwwAuthenticate);
}
}
#if !NETCOREAPP2_0
// https://github.com/aspnet/ServerTests/issues/82
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
[InlineData(AuthenticationSchemes.Basic)]
public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationSchemes authType)
{
string address;
using (var server = Utilities.CreateHttpAuthServer(authType, DenyAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var contextTask = server.AcceptAsync(Utilities.DefaultTimeout); // Fails when the server shuts down, the challenge happens internally.
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
[InlineData(AuthenticationSchemes.Basic)]
public async Task AuthType_AllowAnonymousButSpecify401_ChallengesAdded(AuthenticationSchemes authType)
{
string address;
using (var server = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
Assert.Equal(authType, context.Response.AuthenticationChallenges);
context.Response.StatusCode = 401;
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[ConditionalFact]
public async Task MultipleAuthTypes_AllowAnonymousButSpecify401_ChallengesAdded()
{
string address;
AuthenticationSchemes authType =
AuthenticationSchemes.Negotiate
| AuthenticationSchemes.NTLM
/* | AuthenticationSchemes.Digest TODO: Not implemented */
| AuthenticationSchemes.Basic;
using (var server = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
Assert.Equal(authType, context.Response.AuthenticationChallenges);
context.Response.StatusCode = 401;
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal("Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
#endif
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationSchemes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationType.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationSchemes authType)
{
string address;
using (var server = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address, useDefaultCredentials: true);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
Assert.Equal(authType, context.Response.AuthenticationChallenges);
context.Response.StatusCode = 401;
context.Dispose();
context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.True(context.User.Identity.IsAuthenticated);
Assert.Equal(authType, context.Response.AuthenticationChallenges);
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[ConditionalTheory]
[InlineData(AuthenticationSchemes.Negotiate)]
[InlineData(AuthenticationSchemes.NTLM)]
// [InlineData(AuthenticationSchemes.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationSchemes.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationType.Digest |*/ AuthenticationSchemes.Basic)]
public async Task AuthTypes_RequireAuth_Success(AuthenticationSchemes authType)
{
string address;
using (var server = Utilities.CreateHttpAuthServer(authType, DenyAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address, useDefaultCredentials: true);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.True(context.User.Identity.IsAuthenticated);
Assert.Equal(authType, context.Response.AuthenticationChallenges);
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[ConditionalFact(Skip = "Requires a domain joined machine - https://github.com/aspnet/HttpSysServer/issues/357")]
public async Task AuthTypes_RequireKerberosAuth_Success()
{
string address;
using (var server = Utilities.CreateHttpAuthServer(AuthenticationSchemes.Kerberos, DenyAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address, useDefaultCredentials: true);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.True(context.User.Identity.IsAuthenticated);
Assert.Equal(AuthenticationSchemes.Kerberos, context.Response.AuthenticationChallenges);
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[ConditionalFact(Skip = "Requires a domain joined machine - https://github.com/aspnet/HttpSysServer/issues/357")]
public async Task MultipleAuthTypes_KerberosAllowAnonymousButSpecify401_ChallengesAdded()
{
string address;
using (var server = Utilities.CreateHttpAuthServer(AuthenticationSchemes.Kerberos, AllowAnoymous, out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.NotNull(context.User);
Assert.False(context.User.Identity.IsAuthenticated);
Assert.Equal(AuthenticationSchemes.Kerberos, context.Response.AuthenticationChallenges);
context.Response.StatusCode = 401;
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal("Kerberos", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
{
HttpClientHandler handler = new HttpClientHandler();
handler.UseDefaultCredentials = useDefaultCredentials;
using (HttpClient client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(5) })
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,172 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.HttpSys.Listener
{
public class HttpsTests
{
// Note these tests can't use dynamic ports or run concurrently because the ssl cert must be pre-registered with a specific port.
private const string Address = "https://localhost:9090/";
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_200OK_Success()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_SendHelloWorld_Success()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
byte[] body = Encoding.UTF8.GetBytes("Hello World");
context.Response.ContentLength = body.Length;
await context.Response.Body.WriteAsync(body, 0, body.Length);
context.Dispose();
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_EchoHelloWorld_Success()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
string input = new StreamReader(context.Request.Body).ReadToEnd();
Assert.Equal("Hello World", input);
context.Response.ContentLength = 11;
var writer = new StreamWriter(context.Response.Body);
await writer.WriteAsync("Hello World");
await writer.FlushAsync();
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_ClientCertNotSent_ClientCertNotPresent()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
var cert = await context.Request.GetClientCertificateAsync();
Assert.Null(cert);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_ClientCertRequested_ClientCertPresent()
{
using (var server = Utilities.CreateHttpsServer())
{
X509Certificate2 clientCert = FindClientCert();
Assert.NotNull(clientCert);
Task<string> responseTask = SendRequestAsync(Address, clientCert);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
var cert = await context.Request.GetClientCertificateAsync();
Assert.NotNull(cert);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
private async Task<string> SendRequestAsync(string uri,
X509Certificate cert = null)
{
WinHttpHandler handler = new WinHttpHandler();
handler.ServerCertificateValidationCallback = (a, b, c, d) => true;
if (cert != null)
{
handler.ClientCertificates.Add(cert);
}
using (HttpClient client = new HttpClient(handler))
{
return await client.GetStringAsync(uri);
}
}
private async Task<string> SendRequestAsync(string uri, string upload)
{
WinHttpHandler handler = new WinHttpHandler();
handler.ServerCertificateValidationCallback = (a, b, c, d) => true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(upload));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private X509Certificate2 FindClientCert()
{
var store = new X509Store();
store.Open(OpenFlags.ReadOnly);
foreach (var cert in store.Certificates)
{
bool isClientAuth = false;
bool isSmartCard = false;
foreach (var extension in cert.Extensions)
{
var eku = extension as X509EnhancedKeyUsageExtension;
if (eku != null)
{
foreach (var oid in eku.EnhancedKeyUsages)
{
if (oid.FriendlyName == "Client Authentication")
{
isClientAuth = true;
}
else if (oid.FriendlyName == "Smart Card Logon")
{
isSmartCard = true;
break;
}
}
}
}
if (isClientAuth && !isSmartCard)
{
return cert;
}
}
return null;
}
}
}

View File

@ -0,0 +1,217 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.HttpSys.Listener
{
public class OpaqueUpgradeTests
{
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
public async Task OpaqueUpgrade_AfterHeadersSent_Throws()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> clientTask = SendRequestAsync(address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
byte[] body = Encoding.UTF8.GetBytes("Hello World");
await context.Response.Body.WriteAsync(body, 0, body.Length);
Assert.Throws<InvalidOperationException>(() => context.Response.Headers["Upgrade"] = "WebSocket"); // Win8.1 blocks anything but WebSocket
await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.UpgradeAsync());
context.Dispose();
HttpResponseMessage response = await clientTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
}
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
public async Task OpaqueUpgrade_GetUpgrade_Success()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<Stream> clientTask = SendOpaqueRequestAsync("GET", address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.True(context.IsUpgradableRequest);
context.Response.Headers["Upgrade"] = "WebSocket"; // Win8.1 blocks anything but WebSocket
Stream serverStream = await context.UpgradeAsync();
Assert.True(serverStream.CanRead);
Assert.True(serverStream.CanWrite);
Stream clientStream = await clientTask;
serverStream.Dispose();
context.Dispose();
clientStream.Dispose();
}
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
// See HTTP_VERB for known verbs
[InlineData("UNKNOWN", null)]
[InlineData("INVALID", null)]
[InlineData("OPTIONS", null)]
[InlineData("GET", null)]
[InlineData("HEAD", null)]
[InlineData("DELETE", null)]
[InlineData("TRACE", null)]
[InlineData("CONNECT", null)]
[InlineData("TRACK", null)]
[InlineData("MOVE", null)]
[InlineData("COPY", null)]
[InlineData("PROPFIND", null)]
[InlineData("PROPPATCH", null)]
[InlineData("MKCOL", null)]
[InlineData("LOCK", null)]
[InlineData("UNLOCK", null)]
[InlineData("SEARCH", null)]
[InlineData("CUSTOMVERB", null)]
[InlineData("PATCH", null)]
[InlineData("POST", "Content-Length: 0")]
[InlineData("PUT", "Content-Length: 0")]
public async Task OpaqueUpgrade_VariousMethodsUpgradeSendAndReceive_Success(string method, string extraHeader)
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<Stream> clientTask = SendOpaqueRequestAsync(method, address, extraHeader);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.True(context.IsUpgradableRequest);
context.Response.Headers["Upgrade"] = "WebSocket"; // Win8.1 blocks anything but WebSocket
Stream serverStream = await context.UpgradeAsync();
Stream clientStream = await clientTask;
byte[] clientBuffer = new byte[] { 0x00, 0x01, 0xFF, 0x00, 0x00 };
await clientStream.WriteAsync(clientBuffer, 0, 3);
byte[] serverBuffer = new byte[clientBuffer.Length];
int read = await serverStream.ReadAsync(serverBuffer, 0, serverBuffer.Length);
Assert.Equal(clientBuffer, serverBuffer);
await serverStream.WriteAsync(serverBuffer, 0, read);
byte[] clientEchoBuffer = new byte[clientBuffer.Length];
read = await clientStream.ReadAsync(clientEchoBuffer, 0, clientEchoBuffer.Length);
Assert.Equal(clientBuffer, clientEchoBuffer);
serverStream.Dispose();
context.Dispose();
clientStream.Dispose();
}
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
// Http.Sys returns a 411 Length Required if PUT or POST does not specify content-length or chunked.
[InlineData("POST", "Content-Length: 10")]
[InlineData("POST", "Transfer-Encoding: chunked")]
[InlineData("PUT", "Content-Length: 10")]
[InlineData("PUT", "Transfer-Encoding: chunked")]
[InlineData("CUSTOMVERB", "Content-Length: 10")]
[InlineData("CUSTOMVERB", "Transfer-Encoding: chunked")]
public async Task OpaqueUpgrade_InvalidMethodUpgrade_Disconnected(string method, string extraHeader)
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
var clientTask = SendOpaqueRequestAsync(method, address, extraHeader);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
Assert.False(context.IsUpgradableRequest);
context.Dispose();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await clientTask);
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetAsync(uri);
}
}
// Returns a bidirectional opaque stream or throws if the upgrade fails
private async Task<Stream> SendOpaqueRequestAsync(string method, string address, string extraHeader = null)
{
// Connect with a socket
Uri uri = new Uri(address);
TcpClient client = new TcpClient();
try
{
await client.ConnectAsync(uri.Host, uri.Port);
NetworkStream stream = client.GetStream();
// Send an HTTP GET request
byte[] requestBytes = BuildGetRequest(method, uri, extraHeader);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
// Read the response headers, fail if it's not a 101
await ParseResponseAsync(stream);
// Return the opaque network stream
return stream;
}
catch (Exception)
{
((IDisposable)client).Dispose();
throw;
}
}
private byte[] BuildGetRequest(string method, Uri uri, string extraHeader)
{
StringBuilder builder = new StringBuilder();
builder.Append(method);
builder.Append(" ");
builder.Append(uri.PathAndQuery);
builder.Append(" HTTP/1.1");
builder.AppendLine();
builder.Append("Host: ");
builder.Append(uri.Host);
builder.Append(':');
builder.Append(uri.Port);
builder.AppendLine();
if (!string.IsNullOrEmpty(extraHeader))
{
builder.AppendLine(extraHeader);
}
builder.AppendLine();
return Encoding.ASCII.GetBytes(builder.ToString());
}
// Read the response headers, fail if it's not a 101
private async Task ParseResponseAsync(NetworkStream stream)
{
StreamReader reader = new StreamReader(stream);
string statusLine = await reader.ReadLineAsync();
string[] parts = statusLine.Split(' ');
if (int.Parse(parts[1]) != 101)
{
throw new InvalidOperationException("The response status code was incorrect: " + statusLine);
}
// Scan to the end of the headers
while (!string.IsNullOrEmpty(reader.ReadLine()))
{
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More