Merge remote-tracking branch 'HttpSysServer/rybrande/release21ToSrc' into rybrande/Mondo2.1
This commit is contained in:
commit
5c0097bf69
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"adx-nonshipping": {
|
||||
"rules": [],
|
||||
"packages": {
|
||||
"Microsoft.AspNetCore.HttpSys.Sources": {}
|
||||
}
|
||||
},
|
||||
"Default": {
|
||||
"rules": [
|
||||
"DefaultCompositeRule"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
HttpSysServer
|
||||
=================
|
||||
|
||||
| AppVeyor | Travis |
|
||||
| ---- | ----
|
||||
| [](https://ci.appveyor.com/project/aspnetci/HttpSysServer/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.
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"profiles": {
|
||||
"HotAddSample": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:12345",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_URLS": "http://localhost:12345",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<gcServer enabled="true"/>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"profiles": {
|
||||
"SelfHostServer": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:5000/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
206
src/HttpSysServer/src/Microsoft.AspNetCore.Server.HttpSys/Properties/Resources.Designer.cs
generated
Normal file
206
src/HttpSysServer/src/Microsoft.AspNetCore.Server.HttpSys/Properties/Resources.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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#>);
|
||||
}
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue