diff --git a/.gitattributes b/.gitattributes index 97b827b758..62fe7c511d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -42,6 +42,7 @@ *.fs text=auto *.fsx text=auto *.hs text=auto +*.rc text=auto *.csproj text=auto *.vbproj text=auto diff --git a/.gitignore b/.gitignore index 79ab4fd067..5004d81d9d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,10 +25,34 @@ project.lock.json *.ncrunchsolution *.*sdf *.ipch -*.vs/ .vscode/ -.testPublish/ -.build/ *.nuget.props *.nuget.targets +*.bin +*.vs/ +.testPublish/ + +*.obj +*.tlog +*.CppClean.log +*msbuild.log + +src/*/Debug/ +src/*/x64/Debug/ +src/*/Release/ +src/*/x64/Release/ +x64/ + +*vcxproj.filters +*.aps +*.pdb +*.lib +*.idb + +src/AspNetCore/aspnetcore_msg.h +src/AspNetCore/aspnetcore_msg.rc +src/AspNetCore/version.h +.build + +*.VC.*db global.json diff --git a/Directory.Build.props b/Directory.Build.props index b0344ff39a..46a1159f78 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/IISIntegration.sln b/IISIntegration.sln index 75d59c5d46..af31823288 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.10 +VisualStudioVersion = 15.0.27130.2010 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04B1EDB6-E967-4D25-89B9-E6F8304038CD}" ProjectSection(SolutionItems) = preProject @@ -18,6 +18,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EF30B533-D715-421A-92B7-92FEF460AC9C}" ProjectSection(SolutionItems) = preProject test\Directory.Build.props = test\Directory.Build.props + test\WebSocketClientEXE\WebSocketClientEXE.csproj = test\WebSocketClientEXE\WebSocketClientEXE.csproj EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9}" @@ -45,6 +46,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISTestSite", "test\IISTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISIntegration.IISServerFunctionalTests", "test\IISIntegration.IISServerFunctionalTests\IISIntegration.IISServerFunctionalTests.csproj", "{C745BBFD-7888-4038-B41B-6D1832D13878}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandler", "src\RequestHandler\RequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreModule.Test", "test\AspNetCoreModule.Test\AspNetCoreModule.Test.csproj", "{5F31656B-4990-44FE-9090-00E32D933376}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreModule.TestSites.Standard", "test\AspNetCoreModule.TestSites.Standard\AspNetCoreModule.TestSites.Standard.csproj", "{93ECA06C-767E-4A4D-AC59-21F250381297}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -150,6 +163,70 @@ Global {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|x64.ActiveCfg = Release|Any CPU {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|x64.Build.0 = Release|Any CPU {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|x86.ActiveCfg = Release|Any CPU + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.ActiveCfg = Debug|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Debug|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.Build.0 = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Any CPU.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x86.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x86.Build.0 = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.ActiveCfg = Debug|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.Build.0 = Debug|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.Build.0 = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Any CPU.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x86.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x86.Build.0 = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.ActiveCfg = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.Build.0 = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.Build.0 = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.ActiveCfg = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.Build.0 = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.Build.0 = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.ActiveCfg = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.Build.0 = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.Build.0 = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Any CPU.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.Build.0 = Release|Win32 + {5F31656B-4990-44FE-9090-00E32D933376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Debug|x64.ActiveCfg = Debug|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Debug|x64.Build.0 = Debug|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Debug|x86.Build.0 = Debug|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Release|Any CPU.Build.0 = Release|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Release|x64.ActiveCfg = Release|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Release|x64.Build.0 = Release|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Release|x86.ActiveCfg = Release|Any CPU + {5F31656B-4990-44FE-9090-00E32D933376}.Release|x86.Build.0 = Release|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Debug|x64.ActiveCfg = Debug|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Debug|x64.Build.0 = Debug|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Debug|x86.ActiveCfg = Debug|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Debug|x86.Build.0 = Debug|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Release|Any CPU.Build.0 = Release|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Release|x64.ActiveCfg = Release|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Release|x64.Build.0 = Release|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Release|x86.ActiveCfg = Release|Any CPU + {93ECA06C-767E-4A4D-AC59-21F250381297}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -163,6 +240,12 @@ Global {9BC4AFCB-325D-4C81-8228-8CF301CE2F97} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} {679FA2A2-898B-4320-884E-C2D294A97CE1} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {C745BBFD-7888-4038-B41B-6D1832D13878} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {439824F9-1455-4CC4-BD79-B44FA0A16552} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {D57EA297-6DC2-4BC0-8C91-334863327863} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {5F31656B-4990-44FE-9090-00E32D933376} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {93ECA06C-767E-4A4D-AC59-21F250381297} = {EF30B533-D715-421A-92B7-92FEF460AC9C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/LICENSE.txt b/LICENSE.txt index 7b2956ecee..d50cef4a3f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -12,3 +12,27 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +ASP.NET Core Module + +Copyright (c) .NET Foundation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the ""Software""), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index b153ab1515..ceddc22350 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -1,4 +1,10 @@ { + "adx-nonshipping": { + "rules": [], + "packages": { + "Microsoft.AspNetCore.AspNetCoreModule": {} + } + }, "Default": { "rules": [ "DefaultCompositeRule" diff --git a/README.md b/README.md index dcfdd28a9b..f9310e5d9e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -ASP.NET Core IIS Integration -======== - -This repo hosts the ASP.NET Core middleware for IIS integration. +This repo hosts the ASP.NET Core middleware for IIS integration and the ASP.NET Core Module. 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. diff --git a/build/Build.Settings b/build/Build.Settings new file mode 100644 index 0000000000..371ec56452 --- /dev/null +++ b/build/Build.Settings @@ -0,0 +1,87 @@ + + + + $(MSBuildThisFileDirectory)..\ + Debug + Win32 + v120 + v140 + v120 + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + $(OutputPath) + aspnetcore + + + + true + false + + + + false + true + + + + + Use + true + true + true + + + Windows + true + $(OutDir)$(TargetName).pdb + $(OutDir)$(TargetName).pub.pdb + true + true + true + + + + + + Disabled + + + + + + MaxSpeed + true + true + + + + + + WIN32;_DEBUG;%(PreprocessorDefinitions) + + + + + + _WIN64;_DEBUG;%(PreprocessorDefinitions) + + + + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + true + + + + + + _WIN64;NDEBUG;%(PreprocessorDefinitions) + + + true + + + + \ No newline at end of file diff --git a/build/Config.Definitions.Props b/build/Config.Definitions.Props new file mode 100644 index 0000000000..974816b5d7 --- /dev/null +++ b/build/Config.Definitions.Props @@ -0,0 +1,21 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + \ No newline at end of file diff --git a/build/build.msbuild b/build/build.msbuild new file mode 100644 index 0000000000..c731cd9ff8 --- /dev/null +++ b/build/build.msbuild @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/dependencies.props b/build/dependencies.props index 9b362cb4f1..2dca7d0862 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,35 +1,41 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.1.0-preview1-15626 + 2.1.0-preview1-15638 + 2.1.0-preview1-27867 1.0.0-pre-10223 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 0.5.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 - 2.1.0-preview1-27849 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 0.5.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 2.1.0-preview1-27867 + 1.1.0 + 2.1.0-preview1-27867 2.0.0 2.1.0-preview1-26016-05 + 2.1.0-preview1-27867 15.3.0 + 11.0.0 4.5.0-preview1-26016-05 0.1.0-e171215-1 + 6.1.7601.17515 4.5.0-preview1-26016-05 4.5.0-preview1-26016-05 4.5.0-preview1-26016-05 diff --git a/build/repo.props b/build/repo.props index 160117a416..7421f4691d 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,4 +1,4 @@ - + @@ -6,7 +6,11 @@ + + + + Internal.AspNetCore.Universe.Lineup diff --git a/build/repo.targets b/build/repo.targets new file mode 100644 index 0000000000..04c26d53da --- /dev/null +++ b/build/repo.targets @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/sources.props b/build/sources.props index 9feff29d09..05a4b7dc2e 100644 --- a/build/sources.props +++ b/build/sources.props @@ -1,4 +1,4 @@ - + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 8d52a6128c..2e540bdffd 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview1-15626 -commithash:fd6410e9c90c428bc01238372303ad09cb9ec889 +version:2.1.0-preview1-15638 +commithash:1d3a0c725dc6b8ae6b0e47800fd6b4d8f8b8d545 diff --git a/korebuild.json b/korebuild.json index bd5d51a51b..80e6e41c09 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,4 +1,14 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev" -} + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", + "channel": "dev", + "toolsets": { + "visualstudio": { + "required": ["Windows"], + "includePrerelease": true, + "minVersion": "15.0.26730.03", + "requiredWorkloads": [ + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" + ] + } + } + } \ No newline at end of file diff --git a/nuget/AspNetCore.csproj b/nuget/AspNetCore.csproj new file mode 100644 index 0000000000..65727b5103 --- /dev/null +++ b/nuget/AspNetCore.csproj @@ -0,0 +1,9 @@ + + + + netstandard1.0 + $(MSBuildThisFileDirectory)AspNetCore.nuspec + version=$(PackageVersion) + + + diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec new file mode 100644 index 0000000000..bb3c627b3e --- /dev/null +++ b/nuget/AspNetCore.nuspec @@ -0,0 +1,30 @@ + + + + Microsoft.AspNetCore.AspNetCoreModule + Microsoft ASP.NET Core Module + $VERSION$ + Microsoft + Microsoft + http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm + © .NET Foundation. All rights reserved. + http://www.asp.net/ + true + ASP.NET Core Module + en-US + Microsoft.AspNetCore.AspNetCoreModule + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props new file mode 100644 index 0000000000..5b01ee63a4 --- /dev/null +++ b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcore.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcore.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcorerh.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcorerh.dll + + + diff --git a/run.ps1 b/run.ps1 index 27dcf848f8..b7de403018 100644 --- a/run.ps1 +++ b/run.ps1 @@ -193,4 +193,4 @@ try { } finally { Remove-Module 'KoreBuild' -ErrorAction Ignore -} +} \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj new file mode 100644 index 0000000000..4af4211f2e --- /dev/null +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -0,0 +1,267 @@ + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {439824F9-1455-4CC4-BD79-B44FA0A16552} + Win32Proj + AspNetCoreModule + AspNetCore + aspnetcore + false + 10.0.15063.0 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform)\ + + + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform)\ + + + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform)\ + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ..\IISLib;.\Inc + ProgramDatabase + MultiThreadedDebug + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + Source.def + + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ..\IISLib;.\Inc + ProgramDatabase + MultiThreadedDebug + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + Source.def + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + ..\IISLib;inc + precomp.hxx + MultiThreaded + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + false + true + true + Source.def + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + ..\IISLib;inc + MultiThreaded + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + false + true + true + Source.def + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + $(build_number) + 0 + + + + + + + + + + + + + + + + + + {55494e58-e061-4c4c-a0a8-837008e72f85} + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + false + + + + + + + + PreserveNewest + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCore/Inc/applicationinfo.h b/src/AspNetCore/Inc/applicationinfo.h new file mode 100644 index 0000000000..47519adb6d --- /dev/null +++ b/src/AspNetCore/Inc/applicationinfo.h @@ -0,0 +1,218 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +typedef +HRESULT +(WINAPI * PFN_ASPNETCORE_CREATE_APPLICATION)( + _In_ IHttpServer *pServer, + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ APPLICATION **pApplication + ); + +typedef +HRESULT +(WINAPI * PFN_ASPNETCORE_CREATE_REQUEST_HANDLER)( + _In_ IHttpContext *pHttpContext, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication, + _Out_ REQUEST_HANDLER **pRequestHandler + ); +// +// The key used for hash-table lookups, consists of the port on which the http process is created. +// +class APPLICATION_INFO_KEY +{ +public: + + APPLICATION_INFO_KEY( + VOID + ) : INLINE_STRU_INIT(m_struKey) + { + } + + HRESULT + Initialize( + _In_ LPCWSTR pszKey + ) + { + return m_struKey.Copy(pszKey); + } + + BOOL + GetIsEqual( + const APPLICATION_INFO_KEY * key2 + ) const + { + return m_struKey.Equals(key2->m_struKey); + } + + DWORD CalcKeyHash() const + { + return Hash(m_struKey.QueryStr()); + } + +private: + + INLINE_STRU(m_struKey, 1024); +}; + + +class APPLICATION_INFO +{ +public: + + APPLICATION_INFO(IHttpServer *pServer) : + m_pServer(pServer), + m_cRefs(1), m_fAppOfflineFound(FALSE), + m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL), + m_pConfiguration(NULL), + m_pfnAspNetCoreCreateApplication(NULL), + m_pfnAspNetCoreCreateRequestHandler(NULL) + { + InitializeSRWLock(&m_srwLock); + } + + APPLICATION_INFO_KEY * + QueryApplicationInfoKey() + { + return &m_applicationInfoKey; + } + + virtual + ~APPLICATION_INFO(); + + HRESULT + Initialize( + _In_ ASPNETCORE_CONFIG *pConfiguration, + _In_ FILE_WATCHER *pFileWatcher + ); + + VOID + ReferenceApplicationInfo() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceApplicationInfo() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + APP_OFFLINE_HTM* QueryAppOfflineHtm() + { + return m_pAppOfflineHtm; + } + + BOOL + AppOfflineFound() + { + return m_fAppOfflineFound; + } + + VOID + UpdateAppOfflineFileHandle(); + + HRESULT + StartMonitoringAppOffline(); + + ASPNETCORE_CONFIG* + QueryConfig() + { + return m_pConfiguration; + } + + APPLICATION* + QueryApplication() + { + return m_pApplication; + } + + HRESULT + EnsureApplicationCreated(); + + PFN_ASPNETCORE_CREATE_REQUEST_HANDLER + QueryCreateRequestHandler() + { + return m_pfnAspNetCoreCreateRequestHandler; + } + +private: + HRESULT FindRequestHandlerAssembly(); + HRESULT FindNativeAssemblyFromGlobalLocation(STRU* struFilename); + HRESULT FindNativeAssemblyFromLocalBin(STRU* struFilename); + HRESULT GetRequestHandlerFromRuntimeStore(STRU* struFilename); + + mutable LONG m_cRefs; + APPLICATION_INFO_KEY m_applicationInfoKey; + BOOL m_fAppOfflineFound; + APP_OFFLINE_HTM *m_pAppOfflineHtm; + FILE_WATCHER_ENTRY *m_pFileWatcherEntry; + ASPNETCORE_CONFIG *m_pConfiguration; + APPLICATION *m_pApplication; + SRWLOCK m_srwLock; + IHttpServer *m_pServer; + PFN_ASPNETCORE_CREATE_APPLICATION m_pfnAspNetCoreCreateApplication; + PFN_ASPNETCORE_CREATE_REQUEST_HANDLER m_pfnAspNetCoreCreateRequestHandler; +}; + +class APPLICATION_INFO_HASH : + public HASH_TABLE +{ + +public: + + APPLICATION_INFO_HASH() + {} + + APPLICATION_INFO_KEY * + ExtractKey( + APPLICATION_INFO *pApplicationInfo + ) + { + return pApplicationInfo->QueryApplicationInfoKey(); + } + + DWORD + CalcKeyHash( + APPLICATION_INFO_KEY *key + ) + { + return key->CalcKeyHash(); + } + + BOOL + EqualKeys( + APPLICATION_INFO_KEY *key1, + APPLICATION_INFO_KEY *key2 + ) + { + return key1->GetIsEqual(key2); + } + + VOID + ReferenceRecord( + APPLICATION_INFO *pApplicationInfo + ) + { + pApplicationInfo->ReferenceApplicationInfo(); + } + + VOID + DereferenceRecord( + APPLICATION_INFO *pApplicationInfo + ) + { + pApplicationInfo->DereferenceApplicationInfo(); + } + +private: + + APPLICATION_INFO_HASH(const APPLICATION_INFO_HASH &); + void operator=(const APPLICATION_INFO_HASH &); +}; diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h new file mode 100644 index 0000000000..9e9b062ba2 --- /dev/null +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -0,0 +1,135 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define DEFAULT_HASH_BUCKETS 293 + +// +// This class will manage the lifecycle of all Asp.Net Core applciation +// It should be global singleton. +// Should always call GetInstance to get the object instance +// +class APPLICATION_MANAGER +{ +public: + + static + APPLICATION_MANAGER* + GetInstance( + VOID + ) + { + if ( sm_pApplicationManager == NULL ) + { + sm_pApplicationManager = new APPLICATION_MANAGER(); + } + + return sm_pApplicationManager; + } + + static + VOID + Cleanup( + VOID + ) + { + if(sm_pApplicationManager != NULL) + { + delete sm_pApplicationManager; + sm_pApplicationManager = NULL; + } + } + + HRESULT + GetApplicationInfo( + _In_ IHttpServer* pServer, + _In_ ASPNETCORE_CONFIG* pConfig, + _Out_ APPLICATION_INFO ** ppApplicationInfo + ); + + HRESULT + RecycleApplication( + _In_ LPCWSTR pszApplicationId + ); + + VOID + ShutDown(); + + ~APPLICATION_MANAGER() + { + if(m_pApplicationInfoHash != NULL) + { + m_pApplicationInfoHash->Clear(); + delete m_pApplicationInfoHash; + m_pApplicationInfoHash = NULL; + } + + if( m_pFileWatcher!= NULL ) + { + delete m_pFileWatcher; + m_pFileWatcher = NULL; + } + } + + FILE_WATCHER* + GetFileWatcher() + { + return m_pFileWatcher; + } + + HRESULT Initialize() + { + HRESULT hr = S_OK; + + if(m_pApplicationInfoHash == NULL) + { + m_pApplicationInfoHash = new APPLICATION_INFO_HASH(); + if(m_pApplicationInfoHash == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pApplicationInfoHash->Initialize(DEFAULT_HASH_BUCKETS); + if(FAILED(hr)) + { + goto Finished; + } + } + + if( m_pFileWatcher == NULL ) + { + m_pFileWatcher = new FILE_WATCHER; + if(m_pFileWatcher == NULL) + { + hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); + goto Finished; + } + + m_pFileWatcher->Create(); + } + + Finished: + return hr; + } + +private: + // + // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload + // + APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL), + m_pFileWatcher(NULL), + m_hostingModel(HOSTING_UNKNOWN), + m_fInShutdown(FALSE) + { + InitializeSRWLock(&m_srwLock); + } + + FILE_WATCHER *m_pFileWatcher; + APPLICATION_INFO_HASH *m_pApplicationInfoHash; + static APPLICATION_MANAGER *sm_pApplicationManager; + SRWLOCK m_srwLock; + APP_HOSTING_MODEL m_hostingModel; + bool m_fInShutdown; +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/appoffline.h b/src/AspNetCore/Inc/appoffline.h new file mode 100644 index 0000000000..53c758f36a --- /dev/null +++ b/src/AspNetCore/Inc/appoffline.h @@ -0,0 +1,101 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +class APP_OFFLINE_HTM +{ +public: + APP_OFFLINE_HTM(LPCWSTR pszPath) : m_cRefs(1) + { + m_Path.Copy(pszPath); + } + + VOID + ReferenceAppOfflineHtm() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceAppOfflineHtm() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + BOOL + Load( + VOID + ) + { + BOOL fResult = TRUE; + LARGE_INTEGER li = { 0 }; + CHAR *pszBuff = NULL; + HANDLE handle = INVALID_HANDLE_VALUE; + + handle = CreateFile(m_Path.QueryStr(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (handle == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + { + fResult = FALSE; + } + + // This Load() member function is supposed be called only when the change notification event of file creation or file modification happens. + // If file is currenlty locked exclusively by other processes, we might get INVALID_HANDLE_VALUE even though the file exists. In that case, we should return TRUE here. + goto Finished; + } + + if (!GetFileSizeEx(handle, &li)) + { + goto Finished; + } + + if (li.HighPart != 0) + { + // > 4gb file size not supported + // todo: log a warning at event log + goto Finished; + } + + DWORD bytesRead = 0; + + if (li.LowPart > 0) + { + pszBuff = new CHAR[li.LowPart + 1]; + + if (ReadFile(handle, pszBuff, li.LowPart, &bytesRead, NULL)) + { + m_Contents.Copy(pszBuff, bytesRead); + } + } + + Finished: + if (handle != INVALID_HANDLE_VALUE) + { + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + } + + if (pszBuff != NULL) + { + delete[] pszBuff; + pszBuff = NULL; + } + + return fResult; + } + + mutable LONG m_cRefs; + STRA m_Contents; + STRU m_Path; +}; diff --git a/src/AspNetCore/Inc/bldver.h b/src/AspNetCore/Inc/bldver.h new file mode 100644 index 0000000000..25648cf819 --- /dev/null +++ b/src/AspNetCore/Inc/bldver.h @@ -0,0 +1,61 @@ +// +// this file is automatically generated +// by beaver.exe 1.11.2003.0 +// + +// +// if you want to use a private version file and customize this, see +// file://samsndrop02/CoreXT-Latest/docs/corext/corext/version.htm +// + +#ifndef _BLDVER_H_ +#define _BLDVER_H_ + +#define BUILD_NUMBER "1965.0" +#define BUILD_NUM 1965,0 +#define PRODUCT_NUMBER "7.1" +#define PRODUCT_NUM 7,1 +#define INET_VERSION "7.1.1965.0" +#define INET_VERSION_L L"7.1.1965.0" +#define INET_VER 7,1,1965,0 + +#define PRODUCT_MAJOR 7 +#define PRODUCT_MAJOR_STRING "7" +#define PRODUCT_MAJOR_NUMBER 7 + +#define PRODUCT_MINOR 1 +#define PRODUCT_MINOR_STRING "1" +#define PRODUCT_MINOR_NUMBER 1 + +#define BUILD_MAJOR 1965 +#define BUILD_MAJOR_STRING "1965" +#define BUILD_MAJOR_NUMBER 1965 + +#define BUILD_MINOR 0 +#define BUILD_MINOR_STRING "0" +#define BUILD_MINOR_NUMBER 0 + +#define BUILD_PRIVATE "Built by panwang on IIS-OOB.\0" + +// beaver.exe can't handle a pragma to disable redefinition +#undef VER_PRODUCTVERSION +#undef VER_PRODUCTVERSION_STR +#undef VER_PRODUCTMAJORVERSION +#undef VER_PRODUCTMINORVERSION +#undef VER_PRODUCTBUILD +#undef VER_PRODUCTBUILD_QFE +#undef VER_PRODUCTNAME_STR +#undef VER_COMPANYNAME_STR + +#define VER_PRODUCTVERSION 7,1,1965,0 +#define VER_PRODUCTVERSION_STR 7.1.1965.0 +#define VER_PRODUCTMAJORVERSION 7 +#define VER_PRODUCTMINORVERSION 1 +#define VER_PRODUCTBUILD 1965 +#define VER_PRODUCTBUILD_QFE 0 +#define VER_PRODUCTNAME_STR "Microsoft Web Platform Extensions" +#define VER_COMPANYNAME_STR "Microsoft Corporation" + + + +#endif diff --git a/src/AspNetCore/Inc/filewatcher.h b/src/AspNetCore/Inc/filewatcher.h new file mode 100644 index 0000000000..c5bff0df0d --- /dev/null +++ b/src/AspNetCore/Inc/filewatcher.h @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) +#define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 +#ifndef CONTAINING_RECORD +// +// Calculate the address of the base of the structure given its type, and an +// address of a field within the structure. +// + +#define CONTAINING_RECORD(address, type, field) \ + ((type *)((PCHAR)(address)-(ULONG_PTR)(&((type *)0)->field))) + +#endif // !CONTAINING_RECORD +#define FILE_NOTIFY_VALID_MASK 0x00000fff +#define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') +#define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') + +class APPLICATION_INFO; + +class FILE_WATCHER{ +public: + + FILE_WATCHER(); + + ~FILE_WATCHER(); + + HRESULT Create(); + + HANDLE + QueryCompletionPort( + VOID + ) const + { + return m_hCompletionPort; + } + + static + DWORD + WINAPI ChangeNotificationThread(LPVOID); + + static + void + WINAPI FileWatcherCompletionRoutine + ( + DWORD dwCompletionStatus, + DWORD cbCompletion, + OVERLAPPED * pOverlapped + ); + +private: + HANDLE m_hCompletionPort; + HANDLE m_hChangeNotificationThread; +}; + +class FILE_WATCHER_ENTRY +{ +public: + FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); + + OVERLAPPED _overlapped; + + HRESULT + Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION_INFO* pApplicationInfo, + _In_ HANDLE hImpersonationToken + ); + + VOID + ReferenceFileWatcherEntry() const + { + InterlockedIncrement(&_cRefs); + } + + VOID + DereferenceFileWatcherEntry() const + { + if (InterlockedDecrement(&_cRefs) == 0) + { + delete this; + } + } + + BOOL + QueryIsValid() const + { + return _fIsValid; + } + + VOID + MarkEntryInValid() + { + _fIsValid = FALSE; + } + + HRESULT Monitor(); + + VOID StopMonitor(); + + HRESULT + HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion + ); + +private: + virtual ~FILE_WATCHER_ENTRY(); + + DWORD _dwSignature; + BUFFER _buffDirectoryChanges; + HANDLE _hImpersonationToken; + HANDLE _hDirectory; + FILE_WATCHER* _pFileMonitor; + APPLICATION_INFO* _pApplicationInfo; + STRU _strFileName; + STRU _strDirectoryName; + LONG _lStopMonitorCalled; + mutable LONG _cRefs; + BOOL _fIsValid; + SRWLOCK _srwLock; +}; diff --git a/src/AspNetCore/Inc/fx_ver.h b/src/AspNetCore/Inc/fx_ver.h new file mode 100644 index 0000000000..f485ba5a6e --- /dev/null +++ b/src/AspNetCore/Inc/fx_ver.h @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef __FX_VER_H__ +#define __FX_VER_H__ +#include + +// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not +// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11 +struct fx_ver_t +{ + fx_ver_t(int major, int minor, int patch); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build); + + int get_major() const { return m_major; } + int get_minor() const { return m_minor; } + int get_patch() const { return m_patch; } + + void set_major(int m) { m_major = m; } + void set_minor(int m) { m_minor = m; } + void set_patch(int p) { m_patch = p; } + + bool is_prerelease() const { return !m_pre.empty(); } + + std::wstring as_str() const; + std::wstring prerelease_glob() const; + std::wstring patch_glob() const; + + bool operator ==(const fx_ver_t& b) const; + bool operator !=(const fx_ver_t& b) const; + bool operator <(const fx_ver_t& b) const; + bool operator >(const fx_ver_t& b) const; + bool operator <=(const fx_ver_t& b) const; + bool operator >=(const fx_ver_t& b) const; + + static bool parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production = false); + +private: + int m_major; + int m_minor; + int m_patch; + std::wstring m_pre; + std::wstring m_build; + + static int compare(const fx_ver_t&a, const fx_ver_t& b); +}; + +#endif // __FX_VER_H__ \ No newline at end of file diff --git a/src/AspNetCore/Inc/globalmodule.h b/src/AspNetCore/Inc/globalmodule.h new file mode 100644 index 0000000000..aca1857051 --- /dev/null +++ b/src/AspNetCore/Inc/globalmodule.h @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class ASPNET_CORE_GLOBAL_MODULE : public CGlobalModule +{ + +public: + + ASPNET_CORE_GLOBAL_MODULE( + APPLICATION_MANAGER* pApplicationManager + ); + + ~ASPNET_CORE_GLOBAL_MODULE() + { + } + + VOID Terminate() + { + // Remove the class from memory. + delete this; + } + + GLOBAL_NOTIFICATION_STATUS + OnGlobalStopListening( + _In_ IGlobalStopListeningProvider * pProvider + ); + + GLOBAL_NOTIFICATION_STATUS + OnGlobalConfigurationChange( + _In_ IGlobalConfigurationChangeProvider * pProvider + ); + +private: + APPLICATION_MANAGER * m_pApplicationManager; +}; diff --git a/src/AspNetCore/Inc/proxymodule.h b/src/AspNetCore/Inc/proxymodule.h new file mode 100644 index 0000000000..7e5f30a8eb --- /dev/null +++ b/src/AspNetCore/Inc/proxymodule.h @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer *g_pHttpServer; +extern HMODULE g_hAspnetCoreRH; + +class ASPNET_CORE_PROXY_MODULE : public CHttpModule +{ + public: + + ASPNET_CORE_PROXY_MODULE(); + + ~ASPNET_CORE_PROXY_MODULE(); + + void * operator new(size_t size, IModuleAllocator * pPlacement) + { + return pPlacement->AllocateMemory(static_cast(size)); + } + + VOID + operator delete( + void * + ) + {} + + __override + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * pProvider + ); + + __override + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + IHttpContext * pHttpContext, + DWORD dwNotification, + BOOL fPostNotification, + IHttpEventProvider * pProvider, + IHttpCompletionInfo * pCompletionInfo + ); + + private: + + APPLICATION_INFO *m_pApplicationInfo; + APPLICATION *m_pApplication; + REQUEST_HANDLER *m_pHandler; +}; + +class ASPNET_CORE_PROXY_MODULE_FACTORY : public IHttpModuleFactory +{ + public: + HRESULT + GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator + ); + + VOID + Terminate(); +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h new file mode 100644 index 0000000000..45cd8f1ef5 --- /dev/null +++ b/src/AspNetCore/Inc/resource.h @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define IDS_INVALID_PROPERTY 1000 +#define IDS_SERVER_ERROR 1001 + +// TODO remove this file? +#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 +#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and is listening on port '%d'." +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." +#define ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG L"Application '%s' failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s', ErrorCode = '0x%x' processStatus code '%x'." +#define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but failed to listen on the given port '%d'" +#define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not reponse or did not listen on the given port '%d', ErrorCode = '0x%x'" +#define ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG L"Warning: Could not create stdoutLogFile %s, ErrorCode = %d." +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." +#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application, ErrorCode = '0x%x." +#define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool." +#define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%s' other than the one of running application(s)." +#define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'." +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread eixt, ErrorCode = '0x%x." \ No newline at end of file diff --git a/src/AspNetCore/Source.def b/src/AspNetCore/Source.def new file mode 100644 index 0000000000..9aae10ab5d --- /dev/null +++ b/src/AspNetCore/Source.def @@ -0,0 +1,4 @@ +LIBRARY aspnetcore + +EXPORTS + RegisterModule diff --git a/src/AspNetCore/Src/applicationinfo.cpp b/src/AspNetCore/Src/applicationinfo.cpp new file mode 100644 index 0000000000..e63e633a93 --- /dev/null +++ b/src/AspNetCore/Src/applicationinfo.cpp @@ -0,0 +1,332 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +APPLICATION_INFO::~APPLICATION_INFO() +{ + if (m_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->DereferenceAppOfflineHtm(); + m_pAppOfflineHtm = NULL; + } + + if (m_pFileWatcherEntry != NULL) + { + // Mark the entry as invalid, + // StopMonitor will close the file handle and trigger a FCN + // the entry will delete itself when processing this FCN + m_pFileWatcherEntry->MarkEntryInValid(); + m_pFileWatcherEntry->StopMonitor(); + m_pFileWatcherEntry = NULL; + } + + if (m_pApplication != NULL) + { + // shutdown the application + m_pApplication->ShutDown(); + m_pApplication->DereferenceApplication(); + m_pApplication = NULL; + } + + // configuration should be dereferenced after application shutdown + // since the former will use it during shutdown + if (m_pConfiguration != NULL) + { + // Need to dereference the configuration instance + m_pConfiguration->DereferenceConfiguration(); + m_pConfiguration = NULL; + } +} + +HRESULT +APPLICATION_INFO::Initialize( + _In_ ASPNETCORE_CONFIG *pConfiguration, + _In_ FILE_WATCHER *pFileWatcher +) +{ + HRESULT hr = S_OK; + + DBG_ASSERT(pConfiguration); + DBG_ASSERT(pFileWatcher); + + m_pConfiguration = pConfiguration; + + // reference the configuration instance to prevent it will be not release + // earlier in case of configuration change and shutdown + m_pConfiguration->ReferenceConfiguration(); + + hr = m_applicationInfoKey.Initialize(pConfiguration->QueryConfigPath()->QueryStr()); + if (FAILED(hr)) + { + goto Finished; + } + + if (m_pFileWatcherEntry == NULL) + { + m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pFileWatcher); + if (m_pFileWatcherEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + UpdateAppOfflineFileHandle(); + +Finished: + return hr; +} + +HRESULT +APPLICATION_INFO::StartMonitoringAppOffline() +{ + HRESULT hr = S_OK; + if (m_pFileWatcherEntry != NULL) + { + hr = m_pFileWatcherEntry->Create(m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), L"app_offline.htm", this, NULL); + } + return hr; +} + +VOID +APPLICATION_INFO::UpdateAppOfflineFileHandle() +{ + STRU strFilePath; + UTILITY::ConvertPathToFullPath(L".\\app_offline.htm", + m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), + &strFilePath); + APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; + APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL; + + if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr()) && + GetLastError() == ERROR_FILE_NOT_FOUND) + { + m_fAppOfflineFound = FALSE; + } + else + { + m_fAppOfflineFound = TRUE; + pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); + + if (pNewAppOfflineHtm != NULL) + { + if (pNewAppOfflineHtm->Load()) + { + // + // loaded the new app_offline.htm + // + pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); + + if (pOldAppOfflineHtm != NULL) + { + pOldAppOfflineHtm->DereferenceAppOfflineHtm(); + pOldAppOfflineHtm = NULL; + } + } + else + { + // ignored the new app_offline file because the file does not exist. + pNewAppOfflineHtm->DereferenceAppOfflineHtm(); + pNewAppOfflineHtm = NULL; + } + } + + // recycle the application + if (m_pApplication != NULL) + { + m_pApplication->ShutDown(); + m_pApplication->DereferenceApplication(); + m_pApplication = NULL; + } + } +} + +HRESULT +APPLICATION_INFO::EnsureApplicationCreated() +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + APPLICATION* pApplication = NULL; + STACK_STRU(struFileName, 300); // >MAX_PATH + STRU hostFxrDllLocation; + + if (m_pApplication != NULL) + { + goto Finished; + } + + hr = FindRequestHandlerAssembly(); + if (FAILED(hr)) + { + goto Finished; + } + + if (m_pApplication == NULL) + { + AcquireSRWLockExclusive(&m_srwLock); + fLocked = TRUE; + if (m_pApplication != NULL) + { + goto Finished; + } + + if (m_pfnAspNetCoreCreateApplication == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + goto Finished; + } + + hr = m_pfnAspNetCoreCreateApplication(m_pServer, m_pConfiguration, &pApplication); + if (FAILED(hr)) + { + goto Finished; + } + m_pApplication = pApplication; + } + +Finished: + if (fLocked) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + return hr; +} + +HRESULT +APPLICATION_INFO::FindRequestHandlerAssembly() +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + STACK_STRU(struFileName, 256); + + if (g_fAspnetcoreRHLoadedError) + { + hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; + goto Finished; + } + else if (!g_fAspnetcoreRHAssemblyLoaded) + { + AcquireSRWLockExclusive(&g_srwLock); + fLocked = TRUE; + if (g_fAspnetcoreRHLoadedError) + { + hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; + goto Finished; + } + if (g_fAspnetcoreRHAssemblyLoaded) + { + goto Finished; + } + + // load assembly and create the application + if (m_pConfiguration->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + // Look at inetsvr only for now. TODO add in functionality + hr = FindNativeAssemblyFromGlobalLocation(&struFileName); + + if (FAILED(hr)) + { + goto Finished; + } + } + else + { + hr = FindNativeAssemblyFromGlobalLocation(&struFileName); + if (FAILED(hr)) + { + goto Finished; + } + } + + g_hAspnetCoreRH = LoadLibraryW(struFileName.QueryStr()); + if (g_hAspnetCoreRH == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + g_pfnAspNetCoreCreateApplication = (PFN_ASPNETCORE_CREATE_APPLICATION) + GetProcAddress(g_hAspnetCoreRH, "CreateApplication"); + if (g_pfnAspNetCoreCreateApplication == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + g_pfnAspNetCoreCreateRequestHandler = (PFN_ASPNETCORE_CREATE_REQUEST_HANDLER) + GetProcAddress(g_hAspnetCoreRH, "CreateRequestHandler"); + if (g_pfnAspNetCoreCreateRequestHandler == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + g_fAspnetcoreRHAssemblyLoaded = TRUE; + } + +Finished: + // + // Question: we remember the load failure so that we will not try again. + // User needs to check whether the fuction pointer is NULL + // + m_pfnAspNetCoreCreateApplication = g_pfnAspNetCoreCreateApplication; + m_pfnAspNetCoreCreateRequestHandler = g_pfnAspNetCoreCreateRequestHandler; + if (!g_fAspnetcoreRHLoadedError && FAILED(hr)) + { + g_fAspnetcoreRHLoadedError = TRUE; + } + + if (fLocked) + { + ReleaseSRWLockExclusive(&g_srwLock); + } + return hr; +} + +HRESULT +APPLICATION_INFO::FindNativeAssemblyFromGlobalLocation(STRU* struFilename) +{ + HRESULT hr = S_OK; + DWORD dwSize = MAX_PATH; + BOOL fDone = FALSE; + DWORD dwPosition = 0; + + // Though we could call LoadLibrary(L"aspnetcorerh.dll") relying the OS to solve + // the path (the targeted dll is the same folder of w3wp.exe/iisexpress) + // let's still load with full path to avoid security issue + while (!fDone) + { + DWORD dwReturnedSize = GetModuleFileName(NULL, struFilename->QueryStr(), dwSize); + if (dwReturnedSize == 0) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + fDone = TRUE; + goto Finished; + } + else if ((dwReturnedSize == dwSize) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + { + dwSize *= 2; // smaller buffer. increase the buffer and retry + struFilename->Resize(dwSize + 20); // aspnetcorerh.dll + } + else + { + fDone = TRUE; + } + } + + if (FAILED(hr = struFilename->SyncWithBuffer())) + { + goto Finished; + } + dwPosition = struFilename->LastIndexOf(L'\\', 0); + struFilename->QueryStr()[dwPosition] = L'\0'; + + if (FAILED(hr = struFilename->SyncWithBuffer()) || + FAILED(hr = struFilename->Append(g_pwzAspnetcoreRequestHandlerName))) + { + goto Finished; + } + +Finished: + return hr; +} diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx new file mode 100644 index 0000000000..094f1d5e1c --- /dev/null +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -0,0 +1,269 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; + +HRESULT +APPLICATION_MANAGER::GetApplicationInfo( + _In_ IHttpServer* pServer, + _In_ ASPNETCORE_CONFIG* pConfig, + _Out_ APPLICATION_INFO ** ppApplicationInfo +) +{ + HRESULT hr = S_OK; + APPLICATION_INFO *pApplicationInfo = NULL; + APPLICATION_INFO_KEY key; + BOOL fExclusiveLock = FALSE; + BOOL fMixedHostingModelError = FALSE; + BOOL fDuplicatedInProcessApp = FALSE; + PCWSTR pszApplicationId = NULL; + LPCWSTR apsz[1]; + STACK_STRU ( strEventMsg, 256 ); + + *ppApplicationInfo = NULL; + + DBG_ASSERT(pServer != NULL); + DBG_ASSERT(pConfig != NULL); + + pszApplicationId = pConfig->QueryConfigPath()->QueryStr(); + + hr = key.Initialize(pszApplicationId); + if (FAILED(hr)) + { + goto Finished; + } + + AcquireSRWLockShared(&m_srwLock); + if (m_fInShutdown) + { + ReleaseSRWLockShared(&m_srwLock); + hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); + goto Finished; + } + m_pApplicationInfoHash->FindKey(&key, ppApplicationInfo); + ReleaseSRWLockShared(&m_srwLock); + + if (*ppApplicationInfo == NULL) + { + switch (pConfig->QueryHostingModel()) + { + case HOSTING_IN_PROCESS: + if (m_pApplicationInfoHash->Count() > 0) + { + // Only one inprocess app is allowed per IIS worker process + fDuplicatedInProcessApp = TRUE; + hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); + goto Finished; + } + break; + + case HOSTING_OUT_PROCESS: + break; + + default: + hr = E_UNEXPECTED; + goto Finished; + } + pApplicationInfo = new APPLICATION_INFO(pServer); + if (pApplicationInfo == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + AcquireSRWLockExclusive(&m_srwLock); + fExclusiveLock = TRUE; + if (m_fInShutdown) + { + // Already in shuting down. No need to create the application + hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); + goto Finished; + } + m_pApplicationInfoHash->FindKey(&key, ppApplicationInfo); + + if (*ppApplicationInfo != NULL) + { + // someone else created the application + delete pApplicationInfo; + pApplicationInfo = NULL; + goto Finished; + } + + // hosting model check. We do not allow mixed scenario for now + // could be changed in the future + if (m_hostingModel != HOSTING_UNKNOWN) + { + if (m_hostingModel != pConfig->QueryHostingModel()) + { + // hosting model does not match, error out + fMixedHostingModelError = TRUE; + hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); + goto Finished; + } + } + + hr = pApplicationInfo->Initialize(pConfig, m_pFileWatcher); + if (FAILED(hr)) + { + goto Finished; + } + + hr = m_pApplicationInfoHash->InsertRecord( pApplicationInfo ); + if (FAILED(hr)) + { + goto Finished; + } + + // + // first application will decide which hosting model allowed by this process + // + if (m_hostingModel == HOSTING_UNKNOWN) + { + m_hostingModel = pConfig->QueryHostingModel(); + } + + *ppApplicationInfo = pApplicationInfo; + ReleaseSRWLockExclusive(&m_srwLock); + fExclusiveLock = FALSE; + + pApplicationInfo->StartMonitoringAppOffline(); + pApplicationInfo = NULL; + } + +Finished: + + if (fExclusiveLock) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + + if (pApplicationInfo != NULL) + { + pApplicationInfo->DereferenceApplicationInfo(); + pApplicationInfo = NULL; + } + + if (FAILED(hr)) + { + if (fDuplicatedInProcessApp) + { + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG, + pszApplicationId))) + { + /*apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_ERROR_TYPE, + 0, + ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, + NULL, + 1, + 0, + apsz, + NULL); + }*/ + } + } + else if (fMixedHostingModelError) + { + //if (SUCCEEDED(strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, + // pszApplicationId, + // pConfig->QueryHostingModelStr()))) + //{ + // apsz[0] = strEventMsg.QueryStr(); + // /*if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_ERROR_TYPE, + // 0, + // ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // }*/ + //} + } + else + { + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, + pszApplicationId, + hr))) + { + apsz[0] = strEventMsg.QueryStr(); + /*if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_ERROR_TYPE, + 0, + ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, + NULL, + 1, + 0, + apsz, + NULL); + }*/ + } + } + } + + return hr; +} + +HRESULT +APPLICATION_MANAGER::RecycleApplication( + _In_ LPCWSTR pszApplicationId +) +{ + HRESULT hr = S_OK; + APPLICATION_INFO_KEY key; + + hr = key.Initialize(pszApplicationId); + if (FAILED(hr)) + { + goto Finished; + } + AcquireSRWLockExclusive(&m_srwLock); + m_pApplicationInfoHash->DeleteKey(&key); + + if (m_pApplicationInfoHash->Count() == 0) + { + m_hostingModel = HOSTING_UNKNOWN; + } + + if (g_fAspnetcoreRHLoadedError) + { + // We had assembly loading failure + // this error blocked the start of all applications + // Let's recycle the worker process if user redeployed any application + g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand due to assembly loading failure"); + } + + ReleaseSRWLockExclusive(&m_srwLock); + +Finished: + + return hr; +} + +VOID +APPLICATION_MANAGER::ShutDown() +{ + m_fInShutdown = TRUE; + if (m_pApplicationInfoHash != NULL) + { + AcquireSRWLockExclusive(&m_srwLock); + + // clean up the hash table so that the application will be informed on shutdown + m_pApplicationInfoHash->Clear(); + ReleaseSRWLockExclusive(&m_srwLock); + } + +} diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp new file mode 100644 index 0000000000..d61a729361 --- /dev/null +++ b/src/AspNetCore/Src/dllmain.cpp @@ -0,0 +1,208 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" +#include + +HTTP_MODULE_ID g_pModuleId = NULL; +IHttpServer * g_pHttpServer = NULL; +BOOL g_fRecycleProcessCalled = FALSE; +PCWSTR g_pszModuleName = NULL; +HINSTANCE g_hModule; +HMODULE g_hAspnetCoreRH = NULL; +BOOL g_fAspnetcoreRHAssemblyLoaded = FALSE; +BOOL g_fAspnetcoreRHLoadedError = FALSE; +DWORD g_dwAspNetCoreDebugFlags = 0; +DWORD g_dwActiveServerProcesses = 0; +SRWLOCK g_srwLock; +DWORD g_dwDebugFlags = 0; +PCSTR g_szDebugLabel = "ASPNET_CORE_MODULE"; +PCWSTR g_pwzAspnetcoreRequestHandlerName = L"\\aspnetcorerh.dll"; +PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication; +PFN_ASPNETCORE_CREATE_REQUEST_HANDLER g_pfnAspNetCoreCreateRequestHandler; + +VOID +StaticCleanup() +{ + APPLICATION_MANAGER::Cleanup(); +} + +BOOL WINAPI DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + UNREFERENCED_PARAMETER(lpReserved); + + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hModule = hModule; + DisableThreadLibraryCalls(hModule); + break; + case DLL_PROCESS_DETACH: + StaticCleanup(); + default: + break; + } + + return TRUE; +} + +HRESULT +__stdcall +RegisterModule( +DWORD dwServerVersion, +IHttpModuleRegistrationInfo * pModuleInfo, +IHttpServer * pHttpServer +) +/*++ + +Routine description: + +Function called by IIS immediately after loading the module, used to let +IIS know what notifications the module is interested in + +Arguments: + +dwServerVersion - IIS version the module is being loaded on +pModuleInfo - info regarding this module +pHttpServer - callback functions which can be used by the module at +any point + +Return value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + HKEY hKey; + BOOL fDisableANCM = FALSE; + ASPNET_CORE_PROXY_MODULE_FACTORY * pFactory = NULL; + ASPNET_CORE_GLOBAL_MODULE * pGlobalModule = NULL; + APPLICATION_MANAGER * pApplicationManager = NULL; + + UNREFERENCED_PARAMETER(dwServerVersion); + +#ifdef DEBUG + CREATE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); + g_dwDebugFlags = DEBUG_FLAGS_ANY; +#endif // DEBUG + + CREATE_DEBUG_PRINT_OBJECT; + + //LoadGlobalConfiguration(); + + InitializeSRWLock(&g_srwLock); + + g_pModuleId = pModuleInfo->GetId(); + g_pszModuleName = pModuleInfo->GetName(); + g_pHttpServer = pHttpServer; + + // check whether the feature is disabled due to security reason + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + 0, + KEY_READ, + &hKey) == NO_ERROR) + { + DWORD dwType; + DWORD dwData; + DWORD cbData; + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"DisableANCM", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + fDisableANCM = (dwData != 0); + } + } + + if (fDisableANCM) + { + // Logging + goto Finished; + } + + // + // Create the factory before any static initialization. + // The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any + // static object initialized. + // + pFactory = new ASPNET_CORE_PROXY_MODULE_FACTORY; + + if (pFactory == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pModuleInfo->SetRequestNotifications( + pFactory, + RQ_EXECUTE_REQUEST_HANDLER, + 0); + if (FAILED(hr)) + { + goto Finished; + } + + pFactory = NULL; + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if(pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pApplicationManager->Initialize(); + if(FAILED(hr)) + { + goto Finished; + } + pGlobalModule = NULL; + + pGlobalModule = new ASPNET_CORE_GLOBAL_MODULE(pApplicationManager); + if (pGlobalModule == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pModuleInfo->SetGlobalNotifications( + pGlobalModule, + GL_CONFIGURATION_CHANGE | GL_STOP_LISTENING); + + if (FAILED(hr)) + { + goto Finished; + } + pGlobalModule = NULL; + + hr = ALLOC_CACHE_HANDLER::StaticInitialize(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + if (pGlobalModule != NULL) + { + delete pGlobalModule; + pGlobalModule = NULL; + } + + if (pFactory != NULL) + { + pFactory->Terminate(); + pFactory = NULL; + } + + return hr; +} + diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx new file mode 100644 index 0000000000..0765b1c39e --- /dev/null +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -0,0 +1,466 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +FILE_WATCHER::FILE_WATCHER() : + m_hCompletionPort(NULL), + m_hChangeNotificationThread(NULL) +{ +} + +FILE_WATCHER::~FILE_WATCHER() +{ + if (m_hChangeNotificationThread != NULL) + { + PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); + WaitForSingleObject(m_hChangeNotificationThread, INFINITE); + CloseHandle(m_hChangeNotificationThread); + m_hChangeNotificationThread = NULL; + } + + if (NULL != m_hCompletionPort) + { + CloseHandle(m_hCompletionPort); + m_hCompletionPort = NULL; + } +} + +HRESULT +FILE_WATCHER::Create( + VOID +) +{ + HRESULT hr = S_OK; + + m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, + 0, + 0); + + if (m_hCompletionPort == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_hChangeNotificationThread = CreateThread(NULL, + 0, + ChangeNotificationThread, + this, + 0, + NULL); + + if (m_hChangeNotificationThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + + CloseHandle(m_hCompletionPort); + m_hCompletionPort = NULL; + + goto Finished; + } + +Finished: + return hr; +} + +DWORD +WINAPI +FILE_WATCHER::ChangeNotificationThread( + LPVOID pvArg +) +/*++ + +Routine Description: + +IO completion thread + +Arguments: + +None + +Return Value: + +Win32 error + +--*/ +{ + FILE_WATCHER * pFileMonitor; + BOOL fSuccess = FALSE; + DWORD cbCompletion = 0; + OVERLAPPED * pOverlapped = NULL; + DWORD dwErrorStatus; + ULONG_PTR completionKey; + + pFileMonitor = (FILE_WATCHER*)pvArg; + DBG_ASSERT(pFileMonitor != NULL); + + while (TRUE) + { + fSuccess = GetQueuedCompletionStatus( + pFileMonitor->m_hCompletionPort, + &cbCompletion, + &completionKey, + &pOverlapped, + INFINITE); + + DBG_ASSERT(fSuccess); + DebugPrint(1, "FILE_WATCHER::ChangeNotificationThread"); + dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); + + if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) + { + break; + } + + DBG_ASSERT(pOverlapped != NULL); + if (pOverlapped != NULL) + { + FileWatcherCompletionRoutine( + dwErrorStatus, + cbCompletion, + pOverlapped); + } + pOverlapped = NULL; + cbCompletion = 0; + } + + return 0; +} + +VOID +WINAPI +FILE_WATCHER::FileWatcherCompletionRoutine( + DWORD dwCompletionStatus, + DWORD cbCompletion, + OVERLAPPED * pOverlapped +) +/*++ + +Routine Description: + +Called when ReadDirectoryChangesW() completes + +Arguments: + +dwCompletionStatus - Error of completion +cbCompletion - Bytes of completion +pOverlapped - State of completion + +Return Value: + +None + +--*/ +{ + FILE_WATCHER_ENTRY * pMonitorEntry; + pMonitorEntry = CONTAINING_RECORD(pOverlapped, FILE_WATCHER_ENTRY, _overlapped); + pMonitorEntry->DereferenceFileWatcherEntry(); + DBG_ASSERT(pMonitorEntry != NULL); + + pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion); + + if (pMonitorEntry->QueryIsValid()) + { + // + // Continue monitoring + // + pMonitorEntry->Monitor(); + } + else + { + // + // Marked by application distructor + // Deference the entry to delete it + // + pMonitorEntry->DereferenceFileWatcherEntry(); + } +} + + +FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : + _pFileMonitor(pFileMonitor), + _hDirectory(INVALID_HANDLE_VALUE), + _hImpersonationToken(NULL), + _pApplicationInfo(NULL), + _lStopMonitorCalled(0), + _cRefs(1), + _fIsValid(TRUE) +{ + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE; + InitializeSRWLock(&_srwLock); +} + +FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() +{ + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; + + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + if (_hImpersonationToken != NULL) + { + CloseHandle(_hImpersonationToken); + _hImpersonationToken = NULL; + } +} + +#pragma warning(disable:4100) + +HRESULT +FILE_WATCHER_ENTRY::HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion +) +/*++ + +Routine Description: + +Handle change notification (see if any of associated config files +need to be flushed) + +Arguments: + +dwCompletionStatus - Completion status +cbCompletion - Bytes of completion + +Return Value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + FILE_NOTIFY_INFORMATION * pNotificationInfo; + BOOL fFileChanged = FALSE; + + AcquireSRWLockExclusive(&_srwLock); + if (!_fIsValid) + { + goto Finished; + } + + // When directory handle is closed then HandleChangeCompletion + // happens with cbCompletion = 0 and dwCompletionStatus = 0 + // From documentation it is not clear if that combination + // of return values is specific to closing handles or whether + // it could also mean an error condition. Hence we will maintain + // explicit flag that will help us determine if entry + // is being shutdown (StopMonitor() called) + // + if (_lStopMonitorCalled) + { + goto Finished; + } + + // + // There could be a FCN overflow + // Let assume the file got changed instead of checking files + // Othersie we have to cache the file info + // + if (cbCompletion == 0) + { + fFileChanged = TRUE; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); + DBG_ASSERT(pNotificationInfo != NULL); + + while (pNotificationInfo != NULL) + { + // + // check whether the monitored file got changed + // + if (_wcsnicmp(pNotificationInfo->FileName, + _strFileName.QueryStr(), + pNotificationInfo->FileNameLength / sizeof(WCHAR)) == 0) + { + fFileChanged = TRUE; + break; + } + // + // Advance to next notification + // + if (pNotificationInfo->NextEntryOffset == 0) + { + pNotificationInfo = NULL; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*) + ((PBYTE)pNotificationInfo + + pNotificationInfo->NextEntryOffset); + } + } + } + + if (fFileChanged) + { + // + // so far we only monitoring app_offline + // + _pApplicationInfo->UpdateAppOfflineFileHandle(); + } + +Finished: + ReleaseSRWLockExclusive(&_srwLock); + return hr; +} + +#pragma warning( error : 4100 ) + +HRESULT +FILE_WATCHER_ENTRY::Monitor(VOID) +{ + HRESULT hr = S_OK; + DWORD cbRead; + + AcquireSRWLockExclusive(&_srwLock); + ReferenceFileWatcherEntry(); + ZeroMemory(&_overlapped, sizeof(_overlapped)); + + if (!ReadDirectoryChangesW(_hDirectory, + _buffDirectoryChanges.QueryPtr(), + _buffDirectoryChanges.QuerySize(), + FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline + FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, + &cbRead, + &_overlapped, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + ReleaseSRWLockExclusive(&_srwLock); + return hr; +} + +VOID +FILE_WATCHER_ENTRY::StopMonitor(VOID) +{ + // + // Flag that monitoring is being stopped so that + // we know that HandleChangeCompletion() call + // can be ignored + // + InterlockedExchange(&_lStopMonitorCalled, 1); + + AcquireSRWLockExclusive(&_srwLock); + + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + ReleaseSRWLockExclusive(&_srwLock); +} + +HRESULT +FILE_WATCHER_ENTRY::Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION_INFO* pApplicationInfo, + _In_ HANDLE hImpersonationToken +) +{ + HRESULT hr = S_OK; + BOOL fRet = FALSE; + + if (pszDirectoryToMonitor == NULL || + pszFileNameToMonitor == NULL || + pApplicationInfo == NULL) + { + DBG_ASSERT(FALSE); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + // + //remember the application + // + _pApplicationInfo = pApplicationInfo; + + if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) + { + goto Finished; + } + + if (FAILED(hr = _strDirectoryName.Copy(pszDirectoryToMonitor))) + { + goto Finished; + } + + // + // Resize change buffer to something "reasonable" + // + if (!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE)) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + + if (hImpersonationToken != NULL) + { + fRet = DuplicateHandle(GetCurrentProcess(), + hImpersonationToken, + GetCurrentProcess(), + &_hImpersonationToken, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + + if (!fRet) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + else + { + if (_hImpersonationToken != NULL) + { + CloseHandle(_hImpersonationToken); + _hImpersonationToken = NULL; + } + } + + _hDirectory = CreateFileW( + _strDirectoryName.QueryStr(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + + if (_hDirectory == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (CreateIoCompletionPort( + _hDirectory, + _pFileMonitor->QueryCompletionPort(), + NULL, + 0) == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Start monitoring + // + hr = Monitor(); + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/globalmodule.cpp b/src/AspNetCore/Src/globalmodule.cpp new file mode 100644 index 0000000000..fc0ba3c6ea --- /dev/null +++ b/src/AspNetCore/Src/globalmodule.cpp @@ -0,0 +1,59 @@ +#include "precomp.hxx" + +ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE( + APPLICATION_MANAGER* pApplicationManager) +{ + m_pApplicationManager = pApplicationManager; +} + +// +// Is called when IIS decided to terminate worker process +// Shut down all core apps +// +GLOBAL_NOTIFICATION_STATUS +ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening( + _In_ IGlobalStopListeningProvider * pProvider +) +{ + UNREFERENCED_PARAMETER(pProvider); + + if (m_pApplicationManager != NULL) + { + // we should let application manager to shudown all allication + // and dereference it as some requests may still reference to application manager + m_pApplicationManager->ShutDown(); + m_pApplicationManager = NULL; + } + + // Return processing to the pipeline. + return GL_NOTIFICATION_CONTINUE; +} + +// +// Is called when configuration changed +// Recycled the corresponding core app if its configuration changed +// +GLOBAL_NOTIFICATION_STATUS +ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange( + _In_ IGlobalConfigurationChangeProvider * pProvider +) +{ + UNREFERENCED_PARAMETER(pProvider); + + // Retrieve the path that has changed. + PCWSTR pwszChangePath = pProvider->GetChangePath(); + + // Test for an error. + if (NULL != pwszChangePath && + _wcsicmp(pwszChangePath, L"MACHINE") != 0 && + _wcsicmp(pwszChangePath, L"MACHINE/WEBROOT") != 0) + { + if (m_pApplicationManager != NULL) + { + m_pApplicationManager->RecycleApplication(pwszChangePath); + } + } + + // Return processing to the pipeline. + return GL_NOTIFICATION_CONTINUE; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx new file mode 100644 index 0000000000..b0c2c5cc4e --- /dev/null +++ b/src/AspNetCore/Src/precomp.hxx @@ -0,0 +1,155 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#pragma warning( disable : 4091) + +// +// System related headers +// +#define _WINSOCKAPI_ + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include +#include +#include + +//#include +#include +#include + +// This should remove our issue of compiling for win7 without header files. +// We force the Windows 8 version check logic in iiswebsocket.h to succeed even though we're compiling for Windows 7. +// Then, we set the version defines back to Windows 7 to for the remainder of the compilation. +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT +#define NTDDI_VERSION 0x06020000 +#define WINVER 0x0602 +#define _WIN32_WINNT 0x0602 +#include +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include +#include + +#include +#include +#include + +// +// Option available starting Windows 8. +// 111 is the value in SDK on May 15, 2012. +// +#ifndef WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS +#define WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS 111 +#endif + +#ifdef max +#undef max +template inline T max(T a, T b) +{ + return a > b ? a : b; +} +#endif + +#ifdef min +#undef min +template inline T min(T a, T b) +{ + return a < b ? a : b; +} +#endif + +inline bool IsSpace(char ch) +{ + switch(ch) + { + case 32: // ' ' + case 9: // '\t' + case 10: // '\n' + case 13: // '\r' + case 11: // '\v' + case 12: // '\f' + return true; + default: + return false; + } +} + +#include +#include +#include "stringa.h" +#include "stringu.h" +#include "dbgutil.h" +#include "ahutil.h" +#include "multisz.h" +#include "multisza.h" +#include "base64.h" +#include +#include +#include +#include +#include + +#include "..\..\CommonLib\environmentvariablehash.h" +#include "..\..\CommonLib\aspnetcoreconfig.h" +#include "..\..\CommonLib\application.h" +#include "..\..\CommonLib\utility.h" +#include "..\..\CommonLib\debugutil.h" +#include "..\..\CommonLib\requesthandler.h" +//#include "..\aspnetcore_msg.h" +//#include "aspnetcore_event.h" +#include "appoffline.h" +#include "filewatcher.h" +#include "applicationinfo.h" +#include "applicationmanager.h" +#include "globalmodule.h" +#include "resource.h" +#include "proxymodule.h" + + +FORCEINLINE +DWORD +WIN32_FROM_HRESULT( + HRESULT hr +) +{ + if ((FAILED(hr)) && + (HRESULT_FACILITY(hr) == FACILITY_WIN32)) + { + return HRESULT_CODE(hr); + } + return hr; +} + +FORCEINLINE +HRESULT +HRESULT_FROM_GETLASTERROR() +{ + return ( GetLastError() != NO_ERROR ) + ? HRESULT_FROM_WIN32( GetLastError() ) + : E_FAIL; +} + +extern PVOID g_pModuleId; +extern BOOL g_fAspnetcoreRHAssemblyLoaded; +extern BOOL g_fAspnetcoreRHLoadedError; +extern BOOL g_fEnableReferenceCountTracing; +extern DWORD g_dwActiveServerProcesses; +extern HMODULE g_hAspnetCoreRH; +extern SRWLOCK g_srwLock; +extern PCWSTR g_pwzAspnetcoreRequestHandlerName; + +extern PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication; +extern PFN_ASPNETCORE_CREATE_REQUEST_HANDLER g_pfnAspNetCoreCreateRequestHandler; +#pragma warning( error : 4091) \ No newline at end of file diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx new file mode 100644 index 0000000000..6f9b6e247a --- /dev/null +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -0,0 +1,192 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +__override +HRESULT +ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator +) +{ + ASPNET_CORE_PROXY_MODULE *pModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(); + if (pModule == NULL) + { + return E_OUTOFMEMORY; + } + + *ppModule = pModule; + return S_OK; +} + +__override +VOID +ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate( + VOID +) +/*++ + +Routine description: + + Function called by IIS for global (non-request-specific) notifications + +Arguments: + + None. + +Return value: + + None + +--*/ +{ + /* FORWARDING_HANDLER::StaticTerminate(); + + WEBSOCKET_HANDLER::StaticTerminate();*/ + + ALLOC_CACHE_HANDLER::StaticTerminate(); + + delete this; +} + +ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE( +) : m_pApplicationInfo(NULL), m_pHandler(NULL) +{ +} + +ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE() +{ + if (m_pApplicationInfo != NULL) + { + m_pApplicationInfo->DereferenceApplicationInfo(); + m_pApplicationInfo = NULL; + } + + if (m_pHandler != NULL) + { + m_pHandler->DereferenceRequestHandler(); + m_pHandler = NULL; + } +} + +__override +REQUEST_NOTIFICATION_STATUS +ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * +) +{ + HRESULT hr = S_OK; + ASPNETCORE_CONFIG *pConfig = NULL; + APPLICATION_MANAGER *pApplicationManager = NULL; + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; + APPLICATION* pApplication = NULL; + STACK_STRU(struFileName, 256); + + hr = ASPNETCORE_CONFIG::GetConfig(g_pHttpServer, g_pModuleId, pHttpContext, &pConfig); + if (FAILED(hr)) + { + goto Finished; + } + + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pApplicationManager->GetApplicationInfo( + g_pHttpServer, + pConfig, + &m_pApplicationInfo); + if (FAILED(hr)) + { + goto Finished; + } + + // app_offline check to avoid loading aspnetcorerh.dll unnecessarily + if (m_pApplicationInfo->AppOfflineFound()) + { + // servicing app_offline + HTTP_DATA_CHUNK DataChunk; + IHttpResponse *pResponse = NULL; + APP_OFFLINE_HTM *pAppOfflineHtm = NULL; + + pResponse = pHttpContext->GetResponse(); + pAppOfflineHtm = m_pApplicationInfo->QueryAppOfflineHtm(); + DBG_ASSERT(pAppOfflineHtm); + DBG_ASSERT(pResponse); + + // Ignore failure hresults as nothing we can do + // Set fTrySkipCustomErrors to true as we want client see the offline content + pResponse->SetStatus(503, "Service Unavailable", 0, hr, NULL, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); + + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = (PVOID)pAppOfflineHtm->m_Contents.QueryStr(); + DataChunk.FromMemory.BufferLength = pAppOfflineHtm->m_Contents.QueryCB(); + pResponse->WriteEntityChunkByReference(&DataChunk); + + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + goto Finished; + } + + // make sure assmebly is loaded and application is created + + hr = m_pApplicationInfo->EnsureApplicationCreated(); + if (FAILED(hr)) + { + goto Finished; + } + pApplication = m_pApplicationInfo->QueryApplication(); + DBG_ASSERT(pApplication); + + // make sure application is in running state + // cannot recreate the application as we cannot reload clr for inprocess + if (pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING) + { + hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); + goto Finished; + } + + // Create RequestHandler and process the request + hr = m_pApplicationInfo->QueryCreateRequestHandler()(pHttpContext, + (HTTP_MODULE_ID*) &g_pModuleId, + pApplication, + &m_pHandler); + + if (FAILED(hr)) + { + goto Finished; + } + retVal = m_pHandler->OnExecuteRequestHandler(); + +Finished: + if (FAILED(hr)) + { + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + } + return retVal; +} + +__override +REQUEST_NOTIFICATION_STATUS +ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion( + IHttpContext *, + DWORD, + BOOL, + IHttpEventProvider *, + IHttpCompletionInfo * pCompletionInfo +) +{ + return m_pHandler->OnAsyncCompletion( + pCompletionInfo->GetCompletionBytes(), + pCompletionInfo->GetCompletionStatus()); +} \ No newline at end of file diff --git a/src/AspNetCore/aspnetcore_schema.xml b/src/AspNetCore/aspnetcore_schema.xml new file mode 100644 index 0000000000..c00cc8a77c --- /dev/null +++ b/src/AspNetCore/aspnetcore_schema.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCore/aspnetcoremodule.rc b/src/AspNetCore/aspnetcoremodule.rc new file mode 100644 index 0000000000..bca79c6a18 --- /dev/null +++ b/src/AspNetCore/aspnetcoremodule.rc @@ -0,0 +1,117 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "version.h" +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +///////////////////////////////////////////////////////////////////////////// +// +// 11 +// + +//1 11 +//BEGIN +// 0x0001, 0x0000, 0x03e8, 0x0000, 0x03ed, 0x0000, 0x0010, 0x0000, 0x0010, +// 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, +// 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, +// 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, +// 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, +// 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, +// 0x0000, 0x0000 +//END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FileVersion + PRODUCTVERSION ProductVersion + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Microsoft" + VALUE "FileDescription", "IIS ASP.NET Core Module" + VALUE "FileVersion", FileVersionStr + VALUE "InternalName", "aspnetcore.dll" + VALUE "LegalCopyright", "Copyright (C) 2016" + VALUE "OriginalFilename", "aspnetcore.dll" + VALUE "ProductName", "ASP.NET Core Module" + VALUE "ProductVersion", ProductVersionStr + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_INVALID_PROPERTY "Property name '%s' in system.webServer/aspNetCore section has invalid value '%s' which does not conform to the prescribed format" + IDS_SERVER_ERROR "There was a connection error while trying to route the request." +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/src/AspNetCore/resource.h b/src/AspNetCore/resource.h new file mode 100644 index 0000000000..f31973012a --- /dev/null +++ b/src/AspNetCore/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by aspnetcoremodule.rc +// +#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 +#define IDS_INVALID_PROPERTY 1000 +#define IDS_SERVER_ERROR 1001 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif \ No newline at end of file diff --git a/src/CommonLib/CommonLib.vcxproj b/src/CommonLib/CommonLib.vcxproj new file mode 100644 index 0000000000..33163a1158 --- /dev/null +++ b/src/CommonLib/CommonLib.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {55494E58-E061-4C4C-A0A8-837008E72F85} + Win32Proj + NewCommon + 10.0.15063.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + false + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + C:\AspNetCoreModule\src\IISLib;$(IncludePath) + + + + Use + Level4 + true + Disabled + false + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreadedDebug + false + + + Windows + true + + + + + Use + Level4 + true + Disabled + false + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + false + MultiThreadedDebug + false + + + Windows + true + + + + + Use + Level4 + true + MaxSpeed + true + true + false + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + false + + + Windows + true + true + true + + + + + Use + Level4 + true + MaxSpeed + true + true + false + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + + + MultiThreaded + false + + + Windows + true + true + true + + + ..\iislib + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + + \ No newline at end of file diff --git a/src/CommonLib/application.cpp b/src/CommonLib/application.cpp new file mode 100644 index 0000000000..966ee83d54 --- /dev/null +++ b/src/CommonLib/application.cpp @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" + +APPLICATION::APPLICATION( + _In_ IHttpServer* pHttpServer, + _In_ ASPNETCORE_CONFIG* pConfig) : + m_cRefs(1), + m_pHttpServer(pHttpServer), + m_pConfig(pConfig), + m_status(APPLICATION_STATUS::UNKNOWN) +{ +} + +APPLICATION::~APPLICATION() +{ +} + +APPLICATION_STATUS +APPLICATION::QueryStatus() +{ + return m_status; +} + +ASPNETCORE_CONFIG* +APPLICATION::QueryConfig() +{ + return m_pConfig; +} + +VOID +APPLICATION::ReferenceApplication() +const +{ + InterlockedIncrement(&m_cRefs); +} + +VOID +APPLICATION::DereferenceApplication() +const +{ + DBG_ASSERT(m_cRefs != 0); + + LONG cRefs = 0; + if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) + { + delete this; + } +} \ No newline at end of file diff --git a/src/CommonLib/application.h b/src/CommonLib/application.h new file mode 100644 index 0000000000..880875ca7e --- /dev/null +++ b/src/CommonLib/application.h @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +enum APPLICATION_STATUS +{ + UNKNOWN = 0, + RUNNING, + FAUL +}; + +class ASPNETCORE_CONFIG; + +class APPLICATION +{ +public: + APPLICATION( + _In_ IHttpServer* pHttpServer, + _In_ ASPNETCORE_CONFIG* pConfig); + + virtual + VOID + ShutDown() = 0; + + virtual + ~APPLICATION(); + + APPLICATION_STATUS + QueryStatus(); + + ASPNETCORE_CONFIG* + QueryConfig(); + + VOID + ReferenceApplication() + const; + + VOID + DereferenceApplication() + const; + +protected: + mutable LONG m_cRefs; + APPLICATION_STATUS m_status; + IHttpServer* m_pHttpServer; + ASPNETCORE_CONFIG* m_pConfig; +}; \ No newline at end of file diff --git a/src/CommonLib/aspnetcoreconfig.cxx b/src/CommonLib/aspnetcoreconfig.cxx new file mode 100644 index 0000000000..ea0799e87e --- /dev/null +++ b/src/CommonLib/aspnetcoreconfig.cxx @@ -0,0 +1,522 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "aspnetcoreconfig.h" + +ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() +{ + if (m_pEnvironmentVariables != NULL) + { + m_pEnvironmentVariables->Clear(); + delete m_pEnvironmentVariables; + m_pEnvironmentVariables = NULL; + } +} + +VOID +ASPNETCORE_CONFIG::ReferenceConfiguration( + VOID +) const +{ + InterlockedIncrement(&m_cRefs); +} + + +VOID +ASPNETCORE_CONFIG::DereferenceConfiguration( + VOID +) const +{ + DBG_ASSERT(m_cRefs != 0); + LONG cRefs = 0; + if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) + { + delete this; + } +} + +HRESULT +ASPNETCORE_CONFIG::GetConfig( + _In_ IHttpServer *pHttpServer, + _In_ HTTP_MODULE_ID pModuleId, + _In_ IHttpContext *pHttpContext, + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig +) +{ + HRESULT hr = S_OK; + IHttpApplication *pHttpApplication = pHttpContext->GetApplication(); + ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; + + if (ppAspNetCoreConfig == NULL) + { + hr = E_INVALIDARG; + goto Finished; + } + + *ppAspNetCoreConfig = NULL; + + // potential bug if user sepcific config at virtual dir level + pAspNetCoreConfig = (ASPNETCORE_CONFIG*) + pHttpApplication->GetModuleContextContainer()->GetModuleContext(pModuleId); + + if (pAspNetCoreConfig != NULL) + { + *ppAspNetCoreConfig = pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + goto Finished; + } + + pAspNetCoreConfig = new ASPNETCORE_CONFIG; + if (pAspNetCoreConfig == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pAspNetCoreConfig->Populate(pHttpServer, pHttpContext); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pHttpApplication->GetModuleContextContainer()-> + SetModuleContext(pAspNetCoreConfig, pModuleId); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) + { + delete pAspNetCoreConfig; + + pAspNetCoreConfig = (ASPNETCORE_CONFIG*)pHttpApplication-> + GetModuleContextContainer()-> + GetModuleContext(pModuleId); + + _ASSERT(pAspNetCoreConfig != NULL); + + hr = S_OK; + } + else + { + goto Finished; + } + } + else + { + // set appliction info here instead of inside Populate() + // as the destructor will delete the backend process + hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); + if (FAILED(hr)) + { + goto Finished; + } + } + + *ppAspNetCoreConfig = pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + +Finished: + + if (pAspNetCoreConfig != NULL) + { + delete pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + } + + return hr; +} + +HRESULT +ASPNETCORE_CONFIG::Populate( + IHttpServer *pHttpServer, + IHttpContext *pHttpContext +) +{ + HRESULT hr = S_OK; + STRU strEnvName; + STRU strEnvValue; + STRU strExpandedEnvValue; + STRU strApplicationFullPath; + IAppHostAdminManager *pAdminManager = NULL; + IAppHostElement *pAspNetCoreElement = NULL; + IAppHostElement *pWindowsAuthenticationElement = NULL; + IAppHostElement *pBasicAuthenticationElement = NULL; + IAppHostElement *pAnonymousAuthenticationElement = NULL; + IAppHostElement *pEnvVarList = NULL; + IAppHostElement *pEnvVar = NULL; + IAppHostElementCollection *pEnvVarCollection = NULL; + ULONGLONG ullRawTimeSpan = 0; + ENUM_INDEX index; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + DWORD dwCounter = 0; + DWORD dwPosition = 0; + WCHAR* pszPath = NULL; + BSTR bstrWindowAuthSection = NULL; + BSTR bstrBasicAuthSection = NULL; + BSTR bstrAnonymousAuthSection = NULL; + BSTR bstrAspNetCoreSection = NULL; + + m_pEnvironmentVariables = new ENVIRONMENT_VAR_HASH(); + if (m_pEnvironmentVariables == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = m_pEnvironmentVariables->Initialize(37 /*prime*/))) + { + delete m_pEnvironmentVariables; + m_pEnvironmentVariables = NULL; + goto Finished; + } + + pAdminManager = pHttpServer->GetAdminManager(); + hr = m_struConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); + if (FAILED(hr)) + { + goto Finished; + } + + hr = m_struApplicationPhysicalPath.Copy(pHttpContext->GetApplication()->GetApplicationPhysicalPath()); + if (FAILED(hr)) + { + goto Finished; + } + + pszPath = m_struConfigPath.QueryStr(); + while (pszPath[dwPosition] != NULL) + { + if (pszPath[dwPosition] == '/') + { + dwCounter++; + if (dwCounter == 4) + break; + } + dwPosition++; + } + + if (dwCounter == 4) + { + hr = m_struApplicationVirtualPath.Copy(pszPath + dwPosition); + } + else + { + hr = m_struApplicationVirtualPath.Copy(L"/"); + } + + // Will setup the application virtual path. + if (FAILED(hr)) + { + goto Finished; + } + + bstrWindowAuthSection = SysAllocString(CS_WINDOWS_AUTHENTICATION_SECTION); + if (bstrWindowAuthSection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pAdminManager->GetAdminSection(bstrWindowAuthSection, + m_struConfigPath.QueryStr(), + &pWindowsAuthenticationElement); + if (FAILED(hr)) + { + // assume the corresponding authen was not enabled + // as the section may get deleted by user in some HWC case + // ToDo: log a warning to event log + m_fWindowsAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pWindowsAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fWindowsAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + bstrBasicAuthSection = SysAllocString(CS_BASIC_AUTHENTICATION_SECTION); + if (bstrBasicAuthSection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + hr = pAdminManager->GetAdminSection(bstrBasicAuthSection, + m_struConfigPath.QueryStr(), + &pBasicAuthenticationElement); + if (FAILED(hr)) + { + m_fBasicAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pBasicAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fBasicAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + bstrAnonymousAuthSection = SysAllocString(CS_ANONYMOUS_AUTHENTICATION_SECTION); + if (bstrAnonymousAuthSection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + hr = pAdminManager->GetAdminSection(bstrAnonymousAuthSection, + m_struConfigPath.QueryStr(), + &pAnonymousAuthenticationElement); + if (FAILED(hr)) + { + m_fAnonymousAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pAnonymousAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fAnonymousAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + bstrAspNetCoreSection = SysAllocString(CS_ASPNETCORE_SECTION); + if (bstrAspNetCoreSection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + hr = pAdminManager->GetAdminSection(bstrAspNetCoreSection, + m_struConfigPath.QueryStr(), + &pAspNetCoreElement); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_EXE_PATH, + &m_struProcessPath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_HOSTING_MODEL, + &m_strHostingModel); + if (FAILED(hr)) + { + // Swallow this error for backward compatability + // Use default behavior for empty string + hr = S_OK; + } + + if (m_strHostingModel.IsEmpty() || m_strHostingModel.Equals(L"outofprocess", TRUE)) + { + m_hostingModel = HOSTING_OUT_PROCESS; + } + else if (m_strHostingModel.Equals(L"inprocess", TRUE)) + { + m_hostingModel = HOSTING_IN_PROCESS; + } + else + { + // block unknown hosting value + hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_ARGUMENTS, + &m_struArguments); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE, + &m_dwRapidFailsPerMinute); + if (FAILED(hr)) + { + goto Finished; + } + + // + // rapidFailsPerMinute cannot be greater than 100. + // + if (m_dwRapidFailsPerMinute > MAX_RAPID_FAILS_PER_MINUTE) + { + m_dwRapidFailsPerMinute = MAX_RAPID_FAILS_PER_MINUTE; + } + + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESSES_PER_APPLICATION, + &m_dwProcessesPerApplication); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementDWORDProperty( + pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT, + &m_dwStartupTimeLimitInMS + ); + if (FAILED(hr)) + { + goto Finished; + } + + m_dwStartupTimeLimitInMS *= MILLISECONDS_IN_ONE_SECOND; + + hr = GetElementDWORDProperty( + pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT, + &m_dwShutdownTimeLimitInMS + ); + if (FAILED(hr)) + { + goto Finished; + } + m_dwShutdownTimeLimitInMS *= MILLISECONDS_IN_ONE_SECOND; + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_FORWARD_WINDOWS_AUTH_TOKEN, + &m_fForwardWindowsAuthToken); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE, + &m_fDisableStartUpErrorPage); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementRawTimeSpanProperty( + pAspNetCoreElement, + CS_ASPNETCORE_WINHTTP_REQUEST_TIMEOUT, + &ullRawTimeSpan + ); + if (FAILED(hr)) + { + goto Finished; + } + + m_dwRequestTimeoutInMS = (DWORD)TIMESPAN_IN_MILLISECONDS(ullRawTimeSpan); + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_ENABLED, + &m_fStdoutLogEnabled); + if (FAILED(hr)) + { + goto Finished; + } + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_FILE, + &m_struStdoutLogFile); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementChildByName(pAspNetCoreElement, + CS_ASPNETCORE_ENVIRONMENT_VARIABLES, + &pEnvVarList); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pEnvVarList->get_Collection(&pEnvVarCollection); + if (FAILED(hr)) + { + goto Finished; + } + + for (hr = FindFirstElement(pEnvVarCollection, &index, &pEnvVar); + SUCCEEDED(hr); + hr = FindNextElement(pEnvVarCollection, &index, &pEnvVar)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + if (FAILED(hr = GetElementStringProperty(pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME, + &strEnvName)) || + FAILED(hr = GetElementStringProperty(pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE, + &strEnvValue)) || + FAILED(hr = strEnvName.Append(L"=")) || + FAILED(hr = STRU::ExpandEnvironmentVariables(strEnvValue.QueryStr(), &strExpandedEnvValue))) + { + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(strEnvName.QueryStr(), strExpandedEnvValue.QueryStr())) || + FAILED(hr = m_pEnvironmentVariables->InsertRecord(pEntry))) + { + goto Finished; + } + strEnvName.Reset(); + strEnvValue.Reset(); + strExpandedEnvValue.Reset(); + pEnvVar->Release(); + pEnvVar = NULL; + pEntry->Dereference(); + pEntry = NULL; + } + +Finished: + + if (pAspNetCoreElement != NULL) + { + pAspNetCoreElement->Release(); + pAspNetCoreElement = NULL; + } + + if (pEnvVarList != NULL) + { + pEnvVarList->Release(); + pEnvVarList = NULL; + } + + if (pEnvVar != NULL) + { + pEnvVar->Release(); + pEnvVar = NULL; + } + + if (pEnvVarCollection != NULL) + { + pEnvVarCollection->Release(); + pEnvVarCollection = NULL; + } + + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + + return hr; +} \ No newline at end of file diff --git a/src/CommonLib/aspnetcoreconfig.h b/src/CommonLib/aspnetcoreconfig.h new file mode 100644 index 0000000000..ffb4928cf9 --- /dev/null +++ b/src/CommonLib/aspnetcoreconfig.h @@ -0,0 +1,286 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#define CS_ROOTWEB_CONFIG L"MACHINE/WEBROOT/APPHOST/" +#define CS_ROOTWEB_CONFIG_LEN _countof(CS_ROOTWEB_CONFIG)-1 +#define CS_ASPNETCORE_SECTION L"system.webServer/aspNetCore" +#define CS_WINDOWS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/windowsAuthentication" +#define CS_BASIC_AUTHENTICATION_SECTION L"system.webServer/security/authentication/basicAuthentication" +#define CS_ANONYMOUS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/anonymousAuthentication" +#define CS_AUTHENTICATION_ENABLED L"enabled" +#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" +#define CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT L"startupTimeLimit" +#define CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT L"shutdownTimeLimit" +#define CS_ASPNETCORE_WINHTTP_REQUEST_TIMEOUT L"requestTimeout" +#define CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE L"rapidFailsPerMinute" +#define CS_ASPNETCORE_STDOUT_LOG_ENABLED L"stdoutLogEnabled" +#define CS_ASPNETCORE_STDOUT_LOG_FILE L"stdoutLogFile" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLES L"environmentVariables" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE L"environmentVariable" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME L"name" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE L"value" +#define CS_ASPNETCORE_PROCESSES_PER_APPLICATION L"processesPerApplication" +#define CS_ASPNETCORE_FORWARD_WINDOWS_AUTH_TOKEN L"forwardWindowsAuthToken" +#define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE L"recycleOnFileChange" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE L"file" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE_PATH L"path" +#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" + +#define MAX_RAPID_FAILS_PER_MINUTE 100 +#define MILLISECONDS_IN_ONE_SECOND 1000 +#define MIN_PORT 1025 +#define MAX_PORT 48000 + +#define TIMESPAN_IN_MILLISECONDS(x) ((x)/((LONGLONG)(10000))) +#define TIMESPAN_IN_SECONDS(x) ((TIMESPAN_IN_MILLISECONDS(x))/((LONGLONG)(1000))) +#define TIMESPAN_IN_MINUTES(x) ((TIMESPAN_IN_SECONDS(x))/((LONGLONG)(60))) + +//#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) + +#include "stdafx.h" + +enum APP_HOSTING_MODEL +{ + HOSTING_UNKNOWN = 0, + HOSTING_IN_PROCESS, + HOSTING_OUT_PROCESS +}; + +class ASPNETCORE_CONFIG : IHttpStoredContext +{ +public: + + virtual + ~ASPNETCORE_CONFIG(); + + VOID + CleanupStoredContext() + { + DereferenceConfiguration(); + } + + static + HRESULT + GetConfig( + _In_ IHttpServer *pHttpServer, + _In_ HTTP_MODULE_ID pModuleId, + _In_ IHttpContext *pHttpContext, + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + ); + + ENVIRONMENT_VAR_HASH* + QueryEnvironmentVariables( + VOID + ) + { + return m_pEnvironmentVariables; + } + + DWORD + QueryRapidFailsPerMinute( + VOID + ) + { + return m_dwRapidFailsPerMinute; + } + + DWORD + QueryStartupTimeLimitInMS( + VOID + ) + { + return m_dwStartupTimeLimitInMS; + } + + DWORD + QueryShutdownTimeLimitInMS( + VOID + ) + { + return m_dwShutdownTimeLimitInMS; + } + + DWORD + QueryProcessesPerApplication( + VOID + ) + { + return m_dwProcessesPerApplication; + } + + DWORD + QueryRequestTimeoutInMS( + VOID + ) + { + return m_dwRequestTimeoutInMS; + } + + STRU* + QueryArguments( + VOID + ) + { + return &m_struArguments; + } + + STRU* + QueryApplicationPath( + VOID + ) + { + return &m_struApplication; + } + + STRU* + QueryApplicationPhysicalPath( + VOID + ) + { + return &m_struApplicationPhysicalPath; + } + + STRU* + QueryApplicationVirtualPath( + VOID + ) + { + return &m_struApplicationVirtualPath; + } + + STRU* + QueryProcessPath( + VOID + ) + { + return &m_struProcessPath; + } + + APP_HOSTING_MODEL + QueryHostingModel( + VOID + ) + { + return m_hostingModel; + } + + BOOL + QueryStdoutLogEnabled() + { + return m_fStdoutLogEnabled; + } + + BOOL + QueryForwardWindowsAuthToken() + { + return m_fForwardWindowsAuthToken; + } + + BOOL + QueryWindowsAuthEnabled() + { + return m_fWindowsAuthEnabled; + } + + BOOL + QueryBasicAuthEnabled() + { + return m_fBasicAuthEnabled; + } + + BOOL + QueryAnonymousAuthEnabled() + { + return m_fAnonymousAuthEnabled; + } + + BOOL + QueryDisableStartUpErrorPage() + { + return m_fDisableStartUpErrorPage; + } + + STRU* + QueryStdoutLogFile() + { + return &m_struStdoutLogFile; + } + + STRU* + QueryConfigPath() + { + return &m_struConfigPath; + } + + STRU* + QueryHostfxrPath() + { + return &m_struHostFxrPath; + } + + BOOL + QueryIsStandAloneApplication( + VOID + ) + { + return m_fIsStandAloneApplication; + } + + VOID + ReferenceConfiguration( + VOID + ) const; + + VOID + DereferenceConfiguration( + VOID + ) const; + +private: + + // + // private constructor + // + ASPNETCORE_CONFIG(): + m_fStdoutLogEnabled( FALSE ), + m_pEnvironmentVariables( NULL ), + m_cRefs( 1 ), + m_hostingModel( HOSTING_UNKNOWN ) + { + } + + HRESULT + Populate( + IHttpServer *pHttpServer, + IHttpContext *pHttpContext + ); + + mutable LONG m_cRefs; + + DWORD m_dwRequestTimeoutInMS; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + DWORD m_dwRapidFailsPerMinute; + DWORD m_dwProcessesPerApplication; + STRU m_struArguments; + STRU m_struProcessPath; + STRU m_struStdoutLogFile; + STRU m_struApplication; + STRU m_struApplicationPhysicalPath; + STRU m_struApplicationVirtualPath; + STRU m_struConfigPath; + STRU m_strHostingModel; + STRU m_struHostFxrPath; + BOOL m_fStdoutLogEnabled; + BOOL m_fForwardWindowsAuthToken; + BOOL m_fDisableStartUpErrorPage; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + BOOL m_fIsStandAloneApplication; + APP_HOSTING_MODEL m_hostingModel; + ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; +}; diff --git a/src/CommonLib/debugutil.h b/src/CommonLib/debugutil.h new file mode 100644 index 0000000000..16fce88edd --- /dev/null +++ b/src/CommonLib/debugutil.h @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#define ASPNETCORE_DEBUG_FLAG_INFO 0x00000001 +#define ASPNETCORE_DEBUG_FLAG_WARNING 0x00000002 +#define ASPNETCORE_DEBUG_FLAG_ERROR 0x00000004 + +extern DWORD g_dwAspNetCoreDebugFlags; + +static +BOOL +IfDebug( + DWORD dwFlag + ) +{ + return ( dwFlag & g_dwAspNetCoreDebugFlags ); +} + +static +VOID +DebugPrint( + DWORD dwFlag, + LPCSTR szString + ) +{ + STACK_STRA (strOutput, 256); + HRESULT hr = S_OK; + + if ( IfDebug( dwFlag ) ) + { + hr = strOutput.SafeSnprintf( + "[aspnetcore.dll] %s\r\n", + szString ); + + if (FAILED (hr)) + { + goto Finished; + } + + OutputDebugStringA( strOutput.QueryStr() ); + } + +Finished: + + return; +} + +static +VOID +DebugPrintf( +DWORD dwFlag, +LPCSTR szFormat, +... +) +{ + STACK_STRA (strCooked,256); + + va_list args; + HRESULT hr = S_OK; + + if ( IfDebug( dwFlag ) ) + { + va_start( args, szFormat ); + + hr = strCooked.SafeVsnprintf(szFormat, args ); + + va_end( args ); + + if (FAILED (hr)) + { + goto Finished; + } + + DebugPrint( dwFlag, strCooked.QueryStr() ); + } + +Finished: + return; +} + diff --git a/src/CommonLib/environmentvariablehash.h b/src/CommonLib/environmentvariablehash.h new file mode 100644 index 0000000000..797c63c21c --- /dev/null +++ b/src/CommonLib/environmentvariablehash.h @@ -0,0 +1,155 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// The key used for hash-table lookups, consists of the port on which the http process is created. +// + +class ENVIRONMENT_VAR_ENTRY +{ +public: + ENVIRONMENT_VAR_ENTRY(): + _cRefs(1) + { + } + + HRESULT + Initialize( + PCWSTR pszName, + PCWSTR pszValue + ) + { + HRESULT hr = S_OK; + if (FAILED(hr = _strName.Copy(pszName)) || + FAILED(hr = _strValue.Copy(pszValue))) + { + } + return hr; + } + + VOID + Reference() const + { + InterlockedIncrement(&_cRefs); + } + + VOID + Dereference() const + { + if (InterlockedDecrement(&_cRefs) == 0) + { + delete this; + } + } + + PWSTR const + QueryName() + { + return _strName.QueryStr(); + } + + PWSTR const + QueryValue() + { + return _strValue.QueryStr(); + } + +private: + ~ENVIRONMENT_VAR_ENTRY() + { + } + + STRU _strName; + STRU _strValue; + mutable LONG _cRefs; +}; + +class ENVIRONMENT_VAR_HASH : public HASH_TABLE +{ +public: + ENVIRONMENT_VAR_HASH() + {} + + PWSTR + ExtractKey( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + return pEntry->QueryName(); + } + + DWORD + CalcKeyHash( + PWSTR pszName + ) + { + return HashStringNoCase(pszName); + } + + BOOL + EqualKeys( + PWSTR pszName1, + PWSTR pszName2 + ) + { + return (_wcsicmp(pszName1, pszName2) == 0); + } + + VOID + ReferenceRecord( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + pEntry->Reference(); + } + + VOID + DereferenceRecord( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + pEntry->Dereference(); + } + + static + VOID + CopyToMultiSz( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + STRU strTemp; + MULTISZ *pMultiSz = static_cast(pvData); + DBG_ASSERT(pMultiSz); + DBG_ASSERT(pEntry); + strTemp.Copy(pEntry->QueryName()); + strTemp.Append(pEntry->QueryValue()); + pMultiSz->Append(strTemp.QueryStr()); + } + + static + VOID + CopyToTable( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + // best effort copy, ignore the failure + ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pNewEntry != NULL) + { + pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); + ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); + DBG_ASSERT(pHash); + pHash->InsertRecord(pNewEntry); + // Need to dereference as InsertRecord references it now + pNewEntry->Dereference(); + } + } + +private: + ENVIRONMENT_VAR_HASH(const ENVIRONMENT_VAR_HASH &); + void operator=(const ENVIRONMENT_VAR_HASH &); +}; diff --git a/src/CommonLib/fx_ver.cxx b/src/CommonLib/fx_ver.cxx new file mode 100644 index 0000000000..7aeb0999c0 --- /dev/null +++ b/src/CommonLib/fx_ver.cxx @@ -0,0 +1,192 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "stdafx.h" + +fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build) + : m_major(major) + , m_minor(minor) + , m_patch(patch) + , m_pre(pre) + , m_build(build) +{ +} + +fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre) + : fx_ver_t(major, minor, patch, pre, TEXT("")) +{ +} + +fx_ver_t::fx_ver_t(int major, int minor, int patch) + : fx_ver_t(major, minor, patch, TEXT(""), TEXT("")) +{ +} + +bool fx_ver_t::operator ==(const fx_ver_t& b) const +{ + return compare(*this, b) == 0; +} + +bool fx_ver_t::operator !=(const fx_ver_t& b) const +{ + return !operator ==(b); +} + +bool fx_ver_t::operator <(const fx_ver_t& b) const +{ + return compare(*this, b) < 0; +} + +bool fx_ver_t::operator >(const fx_ver_t& b) const +{ + return compare(*this, b) > 0; +} + +bool fx_ver_t::operator <=(const fx_ver_t& b) const +{ + return compare(*this, b) <= 0; +} + +bool fx_ver_t::operator >=(const fx_ver_t& b) const +{ + return compare(*this, b) >= 0; +} + +std::wstring fx_ver_t::as_str() const +{ + std::wstringstream stream; + stream << m_major << TEXT(".") << m_minor << TEXT(".") << m_patch; + if (!m_pre.empty()) + { + stream << m_pre; + } + if (!m_build.empty()) + { + stream << TEXT("+") << m_build; + } + return stream.str(); +} + +/* static */ +int fx_ver_t::compare(const fx_ver_t&a, const fx_ver_t& b) +{ + // compare(u.v.w-p+b, x.y.z-q+c) + if (a.m_major != b.m_major) + { + return (a.m_major > b.m_major) ? 1 : -1; + } + + if (a.m_minor != b.m_minor) + { + return (a.m_minor > b.m_minor) ? 1 : -1; + } + + if (a.m_patch != b.m_patch) + { + return (a.m_patch > b.m_patch) ? 1 : -1; + } + + if (a.m_pre.empty() != b.m_pre.empty()) + { + // Either a is empty or b is empty + return a.m_pre.empty() ? 1 : -1; + } + + // Either both are empty or both are non-empty (may be equal) + int pre_cmp = a.m_pre.compare(b.m_pre); + if (pre_cmp != 0) + { + return pre_cmp; + } + + return a.m_build.compare(b.m_build); +} + +bool try_stou(const std::wstring& str, unsigned* num) +{ + if (str.empty()) + { + return false; + } + if (str.find_first_not_of(TEXT("0123456789")) != std::wstring::npos) + { + return false; + } + *num = (unsigned)std::stoul(str); + return true; +} + +bool parse_internal(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production) +{ + size_t maj_start = 0; + size_t maj_sep = ver.find(TEXT('.')); + if (maj_sep == std::wstring::npos) + { + return false; + } + unsigned major = 0; + if (!try_stou(ver.substr(maj_start, maj_sep), &major)) + { + return false; + } + + size_t min_start = maj_sep + 1; + size_t min_sep = ver.find(TEXT('.'), min_start); + if (min_sep == std::wstring::npos) + { + return false; + } + + unsigned minor = 0; + if (!try_stou(ver.substr(min_start, min_sep - min_start), &minor)) + { + return false; + } + + unsigned patch = 0; + size_t pat_start = min_sep + 1; + size_t pat_sep = ver.find_first_not_of(TEXT("0123456789"), pat_start); + if (pat_sep == std::wstring::npos) + { + if (!try_stou(ver.substr(pat_start), &patch)) + { + return false; + } + + *fx_ver = fx_ver_t(major, minor, patch); + return true; + } + + if (parse_only_production) + { + // This is a prerelease or has build suffix. + return false; + } + + if (!try_stou(ver.substr(pat_start, pat_sep - pat_start), &patch)) + { + return false; + } + + size_t pre_start = pat_sep; + size_t pre_sep = ver.find(TEXT('+'), pre_start); + if (pre_sep == std::wstring::npos) + { + *fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start)); + return true; + } + else + { + size_t build_start = pre_sep + 1; + *fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start, pre_sep - pre_start), ver.substr(build_start)); + return true; + } +} + +/* static */ +bool fx_ver_t::parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production) +{ + bool valid = parse_internal(ver, fx_ver, parse_only_production); + assert(!valid || fx_ver->as_str() == ver); + return valid; +} \ No newline at end of file diff --git a/src/CommonLib/fx_ver.h b/src/CommonLib/fx_ver.h new file mode 100644 index 0000000000..1626b2697c --- /dev/null +++ b/src/CommonLib/fx_ver.h @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not +// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11 +struct fx_ver_t +{ + fx_ver_t(int major, int minor, int patch); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build); + + int get_major() const { return m_major; } + int get_minor() const { return m_minor; } + int get_patch() const { return m_patch; } + + void set_major(int m) { m_major = m; } + void set_minor(int m) { m_minor = m; } + void set_patch(int p) { m_patch = p; } + + bool is_prerelease() const { return !m_pre.empty(); } + + std::wstring as_str() const; + std::wstring prerelease_glob() const; + std::wstring patch_glob() const; + + bool operator ==(const fx_ver_t& b) const; + bool operator !=(const fx_ver_t& b) const; + bool operator <(const fx_ver_t& b) const; + bool operator >(const fx_ver_t& b) const; + bool operator <=(const fx_ver_t& b) const; + bool operator >=(const fx_ver_t& b) const; + + static bool parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production = false); + +private: + int m_major; + int m_minor; + int m_patch; + std::wstring m_pre; + std::wstring m_build; + + static int compare(const fx_ver_t&a, const fx_ver_t& b); +}; + diff --git a/src/CommonLib/hostfxr_utility.cpp b/src/CommonLib/hostfxr_utility.cpp new file mode 100644 index 0000000000..a5ec15fabb --- /dev/null +++ b/src/CommonLib/hostfxr_utility.cpp @@ -0,0 +1,259 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" + +HOSTFXR_UTILITY::HOSTFXR_UTILITY() +{ +} + + +HOSTFXR_UTILITY::~HOSTFXR_UTILITY() +{ +} + +HRESULT +HOSTFXR_UTILITY::FindHostFxrDll( + ASPNETCORE_CONFIG *pConfig, + STRU* struHostFxrDllLocation, + BOOL* fStandAlone +) +{ + HRESULT hr = S_OK; + + // If the process path isn't dotnet, assume we are a standalone appliction. + // TODO: this should be a path equivalent check + if (!(pConfig->QueryProcessPath()->Equals(L".\\dotnet") + || pConfig->QueryProcessPath()->Equals(L"dotnet") + || pConfig->QueryProcessPath()->Equals(L".\\dotnet.exe") + || pConfig->QueryProcessPath()->Equals(L"dotnet.exe"))) + { + // hostfxr is in the same folder, parse and use it. + hr = GetStandaloneHostfxrLocation(struHostFxrDllLocation, pConfig); + *fStandAlone = TRUE; + } + else + { + hr = GetPortableHostfxrLocation(struHostFxrDllLocation); + fStandAlone = FALSE; + } + + return hr; +} + +// +// Runs a standalone appliction. +// The folder structure looks like this: +// Application/ +// hostfxr.dll +// Application.exe +// Application.dll +// etc. +// We get the full path to hostfxr.dll and Application.dll and run hostfxr_main, +// passing in Application.dll. +// Assuming we don't need Application.exe as the dll is the actual application. +// +HRESULT +HOSTFXR_UTILITY::GetStandaloneHostfxrLocation( + STRU* struHostfxrPath, + ASPNETCORE_CONFIG *pConfig +) +{ + HRESULT hr = S_OK; + HANDLE hFileHandle = INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES saAttr; + + // Get the full path to the exe and check if it exists + if (FAILED(hr = UTILITY::ConvertPathToFullPath(L"\\hostfxr.dll", + pConfig->QueryApplicationPhysicalPath()->QueryStr(), + struHostfxrPath))) + { + goto Finished; + } + + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + hFileHandle = CreateFile(struHostfxrPath->QueryStr(), + GENERIC_READ, + FILE_SHARE_READ, + &saAttr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hFileHandle == INVALID_HANDLE_VALUE) + { + // Treat access isseu as File not found + hr = ERROR_FILE_NOT_FOUND; + goto Finished; + } + else + { + CloseHandle(hFileHandle); + } + +Finished: + return hr; +} + +HRESULT +HOSTFXR_UTILITY::GetPortableHostfxrLocation( + STRU* struHostfxrPath +) +{ + HRESULT hr = S_OK; + + STRU struSystemPathVariable; + STRU strDotnetExeLocation; + STRU strHostFxrSearchExpression; + STRU strHighestDotnetVersion; + PWSTR pwzDelimeterContext = NULL; + PCWSTR pszDotnetLocation = NULL; + PCWSTR pszDotnetExeString(L"dotnet.exe"); + DWORD dwCopyLength; + BOOL fFound = FALSE; + HANDLE hFileHandle = INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES saAttr; + std::vector vVersionFolders; + + if (FAILED(hr)) + { + goto Finished; + } + + // Get the System PATH value. + if (!UTILITY::GetSystemPathVariable(L"PATH", &struSystemPathVariable)) + { + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + // Split on ';', checking to see if dotnet.exe exists in any folders. + pszDotnetLocation = wcstok_s(struSystemPathVariable.QueryStr(), L";", &pwzDelimeterContext); + while (pszDotnetLocation != NULL) + { + dwCopyLength = (DWORD) wcsnlen_s(pszDotnetLocation, 260); + + // We store both the exe and folder locations as we eventually need to check inside of host\\fxr + // which doesn't need the dotnet.exe portion of the string + hr = strDotnetExeLocation.Copy(pszDotnetLocation, dwCopyLength); + if (FAILED(hr)) + { + goto Finished; + } + + if (dwCopyLength > 0 && pszDotnetLocation[dwCopyLength - 1] != L'\\') + { + hr = strDotnetExeLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = struHostfxrPath->Copy(strDotnetExeLocation); + if (FAILED(hr)) + { + goto Finished; + } + + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Finished; + } + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + hFileHandle = CreateFile(strDotnetExeLocation.QueryStr(), + GENERIC_READ, + FILE_SHARE_READ, + &saAttr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hFileHandle != INVALID_HANDLE_VALUE) + { + // means we found the folder with a dotnet.exe inside of it. + fFound = TRUE; + CloseHandle(hFileHandle); + break; + } + pszDotnetLocation = wcstok_s(NULL, L";", &pwzDelimeterContext); + } + + if (!fFound) + { + // could not find dotnet.exe, error out + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + hr = struHostfxrPath->Append(L"host\\fxr"); + if (FAILED(hr)) + { + goto Finished; + } + + if (!UTILITY::DirectoryExists(struHostfxrPath)) + { + // error, not found the folder + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + // Find all folders under host\\fxr\\ for version numbers. + hr = strHostFxrSearchExpression.Copy(struHostfxrPath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = strHostFxrSearchExpression.Append(L"\\*"); + if (FAILED(hr)) + { + goto Finished; + } + + // As we use the logic from core-setup, we are opting to use std here. + // TODO remove all uses of std? + UTILITY::FindDotNetFolders(strHostFxrSearchExpression.QueryStr(), &vVersionFolders); + + if (vVersionFolders.size() == 0) + { + // no core framework was found + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + hr = UTILITY::FindHighestDotNetVersion(vVersionFolders, &strHighestDotnetVersion); + if (FAILED(hr)) + { + goto Finished; + } + hr = struHostfxrPath->Append(L"\\"); + if (FAILED(hr)) + { + goto Finished; + } + + hr = struHostfxrPath->Append(strHighestDotnetVersion.QueryStr()); + if (FAILED(hr)) + { + goto Finished; + + } + + hr = struHostfxrPath->Append(L"\\hostfxr.dll"); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + return hr; +} \ No newline at end of file diff --git a/src/CommonLib/hostfxr_utility.h b/src/CommonLib/hostfxr_utility.h new file mode 100644 index 0000000000..adff785388 --- /dev/null +++ b/src/CommonLib/hostfxr_utility.h @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class HOSTFXR_UTILITY +{ +public: + HOSTFXR_UTILITY(); + ~HOSTFXR_UTILITY(); + + static + HRESULT + FindHostFxrDll( + ASPNETCORE_CONFIG *pConfig, + STRU* struHostFxrDllLocation, + BOOL* fStandAlone + ); + + static + HRESULT + GetStandaloneHostfxrLocation( + STRU* struHostfxrPath, + ASPNETCORE_CONFIG *pConfig + ); + + static + HRESULT + GetPortableHostfxrLocation( + STRU* struHostfxrPath + ); +}; + diff --git a/src/CommonLib/requesthandler.cxx b/src/CommonLib/requesthandler.cxx new file mode 100644 index 0000000000..bcf1887d35 --- /dev/null +++ b/src/CommonLib/requesthandler.cxx @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" + +REQUEST_HANDLER::REQUEST_HANDLER( + _In_ IHttpContext *pW3Context, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication) + : m_cRefs(1) +{ + m_pW3Context = pW3Context; + m_pApplication = pApplication; + m_pModuleId = *pModuleId; +} + + +REQUEST_HANDLER::~REQUEST_HANDLER() +{ +} + +VOID +REQUEST_HANDLER::ReferenceRequestHandler( + VOID +) const +{ + InterlockedIncrement(&m_cRefs); +} + + +VOID +REQUEST_HANDLER::DereferenceRequestHandler( + VOID +) const +{ + DBG_ASSERT(m_cRefs != 0); + + LONG cRefs = 0; + if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) + { + delete this; + } + +} diff --git a/src/CommonLib/requesthandler.h b/src/CommonLib/requesthandler.h new file mode 100644 index 0000000000..28f4fb725e --- /dev/null +++ b/src/CommonLib/requesthandler.h @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "stdafx.h" +#include "application.h" + +// +// Abstract class +// +class REQUEST_HANDLER +{ +public: + REQUEST_HANDLER( + _In_ IHttpContext *pW3Context, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication + ); + + virtual + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler() = 0; + + virtual + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ) = 0; + + virtual + VOID + TerminateRequest( + bool fClientInitiated + ) = 0; + + virtual + ~REQUEST_HANDLER( + VOID + ); + + VOID + ReferenceRequestHandler( + VOID + ) const; + + virtual + VOID + DereferenceRequestHandler( + VOID + ) const; + +protected: + mutable LONG m_cRefs; + IHttpContext* m_pW3Context; + APPLICATION* m_pApplication; + HTTP_MODULE_ID m_pModuleId; +}; \ No newline at end of file diff --git a/src/CommonLib/stdafx.cpp b/src/CommonLib/stdafx.cpp new file mode 100644 index 0000000000..6850f4cb6e --- /dev/null +++ b/src/CommonLib/stdafx.cpp @@ -0,0 +1,4 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" diff --git a/src/CommonLib/stdafx.h b/src/CommonLib/stdafx.h new file mode 100644 index 0000000000..5b40fe3fbc --- /dev/null +++ b/src/CommonLib/stdafx.h @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include +#include +#include +#include "..\IISLib\hashtable.h" +#include "..\IISLib\stringu.h" +#include "..\IISLib\stringa.h" +#include "..\IISLib\multisz.h" +#include "..\IISLib\dbgutil.h" +#include "..\IISLib\ahutil.h" +#include "..\IISLib\hashfn.h" +#include "environmentvariablehash.h" +#include "utility.h" +#include "aspnetcoreconfig.h" +#include "application.h" +#include "requesthandler.h" +#include "fx_ver.h" +#include "hostfxr_utility.h" diff --git a/src/CommonLib/targetver.h b/src/CommonLib/targetver.h new file mode 100644 index 0000000000..617620b372 --- /dev/null +++ b/src/CommonLib/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include \ No newline at end of file diff --git a/src/CommonLib/utility.cxx b/src/CommonLib/utility.cxx new file mode 100644 index 0000000000..10876c104e --- /dev/null +++ b/src/CommonLib/utility.cxx @@ -0,0 +1,609 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include"stdafx.h" + +// static +HRESULT +UTILITY::SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl +) +/*++ + +Routine Description: + + Split the URL specified for forwarding into its specific components + The format of the URL looks like + http[s]://destination[:port]/path + when port is omitted, the default port for that specific protocol is used + when host is omitted, it gets the same value as the destination + +Arguments: + + pszDestinationUrl - the url to be split up + pfSecure - SSL to be used in forwarding? + pstrDestination - destination + pDestinationPort - port + pstrUrl - URL + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr; + + // + // First determine if the target is secure + // + if (_wcsnicmp(pszDestinationUrl, L"http://", 7) == 0) + { + *pfSecure = FALSE; + pszDestinationUrl += 7; + } + else if (_wcsnicmp(pszDestinationUrl, L"https://", 8) == 0) + { + *pfSecure = TRUE; + pszDestinationUrl += 8; + } + else + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + if (*pszDestinationUrl == L'\0') + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + // + // Find the 3rd slash corresponding to the url + // + LPCWSTR pszSlash = wcschr(pszDestinationUrl, L'/'); + if (pszSlash == NULL) + { + if (FAILED(hr = pstrUrl->Copy(L"/", 1)) || + FAILED(hr = pstrDestination->Copy(pszDestinationUrl))) + { + return hr; + } + } + else + { + if (FAILED(hr = pstrUrl->Copy(pszSlash)) || + FAILED(hr = pstrDestination->Copy(pszDestinationUrl, + (DWORD)(pszSlash - pszDestinationUrl)))) + { + return hr; + } + } + + return S_OK; +} + +// Change a hexadecimal digit to its numerical equivalent +#define TOHEX( ch ) \ + ((ch) > L'9' ? \ + (ch) >= L'a' ? \ + (ch) - L'a' + 10 : \ + (ch) - L'A' + 10 \ + : (ch) - L'0') + +// static +HRESULT +UTILITY::UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + bool fCopyQuery, + STRA * pstrResult +) +{ + HRESULT hr; + CHAR pch[2]; + pch[1] = '\0'; + DWORD cchStart = 0; + DWORD index = 0; + + while (index < cchUrl && + (fCopyQuery || pszUrl[index] != L'?')) + { + switch (pszUrl[index]) + { + case L'%': + if (iswxdigit(pszUrl[index+1]) && iswxdigit(pszUrl[index+2])) + { + if (index > cchStart && + FAILED(hr = pstrResult->AppendW(pszUrl + cchStart, + index - cchStart))) + { + return hr; + } + cchStart = index+3; + + pch[0] = static_cast(TOHEX(pszUrl[index+1]) * 16 + + TOHEX(pszUrl[index+2])); + if (FAILED(hr = pstrResult->Append(pch, 1))) + { + return hr; + } + index += 3; + break; + } + + __fallthrough; + default: + index++; + } + } + + if (index > cchStart) + { + return pstrResult->AppendW(pszUrl + cchStart, + index - cchStart); + } + + return S_OK; +} + +// static +HRESULT +UTILITY::UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + STRU * pstrResult +) +{ + HRESULT hr; + WCHAR pch[2]; + pch[1] = L'\0'; + DWORD cchStart = 0; + DWORD index = 0; + bool fInQuery = FALSE; + + while (index < cchUrl) + { + switch (pszUrl[index]) + { + case L'%': + if (iswxdigit(pszUrl[index+1]) && iswxdigit(pszUrl[index+2])) + { + if (index > cchStart && + FAILED(hr = pstrResult->Append(pszUrl + cchStart, + index - cchStart))) + { + return hr; + } + cchStart = index+3; + + pch[0] = static_cast(TOHEX(pszUrl[index+1]) * 16 + + TOHEX(pszUrl[index+2])); + if (FAILED(hr = pstrResult->Append(pch, 1))) + { + return hr; + } + index += 3; + if (pch[0] == L'?') + { + fInQuery = TRUE; + } + break; + } + + index++; + break; + + case L'/': + if (fInQuery) + { + if (index > cchStart && + FAILED(hr = pstrResult->Append(pszUrl + cchStart, + index - cchStart))) + { + return hr; + } + cchStart = index+1; + + if (FAILED(hr = pstrResult->Append(L"\\", 1))) + { + return hr; + } + index += 1; + break; + } + + __fallthrough; + default: + index++; + } + } + + if (index > cchStart) + { + return pstrResult->Append(pszUrl + cchStart, + index - cchStart); + } + + return S_OK; +} + +HRESULT +UTILITY::EscapeAbsPath( + IHttpRequest * pRequest, + STRU * strEscapedUrl +) +{ + HRESULT hr = S_OK; + STRU strAbsPath; + LPCWSTR pszAbsPath = NULL; + LPCWSTR pszFindStr = NULL; + + hr = strAbsPath.Copy( pRequest->GetRawHttpRequest()->CookedUrl.pAbsPath, + pRequest->GetRawHttpRequest()->CookedUrl.AbsPathLength / sizeof(WCHAR) ); + if(FAILED(hr)) + { + goto Finished; + } + + pszAbsPath = strAbsPath.QueryStr(); + pszFindStr = wcschr(pszAbsPath, L'?'); + + while(pszFindStr != NULL) + { + strEscapedUrl->Append( pszAbsPath, pszFindStr - pszAbsPath); + strEscapedUrl->Append(L"%3F"); + pszAbsPath = pszFindStr + 1; + pszFindStr = wcschr(pszAbsPath, L'?'); + } + + strEscapedUrl->Append(pszAbsPath); + strEscapedUrl->Append(pRequest->GetRawHttpRequest()->CookedUrl.pQueryString, + pRequest->GetRawHttpRequest()->CookedUrl.QueryStringLength / sizeof(WCHAR)); + +Finished: + return hr; +} + +// static +bool +UTILITY::IsValidAttributeNameChar( + WCHAR ch +) +{ + // + // Values based on ASP.NET rendering for cookie names. RFC 2965 is not clear + // what the non-special characters are. + // + return ch == L'\t' || (ch > 31 && ch < 127); +} + +// static +bool +UTILITY::FindInMultiString( + PCWSTR pszMultiString, + PCWSTR pszStringToFind +) +{ + while (*pszMultiString != L'\0') + { + if (wcscmp(pszMultiString, pszStringToFind) == 0) + { + return TRUE; + } + pszMultiString += wcslen(pszMultiString) + 1; + } + + return FALSE; +} + +// static +bool +UTILITY::IsValidQueryStringName( + PCWSTR pszName +) +{ + while (*pszName != L'\0') + { + WCHAR c = *pszName; + if (c != L'-' && c != L'_' && c != L'+' && + c != L'.' && c != L'*' && c != L'$' && c != L'%' && c != L',' && + !iswalnum(c)) + { + return FALSE; + } + pszName++; + } + + return TRUE; +} + +// static +bool +UTILITY::IsValidHeaderName( + PCWSTR pszName +) +{ + while (*pszName != L'\0') + { + WCHAR c = *pszName; + if (c != L'-' && c != L'_' && c != L'+' && + c != L'.' && c != L'*' && c != L'$' && c != L'%' + && !iswalnum(c)) + { + return FALSE; + } + pszName++; + } + + return TRUE; +} + +HRESULT +UTILITY::IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc +) +{ + HRESULT hr = S_OK; + STRU strTempPath; + + if ( pszPath == NULL || pfIsUnc == NULL ) + { + hr = E_INVALIDARG; + goto Finished; + } + + hr = MakePathCanonicalizationProof( (LPWSTR) pszPath, &strTempPath ); + if ( FAILED(hr) ) + { + goto Finished; + } + + // + // MakePathCanonicalizationProof will map \\?\UNC, \\.\UNC and \\ to \\?\UNC + // + (*pfIsUnc) = ( _wcsnicmp( strTempPath.QueryStr(), L"\\\\?\\UNC\\", 8 /* sizeof \\?\UNC\ */) == 0 ); + +Finished: + + return hr; +} + +HRESULT +UTILITY::ConvertPathToFullPath( + _In_ LPCWSTR pszPath, + _In_ LPCWSTR pszRootPath, + _Out_ STRU* pStruFullPath +) +{ + HRESULT hr = S_OK; + STRU strFileFullPath; + LPWSTR pszFullPath = NULL; + + // if relative path, prefix with root path and then convert to absolute path. + if ( pszPath[0] == L'.' ) + { + hr = strFileFullPath.Copy(pszRootPath); + if(FAILED(hr)) + { + goto Finished; + } + + if(!strFileFullPath.EndsWith(L"\\")) + { + hr = strFileFullPath.Append(L"\\"); + if(FAILED(hr)) + { + goto Finished; + } + } + } + + hr = strFileFullPath.Append( pszPath ); + if (FAILED(hr)) + { + goto Finished; + } + + pszFullPath = new WCHAR[ strFileFullPath.QueryCCH() + 1]; + if ( pszFullPath == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if(_wfullpath( pszFullPath, + strFileFullPath.QueryStr(), + strFileFullPath.QueryCCH() + 1 ) == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + // convert to canonical path + hr = MakePathCanonicalizationProof( pszFullPath, pStruFullPath ); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + if ( pszFullPath != NULL ) + { + delete[] pszFullPath; + pszFullPath = NULL; + } + + return hr; +} + +HRESULT +UTILITY::EnsureDirectoryPathExist( + _In_ LPCWSTR pszPath +) +{ + HRESULT hr = S_OK; + STRU struPath; + DWORD dwPosition = 0; + BOOL fDone = FALSE; + BOOL fUnc = FALSE; + + struPath.Copy(pszPath); + hr = IsPathUnc(pszPath, &fUnc); + if (FAILED(hr)) + { + goto Finished; + } + if (fUnc) + { + // "\\?\UNC\" + dwPosition = 8; + } + else if (struPath.IndexOf(L'?', 0) != -1) + { + // sceanrio "\\?\" + dwPosition = 4; + } + while (!fDone) + { + dwPosition = struPath.IndexOf(L'\\', dwPosition + 1); + if (dwPosition == -1) + { + // not found '/' + fDone = TRUE; + goto Finished; + } + else if (dwPosition ==0) + { + hr = ERROR_INTERNAL_ERROR; + goto Finished; + } + else if (struPath.QueryStr()[dwPosition-1] == L':') + { + // skip volume case + continue; + } + else + { + struPath.QueryStr()[dwPosition] = L'\0'; + } + + if (!CreateDirectory(struPath.QueryStr(), NULL) && + ERROR_ALREADY_EXISTS != GetLastError()) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + fDone = TRUE; + goto Finished; + } + struPath.QueryStr()[dwPosition] = L'\\'; + } + +Finished: + return hr; +} + +HRESULT +UTILITY::FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult +) +{ + HRESULT hr = S_OK; + fx_ver_t max_ver(-1, -1, -1); + for (const auto& dir : vFolders) + { + fx_ver_t fx_ver(-1, -1, -1); + if (fx_ver_t::parse(dir, &fx_ver, false)) + { + // TODO using max instead of std::max works + max_ver = max(max_ver, fx_ver); + } + } + + hr = pstrResult->Copy(max_ver.as_str().c_str()); + + // we check FAILED(hr) outside of function + return hr; +} + +BOOL +UTILITY::DirectoryExists( + _In_ STRU *pstrPath +) +{ + WIN32_FILE_ATTRIBUTE_DATA data; + + if (pstrPath->IsEmpty()) + { + return false; + } + + return GetFileAttributesExW(pstrPath->QueryStr(), GetFileExInfoStandard, &data); +} + +BOOL +UTILITY::GetSystemPathVariable( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult +) +{ + DWORD dwLength; + PWSTR pszBuffer = NULL; + BOOL fSucceeded = FALSE; + + if (pszEnvironmentVariable == NULL) + { + goto Finished; + } + pstrResult->Reset(); + dwLength = GetEnvironmentVariableW(pszEnvironmentVariable, NULL, 0); + + if (dwLength == 0) + { + goto Finished; + } + + pszBuffer = new WCHAR[dwLength]; + if (GetEnvironmentVariableW(pszEnvironmentVariable, pszBuffer, dwLength) == 0) + { + goto Finished; + } + + pstrResult->Copy(pszBuffer); + + fSucceeded = TRUE; + +Finished: + if (pszBuffer != NULL) { + delete[] pszBuffer; + } + return fSucceeded; +} + +VOID +UTILITY::FindDotNetFolders( + _In_ PCWSTR pszPath, + _Out_ std::vector *pvFolders +) +{ + HANDLE handle = NULL; + WIN32_FIND_DATAW data = { 0 }; + + handle = FindFirstFileExW(pszPath, FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); + if (handle == INVALID_HANDLE_VALUE) + { + return; + } + + do + { + std::wstring folder(data.cFileName); + pvFolders->push_back(folder); + } while (FindNextFileW(handle, &data)); + + FindClose(handle); +} \ No newline at end of file diff --git a/src/CommonLib/utility.h b/src/CommonLib/utility.h new file mode 100644 index 0000000000..7ad0119445 --- /dev/null +++ b/src/CommonLib/utility.h @@ -0,0 +1,119 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class UTILITY +{ +public: + + static + HRESULT + SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl + ); + + static + HRESULT + UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + bool fCopyQuery, + STRA * pstrResult + ); + + static + HRESULT + UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + STRU * pstrResult + ); + + static HRESULT + EscapeAbsPath( + IHttpRequest * pRequest, + STRU * strEscapedUrl + ); + + static + bool + IsValidAttributeNameChar( + WCHAR ch + ); + + static + bool + IsValidQueryStringName( + PCWSTR pszName + ); + + static + bool + IsValidHeaderName( + PCWSTR pszName + ); + + static + bool + FindInMultiString( + PCWSTR pszMultiString, + PCWSTR pszStringToFind + ); + + static + HRESULT + IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc + ); + + static + HRESULT + ConvertPathToFullPath( + _In_ LPCWSTR pszPath, + _In_ LPCWSTR pszRootPath, + _Out_ STRU* pStrFullPath + ); + + static + HRESULT + EnsureDirectoryPathExist( + _In_ LPCWSTR pszPath + ); + + static + BOOL + DirectoryExists( + _In_ STRU *pstrPath + ); + + static + BOOL + GetSystemPathVariable( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult + ); + + static + VOID + FindDotNetFolders( + _In_ PCWSTR pszPath, + _Out_ std::vector *pvFolders + ); + + static + HRESULT + FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult + ); + +private: + + UTILITY() {} + ~UTILITY() {} +}; \ No newline at end of file diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj new file mode 100644 index 0000000000..05a4e90bf5 --- /dev/null +++ b/src/IISLib/IISLib.vcxproj @@ -0,0 +1,186 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} + Win32Proj + IISLib + IISLib + 8.1 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + MultiThreadedDebug + false + + + Windows + true + + + + + + + Level4 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + MultiThreadedDebug + false + + + Windows + true + + + + + Level4 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + false + + + Windows + true + true + true + + + + + Level4 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + false + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/IISLib/acache.cxx b/src/IISLib/acache.cxx new file mode 100644 index 0000000000..d68813edbc --- /dev/null +++ b/src/IISLib/acache.cxx @@ -0,0 +1,443 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +LONG ALLOC_CACHE_HANDLER::sm_nFillPattern = 0xACA50000; +HANDLE ALLOC_CACHE_HANDLER::sm_hHeap; + +// +// This class is used to implement the free list. We cast the free'd +// memory block to a FREE_LIST_HEADER*. The signature is used to guard against +// double deletion. We also fill memory with a pattern. +// +class FREE_LIST_HEADER +{ +public: + SLIST_ENTRY ListEntry; + DWORD dwSignature; + + enum + { + FREE_SIGNATURE = (('A') | ('C' << 8) | ('a' << 16) | (('$' << 24) | 0x80)), + }; +}; + +ALLOC_CACHE_HANDLER::ALLOC_CACHE_HANDLER( + VOID +) : m_nThreshold(0), + m_cbSize(0), + m_pFreeLists(NULL), + m_nTotal(0) +{ +} + +ALLOC_CACHE_HANDLER::~ALLOC_CACHE_HANDLER( + VOID +) +{ + if (m_pFreeLists != NULL) + { + CleanupLookaside(); + m_pFreeLists->Dispose(); + m_pFreeLists = NULL; + } +} + +HRESULT +ALLOC_CACHE_HANDLER::Initialize( + DWORD cbSize, + LONG nThreshold +) +{ + HRESULT hr = S_OK; + + m_nThreshold = nThreshold; + if ( m_nThreshold > 0xffff) + { + // + // This will be compared against QueryDepthSList return value (USHORT). + // + m_nThreshold = 0xffff; + } + + if ( IsPageheapEnabled() ) + { + // + // Disable acache. + // + m_nThreshold = 0; + } + + // + // Make sure the block is big enough to hold a FREE_LIST_HEADER. + // + m_cbSize = cbSize; + m_cbSize = max(m_cbSize, sizeof(FREE_LIST_HEADER)); + + // + // Round up the block size to a multiple of the size of a LONG (for + // the fill pattern in Free()). + // + m_cbSize = (m_cbSize + sizeof(LONG) - 1) & ~(sizeof(LONG) - 1); + +#if defined(_MSC_VER) && _MSC_VER >= 1600 // VC10 + auto Init = [] (SLIST_HEADER* pHead) + { + InitializeSListHead(pHead); + }; +#else + class Functor + { + public: + void operator()(SLIST_HEADER* pHead) + { + InitializeSListHead(pHead); + } + } Init; +#endif + + hr = PER_CPU::Create(Init, + &m_pFreeLists ); + if (FAILED(hr)) + { + goto Finished; + } + + m_nFillPattern = InterlockedIncrement(&sm_nFillPattern); + +Finished: + + return hr; +} + +// static +HRESULT +ALLOC_CACHE_HANDLER::StaticInitialize( + VOID +) +{ + // + // Since the memory allocated is fixed size, + // a heap is not really needed, allocations can be done + // using VirtualAllocEx[Numa]. For now use Windows Heap. + // + // Be aware that creating one private heap consumes more + // virtual address space for the worker process. + // + sm_hHeap = GetProcessHeap(); + return S_OK; +} + + +// static +VOID +ALLOC_CACHE_HANDLER::StaticTerminate( + VOID +) +{ + sm_hHeap = NULL; +} + +VOID +ALLOC_CACHE_HANDLER::CleanupLookaside( + VOID +) +/*++ + Description: + This function cleans up the lookaside list by removing storage space. + + Arguments: + None. + + Returns: + None +--*/ +{ + // + // Free up all the entries in the list. + // Don't use InterlockedFlushSList, in order to work + // memory must be 16 bytes aligned and currently it is 64. + // + +#if defined(_MSC_VER) && _MSC_VER >= 1600 // VC10 + auto Predicate = [=] (SLIST_HEADER * pListHeader) + { + PSLIST_ENTRY pl; + LONG NodesToDelete = QueryDepthSList( pListHeader ); + + pl = InterlockedPopEntrySList( pListHeader ); + while ( pl != NULL && --NodesToDelete >= 0 ) + { + InterlockedDecrement( &m_nTotal); + + ::HeapFree( sm_hHeap, 0, pl ); + + pl = InterlockedPopEntrySList(pListHeader); + } + }; +#else + class Functor + { + public: + explicit Functor(ALLOC_CACHE_HANDLER * pThis) : _pThis(pThis) + { + } + void operator()(SLIST_HEADER * pListHeader) + { + PSLIST_ENTRY pl; + LONG NodesToDelete = QueryDepthSList( pListHeader ); + + pl = InterlockedPopEntrySList( pListHeader ); + while ( pl != NULL && --NodesToDelete >= 0 ) + { + InterlockedDecrement( &_pThis->m_nTotal); + + ::HeapFree( sm_hHeap, 0, pl ); + + pl = InterlockedPopEntrySList(pListHeader); + } + } + private: + ALLOC_CACHE_HANDLER * _pThis; + } Predicate(this); +#endif + + m_pFreeLists ->ForEach(Predicate); +} + +LPVOID +ALLOC_CACHE_HANDLER::Alloc( + VOID +) +{ + LPVOID pMemory = NULL; + + if ( m_nThreshold > 0 ) + { + SLIST_HEADER * pListHeader = m_pFreeLists ->GetLocal(); + pMemory = (LPVOID) InterlockedPopEntrySList(pListHeader); // get the real object + + if (pMemory != NULL) + { + FREE_LIST_HEADER* pfl = (FREE_LIST_HEADER*) pMemory; + // + // If the signature is wrong then somebody's been scribbling + // on memory that they've freed. + // + DBG_ASSERT(pfl->dwSignature == FREE_LIST_HEADER::FREE_SIGNATURE); + } + } + + if ( pMemory == NULL ) + { + // + // No free entry. Need to alloc a new object. + // + pMemory = (LPVOID) ::HeapAlloc( sm_hHeap, + 0, + m_cbSize ); + + if ( pMemory != NULL ) + { + // + // Update counters. + // + m_nTotal++; + } + } + + if ( pMemory == NULL ) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + } + else + { + FREE_LIST_HEADER* pfl = (FREE_LIST_HEADER*) pMemory; + pfl->dwSignature = 0; // clear; just in case caller never overwrites + } + + return pMemory; +} + +VOID +ALLOC_CACHE_HANDLER::Free( + __in LPVOID pMemory +) +{ + // + // Assume that this is allocated using the Alloc() function. + // + DBG_ASSERT(NULL != pMemory); + + // + // Use a signature to check against double deletions. + // + FREE_LIST_HEADER* pfl = (FREE_LIST_HEADER*) pMemory; + DBG_ASSERT(pfl->dwSignature != FREE_LIST_HEADER::FREE_SIGNATURE); + + // + // Start filling the space beyond the portion overlaid by the initial + // FREE_LIST_HEADER. Fill at most 6 DWORDS. + // + LONG* pl = (LONG*) (pfl+1); + + for (LONG cb = (LONG)min(6 * sizeof(LONG),m_cbSize) - sizeof(FREE_LIST_HEADER); + cb > 0; + cb -= sizeof(LONG)) + { + *pl++ = m_nFillPattern; + } + + // + // Now, set the signature. + // + pfl->dwSignature = FREE_LIST_HEADER::FREE_SIGNATURE; + + // + // Store the items in the alloc cache. + // + SLIST_HEADER * pListHeader = m_pFreeLists ->GetLocal(); + + if ( QueryDepthSList(pListHeader) >= m_nThreshold ) + { + // + // Threshold for free entries is exceeded. Free the object to + // process pool. + // + ::HeapFree( sm_hHeap, 0, pMemory ); + } + else + { + // + // Store the given pointer in the single linear list + // + InterlockedPushEntrySList(pListHeader, &pfl->ListEntry); + } +} + +DWORD +ALLOC_CACHE_HANDLER::QueryDepthForAllSLists( + VOID +) +/*++ + +Description: + + Aggregates the total count of elements in all lists. + +Arguments: + + None. + +Return Value: + + Total count (snapshot). + +--*/ +{ + DWORD Count = 0; + + if (m_pFreeLists != NULL) + { +#if defined(_MSC_VER) && _MSC_VER >= 1600 // VC10 + auto Predicate = [&Count] (SLIST_HEADER * pListHeader) + { + Count += QueryDepthSList(pListHeader); + }; +#else + class Functor + { + public: + explicit Functor(DWORD& Count) : _Count(Count) + { + } + void operator()(SLIST_HEADER * pListHeader) + { + _Count += QueryDepthSList(pListHeader); + } + private: + DWORD& _Count; + } Predicate(Count); +#endif + // + // [&Count] means that the method can modify local variable Count. + // + m_pFreeLists ->ForEach(Predicate); + } + + return Count; +} + +// static +BOOL +ALLOC_CACHE_HANDLER::IsPageheapEnabled( + VOID +) +{ + BOOL fRet = FALSE; + BOOL fLockedHeap = FALSE; + HMODULE hModule = NULL; + HANDLE hHeap = NULL; + PROCESS_HEAP_ENTRY heapEntry = {0}; + + // + // If verifier.dll is loaded - we are running under app verifier == pageheap is enabled + // + hModule = GetModuleHandle( L"verifier.dll" ); + if ( hModule != NULL ) + { + hModule = NULL; + fRet = TRUE; + goto Finished; + } + + // + // Create a heap for calling heapwalk + // otherwise HeapWalk turns off lookasides for a useful heap + // + hHeap = ::HeapCreate( 0, 0, 0 ); + if ( hHeap == NULL ) + { + fRet = FALSE; + goto Finished; + } + + fRet = ::HeapLock( hHeap ); + if ( !fRet ) + { + goto Finished; + } + fLockedHeap = TRUE; + + // + // If HeapWalk is unsupported -> then running page heap + // + fRet = ::HeapWalk( hHeap, &heapEntry ); + if ( !fRet ) + { + if ( GetLastError() == ERROR_INVALID_FUNCTION ) + { + fRet = TRUE; + goto Finished; + } + } + + fRet = FALSE; + +Finished: + + if ( fLockedHeap ) + { + fLockedHeap = FALSE; + DBG_REQUIRE( ::HeapUnlock( hHeap ) ); + } + + if ( hHeap ) + { + DBG_REQUIRE( ::HeapDestroy( hHeap ) ); + hHeap = NULL; + } + + return fRet; +} \ No newline at end of file diff --git a/src/IISLib/acache.h b/src/IISLib/acache.h new file mode 100644 index 0000000000..048df2b507 --- /dev/null +++ b/src/IISLib/acache.h @@ -0,0 +1,115 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "percpu.h" + +class ALLOC_CACHE_HANDLER +{ +public: + + ALLOC_CACHE_HANDLER( + VOID + ); + + ~ALLOC_CACHE_HANDLER( + VOID + ); + + HRESULT + Initialize( + DWORD cbSize, + LONG nThreshold + ); + + LPVOID + Alloc( + VOID + ); + + VOID + Free( + __in LPVOID pMemory + ); + + +private: + + VOID + CleanupLookaside( + VOID + ); + + DWORD + QueryDepthForAllSLists( + VOID + ); + + LONG m_nThreshold; + DWORD m_cbSize; + + PER_CPU * m_pFreeLists; + + // + // Total heap allocations done over the lifetime. + // Note that this is not interlocked, it is just a hint for debugging. + // + volatile LONG m_nTotal; + + LONG m_nFillPattern; + +public: + + static + HRESULT + StaticInitialize( + VOID + ); + + static + VOID + StaticTerminate( + VOID + ); + + static + BOOL + IsPageheapEnabled(); + +private: + + static LONG sm_nFillPattern; + static HANDLE sm_hHeap; +}; + + +// You can use ALLOC_CACHE_HANDLER as a per-class allocator +// in your C++ classes. Add the following to your class definition: +// +// protected: +// static ALLOC_CACHE_HANDLER* sm_palloc; +// public: +// static void* operator new(size_t s) +// { +// IRTLASSERT(s == sizeof(C)); +// IRTLASSERT(sm_palloc != NULL); +// return sm_palloc->Alloc(); +// } +// static void operator delete(void* pv) +// { +// IRTLASSERT(pv != NULL); +// if (sm_palloc != NULL) +// sm_palloc->Free(pv); +// } +// +// Obviously, you must initialize sm_palloc before you can allocate +// any objects of this class. +// +// Note that if you derive a class from this base class, the derived class +// must also provide its own operator new and operator delete. If not, the +// base class's allocator will be called, but the size of the derived +// object will almost certainly be larger than that of the base object. +// Furthermore, the allocator will not be used for arrays of objects +// (override operator new[] and operator delete[]), but this is a +// harder problem since the allocator works with one fixed size. diff --git a/src/IISLib/ahutil.cpp b/src/IISLib/ahutil.cpp new file mode 100644 index 0000000000..dae2027f33 --- /dev/null +++ b/src/IISLib/ahutil.cpp @@ -0,0 +1,1671 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +HRESULT +SetElementProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST VARIANT * varPropValue + ) +{ + HRESULT hr = NOERROR; + + CComPtr pPropElement; + + BSTR bstrPropName = SysAllocString( szPropName ); + + if( !bstrPropName ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = pElement->GetPropertyByName( bstrPropName, + &pPropElement ); + if( FAILED(hr) ) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pPropElement->put_Value( *varPropValue ); + if( FAILED(hr) ) + { + DBGERROR_HR( hr ); + goto exit; + } + +exit: + + if( bstrPropName ) + { + SysFreeString( bstrPropName ); + bstrPropName = NULL; + } + + return hr; +} + +HRESULT +SetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST WCHAR * szPropValue + ) +{ + HRESULT hr; + VARIANT varPropValue; + VariantInit(&varPropValue); + + hr = VariantAssign(&varPropValue, szPropValue); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = SetElementProperty(pElement, szPropName, &varPropValue); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + VariantClear(&varPropValue); + return hr; +} + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT BSTR * pbstrPropValue + ) +{ + HRESULT hr = S_OK; + BSTR bstrPropName = SysAllocString( szPropName ); + IAppHostProperty* pProperty = NULL; + + *pbstrPropValue = NULL; + + if (!bstrPropName) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = pElement->GetPropertyByName( bstrPropName, &pProperty ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pProperty->get_StringValue( pbstrPropValue ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + +exit: + + if (pProperty) + { + pProperty->Release(); + } + + if (bstrPropName) + { + SysFreeString( bstrPropName ); + } + + return hr; +} + + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT STRU * pstrPropValue + ) +{ + HRESULT hr = S_OK; + BSTR bstrPropName = SysAllocString( szPropName ); + IAppHostProperty* pProperty = NULL; + BSTR bstrPropValue = NULL; + + if (!bstrPropName) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = pElement->GetPropertyByName( bstrPropName, &pProperty ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pProperty->get_StringValue( &bstrPropValue ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pstrPropValue->Copy(bstrPropValue); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + +exit: + + if (pProperty) + { + pProperty->Release(); + } + + if (bstrPropValue) + { + SysFreeString( bstrPropValue ); + } + + if (bstrPropName) + { + SysFreeString( bstrPropName ); + } + + return hr; +} + +HRESULT +GetElementChildByName( + IN IAppHostElement * pElement, + IN LPCWSTR pszElementName, + OUT IAppHostElement ** ppChildElement +) +{ + BSTR bstrElementName = SysAllocString(pszElementName); + if (bstrElementName == NULL) + { + return E_OUTOFMEMORY; + } + HRESULT hr = pElement->GetElementByName(bstrElementName, + ppChildElement); + SysFreeString(bstrElementName); + return hr; +} + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT bool * pBool +) +{ + BOOL fValue; + HRESULT hr = GetElementBoolProperty(pElement, + pszPropertyName, + &fValue); + if (SUCCEEDED(hr)) + { + *pBool = !!fValue; + } + return hr; +} + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT BOOL * pBool +) +{ + HRESULT hr = S_OK; + BSTR bstrPropertyName = NULL; + IAppHostProperty * pProperty = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrPropertyName = SysAllocString( pszPropertyName ); + if ( bstrPropertyName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + // Now ask for the property and if it succeeds it is returned directly back. + hr = pElement->GetPropertyByName( bstrPropertyName, &pProperty ); + if ( FAILED ( hr ) ) + { + goto exit; + } + + // Now let's get the property and then extract it from the Variant. + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto exit; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_BOOL ); + if ( FAILED ( hr ) ) + { + goto exit; + } + + // extract the value + *pBool = ( V_BOOL( &varValue ) == VARIANT_TRUE ); + +exit: + + VariantClear( &varValue ); + + if ( bstrPropertyName != NULL ) + { + SysFreeString( bstrPropertyName ); + bstrPropertyName = NULL; + } + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + return hr; + +} + +HRESULT +GetElementDWORDProperty( + IN IAppHostElement * pSitesCollectionEntry, + IN LPCWSTR pwszName, + OUT DWORD * pdwValue +) +{ + HRESULT hr = S_OK; + IAppHostProperty * pProperty = NULL; + BSTR bstrName = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrName = SysAllocString( pwszName ); + if ( bstrName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto error; + } + + hr = pSitesCollectionEntry->GetPropertyByName( bstrName, + &pProperty ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_UI4 ); + if ( FAILED ( hr ) ) + { + goto error; + } + + // extract the value + *pdwValue = varValue.ulVal; + +error: + + VariantClear( &varValue ); + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + if ( bstrName != NULL ) + { + SysFreeString( bstrName ); + bstrName = NULL; + } + + return hr; +} + +HRESULT +GetElementLONGLONGProperty( + IN IAppHostElement * pSitesCollectionEntry, + IN LPCWSTR pwszName, + OUT LONGLONG * pllValue +) +{ + HRESULT hr = S_OK; + IAppHostProperty * pProperty = NULL; + BSTR bstrName = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrName = SysAllocString( pwszName ); + if ( bstrName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto error; + } + + hr = pSitesCollectionEntry->GetPropertyByName( bstrName, + &pProperty ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_I8 ); + if ( FAILED ( hr ) ) + { + goto error; + } + + // extract the value + *pllValue = varValue.ulVal; + +error: + + VariantClear( &varValue ); + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + if ( bstrName != NULL ) + { + SysFreeString( bstrName ); + bstrName = NULL; + } + + return hr; +} + +HRESULT +GetElementRawTimeSpanProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT ULONGLONG * pulonglong +) +{ + HRESULT hr = S_OK; + BSTR bstrPropertyName = NULL; + IAppHostProperty * pProperty = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrPropertyName = SysAllocString( pszPropertyName ); + if ( bstrPropertyName == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); + goto Finished; + } + + // Now ask for the property and if it succeeds it is returned directly back + hr = pElement->GetPropertyByName( bstrPropertyName, &pProperty ); + if ( FAILED ( hr ) ) + { + goto Finished; + } + + // Now let's get the property and then extract it from the Variant. + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto Finished; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_UI8 ); + if ( FAILED ( hr ) ) + { + goto Finished; + } + + // extract the value + *pulonglong = varValue.ullVal; + + +Finished: + + VariantClear( &varValue ); + + if ( bstrPropertyName != NULL ) + { + SysFreeString( bstrPropertyName ); + bstrPropertyName = NULL; + } + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + return hr; + +} // end of Config_GetRawTimeSpanProperty + +HRESULT +DeleteElementFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + BOOL * pfDeleted + ) +{ + HRESULT hr = NOERROR; + ULONG index; + + VARIANT varIndex; + VariantInit( &varIndex ); + + *pfDeleted = FALSE; + + hr = FindElementInCollection( + pCollection, + szKeyName, + szKeyValue, + BehaviorFlags, + &index + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (hr == S_FALSE) + { + // + // Not found. + // + + goto exit; + } + + varIndex.vt = VT_UI4; + varIndex.ulVal = index; + + hr = pCollection->DeleteElement( varIndex ); + + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + *pfDeleted = TRUE; + +exit: + + return hr; +} + +HRESULT +DeleteAllElementsFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + UINT * pNumDeleted + ) +{ + HRESULT hr = S_OK; + UINT numDeleted = 0; + BOOL fDeleted = TRUE; + + while (fDeleted) + { + hr = DeleteElementFromCollection( + pCollection, + szKeyName, + szKeyValue, + BehaviorFlags, + &fDeleted + ); + + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + break; + } + + if (fDeleted) + { + numDeleted++; + } + } + + *pNumDeleted = numDeleted; + return hr; +} + +BOOL +FindCompareCaseSensitive( + CONST WCHAR * szLookupValue, + CONST WCHAR * szKeyValue + ) +{ + return !wcscmp(szLookupValue, szKeyValue); +} + +BOOL +FindCompareCaseInsensitive( + CONST WCHAR * szLookupValue, + CONST WCHAR * szKeyValue + ) +{ + return !_wcsicmp(szLookupValue, szKeyValue); +} + +typedef +BOOL +(*PFN_FIND_COMPARE_PROC)( + CONST WCHAR *szLookupValue, + CONST WCHAR *szKeyValue + ); + +HRESULT +FindElementInCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + OUT ULONG * pIndex + ) +{ + HRESULT hr = NOERROR; + + CComPtr pElement; + CComPtr pKeyProperty; + + VARIANT varIndex; + VariantInit( &varIndex ); + + VARIANT varKeyValue; + VariantInit( &varKeyValue ); + + DWORD count; + DWORD i; + + BSTR bstrKeyName = NULL; + PFN_FIND_COMPARE_PROC compareProc; + + compareProc = (BehaviorFlags & FIND_ELEMENT_CASE_INSENSITIVE) + ? &FindCompareCaseInsensitive + : &FindCompareCaseSensitive; + + bstrKeyName = SysAllocString( szKeyName ); + if( !bstrKeyName ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = pCollection->get_Count( &count ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + for( i = 0; i < count; i++ ) + { + varIndex.vt = VT_UI4; + varIndex.ulVal = i; + + hr = pCollection->get_Item( varIndex, + &pElement ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto tryNext; + } + + hr = pElement->GetPropertyByName( bstrKeyName, + &pKeyProperty ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto tryNext; + } + + hr = pKeyProperty->get_Value( &varKeyValue ); + + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto tryNext; + } + + if ((compareProc)(szKeyValue, varKeyValue.bstrVal)) + { + *pIndex = i; + break; + } + +tryNext: + + pElement.Release(); + pKeyProperty.Release(); + + VariantClear( &varKeyValue ); + } + + if (i >= count) + { + hr = S_FALSE; + } + +exit: + + SysFreeString( bstrKeyName ); + VariantClear( &varKeyValue ); + + return hr; +} + +HRESULT +VariantAssign( + IN OUT VARIANT * pv, + IN CONST WCHAR * sz + ) +{ + if( !pv || !sz ) + { + return E_INVALIDARG; + } + + HRESULT hr = NOERROR; + + BSTR bstr = SysAllocString( sz ); + if( !bstr ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = VariantClear( pv ); + if( FAILED(hr) ) + { + DBGERROR_HR( hr ); + goto exit; + } + + pv->vt = VT_BSTR; + pv->bstrVal = bstr; + bstr = NULL; + +exit: + + SysFreeString( bstr ); + + return hr; +} + +HRESULT +GetLocationFromFile( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szLocationPath, + OUT IAppHostConfigLocation ** ppLocation, + OUT BOOL * pFound + ) +{ + HRESULT hr = NOERROR; + + CComPtr pLocationCollection; + CComPtr pLocation; + + BSTR bstrLocationPath = NULL; + + *ppLocation = NULL; + *pFound = FALSE; + + hr = GetLocationCollection( pAdminMgr, + szConfigPath, + &pLocationCollection ); + + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + DWORD count; + DWORD i; + VARIANT varIndex; + VariantInit( &varIndex ); + + hr = pLocationCollection->get_Count( &count ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + for( i = 0; i < count; i++ ) + { + varIndex.vt = VT_UI4; + varIndex.ulVal = i; + + hr = pLocationCollection->get_Item( varIndex, + &pLocation ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pLocation->get_Path( &bstrLocationPath ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + if( 0 == wcscmp ( szLocationPath, bstrLocationPath ) ) + { + *pFound = TRUE; + *ppLocation = pLocation.Detach(); + break; + } + + + pLocation.Release(); + + SysFreeString( bstrLocationPath ); + bstrLocationPath = NULL; + } + +exit: + + SysFreeString( bstrLocationPath ); + + return hr; +} + +HRESULT +GetSectionFromLocation( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szSectionName, + OUT IAppHostElement ** ppSectionElement, + OUT BOOL * pFound + ) +{ + HRESULT hr = NOERROR; + + CComPtr pSectionElement; + + DWORD count; + DWORD i; + + VARIANT varIndex; + VariantInit( &varIndex ); + + BSTR bstrSectionName = NULL; + + *pFound = FALSE; + *ppSectionElement = NULL; + + hr = pLocation->get_Count( &count ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + for( i = 0; i < count; i++ ) + { + varIndex.vt = VT_UI4; + varIndex.ulVal = i; + + + hr = pLocation->get_Item( varIndex, + &pSectionElement ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pSectionElement->get_Name( &bstrSectionName ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + if( 0 == wcscmp ( szSectionName, bstrSectionName ) ) + { + *pFound = TRUE; + *ppSectionElement = pSectionElement.Detach(); + break; + } + + pSectionElement.Release(); + + SysFreeString( bstrSectionName ); + bstrSectionName = NULL; + } + +exit: + + SysFreeString( bstrSectionName ); + + return hr; +} + + +HRESULT +GetAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName, + OUT IAppHostElement ** pElement +) +{ + HRESULT hr = S_OK; + BSTR bstrConfigPath = NULL; + BSTR bstrElementName = NULL; + + bstrConfigPath = SysAllocString(szConfigPath); + bstrElementName = SysAllocString(szElementName); + + if (bstrConfigPath == NULL || bstrElementName == NULL) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = pAdminMgr->GetAdminSection( bstrElementName, + bstrConfigPath, + pElement ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + if ( bstrElementName != NULL ) + { + SysFreeString(bstrElementName); + bstrElementName = NULL; + } + if ( bstrConfigPath != NULL ) + { + SysFreeString(bstrConfigPath); + bstrConfigPath = NULL; + } + + return hr; +} + + +HRESULT +ClearAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pElement; + + hr = GetAdminElement( + pAdminMgr, + szConfigPath, + szElementName, + &pElement + ); + + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + { + hr = S_OK; + } + else + { + DBGERROR_HR(hr); + } + + goto exit; + } + + hr = pElement->Clear(); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + return hr; +} + + +HRESULT +ClearElementFromAllSites( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pSitesCollection; + CComPtr pSiteElement; + CComPtr pChildCollection; + ENUM_INDEX index; + BOOL found; + + // + // Enumerate the sites, remove the specified elements. + // + + hr = GetSitesCollection( + pAdminMgr, + szConfigPath, + &pSitesCollection + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + for (hr = FindFirstElement(pSitesCollection, &index, &pSiteElement) ; + SUCCEEDED(hr) ; + hr = FindNextElement(pSitesCollection, &index, &pSiteElement)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = pSiteElement->get_ChildElements(&pChildCollection); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (pChildCollection) + { + hr = ClearChildElementsByName( + pChildCollection, + szElementName, + &found + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + } + + pSiteElement.Release(); + } + +exit: + + return hr; + +} + + +HRESULT +ClearElementFromAllLocations( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pLocationCollection; + CComPtr pLocation; + CComPtr pChildCollection; + ENUM_INDEX index; + + // + // Enum the tags, remove the specified elements. + // + + hr = GetLocationCollection( + pAdminMgr, + szConfigPath, + &pLocationCollection + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + for (hr = FindFirstLocation(pLocationCollection, &index, &pLocation) ; + SUCCEEDED(hr) ; + hr = FindNextLocation(pLocationCollection, &index, &pLocation)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = ClearLocationElements(pLocation, szElementName); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + pLocation.Release(); + } + +exit: + + return hr; + +} + +HRESULT +ClearLocationElements( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pElement; + ENUM_INDEX index; + BOOL matched; + + for (hr = FindFirstLocationElement(pLocation, &index, &pElement) ; + SUCCEEDED(hr) ; + hr = FindNextLocationElement(pLocation, &index, &pElement)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = CompareElementName(pElement, szElementName, &matched); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (matched) + { + pElement->Clear(); + } + + pElement.Release(); + } + +exit: + + return hr; +} + +HRESULT +CompareElementName( + IN IAppHostElement * pElement, + IN CONST WCHAR * szNameToMatch, + OUT BOOL * pMatched + ) +{ + HRESULT hr; + BSTR bstrElementName = NULL; + + *pMatched = FALSE; // until proven otherwise + + hr = pElement->get_Name(&bstrElementName); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if( 0 == wcscmp ( szNameToMatch, bstrElementName ) ) + { + *pMatched = TRUE; + } + +exit: + + SysFreeString(bstrElementName); + return hr; +} + + +HRESULT +ClearChildElementsByName( + IN IAppHostChildElementCollection * pCollection, + IN CONST WCHAR * szElementName, + OUT BOOL * pFound + ) +{ + HRESULT hr; + CComPtr pElement; + ENUM_INDEX index; + BOOL matched; + + *pFound = FALSE; + + for (hr = FindFirstChildElement(pCollection, &index, &pElement) ; + SUCCEEDED(hr) ; + hr = FindNextChildElement(pCollection, &index, &pElement)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = CompareElementName(pElement, szElementName, &matched); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (matched) + { + hr = pElement->Clear(); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + *pFound = TRUE; + } + + pElement.Release(); + } + +exit: + + return hr; +} + + +HRESULT +GetSitesCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostElementCollection ** pSitesCollection + ) +{ + HRESULT hr; + CComPtr pSitesElement; + BSTR bstrConfigPath; + BSTR bstrSitesSectionName; + + bstrConfigPath = SysAllocString(szConfigPath); + bstrSitesSectionName = SysAllocString(L"system.applicationHost/sites"); + *pSitesCollection = NULL; + + if (bstrConfigPath == NULL || bstrSitesSectionName == NULL) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + // + // Chase down the sites collection. + // + + hr = pAdminMgr->GetAdminSection( bstrSitesSectionName, + bstrConfigPath, + &pSitesElement ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pSitesElement->get_Collection(pSitesCollection); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + SysFreeString(bstrSitesSectionName); + SysFreeString(bstrConfigPath); + return hr; +} + + +HRESULT +GetLocationCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostConfigLocationCollection ** pLocationCollection + ) +{ + HRESULT hr; + BSTR bstrConfigPath; + CComPtr pConfigMgr; + CComPtr pConfigFile; + + bstrConfigPath = SysAllocString(szConfigPath); + *pLocationCollection = NULL; + + if (bstrConfigPath == NULL) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = pAdminMgr->get_ConfigManager(&pConfigMgr); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pConfigMgr->GetConfigFile(bstrConfigPath, &pConfigFile); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pConfigFile->get_Locations(pLocationCollection); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + SysFreeString(bstrConfigPath); + return hr; +} + + +HRESULT +FindFirstElement( + IN IAppHostElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + hr = pCollection->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextElement(pCollection, pIndex, pElement); +} + +HRESULT +FindNextElement( + IN IAppHostElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + *pElement = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pCollection->get_Item(pIndex->Index, pElement); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +FindFirstChildElement( + IN IAppHostChildElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + hr = pCollection->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextChildElement(pCollection, pIndex, pElement); +} + +HRESULT +FindNextChildElement( + IN IAppHostChildElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + *pElement = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pCollection->get_Item(pIndex->Index, pElement); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +FindFirstLocation( + IN IAppHostConfigLocationCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ) +{ + HRESULT hr; + + hr = pCollection->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextLocation(pCollection, pIndex, pLocation); +} + +HRESULT +FindNextLocation( + IN IAppHostConfigLocationCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ) +{ + HRESULT hr; + + *pLocation = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pCollection->get_Item(pIndex->Index, pLocation); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +FindFirstLocationElement( + IN IAppHostConfigLocation * pLocation, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + hr = pLocation->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextLocationElement(pLocation, pIndex, pElement); +} + +HRESULT +FindNextLocationElement( + IN IAppHostConfigLocation * pLocation, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + *pElement = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pLocation->get_Item(pIndex->Index, pElement); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +GetSharedConfigEnabled( + BOOL * pfIsSharedConfig +) +/*++ + +Routine Description: + Search the configuration for the shared configuration property. + +Arguments: + + pfIsSharedConfig - true if shared configuration is enabled + +Return Value: + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + IAppHostAdminManager *pAdminManager = NULL; + + BSTR bstrSectionName = NULL; + BSTR bstrConfigPath = NULL; + + IAppHostElement * pConfigRedirSection = NULL; + + + bstrSectionName = SysAllocString( L"configurationRedirection" ); + + if ( bstrSectionName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + bstrConfigPath = SysAllocString( L"MACHINE/REDIRECTION" ); + if ( bstrConfigPath == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = CoCreateInstance( CLSID_AppHostAdminManager, + NULL, + CLSCTX_INPROC_SERVER, + IID_IAppHostAdminManager, + (VOID **)&pAdminManager ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pAdminManager->GetAdminSection( bstrSectionName, + bstrConfigPath, + &pConfigRedirSection ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = GetElementBoolProperty( pConfigRedirSection, + L"enabled", + pfIsSharedConfig ); + + if ( FAILED( hr ) ) + { + DBGERROR_HR(hr); + goto exit; + } + + pConfigRedirSection->Release(); + pConfigRedirSection = NULL; + + +exit: + + // + // dump config exception to setup log file (if available) + // + + if ( pConfigRedirSection != NULL ) + { + pConfigRedirSection->Release(); + } + + if ( pAdminManager != NULL ) + { + pAdminManager->Release(); + } + + if ( bstrConfigPath != NULL ) + { + SysFreeString( bstrConfigPath ); + } + + if ( bstrSectionName != NULL ) + { + SysFreeString( bstrSectionName ); + } + + return hr; +} diff --git a/src/IISLib/ahutil.h b/src/IISLib/ahutil.h new file mode 100644 index 0000000000..5694b21b7e --- /dev/null +++ b/src/IISLib/ahutil.h @@ -0,0 +1,258 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include + +HRESULT +SetElementProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST VARIANT * varPropValue + ); + +HRESULT +SetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST WCHAR * szPropValue + ); + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT BSTR * pbstrPropValue + ); + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT STRU * pstrPropValue + ); + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT BOOL * pBool + ); + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT bool * pBool + ); + +HRESULT +GetElementChildByName( + IN IAppHostElement * pElement, + IN LPCWSTR pszElementName, + OUT IAppHostElement ** ppChildElement + ); + +HRESULT +GetElementDWORDProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT DWORD * pdwValue + ); + +HRESULT +GetElementLONGLONGProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT LONGLONG * pllValue +); + + +HRESULT +GetElementRawTimeSpanProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT ULONGLONG * pulonglong + ); + +#define FIND_ELEMENT_CASE_SENSITIVE 0x00000000 +#define FIND_ELEMENT_CASE_INSENSITIVE 0x00000001 + +HRESULT +DeleteElementFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + BOOL * pfDeleted + ); + +HRESULT +DeleteAllElementsFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + UINT * pNumDeleted + ); + +HRESULT +FindElementInCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + OUT ULONG * pIndex + ); + +HRESULT +VariantAssign( + IN OUT VARIANT * pv, + IN CONST WCHAR * sz + ); + +HRESULT +GetLocationFromFile( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szLocationPath, + OUT IAppHostConfigLocation ** ppLocation, + OUT BOOL * pFound + ); + +HRESULT +GetSectionFromLocation( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szSectionName, + OUT IAppHostElement ** ppSectionElement, + OUT BOOL * pFound + ); + +HRESULT +GetAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName, + OUT IAppHostElement ** pElement + ); + +HRESULT +ClearAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ); + +HRESULT +ClearElementFromAllSites( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ); + +HRESULT +ClearElementFromAllLocations( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ); + +HRESULT +ClearLocationElements( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szElementName + ); + +HRESULT +CompareElementName( + IN IAppHostElement * pElement, + IN CONST WCHAR * szNameToMatch, + OUT BOOL * pMatched + ); + +HRESULT +ClearChildElementsByName( + IN IAppHostChildElementCollection * pCollection, + IN CONST WCHAR * szElementName, + OUT BOOL * pFound + ); + +HRESULT +GetSitesCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostElementCollection ** pSitesCollection + ); + +HRESULT +GetLocationCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostConfigLocationCollection ** pLocationCollection + ); + +struct ENUM_INDEX +{ + VARIANT Index; + ULONG Count; +}; + +HRESULT +FindFirstElement( + IN IAppHostElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindNextElement( + IN IAppHostElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindFirstChildElement( + IN IAppHostChildElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindNextChildElement( + IN IAppHostChildElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindFirstLocation( + IN IAppHostConfigLocationCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ); + +HRESULT +FindNextLocation( + IN IAppHostConfigLocationCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ); + +HRESULT +FindFirstLocationElement( + IN IAppHostConfigLocation * pLocation, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindNextLocationElement( + IN IAppHostConfigLocation * pLocation, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT GetSharedConfigEnabled( + BOOL * pfIsSharedConfig +); \ No newline at end of file diff --git a/src/IISLib/base64.cpp b/src/IISLib/base64.cpp new file mode 100644 index 0000000000..b8b6a0bf74 --- /dev/null +++ b/src/IISLib/base64.cpp @@ -0,0 +1,482 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +DWORD +Base64Encode( + __in_bcount(cbDecodedBufferSize) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt(cchEncodedStringSize) PWSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pDecodedBuffer (IN) - buffer to encode. + cbDecodedBufferSize (IN) - size of buffer to encode. + cchEncodedStringSize (IN) - size of the buffer for the encoded string. + pszEncodedString (OUT) = the encoded string. + pcchEncoded (OUT) - size in characters of the encoded string. + +Return Values: + + 0 - success. + E_OUTOFMEMORY + +--*/ +{ + static WCHAR rgchEncodeTable[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + DWORD ib; + DWORD ich; + DWORD cchEncoded; + BYTE b0, b1, b2; + BYTE * pbDecodedBuffer = (BYTE *) pDecodedBuffer; + + // Calculate encoded string size. + cchEncoded = 1 + (cbDecodedBufferSize + 2) / 3 * 4; + + if (NULL != pcchEncoded) { + *pcchEncoded = cchEncoded; + } + + if (cchEncodedStringSize == 0 && pszEncodedString == NULL) { + return ERROR_SUCCESS; + } + + if (cchEncodedStringSize < cchEncoded) { + // Given buffer is too small to hold encoded string. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Encode data byte triplets into four-byte clusters. + ib = ich = 0; + while (ib < cbDecodedBufferSize) { + b0 = pbDecodedBuffer[ib++]; + b1 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + b2 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + + // + // The checks below for buffer overflow seems redundant to me. + // But it's the only way I can find to keep OACR quiet so it + // will have to do. + // + + pszEncodedString[ich++] = rgchEncodeTable[b0 >> 2]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b0 << 4) & 0x30) | ((b1 >> 4) & 0x0f)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b1 << 2) & 0x3c) | ((b2 >> 6) & 0x03)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[b2 & 0x3f]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + } + + // Pad the last cluster as necessary to indicate the number of data bytes + // it represents. + switch (cbDecodedBufferSize % 3) { + case 0: + break; + case 1: + pszEncodedString[ich - 2] = '='; + __fallthrough; + case 2: + pszEncodedString[ich - 1] = '='; + break; + } + + // Null-terminate the encoded string. + pszEncodedString[ich++] = '\0'; + + DBG_ASSERT(ich == cchEncoded); + + return ERROR_SUCCESS; +} + + +DWORD +Base64Decode( + __in PCWSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pszEncodedString (IN) - base64-encoded string to decode. + cbDecodeBufferSize (IN) - size in bytes of the decode buffer. + pbDecodeBuffer (OUT) - holds the decoded data. + pcbDecoded (OUT) - number of data bytes in the decoded data (if success or + STATUS_BUFFER_TOO_SMALL). + +Return Values: + + 0 - success. + E_OUTOFMEMORY + E_INVALIDARG + +--*/ +{ +#define NA (255) +#define DECODE(x) (((ULONG)(x) < sizeof(rgbDecodeTable)) ? rgbDecodeTable[x] : NA) + + static BYTE rgbDecodeTable[128] = { + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 0-15 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 16-31 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 62, NA, NA, NA, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NA, NA, NA, 0, NA, NA, // 48-63 + NA, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NA, NA, NA, NA, NA, // 80-95 + NA, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NA, NA, NA, NA, NA, // 112-127 + }; + + DWORD cbDecoded; + DWORD cchEncodedSize; + DWORD ich; + DWORD ib; + BYTE b0, b1, b2, b3; + BYTE * pbDecodeBuffer = (BYTE *) pDecodeBuffer; + + cchEncodedSize = (DWORD)wcslen(pszEncodedString); + if (NULL != pcbDecoded) { + *pcbDecoded = 0; + } + + if ((0 == cchEncodedSize) || (0 != (cchEncodedSize % 4))) { + // Input string is not sized correctly to be base64. + return ERROR_INVALID_PARAMETER; + } + + // Calculate decoded buffer size. + cbDecoded = (cchEncodedSize + 3) / 4 * 3; + if (pszEncodedString[cchEncodedSize-1] == '=') { + if (pszEncodedString[cchEncodedSize-2] == '=') { + // Only one data byte is encoded in the last cluster. + cbDecoded -= 2; + } + else { + // Only two data bytes are encoded in the last cluster. + cbDecoded -= 1; + } + } + + if (NULL != pcbDecoded) { + *pcbDecoded = cbDecoded; + } + + if (cbDecodeBufferSize == 0 && pDecodeBuffer == NULL) { + return ERROR_SUCCESS; + } + + if (cbDecoded > cbDecodeBufferSize) { + // Supplied buffer is too small. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Decode each four-byte cluster into the corresponding three data bytes. + ich = ib = 0; + while (ich < cchEncodedSize) { + b0 = DECODE(pszEncodedString[ich]); ich++; + b1 = DECODE(pszEncodedString[ich]); ich++; + b2 = DECODE(pszEncodedString[ich]); ich++; + b3 = DECODE(pszEncodedString[ich]); ich++; + + if ((NA == b0) || (NA == b1) || (NA == b2) || (NA == b3)) { + // Contents of input string are not base64. + return ERROR_INVALID_PARAMETER; + } + + pbDecodeBuffer[ib++] = (b0 << 2) | (b1 >> 4); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b1 << 4) | (b2 >> 2); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b2 << 6) | b3; + } + } + } + + DBG_ASSERT(ib == cbDecoded); + + return ERROR_SUCCESS; +} + + +DWORD +Base64Encode( + __in_bcount(cbDecodedBufferSize) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt(cchEncodedStringSize) PSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pDecodedBuffer (IN) - buffer to encode. + cbDecodedBufferSize (IN) - size of buffer to encode. + cchEncodedStringSize (IN) - size of the buffer for the encoded string. + pszEncodedString (OUT) = the encoded string. + pcchEncoded (OUT) - size in characters of the encoded string. + +Return Values: + + 0 - success. + E_OUTOFMEMORY + +--*/ +{ + static CHAR rgchEncodeTable[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + DWORD ib; + DWORD ich; + DWORD cchEncoded; + BYTE b0, b1, b2; + BYTE * pbDecodedBuffer = (BYTE *) pDecodedBuffer; + + // Calculate encoded string size. + cchEncoded = 1 + (cbDecodedBufferSize + 2) / 3 * 4; + + if (NULL != pcchEncoded) { + *pcchEncoded = cchEncoded; + } + + if (cchEncodedStringSize == 0 && pszEncodedString == NULL) { + return ERROR_SUCCESS; + } + + if (cchEncodedStringSize < cchEncoded) { + // Given buffer is too small to hold encoded string. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Encode data byte triplets into four-byte clusters. + ib = ich = 0; + while (ib < cbDecodedBufferSize) { + b0 = pbDecodedBuffer[ib++]; + b1 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + b2 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + + // + // The checks below for buffer overflow seems redundant to me. + // But it's the only way I can find to keep OACR quiet so it + // will have to do. + // + + pszEncodedString[ich++] = rgchEncodeTable[b0 >> 2]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b0 << 4) & 0x30) | ((b1 >> 4) & 0x0f)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b1 << 2) & 0x3c) | ((b2 >> 6) & 0x03)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[b2 & 0x3f]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + } + + // Pad the last cluster as necessary to indicate the number of data bytes + // it represents. + switch (cbDecodedBufferSize % 3) { + case 0: + break; + case 1: + pszEncodedString[ich - 2] = '='; + __fallthrough; + case 2: + pszEncodedString[ich - 1] = '='; + break; + } + + // Null-terminate the encoded string. + pszEncodedString[ich++] = '\0'; + + DBG_ASSERT(ich == cchEncoded); + + return ERROR_SUCCESS; +} + + +DWORD +Base64Decode( + __in PCSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pszEncodedString (IN) - base64-encoded string to decode. + cbDecodeBufferSize (IN) - size in bytes of the decode buffer. + pbDecodeBuffer (OUT) - holds the decoded data. + pcbDecoded (OUT) - number of data bytes in the decoded data (if success or + STATUS_BUFFER_TOO_SMALL). + +Return Values: + + 0 - success. + E_OUTOFMEMORY + E_INVALIDARG + +--*/ +{ +#define NA (255) +#define DECODE(x) (((ULONG)(x) < sizeof(rgbDecodeTable)) ? rgbDecodeTable[x] : NA) + + static BYTE rgbDecodeTable[128] = { + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 0-15 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 16-31 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 62, NA, NA, NA, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NA, NA, NA, 0, NA, NA, // 48-63 + NA, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NA, NA, NA, NA, NA, // 80-95 + NA, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NA, NA, NA, NA, NA, // 112-127 + }; + + DWORD cbDecoded; + DWORD cchEncodedSize; + DWORD ich; + DWORD ib; + BYTE b0, b1, b2, b3; + BYTE * pbDecodeBuffer = (BYTE *) pDecodeBuffer; + + cchEncodedSize = (DWORD)strlen(pszEncodedString); + if (NULL != pcbDecoded) { + *pcbDecoded = 0; + } + + if ((0 == cchEncodedSize) || (0 != (cchEncodedSize % 4))) { + // Input string is not sized correctly to be base64. + return ERROR_INVALID_PARAMETER; + } + + // Calculate decoded buffer size. + cbDecoded = (cchEncodedSize + 3) / 4 * 3; + if (pszEncodedString[cchEncodedSize-1] == '=') { + if (pszEncodedString[cchEncodedSize-2] == '=') { + // Only one data byte is encoded in the last cluster. + cbDecoded -= 2; + } + else { + // Only two data bytes are encoded in the last cluster. + cbDecoded -= 1; + } + } + + if (NULL != pcbDecoded) { + *pcbDecoded = cbDecoded; + } + + if (cbDecodeBufferSize == 0 && pDecodeBuffer == NULL) { + return ERROR_SUCCESS; + } + + if (cbDecoded > cbDecodeBufferSize) { + // Supplied buffer is too small. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Decode each four-byte cluster into the corresponding three data bytes. + ich = ib = 0; + while (ich < cchEncodedSize) { + b0 = DECODE(pszEncodedString[ich]); ich++; + b1 = DECODE(pszEncodedString[ich]); ich++; + b2 = DECODE(pszEncodedString[ich]); ich++; + b3 = DECODE(pszEncodedString[ich]); ich++; + + if ((NA == b0) || (NA == b1) || (NA == b2) || (NA == b3)) { + // Contents of input string are not base64. + return ERROR_INVALID_PARAMETER; + } + + pbDecodeBuffer[ib++] = (b0 << 2) | (b1 >> 4); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b1 << 4) | (b2 >> 2); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b2 << 6) | b3; + } + } + } + + DBG_ASSERT(ib == cbDecoded); + + return ERROR_SUCCESS; +} + diff --git a/src/IISLib/base64.h b/src/IISLib/base64.h new file mode 100644 index 0000000000..469b074d73 --- /dev/null +++ b/src/IISLib/base64.h @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +DWORD +Base64Encode( + __in_bcount( cbDecodedBufferSize ) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt( cchEncodedStringSize ) PWSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ); + +DWORD +Base64Decode( + __in PCWSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ); + +DWORD +Base64Encode( + __in_bcount( cbDecodedBufferSize ) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt( cchEncodedStringSize ) PSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ); + +DWORD +Base64Decode( + __in PCSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ); + +#endif // _BASE64_HXX_ + diff --git a/src/IISLib/buffer.h b/src/IISLib/buffer.h new file mode 100644 index 0000000000..1d68155387 --- /dev/null +++ b/src/IISLib/buffer.h @@ -0,0 +1,271 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + + +// +// BUFFER_T class shouldn't be used directly. Use BUFFER specialization class instead. +// The only BUFFER_T partners are STRU and STRA classes. +// BUFFER_T cannot hold other but primitive types since it doesn't call +// constructor and destructor. +// +// Note: Size is in bytes. +// +template +class BUFFER_T +{ +public: + + BUFFER_T() + : m_cbBuffer( sizeof(m_rgBuffer) ), + m_fHeapAllocated( false ), + m_pBuffer(m_rgBuffer) + /*++ + Description: + + Default constructor where the inline buffer is used. + + Arguments: + + None. + + Returns: + + None. + + --*/ + { + } + + BUFFER_T( + __inout_bcount(cbInit) T* pbInit, + __in DWORD cbInit + ) : m_pBuffer( pbInit ), + m_cbBuffer( cbInit ), + m_fHeapAllocated( false ) + /*++ + Description: + + Instantiate BUFFER, initially using pbInit as buffer + This is useful for stack-buffers and inline-buffer class members + (see STACK_BUFFER and INLINE_BUFFER_INIT below) + + BUFFER does not free pbInit. + + Arguments: + + pbInit - Initial buffer to use. + cbInit - Size of pbInit in bytes (not in elements). + + Returns: + + None. + + --*/ + { + _ASSERTE( NULL != pbInit ); + _ASSERTE( cbInit > 0 ); + } + + ~BUFFER_T() + { + if( IsHeapAllocated() ) + { + _ASSERTE( NULL != m_pBuffer ); + HeapFree( GetProcessHeap(), 0, m_pBuffer ); + m_pBuffer = NULL; + m_cbBuffer = 0; + m_fHeapAllocated = false; + } + } + + T* + QueryPtr( + VOID + ) const + { + // + // Return pointer to data buffer. + // + return m_pBuffer; + } + + DWORD + QuerySize( + VOID + ) const + { + // + // Return number of bytes. + // + return m_cbBuffer; + } + + __success(return == true) + bool + Resize( + const SIZE_T cbNewSize, + const bool fZeroMemoryBeyondOldSize = false + ) + /*++ + Description: + + Resizes the buffer. + + Arguments: + + cbNewSize - Size in bytes to grow to. + fZeroMemoryBeyondOldSize + - Whether to zero the region of memory of the + new buffer beyond the original size. + + Returns: + + TRUE on success, FALSE on failure. + + --*/ + { + PVOID pNewMem; + + if ( cbNewSize <= m_cbBuffer ) + { + return true; + } + + if ( cbNewSize > MAXDWORD ) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return false; + } + + DWORD dwHeapAllocFlags = fZeroMemoryBeyondOldSize ? HEAP_ZERO_MEMORY : 0; + + if( IsHeapAllocated() ) + { + pNewMem = HeapReAlloc( GetProcessHeap(), dwHeapAllocFlags, m_pBuffer, cbNewSize ); + } + else + { + pNewMem = HeapAlloc( GetProcessHeap(), dwHeapAllocFlags, cbNewSize ); + } + + if( pNewMem == NULL ) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return false; + } + + if( !IsHeapAllocated() ) + { + // + // First time this block is allocated. Copy over old contents. + // + memcpy_s( pNewMem, static_cast(cbNewSize), m_pBuffer, m_cbBuffer ); + m_fHeapAllocated = true; + } + + m_pBuffer = reinterpret_cast(pNewMem); + m_cbBuffer = static_cast(cbNewSize); + + _ASSERTE( m_pBuffer != NULL ); + + return true; + } + +private: + + bool + IsHeapAllocated( + VOID + ) const + { + return m_fHeapAllocated; + } + + // + // The default inline buffer. + // This member should be at the beginning for alignment purposes. + // + T m_rgBuffer[LENGTH]; + + // + // Is m_pBuffer dynamically allocated? + // + bool m_fHeapAllocated; + + // + // Size of the buffer as requested by client in bytes. + // + DWORD m_cbBuffer; + + // + // Pointer to buffer. + // + __field_bcount_full(m_cbBuffer) + T* m_pBuffer; +}; + +// +// Resizes the buffer by 2 if the ideal size is bigger +// than the buffer length. That give us lg(n) allocations. +// +// Use template inferring like: +// +// BUFFER buff; +// hr = ResizeBufferByTwo(buff, 100); +// +template +HRESULT +ResizeBufferByTwo( + BUFFER_T& Buffer, + SIZE_T cbIdealSize, + bool fZeroMemoryBeyondOldSize = false +) +{ + if (cbIdealSize > Buffer.QuerySize()) + { + if (!Buffer.Resize(max(cbIdealSize, static_cast(Buffer.QuerySize() * 2)), + fZeroMemoryBeyondOldSize)) + { + return E_OUTOFMEMORY; + } + } + return S_OK; +} + + +// +// +// Lots of code uses BUFFER class to store a bunch of different +// structures, so m_rgBuffer needs to be 8 byte aligned when it is used +// as an opaque buffer. +// +#define INLINED_BUFFER_LEN 32 +typedef BUFFER_T BUFFER; + +// +// Assumption of macros below for pointer alignment purposes +// +C_ASSERT( sizeof(VOID*) <= sizeof(ULONGLONG) ); + +// +// Declare a BUFFER that will use stack memory of +// bytes. If the buffer overflows then a heap buffer will be allocated. +// +#define STACK_BUFFER( _name, _size ) \ + ULONGLONG __aqw##_name[ ( ( (_size) + sizeof(ULONGLONG) - 1 ) / sizeof(ULONGLONG) ) ]; \ + BUFFER _name( (BYTE*)__aqw##_name, sizeof(__aqw##_name) ) + +// +// Macros for declaring and initializing a BUFFER that will use inline memory +// of bytes as a member of an object. +// +#define INLINE_BUFFER( _name, _size ) \ + ULONGLONG __aqw##_name[ ( ( (_size) + sizeof(ULONGLONG) - 1 ) / sizeof(ULONGLONG) ) ]; \ + BUFFER _name; + +#define INLINE_BUFFER_INIT( _name ) \ + _name( (BYTE*)__aqw##_name, sizeof( __aqw##_name ) ) diff --git a/src/IISLib/datetime.h b/src/IISLib/datetime.h new file mode 100644 index 0000000000..fd09b7a6a0 --- /dev/null +++ b/src/IISLib/datetime.h @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _DATETIME_H_ +#define _DATETIME_H_ + +BOOL +StringTimeToFileTime( + PCSTR pszTime, + ULONGLONG * pulTime +); + +#endif + diff --git a/src/IISLib/dbgutil.h b/src/IISLib/dbgutil.h new file mode 100644 index 0000000000..45c26777a9 --- /dev/null +++ b/src/IISLib/dbgutil.h @@ -0,0 +1,102 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _DBGUTIL_H_ +#define _DBGUTIL_H_ + +#include + +// +// TODO +// Using _CrtDbg implementation. If hooking is desired +// wrappers should be provided here so that we can reimplement +// if neecessary. +// +// IF_DEBUG/DEBUG FLAGS +// +// registry configuration +// + +// +// Debug error levels for DEBUG_FLAGS_VAR. +// + +#define DEBUG_FLAG_INFO 0x00000001 +#define DEBUG_FLAG_WARN 0x00000002 +#define DEBUG_FLAG_ERROR 0x00000004 + +// +// Predefined error level values. These are backwards from the +// windows definitions. +// + +#define DEBUG_FLAGS_INFO (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN | DEBUG_FLAG_INFO) +#define DEBUG_FLAGS_WARN (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN) +#define DEBUG_FLAGS_ERROR (DEBUG_FLAG_ERROR) +#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) + +// +// Global variables to control tracing. Generally per module +// + +#ifndef DEBUG_FLAGS_VAR +#define DEBUG_FLAGS_VAR g_dwDebugFlags +#endif + +#ifndef DEBUG_LABEL_VAR +#define DEBUG_LABEL_VAR g_szDebugLabel +#endif + +extern PCSTR DEBUG_LABEL_VAR; +extern DWORD DEBUG_FLAGS_VAR; + +// +// Module should make this declaration globally. +// + +#define DECLARE_DEBUG_PRINT_OBJECT( _pszLabel_ ) \ + PCSTR DEBUG_LABEL_VAR = _pszLabel_; \ + DWORD DEBUG_FLAGS_VAR = DEBUG_FLAGS_ANY; \ + +#define DECLARE_DEBUG_PRINT_OBJECT2( _pszLabel_, _dwLevel_ ) \ + PCSTR DEBUG_LABEL_VAR = _pszLabel_; \ + DWORD DEBUG_FLAGS_VAR = _dwLevel_; \ + +// +// This doesn't do anything now. Should be safe to call in dll main. +// + +#define CREATE_DEBUG_PRINT_OBJECT + +// +// Trace macros +// + +#define DBG_CONTEXT _CRT_WARN, __FILE__, __LINE__, DEBUG_LABEL_VAR + +#ifdef DEBUG +#define DBGINFO(args) \ +{if (DEBUG_FLAGS_VAR & DEBUG_FLAG_INFO) { _CrtDbgReport args; }} +#define DBGWARN(args) \ +{if (DEBUG_FLAGS_VAR & DEBUG_FLAG_WARN) { _CrtDbgReport args; }} +#define DBGERROR(args) \ +{if (DEBUG_FLAGS_VAR & DEBUG_FLAG_ERROR) { _CrtDbgReport args; }} +#else +#define DBGINFO +#define DBGWARN +#define DBGERROR +#endif + +#define DBGPRINTF DBGINFO + +// +// Simple error traces +// + +#define DBGERROR_HR( _hr_ ) \ + DBGERROR((DBG_CONTEXT, "hr=0x%x\n", _hr_)) + +#define DBGERROR_STATUS( _status_ ) \ + DBGERROR((DBG_CONTEXT, "status=%d\n", _status_)) + +#endif diff --git a/src/IISLib/hashfn.h b/src/IISLib/hashfn.h new file mode 100644 index 0000000000..a7bfeda2cf --- /dev/null +++ b/src/IISLib/hashfn.h @@ -0,0 +1,325 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef __HASHFN_H__ +#define __HASHFN_H__ + + +// Produce a scrambled, randomish number in the range 0 to RANDOM_PRIME-1. +// Applying this to the results of the other hash functions is likely to +// produce a much better distribution, especially for the identity hash +// functions such as Hash(char c), where records will tend to cluster at +// the low end of the hashtable otherwise. LKRhash applies this internally +// to all hash signatures for exactly this reason. + +inline DWORD +HashScramble(DWORD dwHash) +{ + // Here are 10 primes slightly greater than 10^9 + // 1000000007, 1000000009, 1000000021, 1000000033, 1000000087, + // 1000000093, 1000000097, 1000000103, 1000000123, 1000000181. + + // default value for "scrambling constant" + const DWORD RANDOM_CONSTANT = 314159269UL; + // large prime number, also used for scrambling + const DWORD RANDOM_PRIME = 1000000007UL; + + return (RANDOM_CONSTANT * dwHash) % RANDOM_PRIME ; +} + + +// Faster scrambling function suggested by Eric Jacobsen + +inline DWORD +HashRandomizeBits(DWORD dw) +{ + return (((dw * 1103515245 + 12345) >> 16) + | ((dw * 69069 + 1) & 0xffff0000)); +} + + +// Small prime number used as a multiplier in the supplied hash functions +const DWORD HASH_MULTIPLIER = 101; + +#undef HASH_SHIFT_MULTIPLY + +#ifdef HASH_SHIFT_MULTIPLY +# define HASH_MULTIPLY(dw) (((dw) << 7) - (dw)) +#else +# define HASH_MULTIPLY(dw) ((dw) * HASH_MULTIPLIER) +#endif + +// Fast, simple hash function that tends to give a good distribution. +// Apply HashScramble to the result if you're using this for something +// other than LKRhash. + +inline DWORD +HashString( + const char* psz, + DWORD dwHash = 0) +{ + // force compiler to use unsigned arithmetic + const unsigned char* upsz = (const unsigned char*) psz; + + for ( ; *upsz; ++upsz) + dwHash = HASH_MULTIPLY(dwHash) + *upsz; + + return dwHash; +} + +inline DWORD +HashString( + __in_ecount(cch) const char* psz, + __in DWORD cch, + __in DWORD dwHash +) +{ + // force compiler to use unsigned arithmetic + const unsigned char* upsz = (const unsigned char*) psz; + + for (DWORD Index = 0; + Index < cch; + ++Index, ++upsz) + { + dwHash = HASH_MULTIPLY(dwHash) + *upsz; + } + + return dwHash; +} + + +// Unicode version of above + +inline DWORD +HashString( + const wchar_t* pwsz, + DWORD dwHash = 0) +{ + for ( ; *pwsz; ++pwsz) + dwHash = HASH_MULTIPLY(dwHash) + *pwsz; + + return dwHash; +} + +// Based on length of the string instead of null-terminating character + +inline DWORD +HashString( + __in_ecount(cch) const wchar_t* pwsz, + __in DWORD cch, + __in DWORD dwHash +) +{ + for (DWORD Index = 0; + Index < cch; + ++Index, ++pwsz) + { + dwHash = HASH_MULTIPLY(dwHash) + *pwsz; + } + + return dwHash; +} + + +// Quick-'n'-dirty case-insensitive string hash function. +// Make sure that you follow up with _stricmp or _mbsicmp. You should +// also cache the length of strings and check those first. Caching +// an uppercase version of a string can help too. +// Again, apply HashScramble to the result if using with something other +// than LKRhash. +// Note: this is not really adequate for MBCS strings. + +inline DWORD +HashStringNoCase( + const char* psz, + DWORD dwHash = 0) +{ + const unsigned char* upsz = (const unsigned char*) psz; + + for ( ; *upsz; ++upsz) + dwHash = HASH_MULTIPLY(dwHash) + + (*upsz & 0xDF); // strip off lowercase bit + + return dwHash; +} + +inline DWORD +HashStringNoCase( + __in_ecount(cch) + const char* psz, + SIZE_T cch, + DWORD dwHash) +{ + const unsigned char* upsz = (const unsigned char*) psz; + + for (SIZE_T Index = 0; + Index < cch; + ++Index, ++upsz) + { + dwHash = HASH_MULTIPLY(dwHash) + + (*upsz & 0xDF); // strip off lowercase bit + } + return dwHash; +} + + +// Unicode version of above + +inline DWORD +HashStringNoCase( + const wchar_t* pwsz, + DWORD dwHash = 0) +{ + for ( ; *pwsz; ++pwsz) + dwHash = HASH_MULTIPLY(dwHash) + (*pwsz & 0xFFDF); + + return dwHash; +} + +// Unicode version of above with length + +inline DWORD +HashStringNoCase( + __in_ecount(cch) + const wchar_t* pwsz, + SIZE_T cch, + DWORD dwHash) +{ + for (SIZE_T Index = 0; + Index < cch; + ++Index, ++pwsz) + { + dwHash = HASH_MULTIPLY(dwHash) + (*pwsz & 0xFFDF); + } + return dwHash; +} + + +// HashBlob returns the hash of a blob of arbitrary binary data. +// +// Warning: HashBlob is generally not the right way to hash a class object. +// Consider: +// class CFoo { +// public: +// char m_ch; +// double m_d; +// char* m_psz; +// }; +// +// inline DWORD Hash(const CFoo& rFoo) +// { return HashBlob(&rFoo, sizeof(CFoo)); } +// +// This is the wrong way to hash a CFoo for two reasons: (a) there will be +// a 7-byte gap between m_ch and m_d imposed by the alignment restrictions +// of doubles, which will be filled with random data (usually non-zero for +// stack variables), and (b) it hashes the address (rather than the +// contents) of the string m_psz. Similarly, +// +// bool operator==(const CFoo& rFoo1, const CFoo& rFoo2) +// { return memcmp(&rFoo1, &rFoo2, sizeof(CFoo)) == 0; } +// +// does the wrong thing. Much better to do this: +// +// DWORD Hash(const CFoo& rFoo) +// { +// return HashString(rFoo.m_psz, +// HASH_MULTIPLIER * Hash(rFoo.m_ch) +// + Hash(rFoo.m_d)); +// } +// +// Again, apply HashScramble if using with something other than LKRhash. + +inline DWORD +HashBlob( + const void* pv, + size_t cb, + DWORD dwHash = 0) +{ + const BYTE * pb = static_cast(pv); + + while (cb-- > 0) + dwHash = HASH_MULTIPLY(dwHash) + *pb++; + + return dwHash; +} + + + +// +// Overloaded hash functions for all the major builtin types. +// Again, apply HashScramble to result if using with something other than +// LKRhash. +// + +inline DWORD Hash(const char* psz) +{ return HashString(psz); } + +inline DWORD Hash(const unsigned char* pusz) +{ return HashString(reinterpret_cast(pusz)); } + +inline DWORD Hash(const signed char* pssz) +{ return HashString(reinterpret_cast(pssz)); } + +inline DWORD Hash(const wchar_t* pwsz) +{ return HashString(pwsz); } + +inline DWORD +Hash( + const GUID* pguid, + DWORD dwHash = 0) +{ + + return * reinterpret_cast(const_cast(pguid)) + dwHash; +} + +// Identity hash functions: scalar values map to themselves +inline DWORD Hash(char c) +{ return c; } + +inline DWORD Hash(unsigned char uc) +{ return uc; } + +inline DWORD Hash(signed char sc) +{ return sc; } + +inline DWORD Hash(short sh) +{ return sh; } + +inline DWORD Hash(unsigned short ush) +{ return ush; } + +inline DWORD Hash(int i) +{ return i; } + +inline DWORD Hash(unsigned int u) +{ return u; } + +inline DWORD Hash(long l) +{ return l; } + +inline DWORD Hash(unsigned long ul) +{ return ul; } + +inline DWORD Hash(float f) +{ + // be careful of rounding errors when computing keys + union { + float f; + DWORD dw; + } u; + u.f = f; + return u.dw; +} + +inline DWORD Hash(double dbl) +{ + // be careful of rounding errors when computing keys + union { + double dbl; + DWORD dw[2]; + } u; + u.dbl = dbl; + return u.dw[0] * HASH_MULTIPLIER + u.dw[1]; +} + +#endif // __HASHFN_H__ diff --git a/src/IISLib/hashtable.h b/src/IISLib/hashtable.h new file mode 100644 index 0000000000..9319e5643d --- /dev/null +++ b/src/IISLib/hashtable.h @@ -0,0 +1,666 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "rwlock.h" +#include "prime.h" + +template +class HASH_NODE +{ + template + friend class HASH_TABLE; + + HASH_NODE( + _Record * pRecord, + DWORD dwHash + ) : _pNext (NULL), + _pRecord (pRecord), + _dwHash (dwHash) + {} + + ~HASH_NODE() + { + _ASSERTE(_pRecord == NULL); + } + + private: + // Next node in the hash table look-aside + HASH_NODE<_Record> *_pNext; + + // actual record + _Record * _pRecord; + + // hash value + DWORD _dwHash; +}; + +template +class HASH_TABLE +{ +protected: + typedef BOOL + (PFN_DELETE_IF)( + _Record * pRecord, + PVOID pvContext + ); + + typedef VOID + (PFN_APPLY)( + _Record * pRecord, + PVOID pvContext + ); + +public: + HASH_TABLE( + VOID + ) + : _ppBuckets( NULL ), + _nBuckets( 0 ), + _nItems( 0 ) + { + } + + virtual + ~HASH_TABLE(); + + virtual + VOID + ReferenceRecord( + _Record * pRecord + ) = 0; + + virtual + VOID + DereferenceRecord( + _Record * pRecord + ) = 0; + + virtual + _Key + ExtractKey( + _Record * pRecord + ) = 0; + + virtual + DWORD + CalcKeyHash( + _Key key + ) = 0; + + virtual + BOOL + EqualKeys( + _Key key1, + _Key key2 + ) = 0; + + DWORD + Count( + VOID + ) const; + + bool + IsInitialized( + VOID + ) const; + + virtual + VOID + Clear(); + + HRESULT + Initialize( + DWORD nBucketSize + ); + + virtual + VOID + FindKey( + _Key key, + _Record ** ppRecord + ); + + virtual + HRESULT + InsertRecord( + _Record * pRecord + ); + + virtual + VOID + DeleteKey( + _Key key + ); + + virtual + VOID + DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext + ); + + VOID + Apply( + PFN_APPLY pfnApply, + PVOID pvContext + ); + +private: + + __success(*ppNode != NULL && return != FALSE) + BOOL + FindNodeInternal( + _Key key, + DWORD dwHash, + __deref_out + HASH_NODE<_Record> ** ppNode, + __deref_opt_out + HASH_NODE<_Record> *** pppPreviousNodeNextPointer = NULL + ); + + VOID + DeleteNode( + HASH_NODE<_Record> * pNode + ) + { + if (pNode->_pRecord != NULL) + { + DereferenceRecord(pNode->_pRecord); + pNode->_pRecord = NULL; + } + + delete pNode; + } + + VOID + RehashTableIfNeeded( + VOID + ); + + HASH_NODE<_Record> ** _ppBuckets; + DWORD _nBuckets; + DWORD _nItems; + // + // Allow to use lock object in const methods. + // + mutable + CWSDRWLock _tableLock; +}; + +template +HRESULT +HASH_TABLE<_Record,_Key>::Initialize( + DWORD nBuckets +) +{ + HRESULT hr = S_OK; + + if ( nBuckets == 0 ) + { + hr = E_INVALIDARG; + goto Failed; + } + + if (nBuckets >= MAXDWORD/sizeof(HASH_NODE<_Record> *)) + { + hr = E_INVALIDARG; + goto Failed; + } + + _ASSERTE(_ppBuckets == NULL ); + if ( _ppBuckets != NULL ) + { + hr = E_INVALIDARG; + goto Failed; + } + + hr = _tableLock.Init(); + if ( FAILED( hr ) ) + { + goto Failed; + } + + _ppBuckets = (HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(HASH_NODE<_Record> *)); + if (_ppBuckets == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Failed; + } + _nBuckets = nBuckets; + + return S_OK; + +Failed: + + if (_ppBuckets) + { + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + } + + return hr; +} + + +template +HASH_TABLE<_Record,_Key>::~HASH_TABLE() +{ + if (_ppBuckets == NULL) + { + return; + } + + _ASSERTE(_nItems == 0); + + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + _nBuckets = 0; +} + +template< class _Record, class _Key> +DWORD +HASH_TABLE<_Record,_Key>::Count() const +{ + return _nItems; +} + +template< class _Record, class _Key> +bool +HASH_TABLE<_Record,_Key>::IsInitialized( + VOID +) const +{ + return _ppBuckets != NULL; +} + + +template +VOID +HASH_TABLE<_Record,_Key>::Clear() +{ + HASH_NODE<_Record> *pCurrent; + HASH_NODE<_Record> *pNext; + + // This is here in the off cases where someone instantiates a hashtable + // and then does an automatic "clear" before its destruction WITHOUT + // ever initializing it. + if ( ! _tableLock.QueryInited() ) + { + return; + } + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pCurrent = _ppBuckets[i]; + _ppBuckets[i] = NULL; + while (pCurrent != NULL) + { + pNext = pCurrent->_pNext; + DeleteNode(pCurrent); + pCurrent = pNext; + } + } + + _nItems = 0; + _tableLock.ExclusiveRelease(); +} + +template +__success(*ppNode != NULL && return != FALSE) +BOOL +HASH_TABLE<_Record,_Key>::FindNodeInternal( + _Key key, + DWORD dwHash, + __deref_out + HASH_NODE<_Record> ** ppNode, + __deref_opt_out + HASH_NODE<_Record> *** pppPreviousNodeNextPointer +) +/*++ + Return value indicates whether the item is found + key, dwHash - key and hash for the node to find + ppNode - on successful return, the node found, on failed return, the first + node with hash value greater than the node to be found + pppPreviousNodeNextPointer - the pointer to previous node's _pNext + + This routine may be called under either read or write lock +--*/ +{ + HASH_NODE<_Record> **ppPreviousNodeNextPointer; + HASH_NODE<_Record> *pNode; + BOOL fFound = FALSE; + + ppPreviousNodeNextPointer = _ppBuckets + (dwHash % _nBuckets); + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + if (pNode->_dwHash == dwHash) + { + if (EqualKeys(key, + ExtractKey(pNode->_pRecord))) + { + fFound = TRUE; + break; + } + } + else if (pNode->_dwHash > dwHash) + { + break; + } + + ppPreviousNodeNextPointer = &(pNode->_pNext); + pNode = *ppPreviousNodeNextPointer; + } + + __analysis_assume( (pNode == NULL && fFound == FALSE) || + (pNode != NULL && fFound == TRUE ) ); + *ppNode = pNode; + if (pppPreviousNodeNextPointer != NULL) + { + *pppPreviousNodeNextPointer = ppPreviousNodeNextPointer; + } + return fFound; +} + +template +VOID +HASH_TABLE<_Record,_Key>::FindKey( + _Key key, + _Record ** ppRecord +) +{ + HASH_NODE<_Record> *pNode; + + *ppRecord = NULL; + + DWORD dwHash = CalcKeyHash(key); + + _tableLock.SharedAcquire(); + + if (FindNodeInternal(key, dwHash, &pNode) && + pNode->_pRecord != NULL) + { + ReferenceRecord(pNode->_pRecord); + *ppRecord = pNode->_pRecord; + } + + _tableLock.SharedRelease(); +} + +template +HRESULT +HASH_TABLE<_Record,_Key>::InsertRecord( + _Record * pRecord +) +/*++ + This method inserts a node for this record and also empty nodes for paths + in the heirarchy leading upto this path + + The insert is done under only a read-lock - this is possible by keeping + the hashes in a bucket in increasing order and using interlocked operations + to actually insert the item in the hash-bucket lookaside list and the parent + children list + + Returns HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) if the record already exists. + Never leak this error to the end user because "*file* already exists" may be confusing. +--*/ +{ + BOOL fLocked = FALSE; + _Key key = ExtractKey(pRecord); + DWORD dwHash = CalcKeyHash(key); + HRESULT hr = S_OK; + HASH_NODE<_Record> * pNewNode; + HASH_NODE<_Record> * pNextNode; + HASH_NODE<_Record> ** ppPreviousNodeNextPointer; + + // + // Ownership of pRecord is not transferred to pNewNode yet, so remember + // to either set it to null before deleting pNewNode or add an extra + // reference later - this is to make sure we do not do an extra ref/deref + // which users may view as getting flushed out of the hash-table + // + pNewNode = new HASH_NODE<_Record>(pRecord, dwHash); + if (pNewNode == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + + _tableLock.SharedAcquire(); + fLocked = TRUE; + + do + { + // + // Find the right place to add this node + // + if (FindNodeInternal(key, dwHash, &pNextNode, &ppPreviousNodeNextPointer)) + { + // + // If node already there, return error + // + pNewNode->_pRecord = NULL; + DeleteNode(pNewNode); + + // + // We should never leak this error to the end user + // because "file already exists" may be confusing. + // + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + goto Finished; + } + + // + // If another node got inserted in between, we will have to retry + // + pNewNode->_pNext = pNextNode; + } while (InterlockedCompareExchangePointer((PVOID *)ppPreviousNodeNextPointer, + pNewNode, + pNextNode) != pNextNode); + // pass ownership of pRecord now + if (pRecord != NULL) + { + ReferenceRecord(pRecord); + pRecord = NULL; + } + InterlockedIncrement((LONG *)&_nItems); + +Finished: + + if (fLocked) + { + _tableLock.SharedRelease(); + } + + if (SUCCEEDED(hr)) + { + RehashTableIfNeeded(); + } + + return hr; +} + +template +VOID +HASH_TABLE<_Record,_Key>::DeleteKey( + _Key key +) +{ + HASH_NODE<_Record> *pNode; + HASH_NODE<_Record> **ppPreviousNodeNextPointer; + + DWORD dwHash = CalcKeyHash(key); + + _tableLock.ExclusiveAcquire(); + + if (FindNodeInternal(key, dwHash, &pNode, &ppPreviousNodeNextPointer)) + { + *ppPreviousNodeNextPointer = pNode->_pNext; + DeleteNode(pNode); + _nItems--; + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +HASH_TABLE<_Record,_Key>::DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext +) +{ + HASH_NODE<_Record> *pNode; + HASH_NODE<_Record> **ppPreviousNodeNextPointer; + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + ppPreviousNodeNextPointer = _ppBuckets + i; + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + // + // Non empty nodes deleted based on DeleteIf, empty nodes deleted + // if they have no children + // + if (pfnDeleteIf(pNode->_pRecord, pvContext)) + { + *ppPreviousNodeNextPointer = pNode->_pNext; + DeleteNode(pNode); + _nItems--; + } + else + { + ppPreviousNodeNextPointer = &pNode->_pNext; + } + + pNode = *ppPreviousNodeNextPointer; + } + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +HASH_TABLE<_Record,_Key>::Apply( + PFN_APPLY pfnApply, + PVOID pvContext +) +{ + HASH_NODE<_Record> *pNode; + + _tableLock.SharedAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + if (pNode->_pRecord != NULL) + { + pfnApply(pNode->_pRecord, pvContext); + } + + pNode = pNode->_pNext; + } + } + + _tableLock.SharedRelease(); +} + +template +VOID +HASH_TABLE<_Record,_Key>::RehashTableIfNeeded( + VOID +) +{ + HASH_NODE<_Record> **ppBuckets; + DWORD nBuckets; + HASH_NODE<_Record> *pNode; + HASH_NODE<_Record> *pNextNode; + HASH_NODE<_Record> **ppNextPointer; + HASH_NODE<_Record> *pNewNextNode; + DWORD nNewBuckets; + + // + // If number of items has become too many, we will double the hash table + // size (we never reduce it however) + // + if (_nItems <= PRIME::GetPrime(2*_nBuckets)) + { + return; + } + + _tableLock.ExclusiveAcquire(); + + nNewBuckets = PRIME::GetPrime(2*_nBuckets); + + if (_nItems <= nNewBuckets) + { + goto Finished; + } + + nBuckets = nNewBuckets; + if (nBuckets >= 0xffffffff/sizeof(HASH_NODE<_Record> *)) + { + goto Finished; + } + ppBuckets = (HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(HASH_NODE<_Record> *)); + if (ppBuckets == NULL) + { + goto Finished; + } + + // + // Take out nodes from the old hash table and insert in the new one, make + // sure to keep the hashes in increasing order + // + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + pNextNode = pNode->_pNext; + + ppNextPointer = ppBuckets + (pNode->_dwHash % nBuckets); + pNewNextNode = *ppNextPointer; + while (pNewNextNode != NULL && + pNewNextNode->_dwHash <= pNode->_dwHash) + { + ppNextPointer = &pNewNextNode->_pNext; + pNewNextNode = pNewNextNode->_pNext; + } + pNode->_pNext = pNewNextNode; + *ppNextPointer = pNode; + + pNode = pNextNode; + } + } + + HeapFree(GetProcessHeap(), 0, _ppBuckets); + _ppBuckets = ppBuckets; + _nBuckets = nBuckets; + ppBuckets = NULL; + +Finished: + + _tableLock.ExclusiveRelease(); +} diff --git a/src/IISLib/listentry.h b/src/IISLib/listentry.h new file mode 100644 index 0000000000..80b70e97a9 --- /dev/null +++ b/src/IISLib/listentry.h @@ -0,0 +1,163 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#ifndef _LIST_ENTRY_H +#define _LIST_ENTRY_H + +// +// Doubly-linked list manipulation routines. +// + + +#define InitializeListHead32(ListHead) (\ + (ListHead)->Flink = (ListHead)->Blink = PtrToUlong((ListHead))) + + +FORCEINLINE +VOID +InitializeListHead( + IN PLIST_ENTRY ListHead + ) +{ + ListHead->Flink = ListHead->Blink = ListHead; +} + +FORCEINLINE +BOOLEAN +IsListEmpty( + IN const LIST_ENTRY * ListHead + ) +{ + return (BOOLEAN)(ListHead->Flink == ListHead); +} + +FORCEINLINE +BOOLEAN +RemoveEntryList( + IN PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Flink; + + Flink = Entry->Flink; + Blink = Entry->Blink; + Blink->Flink = Flink; + Flink->Blink = Blink; + return (BOOLEAN)(Flink == Blink); +} + +FORCEINLINE +PLIST_ENTRY +RemoveHeadList( + IN PLIST_ENTRY ListHead + ) +{ + PLIST_ENTRY Flink; + PLIST_ENTRY Entry; + + Entry = ListHead->Flink; + Flink = Entry->Flink; + ListHead->Flink = Flink; + Flink->Blink = ListHead; + return Entry; +} + + + +FORCEINLINE +PLIST_ENTRY +RemoveTailList( + IN PLIST_ENTRY ListHead + ) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Entry; + + Entry = ListHead->Blink; + Blink = Entry->Blink; + ListHead->Blink = Blink; + Blink->Flink = ListHead; + return Entry; +} + + +FORCEINLINE +VOID +InsertTailList( + IN PLIST_ENTRY ListHead, + IN PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Blink; + + Blink = ListHead->Blink; + Entry->Flink = ListHead; + Entry->Blink = Blink; + Blink->Flink = Entry; + ListHead->Blink = Entry; +} + + +FORCEINLINE +VOID +InsertHeadList( + IN PLIST_ENTRY ListHead, + IN PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Flink; + + Flink = ListHead->Flink; + Entry->Flink = Flink; + Entry->Blink = ListHead; + Flink->Blink = Entry; + ListHead->Flink = Entry; +} + +FORCEINLINE +VOID +AppendTailList( + IN PLIST_ENTRY ListHead, + IN PLIST_ENTRY ListToAppend + ) +{ + PLIST_ENTRY ListEnd = ListHead->Blink; + + ListHead->Blink->Flink = ListToAppend; + ListHead->Blink = ListToAppend->Blink; + ListToAppend->Blink->Flink = ListHead; + ListToAppend->Blink = ListEnd; +} + +FORCEINLINE +PSINGLE_LIST_ENTRY +PopEntryList( + PSINGLE_LIST_ENTRY ListHead + ) +{ + PSINGLE_LIST_ENTRY FirstEntry; + FirstEntry = ListHead->Next; + if (FirstEntry != NULL) { + ListHead->Next = FirstEntry->Next; + } + + return FirstEntry; +} + + +FORCEINLINE +VOID +PushEntryList( + PSINGLE_LIST_ENTRY ListHead, + PSINGLE_LIST_ENTRY Entry + ) +{ + Entry->Next = ListHead->Next; + ListHead->Next = Entry; +} + + +#endif diff --git a/src/IISLib/macros.h b/src/IISLib/macros.h new file mode 100644 index 0000000000..960f663a98 --- /dev/null +++ b/src/IISLib/macros.h @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _MACROS_H +#define _MACROS_H + +// +// The DIFF macro should be used around an expression involving pointer +// subtraction. The expression passed to DIFF is cast to a size_t type, +// allowing the result to be easily assigned to any 32-bit variable or +// passed to a function expecting a 32-bit argument. +// + +#define DIFF(x) ((size_t)(x)) + +// Change a hexadecimal digit to its numerical equivalent +#define TOHEX( ch ) \ + ((ch) > L'9' ? \ + (ch) >= L'a' ? \ + (ch) - L'a' + 10 : \ + (ch) - L'A' + 10 \ + : (ch) - L'0') + + +// Change a number to its Hexadecimal equivalent + +#define TODIGIT( nDigit ) \ + (CHAR)((nDigit) > 9 ? \ + (nDigit) - 10 + 'A' \ + : (nDigit) + '0') + + +inline int +SAFEIsSpace(UCHAR c) +{ + return isspace( c ); +} + +inline int +SAFEIsAlNum(UCHAR c) +{ + return isalnum( c ); +} + +inline int +SAFEIsAlpha(UCHAR c) +{ + return isalpha( c ); +} + +inline int +SAFEIsXDigit(UCHAR c) +{ + return isxdigit( c ); +} + +inline int +SAFEIsDigit(UCHAR c) +{ + return isdigit( c ); +} + +#endif // _MACROS_H diff --git a/src/IISLib/multisz.cpp b/src/IISLib/multisz.cpp new file mode 100644 index 0000000000..775ec4cd0c --- /dev/null +++ b/src/IISLib/multisz.cpp @@ -0,0 +1,474 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + + +#pragma warning (disable : 4267) + +#include "precomp.h" +#include "multisz.h" +#include + +// +// Private Definitions +// + +#define MAXULONG 4294967295 +#define ISWHITE( ch ) ((ch) == L' ' || (ch) == L'\t' || (ch) == L'\r') + +// +// When appending data, this is the extra amount we request to avoid +// reallocations +// +#define STR_SLOP 128 + + +DWORD +MULTISZ::CalcLength( const WCHAR * str, + LPDWORD pcStrings ) +{ + DWORD count = 0; + DWORD total = 1; + DWORD len; + + while( *str ) { + len = ::wcslen( str ) + 1; + total += len; + str += len; + count++; + } + + if( pcStrings != NULL ) { + *pcStrings = count; + } + + return total; + +} // MULTISZ::CalcLength + + +BOOL +MULTISZ::FindString( const WCHAR * str ) +{ + + WCHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !::wcscmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += ::wcslen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZ::FindString + + +BOOL +MULTISZ::FindStringNoCase( const WCHAR * str ) +{ + + WCHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !_wcsicmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += wcslen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZ::FindStringNoCase + + +VOID +MULTISZ::AuxInit( const WCHAR * pInit ) +{ + BOOL fRet; + + if ( pInit ) + { + DWORD cStrings; + int cbCopy = CalcLength( pInit, &cStrings ) * sizeof(WCHAR); + fRet = Resize( cbCopy ); + + if ( fRet ) { + CopyMemory( QueryPtr(), pInit, cbCopy ); + m_cchLen = (cbCopy)/sizeof(WCHAR); + m_cStrings = cStrings; + } else { +// BUFFER::SetValid( FALSE); + } + + } else { + + Reset(); + + } + +} // MULTISZ::AuxInit() + + +/******************************************************************* + + NAME: MULTISZ::AuxAppend + + SYNOPSIS: Appends the string onto the multisz. + + ENTRY: Object to append +********************************************************************/ + +BOOL MULTISZ::AuxAppend( const WCHAR * pStr, UINT cbStr, BOOL fAddSlop ) +{ + DBG_ASSERT( pStr != NULL ); + + UINT cbThis = QueryCB(); + + DBG_ASSERT( cbThis >= 2 ); + + if( cbThis == 4 ) { + + // + // It's empty, so start at the beginning. + // + + cbThis = 0; + + } else { + + // + // It's not empty, so back up over the final terminating NULL. + // + + cbThis -= sizeof(WCHAR); + + } + + // + // Only resize when we have to. When we do resize, we tack on + // some extra space to avoid extra reallocations. + // + // Note: QuerySize returns the requested size of the string buffer, + // *not* the strlen of the buffer + // + + //AcIncrement( CacMultiszAppend); + + // + // Check for the arithmetic overflow + // + // ( 2 * sizeof( WCHAR ) ) is for the double terminator + // + ULONGLONG cb64Required = (ULONGLONG)cbThis + cbStr + 2 * sizeof(WCHAR); + if ( cb64Required > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( QuerySize() < (DWORD) cb64Required ) + { + ULONGLONG cb64AllocSize = cb64Required + (fAddSlop ? STR_SLOP : 0 ); + // + // Check for the arithmetic overflow + // + if ( cb64AllocSize > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( !Resize( (DWORD) cb64AllocSize ) ) + return FALSE; + } + + // copy the exact string and tack on the double terminator + memcpy( (BYTE *) QueryPtr() + cbThis, + pStr, + cbStr); + *(WCHAR *)((BYTE *)QueryPtr() + cbThis + cbStr) = L'\0'; + *(WCHAR *)((BYTE *)QueryPtr() + cbThis + cbStr + sizeof(WCHAR) ) = L'\0'; + + m_cchLen = CalcLength( (const WCHAR *)QueryPtr(), &m_cStrings ); + return TRUE; + +} // MULTISZ::AuxAppend() + + +#if 0 + +BOOL +MULTISZ::CopyToBuffer( WCHAR * lpszBuffer, LPDWORD lpcch) const +/*++ + Description: + Copies the string into the WCHAR buffer passed in if the buffer + is sufficient to hold the translated string. + If the buffer is small, the function returns small and sets *lpcch + to contain the required number of characters. + + Arguments: + lpszBuffer pointer to WCHAR buffer which on return contains + the UNICODE version of string on success. + lpcch pointer to DWORD containing the length of the buffer. + If *lpcch == 0 then the function returns TRUE with + the count of characters required stored in *lpcch. + Also in this case lpszBuffer is not affected. + Returns: + TRUE on success. + FALSE on failure. Use GetLastError() for further details. +--*/ +{ + BOOL fReturn = TRUE; + + if ( lpcch == NULL) { + SetLastError( ERROR_INVALID_PARAMETER); + return ( FALSE); + } + + if ( *lpcch == 0) { + + // + // Inquiring the size of buffer alone + // + *lpcch = QueryCCH() + 1; // add one character for terminating null + } else { + + // + // Copy after conversion from ANSI to Unicode + // + int iRet; + iRet = MultiByteToWideChar( CP_ACP, + MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, + QueryStrA(), QueryCCH() + 1, + lpszBuffer, (int )*lpcch); + + if ( iRet == 0 || iRet != (int ) *lpcch) { + + // + // Error in conversion. + // + fReturn = FALSE; + } + } + + return ( fReturn); +} // MULTISZ::CopyToBuffer() +#endif + +BOOL +MULTISZ::CopyToBuffer( __out_ecount_opt(*lpcch) WCHAR * lpszBuffer, LPDWORD lpcch) const +/*++ + Description: + Copies the string into the WCHAR buffer passed in if the buffer + is sufficient to hold the translated string. + If the buffer is small, the function returns small and sets *lpcch + to contain the required number of characters. + + Arguments: + lpszBuffer pointer to WCHAR buffer which on return contains + the string on success. + lpcch pointer to DWORD containing the length of the buffer. + If *lpcch == 0 then the function returns TRUE with + the count of characters required stored in lpcch. + Also in this case lpszBuffer is not affected. + Returns: + TRUE on success. + FALSE on failure. Use GetLastError() for further details. +--*/ +{ + BOOL fReturn = TRUE; + + if ( lpcch == NULL) { + SetLastError( ERROR_INVALID_PARAMETER); + return ( FALSE); + } + + register DWORD cch = QueryCCH(); + + if ( *lpcch >= cch) { + + DBG_ASSERT( lpszBuffer); + memcpy( lpszBuffer, QueryStr(), cch * sizeof(WCHAR)); + } else { + DBG_ASSERT( *lpcch < cch); + SetLastError( ERROR_INSUFFICIENT_BUFFER); + fReturn = FALSE; + } + + *lpcch = cch; + + return ( fReturn); +} // MULTISZ::CopyToBuffer() + +BOOL +MULTISZ::Equals( + MULTISZ* pmszRhs +) +// +// Compares this to pmszRhs, returns TRUE if equal +// +{ + DBG_ASSERT( NULL != pmszRhs ); + + PCWSTR pszLhs = First( ); + PCWSTR pszRhs = pmszRhs->First( ); + + if( m_cStrings != pmszRhs->m_cStrings ) + { + return FALSE; + } + + while( NULL != pszLhs ) + { + DBG_ASSERT( NULL != pszRhs ); + + if( 0 != wcscmp( pszLhs, pszRhs ) ) + { + return FALSE; + } + + pszLhs = Next( pszLhs ); + pszRhs = pmszRhs->Next( pszRhs ); + } + + return TRUE; +} + +HRESULT +SplitCommaDelimitedString( + PCWSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZ * pmszList +) +/*++ + +Routine Description: + + Split comma delimited string into a multisz. Additional leading empty + entries after the first are discarded. + +Arguments: + + pszList - List to split up + fTrimEntries - Whether each entry should be trimmed before added to multisz + fRemoveEmptyEntries - Whether empty entires should be discarded + pmszList - Filled with MULTISZ list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + if ( pszList == NULL || + pmszList == NULL ) + { + DBG_ASSERT( FALSE ); + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + pmszList->Reset(); + + /* + pszCurrent: start of the current entry which may be the comma that + precedes the next entry if the entry is empty + + pszNext: the comma that precedes the next entry. If + pszCurrent == pszNext, then the entry is empty + + pszEnd: just past the end of the current entry + */ + + for ( PCWSTR pszCurrent = pszList, + pszNext = wcschr( pszCurrent, L',' ) + ; + ; + pszCurrent = pszNext + 1, + pszNext = wcschr( pszCurrent, L',' ) ) + { + PCWSTR pszEnd = NULL; + + if ( pszNext != NULL ) + { + pszEnd = pszNext; + } + else + { + pszEnd = pszCurrent + wcslen( pszCurrent ); + } + + if ( fTrimEntries ) + { + while ( pszCurrent < pszEnd && ISWHITE( pszCurrent[ 0 ] ) ) + { + pszCurrent++; + } + + while ( pszEnd > pszCurrent && ISWHITE( pszEnd[ -1 ] ) ) + { + pszEnd--; + } + } + + if ( pszCurrent != pszEnd || !fRemoveEmptyEntries ) + { + if ( !pmszList->Append( pszCurrent, (DWORD) ( pszEnd - pszCurrent ) ) ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + } + + if ( pszNext == NULL ) + { + break; + } + } + +Finished: + + return hr; +} + +#pragma warning(default:4267) \ No newline at end of file diff --git a/src/IISLib/multisz.h b/src/IISLib/multisz.h new file mode 100644 index 0000000000..f65c151d4f --- /dev/null +++ b/src/IISLib/multisz.h @@ -0,0 +1,225 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _MULTISZ_H_ +#define _MULTISZ_H_ + +#include "stringu.h" +#include "ntassert.h" + +/*++ + class MULTISZ: + + Intention: + A light-weight multi-string class supporting encapsulated string class. + + This object is derived from BUFFER class. + It maintains following state: + + m_fValid - whether this object is valid - + used only by MULTISZ() init functions + * NYI: I need to kill this someday * + m_cchLen - string length cached when we update the string. + m_cStrings - number of strings. + + Member Functions: + There are two categories of functions: + 1) Safe Functions - which do integrity checking of state + 2) UnSafe Functions - which do not do integrity checking, but + enable writing to the data stream freely. + (someday this will be enabled as Safe versions without + problem for users) + +--*/ +class MULTISZ : public BUFFER +{ +public: + + MULTISZ() + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { Reset(); } + + // creates a stack version of the MULTISZ object - uses passed in stack buffer + // MULTISZ does not free this pbInit on its own. + MULTISZ( __in_bcount(cbInit) WCHAR * pbInit, DWORD cbInit) + : BUFFER( (BYTE *) pbInit, cbInit), + m_cchLen (0), + m_cStrings(0) + {} + + MULTISZ( const WCHAR * pchInit ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit(pchInit); } + + MULTISZ( const MULTISZ & str ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit( str.QueryStr()); } + +// BOOL IsValid(VOID) const { return ( BUFFER::IsValid()) ; } + // + // Checks and returns TRUE if this string has no valid data else FALSE + // + BOOL IsEmpty( VOID) const { return ( *QueryStr() == L'\0'); } + + BOOL Append( const WCHAR * pchInit ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + (DWORD) (::wcslen(pchInit)) * sizeof(WCHAR) + )) : + TRUE); + } + + + BOOL Append( const WCHAR * pchInit, DWORD cchLen ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + cchLen * sizeof(WCHAR))) : + TRUE); + } + + BOOL Append( STRU & str ) + { return AuxAppend( str.QueryStr(), + (str.QueryCCH()) * sizeof(WCHAR)); } + + // Resets the internal string to be NULL string. Buffer remains cached. + VOID Reset( VOID) + { DBG_ASSERT( QueryPtr() != NULL); + QueryStr()[0] = L'\0'; + QueryStr()[1] = L'\0'; + m_cchLen = 2; + m_cStrings = 0; + } + + BOOL Copy( const WCHAR * pchInit, IN DWORD cbLen ) { + if ( QueryPtr() ) { Reset(); } + return ( (pchInit != NULL) ? + AuxAppend( pchInit, cbLen, FALSE ): + TRUE); + } + + BOOL Copy( const MULTISZ & str ) + { return ( Copy(str.QueryStr(), str.QueryCB())); } + + // + // Returns the number of bytes in the string including the terminating + // NULLs + // + UINT QueryCB( VOID ) const + { return ( m_cchLen * sizeof(WCHAR)); } + + // + // Returns # of characters in the string including the terminating NULLs + // + UINT QueryCCH( VOID ) const { return (m_cchLen); } + + // + // Returns # of strings in the multisz. + // + + DWORD QueryStringCount( VOID ) const { return m_cStrings; } + + // + // Makes a copy of the stored string in given buffer + // + BOOL CopyToBuffer( __out_ecount_opt(*lpcch) WCHAR * lpszBuffer, LPDWORD lpcch) const; + + // + // Return the string buffer + // + WCHAR * QueryStrA( VOID ) const { return ( QueryStr()); } + WCHAR * QueryStr( VOID ) const { return ((WCHAR *) QueryPtr()); } + + // + // Makes a clone of the current string in the string pointer passed in. + // + BOOL + Clone( OUT MULTISZ * pstrClone) const + { + return ((pstrClone == NULL) ? + (SetLastError(ERROR_INVALID_PARAMETER), FALSE) : + (pstrClone->Copy( *this)) + ); + } // MULTISZ::Clone() + + // + // Recalculates the length of *this because we've modified the buffers + // directly + // + + VOID RecalcLen( VOID ) + { m_cchLen = MULTISZ::CalcLength( QueryStr(), &m_cStrings ); } + + // + // Calculate total character length of a MULTI_SZ, including the + // terminating NULLs. + // + + static DWORD CalcLength( const WCHAR * str, + LPDWORD pcStrings = NULL ); + + // + // Determine if the MULTISZ contains a specific string. + // + + BOOL FindString( const WCHAR * str ); + + BOOL FindString( STRU & str ) + { return FindString( str.QueryStr() ); } + + // + // Determine if the MULTISZ contains a specific string - case-insensitive + // + + BOOL FindStringNoCase( const WCHAR * str ); + + BOOL FindStringNoCase( STRU & str ) + { return FindStringNoCase( str.QueryStr() ); } + + // + // Used for scanning a multisz. + // + + const WCHAR * First( VOID ) const + { return *QueryStr() == L'\0' ? NULL : QueryStr(); } + + const WCHAR * Next( const WCHAR * Current ) const + { Current += ::wcslen( Current ) + 1; + return *Current == L'\0' ? NULL : Current; } + + BOOL + Equals( + MULTISZ* pmszRhs + ); + +private: + + DWORD m_cchLen; + DWORD m_cStrings; + VOID AuxInit( const WCHAR * pInit ); + BOOL AuxAppend( const WCHAR * pInit, + UINT cbStr, BOOL fAddSlop = TRUE ); + +}; + +// +// Quick macro for declaring a MULTISZ that will use stack memory of +// bytes. If the buffer overflows then a heap buffer will be allocated +// + +#define STACK_MULTISZ( name, size ) WCHAR __ach##name[size]; \ + MULTISZ name( __ach##name, sizeof( __ach##name )) + +HRESULT +SplitCommaDelimitedString( + PCWSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZ * pmszList +); + +#endif // !_MULTISZ_HXX_ + diff --git a/src/IISLib/multisza.cpp b/src/IISLib/multisza.cpp new file mode 100644 index 0000000000..54717edf05 --- /dev/null +++ b/src/IISLib/multisza.cpp @@ -0,0 +1,408 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma warning (disable : 4267) +#include "precomp.h" +#include "multisza.h" +#include + +// +// Private Definitions +// + +#define MAXULONG 4294967295 +#define ISWHITE( ch ) ((ch) == L' ' || (ch) == L'\t' || (ch) == L'\r') + +// +// When appending data, this is the extra amount we request to avoid +// reallocations +// +#define STR_SLOP 128 + + +DWORD +MULTISZA::CalcLength( const CHAR * str, + LPDWORD pcStrings ) +{ + DWORD count = 0; + DWORD total = 1; + DWORD len; + + while( *str ) { + len = ::strlen( str ) + 1; + total += len; + str += len; + count++; + } + + if( pcStrings != NULL ) { + *pcStrings = count; + } + + return total; + +} // MULTISZA::CalcLength + + +BOOL +MULTISZA::FindString( const CHAR * str ) +{ + + CHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !::strcmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += ::strlen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZA::FindString + + +BOOL +MULTISZA::FindStringNoCase( const CHAR * str ) +{ + + CHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !_stricmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += strlen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZA::FindStringNoCase + + +VOID +MULTISZA::AuxInit( const CHAR * pInit ) +{ + BOOL fRet; + + if ( pInit ) + { + DWORD cStrings; + int cbCopy = CalcLength( pInit, &cStrings ) * sizeof(CHAR); + fRet = Resize( cbCopy ); + + if ( fRet ) { + CopyMemory( QueryPtr(), pInit, cbCopy ); + m_cchLen = (cbCopy)/sizeof(CHAR); + m_cStrings = cStrings; + } else { +// BUFFER::SetValid( FALSE); + } + + } else { + + Reset(); + + } + +} // MULTISZA::AuxInit() + + +/******************************************************************* + + NAME: MULTISZA::AuxAppend + + SYNOPSIS: Appends the string onto the MULTISZA. + + ENTRY: Object to append +********************************************************************/ + +BOOL MULTISZA::AuxAppend( const CHAR * pStr, UINT cbStr, BOOL fAddSlop ) +{ + DBG_ASSERT( pStr != NULL ); + + UINT cbThis = QueryCB(); + + if( cbThis == 2 ) { + + // + // It's empty, so start at the beginning. + // + + cbThis = 0; + + } else { + + // + // It's not empty, so back up over the final terminating NULL. + // + + cbThis -= sizeof(CHAR); + + } + + // + // Only resize when we have to. When we do resize, we tack on + // some extra space to avoid extra reallocations. + // + // Note: QuerySize returns the requested size of the string buffer, + // *not* the strlen of the buffer + // + + //AcIncrement( CacMultiszAppend); + + // + // Check for the arithmetic overflow + // + // ( 2 * sizeof( CHAR ) ) is for the double terminator + // + ULONGLONG cb64Required = (ULONGLONG)cbThis + cbStr + 2 * sizeof(CHAR); + if ( cb64Required > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( QuerySize() < (DWORD) cb64Required ) + { + ULONGLONG cb64AllocSize = cb64Required + (fAddSlop ? STR_SLOP : 0 ); + // + // Check for the arithmetic overflow + // + if ( cb64AllocSize > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( !Resize( (DWORD) cb64AllocSize ) ) + return FALSE; + } + + // copy the exact string and tack on the double terminator + memcpy( (BYTE *) QueryPtr() + cbThis, + pStr, + cbStr); + *(CHAR *)((BYTE *)QueryPtr() + cbThis + cbStr) = L'\0'; + *(CHAR *)((BYTE *)QueryPtr() + cbThis + cbStr + sizeof(CHAR) ) = L'\0'; + + m_cchLen = CalcLength( (const CHAR *)QueryPtr(), &m_cStrings ); + return TRUE; + +} // MULTISZA::AuxAppend() + +BOOL +MULTISZA::CopyToBuffer( __out_ecount_opt(*lpcch) CHAR * lpszBuffer, LPDWORD lpcch) const +/*++ + Description: + Copies the string into the CHAR buffer passed in if the buffer + is sufficient to hold the translated string. + If the buffer is small, the function returns small and sets *lpcch + to contain the required number of characters. + + Arguments: + lpszBuffer pointer to CHAR buffer which on return contains + the string on success. + lpcch pointer to DWORD containing the length of the buffer. + If *lpcch == 0 then the function returns TRUE with + the count of characters required stored in lpcch. + Also in this case lpszBuffer is not affected. + Returns: + TRUE on success. + FALSE on failure. Use GetLastError() for further details. +--*/ +{ + BOOL fReturn = TRUE; + + if ( lpcch == NULL) { + SetLastError( ERROR_INVALID_PARAMETER); + return ( FALSE); + } + + register DWORD cch = QueryCCH(); + + if ( *lpcch >= cch) { + + DBG_ASSERT( lpszBuffer); + memcpy( lpszBuffer, QueryStr(), cch * sizeof(CHAR)); + } else { + DBG_ASSERT( *lpcch < cch); + SetLastError( ERROR_INSUFFICIENT_BUFFER); + fReturn = FALSE; + } + + *lpcch = cch; + + return ( fReturn); +} // MULTISZA::CopyToBuffer() + +BOOL +MULTISZA::Equals( + MULTISZA* pmszRhs +) +// +// Compares this to pmszRhs, returns TRUE if equal +// +{ + DBG_ASSERT( NULL != pmszRhs ); + + PCSTR pszLhs = First( ); + PCSTR pszRhs = pmszRhs->First( ); + + if( m_cStrings != pmszRhs->m_cStrings ) + { + return FALSE; + } + + while( NULL != pszLhs ) + { + DBG_ASSERT( NULL != pszRhs ); + + if( 0 != strcmp( pszLhs, pszRhs ) ) + { + return FALSE; + } + + pszLhs = Next( pszLhs ); + pszRhs = pmszRhs->Next( pszRhs ); + } + + return TRUE; +} + +HRESULT +SplitCommaDelimitedString( + PCSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZA * pmszList +) +/*++ + +Routine Description: + + Split comma delimited string into a MULTISZA. Additional leading empty + entries after the first are discarded. + +Arguments: + + pszList - List to split up + fTrimEntries - Whether each entry should be trimmed before added to MULTISZA + fRemoveEmptyEntries - Whether empty entires should be discarded + pmszList - Filled with MULTISZA list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + if ( pszList == NULL || + pmszList == NULL ) + { + DBG_ASSERT( FALSE ); + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + pmszList->Reset(); + + /* + pszCurrent: start of the current entry which may be the comma that + precedes the next entry if the entry is empty + + pszNext: the comma that precedes the next entry. If + pszCurrent == pszNext, then the entry is empty + + pszEnd: just past the end of the current entry + */ + + for ( PCSTR pszCurrent = pszList, + pszNext = strchr( pszCurrent, L',' ) + ; + ; + pszCurrent = pszNext + 1, + pszNext = strchr( pszCurrent, L',' ) ) + { + PCSTR pszEnd = NULL; + + if ( pszNext != NULL ) + { + pszEnd = pszNext; + } + else + { + pszEnd = pszCurrent + strlen( pszCurrent ); + } + + if ( fTrimEntries ) + { + while ( pszCurrent < pszEnd && ISWHITE( pszCurrent[ 0 ] ) ) + { + pszCurrent++; + } + + while ( pszEnd > pszCurrent && ISWHITE( pszEnd[ -1 ] ) ) + { + pszEnd--; + } + } + + if ( pszCurrent != pszEnd || !fRemoveEmptyEntries ) + { + if ( !pmszList->Append( pszCurrent, (DWORD) ( pszEnd - pszCurrent ) ) ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + } + + if ( pszNext == NULL ) + { + break; + } + } + +Finished: + + return hr; +} +#pragma warning(default:4267) \ No newline at end of file diff --git a/src/IISLib/multisza.h b/src/IISLib/multisza.h new file mode 100644 index 0000000000..d575ec9423 --- /dev/null +++ b/src/IISLib/multisza.h @@ -0,0 +1,226 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _MULTISZA_H_ +#define _MULTISZA_H_ + +#include +#include "stringa.h" + + +/*++ + class MULTISZ: + + Intention: + A light-weight multi-string class supporting encapsulated string class. + + This object is derived from BUFFER class. + It maintains following state: + + m_fValid - whether this object is valid - + used only by MULTISZ() init functions + * NYI: I need to kill this someday * + m_cchLen - string length cached when we update the string. + m_cStrings - number of strings. + + Member Functions: + There are two categories of functions: + 1) Safe Functions - which do integrity checking of state + 2) UnSafe Functions - which do not do integrity checking, but + enable writing to the data stream freely. + (someday this will be enabled as Safe versions without + problem for users) + +--*/ +class MULTISZA : public BUFFER +{ +public: + + MULTISZA() + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { Reset(); } + + // creates a stack version of the MULTISZA object - uses passed in stack buffer + // MULTISZA does not free this pbInit on its own. + MULTISZA( __in_bcount(cbInit) CHAR * pbInit, DWORD cbInit) + : BUFFER( (BYTE *) pbInit, cbInit), + m_cchLen (0), + m_cStrings(0) + {} + + MULTISZA( const CHAR * pchInit ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit(pchInit); } + + MULTISZA( const MULTISZA & str ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit( str.QueryStr()); } + +// BOOL IsValid(VOID) const { return ( BUFFER::IsValid()) ; } + // + // Checks and returns TRUE if this string has no valid data else FALSE + // + BOOL IsEmpty( VOID) const { return ( *QueryStr() == L'\0'); } + + BOOL Append( const CHAR * pchInit ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + (DWORD) (::strlen(pchInit)) * sizeof(CHAR) + )) : + TRUE); + } + + + BOOL Append( const CHAR * pchInit, DWORD cchLen ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + cchLen * sizeof(CHAR))) : + TRUE); + } + + BOOL Append( STRA & str ) + { return AuxAppend( str.QueryStr(), + (str.QueryCCH()) * sizeof(CHAR)); } + + // Resets the internal string to be NULL string. Buffer remains cached. + VOID Reset( VOID) + { DBG_ASSERT( QueryPtr() != NULL); + QueryStr()[0] = L'\0'; + QueryStr()[1] = L'\0'; + m_cchLen = 2; + m_cStrings = 0; + } + + BOOL Copy( const CHAR * pchInit, IN DWORD cbLen ) { + if ( QueryPtr() ) { Reset(); } + return ( (pchInit != NULL) ? + AuxAppend( pchInit, cbLen, FALSE ): + TRUE); + } + + BOOL Copy( const MULTISZA & str ) + { return ( Copy(str.QueryStr(), str.QueryCB())); } + + // + // Returns the number of bytes in the string including the terminating + // NULLs + // + UINT QueryCB( VOID ) const + { return ( m_cchLen * sizeof(CHAR)); } + + // + // Returns # of characters in the string including the terminating NULLs + // + UINT QueryCCH( VOID ) const { return (m_cchLen); } + + // + // Returns # of strings in the MULTISZA. + // + + DWORD QueryStringCount( VOID ) const { return m_cStrings; } + + // + // Makes a copy of the stored string in given buffer + // + BOOL CopyToBuffer( __out_ecount_opt(*lpcch) CHAR * lpszBuffer, LPDWORD lpcch) const; + + // + // Return the string buffer + // + CHAR * QueryStrA( VOID ) const { return ( QueryStr()); } + CHAR * QueryStr( VOID ) const { return ((CHAR *) QueryPtr()); } + + // + // Makes a clone of the current string in the string pointer passed in. + // + BOOL + Clone( OUT MULTISZA * pstrClone) const + { + return ((pstrClone == NULL) ? + (SetLastError(ERROR_INVALID_PARAMETER), FALSE) : + (pstrClone->Copy( *this)) + ); + } // MULTISZA::Clone() + + // + // Recalculates the length of *this because we've modified the buffers + // directly + // + + VOID RecalcLen( VOID ) + { m_cchLen = MULTISZA::CalcLength( QueryStr(), &m_cStrings ); } + + // + // Calculate total character length of a MULTI_SZ, including the + // terminating NULLs. + // + + static DWORD CalcLength( const CHAR * str, + LPDWORD pcStrings = NULL ); + + // + // Determine if the MULTISZA contains a specific string. + // + + BOOL FindString( const CHAR * str ); + + BOOL FindString( STRA & str ) + { return FindString( str.QueryStr() ); } + + // + // Determine if the MULTISZA contains a specific string - case-insensitive + // + + BOOL FindStringNoCase( const CHAR * str ); + + BOOL FindStringNoCase( STRA & str ) + { return FindStringNoCase( str.QueryStr() ); } + + // + // Used for scanning a MULTISZA. + // + + const CHAR * First( VOID ) const + { return *QueryStr() == L'\0' ? NULL : QueryStr(); } + + const CHAR * Next( const CHAR * Current ) const + { Current += ::strlen( Current ) + 1; + return *Current == L'\0' ? NULL : Current; } + + BOOL + Equals( + MULTISZA* pmszRhs + ); + +private: + + DWORD m_cchLen; + DWORD m_cStrings; + VOID AuxInit( const CHAR * pInit ); + BOOL AuxAppend( const CHAR * pInit, + UINT cbStr, BOOL fAddSlop = TRUE ); + +}; + +// +// Quick macro for declaring a MULTISZA that will use stack memory of +// bytes. If the buffer overflows then a heap buffer will be allocated +// + +#define STACK_MULTISZA( name, size ) CHAR __ach##name[size]; \ + MULTISZA name( __ach##name, sizeof( __ach##name )) + +HRESULT +SplitCommaDelimitedString( + PCSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZA * pmszList +); + +#endif // !_MULTISZA_HXX_ + diff --git a/src/IISLib/ntassert.h b/src/IISLib/ntassert.h new file mode 100644 index 0000000000..6d2f3b9a30 --- /dev/null +++ b/src/IISLib/ntassert.h @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#ifdef _ASSERTE + #undef _ASSERTE +#endif + +#ifdef ASSERT + #undef ASSERT +#endif + +#if defined( DBG ) && DBG + #define SX_ASSERT( _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L#_x ), DbgRaiseAssertionFailure(), FALSE ) ) ) + #define SX_ASSERTMSG( _m, _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L##_m ), DbgRaiseAssertionFailure(), FALSE ) ) ) + #define SX_VERIFY( _x ) SX_ASSERT( _x ) + #define _ASSERTE( _x ) SX_ASSERT( _x ) + #define ASSERT( _x ) SX_ASSERT( _x ) + #define assert( _x ) SX_ASSERT( _x ) + #define DBG_ASSERT( _x ) SX_ASSERT( _x ) + #define DBG_REQUIRE( _x ) SX_ASSERT( _x ) +#else + #define SX_ASSERT( _x ) ( (VOID)0 ) + #define SX_ASSERTMSG( _m, _x ) ( (VOID)0 ) + #define SX_VERIFY( _x ) ( (VOID)( ( _x ) ? TRUE : FALSE ) ) + #define _ASSERTE( _x ) ( (VOID)0 ) + #define assert( _x ) ( (VOID)0 ) + #define DBG_ASSERT( _x ) ( (VOID)0 ) + #define DBG_REQUIRE( _x ) ((VOID)(_x)) +#endif + diff --git a/src/IISLib/percpu.h b/src/IISLib/percpu.h new file mode 100644 index 0000000000..5d3c563935 --- /dev/null +++ b/src/IISLib/percpu.h @@ -0,0 +1,305 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +template +class PER_CPU +{ +public: + + template + inline + static + HRESULT + Create( + FunctionInitializer Initializer, + __deref_out PER_CPU ** ppInstance + ); + + inline + T * + GetLocal( + VOID + ); + + template + inline + VOID + ForEach( + FunctionForEach Function + ); + + inline + VOID + Dispose( + VOID + ); + +private: + + PER_CPU( + VOID + ) + { + // + // Don't perform any operation during constructor. + // Constructor will never be called. + // + } + + ~PER_CPU( + VOID + ) + { + // + // Don't perform any operation during destructor. + // Constructor will never be called. + // + } + + template + HRESULT + Initialize( + FunctionInitializer Initializer, + DWORD NumberOfVariables, + DWORD Alignment + ); + + T * + GetObject( + DWORD Index + ); + + static + HRESULT + GetProcessorInformation( + __out DWORD * pCacheLineSize, + __out DWORD * pNumberOfProcessors + ); + + // + // Pointer to the begining of the inlined array. + // + PVOID m_pVariables; + SIZE_T m_Alignment; + SIZE_T m_VariablesCount; +}; + +template +template +inline +// static +HRESULT +PER_CPU::Create( + FunctionInitializer Initializer, + __deref_out PER_CPU ** ppInstance +) +{ + HRESULT hr = S_OK; + DWORD CacheLineSize = 0; + DWORD ObjectCacheLineSize = 0; + DWORD NumberOfProcessors = 0; + PER_CPU * pInstance = NULL; + + hr = GetProcessorInformation(&CacheLineSize, + &NumberOfProcessors); + if (FAILED(hr)) + { + goto Finished; + } + + if (sizeof(T) > CacheLineSize) + { + // + // Round to the next multiple of the cache line size. + // + ObjectCacheLineSize = (sizeof(T) + CacheLineSize-1) & (CacheLineSize-1); + } + else + { + ObjectCacheLineSize = CacheLineSize; + } + + // + // Calculate the size of the PER_CPU object, including the array. + // The first cache line is for the member variables and the array + // starts in the next cache line. + // + SIZE_T Size = CacheLineSize + NumberOfProcessors * ObjectCacheLineSize; + + pInstance = (PER_CPU*) _aligned_malloc(Size, CacheLineSize); + if (pInstance == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + ZeroMemory(pInstance, Size); + + // + // The array start in the 2nd cache line. + // + pInstance->m_pVariables = reinterpret_cast(pInstance) + CacheLineSize; + + // + // Pass a disposer for disposing initialized items in case of failure. + // + hr = pInstance->Initialize(Initializer, + NumberOfProcessors, + ObjectCacheLineSize); + if (FAILED(hr)) + { + goto Finished; + } + + *ppInstance = pInstance; + pInstance = NULL; + +Finished: + + if (pInstance != NULL) + { + // + // Free the instance without disposing it. + // + pInstance->Dispose(); + pInstance = NULL; + } + + return hr; +} + +template +inline +T * +PER_CPU::GetLocal( + VOID +) +{ + // Use GetCurrentProcessorNumber (up to 64 logical processors) instead of + // GetCurrentProcessorNumberEx (more than 64 logical processors) because + // the number of processors are not densely packed per group. + // The idea of distributing variables per CPU is to have + // a scalability multiplier (could be NUMA node instead). + // + // Make sure the index don't go beyond the array size, if that happens, + // there won't be even distribution, but still better + // than one single variable. + // + return GetObject(GetCurrentProcessorNumber()); +} + +template +inline +T * +PER_CPU::GetObject( + DWORD Index +) +{ + return reinterpret_cast(static_cast(m_pVariables) + Index * m_Alignment); +} + +template +template +inline +VOID +PER_CPU::ForEach( + FunctionForEach Function +) +{ + for(DWORD Index = 0; Index < m_VariablesCount; ++Index) + { + T * pObject = GetObject(Index); + Function(pObject); + } +} + +template +VOID +PER_CPU::Dispose( + VOID +) +{ + _aligned_free(this); +} + +template +template +inline +HRESULT +PER_CPU::Initialize( + FunctionInitializer Initializer, + DWORD NumberOfVariables, + DWORD Alignment +) +/*++ + +Routine Description: + + Initialize each object using the initializer function. + If initialization for any object fails, it dispose the + objects that were successfully initialized. + +Arguments: + + Initializer - Function for initialize one object. + Signature: HRESULT Func(T*) + Dispose - Function for disposing initialized objects in case of failure. + Signature: void Func(T*) + NumberOfVariables - The length of the array of variables. + Alignment - Alignment to use for avoiding false sharing. + +Return: + + HRESULT - E_OUTOFMEMORY + +--*/ +{ + HRESULT hr = S_OK; + DWORD Index = 0; + + m_VariablesCount = NumberOfVariables; + m_Alignment = Alignment; + + for (; Index < m_VariablesCount; ++Index) + { + T * pObject = GetObject(Index); + Initializer(pObject); + } + + return hr; +} + +template +// static +HRESULT +PER_CPU::GetProcessorInformation( + __out DWORD * pCacheLineSize, + __out DWORD * pNumberOfProcessors +) +/*++ + +Routine Description: + + Gets the CPU cache-line size for the current system. + This information is used for avoiding CPU false sharing. + +Arguments: + + pCacheLineSize - The processor cache-line size. + pNumberOfProcessors - Maximum number of processors per group. + +Return: + + HRESULT - E_OUTOFMEMORY + +--*/ +{ + SYSTEM_INFO SystemInfo = { }; + + GetSystemInfo(&SystemInfo); + *pNumberOfProcessors = SystemInfo.dwNumberOfProcessors; + *pCacheLineSize = SYSTEM_CACHE_ALIGNMENT_SIZE; + + return S_OK; +} \ No newline at end of file diff --git a/src/IISLib/precomp.h b/src/IISLib/precomp.h new file mode 100644 index 0000000000..9cccea4045 --- /dev/null +++ b/src/IISLib/precomp.h @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include +#pragma warning( disable:4127 ) +#include +#include +#include +#include +#include +#include + +#include "macros.h" +#include "stringu.h" +#include "stringa.h" +#include "dbgutil.h" +#include "ntassert.h" +#include "ahutil.h" +#include "acache.h" +//#include "base64.hxx" + diff --git a/src/IISLib/prime.h b/src/IISLib/prime.h new file mode 100644 index 0000000000..6a6a88ed78 --- /dev/null +++ b/src/IISLib/prime.h @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include + +// +// Pre-calculated prime numbers (up to 10,049,369). +// +extern __declspec(selectany) const DWORD g_Primes [] = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, + 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, + 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, + 130363, 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, + 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, + 5999471, 7199369, 7849369, 8649369, 9249369, 10049369 +}; + +class PRIME +{ +public: + + static + DWORD + GetPrime( + DWORD dwMinimum + ) + { + // + // Try to use the precalculated numbers. + // + for ( DWORD Index = 0; Index < _countof( g_Primes ); Index++ ) + { + DWORD dwCandidate = g_Primes[Index]; + if ( dwCandidate >= dwMinimum ) + { + return dwCandidate; + } + } + + // + // Do calculation. + // + for ( DWORD dwCandidate = dwMinimum | 1; + dwCandidate < MAXDWORD; + dwCandidate += 2 ) + { + if ( IsPrime( dwCandidate ) ) + { + return dwCandidate; + } + } + return dwMinimum; + } + +private: + + static + BOOL + IsPrime( + DWORD dwCandidate + ) + { + if ((dwCandidate & 1) == 0) + { + return ( dwCandidate == 2 ); + } + + DWORD dwMax = static_cast(sqrt(static_cast(dwCandidate))); + + for ( DWORD Index = 3; Index <= dwMax; Index += 2 ) + { + if ( (dwCandidate % Index) == 0 ) + { + return FALSE; + } + } + return TRUE; + } + + PRIME() {} + ~PRIME() {} +}; \ No newline at end of file diff --git a/src/IISLib/pudebug.h b/src/IISLib/pudebug.h new file mode 100644 index 0000000000..7b0e35da0f --- /dev/null +++ b/src/IISLib/pudebug.h @@ -0,0 +1,736 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +# ifndef _PUDEBUG_H_ +# define _PUDEBUG_H_ + +#ifndef _NO_TRACING_ +# define _NO_TRACING_ +#endif // _NO_TRACING_ + +/************************************************************ + * Include Headers + ************************************************************/ + +# ifdef __cplusplus +extern "C" { +# endif // __cplusplus + +# include + +# ifndef dllexp +# define dllexp __declspec( dllexport) +# endif // dllexp + +#include + +#ifndef IN_OUT +#define IN_OUT __inout +#endif + +/*********************************************************** + * Macros + ************************************************************/ + +enum PRINT_REASONS { + PrintNone = 0x0, // Nothing to be printed + PrintError = 0x1, // An error message + PrintWarning = 0x2, // A warning message + PrintLog = 0x3, // Just logging. Indicates a trace of where ... + PrintMsg = 0x4, // Echo input message + PrintCritical = 0x5, // Print and Exit + PrintAssertion= 0x6 // Printing for an assertion failure + }; + + +enum DEBUG_OUTPUT_FLAGS { + DbgOutputNone = 0x0, // None + DbgOutputKdb = 0x1, // Output to Kernel Debugger + DbgOutputLogFile = 0x2, // Output to LogFile + DbgOutputTruncate = 0x4, // Truncate Log File if necessary + DbgOutputStderr = 0x8, // Send output to std error + DbgOutputBackup = 0x10, // Make backup of debug file ? + DbgOutputMemory = 0x20, // Dump to memory buffer + DbgOutputAll = 0xFFFFFFFF // All the bits set. + }; + + +# define MAX_LABEL_LENGTH ( 100) + + +// The following flags are used internally to track what level of tracing we +// are currently using. Bitmapped for extensibility. +#define DEBUG_FLAG_ODS 0x00000001 +//#define DEBUG_FLAG_INFO 0x00000002 +//#define DEBUG_FLAG_WARN 0x00000004 +//#define DEBUG_FLAG_ERROR 0x00000008 +// The following are used internally to determine whether to log or not based +// on what the current state is +//#define DEBUG_FLAGS_INFO (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO) +//#define DEBUG_FLAGS_WARN (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO | DEBUG_FLAG_WARN) +//#define DEBUG_FLAGS_ERROR (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) + +#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) + +// +// user of DEBUG infrastructure may choose unique variable name for DEBUG_FLAGS +// that's specially useful for cases where DEBUG infrastructure is used within +// static library (static library may prefer to maintain it's own DebugFlags independent +// on the main program it links to +// +#ifndef DEBUG_FLAGS_VAR +#define DEBUG_FLAGS_VAR g_dwDebugFlags +#endif + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus + DWORD DEBUG_FLAGS_VAR ; // Debugging Flags + +# define DECLARE_DEBUG_VARIABLE() + +# define SET_DEBUG_FLAGS( dwFlags) DEBUG_FLAGS_VAR = dwFlags +# define GET_DEBUG_FLAGS() ( DEBUG_FLAGS_VAR ) + +# define LOAD_DEBUG_FLAGS_FROM_REG(hkey, dwDefault) \ + DEBUG_FLAGS_VAR = PuLoadDebugFlagsFromReg((hkey), (dwDefault)) + +# define LOAD_DEBUG_FLAGS_FROM_REG_STR(pszRegKey, dwDefault) \ + DEBUG_FLAGS_VAR = PuLoadDebugFlagsFromRegStr((pszRegKey), (dwDefault)) + +# define SAVE_DEBUG_FLAGS_IN_REG(hkey, dwDbg) \ + PuSaveDebugFlagsInReg((hkey), (dwDbg)) + +# define DEBUG_IF( arg, s) if ( DEBUG_ ## arg & GET_DEBUG_FLAGS()) { \ + s \ + } else {} + +# define IF_DEBUG( arg) if ( DEBUG_## arg & GET_DEBUG_FLAGS()) + + +/*++ + class DEBUG_PRINTS + + This class is responsible for printing messages to log file / kernel debugger + + Currently the class supports only member functions for char. + ( not unicode-strings). + +--*/ + + +typedef struct _DEBUG_PRINTS { + + CHAR m_rgchLabel[MAX_LABEL_LENGTH]; + CHAR m_rgchLogFilePath[MAX_PATH]; + CHAR m_rgchLogFileName[MAX_PATH]; + HANDLE m_LogFileHandle; + HANDLE m_StdErrHandle; + BOOL m_fInitialized; + BOOL m_fBreakOnAssert; + DWORD m_dwOutputFlags; + VOID *m_pMemoryLog; +} DEBUG_PRINTS, FAR * LPDEBUG_PRINTS; + + +LPDEBUG_PRINTS +PuCreateDebugPrintsObject( + IN const char * pszPrintLabel, + IN DWORD dwOutputFlags); + +// +// frees the debug prints object and closes any file if necessary. +// Returns NULL on success or returns pDebugPrints on failure. +// +LPDEBUG_PRINTS +PuDeleteDebugPrintsObject( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + + +VOID +PuDbgPrint( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszFormat, + ...); + // arglist +VOID +PuDbgPrintW( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const WCHAR * pszFormat, + ...); // arglist + +// PuDbgPrintError is similar to PuDbgPrint() but allows +// one to print error code in friendly manner +VOID +PuDbgPrintError( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN DWORD dwError, + IN const char * pszFormat, + ...); // arglist + +/*++ + PuDbgDump() does not do any formatting of output. + It just dumps the given message onto the debug destinations. +--*/ +VOID +PuDbgDump( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszDump + ); + +// +// PuDbgAssertFailed() *must* be __cdecl to properly capture the +// thread context at the time of the failure. +// + +INT +__cdecl +PuDbgAssertFailed( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszExpression, + IN const char * pszMessage); + +INT +WINAPI +PuDbgPrintAssertFailed( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszExpression, + IN const char * pszMessage); + +VOID +PuDbgCaptureContext ( + OUT PCONTEXT ContextRecord + ); + +VOID +PuDbgPrintCurrentTime( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName + ); + +VOID +PuSetDbgOutputFlags( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN DWORD dwFlags); + +DWORD +PuGetDbgOutputFlags( + IN const LPDEBUG_PRINTS pDebugPrints); + + +// +// Following functions return Win32 error codes. +// NO_ERROR if success +// + +DWORD +PuOpenDbgPrintFile( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFileName, + IN const char * pszPathForFile); + +DWORD +PuReOpenDbgPrintFile( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuCloseDbgPrintFile( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuOpenDbgMemoryLog( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuCloseDbgMemoryLog( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuLoadDebugFlagsFromReg(IN HKEY hkey, IN DWORD dwDefault); + +DWORD +PuLoadDebugFlagsFromRegStr(IN LPCSTR pszRegKey, IN DWORD dwDefault); + +DWORD +PuSaveDebugFlagsInReg(IN HKEY hkey, IN DWORD dwDbg); + + +# define PuPrintToKdb( pszOutput) \ + if ( pszOutput != NULL) { \ + OutputDebugString( pszOutput); \ + } else {} + + + +# ifdef __cplusplus +}; +# endif // __cplusplus + +// begin_user_unmodifiable + + + +/*********************************************************** + * Macros + ************************************************************/ + + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus +DEBUG_PRINTS * g_pDebug; // define a global debug variable + +# if DBG + +// For the CHK build we want ODS enabled. For an explanation of these flags see +// the comment just after the definition of DBG_CONTEXT +# define DECLARE_DEBUG_PRINTS_OBJECT() \ + DEBUG_PRINTS * g_pDebug = NULL; \ + DWORD DEBUG_FLAGS_VAR = DEBUG_FLAG_ERROR; + +#else // !DBG + +# define DECLARE_DEBUG_PRINTS_OBJECT() \ + DEBUG_PRINTS * g_pDebug = NULL; \ + DWORD DEBUG_FLAGS_VAR = 0; + +#endif // !DBG + + +// +// Call the following macro as part of your initialization for program +// planning to use the debugging class. +// +/** DEBUGDEBUG +# define CREATE_DEBUG_PRINT_OBJECT( pszLabel) \ + g_pDebug = PuCreateDebugPrintsObject( pszLabel, DEFAULT_OUTPUT_FLAGS);\ + if ( g_pDebug == NULL) { \ + OutputDebugStringA( "Unable to Create Debug Print Object \n"); \ + } +*/ + +// +// Call the following macro once as part of the termination of program +// which uses the debugging class. +// +# define DELETE_DEBUG_PRINT_OBJECT( ) \ + g_pDebug = PuDeleteDebugPrintsObject( g_pDebug); + + +# define VALID_DEBUG_PRINT_OBJECT() \ + ( ( g_pDebug != NULL) && g_pDebug->m_fInitialized) + + +// +// Use the DBG_CONTEXT without any surrounding braces. +// This is used to pass the values for global DebugPrintObject +// and File/Line information +// +//# define DBG_CONTEXT g_pDebug, __FILE__, __LINE__, __FUNCTION__ + +// The 3 main tracing macros, each one corresponds to a different level of +// tracing + +// The 3 main tracing macros, each one corresponds to a different level of +// tracing +//# define DBGINFO(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_INFO) { PuDbgPrint args; }} +//# define DBGWARN(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_WARN) { PuDbgPrint args; }} +//# define DBGERROR(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrint args; }} + +# define DBGINFOW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_INFO) { PuDbgPrintW args; }} +# define DBGWARNW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_WARN) { PuDbgPrintW args; }} +# define DBGERRORW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrintW args; }} + + +// +// DBGPRINTF() is printing function ( much like printf) but always called +// with the DBG_CONTEXT as follows +// DBGPRINTF( ( DBG_CONTEXT, format-string, arguments for format list)); +// +# define DBGPRINTF DBGINFO + +// +// DPERROR() is printing function ( much like printf) but always called +// with the DBG_CONTEXT as follows +// DPERROR( ( DBG_CONTEXT, error, format-string, +// arguments for format list)); +// +# define DPERROR( args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrintError args; }} + +# if DBG + +# define DBG_CODE(s) s /* echoes code in debugging mode */ + +// The same 3 main tracing macros however in this case the macros are only compiled +// into the CHK build. This is necessary because some tracing info used functions or +// variables which are not compiled into the FRE build. +# define CHKINFO(args) { PuDbgPrint args; } +# define CHKWARN(args) { PuDbgPrint args; } +# define CHKERROR(args) { PuDbgPrint args; } + +# define CHKINFOW(args) { PuDbgPrintW args; } +# define CHKWARNW(args) { PuDbgPrintW args; } +# define CHKERRORW(args) { PuDbgPrintW args; } + + +#ifndef DBG_ASSERT +# ifdef _PREFAST_ +# define DBG_ASSERT(exp) ((void)0) /* Do Nothing */ +# define DBG_ASSERT_MSG(exp, pszMsg) ((void)0) /* Do Nothing */ +# define DBG_REQUIRE( exp) ((void) (exp)) +# else // !_PREFAST_ +# define DBG_ASSERT( exp ) \ + ( (VOID)( ( exp ) || ( DebugBreak(), \ + PuDbgPrintAssertFailed( DBG_CONTEXT, #exp, "" ) ) ) ) + +# define DBG_ASSERT_MSG( exp, pszMsg) \ + ( (VOID)( ( exp ) || ( DebugBreak(), \ + PuDbgPrintAssertFailed( DBG_CONTEXT, #exp, pszMsg ) ) ) ) + +# define DBG_REQUIRE( exp ) \ + DBG_ASSERT( exp ) +# endif // !_PREFAST_ +#endif + + +# define DBG_LOG() PuDbgPrint( DBG_CONTEXT, "\n" ) + +# define DBG_OPEN_LOG_FILE( pszFile, pszPath ) \ + PuOpenDbgPrintFile( g_pDebug, (pszFile), (pszPath) ) + +# define DBG_CLOSE_LOG_FILE( ) \ + PuCloseDbgPrintFile( g_pDebug ) + +# define DBG_OPEN_MEMORY_LOG( ) \ + PuOpenDbgMemoryLog( g_pDebug ) + + +# define DBGDUMP( args ) PuDbgDump args + +# define DBGPRINT_CURRENT_TIME() PuDbgPrintCurrentTime( DBG_CONTEXT ) + +# else // !DBG + +# define DBG_CODE(s) ((void)0) /* Do Nothing */ + +# define CHKINFO(args) ((void)0) /* Do Nothing */ +# define CHKWARN(args) ((void)0) /* Do Nothing */ +# define CHKERROR(args) ((void)0) /* Do Nothing */ + +# define CHKINFOW(args) ((void)0) /* Do Nothing */ +# define CHKWARNW(args) ((void)0) /* Do Nothing */ +# define CHKERRORW(args) ((void)0) /* Do Nothing */ + +#ifndef DBG_ASSERT +# define DBG_ASSERT(exp) ((void)0) /* Do Nothing */ + +# define DBG_ASSERT_MSG(exp, pszMsg) ((void)0) /* Do Nothing */ + +# define DBG_REQUIRE( exp) ((void) (exp)) +#endif // !DBG_ASSERT + +# define DBGDUMP( args) ((void)0) /* Do nothing */ + +# define DBG_LOG() ((void)0) /* Do Nothing */ + +# define DBG_OPEN_LOG_FILE( pszFile, pszPath) ((void)0) /* Do Nothing */ + +# define DBG_OPEN_MEMORY_LOG() ((void)0) /* Do Nothing */ + +# define DBG_CLOSE_LOG_FILE() ((void)0) /* Do Nothing */ + +# define DBGPRINT_CURRENT_TIME() ((void)0) /* Do Nothing */ + +# endif // !DBG + + +// end_user_unmodifiable + +// begin_user_unmodifiable + + +#ifdef ASSERT +# undef ASSERT +#endif + + +# define ASSERT( exp) DBG_ASSERT( exp) + + +// end_user_unmodifiable + +// begin_user_modifiable + +// +// Debugging constants consist of two pieces. +// All constants in the range 0x0 to 0x8000 are reserved +// User extensions may include additional constants (bit flags) +// + +# define DEBUG_API_ENTRY 0x00000001L +# define DEBUG_API_EXIT 0x00000002L +# define DEBUG_INIT_CLEAN 0x00000004L +# define DEBUG_ERROR 0x00000008L + + // End of Reserved Range +# define DEBUG_RESERVED 0x00000FFFL + +// end_user_modifiable + + + +/*********************************************************** + * Platform Type related variables and macros + ************************************************************/ + +// +// Enum for product types +// + +typedef enum _PLATFORM_TYPE { + + PtInvalid = 0, // Invalid + PtNtWorkstation = 1, // NT Workstation + PtNtServer = 2, // NT Server + +} PLATFORM_TYPE; + +// +// IISGetPlatformType is the function used to the platform type +// + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus +PLATFORM_TYPE +IISGetPlatformType( + VOID + ); + +// +// External Macros +// + +#define InetIsNtServer( _pt ) ((_pt) == PtNtServer) +#define InetIsNtWksta( _pt ) ((_pt) == PtNtWorkstation) +#define InetIsValidPT(_pt) ((_pt) != PtInvalid) + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus +PLATFORM_TYPE g_PlatformType; + + +// Use the DECLARE_PLATFORM_TYPE macro to declare the platform type +#define DECLARE_PLATFORM_TYPE() \ + PLATFORM_TYPE g_PlatformType = PtInvalid; + +// Use the INITIALIZE_PLATFORM_TYPE to init the platform type +// This should typically go inside the DLLInit or equivalent place. +#define INITIALIZE_PLATFORM_TYPE() \ + g_PlatformType = IISGetPlatformType(); + +// +// Additional Macros to use the Platform Type +// + +#define TsIsNtServer( ) InetIsNtServer(g_PlatformType) +#define TsIsNtWksta( ) InetIsNtWksta(g_PlatformType) +#define IISIsValidPlatform() InetIsValidPT(g_PlatformType) +#define IISPlatformType() (g_PlatformType) + + +/*********************************************************** + * Some utility functions for Critical Sections + ************************************************************/ + +// +// IISSetCriticalSectionSpinCount() provides a thunk for the +// original NT4.0sp3 API SetCriticalSectionSpinCount() for CS with Spin counts +// Users of this function should definitely dynlink with kernel32.dll, +// Otherwise errors will surface to a large extent +// +extern +# ifdef __cplusplus +"C" +# endif // _cplusplus +DWORD +IISSetCriticalSectionSpinCount( + LPCRITICAL_SECTION lpCriticalSection, + DWORD dwSpinCount +); + + +// +// Macro for the calls to SetCriticalSectionSpinCount() +// +# define SET_CRITICAL_SECTION_SPIN_COUNT( lpCS, dwSpins) \ + IISSetCriticalSectionSpinCount( (lpCS), (dwSpins)) + +// +// IIS_DEFAULT_CS_SPIN_COUNT is the default value of spins used by +// Critical sections defined within IIS. +// NYI: We should have to switch the individual values based on experiments! +// Current value is an arbitrary choice +// +# define IIS_DEFAULT_CS_SPIN_COUNT (1000) + +// +// Initializes a critical section and sets its spin count +// to IIS_DEFAULT_CS_SPIN_COUNT. Equivalent to +// InitializeCriticalSectionAndSpinCount(lpCS, IIS_DEFAULT_CS_SPIN_COUNT), +// but provides a safe thunking layer for older systems that don't provide +// this API. +// +extern +# ifdef __cplusplus +"C" +# endif // _cplusplus +BOOL +IISInitializeCriticalSection( + LPCRITICAL_SECTION lpCriticalSection +); + +// +// Macro for the calls to InitializeCriticalSection() +// +# define INITIALIZE_CRITICAL_SECTION(lpCS) IISInitializeCriticalSection(lpCS) + +# endif /* _DEBUG_HXX_ */ + +// +// The following macros allow the automatic naming of certain Win32 objects. +// See IIS\SVCS\IISRTL\WIN32OBJ.C for details on the naming convention. +// +// Set IIS_NAMED_WIN32_OBJECTS to a non-zero value to enable named events, +// semaphores, and mutexes. +// + +#if DBG +#define IIS_NAMED_WIN32_OBJECTS 1 +#else +#define IIS_NAMED_WIN32_OBJECTS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +HANDLE +PuDbgCreateEvent( + __in LPSTR FileName, + IN ULONG LineNumber, + __in LPSTR MemberName, + IN PVOID Address, + IN BOOL ManualReset, + IN BOOL InitialState + ); + +HANDLE +PuDbgCreateSemaphore( + __in LPSTR FileName, + IN ULONG LineNumber, + __in LPSTR MemberName, + IN PVOID Address, + IN LONG InitialCount, + IN LONG MaximumCount + ); + +HANDLE +PuDbgCreateMutex( + __in LPSTR FileName, + IN ULONG LineNumber, + __in LPSTR MemberName, + IN PVOID Address, + IN BOOL InitialOwner + ); + +#ifdef __cplusplus +} // extern "C" +#endif + +#if IIS_NAMED_WIN32_OBJECTS + +#define IIS_CREATE_EVENT( membername, address, manual, state ) \ + PuDbgCreateEvent( \ + (LPSTR)__FILE__, \ + (ULONG)__LINE__, \ + (membername), \ + (PVOID)(address), \ + (manual), \ + (state) \ + ) + +#define IIS_CREATE_SEMAPHORE( membername, address, initial, maximum ) \ + PuDbgCreateSemaphore( \ + (LPSTR)__FILE__, \ + (ULONG)__LINE__, \ + (membername), \ + (PVOID)(address), \ + (initial), \ + (maximum) \ + ) + +#define IIS_CREATE_MUTEX( membername, address, initial ) \ + PuDbgCreateMutex( \ + (LPSTR)__FILE__, \ + (ULONG)__LINE__, \ + (membername), \ + (PVOID)(address), \ + (initial) \ + ) + +#else // !IIS_NAMED_WIN32_OBJECTS + +#define IIS_CREATE_EVENT( membername, address, manual, state ) \ + CreateEventA( \ + NULL, \ + (manual), \ + (state), \ + NULL \ + ) + +#define IIS_CREATE_SEMAPHORE( membername, address, initial, maximum ) \ + CreateSemaphoreA( \ + NULL, \ + (initial), \ + (maximum), \ + NULL \ + ) + +#define IIS_CREATE_MUTEX( membername, address, initial ) \ + CreateMutexA( \ + NULL, \ + (initial), \ + NULL \ + ) + +#endif // IIS_NAMED_WIN32_OBJECTS + + +/************************ End of File ***********************/ + diff --git a/src/IISLib/reftrace.c b/src/IISLib/reftrace.c new file mode 100644 index 0000000000..c1b2e13a6e --- /dev/null +++ b/src/IISLib/reftrace.c @@ -0,0 +1,229 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include "dbgutil.h" +#include "pudebug.h" +#include "reftrace.h" + + +PTRACE_LOG +CreateRefTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader + ) +/*++ + +Routine Description: + + Creates a new (empty) ref count trace log buffer. + +Arguments: + + LogSize - The number of entries in the log. + + ExtraBytesInHeader - The number of extra bytes to include in the + log header. This is useful for adding application-specific + data to the log. + +Return Value: + + PTRACE_LOG - Pointer to the newly created log if successful, + NULL otherwise. + +--*/ +{ + + return CreateTraceLog( + LogSize, + ExtraBytesInHeader, + sizeof(REF_TRACE_LOG_ENTRY) + ); + +} // CreateRefTraceLog + + +VOID +DestroyRefTraceLog( + IN PTRACE_LOG Log + ) +/*++ + +Routine Description: + + Destroys a ref count trace log buffer created with CreateRefTraceLog(). + +Arguments: + + Log - The ref count trace log buffer to destroy. + +Return Value: + + None. + +--*/ +{ + + DestroyTraceLog( Log ); + +} // DestroyRefTraceLog + + +// +// N.B. For RtlCaptureBacktrace() to work properly, the calling function +// *must* be __cdecl, and must have a "normal" stack frame. So, we decorate +// WriteRefTraceLog[Ex]() with the __cdecl modifier and disable the frame +// pointer omission (FPO) optimization. +// + +//#pragma optimize( "y", off ) // disable frame pointer omission (FPO) +#pragma optimize( "", off ) // disable frame pointer omission (FPO) + +LONG +__cdecl +WriteRefTraceLog( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context + ) +/*++ + +Routine Description: + + Writes a new entry to the specified ref count trace log. The entry + written contains the updated reference count and a stack backtrace + leading up to the current caller. + +Arguments: + + Log - The log to write to. + + NewRefCount - The updated reference count. + + Context - An uninterpreted context to associate with the log entry. + +Return Value: + + Index of entry in log. + +--*/ +{ + + return WriteRefTraceLogEx( + Log, + NewRefCount, + Context, + REF_TRACE_EMPTY_CONTEXT, // suppress use of optional extra contexts + REF_TRACE_EMPTY_CONTEXT, + REF_TRACE_EMPTY_CONTEXT + ); + +} // WriteRefTraceLog + + + + +LONG +__cdecl +WriteRefTraceLogEx( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context, + IN CONST VOID * Context1, // optional extra context + IN CONST VOID * Context2, // optional extra context + IN CONST VOID * Context3 // optional extra context + ) +/*++ + +Routine Description: + + Writes a new "extended" entry to the specified ref count trace log. + The entry written contains the updated reference count, stack backtrace + leading up to the current caller and extra context information. + +Arguments: + + Log - The log to write to. + + NewRefCount - The updated reference count. + + Context - An uninterpreted context to associate with the log entry. + Context1 - An uninterpreted context to associate with the log entry. + Context2 - An uninterpreted context to associate with the log entry. + Context3 - An uninterpreted context to associate with the log entry. + + NOTE Context1/2/3 are "optional" in that the caller may suppress + debug display of these values by passing REF_TRACE_EMPTY_CONTEXT + for each of them. + +Return Value: + + Index of entry in log. + +--*/ +{ + + REF_TRACE_LOG_ENTRY entry; + ULONG hash; + DWORD cStackFramesSkipped; + + // + // Initialize the entry. + // + + RtlZeroMemory( + &entry, + sizeof(entry) + ); + + // + // Set log entry members. + // + + entry.NewRefCount = NewRefCount; + entry.Context = Context; + entry.Thread = GetCurrentThreadId(); + entry.Context1 = Context1; + entry.Context2 = Context2; + entry.Context3 = Context3; + + // + // Capture the stack backtrace. Normally, we skip two stack frames: + // one for this routine, and one for RtlCaptureBacktrace() itself. + // For non-Ex callers who come in via WriteRefTraceLog, + // we skip three stack frames. + // + + if ( entry.Context1 == REF_TRACE_EMPTY_CONTEXT + && entry.Context2 == REF_TRACE_EMPTY_CONTEXT + && entry.Context3 == REF_TRACE_EMPTY_CONTEXT + ) { + + cStackFramesSkipped = 2; + + } else { + + cStackFramesSkipped = 1; + + } + + RtlCaptureStackBackTrace( + cStackFramesSkipped, + REF_TRACE_LOG_STACK_DEPTH, + entry.Stack, + &hash + ); + + // + // Write it to the log. + // + + return WriteTraceLog( + Log, + &entry + ); + +} // WriteRefTraceLogEx + +#pragma optimize( "", on ) // restore frame pointer omission (FPO) + diff --git a/src/IISLib/reftrace.h b/src/IISLib/reftrace.h new file mode 100644 index 0000000000..e90ca0444a --- /dev/null +++ b/src/IISLib/reftrace.h @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _REFTRACE_H_ +#define _REFTRACE_H_ + + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus + +#include +#include "tracelog.h" + +// +// This is the number of stack backtrace values captured in each +// trace log entry. This value is chosen to make the log entry +// exactly twelve dwords long, making it a bit easier to interpret +// from within the debugger without the debugger extension. +// + +#define REF_TRACE_LOG_STACK_DEPTH 9 + +// No-op value for the Context1,2,3 parameters of WriteRefTraceLogEx +//#define REF_TRACE_EMPTY_CONTEXT ((PVOID) -1) +#define REF_TRACE_EMPTY_CONTEXT NULL + + +// +// This defines the entry written to the trace log. +// + +typedef struct _REF_TRACE_LOG_ENTRY { + + LONG NewRefCount; + CONST VOID * Context; + CONST VOID * Context1; + CONST VOID * Context2; + CONST VOID * Context3; + DWORD Thread; + PVOID Stack[REF_TRACE_LOG_STACK_DEPTH]; + +} REF_TRACE_LOG_ENTRY, *PREF_TRACE_LOG_ENTRY; + + +// +// Manipulators. +// + +PTRACE_LOG +CreateRefTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader + ); + +VOID +DestroyRefTraceLog( + IN PTRACE_LOG Log + ); + +LONG +__cdecl +WriteRefTraceLog( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context + ); + +LONG +__cdecl +WriteRefTraceLogEx( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context, + IN CONST VOID * Context1, + IN CONST VOID * Context2, + IN CONST VOID * Context3 + ); + + +#if defined(__cplusplus) +} // extern "C" +#endif // __cplusplus + + +#endif // _REFTRACE_H_ + diff --git a/src/IISLib/rwlock.h b/src/IISLib/rwlock.h new file mode 100644 index 0000000000..dc7ccf834b --- /dev/null +++ b/src/IISLib/rwlock.h @@ -0,0 +1,193 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#if (_WIN32_WINNT < 0x600) + +// +// XP implementation. +// +class CWSDRWLock +{ +public: + + CWSDRWLock() + : m_bInited(FALSE) + { + } + + ~CWSDRWLock() + { + if (m_bInited) + { + DeleteCriticalSection(&m_rwLock.critsec); + CloseHandle(m_rwLock.ReadersDoneEvent); + } + } + + BOOL QueryInited() const + { + return m_bInited; + } + + HRESULT Init() + { + HRESULT hr = S_OK; + + if (FALSE == m_bInited) + { + m_rwLock.fWriterWaiting = FALSE; + m_rwLock.LockCount = 0; + if ( !InitializeCriticalSectionAndSpinCount( &m_rwLock.critsec, 0 )) + { + DWORD dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + return hr; + } + + m_rwLock.ReadersDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if( NULL == m_rwLock.ReadersDoneEvent ) + { + DWORD dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + DeleteCriticalSection(&m_rwLock.critsec); + return hr; + } + m_bInited = TRUE; + } + + return hr; + } + + void SharedAcquire() + { + EnterCriticalSection(&m_rwLock.critsec); + InterlockedIncrement(&m_rwLock.LockCount); + LeaveCriticalSection(&m_rwLock.critsec); + } + + void SharedRelease() + { + ReleaseRWLock(); + } + + void ExclusiveAcquire() + { + EnterCriticalSection( &m_rwLock.critsec ); + + m_rwLock.fWriterWaiting = TRUE; + + // check if there are any readers active + if ( InterlockedExchangeAdd( &m_rwLock.LockCount, 0 ) > 0 ) + { + // + // Wait for all the readers to get done.. + // + WaitForSingleObject( m_rwLock.ReadersDoneEvent, INFINITE ); + } + m_rwLock.LockCount = -1; + } + + void ExclusiveRelease() + { + ReleaseRWLock(); + } + +private: + + BOOL m_bInited; + + typedef struct _RW_LOCK + { + BOOL fWriterWaiting; // Is a writer waiting on the lock? + LONG LockCount; + CRITICAL_SECTION critsec; + HANDLE ReadersDoneEvent; + } RW_LOCK, *PRW_LOCK; + + RW_LOCK m_rwLock; + +private: + + void ReleaseRWLock() + { + LONG Count = InterlockedDecrement( &m_rwLock.LockCount ); + + if ( 0 <= Count ) + { + // releasing a read lock + if (( m_rwLock.fWriterWaiting ) && ( 0 == Count )) + { + SetEvent( m_rwLock.ReadersDoneEvent ); + } + } + else + { + // Releasing a write lock + m_rwLock.LockCount = 0; + m_rwLock.fWriterWaiting = FALSE; + LeaveCriticalSection(&m_rwLock.critsec); + } + } +}; + +#else + +// +// Implementation for Windows Vista or greater. +// +class CWSDRWLock +{ +public: + + CWSDRWLock() + { + InitializeSRWLock(&m_rwLock); + } + + BOOL QueryInited() + { + return TRUE; + } + + + HRESULT Init() + { + // + // Method defined to keep compatibility with CWSDRWLock class for XP. + // + return S_OK; + } + + void SharedAcquire() + { + AcquireSRWLockShared(&m_rwLock); + } + + void SharedRelease() + { + ReleaseSRWLockShared(&m_rwLock); + } + + void ExclusiveAcquire() + { + AcquireSRWLockExclusive(&m_rwLock); + } + + void ExclusiveRelease() + { + ReleaseSRWLockExclusive(&m_rwLock); + } + +private: + + SRWLOCK m_rwLock; +}; + +#endif + +// +// Rename the lock class to a more clear name. +// +typedef CWSDRWLock READ_WRITE_LOCK; \ No newline at end of file diff --git a/src/IISLib/stringa.cpp b/src/IISLib/stringa.cpp new file mode 100644 index 0000000000..29da773bca --- /dev/null +++ b/src/IISLib/stringa.cpp @@ -0,0 +1,1767 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +STRA::STRA( + VOID +) : m_cchLen( 0 ) +{ + *( QueryStr() ) = '\0'; +} + +STRA::STRA( + __inout_ecount(cchInit) CHAR* pbInit, + __in DWORD cchInit +) : m_Buff( pbInit, cchInit * sizeof( CHAR ) ), + m_cchLen(0) +/*++ + Description: + + Used by STACK_STRA. Initially populates underlying buffer with pbInit. + + pbInit is not freed. + + Arguments: + + pbInit - initial memory to use + cchInit - count, in characters, of pbInit + + Returns: + + None. + +--*/ +{ + _ASSERTE( NULL != pbInit ); + _ASSERTE( cchInit > 0 ); + _ASSERTE( pbInit[0] == '\0' ); +} + +BOOL +STRA::IsEmpty( + VOID +) const +{ + return ( m_cchLen == 0 ); +} + +BOOL +STRA::Equals( + __in PCSTR pszRhs, + __in BOOL fIgnoreCase /*= FALSE*/ +) const +{ + _ASSERTE( NULL != pszRhs ); + + if( fIgnoreCase ) + { + return ( 0 == _stricmp( QueryStr(), pszRhs ) ); + } + + return ( 0 == strcmp( QueryStr(), pszRhs ) ); +} + +BOOL +STRA::Equals( + __in const STRA * pstrRhs, + __in BOOL fIgnoreCase /*= FALSE*/ +) const +{ + _ASSERTE( NULL != pstrRhs ); + return Equals( pstrRhs->QueryStr(), fIgnoreCase ); +} + +BOOL +STRA::Equals( + __in const STRA & strRhs, + __in BOOL fIgnoreCase /*= FALSE*/ +) const +{ + return Equals( strRhs.QueryStr(), fIgnoreCase ); +} + +DWORD +STRA::QueryCB( + VOID +) const +// +// Returns the number of bytes in the string excluding the terminating NULL +// +{ + return m_cchLen * sizeof( CHAR ); +} + +DWORD +STRA::QueryCCH( + VOID +) const +// +// Returns the number of characters in the string excluding the terminating NULL +// +{ + return m_cchLen; +} + +DWORD +STRA::QuerySizeCCH( + VOID +) const +// +// Returns size of the underlying storage buffer, in characters +// +{ + return m_Buff.QuerySize() / sizeof( CHAR ); +} + +DWORD +STRA::QuerySize( + VOID +) const +// +// Returns the size of the storage buffer in bytes +// +{ + return m_Buff.QuerySize(); +} + +__nullterminated +__bcount(this->m_cchLen) +CHAR * +STRA::QueryStr( + VOID +) const +// +// Return the string buffer +// +{ + return m_Buff.QueryPtr(); +} + +VOID +STRA::Reset( + VOID +) +// +// Resets the internal string to be NULL string. Buffer remains cached. +// +{ + _ASSERTE( QueryStr() != NULL ); + *(QueryStr()) = '\0'; + m_cchLen = 0; +} + +HRESULT +STRA::Resize( + __in DWORD cchSize +) +{ + if( !m_Buff.Resize( cchSize * sizeof( CHAR ) ) ) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT +STRA::SyncWithBuffer( + VOID +) +// +// Recalculate the length of the string, etc. because we've modified +// the buffer directly. +// +{ + HRESULT hr; + size_t size; + hr = StringCchLengthA( QueryStr(), + QuerySizeCCH(), + &size ); + if ( SUCCEEDED( hr ) ) + { + m_cchLen = static_cast(size); + } + return hr; +} + +HRESULT +STRA::Copy( + __in PCSTR pszCopy +) +{ + HRESULT hr; + size_t cbLen; + hr = StringCbLengthA( pszCopy, + STRSAFE_MAX_CCH, + &cbLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return Copy( pszCopy, cbLen ); +} + + +HRESULT +STRA::Copy( + __in_ecount(cchLen) + PCSTR pszCopy, + __in SIZE_T cbLen +) +// +// Copy the contents of another string to this one +// +{ + _ASSERTE( cbLen <= MAXDWORD ); + + return AuxAppend( + pszCopy, + static_cast(cbLen), + 0 + ); +} + +HRESULT +STRA::Copy( + __in const STRA * pstrRhs +) +{ + _ASSERTE( pstrRhs != NULL ); + return Copy( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRA::Copy( + __in const STRA & strRhs +) +{ + return Copy( strRhs.QueryStr(), strRhs.QueryCCH() ); +} + +HRESULT +STRA::CopyW( + __in PCWSTR pszCopyW +) +{ + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszCopyW, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return CopyW( pszCopyW, cchLen ); +} + +HRESULT +STRA::CopyWTruncate( + __in PCWSTR pszCopyWTruncate +) +{ + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszCopyWTruncate, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return CopyWTruncate( pszCopyWTruncate, cchLen ); +} + +HRESULT +STRA::CopyWTruncate( + __in_ecount(cchLen) + PCWSTR pszCopyWTruncate, + __in SIZE_T cchLen +) +// +// The "Truncate" methods do not do proper conversion. They do a (CHAR) caste +// +{ + _ASSERTE( cchLen <= MAXDWORD ); + + return AuxAppendWTruncate( + pszCopyWTruncate, + static_cast(cchLen), + 0 + ); +} + +HRESULT +STRA::Append( + __in PCSTR pszAppend +) +{ + HRESULT hr; + size_t cbLen; + hr = StringCbLengthA( pszAppend, + STRSAFE_MAX_CCH, + &cbLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return Append( pszAppend, cbLen ); +} + +HRESULT +STRA::Append( + __in_ecount(cchLen) + PCSTR pszAppend, + __in SIZE_T cbLen +) +{ + _ASSERTE( cbLen <= MAXDWORD ); + if ( cbLen == 0 ) + { + return S_OK; + } + return AuxAppend( + pszAppend, + static_cast(cbLen), + QueryCB() + ); +} + +HRESULT +STRA::Append( + __in const STRA * pstrRhs +) +{ + _ASSERTE( pstrRhs != NULL ); + return Append( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRA::Append( + __in const STRA & strRhs +) +{ + return Append( strRhs.QueryStr(), strRhs.QueryCCH() ); +} + +HRESULT +STRA::AppendWTruncate( + __in PCWSTR pszAppendWTruncate +) +{ + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszAppendWTruncate, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return AppendWTruncate( pszAppendWTruncate, cchLen ); +} + +HRESULT +STRA::AppendWTruncate( + __in_ecount(cchLen) + PCWSTR pszAppendWTruncate, + __in SIZE_T cchLen +) +// +// The "Truncate" methods do not do proper conversion. They do a (CHAR) caste +// +{ + _ASSERTE( cchLen <= MAXDWORD ); + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppendWTruncate( + pszAppendWTruncate, + static_cast(cchLen), + QueryCB() + ); +} + +HRESULT +STRA::CopyToBuffer( + __out_bcount(*pcb) CHAR* pszBuffer, + __inout DWORD * pcb +) const +// +// Makes a copy of the stored string into the given buffer +// +{ + _ASSERTE( NULL != pszBuffer ); + _ASSERTE( NULL != pcb ); + + HRESULT hr = S_OK; + DWORD cbNeeded = QueryCB() + sizeof( CHAR ); + + if( *pcb < cbNeeded ) + { + hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ); + goto Finished; + } + + memcpy( pszBuffer, QueryStr(), cbNeeded ); + +Finished: + + *pcb = cbNeeded; + + return hr; +} + +HRESULT +STRA::SetLen( + __in DWORD cchLen +) +/*++ + * +Routine Description: + + Set the length of the string and null terminate, if there + is sufficient buffer already allocated. Will not reallocate. + +Arguments: + + cchLen - The number of characters in the new string. + +Return Value: + + HRESULT + +--*/ +{ + if( cchLen >= QuerySizeCCH() ) + { + return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + } + + *( QueryStr() + cchLen ) = '\0'; + m_cchLen = cchLen; + + return S_OK; +} + + +HRESULT +STRA::SafeSnprintf( + __in __format_string + PCSTR pszFormatString, + ... +) +/*++ + +Routine Description: + + Writes to a STRA, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pszFormatString - printf format + ... - printf args + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + va_list argsList; + va_start( argsList, pszFormatString ); + + hr = SafeVsnprintf(pszFormatString, argsList); + + va_end( argsList ); + return hr; +} + +HRESULT +STRA::SafeVsnprintf( + __in __format_string + PCSTR pszFormatString, + va_list argsList +) +/*++ + +Routine Description: + + Writes to a STRA, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pszFormatString - printf format + argsList - printf va_list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + int cchOutput; + int cchNeeded; + + // + // Format the incoming message using vsnprintf() + // so that the overflows are captured + // + cchOutput = _vsnprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pszFormatString, + argsList + ); + + if( cchOutput == -1 ) + { + // + // Couldn't fit this in the original STRU size. + // + cchNeeded = _vscprintf( pszFormatString, argsList ); + if( cchNeeded > 64 * 1024 ) + { + // + // If we're trying to produce a string > 64k chars, then + // there is probably a problem + // + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + + // + // _vscprintf doesn't include terminating null character + // + cchNeeded++; + + hr = Resize( cchNeeded ); + if( FAILED( hr ) ) + { + goto Finished; + } + + cchOutput = _vsnprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pszFormatString, + argsList + ); + if( -1 == cchOutput ) + { + // + // This should never happen, cause we should already have correctly sized memory + // + _ASSERTE( FALSE ); + + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + } + + // + // always null terminate at the last WCHAR + // + QueryStr()[ QuerySizeCCH() - 1 ] = L'\0'; + + // + // we directly touched the buffer - therefore: + // + hr = SyncWithBuffer(); + if( FAILED( hr ) ) + { + goto Finished; + } + +Finished: + + if( FAILED( hr ) ) + { + Reset(); + } + + return hr; +} + +bool +FShouldEscapeUtf8( + BYTE ch + ) +{ + if ( ( ch >= 128 ) ) + { + return true; + } + + return false; +} + +bool +FShouldEscapeUrl( + BYTE ch + ) +{ + if ( ( ch >= 128 || + ch <= 32 || + ch == '<' || + ch == '>' || + ch == '%' || + ch == '?' || + ch == '#' ) && + !( ch == '\n' || ch == '\r' ) ) + { + return true; + } + + return false; +} + +HRESULT +STRA::Escape( + VOID +) +/*++ + +Routine Description: + + Escapes a STRA + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + return EscapeInternal( FShouldEscapeUrl ); +} + +HRESULT +STRA::EscapeUtf8( + VOID +) +/*++ + +Routine Description: + + Escapes the high-bit chars in a STRA. LWS, CR, LF & controls are untouched. + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + return EscapeInternal( FShouldEscapeUtf8 ); +} + + +HRESULT +STRA::EscapeInternal( + PFN_F_SHOULD_ESCAPE pfnFShouldEscape +) +/*++ + +Routine Description: + + Escapes a STRA according to the predicate function passed in + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + LPCSTR pch = QueryStr(); + __analysis_assume( pch != NULL ); + int i = 0; + BYTE ch; + HRESULT hr = S_OK; + BOOL fRet = FALSE; + SIZE_T NewSize = 0; + + // Set to true if any % escaping occurs + BOOL fEscapingDone = FALSE; + + // + // If there are any characters that need to be escaped we copy the entire string + // character by character into straTemp, escaping as we go, then at the end + // copy all of straTemp over. Don't modify InlineBuffer directly. + // + CHAR InlineBuffer[512]; + InlineBuffer[0] = '\0'; + STRA straTemp(InlineBuffer, sizeof(InlineBuffer)/sizeof(*InlineBuffer)); + + _ASSERTE( pch ); + + while (ch = pch[i]) + { + // + // Escape characters that are in the non-printable range + // but ignore CR and LF + // + + if ( pfnFShouldEscape( ch ) ) + { + if (FALSE == fEscapingDone) + { + // first character in the string that needed escaping + fEscapingDone = TRUE; + + // guess that the size needs to be larger than + // what we used to have times two + NewSize = QueryCCH() * 2; + if ( NewSize > MAXDWORD ) + { + hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + return hr; + } + + hr = straTemp.Resize( static_cast(NewSize) ); + + if (FAILED(hr)) + { + return hr; + } + + // Copy all of the previous buffer into buffTemp, only if it is not the first character: + + if ( i > 0) + { + hr = straTemp.Copy(QueryStr(), + i * sizeof(CHAR)); + if (FAILED(hr)) + { + return hr; + } + } + } + + // resize the temporary (if needed) with the slop of the entire buffer length + // this fixes constant reallocation if the entire string needs to be escaped + NewSize = QueryCCH() + 2 * sizeof(CHAR) + 1 * sizeof(CHAR); + if ( NewSize > MAXDWORD ) + { + hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + return hr; + } + + fRet = straTemp.m_Buff.Resize( NewSize ); + if ( !fRet ) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + // + // Create the string to append for the current character + // + + CHAR chHex[3]; + chHex[0] = '%'; + + // + // Convert the low then the high character to hex + // + + UINT nLowDigit = (UINT)(ch % 16); + chHex[2] = TODIGIT( nLowDigit ); + + ch /= 16; + + UINT nHighDigit = (UINT)(ch % 16); + + chHex[1] = TODIGIT( nHighDigit ); + + // + // Actually append the converted character to the end of the temporary + // + hr = straTemp.Append(chHex, 3); + if (FAILED(hr)) + { + return hr; + } + } + else + { + // if no escaping done, no need to copy + if (fEscapingDone) + { + // if ANY escaping done, copy current character into new buffer + straTemp.Append(&pch[i], 1); + } + } + + // inspect the next character in the string + i++; + } + + if (fEscapingDone) + { + // the escaped string is now in straTemp + hr = Copy(straTemp); + } + + return hr; + +} // EscapeInternal() + +VOID +STRA::Unescape( + VOID +) +/*++ + +Routine Description: + + Unescapes a STRA + + Supported escape sequences are: + %uxxxx unescapes Unicode character xxxx into system codepage + %xx unescapes character xx + % without following hex digits is ignored + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + CHAR *pScan; + CHAR *pDest; + CHAR *pNextScan; + WCHAR wch; + DWORD dwLen; + BOOL fChanged = FALSE; + + // + // Now take care of any escape characters + // + pDest = pScan = strchr(QueryStr(), '%'); + + while (pScan) + { + if ((pScan[1] == 'u' || pScan[1] == 'U') && + SAFEIsXDigit(pScan[2]) && + SAFEIsXDigit(pScan[3]) && + SAFEIsXDigit(pScan[4]) && + SAFEIsXDigit(pScan[5])) + { + wch = TOHEX(pScan[2]) * 4096 + TOHEX(pScan[3]) * 256 + + TOHEX(pScan[4]) * 16 + TOHEX(pScan[5]); + + dwLen = WideCharToMultiByte(CP_ACP, + WC_NO_BEST_FIT_CHARS, + &wch, + 1, + (LPSTR) pDest, + 6, + NULL, + NULL); + + pDest += dwLen; + pScan += 6; + fChanged = TRUE; + } + else if (SAFEIsXDigit(pScan[1]) && SAFEIsXDigit(pScan[2])) + { + *pDest = TOHEX(pScan[1]) * 16 + TOHEX(pScan[2]); + + pDest ++; + pScan += 3; + fChanged = TRUE; + } + else // Not an escaped char, just a '%' + { + if (fChanged) + { + *pDest = *pScan; + } + + pDest++; + pScan++; + } + + // + // Copy all the information between this and the next escaped char + // + pNextScan = strchr(pScan, '%'); + + if (fChanged) // pScan!=pDest, so we have to copy the char's + { + if (!pNextScan) // That was the last '%' in the string + { + memmove(pDest, + pScan, + QueryCCH() - DIFF(pScan - QueryStr()) + 1); + } + else + { + // There is another '%', move intermediate chars + if ((dwLen = (DWORD)DIFF(pNextScan - pScan)) != 0) + { + memmove(pDest, + pScan, + dwLen); + pDest += dwLen; + } + } + } + + pScan = pNextScan; + } + + if (fChanged) + { + m_cchLen = (DWORD)strlen(QueryStr()); // for safety recalc the length + } + + return; +} + +HRESULT +STRA::CopyWToUTF8Unescaped( + __in LPCWSTR cpchStr +) +{ + return STRA::CopyWToUTF8Unescaped(cpchStr, (DWORD) wcslen(cpchStr)); +} + +HRESULT +STRA::CopyWToUTF8Unescaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch +) +{ + HRESULT hr = S_OK; + int iRet; + + if (cch == 0) + { + Reset(); + return S_OK; + } + + iRet = ConvertUnicodeToUTF8(cpchStr, + &m_Buff, + cch); + if (-1 == iRet) + { + // could not convert + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_cchLen = iRet; + + _ASSERTE(strlen(m_Buff.QueryPtr()) == m_cchLen); +Finished: + return hr; +} + +HRESULT +STRA::CopyWToUTF8Escaped( + __in LPCWSTR cpchStr +) +{ + return STRA::CopyWToUTF8Escaped(cpchStr, (DWORD) wcslen(cpchStr)); +} + +HRESULT +STRA::CopyWToUTF8Escaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch +) +{ + HRESULT hr = S_OK; + + hr = CopyWToUTF8Unescaped(cpchStr, cch); + if (FAILED(hr)) + { + goto Finished; + } + + hr = Escape(); + if (FAILED(hr)) + { + goto Finished; + } + + hr = S_OK; +Finished: + return hr; +} + +HRESULT +STRA::AuxAppend( + __in_ecount(cbLen) + LPCSTR pStr, + __in DWORD cbLen, + __in DWORD cbOffset +) +{ + _ASSERTE( NULL != pStr ); + _ASSERTE( cbOffset <= QueryCB() ); + + ULONGLONG cb64NewSize = (ULONGLONG)cbOffset + cbLen + sizeof( CHAR ); + if( cb64NewSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( m_Buff.QuerySize() < cb64NewSize ) + { + if( !m_Buff.Resize( static_cast(cb64NewSize) ) ) + { + return E_OUTOFMEMORY; + } + } + + memcpy( reinterpret_cast(m_Buff.QueryPtr()) + cbOffset, pStr, cbLen ); + + m_cchLen = cbLen + cbOffset; + + *( QueryStr() + m_cchLen ) = '\0'; + + return S_OK; +} + +HRESULT +STRA::AuxAppendW( + __in_ecount(cchAppendW) + PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset, + __in UINT CodePage, + __in BOOL fFailIfNoTranslation, + __in DWORD dwFlags +) +{ + HRESULT hr = S_OK; + DWORD cbAvailable = 0; + DWORD cbRet = 0; + + // + // There are only two expect places to append + // + _ASSERTE( 0 == cbOffset || QueryCB() == cbOffset ); + + if ( cchAppendW == 0 ) + { + goto Finished; + } + + // + // start by assuming 1 char to 1 char will be enough space + // + if( !m_Buff.Resize( cbOffset + cchAppendW + sizeof( CHAR ) ) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + cbAvailable = m_Buff.QuerySize() - cbOffset; + + cbRet = WideCharToMultiByte( + CodePage, + dwFlags, + pszAppendW, + cchAppendW, + QueryStr() + cbOffset, + cbAvailable, + NULL, + NULL + ); + if( 0 != cbRet ) + { + if(!m_Buff.Resize(cbOffset + cbRet + 1)) + { + hr = E_OUTOFMEMORY; + } + + // + // not zero --> success, so we're done + // + goto Finished; + } + + // + // We only know how to handle ERROR_INSUFFICIENT_BUFFER + // + hr = HRESULT_FROM_WIN32( GetLastError() ); + if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) ) + { + goto Finished; + } + + // + // Reset HResult because we need to get the number of bytes needed + // + hr = S_OK; + cbRet = WideCharToMultiByte( + CodePage, + dwFlags, + pszAppendW, + cchAppendW, + NULL, + 0, + NULL, + NULL + ); + if( 0 == cbRet ) + { + // + // no idea how we could ever reach here + // + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + + if( !m_Buff.Resize( cbOffset + cbRet + 1) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + cbAvailable = m_Buff.QuerySize() - cbOffset; + + cbRet = WideCharToMultiByte( + CodePage, + dwFlags, + pszAppendW, + cchAppendW, + QueryStr() + cbOffset, + cbAvailable, + NULL, + NULL + ); + if( 0 == cbRet ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + +Finished: + + if( SUCCEEDED( hr ) && 0 != cbRet ) + { + m_cchLen = cbRet + cbOffset; + } + + // + // ensure we're still NULL terminated in the right spot + // (regardless of success or failure) + // + QueryStr()[m_cchLen] = '\0'; + + return hr; +} + +HRESULT +STRA::AuxAppendWTruncate( + __in_ecount(cchAppendW) + __in PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset +) +// +// Cheesey WCHAR --> CHAR conversion +// +{ + HRESULT hr = S_OK; + CHAR* pszBuffer; + + _ASSERTE( NULL != pszAppendW ); + _ASSERTE( 0 == cbOffset || cbOffset == QueryCB() ); + + if( !pszAppendW ) + { + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + ULONGLONG cbNeeded = (ULONGLONG)cbOffset + cchAppendW + sizeof( CHAR ); + if( cbNeeded > MAXDWORD ) + { + hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + goto Finished; + } + + if( !m_Buff.Resize( static_cast(cbNeeded) ) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // Copy/convert the UNICODE string over (by making two bytes into one) + // + pszBuffer = QueryStr() + cbOffset; + for( DWORD i = 0; i < cchAppendW; i++ ) + { + pszBuffer[i] = static_cast(pszAppendW[i]); + } + + m_cchLen = cchAppendW + cbOffset; + *( QueryStr() + m_cchLen ) = '\0'; + +Finished: + + return hr; +} + +// static +int +STRA::ConvertUnicodeToCodePage( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __inout BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen, + __in UINT uCodePage +) +{ + _ASSERTE(NULL != pszSrcUnicodeString); + _ASSERTE(NULL != pbufDstAnsiString); + + BOOL bTemp; + int iStrLen = 0; + DWORD dwFlags; + + if (uCodePage == CP_ACP) + { + dwFlags = WC_NO_BEST_FIT_CHARS; + } + else + { + dwFlags = 0; + } + + iStrLen = WideCharToMultiByte(uCodePage, + dwFlags, + pszSrcUnicodeString, + dwStringLen, + (LPSTR)pbufDstAnsiString->QueryPtr(), + (int)pbufDstAnsiString->QuerySize(), + NULL, + NULL); + if ((iStrLen == 0) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + iStrLen = WideCharToMultiByte(uCodePage, + dwFlags, + pszSrcUnicodeString, + dwStringLen, + NULL, + 0, + NULL, + NULL); + if (iStrLen != 0) { + // add one just for the extra NULL + bTemp = pbufDstAnsiString->Resize(iStrLen + 1); + if (!bTemp) + { + iStrLen = 0; + } + else + { + iStrLen = WideCharToMultiByte(uCodePage, + dwFlags, + pszSrcUnicodeString, + dwStringLen, + (LPSTR)pbufDstAnsiString->QueryPtr(), + (int)pbufDstAnsiString->QuerySize(), + NULL, + NULL); + } + + } + } + + if (0 != iStrLen && + pbufDstAnsiString->Resize(iStrLen + 1)) + { + // insert a terminating NULL into buffer for the dwStringLen+1 in the case that the dwStringLen+1 was not a NULL. + ((CHAR*)pbufDstAnsiString->QueryPtr())[iStrLen] = '\0'; + } + else + { + iStrLen = -1; + } + + return iStrLen; +} + +// static +HRESULT +STRA::ConvertUnicodeToMultiByte( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen +) +{ + return ConvertUnicodeToCodePage( pszSrcUnicodeString, + pbufDstAnsiString, + dwStringLen, + CP_ACP ); +} + +// static +HRESULT +STRA::ConvertUnicodeToUTF8( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen +) +{ + return ConvertUnicodeToCodePage( pszSrcUnicodeString, + pbufDstAnsiString, + dwStringLen, + CP_UTF8 ); +} + +/*++ + +Routine Description: + + Removes leading and trailing whitespace + +--*/ + +VOID +STRA::Trim() +{ + PSTR pszString = QueryStr(); + DWORD cchNewLength = m_cchLen; + DWORD cchLeadingWhitespace = 0; + DWORD cchTempLength = 0; + + for (LONG ixString = m_cchLen - 1; ixString >= 0; ixString--) + { + if (isspace((unsigned char) pszString[ixString]) != 0) + { + pszString[ixString] = '\0'; + cchNewLength--; + } + else + { + break; + } + } + + cchTempLength = cchNewLength; + for (DWORD ixString = 0; ixString < cchTempLength; ixString++) + { + if (isspace((unsigned char) pszString[ixString]) != 0) + { + cchLeadingWhitespace++; + cchNewLength--; + } + else + { + break; + } + } + + if (cchNewLength == 0) + { + + Reset(); + } + else if (cchLeadingWhitespace > 0) + { + memmove(pszString, pszString + cchLeadingWhitespace, cchNewLength * sizeof(CHAR)); + pszString[cchNewLength] = '\0'; + } + + SyncWithBuffer(); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + pStraPrefix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::StartsWith( + __in const STRA * pStraPrefix, + __in bool fIgnoreCase) const +{ + _ASSERTE( pStraPrefix != NULL ); + return StartsWith(pStraPrefix->QueryStr(), fIgnoreCase); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + straPrefix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::StartsWith( + __in const STRA & straPrefix, + __in bool fIgnoreCase) const +{ + return StartsWith(straPrefix.QueryStr(), fIgnoreCase); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + pszPrefix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::StartsWith( + __in PCSTR pszPrefix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + BOOL fMatch = FALSE; + size_t cchPrefix = 0; + + if (pszPrefix == NULL) + { + goto Finished; + } + + hr = StringCchLengthA( pszPrefix, + STRSAFE_MAX_CCH, + &cchPrefix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchPrefix <= MAXDWORD ); + + if (cchPrefix > m_cchLen) + { + goto Finished; + } + + if( fIgnoreCase ) + { + fMatch = ( 0 == _strnicmp( QueryStr(), pszPrefix, cchPrefix ) ); + } + else + { + fMatch = ( 0 == strncmp( QueryStr(), pszPrefix, cchPrefix ) ); + } + + +Finished: + + return fMatch; +} + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + pStraSuffix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::EndsWith( + __in const STRA * pStraSuffix, + __in bool fIgnoreCase) const +{ + _ASSERTE( pStraSuffix != NULL ); + return EndsWith(pStraSuffix->QueryStr(), fIgnoreCase); +} + + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + straSuffix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::EndsWith( + __in const STRA & straSuffix, + __in bool fIgnoreCase) const +{ + return EndsWith(straSuffix.QueryStr(), fIgnoreCase); +} + + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + pszSuffix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::EndsWith( + __in PCSTR pszSuffix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + PSTR pszString = QueryStr(); + BOOL fMatch = FALSE; + size_t cchSuffix = 0; + ptrdiff_t ixOffset = 0; + + if (pszSuffix == NULL) + { + goto Finished; + } + + hr = StringCchLengthA( pszSuffix, + STRSAFE_MAX_CCH, + &cchSuffix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchSuffix <= MAXDWORD ); + + if (cchSuffix > m_cchLen) + { + goto Finished; + } + + ixOffset = m_cchLen - cchSuffix; + _ASSERTE(ixOffset >= 0 && ixOffset <= MAXDWORD); + + if( fIgnoreCase ) + { + fMatch = ( 0 == _strnicmp( pszString + ixOffset, pszSuffix, cchSuffix ) ); + } + else + { + fMatch = ( 0 == strncmp( pszString + ixOffset, pszSuffix, cchSuffix ) ); + } + +Finished: + + return fMatch; +} + + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - the initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRA::IndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const CHAR* pChar = strchr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified substring. + +Arguments: + + pszValue - substring to find + dwStartIndex - initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRA::IndexOf( + __in PCSTR pszValue, + __in DWORD dwStartIndex + ) const +{ + HRESULT hr = S_OK; + INT nIndex = -1; + SIZE_T cchValue = 0; + + // Validate input parameters + if( dwStartIndex >= QueryCCH() || !pszValue ) + { + goto Finished; + } + + const CHAR* pChar = strstr( QueryStr() + dwStartIndex, pszValue ); + + // Determine the index if found + if( pChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the last occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - initial index. + +Return Value: + + The index for the last character occurence in the string. + + -1 if not found. + +--*/ +INT +STRA::LastIndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const CHAR* pChar = strrchr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} diff --git a/src/IISLib/stringa.h b/src/IISLib/stringa.h new file mode 100644 index 0000000000..39737f4a69 --- /dev/null +++ b/src/IISLib/stringa.h @@ -0,0 +1,515 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "buffer.h" +#include "macros.h" +#include + +class STRA +{ + +public: + + STRA( + VOID + ); + + STRA( + __inout_ecount(cchInit) CHAR* pbInit, + __in DWORD cchInit + ); + + BOOL + IsEmpty( + VOID + ) const; + + BOOL + Equals( + __in PCSTR pszRhs, + __in BOOL fIgnoreCase = FALSE + ) const; + + BOOL + Equals( + __in const STRA * pstrRhs, + __in BOOL fIgnoreCase = FALSE + ) const; + + BOOL + Equals( + __in const STRA & strRhs, + __in BOOL fIgnoreCase = FALSE + ) const; + + static + BOOL + Equals( + __in PCSTR pszLhs, + __in PCSTR pszRhs, + __in bool fIgnoreCase = false + ) + { + // Return FALSE if either or both strings are NULL. + if (!pszLhs || !pszRhs) return FALSE; + + if( fIgnoreCase ) + { + return ( 0 == _stricmp( pszLhs, pszRhs ) ); + } + + return ( 0 == strcmp( pszLhs, pszRhs ) ); + } + + VOID + Trim(); + + BOOL + StartsWith( + __in const STRA * pStraPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + StartsWith( + __in const STRA & straPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + StartsWith( + __in PCSTR pszPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in const STRA * pStraSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in const STRA & straSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in PCSTR pszSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + INT + IndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + IndexOf( + __in PCSTR pszValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + LastIndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + DWORD + QueryCB( + VOID + ) const; + + DWORD + QueryCCH( + VOID + ) const; + + DWORD + QuerySizeCCH( + VOID + ) const; + + DWORD + QuerySize( + VOID + ) const; + + __nullterminated + __bcount(this->m_cchLen) + CHAR * + QueryStr( + VOID + ) const; + + VOID + Reset( + VOID + ); + + HRESULT + Resize( + __in DWORD cchSize + ); + + HRESULT + SyncWithBuffer( + VOID + ); + + HRESULT + Copy( + __in PCSTR pszCopy + ); + + HRESULT + Copy( + __in_ecount(cbLen) + PCSTR pszCopy, + __in SIZE_T cbLen + ); + + HRESULT + Copy( + __in const STRA * pstrRhs + ); + + HRESULT + Copy( + __in const STRA & strRhs + ); + + HRESULT + CopyW( + __in PCWSTR pszCopyW + ); + + HRESULT + CopyW( + __in_ecount(cchLen) + PCWSTR pszCopyW, + __in SIZE_T cchLen, + __in UINT CodePage = CP_UTF8, + __in BOOL fFailIfNoTranslation = FALSE + ) + { + _ASSERTE( cchLen <= MAXDWORD ); + + return AuxAppendW( + pszCopyW, + static_cast(cchLen), + 0, + CodePage, + fFailIfNoTranslation + ); + } + + HRESULT + CopyWTruncate( + __in PCWSTR pszCopyWTruncate + ); + + HRESULT + CopyWTruncate( + __in_ecount(cchLen) + PCWSTR pszCopyWTruncate, + __in SIZE_T cchLen + ); + + HRESULT + Append( + __in PCSTR pszAppend + ); + + HRESULT + Append( + __in_ecount(cbLen) + PCSTR pszAppend, + __in SIZE_T cbLen + ); + + HRESULT + Append( + __in const STRA * pstrRhs + ); + + HRESULT + Append( + __in const STRA & strRhs + ); + + HRESULT + AppendW( + __in PCWSTR pszAppendW + ) + { + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszAppendW, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return AppendW( pszAppendW, cchLen ); + } + + HRESULT + AppendW( + __in_ecount(cchLen) + PCWSTR pszAppendW, + __in SIZE_T cchLen, + __in UINT CodePage = CP_UTF8, + __in BOOL fFailIfNoTranslation = FALSE + ) + { + _ASSERTE( cchLen <= MAXDWORD ); + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppendW( + pszAppendW, + static_cast(cchLen), + QueryCB(), + CodePage, + fFailIfNoTranslation + ); + } + + HRESULT + AppendWTruncate( + __in PCWSTR pszAppendWTruncate + ); + + HRESULT + AppendWTruncate( + __in_ecount(cchLen) + PCWSTR pszAppendWTruncate, + __in SIZE_T cchLen + ); + + HRESULT + CopyToBuffer( + __out_bcount(*pcb) CHAR* pszBuffer, + __inout DWORD * pcb + ) const; + + HRESULT + SetLen( + __in DWORD cchLen + ); + + HRESULT + SafeSnprintf( + __in __format_string + PCSTR pszFormatString, + ... + ); + + HRESULT + SafeVsnprintf( + __in __format_string + PCSTR pszFormatString, + va_list argsList + ); + + HRESULT + Escape( + VOID + ); + + HRESULT + EscapeUtf8( + VOID + ); + + VOID + Unescape( + VOID + ); + + HRESULT + CopyWToUTF8Unescaped( + __in LPCWSTR cpchStr + ); + + HRESULT + CopyWToUTF8Unescaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch + ); + + HRESULT + CopyWToUTF8Escaped( + __in LPCWSTR cpchStr + ); + + HRESULT + CopyWToUTF8Escaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch + ); + +private: + + // + // Avoid C++ errors. This object should never go through a copy + // constructor, unintended cast or assignment. + // + STRA( const STRA &); + STRA & operator = (const STRA &); + + HRESULT + AuxAppend( + __in_ecount(cbLen) + LPCSTR pStr, + __in DWORD cbLen, + __in DWORD cbOffset + ); + + HRESULT + AuxAppendW( + __in_ecount(cchAppendW) + PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset, + __in UINT CodePage, + __in BOOL fFailIfNoTranslation + ) + { + DWORD dwFlags = 0; + + if( CP_ACP == CodePage ) + { + dwFlags = WC_NO_BEST_FIT_CHARS; + } + else if( fFailIfNoTranslation && CodePage == CP_UTF8 ) + { + // + // WC_ERR_INVALID_CHARS is only supported in Longhorn or greater. + // +#if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + dwFlags |= WC_ERR_INVALID_CHARS; +#else + UNREFERENCED_PARAMETER(fFailIfNoTranslation); +#endif + } + + return AuxAppendW( pszAppendW, + cchAppendW, + cbOffset, + CodePage, + fFailIfNoTranslation, + dwFlags ); + } + + HRESULT + AuxAppendW( + __in_ecount(cchAppendW) + PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset, + __in UINT CodePage, + __in BOOL fFailIfNoTranslation, + __in DWORD dwFlags + ); + + HRESULT + AuxAppendWTruncate( + __in_ecount(cchAppendW) + __in PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset + ); + + static + int + ConvertUnicodeToCodePage( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __inout BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen, + __in UINT uCodePage + ); + + static + HRESULT + ConvertUnicodeToMultiByte( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen + ); + + static + HRESULT + ConvertUnicodeToUTF8( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen + ); + + typedef bool (* PFN_F_SHOULD_ESCAPE)(BYTE ch); + + HRESULT + EscapeInternal( + PFN_F_SHOULD_ESCAPE pfnFShouldEscape + ); + + // + // Buffer with an inline buffer of 1, + // enough to hold null-terminating character. + // + BUFFER_T m_Buff; + DWORD m_cchLen; +}; + +inline +HRESULT +AppendToString( + ULONGLONG Number, + STRA & String +) +{ + // prefast complains Append requires input + // to be null terminated, so zero initialize + // and pass the size of the buffer minus one + // to _ui64toa_s + CHAR chNumber[32] = {0}; + if (_ui64toa_s(Number, + chNumber, + sizeof(chNumber) - sizeof(CHAR), + 10) != 0) + { + return E_INVALIDARG; + } + return String.Append(chNumber); +} + +template +CHAR* InitHelper(__out CHAR (&psz)[size]) +{ + psz[0] = '\0'; + return psz; +} + +// +// Heap operation reduction macros +// +#define STACK_STRA(name, size) CHAR __ach##name[size];\ + STRA name(InitHelper(__ach##name), sizeof(__ach##name)) + +#define INLINE_STRA(name, size) CHAR __ach##name[size];\ + STRA name; + +#define INLINE_STRA_INIT(name) name(InitHelper(__ach##name), sizeof(__ach##name)) diff --git a/src/IISLib/stringu.cpp b/src/IISLib/stringu.cpp new file mode 100644 index 0000000000..15da79a7fe --- /dev/null +++ b/src/IISLib/stringu.cpp @@ -0,0 +1,1271 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma warning (disable : 4267) + +#include "precomp.h" + +STRU::STRU( + VOID +) : m_cchLen( 0 ) +{ + *(QueryStr()) = L'\0'; +} + +STRU::STRU( + __inout_ecount(cchInit) WCHAR* pbInit, + __in DWORD cchInit +) : m_Buff( pbInit, cchInit * sizeof( WCHAR ) ), + m_cchLen( 0 ) +/*++ + Description: + + Used by STACK_STRU. Initially populates underlying buffer with pbInit. + + pbInit is not freed. + + Arguments: + + pbInit - initial memory to use + cchInit - count, in characters, of pbInit + + Returns: + + None. + +--*/ +{ + _ASSERTE( cchInit <= (MAXDWORD / sizeof( WCHAR )) ); + _ASSERTE( NULL != pbInit ); + _ASSERTE(cchInit > 0 ); + _ASSERTE(pbInit[0] == L'\0'); +} + +BOOL +STRU::IsEmpty( + VOID +) const +{ + return ( m_cchLen == 0 ); +} + +DWORD +STRU::QueryCB( + VOID +) const +// +// Returns the number of bytes in the string excluding the terminating NULL +// +{ + return m_cchLen * sizeof( WCHAR ); +} + +DWORD +STRU::QueryCCH( + VOID +) const +// +// Returns the number of characters in the string excluding the terminating NULL +// +{ + return m_cchLen; +} + +DWORD +STRU::QuerySizeCCH( + VOID +) const +// +// Returns size of the underlying storage buffer, in characters +// +{ + return m_Buff.QuerySize() / sizeof( WCHAR ); +} + +__nullterminated +__ecount(this->m_cchLen) +WCHAR* +STRU::QueryStr( + VOID +) const +// +// Return the string buffer +// +{ + return m_Buff.QueryPtr(); +} + +VOID +STRU::Reset( + VOID +) +// +// Resets the internal string to be NULL string. Buffer remains cached. +// +{ + _ASSERTE( QueryStr() != NULL ); + *(QueryStr()) = L'\0'; + m_cchLen = 0; +} + +HRESULT +STRU::Resize( + DWORD cchSize +) +{ + SIZE_T cbSize = cchSize * sizeof( WCHAR ); + if ( cbSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + if( !m_Buff.Resize( cbSize ) ) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT +STRU::SyncWithBuffer( + VOID +) +// +// Recalculate the length of the string, etc. because we've modified +// the buffer directly. +// +{ + HRESULT hr; + size_t size; + hr = StringCchLengthW( QueryStr(), + QuerySizeCCH(), + &size ); + if ( SUCCEEDED( hr ) ) + { + m_cchLen = static_cast(size); + } + return hr; +} + +HRESULT +STRU::Copy( + __in PCWSTR pszCopy +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCchLengthW( pszCopy, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return Copy( pszCopy, + cbStr ); +} + +HRESULT +STRU::Copy( + __in_ecount(cchLen) + PCWSTR pszCopy, + SIZE_T cchLen +) +// +// Copy the contents of another string to this one +// +{ + return AuxAppend( pszCopy, + cchLen * sizeof(WCHAR), + 0); +} + +HRESULT +STRU::Copy( + __in const STRU * pstrRhs +) +{ + _ASSERTE( NULL != pstrRhs ); + return Copy( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRU::Copy( + __in const STRU & str +) +{ + return Copy( str.QueryStr(), str.QueryCCH() ); +} + +HRESULT +STRU::CopyAndExpandEnvironmentStrings( + __in PCWSTR pszSource +) +{ + HRESULT hr = S_OK; + DWORD cchDestReqBuff = 0; + + Reset(); + + cchDestReqBuff = ExpandEnvironmentStringsW( pszSource, + QueryStr(), + QuerySizeCCH() ); + if ( cchDestReqBuff == 0 ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + else if ( cchDestReqBuff > QuerySizeCCH() ) + { + hr = Resize( cchDestReqBuff ); + if ( FAILED( hr ) ) + { + goto Finished; + } + + cchDestReqBuff = ExpandEnvironmentStringsW( pszSource, + QueryStr(), + QuerySizeCCH() ); + + if ( cchDestReqBuff == 0 || cchDestReqBuff > QuerySizeCCH() ) + { + _ASSERTE( FALSE ); + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + } + + hr = SyncWithBuffer(); + if ( FAILED( hr ) ) + { + goto Finished; + } + +Finished: + + return hr; + +} + +HRESULT +STRU::CopyA( + __in PCSTR pszCopyA +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCbLengthA( pszCopyA, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return CopyA( pszCopyA, + cbStr ); +} + +HRESULT +STRU::CopyA( + __in_bcount(cchLen) + PCSTR pszCopyA, + SIZE_T cchLen, + UINT CodePage /*= CP_UTF8*/ +) +{ + return AuxAppendA( + pszCopyA, + cchLen, + 0, + CodePage + ); +} + +HRESULT +STRU::Append( + __in PCWSTR pszAppend +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCchLengthW( pszAppend, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return Append( pszAppend, + cbStr ); +} + +HRESULT +STRU::Append( + __in_ecount(cchLen) + PCWSTR pszAppend, + SIZE_T cchLen +) +// +// Append something to the end of the string +// +{ + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppend( pszAppend, + cchLen * sizeof(WCHAR), + QueryCB() ); +} + +HRESULT +STRU::Append( + __in const STRU * pstrRhs +) +{ + _ASSERTE( NULL != pstrRhs ); + return Append( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRU::Append( + __in const STRU & strRhs +) +{ + return Append( strRhs.QueryStr(), strRhs.QueryCCH() ); +} + +HRESULT +STRU::AppendA( + __in PCSTR pszAppendA +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCbLengthA( pszAppendA, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return AppendA( pszAppendA, + cbStr ); +} + +HRESULT +STRU::AppendA( + __in_bcount(cchLen) + PCSTR pszAppendA, + SIZE_T cchLen, + UINT CodePage /*= CP_UTF8*/ +) +{ + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppendA( + pszAppendA, + cchLen, + QueryCB(), + CodePage + ); +} + +HRESULT +STRU::CopyToBuffer( + __out_bcount(*pcb) WCHAR* pszBuffer, + PDWORD pcb +) const +// +// Makes a copy of the stored string into the given buffer +// +{ + _ASSERTE( NULL != pszBuffer ); + _ASSERTE( NULL != pcb ); + + HRESULT hr = S_OK; + DWORD cbNeeded = QueryCB() + sizeof( WCHAR ); + + if( *pcb < cbNeeded ) + { + hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ); + goto Finished; + } + + // + // BUGBUG: StringCchCopy? + // + memcpy( pszBuffer, QueryStr(), cbNeeded ); + +Finished: + + *pcb = cbNeeded; + + return hr; +} + +HRESULT +STRU::SetLen( + __in DWORD cchLen +) +/*++ + * +Routine Description: + + Set the length of the string and null terminate, if there + is sufficient buffer already allocated. Will not reallocate. + +Arguments: + + cchLen - The number of characters in the new string. + +Return Value: + + HRESULT + +--*/ +{ + if( cchLen >= QuerySizeCCH() ) + { + return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + } + + *( QueryStr() + cchLen ) = L'\0'; + m_cchLen = cchLen; + + return S_OK; +} + +HRESULT +STRU::SafeSnwprintf( + __in PCWSTR pwszFormatString, + ... +) +/*++ + +Routine Description: + + Writes to a STRU, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pwszFormatString - printf format + ... - printf args + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + va_list argsList; + va_start( argsList, pwszFormatString ); + + hr = SafeVsnwprintf(pwszFormatString, argsList); + + va_end( argsList ); + return hr; +} + +HRESULT +STRU::SafeVsnwprintf( + __in PCWSTR pwszFormatString, + va_list argsList +) +/*++ + +Routine Description: + + Writes to a STRU, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pwszFormatString - printf format + argsList - printf va_list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + int cchOutput; + int cchNeeded; + + // + // Format the incoming message using vsnprintf() + // so that the overflows are captured + // + cchOutput = _vsnwprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pwszFormatString, + argsList + ); + + if( cchOutput == -1 ) + { + // + // Couldn't fit this in the original STRU size. + // + cchNeeded = _vscwprintf( pwszFormatString, argsList ); + if( cchNeeded > 64 * 1024 ) + { + // + // If we're trying to produce a string > 64k chars, then + // there is probably a problem + // + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + + // + // _vscprintf doesn't include terminating null character + // + cchNeeded++; + + hr = Resize( cchNeeded ); + if( FAILED( hr ) ) + { + goto Finished; + } + + cchOutput = _vsnwprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pwszFormatString, + argsList + ); + if( -1 == cchOutput ) + { + // + // This should never happen, cause we should already have correctly sized memory + // + _ASSERTE( FALSE ); + + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + } + + // + // always null terminate at the last WCHAR + // + QueryStr()[ QuerySizeCCH() - 1 ] = L'\0'; + + // + // we directly touched the buffer - therefore: + // + hr = SyncWithBuffer(); + if ( FAILED( hr ) ) + { + goto Finished; + } + +Finished: + + if( FAILED( hr ) ) + { + Reset(); + } + + return hr; +} + +HRESULT +STRU::AuxAppend( + __in_ecount(cNumStrings) + PCWSTR const rgpszStrings[], + SIZE_T cNumStrings +) +/*++ + +Routine Description: + + Appends an array of strings of length cNumStrings + +Arguments: + + rgStrings - The array of strings to be appened + cNumStrings - The count of String + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + size_t cbStringsTotal = sizeof( WCHAR ); // Account for null-terminator + + // + // Compute total size of the string. + // Resize internal buffer + // Copy each array element one by one to backing buffer + // Update backing buffer string length + // + for ( SIZE_T i = 0; i < cNumStrings; i++ ) + { + _ASSERTE( rgpszStrings[ i ] != NULL ); + if ( NULL == rgpszStrings[ i ] ) + { + return E_INVALIDARG; + } + + size_t cbString = 0; + + hr = StringCbLengthW( rgpszStrings[ i ], + STRSAFE_MAX_CCH * sizeof( WCHAR ), + &cbString ); + if ( FAILED( hr ) ) + { + return hr; + } + + cbStringsTotal += cbString; + + if ( cbStringsTotal > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + } + + size_t cbBufSizeRequired = QueryCB() + cbStringsTotal; + if ( cbBufSizeRequired > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( m_Buff.QuerySize() < cbBufSizeRequired ) + { + if( !m_Buff.Resize( cbBufSizeRequired ) ) + { + return E_OUTOFMEMORY; + } + } + + STRSAFE_LPWSTR pszStringEnd = QueryStr() + QueryCCH(); + size_t cchRemaining = QuerySizeCCH() - QueryCCH(); + for ( SIZE_T i = 0; i < cNumStrings; i++ ) + { + hr = StringCchCopyExW( pszStringEnd, // pszDest + cchRemaining, // cchDest + rgpszStrings[ i ], // pszSrc + &pszStringEnd, // ppszDestEnd + &cchRemaining, // pcchRemaining + 0 ); // dwFlags + if ( FAILED( hr ) ) + { + _ASSERTE( FALSE ); + HRESULT hr2 = SyncWithBuffer(); + if ( FAILED( hr2 ) ) + { + return hr2; + } + return hr; + } + } + + m_cchLen = static_cast< DWORD >( cbBufSizeRequired ) / sizeof( WCHAR ) - 1; + + return S_OK; +} + +HRESULT +STRU::AuxAppend( + __in_bcount(cbStr) + const WCHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset +) +/*++ + +Routine Description: + + Appends to the string starting at the (byte) offset cbOffset. + +Arguments: + + pStr - A unicode string to be appended + cbStr - Length, in bytes, of pStr + cbOffset - Offset, in bytes, at which to begin the append + +Return Value: + + HRESULT + +--*/ +{ + _ASSERTE( NULL != pStr ); + _ASSERTE( 0 == cbStr % sizeof( WCHAR ) ); + _ASSERTE( cbOffset <= QueryCB() ); + _ASSERTE( 0 == cbOffset % sizeof( WCHAR ) ); + + ULONGLONG cb64NewSize = (ULONGLONG)cbOffset + cbStr + sizeof( WCHAR ); + if( cb64NewSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( m_Buff.QuerySize() < cb64NewSize ) + { + if( !m_Buff.Resize( static_cast(cb64NewSize) ) ) + { + return E_OUTOFMEMORY; + } + } + + memcpy( reinterpret_cast(m_Buff.QueryPtr()) + cbOffset, pStr, cbStr ); + + m_cchLen = (static_cast(cbStr) + cbOffset) / sizeof(WCHAR); + + *( QueryStr() + m_cchLen ) = L'\0'; + + return S_OK; +} + +HRESULT +STRU::AuxAppendA( + __in_bcount(cbStr) + const CHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset, + UINT CodePage +) +/*++ + +Routine Description: + + Convert and append an ANSI string to the string starting at + the (byte) offset cbOffset + +Arguments: + + pStr - An ANSI string to be appended + cbStr - Length, in bytes, of pStr + cbOffset - Offset, in bytes, at which to begin the append + CodePage - code page to use for conversion + +Return Value: + + HRESULT + +--*/ +{ + WCHAR* pszBuffer; + DWORD cchBuffer; + DWORD cchCharsCopied = 0; + + _ASSERTE( NULL != pStr ); + _ASSERTE( cbOffset <= QueryCB() ); + _ASSERTE( 0 == cbOffset % sizeof( WCHAR ) ); + + if ( NULL == pStr ) + { + return E_INVALIDARG; + } + + if( 0 == cbStr ) + { + return S_OK; + } + + // + // Only resize when we have to. When we do resize, we tack on + // some extra space to avoid extra reallocations. + // + if( m_Buff.QuerySize() < (ULONGLONG)cbOffset + (cbStr * sizeof( WCHAR )) + sizeof(WCHAR) ) + { + ULONGLONG cb64NewSize = (ULONGLONG)( cbOffset + cbStr * sizeof(WCHAR) + sizeof( WCHAR ) ); + + // + // Check for the arithmetic overflow + // + if( cb64NewSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( !m_Buff.Resize( static_cast(cb64NewSize) ) ) + { + return E_OUTOFMEMORY; + } + } + + pszBuffer = reinterpret_cast(reinterpret_cast(m_Buff.QueryPtr()) + cbOffset); + cchBuffer = ( m_Buff.QuerySize() - cbOffset - sizeof( WCHAR ) ) / sizeof( WCHAR ); + + cchCharsCopied = MultiByteToWideChar( + CodePage, + MB_ERR_INVALID_CHARS, + pStr, + static_cast(cbStr), + pszBuffer, + cchBuffer + ); + if( 0 == cchCharsCopied ) + { + return HRESULT_FROM_WIN32( GetLastError() ); + } + + // + // set the new length + // + m_cchLen = cchCharsCopied + cbOffset/sizeof(WCHAR); + + // + // Must be less than, cause still need to add NULL + // + _ASSERTE( m_cchLen < QuerySizeCCH() ); + + // + // append NULL character + // + *(QueryStr() + m_cchLen) = L'\0'; + + return S_OK; +} + + +/*++ + +Routine Description: + + Removes leading and trailing whitespace + +--*/ + +VOID +STRU::Trim() +{ + PWSTR pwszString = QueryStr(); + DWORD cchNewLength = m_cchLen; + DWORD cchLeadingWhitespace = 0; + DWORD cchTempLength = 0; + + for (LONG ixString = m_cchLen - 1; ixString >= 0; ixString--) + { + if (iswspace(pwszString[ixString]) != 0) + { + pwszString[ixString] = L'\0'; + cchNewLength--; + } + else + { + break; + } + } + + cchTempLength = cchNewLength; + for (DWORD ixString = 0; ixString < cchTempLength; ixString++) + { + if (iswspace(pwszString[ixString]) != 0) + { + cchLeadingWhitespace++; + cchNewLength--; + } + else + { + break; + } + } + + if (cchNewLength == 0) + { + + Reset(); + } + else if (cchLeadingWhitespace > 0) + { + memmove(pwszString, pwszString + cchLeadingWhitespace, cchNewLength * sizeof(WCHAR)); + pwszString[cchNewLength] = L'\0'; + } + + SyncWithBuffer(); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + pwszPrefix - wide char string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ + +BOOL +STRU::StartsWith( + __in PCWSTR pwszPrefix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + BOOL fMatch = FALSE; + size_t cchPrefix = 0; + + if (pwszPrefix == NULL) + { + goto Finished; + } + + hr = StringCchLengthW( pwszPrefix, + STRSAFE_MAX_CCH, + &cchPrefix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchPrefix <= MAXDWORD ); + + if (cchPrefix > m_cchLen) + { + goto Finished; + } + + #if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + fMatch = ( CSTR_EQUAL == CompareStringOrdinal( QueryStr(), + cchPrefix, + pwszPrefix, + cchPrefix, + fIgnoreCase ) ); + #else + + if( fIgnoreCase ) + { + fMatch = ( 0 == _wcsnicmp( QueryStr(), pwszPrefix, cchPrefix ) ); + } + else + { + fMatch = ( 0 == wcsncmp( QueryStr(), pwszPrefix, cchPrefix ) ); + } + + #endif + +Finished: + + return fMatch; +} + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + pwszSuffix - wide char string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ + + +BOOL +STRU::EndsWith( + __in PCWSTR pwszSuffix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + PWSTR pwszString = QueryStr(); + BOOL fMatch = FALSE; + size_t cchSuffix = 0; + ptrdiff_t ixOffset = 0; + + if (pwszSuffix == NULL) + { + goto Finished; + } + + hr = StringCchLengthW( pwszSuffix, + STRSAFE_MAX_CCH, + &cchSuffix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchSuffix <= MAXDWORD ); + + if (cchSuffix > m_cchLen) + { + goto Finished; + } + + ixOffset = m_cchLen - cchSuffix; + _ASSERTE(ixOffset >= 0 && ixOffset <= MAXDWORD); + + #if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + fMatch = ( CSTR_EQUAL == CompareStringOrdinal( pwszString + ixOffset, + cchSuffix, + pwszSuffix, + cchSuffix, + fIgnoreCase ) ); + #else + + if( fIgnoreCase ) + { + fMatch = ( 0 == _wcsnicmp( pwszString + ixOffset, pwszSuffix, cchSuffix ) ); + } + else + { + fMatch = ( 0 == wcsncmp( pwszString + ixOffset, pwszSuffix, cchSuffix ) ); + } + + #endif + +Finished: + + return fMatch; +} + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - the initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRU::IndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const WCHAR* pwChar = wcschr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pwChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pwChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified substring. + +Arguments: + + pwszValue - substring to find + dwStartIndex - initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRU::IndexOf( + __in PCWSTR pwszValue, + __in DWORD dwStartIndex + ) const +{ + HRESULT hr = S_OK; + INT nIndex = -1; + SIZE_T cchValue = 0; + + // Validate input parameters + if( dwStartIndex >= QueryCCH() || !pwszValue ) + { + goto Finished; + } + + const WCHAR* pwChar = wcsstr( QueryStr() + dwStartIndex, pwszValue ); + + // Determine the index if found + if( pwChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pwChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the last occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - initial index. + +Return Value: + + The index for the last character occurence in the string. + + -1 if not found. + +--*/ +INT +STRU::LastIndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const WCHAR* pwChar = wcsrchr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pwChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pwChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + +//static +HRESULT +STRU::ExpandEnvironmentVariables( + __in PCWSTR pszString, + __out STRU * pstrExpandedString + ) +/*++ + +Routine Description: + + Expand the environment variables in a string + +Arguments: + + pszString - String with environment variables to expand + pstrExpandedString - Receives expanded string on success + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + DWORD cchNewSize = 0; + + if ( pszString == NULL || + pstrExpandedString == NULL ) + { + DBG_ASSERT( FALSE ); + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Exit; + } + + cchNewSize = ExpandEnvironmentStrings( pszString, + pstrExpandedString->QueryStr(), + pstrExpandedString->QuerySizeCCH() ); + if ( cchNewSize == 0 ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Exit; + } + + if ( cchNewSize > pstrExpandedString->QuerySizeCCH() ) + { + hr = pstrExpandedString->Resize( + ( cchNewSize + 1 ) * sizeof( WCHAR ) + ); + if ( FAILED( hr ) ) + { + goto Exit; + } + + cchNewSize = ExpandEnvironmentStrings( + pszString, + pstrExpandedString->QueryStr(), + pstrExpandedString->QuerySizeCCH() + ); + + if ( cchNewSize == 0 || + cchNewSize > pstrExpandedString->QuerySizeCCH() ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Exit; + } + } + + pstrExpandedString->SyncWithBuffer(); + + hr = S_OK; + +Exit: + + return hr; +} + +#pragma warning(default:4267) diff --git a/src/IISLib/stringu.h b/src/IISLib/stringu.h new file mode 100644 index 0000000000..6f27c5421d --- /dev/null +++ b/src/IISLib/stringu.h @@ -0,0 +1,427 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "buffer.h" +#include + +class STRU +{ + +public: + + STRU( + VOID + ); + + STRU( + __inout_ecount(cchInit) WCHAR* pbInit, + __in DWORD cchInit + ); + + BOOL + IsEmpty( + VOID + ) const; + + BOOL + Equals( + __in const STRU * pstrRhs, + __in BOOL fIgnoreCase = FALSE + ) const + { + _ASSERTE( pstrRhs != NULL ); + return Equals( pstrRhs->QueryStr(), fIgnoreCase ); + } + + BOOL + Equals( + __in const STRU & strRhs, + __in BOOL fIgnoreCase = FALSE + ) const + { + return Equals( strRhs.QueryStr(), fIgnoreCase ); + } + + BOOL + Equals( + __in PCWSTR pszRhs, + __in BOOL fIgnoreCase = FALSE + ) const + { + _ASSERTE( NULL != pszRhs ); + if ( NULL == pszRhs ) + { + return FALSE; + } + + #if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + return ( CSTR_EQUAL == CompareStringOrdinal( QueryStr(), + QueryCCH(), + pszRhs, + -1, + fIgnoreCase ) ); + #else + + if( fIgnoreCase ) + { + return ( 0 == _wcsicmp( QueryStr(), pszRhs ) ); + } + return ( 0 == wcscmp( QueryStr(), pszRhs ) ); + + #endif + } + + + static + BOOL + Equals( + __in PCWSTR pwszLhs, + __in PCWSTR pwszRhs, + __in bool fIgnoreCase = false + ) + { + // Return FALSE if either or both strings are NULL. + if (!pwszLhs || !pwszRhs) return FALSE; + + // + // This method performs a ordinal string comparison when OS is Vista or + // greater and a culture sensitive comparison if not (XP). This is + // consistent with the existing Equals implementation (see above). + // +#if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + return ( CSTR_EQUAL == CompareStringOrdinal( pwszLhs, + -1, + pwszRhs, + -1, + fIgnoreCase ) ); +#else + + if( fIgnoreCase ) + { + return ( 0 == _wcsicmp( pwszLhs, pwszRhs ) ); + } + else + { + return ( 0 == wcscmp( pwszLhs, pwszRhs ) ); + } + +#endif + } + + VOID + Trim(); + + BOOL + StartsWith( + __in const STRU * pStruPrefix, + __in bool fIgnoreCase = FALSE + ) const + { + _ASSERTE( pStruPrefix != NULL ); + return StartsWith( pStruPrefix->QueryStr(), fIgnoreCase ); + } + + BOOL + StartsWith( + __in const STRU & struPrefix, + __in bool fIgnoreCase = FALSE + ) const + { + return StartsWith( struPrefix.QueryStr(), fIgnoreCase ); + } + + BOOL + StartsWith( + __in PCWSTR pwszPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in const STRU * pStruSuffix, + __in bool fIgnoreCase = FALSE + ) const + { + _ASSERTE( pStruSuffix != NULL ); + return EndsWith( pStruSuffix->QueryStr(), fIgnoreCase ); + } + + BOOL + EndsWith( + __in const STRU & struSuffix, + __in bool fIgnoreCase = FALSE + ) const + { + return EndsWith( struSuffix.QueryStr(), fIgnoreCase ); + } + + BOOL + EndsWith( + __in PCWSTR pwszSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + INT + IndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + IndexOf( + __in PCWSTR pwszValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + LastIndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + DWORD + QueryCB( + VOID + ) const; + + DWORD + QueryCCH( + VOID + ) const; + + DWORD + QuerySizeCCH( + VOID + ) const; + + __nullterminated + __ecount(this->m_cchLen) + WCHAR* + QueryStr( + VOID + ) const; + + VOID + Reset( + VOID + ); + + HRESULT + Resize( + DWORD cchSize + ); + + HRESULT + SyncWithBuffer( + VOID + ); + + template + HRESULT + Copy( + __in PCWSTR const (&rgpszStrings)[size] + ) + // + // Copies an array of strings declared as stack array. For example: + // + // LPCWSTR rgExample[] { L"one", L"two" }; + // hr = str.Copy( rgExample ); + // + { + Reset(); + + return AuxAppend( rgpszStrings, _countof( rgpszStrings ) ); + } + + HRESULT + Copy( + __in PCWSTR pszCopy + ); + + HRESULT + Copy( + __in_ecount(cchLen) + PCWSTR pszCopy, + SIZE_T cchLen + ); + + HRESULT + Copy( + __in const STRU * pstrRhs + ); + + HRESULT + Copy( + __in const STRU & str + ); + + HRESULT + CopyAndExpandEnvironmentStrings( + __in PCWSTR pszSource + ); + + HRESULT + CopyA( + __in PCSTR pszCopyA + ); + + HRESULT + CopyA( + __in_bcount(cchLen) + PCSTR pszCopyA, + SIZE_T cchLen, + UINT CodePage = CP_UTF8 + ); + + template + HRESULT + Append( + __in PCWSTR const (&rgpszStrings)[size] + ) + // + // Appends an array of strings declared as stack array. For example: + // + // LPCWSTR rgExample[] { L"one", L"two" }; + // hr = str.Append( rgExample ); + // + { + return AuxAppend( rgpszStrings, _countof( rgpszStrings ) ); + } + + HRESULT + Append( + __in PCWSTR pszAppend + ); + + HRESULT + Append( + __in_ecount(cchLen) + PCWSTR pszAppend, + SIZE_T cchLen + ); + + HRESULT + Append( + __in const STRU * pstrRhs + ); + + HRESULT + Append( + __in const STRU & strRhs + ); + + HRESULT + AppendA( + __in PCSTR pszAppendA + ); + + HRESULT + AppendA( + __in_bcount(cchLen) + PCSTR pszAppendA, + SIZE_T cchLen, + UINT CodePage = CP_UTF8 + ); + + HRESULT + CopyToBuffer( + __out_bcount(*pcb) WCHAR* pszBuffer, + PDWORD pcb + ) const; + + HRESULT + SetLen( + __in DWORD cchLen + ); + + HRESULT + SafeSnwprintf( + __in PCWSTR pwszFormatString, + ... + ); + + HRESULT + SafeVsnwprintf( + __in PCWSTR pwszFormatString, + va_list argsList + ); + + static + HRESULT ExpandEnvironmentVariables( + __in PCWSTR pszString, + __out STRU * pstrExpandedString + ); + +private: + + // + // Avoid C++ errors. This object should never go through a copy + // constructor, unintended cast or assignment. + // + STRU( const STRU & ); + STRU & operator = ( const STRU & ); + + HRESULT + AuxAppend( + __in_ecount(cNumStrings) + PCWSTR const rgpszStrings[], + SIZE_T cNumStrings + ); + + HRESULT + AuxAppend( + __in_bcount(cbStr) + const WCHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset + ); + + HRESULT + AuxAppendA( + __in_bcount(cbStr) + const CHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset, + UINT CodePage + ); + + // + // Buffer with an inline buffer of 1, + // enough to hold null-terminating character. + // + BUFFER_T m_Buff; + DWORD m_cchLen; +}; + +// +// Helps to initialize an external buffer before +// constructing the STRU object. +// +template +WCHAR* InitHelper(__out WCHAR (&psz)[size]) +{ + psz[0] = L'\0'; + return psz; +} + +// +// Heap operation reduction macros +// +#define STACK_STRU(name, size) WCHAR __ach##name[size];\ + STRU name(InitHelper(__ach##name), sizeof(__ach##name)/sizeof(*__ach##name)) + +#define INLINE_STRU(name, size) WCHAR __ach##name[size];\ + STRU name; + +#define INLINE_STRU_INIT(name) name(InitHelper(__ach##name), sizeof(__ach##name)/sizeof(*__ach##name)) + + +HRESULT +MakePathCanonicalizationProof( + IN PCWSTR pszName, + OUT STRU * pstrPath +); diff --git a/src/IISLib/tracelog.c b/src/IISLib/tracelog.c new file mode 100644 index 0000000000..f7b2da5e43 --- /dev/null +++ b/src/IISLib/tracelog.c @@ -0,0 +1,235 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include "pudebug.h" +#include "tracelog.h" +#include + + +#define ALLOC_MEM(cb) (PVOID)LocalAlloc( LPTR, (cb) ) +#define FREE_MEM(ptr) (VOID)LocalFree( (HLOCAL)(ptr) ) + + + +PTRACE_LOG +CreateTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader, + IN LONG EntrySize + ) +/*++ + +Routine Description: + + Creates a new (empty) trace log buffer. + +Arguments: + + LogSize - The number of entries in the log. + + ExtraBytesInHeader - The number of extra bytes to include in the + log header. This is useful for adding application-specific + data to the log. + + EntrySize - The size (in bytes) of each entry. + +Return Value: + + PTRACE_LOG - Pointer to the newly created log if successful, + NULL otherwise. + +--*/ +{ + + ULONG ulTotalSize = 0; + ULONG ulLogSize = 0; + ULONG ulEntrySize = 0; + ULONG ulTmpResult = 0; + ULONG ulExtraBytesInHeader = 0; + PTRACE_LOG log = NULL; + HRESULT hr = S_OK; + + // + // Sanity check the parameters. + // + + //DBG_ASSERT( LogSize > 0 ); + //DBG_ASSERT( EntrySize > 0 ); + //DBG_ASSERT( ExtraBytesInHeader >= 0 ); + //DBG_ASSERT( ( EntrySize & 3 ) == 0 ); + + // + // converting to unsigned long. Since all these values are positive + // so its safe to cast them to their unsigned equivalent directly. + // + ulLogSize = (ULONG) LogSize; + ulEntrySize = (ULONG) EntrySize; + ulExtraBytesInHeader = (ULONG) ExtraBytesInHeader; + + // + // Check if the multiplication operation will overflow a LONG + // ulTotalSize = LogSize * EntrySize; + // + hr = ULongMult( ulLogSize, ulEntrySize, &ulTotalSize ); + if ( FAILED(hr) ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + // + // check for overflow in addition operation. + // ulTmpResult = sizeof(TRACE_LOG) + ulExtraBytesInHeader + // + hr = ULongAdd( (ULONG) sizeof(TRACE_LOG), ulExtraBytesInHeader, &ulTmpResult ); + if ( FAILED(hr) ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + // + // check for overflow in addition operation. + // ulTotalSize = ulTotalSize + ulTmpResult; + // + hr = ULongAdd( ulTmpResult, ulTotalSize, &ulTotalSize ); + if ( FAILED(hr) ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + if ( ulTotalSize > (ULONG) 0x7FFFFFFF ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + // + // Allocate & initialize the log structure. + // + + log = (PTRACE_LOG)ALLOC_MEM( ulTotalSize ); + + // + // Initialize it. + // + + if( log != NULL ) { + + RtlZeroMemory( log, ulTotalSize ); + + log->Signature = TRACE_LOG_SIGNATURE; + log->LogSize = LogSize; + log->NextEntry = -1; + log->EntrySize = EntrySize; + log->LogBuffer = (PUCHAR)( log + 1 ) + ExtraBytesInHeader; + } + + return log; + +} // CreateTraceLog + + +VOID +DestroyTraceLog( + IN PTRACE_LOG Log + ) +/*++ + +Routine Description: + + Destroys a trace log buffer created with CreateTraceLog(). + +Arguments: + + Log - The trace log buffer to destroy. + +Return Value: + + None. + +--*/ +{ + if ( Log != NULL ) { + //DBG_ASSERT( Log->Signature == TRACE_LOG_SIGNATURE ); + + Log->Signature = TRACE_LOG_SIGNATURE_X; + FREE_MEM( Log ); + } + +} // DestroyTraceLog + + +LONG +WriteTraceLog( + IN PTRACE_LOG Log, + IN PVOID Entry + ) +/*++ + +Routine Description: + + Writes a new entry to the specified trace log. + +Arguments: + + Log - The log to write to. + + Entry - Pointer to the data to write. This buffer is assumed to be + Log->EntrySize bytes long. + +Return Value: + + Index of entry in log. This is useful for correlating the output + of !inetdbg.ref to a particular point in the output debug stream + +--*/ +{ + + PUCHAR target; + ULONG index; + + //DBG_ASSERT( Log != NULL ); + //DBG_ASSERT( Log->Signature == TRACE_LOG_SIGNATURE ); + //DBG_ASSERT( Entry != NULL ); + + // + // Find the next slot, copy the entry to the slot. + // + + index = ( (ULONG) InterlockedIncrement( &Log->NextEntry ) ) % (ULONG) Log->LogSize; + + //DBG_ASSERT( index < (ULONG) Log->LogSize ); + + target = Log->LogBuffer + ( index * Log->EntrySize ); + + RtlCopyMemory( + target, + Entry, + Log->EntrySize + ); + + return index; +} // WriteTraceLog + + +VOID +ResetTraceLog( + IN PTRACE_LOG Log + ) +{ + + //DBG_ASSERT( Log != NULL ); + //DBG_ASSERT( Log->Signature == TRACE_LOG_SIGNATURE ); + + RtlZeroMemory( + ( Log + 1 ), + Log->LogSize * Log->EntrySize + ); + + Log->NextEntry = -1; + +} // ResetTraceLog + diff --git a/src/IISLib/tracelog.h b/src/IISLib/tracelog.h new file mode 100644 index 0000000000..ed34bcffc9 --- /dev/null +++ b/src/IISLib/tracelog.h @@ -0,0 +1,105 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _TRACELOG_H_ +#define _TRACELOG_H_ + + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus + + +typedef struct _TRACE_LOG { + + // + // Signature. + // + + LONG Signature; + + // + // The total number of entries available in the log. + // + + LONG LogSize; + + // + // The index of the next entry to use. + // + + LONG NextEntry; + + // + // The byte size of each entry. + // + + LONG EntrySize; + + // + // Pointer to the start of the circular buffer. + // + + PUCHAR LogBuffer; + + // + // The extra header bytes and actual log entries go here. + // + // BYTE ExtraHeaderBytes[ExtraBytesInHeader]; + // BYTE Entries[LogSize][EntrySize]; + // + +} TRACE_LOG, *PTRACE_LOG; + + +// +// Log header signature. +// + +#define TRACE_LOG_SIGNATURE ((DWORD)'gOlT') +#define TRACE_LOG_SIGNATURE_X ((DWORD)'golX') + + +// +// This macro maps a TRACE_LOG pointer to a pointer to the 'extra' +// data associated with the log. +// + +#define TRACE_LOG_TO_EXTRA_DATA(log) (PVOID)( (log) + 1 ) + + +// +// Manipulators. +// + +PTRACE_LOG +CreateTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader, + IN LONG EntrySize + ); + +VOID +DestroyTraceLog( + IN PTRACE_LOG Log + ); + +LONG +WriteTraceLog( + IN PTRACE_LOG Log, + IN PVOID Entry + ); + +VOID +ResetTraceLog( + IN PTRACE_LOG Log + ); + + +#if defined(__cplusplus) +} // extern "C" +#endif // __cplusplus + + +#endif // _TRACELOG_H_ + diff --git a/src/IISLib/treehash.h b/src/IISLib/treehash.h new file mode 100644 index 0000000000..baa50726ce --- /dev/null +++ b/src/IISLib/treehash.h @@ -0,0 +1,850 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "rwlock.h" +#include "prime.h" + +template +class TREE_HASH_NODE +{ + template + friend class TREE_HASH_TABLE; + + private: + // Next node in the hash table look-aside + TREE_HASH_NODE<_Record> *_pNext; + + // links in the tree structure + TREE_HASH_NODE * _pParentNode; + TREE_HASH_NODE * _pFirstChild; + TREE_HASH_NODE * _pNextSibling; + + // actual record + _Record * _pRecord; + + // hash value + PCWSTR _pszPath; + DWORD _dwHash; +}; + +template +class TREE_HASH_TABLE +{ +protected: + typedef BOOL + (PFN_DELETE_IF)( + _Record * pRecord, + PVOID pvContext + ); + + typedef VOID + (PFN_APPLY)( + _Record * pRecord, + PVOID pvContext + ); + +public: + TREE_HASH_TABLE( + BOOL fCaseSensitive + ) : _ppBuckets( NULL ), + _nBuckets( 0 ), + _nItems( 0 ), + _fCaseSensitive( fCaseSensitive ) + { + } + + virtual + ~TREE_HASH_TABLE(); + + virtual + VOID + ReferenceRecord( + _Record * pRecord + ) = 0; + + virtual + VOID + DereferenceRecord( + _Record * pRecord + ) = 0; + + virtual + PCWSTR + GetKey( + _Record * pRecord + ) = 0; + + DWORD + Count() + { + return _nItems; + } + + virtual + VOID + Clear(); + + HRESULT + Initialize( + DWORD nBucketSize + ); + + DWORD + CalcHash( + PCWSTR pszKey + ) + { + return _fCaseSensitive ? HashString(pszKey) : HashStringNoCase(pszKey); + } + + virtual + VOID + FindKey( + PCWSTR pszKey, + _Record ** ppRecord + ); + + virtual + HRESULT + InsertRecord( + _Record * pRecord + ); + + virtual + VOID + DeleteKey( + PCWSTR pszKey + ); + + virtual + VOID + DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext + ); + + VOID + Apply( + PFN_APPLY pfnApply, + PVOID pvContext + ); + +private: + + BOOL + FindNodeInternal( + PCWSTR pszKey, + DWORD dwHash, + TREE_HASH_NODE<_Record> ** ppNode, + TREE_HASH_NODE<_Record> *** pppPreviousNodeNextPointer = NULL + ); + + HRESULT + AddNodeInternal( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode + ); + + HRESULT + AllocateNode( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode + ); + + VOID + DeleteNode( + TREE_HASH_NODE<_Record> * pNode + ) + { + if (pNode->_pRecord != NULL) + { + DereferenceRecord(pNode->_pRecord); + pNode->_pRecord = NULL; + } + + HeapFree(GetProcessHeap(), + 0, + pNode); + } + + VOID + DeleteNodeInternal( + TREE_HASH_NODE<_Record> ** ppPreviousNodeNextPointer, + TREE_HASH_NODE<_Record> * pNode + ); + + VOID + RehashTableIfNeeded( + VOID + ); + + TREE_HASH_NODE<_Record> ** _ppBuckets; + DWORD _nBuckets; + DWORD _nItems; + BOOL _fCaseSensitive; + CWSDRWLock _tableLock; +}; + +template +HRESULT +TREE_HASH_TABLE<_Record>::AllocateNode( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode +) +{ + // + // Allocate enough extra space for pszPath + // + DWORD cchPath = (DWORD) wcslen(pszPath); + if (cchPath >= ((0xffffffff - sizeof(TREE_HASH_NODE<_Record>))/sizeof(WCHAR) - 1)) + { + return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + TREE_HASH_NODE<_Record> *pNode = (TREE_HASH_NODE<_Record> *)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + sizeof(TREE_HASH_NODE<_Record>) + (cchPath+1)*sizeof(WCHAR)); + if (pNode == NULL) + { + return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + + memcpy(pNode+1, pszPath, (cchPath+1)*sizeof(WCHAR)); + pNode->_pszPath = (PCWSTR)(pNode+1); + pNode->_dwHash = dwHash; + pNode->_pNext = pNode->_pNextSibling = pNode->_pFirstChild = NULL; + pNode->_pParentNode = pParentNode; + pNode->_pRecord = pRecord; + + *ppNewNode = pNode; + return S_OK; +} + +template +HRESULT +TREE_HASH_TABLE<_Record>::Initialize( + DWORD nBuckets +) +{ + HRESULT hr = S_OK; + + if ( nBuckets == 0 ) + { + hr = E_INVALIDARG; + goto Failed; + } + + hr = _tableLock.Init(); + if ( FAILED( hr ) ) + { + goto Failed; + } + + if (nBuckets >= 0xffffffff/sizeof(TREE_HASH_NODE<_Record> *)) + { + hr = E_INVALIDARG; + goto Failed; + } + + _ppBuckets = (TREE_HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(TREE_HASH_NODE<_Record> *)); + if (_ppBuckets == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Failed; + } + _nBuckets = nBuckets; + + return S_OK; + +Failed: + + if (_ppBuckets) + { + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + } + + return hr; +} + + +template +TREE_HASH_TABLE<_Record>::~TREE_HASH_TABLE() +{ + if (_ppBuckets == NULL) + { + return; + } + + _ASSERTE(_nItems == 0); + + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + _nBuckets = 0; +} + +template +VOID +TREE_HASH_TABLE<_Record>::Clear() +{ + TREE_HASH_NODE<_Record> *pCurrent; + TREE_HASH_NODE<_Record> *pNext; + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pCurrent = _ppBuckets[i]; + _ppBuckets[i] = NULL; + while (pCurrent != NULL) + { + pNext = pCurrent->_pNext; + DeleteNode(pCurrent); + pCurrent = pNext; + } + } + + _nItems = 0; + _tableLock.ExclusiveRelease(); +} + +template +BOOL +TREE_HASH_TABLE<_Record>::FindNodeInternal( + PCWSTR pszKey, + DWORD dwHash, + TREE_HASH_NODE<_Record> ** ppNode, + TREE_HASH_NODE<_Record> *** pppPreviousNodeNextPointer +) +/*++ + Return value indicates whether the item is found + key, dwHash - key and hash for the node to find + ppNode - on successful return, the node found, on failed return, the first + node with hash value greater than the node to be found + pppPreviousNodeNextPointer - the pointer to previous node's _pNext + + This routine may be called under either read or write lock +--*/ +{ + TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer; + TREE_HASH_NODE<_Record> *pNode; + BOOL fFound = FALSE; + + ppPreviousNodeNextPointer = _ppBuckets + (dwHash % _nBuckets); + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + if (pNode->_dwHash == dwHash) + { + if (CompareStringOrdinal(pszKey, + -1, + pNode->_pszPath, + -1, + !_fCaseSensitive) == CSTR_EQUAL) + { + fFound = TRUE; + break; + } + } + else if (pNode->_dwHash > dwHash) + { + break; + } + + ppPreviousNodeNextPointer = &(pNode->_pNext); + pNode = *ppPreviousNodeNextPointer; + } + + *ppNode = pNode; + if (pppPreviousNodeNextPointer != NULL) + { + *pppPreviousNodeNextPointer = ppPreviousNodeNextPointer; + } + return fFound; +} + +template +VOID +TREE_HASH_TABLE<_Record>::FindKey( + PCWSTR pszKey, + _Record ** ppRecord +) +{ + TREE_HASH_NODE<_Record> *pNode; + + *ppRecord = NULL; + + DWORD dwHash = CalcHash(pszKey); + + _tableLock.SharedAcquire(); + + if (FindNodeInternal(pszKey, dwHash, &pNode) && + pNode->_pRecord != NULL) + { + ReferenceRecord(pNode->_pRecord); + *ppRecord = pNode->_pRecord; + } + + _tableLock.SharedRelease(); +} + +template +HRESULT +TREE_HASH_TABLE<_Record>::AddNodeInternal( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode +) +/*++ + Return value is HRESULT indicating sucess or failure + pszPath, dwHash, pRecord - path, hash value and record to be inserted + pParentNode - this will be the parent of the node being inserted + ppNewNode - on successful return, the new node created and inserted + + This function may be called under a read or write lock +--*/ +{ + TREE_HASH_NODE<_Record> *pNewNode; + TREE_HASH_NODE<_Record> *pNextNode; + TREE_HASH_NODE<_Record> **ppNextPointer; + HRESULT hr; + + // + // Ownership of pRecord is not transferred to pNewNode yet, so remember + // to either set it to null before deleting pNewNode or add an extra + // reference later - this is to make sure we do not do an extra ref/deref + // which users may view as getting flushed out of the hash-table + // + hr = AllocateNode(pszPath, + dwHash, + pRecord, + pParentNode, + &pNewNode); + if (FAILED(hr)) + { + return hr; + } + + do + { + // + // Find the right place to add this node + // + + if (FindNodeInternal(pszPath, dwHash, &pNextNode, &ppNextPointer)) + { + // + // If node already there, record may still need updating + // + if (pRecord != NULL && + InterlockedCompareExchangePointer((PVOID *)&pNextNode->_pRecord, + pRecord, + NULL) == NULL) + { + ReferenceRecord(pRecord); + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + } + + // ownership of pRecord has either passed to existing record or + // not to anyone at all + pNewNode->_pRecord = NULL; + DeleteNode(pNewNode); + *ppNewNode = pNextNode; + return hr; + } + + // + // If another node got inserted in betwen, we will have to retry + // + pNewNode->_pNext = pNextNode; + } while (InterlockedCompareExchangePointer((PVOID *)ppNextPointer, + pNewNode, + pNextNode) != pNextNode); + // pass ownership of pRecord now + if (pRecord != NULL) + { + ReferenceRecord(pRecord); + pRecord = NULL; + } + InterlockedIncrement((LONG *)&_nItems); + + // + // update the parent + // + if (pParentNode != NULL) + { + ppNextPointer = &pParentNode->_pFirstChild; + do + { + pNextNode = *ppNextPointer; + pNewNode->_pNextSibling = pNextNode; + } while (InterlockedCompareExchangePointer((PVOID *)ppNextPointer, + pNewNode, + pNextNode) != pNextNode); + } + + *ppNewNode = pNewNode; + return S_OK; +} + +template +HRESULT +TREE_HASH_TABLE<_Record>::InsertRecord( + _Record * pRecord +) +/*++ + This method inserts a node for this record and also empty nodes for paths + in the heirarchy leading upto this path + + The insert is done under only a read-lock - this is possible by keeping + the hashes in a bucket in increasing order and using interlocked operations + to actually insert the item in the hash-bucket lookaside list and the parent + children list + + Returns HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) if the record already exists. + Never leak this error to the end user because "*file* already exists" may be confusing. +--*/ +{ + PCWSTR pszKey = GetKey(pRecord); + STACK_STRU( strPartialPath, 256); + PWSTR pszPartialPath; + DWORD dwHash; + DWORD cchEnd; + HRESULT hr; + TREE_HASH_NODE<_Record> *pParentNode = NULL; + + hr = strPartialPath.Copy(pszKey); + if (FAILED(hr)) + { + goto Finished; + } + pszPartialPath = strPartialPath.QueryStr(); + + _tableLock.SharedAcquire(); + + // + // First find the lowest parent node present + // + for (cchEnd = strPartialPath.QueryCCH() - 1; cchEnd > 0; cchEnd--) + { + if (pszPartialPath[cchEnd] == L'/' || pszPartialPath[cchEnd] == L'\\') + { + pszPartialPath[cchEnd] = L'\0'; + + dwHash = CalcHash(pszPartialPath); + if (FindNodeInternal(pszPartialPath, dwHash, &pParentNode)) + { + pszPartialPath[cchEnd] = pszKey[cchEnd]; + break; + } + pParentNode = NULL; + } + } + + // + // Now go ahead and add the rest of the tree (including our record) + // + for (; cchEnd <= strPartialPath.QueryCCH(); cchEnd++) + { + if (pszPartialPath[cchEnd] == L'\0') + { + dwHash = CalcHash(pszPartialPath); + hr = AddNodeInternal( + pszPartialPath, + dwHash, + (cchEnd == strPartialPath.QueryCCH()) ? pRecord : NULL, + pParentNode, + &pParentNode); + if (FAILED(hr) && + hr != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) + { + goto Finished; + } + + pszPartialPath[cchEnd] = pszKey[cchEnd]; + } + } + +Finished: + _tableLock.SharedRelease(); + + if (SUCCEEDED(hr)) + { + RehashTableIfNeeded(); + } + + return hr; +} + +template +VOID +TREE_HASH_TABLE<_Record>::DeleteNodeInternal( + TREE_HASH_NODE<_Record> ** ppNextPointer, + TREE_HASH_NODE<_Record> * pNode +) +/*++ + pNode is the node to be deleted + ppNextPointer is the pointer to the previous node's next pointer pointing + to this node + + This function should be called under write-lock +--*/ +{ + // + // First remove this node from hash table + // + *ppNextPointer = pNode->_pNext; + + // + // Now fixup parent + // + if (pNode->_pParentNode != NULL) + { + ppNextPointer = &pNode->_pParentNode->_pFirstChild; + while (*ppNextPointer != pNode) + { + ppNextPointer = &(*ppNextPointer)->_pNextSibling; + } + *ppNextPointer = pNode->_pNextSibling; + } + + // + // Now remove all children recursively + // + TREE_HASH_NODE<_Record> *pChild = pNode->_pFirstChild; + TREE_HASH_NODE<_Record> *pNextChild; + while (pChild != NULL) + { + pNextChild = pChild->_pNextSibling; + + ppNextPointer = _ppBuckets + (pChild->_dwHash % _nBuckets); + while (*ppNextPointer != pChild) + { + ppNextPointer = &(*ppNextPointer)->_pNext; + } + pChild->_pParentNode = NULL; + DeleteNodeInternal(ppNextPointer, pChild); + + pChild = pNextChild; + } + + DeleteNode(pNode); + _nItems--; +} + +template +VOID +TREE_HASH_TABLE<_Record>::DeleteKey( + PCWSTR pszKey +) +{ + TREE_HASH_NODE<_Record> *pNode; + TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer; + + DWORD dwHash = CalcHash(pszKey); + + _tableLock.ExclusiveAcquire(); + + if (FindNodeInternal(pszKey, dwHash, &pNode, &ppPreviousNodeNextPointer)) + { + DeleteNodeInternal(ppPreviousNodeNextPointer, pNode); + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +TREE_HASH_TABLE<_Record>::DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext +) +{ + TREE_HASH_NODE<_Record> *pNode; + TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer; + BOOL fDelete; + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + ppPreviousNodeNextPointer = _ppBuckets + i; + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + // + // Non empty nodes deleted based on DeleteIf, empty nodes deleted + // if they have no children + // + fDelete = FALSE; + if (pNode->_pRecord != NULL) + { + if (pfnDeleteIf(pNode->_pRecord, pvContext)) + { + fDelete = TRUE; + } + } + else if (pNode->_pFirstChild == NULL) + { + fDelete = TRUE; + } + + if (fDelete) + { + if (pNode->_pFirstChild == NULL) + { + DeleteNodeInternal(ppPreviousNodeNextPointer, pNode); + } + else + { + DereferenceRecord(pNode->_pRecord); + pNode->_pRecord = NULL; + } + } + else + { + ppPreviousNodeNextPointer = &pNode->_pNext; + } + + pNode = *ppPreviousNodeNextPointer; + } + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +TREE_HASH_TABLE<_Record>::Apply( + PFN_APPLY pfnApply, + PVOID pvContext +) +{ + TREE_HASH_NODE<_Record> *pNode; + + _tableLock.SharedAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + if (pNode->_pRecord != NULL) + { + pfnApply(pNode->_pRecord, pvContext); + } + + pNode = pNode->_pNext; + } + } + + _tableLock.SharedRelease(); +} + +template +VOID +TREE_HASH_TABLE<_Record>::RehashTableIfNeeded( + VOID +) +{ + TREE_HASH_NODE<_Record> **ppBuckets; + DWORD nBuckets; + TREE_HASH_NODE<_Record> *pNode; + TREE_HASH_NODE<_Record> *pNextNode; + TREE_HASH_NODE<_Record> **ppNextPointer; + TREE_HASH_NODE<_Record> *pNewNextNode; + DWORD nNewBuckets; + + // + // If number of items has become too many, we will double the hash table + // size (we never reduce it however) + // + if (_nItems <= PRIME::GetPrime(2*_nBuckets)) + { + return; + } + + _tableLock.ExclusiveAcquire(); + + nNewBuckets = PRIME::GetPrime(2*_nBuckets); + + if (_nItems <= nNewBuckets) + { + goto Finished; + } + + nBuckets = nNewBuckets; + if (nBuckets >= 0xffffffff/sizeof(TREE_HASH_NODE<_Record> *)) + { + goto Finished; + } + ppBuckets = (TREE_HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(TREE_HASH_NODE<_Record> *)); + if (ppBuckets == NULL) + { + goto Finished; + } + + // + // Take out nodes from the old hash table and insert in the new one, make + // sure to keep the hashes in increasing order + // + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + pNextNode = pNode->_pNext; + + ppNextPointer = ppBuckets + (pNode->_dwHash % nBuckets); + pNewNextNode = *ppNextPointer; + while (pNewNextNode != NULL && + pNewNextNode->_dwHash <= pNode->_dwHash) + { + ppNextPointer = &pNewNextNode->_pNext; + pNewNextNode = pNewNextNode->_pNext; + } + pNode->_pNext = pNewNextNode; + *ppNextPointer = pNode; + + pNode = pNextNode; + } + } + + HeapFree(GetProcessHeap(), 0, _ppBuckets); + _ppBuckets = ppBuckets; + _nBuckets = nBuckets; + ppBuckets = NULL; + +Finished: + + _tableLock.ExclusiveRelease(); +} + diff --git a/src/IISLib/util.cxx b/src/IISLib/util.cxx new file mode 100644 index 0000000000..bde325025e --- /dev/null +++ b/src/IISLib/util.cxx @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +HRESULT +MakePathCanonicalizationProof( + IN PCWSTR pszName, + OUT STRU * pstrPath +) +/*++ + +Routine Description: + + This functions adds a prefix + to the string, which is "\\?\UNC\" for a UNC path, and "\\?\" for + other paths. This prefix tells Windows not to parse the path. + +Arguments: + + IN pszName - The path to be converted + OUT pstrPath - Output path created + +Return Values: + + HRESULT + +--*/ +{ + HRESULT hr; + + if (pszName[0] == L'\\' && pszName[1] == L'\\') + { + // + // If the path is already canonicalized, just return + // + + if ((pszName[2] == '?' || pszName[2] == '.') && + pszName[3] == '\\') + { + hr = pstrPath->Copy(pszName); + + if (SUCCEEDED(hr)) + { + // + // If the path was in DOS form ("\\.\"), + // we need to change it to Win32 from ("\\?\") + // + + pstrPath->QueryStr()[2] = L'?'; + } + + return hr; + } + + pszName += 2; + + + if (FAILED(hr = pstrPath->Copy(L"\\\\?\\UNC\\"))) + { + return hr; + } + } + else + { + if (FAILED(hr = pstrPath->Copy(L"\\\\?\\"))) + { + return hr; + } + } + + return pstrPath->Append(pszName); +} + diff --git a/src/RequestHandler/RequestHandler.rc b/src/RequestHandler/RequestHandler.rc new file mode 100644 index 0000000000..6d28532915 Binary files /dev/null and b/src/RequestHandler/RequestHandler.rc differ diff --git a/src/RequestHandler/RequestHandler.vcxproj b/src/RequestHandler/RequestHandler.vcxproj new file mode 100644 index 0000000000..d3ffaac1cc --- /dev/null +++ b/src/RequestHandler/RequestHandler.vcxproj @@ -0,0 +1,287 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D57EA297-6DC2-4BC0-8C91-334863327863} + Win32Proj + RequestHandler + 10.0.15063.0 + RequestHandler + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + aspnetcorerh + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ + + + true + aspnetcorerh + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ + + + false + aspnetcorerh + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ + + + false + aspnetcorerh + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ProgramDatabase + MultiThreadedDebug + ..\IISLib;..\CommonLib;.\Inc + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + Source.def + + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ProgramDatabase + MultiThreadedDebug + ..\IISLib;..\CommonLib;.\Inc + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + Source.def + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;REQUESTHANDLER_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + MultiThreaded + ..\IISLib;..\CommonLib;.\Inc + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + false + true + true + Source.def + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + + + + + Level4 + NotUsing + MaxSpeed + true + true + NDEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + precomp.hxx + MultiThreaded + ..\IISLib;..\CommonLib;.\Inc + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + false + true + true + Source.def + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Document + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + + + + + {55494e58-e061-4c4c-a0a8-837008e72f85} + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RequestHandler/RequestHandler.vcxproj.filters b/src/RequestHandler/RequestHandler.vcxproj.filters new file mode 100644 index 0000000000..b891f5ff15 --- /dev/null +++ b/src/RequestHandler/RequestHandler.vcxproj.filters @@ -0,0 +1,109 @@ + + + + + + InProcess + + + InProcess + + + InProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + + + + + InProcess + + + InProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + OutOfProcess + + + + + + + + + + + + + + + + + + + + + {e567abb5-bac5-4f05-a320-5e25dcfc0000} + + + {5568209f-269e-4d0a-bbb7-ba14f874ccb7} + + + + + + \ No newline at end of file diff --git a/src/RequestHandler/Resource.rc b/src/RequestHandler/Resource.rc new file mode 100644 index 0000000000..caf820af68 --- /dev/null +++ b/src/RequestHandler/Resource.rc @@ -0,0 +1,98 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "version.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO +FILEVERSION FileVersion +PRODUCTVERSION ProductVersion +FILEFLAGSMASK 0x3fL +#ifdef _DEBUG +FILEFLAGS 0x1L +#else +FILEFLAGS 0x0L +#endif +FILEOS 0x40004L +FILETYPE 0x2L +FILESUBTYPE 0x0L +BEGIN +BLOCK "StringFileInfo" +BEGIN +BLOCK "040904b0" +BEGIN +VALUE "CompanyName", "Microsoft" +VALUE "FileDescription", "IIS ASP.NET Core Module Request Handler" +VALUE "FileVersion", FileVersionStr +VALUE "InternalName", "aspnetcorerh.dll" +VALUE "LegalCopyright", "Copyright (C) 2016" +VALUE "OriginalFilename", "aspnetcorerh.dll" +VALUE "ProductName", "ASP.NET Core Module" +VALUE "ProductVersion", ProductVersionStr +END +END +BLOCK "VarFileInfo" +BEGIN +VALUE "Translation", 0x409, 1200 +END +END + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/RequestHandler/Source.def b/src/RequestHandler/Source.def new file mode 100644 index 0000000000..889bd1a39b --- /dev/null +++ b/src/RequestHandler/Source.def @@ -0,0 +1,6 @@ +LIBRARY aspnetcorerh + +EXPORTS + CreateApplication + CreateRequestHandler + diff --git a/src/RequestHandler/aspnetcore_event.h b/src/RequestHandler/aspnetcore_event.h new file mode 100644 index 0000000000..11f9e248a2 --- /dev/null +++ b/src/RequestHandler/aspnetcore_event.h @@ -0,0 +1,550 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef __ASPNETCOREEVENT_H__ +#define __ASPNETCOREEVENT_H__ +/*++ + + Module Name: + + aspnetcore_event.h + + Abstract: + + Header file has been generated from mof file containing + IIS trace event descriptions + +--*/ + +// +// Start of the new provider class WWWServerTraceProvider, +// GUID: {3a2a4e84-4c21-4981-ae10-3fda0d9b0f83} +// Description: IIS: WWW Server +// + +class WWWServerTraceProvider +{ +public: + static + LPCGUID + GetProviderGuid( VOID ) + // return GUID for the current event class + { + static const GUID ProviderGuid = + {0x3a2a4e84,0x4c21,0x4981,{0xae,0x10,0x3f,0xda,0x0d,0x9b,0x0f,0x83}}; + return &ProviderGuid; + }; + enum enumAreaFlags + { + // AspNetCore module events + ANCM = 0x10000 + }; + static + LPCWSTR + TranslateEnumAreaFlagsToString( enum enumAreaFlags EnumValue) + { + switch( (DWORD) EnumValue ) + { + case 0x10000: return L"ANCM"; + } + return NULL; + }; + + static + BOOL + CheckTracingEnabled( + IHttpTraceContext * pHttpTraceContext, + enumAreaFlags AreaFlags, + DWORD dwVerbosity ) + { + HRESULT hr; + HTTP_TRACE_CONFIGURATION TraceConfig; + TraceConfig.pProviderGuid = GetProviderGuid(); + hr = pHttpTraceContext->GetTraceConfiguration( &TraceConfig ); + if ( FAILED( hr ) || !TraceConfig.fProviderEnabled ) + { + return FALSE; + } + if ( TraceConfig.dwVerbosity >= dwVerbosity && + ( TraceConfig.dwAreas == (DWORD) AreaFlags || + ( TraceConfig.dwAreas & (DWORD)AreaFlags ) == (DWORD)AreaFlags ) ) + { + return TRUE; + } + return FALSE; + }; +}; + +// +// Start of the new event class ANCMEvents, +// GUID: {82ADEAD7-12B2-4781-BDCA-5A4B6C757191} +// Description: ANCM runtime events +// + +class ANCMEvents +{ +public: + static + LPCGUID + GetAreaGuid( VOID ) + // return GUID for the current event class + { + static const GUID AreaGuid = + {0x82adead7,0x12b2,0x4781,{0xbd,0xca,0x5a,0x4b,0x6c,0x75,0x71,0x91}}; + return &AreaGuid; + }; + + // + // Event: mof class name ANCMAppStart, + // Description: Start application success + // EventTypeName: ANCM_START_APPLICATION_SUCCESS + // EventType: 1 + // EventLevel: 4 + // + + class ANCM_START_APPLICATION_SUCCESS + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + LPCWSTR pAppDescription + ) + // + // Raise ANCM_START_APPLICATION_SUCCESS Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 1; + Event.pszEventName = L"ANCM_START_APPLICATION_SUCCESS"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"AppDescription"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCWSTR; // mof type (string) + Items[ 1 ].pbData = (PBYTE) pAppDescription; + Items[ 1 ].cbData = + ( Items[ 1 ].pbData == NULL )? 0 : ( sizeof(WCHAR) * (1 + (DWORD) wcslen( (PWSTR) Items[ 1 ].pbData ) ) ); + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMAppStartFail, + // Description: Start application failed + // EventTypeName: ANCM_START_APPLICATION_FAIL + // EventType: 2 + // EventLevel: 2 + // + + class ANCM_START_APPLICATION_FAIL + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + LPCWSTR pFailureDescription + ) + // + // Raise ANCM_START_APPLICATION_FAIL Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 2; + Event.pszEventName = L"ANCM_START_APPLICATION_FAIL"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 2; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"FailureDescription"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCWSTR; // mof type (string) + Items[ 1 ].pbData = (PBYTE) pFailureDescription; + Items[ 1 ].cbData = + ( Items[ 1 ].pbData == NULL )? 0 : ( sizeof(WCHAR) * (1 + (DWORD) wcslen( (PWSTR) Items[ 1 ].pbData ) ) ); + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 2 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardStart, + // Description: Start fardwarding request + // EventTypeName: ANCM_REQUEST_FORWARD_START + // EventType: 3 + // EventLevel: 4 + // + + class ANCM_REQUEST_FORWARD_START + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_REQUEST_FORWARD_START Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 3; + Event.pszEventName = L"ANCM_REQUEST_FORWARD_START"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardEnd, + // Description: Finish forwarding request + // EventTypeName: ANCM_REQUEST_FORWARD_END + // EventType: 4 + // EventLevel: 4 + // + + class ANCM_REQUEST_FORWARD_END + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_REQUEST_FORWARD_END Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 4; + Event.pszEventName = L"ANCM_REQUEST_FORWARD_END"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardFail, + // Description: Forwarding request failure + // EventTypeName: ANCM_REQUEST_FORWARD_FAIL + // EventType: 5 + // EventLevel: 2 + // + + class ANCM_REQUEST_FORWARD_FAIL + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG ErrorCode + ) + // + // Raise ANCM_REQUEST_FORWARD_FAIL Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 5; + Event.pszEventName = L"ANCM_REQUEST_FORWARD_FAIL"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 2; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"ErrorCode"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &ErrorCode; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 2 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMWinHttpCallBack, + // Description: Receiving callback from WinHttp + // EventTypeName: ANCM_WINHTTP_CALLBACK + // EventType: 6 + // EventLevel: 4 + // + + class ANCM_WINHTTP_CALLBACK + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG InternetStatus + ) + // + // Raise ANCM_WINHTTP_CALLBACK Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 6; + Event.pszEventName = L"ANCM_WINHTTP_CALLBACK"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"InternetStatus"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &InternetStatus; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardEnd, + // Description: Inprocess executing request failure + // EventTypeName: ANCM_EXECUTE_REQUEST_FAIL + // EventType: 7 + // EventLevel: 2 + // + + class ANCM_EXECUTE_REQUEST_FAIL + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG ErrorCode + ) + // + // Raise ANCM_EXECUTE_REQUEST_FAIL Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 7; + Event.pszEventName = L"ANCM_EXECUTE_REQUEST_FAIL"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 2; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"ErrorCode"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &ErrorCode; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 2 ); //Verbosity + }; + }; +}; +#endif diff --git a/src/RequestHandler/aspnetcore_msg.h b/src/RequestHandler/aspnetcore_msg.h new file mode 100644 index 0000000000..f99bc99084 --- /dev/null +++ b/src/RequestHandler/aspnetcore_msg.h @@ -0,0 +1,165 @@ +/*++ + + Copyright (c) .NET Foundation. All rights reserved. + Licensed under the MIT License. See License.txt in the project root for license information. + +Module Name: + + aspnetcore_msg.mc + +Abstract: + + Asp.Net Core Module localizable messages. + +--*/ + + +#ifndef _ASPNETCORE_MSG_H_ +#define _ASPNETCORE_MSG_H_ + +// +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// + + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_ERROR ((DWORD)0x000003E8L) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_SUCCESS +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS ((DWORD)0x000003E9L) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_CRASH +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_CRASH ((DWORD)0x000003EAL) + +// +// MessageId: ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED ((DWORD)0x000003EBL) + +// +// MessageId: ASPNETCORE_EVENT_CONFIG_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_CONFIG_ERROR ((DWORD)0x000003ECL) + +// +// MessageId: ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE ((DWORD)0x000003EDL) + +// +// MessageId: ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST ((DWORD)0x000003EEL) + +// +// MessageId: ASPNETCORE_EVENT_LOAD_CLR_FALIURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE ((DWORD)0x000003EFL) + +// +// MessageId: ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP ((DWORD)0x000003F0L) + +// +// MessageId: ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR ((DWORD)0x000003F1L) + +// +// MessageId: ASPNETCORE_EVENT_ADD_APPLICATION_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR ((DWORD)0x000003F2L) + +// +// MessageId: ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT ((DWORD)0x000003F3L) + + +#endif // _ASPNETCORE_MODULE_MSG_H_ diff --git a/src/RequestHandler/aspnetcore_msg.mc b/src/RequestHandler/aspnetcore_msg.mc new file mode 100644 index 0000000000..ae8eec4e88 --- /dev/null +++ b/src/RequestHandler/aspnetcore_msg.mc @@ -0,0 +1,103 @@ +;/*++ +; +; Copyright (c) .NET Foundation. All rights reserved. +; Licensed under the MIT License. See License.txt in the project root for license information. +; +;Module Name: +; +; aspnetcore_msg.mc +; +;Abstract: +; +; Asp.Net Core Module localizable messages. +; +;--*/ +; +; +;#ifndef _ASPNETCORE_MSG_H_ +;#define _ASPNETCORE_MSG_H_ +; + +SeverityNames=(Success=0x0 + Informational=0x1 + Warning=0x2 + Error=0x3 + ) + +MessageIdTypedef=DWORD + +Messageid=1000 +SymbolicName=ASPNETCORE_EVENT_PROCESS_START_ERROR +Language=English +%1 +. + +Messageid=1001 +SymbolicName=ASPNETCORE_EVENT_PROCESS_START_SUCCESS +Language=English +%1 +. + +Messageid=1002 +SymbolicName=ASPNETCORE_EVENT_PROCESS_CRASH +Language=English +%1 +. + +Messageid=1003 +SymbolicName=ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED +Language=English +%1 +. + +Messageid=1004 +SymbolicName=ASPNETCORE_EVENT_CONFIG_ERROR +Language=English +%1 +. + +Messageid=1005 +SymbolicName=ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE +Language=English +%1 +. + +Messageid=1006 +SymbolicName=ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +Language=English +%1 +. + +Messageid=1007 +SymbolicName=ASPNETCORE_EVENT_LOAD_CLR_FALIURE +Language=English +%1 +. + +Messageid=1008 +SymbolicName=ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP +Language=English +%1 +. + +Messageid=1009 +SymbolicName=ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR +Language=English +%1 +. + +Messageid=1010 +SymbolicName=ASPNETCORE_EVENT_ADD_APPLICATION_ERROR +Language=English +%1 +. + +Messageid=1011 +SymbolicName=ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT +Language=English +%1 +. + +; +;#endif // _ASPNETCORE_MODULE_MSG_H_ +; \ No newline at end of file diff --git a/src/RequestHandler/aspnetcore_msg.rc b/src/RequestHandler/aspnetcore_msg.rc new file mode 100644 index 0000000000..0abcb0fa2c --- /dev/null +++ b/src/RequestHandler/aspnetcore_msg.rc @@ -0,0 +1,2 @@ +LANGUAGE 0x9,0x1 +1 11 "MSG00001.bin" diff --git a/src/RequestHandler/disconnectcontext.h b/src/RequestHandler/disconnectcontext.h new file mode 100644 index 0000000000..e43a49c0a0 --- /dev/null +++ b/src/RequestHandler/disconnectcontext.h @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext +{ +public: + ASYNC_DISCONNECT_CONTEXT() + { + m_pHandler = NULL; + } + + VOID + CleanupStoredContext() + { + DBG_ASSERT(m_pHandler == NULL); + delete this; + } + + VOID + NotifyDisconnect() + { + REQUEST_HANDLER *pInitialValue = (REQUEST_HANDLER*) + InterlockedExchangePointer((PVOID*)&m_pHandler, NULL); + + if (pInitialValue != NULL) + { + pInitialValue->TerminateRequest(TRUE); + pInitialValue->DereferenceRequestHandler(); + } + } + + VOID + SetHandler( + REQUEST_HANDLER *pHandler + ) + { + // + // Take a reference on the forwarding handler. + // This reference will be released on either of two conditions: + // + // 1. When the request processing ends, in which case a ResetHandler() + // is called. + // + // 2. When a disconnect notification arrives. + // + // We need to make sure that only one of them ends up dereferencing + // the object. + // + + DBG_ASSERT(pHandler != NULL); + DBG_ASSERT(m_pHandler == NULL); + + pHandler->ReferenceRequestHandler(); + InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler); + } + + VOID + ResetHandler( + VOID + ) + { + REQUEST_HANDLER *pInitialValue = (REQUEST_HANDLER*) + InterlockedExchangePointer((PVOID*)&m_pHandler, NULL); + + if (pInitialValue != NULL) + { + pInitialValue->DereferenceRequestHandler(); + } + } + +private: + ~ASYNC_DISCONNECT_CONTEXT() + {} + + REQUEST_HANDLER * m_pHandler; +}; \ No newline at end of file diff --git a/src/RequestHandler/dllmain.cxx b/src/RequestHandler/dllmain.cxx new file mode 100644 index 0000000000..f4b476a856 --- /dev/null +++ b/src/RequestHandler/dllmain.cxx @@ -0,0 +1,340 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "precomp.hxx" +#include +#include + +BOOL g_fNsiApiNotSupported = FALSE; +BOOL g_fWebSocketSupported = FALSE; +BOOL g_fEnableReferenceCountTracing = FALSE; +BOOL g_fOutOfProcessInitialize = FALSE; +BOOL g_fOutOfProcessInitializeError = FALSE; +BOOL g_fWinHttpNonBlockingCallbackAvailable = FALSE; +DWORD g_OptionalWinHttpFlags = 0; +DWORD g_dwAspNetCoreDebugFlags = 0; +DWORD g_dwDebugFlags = 0; +DWORD g_dwTlsIndex = TLS_OUT_OF_INDEXES; +SRWLOCK g_srwLockRH; +HINTERNET g_hWinhttpSession = NULL; +IHttpServer * g_pHttpServer = NULL; +HINSTANCE g_hWinHttpModule; + + +VOID +InitializeGlobalConfiguration( + VOID +) +{ + HKEY hKey; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + 0, + KEY_READ, + &hKey) == NO_ERROR) + { + DWORD dwType; + DWORD dwData; + DWORD cbData; + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"OptionalWinHttpFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + g_OptionalWinHttpFlags = dwData; + } + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"EnableReferenceCountTracing", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD) && (dwData == 1 || dwData == 0)) + { + g_fEnableReferenceCountTracing = !!dwData; + } + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"DebugFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + g_dwAspNetCoreDebugFlags = dwData; + } + + RegCloseKey(hKey); + } + + DWORD dwSize = 0; + DWORD dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + g_fNsiApiNotSupported = TRUE; + } + + // WebSocket is supported on Win8 and above only + // todo: test on win7 + g_fWebSocketSupported = IsWindows8OrGreater(); + +} + +// +// Global initialization routine for OutOfProcess +// +HRESULT +EnsureOutOfProcessInitializtion( IHttpServer* pServer) +{ + + DBG_ASSERT(pServer); + + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + + g_pHttpServer = pServer; + + if (g_fOutOfProcessInitializeError) + { + hr = E_NOT_VALID_STATE; + goto Finished; + } + if (!g_fOutOfProcessInitialize) + { + AcquireSRWLockExclusive(&g_srwLockRH); + fLocked = TRUE; + if (g_fOutOfProcessInitializeError) + { + hr = E_NOT_VALID_STATE; + goto Finished; + } + + if (g_fOutOfProcessInitialize) + { + // Done by another thread + goto Finished; + } + + // Initialze some global variables here + InitializeGlobalConfiguration(); + + g_hWinHttpModule = GetModuleHandle(TEXT("winhttp.dll")); + + hr = WINHTTP_HELPER::StaticInitialize(); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND)) + { + g_fWebSocketSupported = FALSE; + } + else + { + goto Finished; + } + } + + g_hWinhttpSession = WinHttpOpen(L"", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + WINHTTP_FLAG_ASYNC); + if (g_hWinhttpSession == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Don't set non-blocking callbacks WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS, + // as we will call WinHttpQueryDataAvailable to get response on the same thread + // that we received callback from Winhttp on completing sending/forwarding the request + // + + // + // Setup the callback function + // + if (WinHttpSetStatusCallback(g_hWinhttpSession, + FORWARDING_HANDLER::OnWinHttpCompletion, + (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Make sure we see the redirects (rather than winhttp doing it + // automatically) + // + DWORD dwRedirectOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; + if (!WinHttpSetOption(g_hWinhttpSession, + WINHTTP_OPTION_REDIRECT_POLICY, + &dwRedirectOption, + sizeof(dwRedirectOption))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + g_dwTlsIndex = TlsAlloc(); + if (g_dwTlsIndex == TLS_OUT_OF_INDEXES) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + + hr = FORWARDING_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing); + if (FAILED(hr)) + { + goto Finished; + } + + hr = WEBSOCKET_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing); + if (FAILED(hr)) + { + goto Finished; + } + } + +Finished: + if (FAILED(hr)) + { + g_fOutOfProcessInitializeError = TRUE; + } + if (fLocked) + { + ReleaseSRWLockExclusive(&g_srwLockRH); + } + return hr; +} + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved +) +{ + UNREFERENCED_PARAMETER(lpReserved); + + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + InitializeSRWLock(&g_srwLockRH); + // Initialze some global variables here + InitializeGlobalConfiguration(); + break; + default: + break; + } + return TRUE; +} + +HRESULT +__stdcall +CreateApplication( + _In_ IHttpServer *pServer, + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ APPLICATION **ppApplication +) +{ + HRESULT hr = S_OK; + APPLICATION *pApplication = NULL; + + //REQUEST_HANDLER::StaticInitialize(pServer); + + if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + pApplication = new IN_PROCESS_APPLICATION(pServer, pConfig); + if (pApplication == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + goto Finished; + } + } + else if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_OUT_PROCESS) + { + hr = EnsureOutOfProcessInitializtion(pServer); + if (FAILED(hr)) + { + goto Finished; + } + + pApplication = new OUT_OF_PROCESS_APPLICATION(pServer, pConfig); + if (pApplication == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + goto Finished; + } + + hr = ((OUT_OF_PROCESS_APPLICATION*)pApplication)->Initialize(); + if (FAILED(hr)) + { + delete pApplication; + pApplication = NULL; + goto Finished; + } + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + goto Finished; + } + + *ppApplication = pApplication; + +Finished: + return hr; +} + +HRESULT +__stdcall +CreateRequestHandler( + _In_ IHttpContext *pHttpContext, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication, + _Out_ REQUEST_HANDLER **pRequestHandler +) +{ + HRESULT hr = S_OK; + REQUEST_HANDLER* pHandler = NULL; + ASPNETCORE_CONFIG* pConfig = pApplication->QueryConfig(); + DBG_ASSERT(pConfig); + + if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + pHandler = new IN_PROCESS_HANDLER(pHttpContext, pModuleId, pApplication); + } + else if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_OUT_PROCESS) + { + pHandler = new FORWARDING_HANDLER(pHttpContext, pModuleId, pApplication); + } + else + { + return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + } + + if (pHandler == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + } + else + { + *pRequestHandler = pHandler; + } + return hr; +} diff --git a/src/RequestHandler/inprocess/inprocessapplication.cpp b/src/RequestHandler/inprocess/inprocessapplication.cpp new file mode 100644 index 0000000000..075287ec3d --- /dev/null +++ b/src/RequestHandler/inprocess/inprocessapplication.cpp @@ -0,0 +1,889 @@ +#include "..\precomp.hxx" + +typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); + +IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; + +IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( + IHttpServer* pHttpServer, + ASPNETCORE_CONFIG* pConfig) : + APPLICATION(pHttpServer, pConfig), + m_ProcessExitCode(0), + m_fManagedAppLoaded(FALSE), + m_fLoadManagedAppError(FALSE), + m_fInitialized(FALSE), + m_fRecycleProcessCalled(FALSE), + m_hLogFileHandle(INVALID_HANDLE_VALUE), + m_fDoneStdRedirect(FALSE) +{ + // is it guaranteed that we have already checked app offline at this point? + // If so, I don't think there is much to do here. + DBG_ASSERT(pHttpServer != NULL); + DBG_ASSERT(pConfig != NULL); + InitializeSRWLock(&m_srwLock); + + // TODO we can probably initialized as I believe we are the only ones calling recycle. + m_fInitialized = TRUE; + m_status = APPLICATION_STATUS::RUNNING; +} + +IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() +{ + Recycle(); +} + +__override +VOID +IN_PROCESS_APPLICATION::ShutDown() +{ + //todo +} + +// This is the same function as before, TODO configrm if we need to change anything for configuration. +VOID +IN_PROCESS_APPLICATION::Recycle( + VOID +) +{ + if (m_fInitialized) + { + DWORD dwThreadStatus = 0; + DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); + HANDLE handle = NULL; + WIN32_FIND_DATA fileData; + + if (m_pStdFile != NULL) + { + fflush(stdout); + fflush(stderr); + fclose(m_pStdFile); + } + + if (m_hLogFileHandle != INVALID_HANDLE_VALUE) + { + m_Timer.CancelTimer(); + CloseHandle(m_hLogFileHandle); + m_hLogFileHandle = INVALID_HANDLE_VALUE; + } + + // delete empty log file, if logging is not enabled + handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData); + if (handle != INVALID_HANDLE_VALUE && + fileData.nFileSizeHigh && + fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh + { + FindClose(handle); + // no need to check whether the deletion succeeds + // as nothing can be done + DeleteFile(m_struLogFilePath.QueryStr()); + } + + AcquireSRWLockExclusive(&m_srwLock); + + if (!m_pHttpServer->IsCommandLineLaunch() && + !m_fRecycleProcessCalled && + (m_pHttpServer->GetAdminManager() != NULL)) + { + // IIS scenario. + // notify IIS first so that new request will be routed to new worker process + m_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand"); + } + + m_fRecycleProcessCalled = TRUE; + + // First call into the managed server and shutdown + if (m_ShutdownHandler != NULL) + { + m_ShutdownHandler(m_ShutdownHandlerContext); + m_ShutdownHandler = NULL; + } + + if (m_hThread != NULL && + GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && + dwThreadStatus == STILL_ACTIVE) + { + // wait for gracefullshut down, i.e., the exit of the background thread or timeout + if (WaitForSingleObject(m_hThread, dwTimeout) != WAIT_OBJECT_0) + { + // if the thread is still running, we need kill it first before exit to avoid AV + if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE) + { + TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT); + } + } + } + + CloseHandle(m_hThread); + m_hThread = NULL; + s_Application = NULL; + + ReleaseSRWLockExclusive(&m_srwLock); + if (m_pHttpServer && m_pHttpServer->IsCommandLineLaunch()) + { + // IISExpress scenario + // Can only call exit to terminate current process + exit(0); + } + } +} + +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_APPLICATION::OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + IN_PROCESS_HANDLER* pInProcessHandler +) +{ + + REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; + + if (pInProcessHandler->QueryIsManagedRequestComplete()) + { + // means PostCompletion has been called and this is the associated callback. + dwRequestNotificationStatus = pInProcessHandler->QueryAsyncCompletionStatus(); + // TODO cleanup whatever disconnect listener there is + return dwRequestNotificationStatus; + } + else + { + // Call the managed handler for async completion. + return m_AsyncCompletionHandler(pInProcessHandler->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); + } +} + +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_APPLICATION::OnExecuteRequest( + _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + if (m_RequestHandler != NULL) + { + return m_RequestHandler(pInProcessHandler, m_RequestHandlerContext); + } + + // + // return error as the application did not register callback + // + if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(pHttpContext->GetTraceContext())) + { + ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(pHttpContext->GetTraceContext(), + NULL, + (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); + } + + pHttpContext->GetResponse()->SetStatus(500, + "Internal Server Error", + 0, + (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); + + return RQ_NOTIFICATION_FINISH_REQUEST; +} + +BOOL +IN_PROCESS_APPLICATION::DirectoryExists( + _In_ STRU *pstrPath +) +{ + WIN32_FILE_ATTRIBUTE_DATA data; + + if (pstrPath->IsEmpty()) + { + return false; + } + + return GetFileAttributesExW(pstrPath->QueryStr(), GetFileExInfoStandard, &data); +} + +BOOL +IN_PROCESS_APPLICATION::GetEnv( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult +) +{ + DWORD dwLength; + PWSTR pszBuffer = NULL; + BOOL fSucceeded = FALSE; + + if (pszEnvironmentVariable == NULL) + { + goto Finished; + } + pstrResult->Reset(); + dwLength = GetEnvironmentVariableW(pszEnvironmentVariable, NULL, 0); + + if (dwLength == 0) + { + goto Finished; + } + + pszBuffer = new WCHAR[dwLength]; + if (GetEnvironmentVariableW(pszEnvironmentVariable, pszBuffer, dwLength) == 0) + { + goto Finished; + } + + pstrResult->Copy(pszBuffer); + + fSucceeded = TRUE; + +Finished: + if (pszBuffer != NULL) { + delete[] pszBuffer; + } + return fSucceeded; +} + +VOID +IN_PROCESS_APPLICATION::FindDotNetFolders( + _In_ PCWSTR pszPath, + _Out_ std::vector *pvFolders +) +{ + HANDLE handle = NULL; + WIN32_FIND_DATAW data = { 0 }; + + handle = FindFirstFileExW(pszPath, FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); + if (handle == INVALID_HANDLE_VALUE) + { + return; + } + + do + { + std::wstring folder(data.cFileName); + pvFolders->push_back(folder); + } while (FindNextFileW(handle, &data)); + + FindClose(handle); +} + +VOID +IN_PROCESS_APPLICATION::SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + m_RequestHandler = request_handler; + m_RequestHandlerContext = pvRequstHandlerContext; + m_ShutdownHandler = shutdown_handler; + m_ShutdownHandlerContext = pvShutdownHandlerContext; + m_AsyncCompletionHandler = async_completion_handler; + + // Initialization complete + SetEvent(m_pInitalizeEvent); +} + +HRESULT +IN_PROCESS_APPLICATION::FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult +) +{ + HRESULT hr = S_OK; + fx_ver_t max_ver(-1, -1, -1); + for (const auto& dir : vFolders) + { + fx_ver_t fx_ver(-1, -1, -1); + if (fx_ver_t::parse(dir, &fx_ver, false)) + { + // TODO using max instead of std::max works + max_ver = max(max_ver, fx_ver); + } + } + + hr = pstrResult->Copy(max_ver.as_str().c_str()); + + // we check FAILED(hr) outside of function + return hr; +} + +VOID +IN_PROCESS_APPLICATION::SetStdOut( + VOID +) +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + STRU struPath; + + SYSTEMTIME systemTime; + SECURITY_ATTRIBUTES saAttr = { 0 }; + + if (!m_fDoneStdRedirect) + { + // Have not set stdout yet, redirect stdout to log file + AcquireSRWLockExclusive(&m_srwLock); + fLocked = TRUE; + if (!m_fDoneStdRedirect) + { + hr = UTILITY::ConvertPathToFullPath( + m_pConfig->QueryStdoutLogFile()->QueryStr(), + m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + &struPath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); + if (FAILED(hr)) + { + goto Finished; + } + + GetSystemTime(&systemTime); + hr = m_struLogFilePath.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", + struPath.QueryStr(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond, + GetCurrentProcessId()); + if (FAILED(hr)) + { + goto Finished; + } + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + m_hLogFileHandle = CreateFileW(m_struLogFilePath.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (m_hLogFileHandle == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // best effort + // no need to capture the error code as nothing we can do here + // in case mamanged layer exits abnormally, may not be able to capture the log content as it is buffered. + // + if (!GetConsoleWindow()) + { + // + // SetStdHandle works as w3wp does not have Console + // Current process does not have a console + // + SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle); + if (m_pConfig->QueryStdoutLogEnabled()) + { + SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle); + // not work + // AllocConsole() does not help + // *stdout = *m_pStdFile; + // *stderr = *m_pStdFile; + // _dup2(_fileno(m_pStdFile), _fileno(stdout)); + // _dup2(_fileno(m_pStdFile), _fileno(stderr)); + // this one cannot capture the process start failure + // _wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout); + + // Periodically flush the log content to file + m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struLogFilePath, 3000, 3000); + } + } + else + { + // The process has console, e.g., IIS Express scenario + CloseHandle(m_hLogFileHandle); + m_hLogFileHandle = INVALID_HANDLE_VALUE; + + if (m_pConfig->QueryStdoutLogEnabled()) + { + if (_wfopen_s(&m_pStdFile, m_struLogFilePath.QueryStr(), L"w") == 0) + { + // known issue: error info may not be capture when process crashes during buffering + // even we disabled FILE buffering + setvbuf(m_pStdFile, NULL, _IONBF, 0); + _dup2(_fileno(m_pStdFile), _fileno(stdout)); + _dup2(_fileno(m_pStdFile), _fileno(stderr)); + } + // not work for console scenario + // close and AllocConsole does not help + //_wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout); + // SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle); + // SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle); + //*stdout = *m_pStdFile; + //*stderr = *m_pStdFile; + } + else + { + // delete the file as log is disabled + WIN32_FIND_DATA fileData; + HANDLE handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData); + if (handle != INVALID_HANDLE_VALUE && + fileData.nFileSizeHigh == 0 && + fileData.nFileSizeLow == 0) + { + FindClose(handle); + // no need to check whether the deletion succeeds + // as nothing can be done + DeleteFile(m_struLogFilePath.QueryStr()); + } + } + } + } + } + +Finished: + m_fDoneStdRedirect = TRUE; + if (fLocked) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + if (FAILED(hr) && m_pConfig->QueryStdoutLogEnabled()) + { + //todo log an warning + //STRU strEventMsg; + //LPCWSTR apsz[1]; + + //if (SUCCEEDED(strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + // m_struLogFilePath.QueryStr(), + // HRESULT_FROM_GETLASTERROR()))) + //{ + // apsz[0] = strEventMsg.QueryStr(); + // // + // // not checking return code because if ReportEvent + // // fails, we cannot do anything. + // // + // if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_WARNING_TYPE, + // 0, + // ASPNETCORE_EVENT_CONFIG_ERROR, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // } + //} + } +} + +// Will be called by the inprocesshandler +HRESULT +IN_PROCESS_APPLICATION::LoadManagedApplication +( + VOID +) +{ + HRESULT hr = S_OK; + DWORD dwTimeout; + DWORD dwResult; + BOOL fLocked = FALSE; + //PCWSTR apsz[1]; + //STACK_STRU(strEventMsg, 256); + + if (m_fManagedAppLoaded || m_fLoadManagedAppError) + { + // Core CLR has already been loaded. + // Cannot load more than once even there was a failure + goto Finished; + } + + // Set up stdout redirect + SetStdOut(); + + AcquireSRWLockExclusive(&m_srwLock); + fLocked = TRUE; + if (m_fManagedAppLoaded || m_fLoadManagedAppError) + { + goto Finished; + } + + m_hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)ExecuteAspNetCoreProcess, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (m_hThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_pInitalizeEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not set + NULL); // name + + if (m_pInitalizeEvent == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + // If the debugger is attached, never timeout + if (IsDebuggerPresent()) + { + dwTimeout = INFINITE; + } + else + { + dwTimeout = m_pConfig->QueryStartupTimeLimitInMS(); + } + + const HANDLE pHandles[2]{ m_hThread, m_pInitalizeEvent }; + + // Wait on either the thread to complete or the event to be set + dwResult = WaitForMultipleObjects(2, pHandles, FALSE, dwTimeout); + + // It all timed out + if (dwResult == WAIT_TIMEOUT) + { + // kill the backend thread as loading dotnet timedout + TerminateThread(m_hThread, 0); + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + else if (dwResult == WAIT_FAILED) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // The thread ended it means that something failed + if (dwResult == WAIT_OBJECT_0) + { + hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; + goto Finished; + } + + m_fManagedAppLoaded = TRUE; + +Finished: + if (fLocked) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + + if (FAILED(hr)) + { + // Question: in case of application loading failure, should we allow retry on + // following request or block the activation at all + m_fLoadManagedAppError = FALSE; // m_hThread != NULL ? + + // TODO + //if (SUCCEEDED(strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, + // m_pConfiguration->QueryApplicationPath()->QueryStr(), + // m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), + // hr))) + //{ + // apsz[0] = strEventMsg.QueryStr(); + + // // + // // not checking return code because if ReportEvent + // // fails, we cannot do anything. + // // + // if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_ERROR_TYPE, + // 0, + // ASPNETCORE_EVENT_LOAD_CLR_FALIURE, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // } + //} + } + return hr; +} + +// static +VOID +IN_PROCESS_APPLICATION::ExecuteAspNetCoreProcess( + _In_ LPVOID pContext +) +{ + + IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext; + DBG_ASSERT(pApplication != NULL); + pApplication->ExecuteApplication(); + // + // no need to log the error here as if error happened, the thread will exit + // the error will ba catched by caller LoadManagedApplication which will log an error + // +} + + +HRESULT +IN_PROCESS_APPLICATION::ExecuteApplication( + VOID +) +{ + HRESULT hr = S_OK; + + STRU strFullPath; + STRU strDotnetExeLocation; + STRU strHostFxrSearchExpression; + STRU strDotnetFolderLocation; + STRU strHighestDotnetVersion; + STRU strApplicationFullPath; + PWSTR strDelimeterContext = NULL; + PCWSTR pszDotnetExeLocation = NULL; + PCWSTR pszDotnetExeString(L"dotnet.exe"); + DWORD dwCopyLength; + HMODULE hModule; + PCWSTR argv[2]; + hostfxr_main_fn pProc; + std::vector vVersionFolders; + bool fFound = FALSE; + + // Get the System PATH value. + if (!GetEnv(L"PATH", &strFullPath)) + { + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + // Split on ';', checking to see if dotnet.exe exists in any folders. + pszDotnetExeLocation = wcstok_s(strFullPath.QueryStr(), L";", &strDelimeterContext); + + while (pszDotnetExeLocation != NULL) + { + dwCopyLength = (DWORD) wcsnlen_s(pszDotnetExeLocation, 260); + if (dwCopyLength == 0) + { + continue; + } + + // We store both the exe and folder locations as we eventually need to check inside of host\\fxr + // which doesn't need the dotnet.exe portion of the string + // TODO consider reducing allocations. + strDotnetExeLocation.Reset(); + strDotnetFolderLocation.Reset(); + hr = strDotnetExeLocation.Copy(pszDotnetExeLocation, dwCopyLength); + if (FAILED(hr)) + { + goto Finished; + } + + hr = strDotnetFolderLocation.Copy(pszDotnetExeLocation, dwCopyLength); + if (FAILED(hr)) + { + goto Finished; + } + + if (dwCopyLength > 0 && pszDotnetExeLocation[dwCopyLength - 1] != L'\\') + { + hr = strDotnetExeLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Finished; + } + + if (PathFileExists(strDotnetExeLocation.QueryStr())) + { + // means we found the folder with a dotnet.exe inside of it. + fFound = TRUE; + break; + } + pszDotnetExeLocation = wcstok_s(NULL, L";", &strDelimeterContext); + } + if (!fFound) + { + // could not find dotnet.exe, error out + hr = ERROR_BAD_ENVIRONMENT; + } + + hr = strDotnetFolderLocation.Append(L"\\host\\fxr"); + if (FAILED(hr)) + { + goto Finished; + } + + if (!DirectoryExists(&strDotnetFolderLocation)) + { + // error, not found the folder + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + // Find all folders under host\\fxr\\ for version numbers. + hr = strHostFxrSearchExpression.Copy(strDotnetFolderLocation); + if (FAILED(hr)) + { + goto Finished; + } + + hr = strHostFxrSearchExpression.Append(L"\\*"); + if (FAILED(hr)) + { + goto Finished; + } + + // As we use the logic from core-setup, we are opting to use std here. + // TODO remove all uses of std? + FindDotNetFolders(strHostFxrSearchExpression.QueryStr(), &vVersionFolders); + + if (vVersionFolders.size() == 0) + { + // no core framework was found + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + hr = FindHighestDotNetVersion(vVersionFolders, &strHighestDotnetVersion); + if (FAILED(hr)) + { + goto Finished; + } + hr = strDotnetFolderLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Finished; + } + + hr = strDotnetFolderLocation.Append(strHighestDotnetVersion.QueryStr()); + if (FAILED(hr)) + { + goto Finished; + + } + + hr = strDotnetFolderLocation.Append(L"\\hostfxr.dll"); + if (FAILED(hr)) + { + goto Finished; + } + + hModule = LoadLibraryW(strDotnetFolderLocation.QueryStr()); + + if (hModule == NULL) + { + // .NET Core not installed (we can log a more detailed error message here) + hr = ERROR_BAD_ENVIRONMENT; + goto Finished; + } + + // Get the entry point for main + pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main"); + if (pProc == NULL) + { + hr = ERROR_BAD_ENVIRONMENT; // better hrresult? + goto Finished; + } + + // The first argument is mostly ignored + argv[0] = strDotnetExeLocation.QueryStr(); + UTILITY::ConvertPathToFullPath(m_pConfig->QueryArguments()->QueryStr(), + m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + &strApplicationFullPath); + argv[1] = strApplicationFullPath.QueryStr(); + + // There can only ever be a single instance of .NET Core + // loaded in the process but we need to get config information to boot it up in the + // first place. This is happening in an execute request handler and everyone waits + // until this initialization is done. + + // We set a static so that managed code can call back into this instance and + // set the callbacks + s_Application = this; + + RunDotnetApplication(argv, pProc); + +Finished: + // + // this method is called by the background thread and should never exit unless shutdown + // + if (!m_fRecycleProcessCalled) + { + //STRU strEventMsg; + //LPCWSTR apsz[1]; + //if (SUCCEEDED(strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, + // m_pConfig->QueryApplicationPath()->QueryStr(), + // m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + // m_ProcessExitCode + //))) + //{ + // apsz[0] = strEventMsg.QueryStr(); + + // // + // // not checking return code because if ReportEvent + // // fails, we cannot do anything. + // // + // if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_ERROR_TYPE, + // 0, + // ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // } + // // error. the thread exits after application started + // // Question: should we shutdown current worker process or keep the application in failure state? + // // for now, we reccylce to keep the same behavior as that of out-of-process + //} + if (m_fManagedAppLoaded) + { + Recycle(); + } + } + return hr; +} + +// +// Calls hostfxr_main with the hostfxr and application as arguments. +// Method should be called with only +// Need to have __try / __except in methods that require unwinding. +// +HRESULT +IN_PROCESS_APPLICATION::RunDotnetApplication(PCWSTR* argv, hostfxr_main_fn pProc) +{ + HRESULT hr = S_OK; + __try + { + m_ProcessExitCode = pProc(2, argv); + } + __except (FilterException(GetExceptionCode(), GetExceptionInformation())) + { + // TODO Log error message here. + hr = E_FAIL; + } + return hr; +} + +// static +INT +IN_PROCESS_APPLICATION::FilterException(unsigned int, struct _EXCEPTION_POINTERS*) +{ + // We assume that any exception is a failure as the applicaiton didn't start or there was a startup error. + // TODO, log error based on exception code. + return EXCEPTION_EXECUTE_HANDLER; +} diff --git a/src/RequestHandler/inprocess/inprocessapplication.h b/src/RequestHandler/inprocess/inprocessapplication.h new file mode 100644 index 0000000000..faa2d374b2 --- /dev/null +++ b/src/RequestHandler/inprocess/inprocessapplication.h @@ -0,0 +1,154 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +typedef void(*request_handler_cb) (int error, IHttpContext* pHttpContext, void* pvCompletionContext); +typedef REQUEST_NOTIFICATION_STATUS(*PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext); +typedef BOOL(*PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); +typedef REQUEST_NOTIFICATION_STATUS(*PFN_MANAGED_CONTEXT_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion); +typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); + +class IN_PROCESS_APPLICATION : public APPLICATION +{ +public: + IN_PROCESS_APPLICATION(IHttpServer* pHttpServer, ASPNETCORE_CONFIG *pConfig); + + ~IN_PROCESS_APPLICATION(); + + __override + VOID + ShutDown(); + + VOID + SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_callback, + _In_ PFN_SHUTDOWN_HANDLER shutdown_callback, + _In_ PFN_MANAGED_CONTEXT_HANDLER managed_context_callback, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext + ); + + VOID + Recycle( + VOID + ); + + // Executes the .NET Core process + HRESULT + ExecuteApplication( + VOID + ); + + HRESULT + LoadManagedApplication( + VOID + ); + + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + IN_PROCESS_HANDLER* pInProcessHandler + ); + + REQUEST_NOTIFICATION_STATUS + OnExecuteRequest + ( + IHttpContext* pHttpContext, + IN_PROCESS_HANDLER* pInProcessHandler + ); + + static + INT + FilterException(unsigned int code, struct _EXCEPTION_POINTERS *ep); + + HRESULT + RunDotnetApplication( + PCWSTR* argv, + hostfxr_main_fn pProc + ); + + static + IN_PROCESS_APPLICATION* + GetInstance( + VOID + ) + { + return s_Application; + } + +private: + // Thread executing the .NET Core process + HANDLE m_hThread; + + // The request handler callback from managed code + PFN_REQUEST_HANDLER m_RequestHandler; + VOID* m_RequestHandlerContext; + + // The shutdown handler callback from managed code + PFN_SHUTDOWN_HANDLER m_ShutdownHandler; + VOID* m_ShutdownHandlerContext; + + PFN_MANAGED_CONTEXT_HANDLER m_AsyncCompletionHandler; + + // The event that gets triggered when managed initialization is complete + HANDLE m_pInitalizeEvent; + + // The std log file handle + HANDLE m_hLogFileHandle; + STRU m_struLogFilePath; + + // The exit code of the .NET Core process + INT m_ProcessExitCode; + + BOOL m_fManagedAppLoaded; + BOOL m_fLoadManagedAppError; + BOOL m_fInitialized; + BOOL m_fIsWebSocketsConnection; + BOOL m_fDoneStdRedirect; + BOOL m_fRecycleProcessCalled; + + FILE* m_pStdFile; + STTIMER m_Timer; + SRWLOCK m_srwLock; + + static IN_PROCESS_APPLICATION* s_Application; + + VOID + SetStdOut( + VOID + ); + + static + VOID + FindDotNetFolders( + _In_ PCWSTR pszPath, + _Out_ std::vector *pvFolders + ); + + static + HRESULT + FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult + ); + + static + BOOL + DirectoryExists( + _In_ STRU *pstrPath //todo: this does not need to be stru, can be PCWSTR + ); + + static BOOL + GetEnv( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult + ); + + static + VOID + ExecuteAspNetCoreProcess( + _In_ LPVOID pContext + ); +}; \ No newline at end of file diff --git a/src/RequestHandler/inprocess/inprocesshandler.cpp b/src/RequestHandler/inprocess/inprocesshandler.cpp new file mode 100644 index 0000000000..92e41649cc --- /dev/null +++ b/src/RequestHandler/inprocess/inprocesshandler.cpp @@ -0,0 +1,146 @@ +#include "..\precomp.hxx" + +IN_PROCESS_HANDLER::IN_PROCESS_HANDLER( + _In_ IHttpContext *pW3Context, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication +): REQUEST_HANDLER(pW3Context, pModuleId, pApplication) +{ + m_fManagedRequestComplete = FALSE; +} + +IN_PROCESS_HANDLER::~IN_PROCESS_HANDLER() +{ + //todo +} + +__override +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_HANDLER::OnExecuteRequestHandler() +{ + // First get the in process Application + HRESULT hr; + hr = ((IN_PROCESS_APPLICATION*)m_pApplication)->LoadManagedApplication(); + if (FAILED(hr)) + { + // TODO remove com_error? + /*_com_error err(hr); + if (ANCMEvents::ANCM_START_APPLICATION_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_START_APPLICATION_FAIL::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL, + err.ErrorMessage()); + } + */ + //fInternalError = TRUE; + m_pW3Context->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; + } + + // FREB log + + if (ANCMEvents::ANCM_START_APPLICATION_SUCCESS::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_START_APPLICATION_SUCCESS::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL, + L"InProcess Application"); + } + + //SetHttpSysDisconnectCallback(); + return ((IN_PROCESS_APPLICATION*)m_pApplication)->OnExecuteRequest(m_pW3Context, this); +} + +__override +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_HANDLER::OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus +) +{ + HRESULT hr; + if (FAILED(hrCompletionStatus)) + { + return RQ_NOTIFICATION_FINISH_REQUEST; + } + else + { + // For now we are assuming we are in our own self contained box. + // TODO refactor Finished and Failure sections to handle in process and out of process failure. + // TODO verify that websocket's OnAsyncCompletion is not calling this. + IN_PROCESS_APPLICATION* application = (IN_PROCESS_APPLICATION*)m_pApplication; + if (application == NULL) + { + hr = E_FAIL; + return RQ_NOTIFICATION_FINISH_REQUEST; + } + + return application->OnAsyncCompletion(cbCompletion, hrCompletionStatus, this); + } +} + +VOID +IN_PROCESS_HANDLER::TerminateRequest( + bool fClientInitiated +) +{ + UNREFERENCED_PARAMETER(fClientInitiated); + //todo +} + +PVOID +IN_PROCESS_HANDLER::QueryManagedHttpContext( + VOID +) +{ + return m_pManagedHttpContext; +} + +BOOL +IN_PROCESS_HANDLER::QueryIsManagedRequestComplete( + VOID +) +{ + return m_fManagedRequestComplete; +} + +IHttpContext* +IN_PROCESS_HANDLER::QueryHttpContext( + VOID +) +{ + return m_pW3Context; +} + +VOID +IN_PROCESS_HANDLER::IndicateManagedRequestComplete( + VOID +) +{ + m_fManagedRequestComplete = TRUE; +} + +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_HANDLER::QueryAsyncCompletionStatus( + VOID +) +{ + return m_requestNotificationStatus; +} + +VOID +IN_PROCESS_HANDLER::SetAsyncCompletionStatus( + REQUEST_NOTIFICATION_STATUS requestNotificationStatus +) +{ + m_requestNotificationStatus = requestNotificationStatus; +} + +VOID +IN_PROCESS_HANDLER::SetManangedHttpContext( + PVOID pManagedHttpContext +) +{ + m_pManagedHttpContext = pManagedHttpContext; +} \ No newline at end of file diff --git a/src/RequestHandler/inprocess/inprocesshandler.h b/src/RequestHandler/inprocess/inprocesshandler.h new file mode 100644 index 0000000000..3e976d0b95 --- /dev/null +++ b/src/RequestHandler/inprocess/inprocesshandler.h @@ -0,0 +1,72 @@ +#pragma once + +class IN_PROCESS_HANDLER : public REQUEST_HANDLER +{ +public: + IN_PROCESS_HANDLER( + + _In_ IHttpContext *pW3Context, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication); + + ~IN_PROCESS_HANDLER(); + + __override + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler(); + + __override + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ); + + __override + VOID + TerminateRequest( + bool fClientInitiated + + ); + + PVOID + QueryManagedHttpContext( + VOID + ); + + VOID + SetManangedHttpContext( + PVOID pManagedHttpContext + ); + + IHttpContext* + QueryHttpContext( + VOID + ); + + BOOL + QueryIsManagedRequestComplete( + VOID + ); + + VOID + IndicateManagedRequestComplete( + VOID + ); + + REQUEST_NOTIFICATION_STATUS + QueryAsyncCompletionStatus( + VOID + ); + + VOID + SetAsyncCompletionStatus( + REQUEST_NOTIFICATION_STATUS requestNotificationStatus + ); + +private: + PVOID m_pManagedHttpContext; + IHttpContext* m_pHttpContext; + BOOL m_fManagedRequestComplete; + REQUEST_NOTIFICATION_STATUS m_requestNotificationStatus; +}; \ No newline at end of file diff --git a/src/RequestHandler/managedexports.cxx b/src/RequestHandler/managedexports.cxx new file mode 100644 index 0000000000..464f31e2ee --- /dev/null +++ b/src/RequestHandler/managedexports.cxx @@ -0,0 +1,395 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "precomp.hxx" + +// +// Initialization export +// +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +register_callbacks( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + IN_PROCESS_APPLICATION::GetInstance()->SetCallbackHandles( + request_handler, + shutdown_handler, + async_completion_handler, + pvRequstHandlerContext, + pvShutdownHandlerContext + ); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_REQUEST* +http_get_raw_request( + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + return pInProcessHandler->QueryHttpContext()->GetRequest()->GetRawHttpRequest(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_RESPONSE* +http_get_raw_response( + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + return pInProcessHandler->QueryHttpContext()->GetResponse()->GetRawHttpResponse(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ USHORT statusCode, + _In_ PCSTR pszReason +) +{ + pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(statusCode, pszReason); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_post_completion( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + DWORD cbBytes +) +{ + return pInProcessHandler->QueryHttpContext()->PostCompletion(cbBytes); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_set_completion_status( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ REQUEST_NOTIFICATION_STATUS requestNotificationStatus +) +{ + HRESULT hr = S_OK; + + pInProcessHandler->IndicateManagedRequestComplete(); + pInProcessHandler->SetAsyncCompletionStatus(requestNotificationStatus); + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_set_managed_context( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ PVOID pvManagedContext +) +{ + // todo: should we consider changing the signature + HRESULT hr = S_OK; + pInProcessHandler->SetManangedHttpContext(pvManagedContext); + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_indicate_completion( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ REQUEST_NOTIFICATION_STATUS notificationStatus +) +{ + pInProcessHandler->QueryHttpContext()->IndicateCompletion(notificationStatus); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_get_completion_info( + _In_ IHttpCompletionInfo2* info, + _Out_ DWORD* cbBytes, + _Out_ HRESULT* hr +) +{ + *cbBytes = info->GetCompletionBytes(); + *hr = info->GetCompletionStatus(); +} + +// +// todo: we should not rely on IN_PROCESS_APPLICATION::GetInstance() +// the signature should be changed. application's based address should be passed in +// + +struct IISConfigurationData +{ + BSTR pwzFullApplicationPath; + BSTR pwzVirtualApplicationPath; + BOOL fWindowsAuthEnabled; + BOOL fBasicAuthEnabled; + BOOL fAnonymousAuthEnable; +}; + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT // TODO probably should make this a wide string +http_get_application_properties( + _In_ IISConfigurationData* pIISCofigurationData +) +{ + ASPNETCORE_CONFIG* pConfiguration = NULL; + IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); + + if (pApplication == NULL) + { + return E_FAIL; + } + + pConfiguration = pApplication->QueryConfig(); + + pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationPhysicalPath()->QueryStr()); + pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr()); + pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled(); + pIISCofigurationData->fBasicAuthEnabled = pConfiguration->QueryBasicAuthEnabled(); + pIISCofigurationData->fAnonymousAuthEnable = pConfiguration->QueryAnonymousAuthEnabled(); + + return S_OK; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_read_request_bytes( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _Out_ CHAR* pvBuffer, + _In_ DWORD dwCbBuffer, + _Out_ DWORD* pdwBytesReceived, + _Out_ BOOL* pfCompletionPending +) +{ + HRESULT hr; + + if (pInProcessHandler == NULL) + { + return E_FAIL; + } + if (dwCbBuffer == 0) + { + return E_FAIL; + } + IHttpRequest *pHttpRequest = (IHttpRequest*)pInProcessHandler->QueryHttpContext()->GetRequest(); + + BOOL fAsync = TRUE; + + hr = pHttpRequest->ReadEntityBody( + pvBuffer, + dwCbBuffer, + fAsync, + pdwBytesReceived, + pfCompletionPending); + + if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + // We reached the end of the data + hr = S_OK; + } + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_write_response_bytes( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD dwChunks, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse *pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse(); + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent = 0; + + HRESULT hr = pHttpResponse->WriteEntityChunks( + pDataChunks, + dwChunks, + fAsync, + fMoreData, + &dwBytesSent, + pfCompletionExpected); + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_flush_response_bytes( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _Out_ BOOL* pfCompletionExpected +) +{ + IHttpResponse *pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent = 0; + + HRESULT hr = pHttpResponse->Flush( + fAsync, + fMoreData, + &dwBytesSent, + pfCompletionExpected); + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_websockets_read_bytes( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ CHAR* pvBuffer, + _In_ DWORD cbBuffer, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ DWORD* pDwBytesReceived, + _In_ BOOL* pfCompletionPending +) +{ + IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pInProcessHandler->QueryHttpContext()->GetRequest(); + + BOOL fAsync = TRUE; + + HRESULT hr = pHttpRequest->ReadEntityBody( + pvBuffer, + cbBuffer, + fAsync, + pfnCompletionCallback, + pvCompletionContext, + pDwBytesReceived, + pfCompletionPending); + + if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + // We reached the end of the data + hr = S_OK; + } + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_websockets_write_bytes( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD dwChunks, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent; + + HRESULT hr = pHttpResponse->WriteEntityChunks( + pDataChunks, + dwChunks, + fAsync, + fMoreData, + pfnCompletionCallback, + pvCompletionContext, + &dwBytesSent, + pfCompletionExpected); + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_websockets_flush_bytes( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent; + + HRESULT hr = pHttpResponse->Flush( + fAsync, + fMoreData, + pfnCompletionCallback, + pvCompletionContext, + &dwBytesSent, + pfCompletionExpected); + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_enable_websockets( + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + //if (!g_fWebSocketSupported) + //{ + // return E_FAIL; + //} + + ((IHttpContext3*)pInProcessHandler->QueryHttpContext())->EnableFullDuplex(); + ((IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse())->DisableBuffering(); + + return S_OK; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_cancel_io( + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + return pInProcessHandler->QueryHttpContext()->CancelIo(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_response_set_unknown_header( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ PCSTR pszHeaderName, + _In_ PCSTR pszHeaderValue, + _In_ USHORT usHeaderValueLength, + _In_ BOOL fReplace +) +{ + return pInProcessHandler->QueryHttpContext()->GetResponse()->SetHeader(pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_response_set_known_header( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ HTTP_HEADER_ID dwHeaderId, + _In_ PCSTR pszHeaderValue, + _In_ USHORT usHeaderValueLength, + _In_ BOOL fReplace +) +{ + return pInProcessHandler->QueryHttpContext()->GetResponse()->SetHeader(dwHeaderId, pszHeaderValue, usHeaderValueLength, fReplace); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_get_authentication_information( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _Out_ BSTR* pstrAuthType, + _Out_ VOID** pvToken +) +{ + *pstrAuthType = SysAllocString(pInProcessHandler->QueryHttpContext()->GetUser()->GetAuthenticationType()); + *pvToken = pInProcessHandler->QueryHttpContext()->GetUser()->GetPrimaryToken(); + + return S_OK; +} + +// End of export \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/forwarderconnection.cxx b/src/RequestHandler/outofprocess/forwarderconnection.cxx new file mode 100644 index 0000000000..99990f938c --- /dev/null +++ b/src/RequestHandler/outofprocess/forwarderconnection.cxx @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "..\precomp.hxx" + +FORWARDER_CONNECTION::FORWARDER_CONNECTION( + VOID +) : m_cRefs (1), + m_hConnection (NULL) +{ +} + +HRESULT +FORWARDER_CONNECTION::Initialize( + DWORD dwPort +) +{ + HRESULT hr = S_OK; + + hr = m_ConnectionKey.Initialize( dwPort ); + if ( FAILED( hr ) ) + { + goto Finished; + } + + m_hConnection = WinHttpConnect(g_hWinhttpSession, + L"127.0.0.1", + (USHORT) dwPort, + 0); + if (m_hConnection == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Since WinHttp will not emit WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING + // when closing WebSocket handle on Win8. Register callback at Connect level as a workaround + // + if (WinHttpSetStatusCallback(m_hConnection, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/forwarderconnection.h b/src/RequestHandler/outofprocess/forwarderconnection.h new file mode 100644 index 0000000000..232e239888 --- /dev/null +++ b/src/RequestHandler/outofprocess/forwarderconnection.h @@ -0,0 +1,157 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// The key used for hash-table lookups, consists of the port on which the http process is created. +// +class FORWARDER_CONNECTION_KEY +{ +public: + + FORWARDER_CONNECTION_KEY( + VOID + ) + { + } + + HRESULT + Initialize( + _In_ DWORD dwPort + ) + { + m_dwPort = dwPort; + return S_OK; + } + + BOOL + GetIsEqual( + const FORWARDER_CONNECTION_KEY * key2 + ) const + { + return m_dwPort == key2->m_dwPort; + } + + DWORD CalcKeyHash() const + { + // TODO: Review hash distribution. + return Hash(m_dwPort); + } + +private: + + DWORD m_dwPort; +}; + +class FORWARDER_CONNECTION +{ +public: + + FORWARDER_CONNECTION( + VOID + ); + + HRESULT + Initialize( + DWORD dwPort + ); + + HINTERNET + QueryHandle() const + { + return m_hConnection; + } + + VOID + ReferenceForwarderConnection() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceForwarderConnection() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + FORWARDER_CONNECTION_KEY * + QueryConnectionKey() + { + return &m_ConnectionKey; + } + +private: + + ~FORWARDER_CONNECTION() + { + if (m_hConnection != NULL) + { + WinHttpCloseHandle(m_hConnection); + m_hConnection = NULL; + } + } + + mutable LONG m_cRefs; + FORWARDER_CONNECTION_KEY m_ConnectionKey; + HINTERNET m_hConnection; +}; + +class FORWARDER_CONNECTION_HASH : + public HASH_TABLE +{ + +public: + + FORWARDER_CONNECTION_HASH() + {} + + FORWARDER_CONNECTION_KEY * + ExtractKey( + FORWARDER_CONNECTION *pConnection + ) + { + return pConnection->QueryConnectionKey(); + } + + DWORD + CalcKeyHash( + FORWARDER_CONNECTION_KEY *key + ) + { + return key->CalcKeyHash(); + } + + BOOL + EqualKeys( + FORWARDER_CONNECTION_KEY *key1, + FORWARDER_CONNECTION_KEY *key2 + ) + { + return key1->GetIsEqual(key2); + } + + VOID + ReferenceRecord( + FORWARDER_CONNECTION *pConnection + ) + { + pConnection->ReferenceForwarderConnection(); + } + + VOID + DereferenceRecord( + FORWARDER_CONNECTION *pConnection + ) + { + pConnection->DereferenceForwarderConnection(); + } + +private: + + FORWARDER_CONNECTION_HASH(const FORWARDER_CONNECTION_HASH &); + void operator=(const FORWARDER_CONNECTION_HASH &); +}; \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/forwardinghandler.cpp b/src/RequestHandler/outofprocess/forwardinghandler.cpp new file mode 100644 index 0000000000..293c56bfd5 --- /dev/null +++ b/src/RequestHandler/outofprocess/forwardinghandler.cpp @@ -0,0 +1,2627 @@ +#include "..\precomp.hxx" + +// Just to be aware of the FORWARDING_HANDLER object size. +C_ASSERT(sizeof(FORWARDING_HANDLER) <= 632); + +#define DEF_MAX_FORWARDS 32 +#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) +#define BUFFER_SIZE (8192UL) +#define ENTITY_BUFFER_SIZE (6 + BUFFER_SIZE + 2) + +#define FORWARDING_HANDLER_SIGNATURE ((DWORD)'FHLR') +#define FORWARDING_HANDLER_SIGNATURE_FREE ((DWORD)'fhlr') + +STRA FORWARDING_HANDLER::sm_pStra502ErrorMsg; +ALLOC_CACHE_HANDLER * FORWARDING_HANDLER::sm_pAlloc = NULL; +TRACE_LOG * FORWARDING_HANDLER::sm_pTraceLog = NULL; +PROTOCOL_CONFIG FORWARDING_HANDLER::sm_ProtocolConfig; +RESPONSE_HEADER_HASH * FORWARDING_HANDLER::sm_pResponseHeaderHash = NULL; + +FORWARDING_HANDLER::FORWARDING_HANDLER( + _In_ IHttpContext *pW3Context, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication +) : REQUEST_HANDLER(pW3Context, pModuleId, pApplication), + m_Signature(FORWARDING_HANDLER_SIGNATURE), + m_RequestStatus(FORWARDER_START), + m_fHandleClosedDueToClient(FALSE), + m_fResponseHeadersReceivedAndSet(FALSE), + m_fDoReverseRewriteHeaders(FALSE), + m_fFinishRequest(FALSE), + m_fHasError(FALSE), + m_pszHeaders(NULL), + m_cchHeaders(0), + m_BytesToReceive(0), + m_BytesToSend(0), + m_fWebSocketEnabled(FALSE), + m_pWebSocket(NULL) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::FORWARDING_HANDLER"); +} + +FORWARDING_HANDLER::~FORWARDING_HANDLER( +) +{ + // + // Destructor has started. + // + m_Signature = FORWARDING_HANDLER_SIGNATURE_FREE; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::~FORWARDING_HANDLER"); + // + // RemoveRequest() should already have been called and m_pDisconnect + // has been freed or m_pDisconnect was never initialized. + // + // Disconnect notification cleanup would happen first, before + // the FORWARDING_HANDLER instance got removed from m_pSharedhandler list. + // The m_pServer cleanup would happen afterwards, since there may be a + // call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders() + // + DBG_ASSERT(m_pDisconnect == NULL); + + if (m_pDisconnect != NULL) + { + m_pDisconnect->ResetHandler(); + m_pDisconnect = NULL; + } + + FreeResponseBuffers(); + + if (m_pWebSocket) + { + m_pWebSocket->Terminate(); + m_pWebSocket = NULL; + } +} + +__override +REQUEST_NOTIFICATION_STATUS +FORWARDING_HANDLER::OnExecuteRequestHandler() +{ + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; + HRESULT hr = S_OK; + bool fRequestLocked = FALSE; + bool fHandleSet = FALSE; + bool fFailedToStartKestrel = FALSE; + BOOL fSecure = FALSE; + HINTERNET hConnect = NULL; + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + IHttpResponse *pResponse = m_pW3Context->GetResponse(); + IHttpConnection *pClientConnection = NULL; + OUT_OF_PROCESS_APPLICATION *pApplication = NULL; + PROTOCOL_CONFIG *pProtocol = &sm_ProtocolConfig; + SERVER_PROCESS *pServerProcess = NULL; + + USHORT cchHostName = 0; + + STACK_STRU(strDestination, 32); + STACK_STRU(strUrl, 2048); + STACK_STRU(struEscapedUrl, 2048); + + // override Protocol related config from aspNetCore config + pProtocol->OverrideConfig(m_pApplication->QueryConfig()); + + // check connection + pClientConnection = m_pW3Context->GetConnection(); + if (pClientConnection == NULL || + !pClientConnection->IsConnected()) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + + pApplication = static_cast (m_pApplication); + if (pApplication == NULL) + { + hr = E_INVALIDARG; + goto Finished; + } + + hr = pApplication->GetProcess(&pServerProcess); + if (FAILED(hr)) + { + fFailedToStartKestrel = TRUE; + goto Failure; + } + + if (pServerProcess == NULL) + { + fFailedToStartKestrel = TRUE; + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Failure; + } + + if (pServerProcess->QueryWinHttpConnection() == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE); + goto Failure; + } + + hConnect = pServerProcess->QueryWinHttpConnection()->QueryHandle(); + + m_pszOriginalHostHeader = pRequest->GetHeader(HttpHeaderHost, &cchHostName); + // + // parse original url + // + if (FAILED(hr = UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + &fSecure, + &strDestination, + &strUrl))) + { + goto Failure; + } + + if (FAILED(hr = UTILITY::EscapeAbsPath(pRequest, &struEscapedUrl))) + { + goto Failure; + } + + m_fDoReverseRewriteHeaders = pProtocol->QueryReverseRewriteHeaders(); + + m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); + + // + // Mark request as websocket if upgrade header is present. + // + if (g_fWebSocketSupported) + { + USHORT cchHeader = 0; + PCSTR pszWebSocketHeader = pRequest->GetHeader("Upgrade", &cchHeader); + if (cchHeader == 9 && _stricmp(pszWebSocketHeader, "websocket") == 0) + { + m_fWebSocketEnabled = TRUE; + } + } + + hr = CreateWinHttpRequest(pRequest, + pProtocol, + hConnect, + &struEscapedUrl, + pServerProcess); + if (FAILED(hr)) + { + goto Failure; + } + + // Set client disconnect callback contract with IIS + m_pDisconnect = static_cast( + pClientConnection->GetModuleContextContainer()-> + GetConnectionModuleContext(m_pModuleId)); + if (m_pDisconnect == NULL) + { + m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT(); + if (m_pDisconnect == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = pClientConnection->GetModuleContextContainer()-> + SetConnectionModuleContext(m_pDisconnect, + m_pModuleId); + DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); + if (FAILED(hr)) + { + goto Failure; + } + } + + m_pDisconnect->SetHandler(this); + fHandleSet = TRUE; + + // require lock as client disconnect callback may happen + AcquireSRWLockShared(&m_RequestLock); + fRequestLocked = TRUE; + + // + // Remember the handler being processed in the current thread + // before staring a WinHTTP operation. + // + DBG_ASSERT(fRequestLocked); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + if (m_hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + + // + // Begins normal request handling. Send request to server. + // + m_RequestStatus = FORWARDER_SENDING_REQUEST; + + // + // Calculate the bytes to receive from the content length. + // + DWORD cbContentLength = 0; + PCSTR pszContentLength = pRequest->GetHeader(HttpHeaderContentLength); + if (pszContentLength != NULL) + { + cbContentLength = m_BytesToReceive = atol(pszContentLength); + if (m_BytesToReceive == INFINITE) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + } + else if (pRequest->GetHeader(HttpHeaderTransferEncoding) != NULL) + { + m_BytesToReceive = INFINITE; + } + + if (m_fWebSocketEnabled) + { + // + // Set the upgrade flag for a websocket request. + // + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, + NULL, + 0)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + m_cchLastSend = m_cchHeaders; + + //FREB log + if (ANCMEvents::ANCM_REQUEST_FORWARD_START::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_REQUEST_FORWARD_START::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL); + } + + if (!WinHttpSendRequest(m_hRequest, + m_pszHeaders, + m_cchHeaders, + NULL, + 0, + cbContentLength, + reinterpret_cast(static_cast(this)))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); + + // FREB log + if (ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL, + hr); + } + + goto Failure; + } + + // + // Async WinHTTP operation is in progress. Release this thread meanwhile, + // OnWinHttpCompletion method should resume the work by posting an IIS completion. + // + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + +Failure: + m_RequestStatus = FORWARDER_DONE; + + //disbale client disconnect callback + if (m_pDisconnect != NULL) + { + if (fHandleSet) + { + m_pDisconnect->ResetHandler(); + } + m_pDisconnect->CleanupStoredContext(); + m_pDisconnect = NULL; + } + + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + if (hr == HRESULT_FROM_WIN32(WSAECONNRESET)) + { + pResponse->SetStatus(400, "Bad Request", 0, hr); + } + else + { + HTTP_DATA_CHUNK DataChunk; + pResponse->SetStatus(502, "Bad Gateway", 5, hr, NULL, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); + + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = (PVOID)sm_pStra502ErrorMsg.QueryStr(); + DataChunk.FromMemory.BufferLength = sm_pStra502ErrorMsg.QueryCB(); + pResponse->WriteEntityChunkByReference(&DataChunk); + } + // + // Finish the request on failure. + // + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + +Finished: + if (fRequestLocked) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + return retVal; +} + +__override +REQUEST_NOTIFICATION_STATUS +FORWARDING_HANDLER::OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus +) +/*++ + +Routine Description: + +Handle the completion from IIS and continue the execution +of this request based on the current state. + +Arguments: + +cbCompletion - Number of bytes associated with this completion +dwCompletionStatus - the win32 status associated with this completion + +Return Value: + +REQUEST_NOTIFICATION_STATUS + +--*/ +{ + HRESULT hr = S_OK; + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_PENDING; + bool fLocked = FALSE; + bool fClientError = FALSE; + bool fClosed = FALSE; + + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + + // + // Take a reference so that object does not go away as a result of + // async completion. + // + ReferenceRequestHandler(); + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnAsyncCompletion Enter", + reinterpret_cast(static_cast(cbCompletion)), + reinterpret_cast(static_cast(hrCompletionStatus))); + } + + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + fLocked = TRUE; + } + + if (m_hRequest == NULL) + { + // Request is Done + if (m_fFinishRequest) + { + if (m_fHasError) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + } + else + { + retVal = RQ_NOTIFICATION_CONTINUE; + } + goto Finished; + } + + fClientError = m_fHandleClosedDueToClient; + goto Failure; + } + + // + // Begins normal completion handling. There is already a shared acquired + // for protecting the WinHTTP request handle from being closed. + // + switch (m_RequestStatus) + { + case FORWARDER_RECEIVED_WEBSOCKET_RESPONSE: + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnAsyncCompletion, Send completed for 101 response"); + // + // This should be the write completion of the 101 response. + // + m_pWebSocket = new WEBSOCKET_HANDLER(); + if (m_pWebSocket == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pWebSocket->ProcessRequest(this, m_pW3Context, m_hRequest); + if (FAILED(hr)) + { + goto Failure; + } + + // + // WebSocket upgrade is successful. Close the WinHttpRequest Handle + // + fClosed = WinHttpCloseHandle(m_hRequest); + + DBG_ASSERT(fClosed); + if (fClosed) + { + m_hRequest = NULL; + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + break; + + case FORWARDER_RECEIVING_RESPONSE: + // + // This is a completion of a write (send) to http.sys, abort in case of + // failure, if there is more data available from WinHTTP, read it + // or else ask if there is more. + // + if (FAILED(hrCompletionStatus)) + { + hr = hrCompletionStatus; + fClientError = TRUE; + goto Failure; + } + + hr = OnReceivingResponse(); + if (FAILED(hr)) + { + goto Failure; + } + break; + + case FORWARDER_SENDING_REQUEST: + hr = OnSendingRequest(cbCompletion, + hrCompletionStatus, + &fClientError); + if (FAILED(hr)) + { + goto Failure; + } + break; + + default: + DBG_ASSERT(m_RequestStatus == FORWARDER_DONE); + goto Finished; + } + + // + // Either OnReceivingResponse or OnSendingRequest initiated an + // async WinHTTP operation, release this thread meanwhile, + // OnWinHttpCompletion method should resume the work by posting an IIS completion. + // + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + +Failure: + + m_fHasError = TRUE; + m_RequestStatus = FORWARDER_DONE; + + //disbale client disconnect callback + if (m_pDisconnect != NULL) + { + m_pDisconnect->ResetHandler(); + m_pDisconnect = NULL; + } + + // + // Do the right thing based on where the error originated from. + // + IHttpResponse *pResponse = m_pW3Context->GetResponse(); + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + if (fClientError) + { + if (!m_fResponseHeadersReceivedAndSet) + { + pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); + } + else + { + // + // Response headers from origin server were + // already received and set for the current response. + // Honor the response status. + // + } + } + else + { + STACK_STRU(strDescription, 128); + + pResponse->SetStatus(502, "Bad Gateway", 3, hr); + +// if (!(hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && +// hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) || +//#pragma prefast (suppress : __WARNING_FUNCTION_NEEDS_REVIEW, "Function and parameters reviewed.") +// FormatMessage( +// FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, +// g_hWinHttpModule, +// HRESULT_CODE(hr), +// 0, +// strDescription.QueryStr(), +// strDescription.QuerySizeCCH(), +// NULL) == 0) +// { +// /*LoadString(g_hModule, +// IDS_SERVER_ERROR, +// strDescription.QueryStr(), +// strDescription.QuerySizeCCH());*/ +// } +// +// (VOID)strDescription.SyncWithBuffer(); +// if (strDescription.QueryCCH() != 0) +// { +// pResponse->SetErrorDescription( +// strDescription.QueryStr(), +// strDescription.QueryCCH(), +// FALSE); +// } + } + + // + // Finish the request on failure. + // Let IIS pipeline continue only after receiving handle close callback + // from WinHttp. This ensures no more callback from WinHttp + // + if (m_hRequest != NULL) + { + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } + else + { + // Failed to close the handle + // which should never happen as we registered a callback with WinHttp + // For this unexpected failure, let conitnue IIS pipeline + /* retVal = RQ_NOTIFICATION_FINISH_REQUEST; + DebugBreak();*/ + } + } + retVal = RQ_NOTIFICATION_PENDING; + +Finished: + if (fLocked) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + DereferenceRequestHandler(); + // + // No code after this point, as the handle might be gone + // + return retVal; +} + +// static +HRESULT +FORWARDING_HANDLER::StaticInitialize( + BOOL fEnableReferenceCountTracing +) +/*++ + +Routine Description: + +Global initialization routine for FORWARDING_HANDLERs + +Arguments: + +fEnableReferenceCountTracing - True if ref count tracing should be use. + +Return Value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + sm_pAlloc = new ALLOC_CACHE_HANDLER; + if (sm_pAlloc == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = sm_pAlloc->Initialize(sizeof(FORWARDING_HANDLER), + 64); // nThreshold + if (FAILED(hr)) + { + goto Finished; + } + + sm_pResponseHeaderHash = new RESPONSE_HEADER_HASH; + if (sm_pResponseHeaderHash == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = sm_pResponseHeaderHash->Initialize(); + if (FAILED(hr)) + { + goto Finished; + } + + // Initialize PROTOCOL_CONFIG + hr = sm_ProtocolConfig.Initialize(); + if (FAILED(hr)) + { + goto Finished; + } + + if (fEnableReferenceCountTracing) + { + sm_pTraceLog = CreateRefTraceLog(10000, 0); + } + + sm_pStra502ErrorMsg.Copy( + " \ + \ + \ + \ + IIS 502.5 Error \ +
\ +

HTTP Error 502.5 - Process Failure

\ +
\ +

Common causes of this issue:

\ +
  • The application process failed to start
  • \ +
  • The application process started but then stopped
  • \ +
  • The application process started but failed to listen on the configured port
\ +
\ +
\ +

Troubleshooting steps:

\ +
  • Check the system event log for error messages
  • \ +
  • Enable logging the application process' stdout messages
  • \ +
  • Attach a debugger to the application process and inspect
\ +

For more information visit: \ + https://go.microsoft.com/fwlink/?LinkID=808681

\ +
\ +
\ +
"); + +Finished: + if (FAILED(hr)) + { + StaticTerminate(); + } + return hr; +} + +//static +VOID +FORWARDING_HANDLER::StaticTerminate() +{ + sm_pStra502ErrorMsg.Reset(); + + if (sm_pResponseHeaderHash != NULL) + { + sm_pResponseHeaderHash->Clear(); + delete sm_pResponseHeaderHash; + sm_pResponseHeaderHash = NULL; + } + + if (sm_pTraceLog != NULL) + { + DestroyRefTraceLog(sm_pTraceLog); + sm_pTraceLog = NULL; + } + + if (sm_pAlloc != NULL) + { + delete sm_pAlloc; + sm_pAlloc = NULL; + } +} + +HRESULT +FORWARDING_HANDLER::GetHeaders( + _In_ const PROTOCOL_CONFIG * pProtocol, + _In_ bool fForwardWindowsAuthToken, + _In_ SERVER_PROCESS* pServerProcess, + _Out_ PCWSTR * ppszHeaders, + _Inout_ DWORD * pcchHeaders +) +{ + HRESULT hr = S_OK; + PCSTR pszCurrentHeader; + PCSTR ppHeadersToBeRemoved; + PCSTR pszFinalHeader; + USHORT cchCurrentHeader; + DWORD cchFinalHeader; + BOOL fSecure = FALSE; // dummy. Used in SplitUrl. Value will not be used + // as ANCM always use http protocol to communicate with backend + STRU struDestination; + STRU struUrl; + STACK_STRA(strTemp, 64); + HTTP_REQUEST_HEADERS *pHeaders; + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + MULTISZA mszMsAspNetCoreHeaders; + + // + // We historically set the host section in request url to the new host header + // this is wrong but Kestrel has dependency on it. + // should change it in the future + // + if (!pProtocol->QueryPreserveHostHeader()) + { + if (FAILED(hr = UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + &fSecure, + &struDestination, + &struUrl)) || + FAILED(hr = strTemp.CopyW(struDestination.QueryStr())) || + FAILED(hr = pRequest->SetHeader(HttpHeaderHost, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + // + // Strip all headers starting with MS-ASPNETCORE. + // These headers are generated by the asp.net core module and + // passed to the process it creates. + // + + pHeaders = &m_pW3Context->GetRequest()->GetRawHttpRequest()->Headers; + for (DWORD i = 0; iUnknownHeaderCount; i++) + { + if (_strnicmp(pHeaders->pUnknownHeaders[i].pName, "MS-ASPNETCORE", 13) == 0) + { + mszMsAspNetCoreHeaders.Append(pHeaders->pUnknownHeaders[i].pName, (DWORD)pHeaders->pUnknownHeaders[i].NameLength); + } + } + + ppHeadersToBeRemoved = mszMsAspNetCoreHeaders.First(); + + // + // iterate the list of headers to be removed and delete them from the request. + // + + while (ppHeadersToBeRemoved != NULL) + { + m_pW3Context->GetRequest()->DeleteHeader(ppHeadersToBeRemoved); + ppHeadersToBeRemoved = mszMsAspNetCoreHeaders.Next(ppHeadersToBeRemoved); + } + + if (pServerProcess->QueryGuid() != NULL) + { + hr = m_pW3Context->GetRequest()->SetHeader("MS-ASPNETCORE-TOKEN", + pServerProcess->QueryGuid(), + (USHORT)strlen(pServerProcess->QueryGuid()), + TRUE); + if (FAILED(hr)) + { + return hr; + } + } + + if (fForwardWindowsAuthToken && + (_wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"negotiate") == 0 || + _wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"ntlm") == 0)) + { + if (m_pW3Context->GetUser()->GetPrimaryToken() != NULL && + m_pW3Context->GetUser()->GetPrimaryToken() != INVALID_HANDLE_VALUE) + { + HANDLE hTargetTokenHandle = NULL; + hr = pServerProcess->SetWindowsAuthToken(m_pW3Context->GetUser()->GetPrimaryToken(), + &hTargetTokenHandle); + if (FAILED(hr)) + { + return hr; + } + + // + // set request header with target token value + // + CHAR pszHandleStr[16] = { 0 }; + if (_ui64toa_s((UINT64)hTargetTokenHandle, pszHandleStr, 16, 16) != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + return hr; + } + + hr = m_pW3Context->GetRequest()->SetHeader("MS-ASPNETCORE-WINAUTHTOKEN", + pszHandleStr, + (USHORT)strlen(pszHandleStr), + TRUE); + if (FAILED(hr)) + { + return hr; + } + } + } + + if (!pProtocol->QueryXForwardedForName()->IsEmpty()) + { + strTemp.Reset(); + + pszCurrentHeader = pRequest->GetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), &cchCurrentHeader); + if (pszCurrentHeader != NULL) + { + if (FAILED(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || + FAILED(hr = strTemp.Append(", ", 2))) + { + return hr; + } + } + + if (FAILED(hr = m_pW3Context->GetServerVariable("REMOTE_ADDR", + &pszFinalHeader, + &cchFinalHeader))) + { + return hr; + } + + if (pRequest->GetRawHttpRequest()->Address.pRemoteAddress->sa_family == AF_INET6) + { + if (FAILED(hr = strTemp.Append("[", 1)) || + FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader)) || + FAILED(hr = strTemp.Append("]", 1))) + { + return hr; + } + } + else + { + if (FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) + { + return hr; + } + } + + if (pProtocol->QueryIncludePortInXForwardedFor()) + { + if (FAILED(hr = m_pW3Context->GetServerVariable("REMOTE_PORT", + &pszFinalHeader, + &cchFinalHeader))) + { + return hr; + } + + if (FAILED(hr = strTemp.Append(":", 1)) || + FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) + { + return hr; + } + } + + if (FAILED(hr = pRequest->SetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + + if (!pProtocol->QuerySslHeaderName()->IsEmpty()) + { + const HTTP_SSL_INFO *pSslInfo = pRequest->GetRawHttpRequest()->pSslInfo; + LPSTR pszScheme = "http"; + if (pSslInfo != NULL) + { + pszScheme = "https"; + } + + strTemp.Reset(); + + pszCurrentHeader = pRequest->GetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), &cchCurrentHeader); + if (pszCurrentHeader != NULL) + { + if (FAILED(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || + FAILED(hr = strTemp.Append(", ", 2))) + { + return hr; + } + } + + if (FAILED(hr = strTemp.Append(pszScheme))) + { + return hr; + } + + if (FAILED(pRequest->SetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), + strTemp.QueryStr(), + (USHORT)strTemp.QueryCCH(), + TRUE))) + { + return hr; + } + } + + if (!pProtocol->QueryClientCertName()->IsEmpty()) + { + if (pRequest->GetRawHttpRequest()->pSslInfo == NULL || + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo == NULL) + { + pRequest->DeleteHeader(pProtocol->QueryClientCertName()->QueryStr()); + } + else + { + // Resize the buffer large enough to hold the encoded certificate info + if (FAILED(hr = strTemp.Resize( + 1 + (pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize + 2) / 3 * 4))) + { + return hr; + } + + Base64Encode( + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->pCertEncoded, + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize, + strTemp.QueryStr(), + strTemp.QuerySize(), + NULL); + strTemp.SyncWithBuffer(); + + if (FAILED(hr = pRequest->SetHeader( + pProtocol->QueryClientCertName()->QueryStr(), + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + } + + // + // Remove the connection header + // + if (!m_fWebSocketEnabled) + { + pRequest->DeleteHeader(HttpHeaderConnection); + } + + // + // Get all the headers to send to the client + // + hr = m_pW3Context->GetServerVariable("ALL_RAW", + ppszHeaders, + pcchHeaders); + if (FAILED(hr)) + { + return hr; + } + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::CreateWinHttpRequest( + _In_ const IHttpRequest * pRequest, + _In_ const PROTOCOL_CONFIG * pProtocol, + _In_ HINTERNET hConnect, + _Inout_ STRU * pstrUrl, +// _In_ ASPNETCORE_CONFIG* pAspNetCoreConfig, + _In_ SERVER_PROCESS* pServerProcess +) +{ + HRESULT hr = S_OK; + PCWSTR pszVersion = NULL; + PCSTR pszVerb; + STACK_STRU(strVerb, 32); + + // + // Create the request handle for this request (leave some fields blank, + // we will fill them when sending the request) + // + pszVerb = pRequest->GetHttpMethod(); + if (FAILED(hr = strVerb.CopyA(pszVerb))) + { + goto Finished; + } + + //pszVersion = pProtocol->QueryVersion(); + if (pszVersion == NULL) + { + DWORD cchUnused; + hr = m_pW3Context->GetServerVariable( + "HTTP_VERSION", + &pszVersion, + &cchUnused); + if (FAILED(hr)) + { + goto Finished; + } + } + + m_hRequest = WinHttpOpenRequest(hConnect, + strVerb.QueryStr(), + pstrUrl->QueryStr(), + pszVersion, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_ESCAPE_DISABLE_QUERY + | g_OptionalWinHttpFlags); + if (m_hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpSetTimeouts(m_hRequest, + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout())) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + DWORD dwResponseBufferLimit = pProtocol->QueryResponseBufferLimit(); + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE, + &dwResponseBufferLimit, + sizeof(dwResponseBufferLimit))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + DWORD dwMaxHeaderSize = pProtocol->QueryMaxResponseHeaderSize(); + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE, + &dwMaxHeaderSize, + sizeof(dwMaxHeaderSize))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + DWORD dwOption = WINHTTP_DISABLE_COOKIES; + + dwOption |= WINHTTP_DISABLE_AUTHENTICATION; + + if (!pProtocol->QueryDoKeepAlive()) + { + dwOption |= WINHTTP_DISABLE_KEEP_ALIVE; + } + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_DISABLE_FEATURE, + &dwOption, + sizeof(dwOption))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_FLAG_HANDLES | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hr = GetHeaders(pProtocol, + m_pApplication->QueryConfig()->QueryForwardWindowsAuthToken(), + pServerProcess, + &m_pszHeaders, + &m_cchHeaders); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + return hr; +} + +VOID +FORWARDING_HANDLER::OnWinHttpCompletion( + HINTERNET hRequest, + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength +) +{ + FORWARDING_HANDLER * pThis = static_cast(reinterpret_cast(dwContext)); + if (pThis == NULL) + { + //error happened, nothing can be done here + return; + } + DBG_ASSERT(pThis->m_Signature == FORWARDING_HANDLER_SIGNATURE); + pThis->OnWinHttpCompletionInternal(hRequest, + dwInternetStatus, + lpvStatusInformation, + dwStatusInformationLength); +} + +VOID +FORWARDING_HANDLER::OnWinHttpCompletionInternal( + _In_ HINTERNET hRequest, + _In_ DWORD dwInternetStatus, + _In_ LPVOID lpvStatusInformation, + _In_ DWORD dwStatusInformationLength +) +/*++ + +Routine Description: + +Completion call associated with a WinHTTP operation + +Arguments: + +hRequest - The winhttp request handle associated with this completion +dwInternetStatus - enum specifying what the completion is for +lpvStatusInformation - completion specific information +dwStatusInformationLength - length of the above information + +Return Value: + +None + +--*/ +{ + HRESULT hr = S_OK; + bool fLockAcquired = FALSE; + bool fClientError = FALSE; + bool fAnotherCompletionExpected = FALSE; + bool fDoPostCompletion = FALSE; + bool fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); + + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + + // Reference the request handler to prevent it from being released prematurely + ReferenceRequestHandler(); + + UNREFERENCED_PARAMETER(dwStatusInformationLength); + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal Enter", + reinterpret_cast(static_cast(dwInternetStatus)), + NULL); + } + + //FREB log + if (ANCMEvents::ANCM_WINHTTP_CALLBACK::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_WINHTTP_CALLBACK::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL, + dwInternetStatus); + } + + // + // ReadLock on the winhttp handle to protect from a client disconnect/ + // server stop closing the handle while we are using it. + // + // WinHttp can call async completion on the same thread/stack, so + // we have to account for that and not try to take the lock again, + // otherwise, we could end up in a deadlock. + // + + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + fLockAcquired = TRUE; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + } + +#ifdef DEBUG + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x -- %d --%p\n", dwInternetStatus, GetCurrentThreadId(), m_pW3Context); +#endif // DEBUG + + if (!fEndRequest) + { + if (!m_pW3Context->GetConnection()->IsConnected()) + { + hr = ERROR_CONNECTION_ABORTED; + fClientError = m_fHandleClosedDueToClient = TRUE; + fAnotherCompletionExpected = TRUE; + goto Failure; + } + } + + // + // In case of websocket, http request handle (m_hRequest) will be closed immediately after upgrading success + // This close will trigger a callback with WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING + // As m_RequestStatus is FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, this callback will be skipped. + // When WebSocket handle (m_pWebsocket) gets closed, another winhttp handle close callback will be triggered + // This callback will be captured and then notify IIS pipeline to continue + // This ensures no request leaks + // + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + fAnotherCompletionExpected = TRUE; + if (m_pWebSocket == NULL) + { + goto Finished; + } + + switch (dwInternetStatus) + { + case WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE: + m_pWebSocket->OnWinHttpShutdownComplete(); + break; + + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + m_pWebSocket->OnWinHttpSendComplete( + (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation + ); + break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + m_pWebSocket->OnWinHttpReceiveComplete( + (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation + ); + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + m_pWebSocket->OnWinHttpIoError( + (WINHTTP_WEB_SOCKET_ASYNC_RESULT*)lpvStatusInformation + ); + break; + } + goto Finished; + } + + switch (dwInternetStatus) + { + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + hr = OnWinHttpCompletionSendRequestOrWriteComplete(hRequest, + dwInternetStatus, + &fClientError, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: + hr = OnWinHttpCompletionStatusHeadersAvailable(hRequest, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: + hr = OnWinHttpCompletionStatusDataAvailable(hRequest, + *reinterpret_cast(lpvStatusInformation), // dwBytes + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + hr = OnWinHttpCompletionStatusReadComplete(pResponse, + dwStatusInformationLength, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + hr = HRESULT_FROM_WIN32(static_cast(lpvStatusInformation)->dwError); + break; + + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + // + // This is a notification, not a completion. This notifiation happens + // during the Send Request operation. + // + fAnotherCompletionExpected = TRUE; + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_SENT: + // + // Need to ignore this event. We get it as a side-effect of registering + // for WINHTTP_CALLBACK_STATUS_SENDING_REQUEST (which we actually need). + // + hr = S_OK; + fAnotherCompletionExpected = TRUE; + break; + + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + if (ANCMEvents::ANCM_REQUEST_FORWARD_END::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_REQUEST_FORWARD_END::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL); + } + if (m_RequestStatus != FORWARDER_DONE || m_fHandleClosedDueToClient) + { + hr = ERROR_CONNECTION_ABORTED; + fClientError = m_fHandleClosedDueToClient; + } + m_hRequest = NULL; + fAnotherCompletionExpected = FALSE; + break; + + case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: + hr = ERROR_CONNECTION_ABORTED; + break; + + default: + // + // E_UNEXPECTED is rarely used, if seen means that this condition may been occurred. + // + DBG_ASSERT(FALSE); + hr = E_UNEXPECTED; + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal Unexpected WinHTTP Status", + reinterpret_cast(static_cast(dwInternetStatus)), + NULL); + } + break; + } + + // + // Handle failure code for switch statement above. + // + if (FAILED(hr)) + { + goto Failure; + } + + // + // WinHTTP completion handled successfully. + // + goto Finished; + +Failure: + + m_RequestStatus = FORWARDER_DONE; + m_fHasError = TRUE; + + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + m_fResetConnection = TRUE; + } + + if (fClientError || m_fHandleClosedDueToClient) + { + if (!m_fResponseHeadersReceivedAndSet) + { + pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); + } + else + { + // + // Response headers from origin server were + // already received and set for the current response. + // Honor the response status. + // + } + } + else + { + STACK_STRU(strDescription, 128); + + pResponse->SetStatus(502, "Bad Gateway", 3, hr); + /* + if (!(hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && + hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) || +#pragma prefast (suppress : __WARNING_FUNCTION_NEEDS_REVIEW, "Function and parameters reviewed.") + FormatMessage( + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL) == 0) + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + + strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + }*/ + } + //} + + // FREB log + if (ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL, + hr); + } + +Finished: + + if (fLockAcquired) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + if (m_RequestStatus == FORWARDER_DONE) + { + //disbale client disconnect callback + if (m_pDisconnect != NULL) + { + m_pDisconnect->ResetHandler(); + m_pDisconnect = NULL; + } + + if (m_hRequest != NULL) + { + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } + else + { + // unexpected WinHttp error, log it + /*DebugBreak(); + m_RequestStatus = FORWARDER_FINISH_REQUEST; + fDoPostCompletion = TRUE;*/ + } + } + + // + // If the request is a websocket request, initiate cleanup. + // + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + } + + if (fEndRequest) + { + // only postCompletion after WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING + // so that no further WinHttp callback will be called + // in case of websocket, m_hRequest has already been closed after upgrade + // websocket will handle completion + m_fFinishRequest = TRUE; + fDoPostCompletion = TRUE; + } + } + // + // Completion may had been already posted to IIS if an async + // operation was started in this method (either WinHTTP or IIS e.g. ReadyEntityBody) + // If fAnotherCompletionExpected is false, this method must post the completion. + // + else if (!fAnotherCompletionExpected) + { + // + // Since we use TLS to guard WinHttp operation, call PostCompletion instead of + // IndicateCompletion to allow cleaning up the TLS before thread reuse. + // + fDoPostCompletion = TRUE; + } + + DereferenceRequestHandler(); + if (fDoPostCompletion) + { + m_pW3Context->PostCompletion(0); + } +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( + HINTERNET hRequest, + DWORD, + __out bool * pfClientError, + __out bool * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + IHttpRequest * pRequest = m_pW3Context->GetRequest(); + + // + // completion for sending the initial request or request entity to + // winhttp, get more request entity if available, else start receiving + // the response + // + if (m_BytesToReceive > 0) + { + if (m_pEntityBuffer == NULL) + { + m_pEntityBuffer = GetNewResponseBuffer( + ENTITY_BUFFER_SIZE); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "Calling ReadEntityBody", + NULL, + NULL); + } + hr = pRequest->ReadEntityBody( + m_pEntityBuffer + 6, + min(m_BytesToReceive, BUFFER_SIZE), + TRUE, // fAsync + NULL, // pcbBytesReceived + NULL); // pfCompletionPending + if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + DBG_ASSERT(m_BytesToReceive == 0 || + m_BytesToReceive == INFINITE); + + // + // ERROR_HANDLE_EOF is not an error. + // + hr = S_OK; + + if (m_BytesToReceive == INFINITE) + { + m_BytesToReceive = 0; + m_cchLastSend = 5; + + // + // WinHttpWriteData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + //ReferenceForwardingHandler(); + if (!WinHttpWriteData(m_hRequest, + "0\r\n\r\n", + 5, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + //DereferenceForwardingHandler(); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + + goto Finished; + } + } + else if (FAILED(hr)) + { + *pfClientError = TRUE; + goto Finished; + } + else + { + // + // ReadEntityBody will post a completion to IIS. + // + *pfAnotherCompletionExpected = TRUE; + + goto Finished; + } + } + + m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; + + if (!WinHttpReceiveResponse(hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( + HINTERNET hRequest, + __out bool * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + STACK_BUFFER(bufHeaderBuffer, 2048); + STACK_STRA(strHeaders, 2048); + DWORD dwHeaderSize = bufHeaderBuffer.QuerySize(); + + UNREFERENCED_PARAMETER(pfAnotherCompletionExpected); + + // + // Headers are available, read the status line and headers and pass + // them on to the client + // + // WinHttpQueryHeaders operates synchronously, + // no need for taking reference. + // + dwHeaderSize = bufHeaderBuffer.QuerySize(); + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) + { + if (!bufHeaderBuffer.Resize(dwHeaderSize)) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // WinHttpQueryHeaders operates synchronously, + // no need for taking reference. + // + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + if (FAILED(hr = strHeaders.CopyW( + reinterpret_cast(bufHeaderBuffer.QueryPtr())))) + { + goto Finished; + } + + // Issue: The reason we add trailing \r\n is to eliminate issues that have been observed + // in some configurations where status and headers would not have final \r\n nor \r\n\r\n + // (last header was null terminated).That caused crash within header parsing code that expected valid + // format. Parsing code was fized to return ERROR_INVALID_PARAMETER, but we still should make + // Example of a status+header string that was causing problems (note the missing \r\n at the end) + // HTTP/1.1 302 Moved Permanently\r\n....\r\nLocation:http://site\0 + // + + if (!strHeaders.IsEmpty() && strHeaders.QueryStr()[strHeaders.QueryCCH() - 1] != '\n') + { + hr = strHeaders.Append("\r\n"); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (FAILED(hr = SetStatusAndHeaders( + strHeaders.QueryStr(), + strHeaders.QueryCCH()))) + { + goto Finished; + } + + FreeResponseBuffers(); + + // + // If the request was websocket, and response was 101, + // trigger a flush, so that IIS's websocket module + // can get a chance to initialize and complete the handshake. + // + + if (m_fWebSocketEnabled) + { + m_RequestStatus = FORWARDER_RECEIVED_WEBSOCKET_RESPONSE; + + hr = m_pW3Context->GetResponse()->Flush( + TRUE, + TRUE, + NULL, + NULL); + + if (FAILED(hr)) + { + *pfAnotherCompletionExpected = FALSE; + } + else + { + *pfAnotherCompletionExpected = TRUE; + } + } + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusDataAvailable( + HINTERNET hRequest, + DWORD dwBytes, + _Out_ bool * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + + // + // Response data is available from winhttp, read it + // + if (dwBytes == 0) + { + if (m_cContentLength != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); + goto Finished; + } + + m_RequestStatus = FORWARDER_DONE; + + goto Finished; + } + + m_BytesToSend = dwBytes; + if (m_cContentLength != 0) + { + m_cContentLength -= dwBytes; + } + + m_pEntityBuffer = GetNewResponseBuffer( + min(m_BytesToSend, BUFFER_SIZE)); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // WinHttpReadData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + //ReferenceForwardingHandler(); + if (!WinHttpReadData(hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + //DereferenceForwardingHandler(); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusReadComplete( + __in IHttpResponse * pResponse, + DWORD dwStatusInformationLength, + __out bool * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + + // + // Response data has been read from winhttp, send it to the client + // + m_BytesToSend -= dwStatusInformationLength; + + if (m_cMinBufferLimit >= BUFFER_SIZE / 2) + { + if (m_cContentLength != 0) + { + m_cContentLength -= dwStatusInformationLength; + } + + // + // If we were not using WinHttpQueryDataAvailable and winhttp + // did not fill our buffer, we must have reached the end of the + // response + // + if (dwStatusInformationLength == 0 || + m_BytesToSend != 0) + { + if (m_cContentLength != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); + goto Finished; + } + + m_RequestStatus = FORWARDER_DONE; + } + } + else + { + DBG_ASSERT(dwStatusInformationLength != 0); + } + + if (dwStatusInformationLength == 0) + { + goto Finished; + } + else + { + m_cBytesBuffered += dwStatusInformationLength; + + HTTP_DATA_CHUNK Chunk; + Chunk.DataChunkType = HttpDataChunkFromMemory; + Chunk.FromMemory.pBuffer = m_pEntityBuffer; + Chunk.FromMemory.BufferLength = dwStatusInformationLength; + if (FAILED(hr = pResponse->WriteEntityChunkByReference(&Chunk))) + { + goto Finished; + } + } + + if (m_cBytesBuffered >= m_cMinBufferLimit) + { + // + // Always post a completion to resume the WinHTTP data pump. + // + hr = pResponse->Flush(TRUE, // fAsync + TRUE, // fMoreData + NULL); // pcbSent + if (FAILED(hr)) + { + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + } + else + { + *pfAnotherCompletionExpected = FALSE; + } + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnSendingRequest( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + __out bool * pfClientError +) +{ + HRESULT hr = S_OK; + // + // This is a completion for a read from http.sys, abort in case + // of failure, if we read anything write it out over WinHTTP, + // but we have already reached EOF, now read the response + // + if (hrCompletionStatus == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + DBG_ASSERT(m_BytesToReceive == 0 || m_BytesToReceive == INFINITE); + if (m_BytesToReceive == INFINITE) + { + m_BytesToReceive = 0; + m_cchLastSend = 5; // "0\r\n\r\n" + + if (!WinHttpWriteData(m_hRequest, + "0\r\n\r\n", + 5, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; + + if (!WinHttpReceiveResponse(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + } + else if (SUCCEEDED(hrCompletionStatus)) + { + DWORD cbOffset; + + if (m_BytesToReceive != INFINITE) + { + m_BytesToReceive -= cbCompletion; + cbOffset = 6; + } + else + { + // + // For chunk-encoded requests, need to re-chunk the entity body + // Add the CRLF just before and after the chunk data + // + m_pEntityBuffer[4] = '\r'; + m_pEntityBuffer[5] = '\n'; + + m_pEntityBuffer[cbCompletion + 6] = '\r'; + m_pEntityBuffer[cbCompletion + 7] = '\n'; + + if (cbCompletion < 0x10) + { + cbOffset = 3; + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion); + cbCompletion += 5; + } + else if (cbCompletion < 0x100) + { + cbOffset = 2; + m_pEntityBuffer[2] = HEX_TO_ASCII(cbCompletion >> 4); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 6; + } + else if (cbCompletion < 0x1000) + { + cbOffset = 1; + m_pEntityBuffer[1] = HEX_TO_ASCII(cbCompletion >> 8); + m_pEntityBuffer[2] = HEX_TO_ASCII((cbCompletion >> 4) & 0xf); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 7; + } + else + { + DBG_ASSERT(cbCompletion < 0x10000); + + cbOffset = 0; + m_pEntityBuffer[0] = HEX_TO_ASCII(cbCompletion >> 12); + m_pEntityBuffer[1] = HEX_TO_ASCII((cbCompletion >> 8) & 0xf); + m_pEntityBuffer[2] = HEX_TO_ASCII((cbCompletion >> 4) & 0xf); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 8; + } + } + m_cchLastSend = cbCompletion; + + if (!WinHttpWriteData(m_hRequest, + m_pEntityBuffer + cbOffset, + cbCompletion, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + hr = hrCompletionStatus; + *pfClientError = TRUE; + goto Failure; + } + +Failure: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnReceivingResponse( +) +{ + HRESULT hr = S_OK; + + if (m_cBytesBuffered >= m_cMinBufferLimit) + { + FreeResponseBuffers(); + } + + if (m_BytesToSend == 0) + { + // + // If response buffering is enabled, try to read large chunks + // at a time - also treat very small buffering limit as no + // buffering + // + m_BytesToSend = min(m_cMinBufferLimit, BUFFER_SIZE); + if (m_BytesToSend < BUFFER_SIZE / 2) + { + // + // Disable buffering. + // + m_BytesToSend = 0; + } + } + + if (m_BytesToSend == 0) + { + // + // No buffering enabled. + // + if (!WinHttpQueryDataAvailable(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + // + // Buffering enabled. + // + if (m_pEntityBuffer == NULL) + { + m_pEntityBuffer = GetNewResponseBuffer(min(m_BytesToSend, BUFFER_SIZE)); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + } + + if (!WinHttpReadData(m_hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + +Failure: + return hr; +} + +BYTE * +FORWARDING_HANDLER::GetNewResponseBuffer( + DWORD dwBufferSize +) +{ + DWORD dwNeededSize = (m_cEntityBuffers + 1) * sizeof(BYTE *); + if (dwNeededSize > m_buffEntityBuffers.QuerySize() && + !m_buffEntityBuffers.Resize( + max(dwNeededSize, m_buffEntityBuffers.QuerySize() * 2))) + { + return NULL; + } + + BYTE *pBuffer = (BYTE *)HeapAlloc(GetProcessHeap(), + 0, // dwFlags + dwBufferSize); + if (pBuffer == NULL) + { + return NULL; + } + + m_buffEntityBuffers.QueryPtr()[m_cEntityBuffers] = pBuffer; + m_cEntityBuffers++; + + return pBuffer; +} + +VOID +FORWARDING_HANDLER::FreeResponseBuffers() +{ + BYTE **pBuffers = m_buffEntityBuffers.QueryPtr(); + for (DWORD i = 0; iGetResponse(); + IHttpRequest * pRequest = m_pW3Context->GetRequest(); + STACK_STRA(strHeaderName, 128); + STACK_STRA(strHeaderValue, 2048); + DWORD index = 0; + PSTR pchNewline; + PCSTR pchEndofHeaderValue; + BOOL fServerHeaderPresent = FALSE; + + _ASSERT(pszHeaders != NULL); + + // + // The first line is the status line + // + PSTR pchStatus = const_cast(strchr(pszHeaders, ' ')); + if (pchStatus == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + USHORT uStatus = static_cast(atoi(pchStatus)); + + if (m_fWebSocketEnabled && uStatus != 101) + { + // + // Expected 101 response. + // + + m_fWebSocketEnabled = FALSE; + } + + pchStatus = strchr(pchStatus, ' '); + if (pchStatus == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + if (*pchStatus == '\r' || *pchStatus == '\n') + { + pchStatus--; + } + + pchNewline = strchr(pchStatus, '\n'); + if (pchNewline == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + if (uStatus != 200) + { + // + // Skip over any spaces before the '\n' + // + for (pchEndofHeaderValue = pchNewline - 1; + (pchEndofHeaderValue > pchStatus) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) + { + } + + // + // Copy the status description + // + if (FAILED(hr = strHeaderValue.Copy( + pchStatus, + (DWORD)(pchEndofHeaderValue - pchStatus) + 1)) || + FAILED(hr = pResponse->SetStatus(uStatus, + strHeaderValue.QueryStr(), + 0, + S_OK, + NULL, + TRUE))) + { + return hr; + } + } + + for (index = static_cast(pchNewline - pszHeaders) + 1; + pszHeaders[index] != '\r' && pszHeaders[index] != '\n' && pszHeaders[index] != '\0'; + index = static_cast(pchNewline - pszHeaders) + 1) + { + // + // Find the ':' in Header : Value\r\n + // + PCSTR pchColon = strchr(pszHeaders + index, ':'); + + // + // Find the '\n' in Header : Value\r\n + // + pchNewline = const_cast(strchr(pszHeaders + index, '\n')); + + if (pchNewline == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + // + // Take care of header continuation + // + while (pchNewline[1] == ' ' || + pchNewline[1] == '\t') + { + pchNewline = strchr(pchNewline + 1, '\n'); + } + + DBG_ASSERT( + (pchColon != NULL) && (pchColon < pchNewline)); + if ((pchColon == NULL) || (pchColon >= pchNewline)) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + // + // Skip over any spaces before the ':' + // + PCSTR pchEndofHeaderName; + for (pchEndofHeaderName = pchColon - 1; + (pchEndofHeaderName >= pszHeaders + index) && + (*pchEndofHeaderName == ' '); + pchEndofHeaderName--) + { + } + + pchEndofHeaderName++; + + // + // Copy the header name + // + if (FAILED(hr = strHeaderName.Copy( + pszHeaders + index, + (DWORD)(pchEndofHeaderName - pszHeaders) - index))) + { + return hr; + } + + // + // Skip over the ':' and any trailing spaces + // + for (index = static_cast(pchColon - pszHeaders) + 1; + pszHeaders[index] == ' '; + index++) + { + } + + // + // Skip over any spaces before the '\n' + // + for (pchEndofHeaderValue = pchNewline - 1; + (pchEndofHeaderValue >= pszHeaders + index) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) + { + } + + pchEndofHeaderValue++; + + // + // Copy the header value + // + if (pchEndofHeaderValue == pszHeaders + index) + { + strHeaderValue.Reset(); + } + else if (FAILED(hr = strHeaderValue.Copy( + pszHeaders + index, + (DWORD)(pchEndofHeaderValue - pszHeaders) - index))) + { + return hr; + } + + // + // Do not pass the transfer-encoding:chunked, Connection, Date or + // Server headers along + // + DWORD headerIndex = sm_pResponseHeaderHash->GetIndex(strHeaderName.QueryStr()); + if (headerIndex == UNKNOWN_INDEX) + { + hr = pResponse->SetHeader(strHeaderName.QueryStr(), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + FALSE); // fReplace + } + else + { + switch (headerIndex) + { + case HttpHeaderTransferEncoding: + if (!strHeaderValue.Equals("chunked", TRUE)) + { + break; + } + __fallthrough; + case HttpHeaderConnection: + case HttpHeaderDate: + continue; + + case HttpHeaderServer: + fServerHeaderPresent = TRUE; + break; + + case HttpHeaderContentLength: + if (pRequest->GetRawHttpRequest()->Verb != HttpVerbHEAD) + { + m_cContentLength = _atoi64(strHeaderValue.QueryStr()); + } + break; + } + + hr = pResponse->SetHeader(static_cast(headerIndex), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + TRUE); // fReplace + } + if (FAILED(hr)) + { + return hr; + } + } + + // + // Explicitly remove the Server header if the back-end didn't set one. + // + + if (!fServerHeaderPresent) + { + pResponse->DeleteHeader("Server"); + } + + if (m_fDoReverseRewriteHeaders) + { + hr = DoReverseRewrite(pResponse); + if (FAILED(hr)) + { + return hr; + } + } + + m_fResponseHeadersReceivedAndSet = TRUE; + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::DoReverseRewrite( + _In_ IHttpResponse *pResponse +) +{ + DBG_ASSERT(pResponse == m_pW3Context->GetResponse()); + BOOL fSecure = (m_pW3Context->GetRequest()->GetRawHttpRequest()->pSslInfo != NULL); + STRA strTemp; + PCSTR pszHeader; + PCSTR pszStartHost; + PCSTR pszEndHost; + HTTP_RESPONSE_HEADERS *pHeaders; + HRESULT hr; + + // + // Content-Location and Location are easy, one known header in + // http[s]://host/url format + // + pszHeader = pResponse->GetHeader(HttpHeaderContentLocation); + if (pszHeader != NULL) + { + if (_strnicmp(pszHeader, "http://", 7) == 0) + { + pszStartHost = pszHeader + 7; + } + else if (_strnicmp(pszHeader, "https://", 8) == 0) + { + pszStartHost = pszHeader + 8; + } + else + { + goto Location; + } + + pszEndHost = strchr(pszStartHost, '/'); + + if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + { + return hr; + } + if (pszEndHost != NULL && + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) + { + return hr; + } + } + +Location: + + pszHeader = pResponse->GetHeader(HttpHeaderLocation); + if (pszHeader != NULL) + { + if (_strnicmp(pszHeader, "http://", 7) == 0) + { + pszStartHost = pszHeader + 7; + } + else if (_strnicmp(pszHeader, "https://", 8) == 0) + { + pszStartHost = pszHeader + 8; + } + else + { + goto SetCookie; + } + + pszEndHost = strchr(pszStartHost, '/'); + + if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + { + return hr; + } + if (pszEndHost != NULL && + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + if (FAILED(hr = pResponse->SetHeader(HttpHeaderLocation, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) + { + return hr; + } + } + +SetCookie: + + // + // Set-Cookie is different - possibly multiple unknown headers with + // syntax name=value ; ... ; Domain=.host ; ... + // + pHeaders = &pResponse->GetRawHttpResponse()->Headers; + for (DWORD i = 0; iUnknownHeaderCount; i++) + { + if (_stricmp(pHeaders->pUnknownHeaders[i].pName, "Set-Cookie") != 0) + { + continue; + } + + pszHeader = pHeaders->pUnknownHeaders[i].pRawValue; + pszStartHost = strchr(pszHeader, ';'); + while (pszStartHost != NULL) + { + pszStartHost++; + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + + if (_strnicmp(pszStartHost, "Domain", 6) != 0) + { + pszStartHost = strchr(pszStartHost, ';'); + continue; + } + pszStartHost += 6; + + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + if (*pszStartHost != '=') + { + break; + } + pszStartHost++; + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + if (*pszStartHost == '.') + { + pszStartHost++; + } + pszEndHost = pszStartHost; + while (!IsSpace(*pszEndHost) && + *pszEndHost != ';' && + *pszEndHost != '\0') + { + pszEndHost++; + } + + if (FAILED(hr = strTemp.Copy(pszHeader, static_cast(pszStartHost - pszHeader))) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader)) || + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + + pszHeader = (PCSTR)m_pW3Context->AllocateRequestMemory(strTemp.QueryCCH() + 1); + if (pszHeader == NULL) + { + return E_OUTOFMEMORY; + } + StringCchCopyA(const_cast(pszHeader), strTemp.QueryCCH() + 1, strTemp.QueryStr()); + pHeaders->pUnknownHeaders[i].pRawValue = pszHeader; + pHeaders->pUnknownHeaders[i].RawValueLength = static_cast(strTemp.QueryCCH()); + + break; + } + } + + return S_OK; +} + +VOID +FORWARDING_HANDLER::TerminateRequest( + bool fClientInitiated +) +{ + UNREFERENCED_PARAMETER(fClientInitiated); + AcquireSRWLockExclusive(&m_RequestLock); + // Set tls as close winhttp handle will immediately trigger + // a winhttp callback on the same thread and we donot want to + // acquire the lock again + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + if (m_hRequest != NULL) + { +#ifdef DEBUG + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::TerminateRequest %d --%p\n", GetCurrentThreadId(), m_pW3Context); +#endif // DEBUG + m_fHandleClosedDueToClient = fClientInitiated; + WinHttpCloseHandle(m_hRequest); + } + + // + // If the request is a websocket request, initiate cleanup. + // + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + } + + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); +} + diff --git a/src/RequestHandler/outofprocess/forwardinghandler.h b/src/RequestHandler/outofprocess/forwardinghandler.h new file mode 100644 index 0000000000..1db353019b --- /dev/null +++ b/src/RequestHandler/outofprocess/forwardinghandler.h @@ -0,0 +1,199 @@ +#pragma once + +extern DWORD g_OptionalWinHttpFlags; + + +enum FORWARDING_REQUEST_STATUS +{ + FORWARDER_START, + FORWARDER_SENDING_REQUEST, + FORWARDER_RECEIVING_RESPONSE, + FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, + FORWARDER_DONE, + FORWARDER_FINISH_REQUEST +}; + + +class FORWARDING_HANDLER : public REQUEST_HANDLER +{ +public: + FORWARDING_HANDLER( + + _In_ IHttpContext *pW3Context, + _In_ HTTP_MODULE_ID *pModuleId, + _In_ APPLICATION *pApplication); + + ~FORWARDING_HANDLER(); + + __override + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler(); + + __override + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ); + + VOID + SetStatus( + FORWARDING_REQUEST_STATUS status + ) + { + m_RequestStatus = status; + } + + static + VOID + CALLBACK + FORWARDING_HANDLER::OnWinHttpCompletion( + HINTERNET hRequest, + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength + ); + + static + HRESULT + StaticInitialize( + BOOL fEnableReferenceCountTracing + ); + + static + VOID + StaticTerminate(); + + VOID + TerminateRequest( + bool fClientInitiated + ); + +private: + HRESULT + CreateWinHttpRequest( + _In_ const IHttpRequest * pRequest, + _In_ const PROTOCOL_CONFIG * pProtocol, + _In_ HINTERNET hConnect, + _Inout_ STRU * pstrUrl, +// _In_ ASPNETCORE_CONFIG* pAspNetCoreConfig, + _In_ SERVER_PROCESS* pServerProcess + ); + + VOID + FORWARDING_HANDLER::OnWinHttpCompletionInternal( + _In_ HINTERNET hRequest, + _In_ DWORD dwInternetStatus, + _In_ LPVOID lpvStatusInformation, + _In_ DWORD dwStatusInformationLength + ); + + HRESULT + OnWinHttpCompletionSendRequestOrWriteComplete( + HINTERNET hRequest, + DWORD dwInternetStatus, + _Out_ bool * pfClientError, + _Out_ bool * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusHeadersAvailable( + HINTERNET hRequest, + _Out_ bool * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusDataAvailable( + HINTERNET hRequest, + DWORD dwBytes, + _Out_ bool * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusReadComplete( + _In_ IHttpResponse * pResponse, + DWORD dwStatusInformationLength, + _Out_ bool * pfAnotherCompletionExpected + ); + + HRESULT + OnSendingRequest( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + _Out_ bool * pfClientError + ); + + HRESULT + OnReceivingResponse(); + + BYTE * + GetNewResponseBuffer( + DWORD dwBufferSize + ); + + VOID + FreeResponseBuffers(); + + HRESULT + SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD cchHeaders + ); + + HRESULT + DoReverseRewrite( + _In_ IHttpResponse *pResponse + ); + + HRESULT + GetHeaders( + _In_ const PROTOCOL_CONFIG * pProtocol, + _In_ bool fForwardWindowsAuthToken, + _In_ SERVER_PROCESS* pServerProcess, + _Out_ PCWSTR * ppszHeaders, + _Inout_ DWORD * pcchHeaders + ); + + DWORD m_Signature; + // + // WinHTTP request handle is protected using a read-write lock. + // + SRWLOCK m_RequestLock; + HINTERNET m_hRequest; + FORWARDING_REQUEST_STATUS m_RequestStatus; + + bool m_fWebSocketEnabled; + bool m_fResponseHeadersReceivedAndSet; + bool m_fResetConnection; + bool m_fHandleClosedDueToClient; + bool m_fFinishRequest; + bool m_fHasError; + BOOL m_fDoReverseRewriteHeaders; + PCSTR m_pszOriginalHostHeader; + PCWSTR m_pszHeaders; + DWORD m_cchHeaders; + DWORD m_BytesToReceive; + DWORD m_BytesToSend; + DWORD m_cchLastSend; + DWORD m_cEntityBuffers; + DWORD m_cBytesBuffered; + DWORD m_cMinBufferLimit; + ULONGLONG m_cContentLength; + WEBSOCKET_HANDLER * m_pWebSocket; + ASYNC_DISCONNECT_CONTEXT * m_pDisconnect; + + BYTE * m_pEntityBuffer; + static const SIZE_T INLINE_ENTITY_BUFFERS = 8; + BUFFER_T m_buffEntityBuffers; + + static ALLOC_CACHE_HANDLER * sm_pAlloc; + static PROTOCOL_CONFIG sm_ProtocolConfig; + static RESPONSE_HEADER_HASH * sm_pResponseHeaderHash; + // + // Reference cout tracing for debugging purposes. + // + static TRACE_LOG * sm_pTraceLog; + + static STRA sm_pStra502ErrorMsg; +}; \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/outprocessapplication.cpp b/src/RequestHandler/outofprocess/outprocessapplication.cpp new file mode 100644 index 0000000000..7e14145585 --- /dev/null +++ b/src/RequestHandler/outofprocess/outprocessapplication.cpp @@ -0,0 +1,66 @@ +#include "..\precomp.hxx" + +OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( + IHttpServer* pHttpServer, + ASPNETCORE_CONFIG* pConfig) : + APPLICATION(pHttpServer, pConfig) +{ + m_status = APPLICATION_STATUS::RUNNING; + m_pProcessManager = NULL; + //todo +} + +OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() +{ + if (m_pProcessManager != NULL) + { + m_pProcessManager->ShutdownAllProcesses(); + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } +} + +HRESULT +OUT_OF_PROCESS_APPLICATION::Initialize( +) +{ + HRESULT hr = S_OK; + if (m_pProcessManager == NULL) + { + m_pProcessManager = new PROCESS_MANAGER; + if (m_pProcessManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pProcessManager->Initialize(); + if (FAILED(hr)) + { + goto Finished; + } + } + +Finished: + return hr; +} + +HRESULT +OUT_OF_PROCESS_APPLICATION::GetProcess( + _Out_ SERVER_PROCESS **ppServerProcess +) +{ + return m_pProcessManager->GetProcess(m_pConfig, ppServerProcess); +} + +__override +VOID +OUT_OF_PROCESS_APPLICATION::ShutDown() +{ + if (m_pProcessManager != NULL) + { + m_pProcessManager->ShutdownAllProcesses(); + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } +} diff --git a/src/RequestHandler/outofprocess/outprocessapplication.h b/src/RequestHandler/outofprocess/outprocessapplication.h new file mode 100644 index 0000000000..8961135c28 --- /dev/null +++ b/src/RequestHandler/outofprocess/outprocessapplication.h @@ -0,0 +1,25 @@ +#pragma once + +class OUT_OF_PROCESS_APPLICATION : public APPLICATION +{ + +public: + OUT_OF_PROCESS_APPLICATION(IHttpServer* pHttpServer, ASPNETCORE_CONFIG *pConfig); + + ~OUT_OF_PROCESS_APPLICATION(); + + HRESULT + Initialize(); + + HRESULT + GetProcess( + _Out_ SERVER_PROCESS **ppServerProcess + ); + + __override + VOID + ShutDown(); + +private: + PROCESS_MANAGER * m_pProcessManager; +}; diff --git a/src/RequestHandler/outofprocess/processmanager.cxx b/src/RequestHandler/outofprocess/processmanager.cxx new file mode 100644 index 0000000000..98ba30441f --- /dev/null +++ b/src/RequestHandler/outofprocess/processmanager.cxx @@ -0,0 +1,296 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "..\precomp.hxx" + +volatile BOOL PROCESS_MANAGER::sm_fWSAStartupDone = FALSE; + +HRESULT +PROCESS_MANAGER::Initialize( + VOID +) +{ + HRESULT hr = S_OK; + WSADATA wsaData; + int result; + BOOL fLocked = FALSE; + + if( !sm_fWSAStartupDone ) + { + AcquireSRWLockExclusive( &m_srwLock ); + fLocked = TRUE; + + if( !sm_fWSAStartupDone ) + { + if( (result = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0 ) + { + hr = HRESULT_FROM_WIN32( result ); + goto Finished; + } + sm_fWSAStartupDone = TRUE; + } + + ReleaseSRWLockExclusive( &m_srwLock ); + fLocked = FALSE; + } + + m_dwRapidFailTickStart = GetTickCount(); + + if( m_hNULHandle == NULL ) + { + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + m_hNULHandle = CreateFileW( L"NUL", + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if( m_hNULHandle == INVALID_HANDLE_VALUE ) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + +Finished: + + if(fLocked) + { + ReleaseSRWLockExclusive( &m_srwLock ); + } + + return hr; +} + +PROCESS_MANAGER::~PROCESS_MANAGER() +{ + AcquireSRWLockExclusive(&m_srwLock); + + //if( m_ppServerProcessList != NULL ) + //{ + // for( DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + // { + // if( m_ppServerProcessList[i] != NULL ) + // { + // m_ppServerProcessList[i]->DereferenceServerProcess(); + // m_ppServerProcessList[i] = NULL; + // } + // } + + // delete[] m_ppServerProcessList; + // m_ppServerProcessList = NULL; + //} + + //if( m_hNULHandle != NULL ) + //{ + // CloseHandle( m_hNULHandle ); + // m_hNULHandle = NULL; + //} + + //if( sm_fWSAStartupDone ) + //{ + // WSACleanup(); + // sm_fWSAStartupDone = FALSE; + //} + + ReleaseSRWLockExclusive(&m_srwLock); +} + +HRESULT +PROCESS_MANAGER::GetProcess( + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ SERVER_PROCESS **ppServerProcess +) +{ + HRESULT hr = S_OK; + BOOL fSharedLock = FALSE; + BOOL fExclusiveLock = FALSE; + //PCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + DWORD dwProcessIndex = 0; + SERVER_PROCESS *pSelectedServerProcess = NULL; + + if (!m_fServerProcessListReady) + { + AcquireSRWLockExclusive(&m_srwLock); + fExclusiveLock = TRUE; + + if (!m_fServerProcessListReady) + { + m_dwProcessesPerApplication = pConfig->QueryProcessesPerApplication(); + m_ppServerProcessList = new SERVER_PROCESS*[m_dwProcessesPerApplication]; + if (m_ppServerProcessList == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + for (DWORD i = 0; i < m_dwProcessesPerApplication; ++i) + { + m_ppServerProcessList[i] = NULL; + } + } + m_fServerProcessListReady = TRUE; + ReleaseSRWLockExclusive(&m_srwLock); + fExclusiveLock = FALSE; + } + + AcquireSRWLockShared(&m_srwLock); + fSharedLock = TRUE; + + // + // round robin through to the next available process. + // + dwProcessIndex = (DWORD)InterlockedIncrement64((LONGLONG*)&m_dwRouteToProcessIndex); + dwProcessIndex = dwProcessIndex % m_dwProcessesPerApplication; + + if (m_ppServerProcessList[dwProcessIndex] != NULL && + m_ppServerProcessList[dwProcessIndex]->IsReady()) + { + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + goto Finished; + } + + ReleaseSRWLockShared(&m_srwLock); + fSharedLock = FALSE; + + // should make the lock per process so that we can start processes simultaneously ? + if (m_ppServerProcessList[dwProcessIndex] == NULL || + !m_ppServerProcessList[dwProcessIndex]->IsReady()) + { + AcquireSRWLockExclusive(&m_srwLock); + fExclusiveLock = TRUE; + + if (m_ppServerProcessList[dwProcessIndex] != NULL) + { + if (!m_ppServerProcessList[dwProcessIndex]->IsReady()) + { + // + // terminate existing process that is not ready + // before creating new one. + // + + //todo: + //ShutdownProcessNoLock( m_ppServerProcessList[dwProcessIndex] ); + } + else + { + // server is already up and ready to serve requests. + //m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + goto Finished; + } + } + + if (RapidFailsPerMinuteExceeded(pConfig->QueryRapidFailsPerMinute())) + { + // + // rapid fails per minute exceeded, do not create new process. + // + + //if( SUCCEEDED( strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG, + // pConfig->QueryRapidFailsPerMinute() ) ) ) + //{ + // apsz[0] = strEventMsg.QueryStr(); + + // // + // // not checking return code because if ReportEvent + // // fails, we cannot do anything. + // // + // if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_INFORMATION_TYPE, + // 0, + // ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // } + //} + + hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); + goto Finished; + } + + if (m_ppServerProcessList[dwProcessIndex] == NULL) + { + + pSelectedServerProcess = new SERVER_PROCESS(); + if (pSelectedServerProcess == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + + hr = pSelectedServerProcess->Initialize( + this, //ProcessManager + pConfig->QueryProcessPath(), // + pConfig->QueryArguments(), // + pConfig->QueryStartupTimeLimitInMS(), + pConfig->QueryShutdownTimeLimitInMS(), + pConfig->QueryWindowsAuthEnabled(), + pConfig->QueryBasicAuthEnabled(), + pConfig->QueryAnonymousAuthEnabled(), + pConfig->QueryEnvironmentVariables(), + pConfig->QueryStdoutLogEnabled(), + pConfig->QueryStdoutLogFile(), + pConfig->QueryApplicationPhysicalPath(), // physical path + pConfig->QueryApplicationPath(), // app path + pConfig->QueryApplicationVirtualPath() // App relative virtual path + ); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pSelectedServerProcess->StartProcess(); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (!pSelectedServerProcess->IsReady()) + { + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + + m_ppServerProcessList[dwProcessIndex] = pSelectedServerProcess; + pSelectedServerProcess = NULL; + + } + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + +Finished: + + if (fSharedLock) + { + ReleaseSRWLockShared(&m_srwLock); + fSharedLock = FALSE; + } + + if (fExclusiveLock) + { + ReleaseSRWLockExclusive(&m_srwLock); + fExclusiveLock = FALSE; + } + + if (pSelectedServerProcess != NULL) + { + delete pSelectedServerProcess; + pSelectedServerProcess = NULL; + } + + return hr; +} \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/processmanager.h b/src/RequestHandler/outofprocess/processmanager.h new file mode 100644 index 0000000000..9523e8a819 --- /dev/null +++ b/src/RequestHandler/outofprocess/processmanager.h @@ -0,0 +1,195 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define ONE_MINUTE_IN_MILLISECONDS 60000 +class SERVER_PROCESS; + +class PROCESS_MANAGER +{ +public: + + virtual + ~PROCESS_MANAGER(); + + VOID + ReferenceProcessManager() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceProcessManager() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + HRESULT + GetProcess( + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ SERVER_PROCESS **ppServerProcess + ); + + HANDLE + QueryNULHandle() + { + return m_hNULHandle; + } + + HRESULT + Initialize( + VOID + ); + + VOID + SendShutdownSignal() + { + AcquireSRWLockExclusive( &m_srwLock ); + + for(DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList != NULL && + m_ppServerProcessList[i] != NULL ) + { + m_ppServerProcessList[i]->SendSignal(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + + ReleaseSRWLockExclusive( &m_srwLock ); + } + + VOID + ShutdownProcess( + SERVER_PROCESS* pServerProcess + ) + { + AcquireSRWLockExclusive( &m_srwLock ); + + ShutdownProcessNoLock( pServerProcess ); + + ReleaseSRWLockExclusive( &m_srwLock ); + } + + VOID + ShutdownAllProcesses( + ) + { + AcquireSRWLockExclusive( &m_srwLock ); + + ShutdownAllProcessesNoLock(); + + ReleaseSRWLockExclusive( &m_srwLock ); + } + + VOID + IncrementRapidFailCount( + VOID + ) + { + InterlockedIncrement(&m_cRapidFailCount); + } + + PROCESS_MANAGER() : + m_ppServerProcessList( NULL ), + m_hNULHandle( NULL ), + m_cRapidFailCount( 0 ), + m_dwProcessesPerApplication( 1 ), + m_dwRouteToProcessIndex( 0 ), + m_fServerProcessListReady(FALSE), + m_cRefs( 1 ) + { + m_ppServerProcessList = NULL; + m_fServerProcessListReady = FALSE; + InitializeSRWLock( &m_srwLock ); + } + +private: + + BOOL + RapidFailsPerMinuteExceeded( + LONG dwRapidFailsPerMinute + ) + { + DWORD dwCurrentTickCount = GetTickCount(); + + if( (dwCurrentTickCount - m_dwRapidFailTickStart) + >= ONE_MINUTE_IN_MILLISECONDS ) + { + // + // reset counters every minute. + // + + InterlockedExchange(&m_cRapidFailCount, 0); + m_dwRapidFailTickStart = dwCurrentTickCount; + } + + return m_cRapidFailCount > dwRapidFailsPerMinute; + } + + VOID + ShutdownProcessNoLock( + SERVER_PROCESS* pServerProcess + ) + { + for(DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList != NULL && + m_ppServerProcessList[i] != NULL && + m_ppServerProcessList[i]->GetPort() == pServerProcess->GetPort() ) + { + // shutdown pServerProcess if not already shutdown. + m_ppServerProcessList[i]->StopProcess(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + } + + VOID + ShutdownAllProcessesNoLock( + VOID + ) + { + for(DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList != NULL && + m_ppServerProcessList[i] != NULL ) + { + // shutdown pServerProcess if not already shutdown. + m_ppServerProcessList[i]->SendSignal(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + } + + volatile LONG m_cRapidFailCount; + DWORD m_dwRapidFailTickStart; + DWORD m_dwProcessesPerApplication; + volatile DWORD m_dwRouteToProcessIndex; + + SRWLOCK m_srwLock; + SERVER_PROCESS **m_ppServerProcessList; + + // + // m_hNULHandle is used to redirect stdout/stderr to NUL. + // If Createprocess is called to launch a batch file for example, + // it tries to write to the console buffer by default. It fails to + // start if the console buffer is owned by the parent process i.e + // in our case w3wp.exe. So we have to redirect the stdout/stderr + // of the child process to NUL or to a file (anything other than + // the console buffer of the parent process). + // + + HANDLE m_hNULHandle; + mutable LONG m_cRefs; + + volatile static BOOL sm_fWSAStartupDone; + volatile BOOL m_fServerProcessListReady; +}; \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/protocolconfig.cxx b/src/RequestHandler/outofprocess/protocolconfig.cxx new file mode 100644 index 0000000000..9faebab82a --- /dev/null +++ b/src/RequestHandler/outofprocess/protocolconfig.cxx @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "..\precomp.hxx" + +HRESULT +PROTOCOL_CONFIG::Initialize() +{ + HRESULT hr; + STRU strTemp; + + m_fKeepAlive = TRUE; + m_msTimeout = 120000; + m_fPreserveHostHeader = TRUE; + m_fReverseRewriteHeaders = FALSE; + + if (FAILED(hr = m_strXForwardedForName.CopyW(L"X-Forwarded-For"))) + { + goto Finished; + } + + if (FAILED(hr = m_strSslHeaderName.CopyW(L"X-Forwarded-Proto"))) + { + goto Finished; + } + + if (FAILED(hr = m_strClientCertName.CopyW(L"MS-ASPNETCORE-CLIENTCERT"))) + { + goto Finished; + } + + m_fIncludePortInXForwardedFor = TRUE; + m_dwMinResponseBuffer = 0; // no response buffering + m_dwResponseBufferLimit = 4096*1024; + m_dwMaxResponseHeaderSize = 65536; + +Finished: + + return hr; +} + +VOID +PROTOCOL_CONFIG::OverrideConfig( + ASPNETCORE_CONFIG *pAspNetCoreConfig +) +{ + m_msTimeout = pAspNetCoreConfig->QueryRequestTimeoutInMS(); +} \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/protocolconfig.h b/src/RequestHandler/outofprocess/protocolconfig.h new file mode 100644 index 0000000000..0bb34aa53d --- /dev/null +++ b/src/RequestHandler/outofprocess/protocolconfig.h @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class PROTOCOL_CONFIG +{ + public: + + PROTOCOL_CONFIG() + { + } + + HRESULT + Initialize(); + + VOID + OverrideConfig( + ASPNETCORE_CONFIG *pAspNetCoreConfig + ); + + BOOL + QueryDoKeepAlive() const + { + return m_fKeepAlive; + } + + DWORD + QueryTimeout() const + { + return m_msTimeout; + } + + BOOL + QueryPreserveHostHeader() const + { + return m_fPreserveHostHeader; + } + + BOOL + QueryReverseRewriteHeaders() const + { + return m_fReverseRewriteHeaders; + } + + const STRA * + QueryXForwardedForName() const + { + return &m_strXForwardedForName; + } + + BOOL + QueryIncludePortInXForwardedFor() const + { + return m_fIncludePortInXForwardedFor; + } + + DWORD + QueryMinResponseBuffer() const + { + return m_dwMinResponseBuffer; + } + + DWORD + QueryResponseBufferLimit() const + { + return m_dwResponseBufferLimit; + } + + DWORD + QueryMaxResponseHeaderSize() const + { + return m_dwMaxResponseHeaderSize; + } + + const STRA* + QuerySslHeaderName() const + { + return &m_strSslHeaderName; + } + + const STRA * + QueryClientCertName() const + { + return &m_strClientCertName; + } + + private: + + BOOL m_fKeepAlive; + BOOL m_fPreserveHostHeader; + BOOL m_fReverseRewriteHeaders; + BOOL m_fIncludePortInXForwardedFor; + + DWORD m_msTimeout; + DWORD m_dwMinResponseBuffer; + DWORD m_dwResponseBufferLimit; + DWORD m_dwMaxResponseHeaderSize; + + STRA m_strXForwardedForName; + STRA m_strSslHeaderName; + STRA m_strClientCertName; +}; diff --git a/src/RequestHandler/outofprocess/responseheaderhash.cxx b/src/RequestHandler/outofprocess/responseheaderhash.cxx new file mode 100644 index 0000000000..f2fae274d5 --- /dev/null +++ b/src/RequestHandler/outofprocess/responseheaderhash.cxx @@ -0,0 +1,98 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "..\precomp.hxx" + +HEADER_RECORD RESPONSE_HEADER_HASH::sm_rgHeaders[] = +{ + { "Cache-Control", HttpHeaderCacheControl }, + { "Connection", HttpHeaderConnection }, + { "Date", HttpHeaderDate }, + { "Keep-Alive", HttpHeaderKeepAlive }, + { "Pragma", HttpHeaderPragma }, + { "Trailer", HttpHeaderTrailer }, + { "Transfer-Encoding", HttpHeaderTransferEncoding }, + { "Upgrade", HttpHeaderUpgrade }, + { "Via", HttpHeaderVia }, + { "Warning", HttpHeaderWarning }, + { "Allow", HttpHeaderAllow }, + { "Content-Length", HttpHeaderContentLength }, + { "Content-Type", HttpHeaderContentType }, + { "Content-Encoding", HttpHeaderContentEncoding }, + { "Content-Language", HttpHeaderContentLanguage }, + { "Content-Location", HttpHeaderContentLocation }, + { "Content-MD5", HttpHeaderContentMd5 }, + { "Content-Range", HttpHeaderContentRange }, + { "Expires", HttpHeaderExpires }, + { "Last-Modified", HttpHeaderLastModified }, + { "Accept-Ranges", HttpHeaderAcceptRanges }, + { "Age", HttpHeaderAge }, + { "ETag", HttpHeaderEtag }, + { "Location", HttpHeaderLocation }, + { "Proxy-Authenticate", HttpHeaderProxyAuthenticate }, + { "Retry-After", HttpHeaderRetryAfter }, + { "Server", HttpHeaderServer }, + // Set it to something which cannot be a header name, in effect + // making Server an unknown header. w:w is used to avoid collision with Keep-Alive. + { "w:w\r\n", HttpHeaderServer }, + // Set it to something which cannot be a header name, in effect + // making Set-Cookie an unknown header + { "y:y\r\n", HttpHeaderSetCookie }, + { "Vary", HttpHeaderVary }, + // Set it to something which cannot be a header name, in effect + // making WWW-Authenticate an unknown header + { "z:z\r\n", HttpHeaderWwwAuthenticate } + +}; + +HRESULT +RESPONSE_HEADER_HASH::Initialize( + VOID +) +/*++ + +Routine Description: + + Initialize global header hash table + +Arguments: + + None + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr; + + // + // 31 response headers. + // Make sure to update the number of buckets it new headers + // are added. Test it to avoid collisions. + // + C_ASSERT(_countof(sm_rgHeaders) == 31); + + // + // 79 buckets will have less collisions for the 31 response headers. + // Known collisions are "Age" colliding with "Expire" and "Location" + // colliding with both "Expire" and "Age". + // + hr = HASH_TABLE::Initialize(79); + if (FAILED(hr)) + { + return hr; + } + + for ( DWORD Index = 0; Index < _countof(sm_rgHeaders); ++Index ) + { + if (FAILED(hr = InsertRecord(&sm_rgHeaders[Index]))) + { + return hr; + } + } + + return S_OK; +} + diff --git a/src/RequestHandler/outofprocess/responseheaderhash.h b/src/RequestHandler/outofprocess/responseheaderhash.h new file mode 100644 index 0000000000..54f9c82954 --- /dev/null +++ b/src/RequestHandler/outofprocess/responseheaderhash.h @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// *_HEADER_HASH maps strings to UlHeader* values +// + +#define UNKNOWN_INDEX (0xFFFFFFFF) + +struct HEADER_RECORD +{ + PCSTR _pszName; + ULONG _ulHeaderIndex; +}; + +class RESPONSE_HEADER_HASH: public HASH_TABLE +{ +public: + RESPONSE_HEADER_HASH() + {} + + VOID + ReferenceRecord( + HEADER_RECORD * + ) + {} + + VOID + DereferenceRecord( + HEADER_RECORD * + ) + {} + + PCSTR + ExtractKey( + HEADER_RECORD * pRecord + ) + { + return pRecord->_pszName; + } + + DWORD + CalcKeyHash( + PCSTR key + ) + { + return HashStringNoCase(key); + } + + BOOL + EqualKeys( + PCSTR key1, + PCSTR key2 + ) + { + return (_stricmp(key1, key2) == 0); + } + + HRESULT + Initialize( + VOID + ); + + VOID + Terminate( + VOID + ); + + DWORD + GetIndex( + PCSTR pszName + ) + { + HEADER_RECORD * pRecord = NULL; + + FindKey(pszName, &pRecord); + if (pRecord != NULL) + { + return pRecord->_ulHeaderIndex; + } + + return UNKNOWN_INDEX; + } + + static + PCSTR + GetString( + ULONG ulIndex + ) + { + if (ulIndex < HttpHeaderResponseMaximum) + { + DBG_ASSERT(sm_rgHeaders[ulIndex]._ulHeaderIndex == ulIndex); + return sm_rgHeaders[ulIndex]._pszName; + } + + return NULL; + } + +private: + + static HEADER_RECORD sm_rgHeaders[]; + + RESPONSE_HEADER_HASH(const RESPONSE_HEADER_HASH &); + void operator=(const RESPONSE_HEADER_HASH &); +}; diff --git a/src/RequestHandler/outofprocess/serverprocess.cxx b/src/RequestHandler/outofprocess/serverprocess.cxx new file mode 100644 index 0000000000..4c94331de9 --- /dev/null +++ b/src/RequestHandler/outofprocess/serverprocess.cxx @@ -0,0 +1,2338 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "..\precomp.hxx" +#include +//#include + +//extern BOOL g_fNsiApiNotSupported; + +#define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000 + + +HRESULT +SERVER_PROCESS::Initialize( + PROCESS_MANAGER *pProcessManager, + STRU *pszProcessExePath, + STRU *pszArguments, + DWORD dwStartupTimeLimitInMS, + DWORD dwShtudownTimeLimitInMS, + BOOL fWindowsAuthEnabled, + BOOL fBasicAuthEnabled, + BOOL fAnonymousAuthEnabled, + ENVIRONMENT_VAR_HASH *pEnvironmentVariables, + BOOL fStdoutLogEnabled, + STRU *pstruStdoutLogFile, + STRU *pszAppPhysicalPath, + STRU *pszAppPath, + STRU *pszAppVirtualPath +) +{ + HRESULT hr = S_OK; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 }; + + m_pProcessManager = pProcessManager; + m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS; + m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS; + m_fStdoutLogEnabled = fStdoutLogEnabled; + m_fWindowsAuthEnabled = fWindowsAuthEnabled; + m_fBasicAuthEnabled = fBasicAuthEnabled; + m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; + m_pProcessManager->ReferenceProcessManager(); + + if (FAILED(hr = m_ProcessPath.Copy(*pszProcessExePath)) || + FAILED(hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| + FAILED(hr = m_struPhysicalPath.Copy(*pszAppPhysicalPath))|| + FAILED(hr = m_struAppFullPath.Copy(*pszAppPath))|| + FAILED(hr = m_struAppVirtualPath.Copy(*pszAppVirtualPath))|| + FAILED(hr = m_Arguments.Copy(*pszArguments))) + { + goto Finished; + } + + if (m_hJobObject == NULL) + { + m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES + NULL); // LPCTSTR lpName +#pragma warning( disable : 4312) + // 0xdeadbeef is used by Antares + if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) + { + m_hJobObject = NULL; + // ignore job object creation error. + } +#pragma warning( error : 4312) + if (m_hJobObject != NULL) + { + jobInfo.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + if (!SetInformationJobObject(m_hJobObject, + JobObjectExtendedLimitInformation, + &jobInfo, + sizeof jobInfo)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + m_pEnvironmentVarTable = pEnvironmentVariables; + } + +Finished: + return hr; +} + +HRESULT +SERVER_PROCESS::GetRandomPort +( + DWORD* pdwPickedPort, + DWORD dwExcludedPort = 0 +) +{ + HRESULT hr = S_OK; + BOOL fPortInUse = FALSE; + DWORD dwActualProcessId = 0; + + if (g_fNsiApiNotSupported) + { + // + // the default value for optional parameter dwExcludedPort is 0 which is reserved + // a random number between MIN_PORT and MAX_PORT + // + while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + } + else + { + DWORD cRetry = 0; + do + { + // + // ignore dwActualProcessId because here we are + // determing whether the randomly generated port is + // in use by any other process. + // + while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + hr = CheckIfServerIsUp(*pdwPickedPort, &dwActualProcessId, &fPortInUse); + } while (fPortInUse && ++cRetry < MAX_RETRY); + + if (cRetry >= MAX_RETRY) + { + hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); + } + } + + return hr; +} + +HRESULT +SERVER_PROCESS::SetupListenPort( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + ENVIRONMENT_VAR_ENTRY *pEntry = NULL; + pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry); + if (pEntry != NULL) + { + pEntry->Dereference(); + if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') + { + m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); + if (m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) + { + hr = E_INVALIDARG; + goto Finished; + // need add log for this one + } + hr = m_struPort.Copy(pEntry->QueryValue()); + goto Finished; + } + else + { + // + // user set the env variable but did not give value, let's set it up + // + pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR); + } + } + + WCHAR buffer[15]; + if (FAILED(hr = GetRandomPort(&m_dwPort))) + { + goto Finished; + } + + if (swprintf_s(buffer, 15, L"%d", m_dwPort) <= 0) + { + hr = E_INVALIDARG; + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)) || + FAILED(hr = m_struPort.Copy(buffer))) + { + goto Finished; + } + +Finished: + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupAppPath( + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + pEnvironmentVarTable->FindKey(ASPNETCORE_APP_PATH_ENV_STR, &pEntry); + if (pEntry != NULL) + { + // user should not set this environment variable in configuration + pEnvironmentVarTable->DeleteKey(ASPNETCORE_APP_PATH_ENV_STR); + pEntry->Dereference(); + pEntry = NULL; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppVirtualPath.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) + { + goto Finished; + } + +Finished: + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupAppToken( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + UUID logUuid; + PSTR pszLogUuid = NULL; + BOOL fRpcStringAllocd = FALSE; + RPC_STATUS rpcStatus; + STRU strAppToken; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + pEnvironmentVarTable->FindKey(ASPNETCORE_APP_TOKEN_ENV_STR, &pEntry); + if (pEntry != NULL) + { + // user sets the environment variable + m_straGuid.Reset(); + hr = m_straGuid.CopyW(pEntry->QueryValue()); + pEntry->Dereference(); + pEntry = NULL; + goto Finished; + } + else + { + if (m_straGuid.IsEmpty()) + { + // the GUID has not been set yet + rpcStatus = UuidCreate(&logUuid); + if (rpcStatus != RPC_S_OK) + { + hr = rpcStatus; + goto Finished; + } + + rpcStatus = UuidToStringA(&logUuid, (BYTE **)&pszLogUuid); + if (rpcStatus != RPC_S_OK) + { + hr = rpcStatus; + goto Finished; + } + + fRpcStringAllocd = TRUE; + + if (FAILED(hr = m_straGuid.Copy(pszLogUuid))) + { + goto Finished; + } + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(strAppToken.CopyA(m_straGuid.QueryStr())) || + FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) + { + goto Finished; + } + } + +Finished: + + if (fRpcStringAllocd) + { + RpcStringFreeA((BYTE **)&pszLogUuid); + pszLogUuid = NULL; + } + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + + +HRESULT +SERVER_PROCESS::InitEnvironmentVariablesTable( + ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + BOOL fFound = FALSE; + DWORD dwResult, dwError; + STRU strIisAuthEnvValue; + STACK_STRU(strStartupAssemblyEnv, 1024); + ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL; + ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL; + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; + + pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); + if (pEnvironmentVarTable == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // few environment variables expected, small bucket size for hash table + // + if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/))) + { + goto Finished; + } + + // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements + m_pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToTable, pEnvironmentVarTable); + if (pEnvironmentVarTable->Count() != m_pEnvironmentVarTable->Count()) + { + // hash table copy failed + hr = E_UNEXPECTED; + goto Finished; + } + + pEnvironmentVarTable->FindKey(ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry); + if (pIISAuthEntry != NULL) + { + // user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off + pIISAuthEntry->Dereference(); + pEnvironmentVarTable->DeleteKey(ASPNETCORE_IIS_AUTH_ENV_STR); + } + + if (m_fWindowsAuthEnabled) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS); + } + + if (m_fBasicAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC); + } + + if (m_fAnonymousAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS); + } + + if (strIisAuthEnvValue.IsEmpty()) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE); + } + + pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pIISAuthEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry))) + { + goto Finished; + } + + + pEnvironmentVarTable->FindKey(HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); + if (pHostingEntry != NULL) + { + // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration + // the value will be used in OutputEnvironmentVariables. Do nothing here + pHostingEntry->Dereference(); + pHostingEntry = NULL; + goto Skipped; + } + + //check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (dwResult == 0) + { + dwError = GetLastError(); + // Windows API (e.g., CreateProcess) allows variable with empty string value + // in such case dwResult will be 0 and dwError will also be 0 + // As UI and CMD does not allow empty value, ignore this environment var + if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH()) + { + // have to increase the buffer and try get environment var again + strStartupAssemblyEnv.Reset(); + strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) + 1); + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (strStartupAssemblyEnv.IsEmpty()) + { + hr = E_UNEXPECTED; + goto Finished; + } + fFound = TRUE; + } + else + { + fFound = TRUE; + } + + strStartupAssemblyEnv.SyncWithBuffer(); + if (fFound) + { + strStartupAssemblyEnv.Append(L";"); + } + strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE); + + // the environment variable was not defined, create it and add to hashtable + pHostingEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pHostingEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry))) + { + goto Finished; + } + +Skipped: + *ppEnvironmentVarTable = pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + +Finished: + if (pHostingEntry != NULL) + { + pHostingEntry->Dereference(); + pHostingEntry = NULL; + } + + if (pIISAuthEntry != NULL) + { + pIISAuthEntry->Dereference(); + pIISAuthEntry = NULL; + } + + if (pEnvironmentVarTable != NULL) + { + pEnvironmentVarTable->Clear(); + delete pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::OutputEnvironmentVariables +( + MULTISZ* pmszOutput, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + LPWSTR pszEnvironmentVariables = NULL; + LPWSTR pszCurrentVariable = NULL; + LPWSTR pszNextVariable = NULL; + LPWSTR pszEqualChar = NULL; + STRU strEnvVar; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + DBG_ASSERT(pmszOutput); + DBG_ASSERT(pEnvironmentVarTable); // We added some startup variables + DBG_ASSERT(pEnvironmentVarTable->Count() >0); + + pszEnvironmentVariables = GetEnvironmentStringsW(); + if (pszEnvironmentVariables == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + pszCurrentVariable = pszEnvironmentVariables; + while (*pszCurrentVariable != L'\0') + { + pszNextVariable = pszCurrentVariable + wcslen(pszCurrentVariable) + 1; + pszEqualChar = wcschr(pszCurrentVariable, L'='); + if (pszEqualChar != NULL) + { + if (FAILED(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1))) + { + goto Finished; + } + pEnvironmentVarTable->FindKey(strEnvVar.QueryStr(), &pEntry); + if (pEntry != NULL) + { + // same env variable is defined in configuration, use it + if (FAILED(hr = strEnvVar.Append(pEntry->QueryValue()))) + { + goto Finished; + } + pmszOutput->Append(strEnvVar); //should we check the returned bool + // remove the record from hash table as we already output it + pEntry->Dereference(); + pEnvironmentVarTable->DeleteKey(pEntry->QueryName()); + strEnvVar.Reset(); + pEntry = NULL; + } + else + { + pmszOutput->Append(pszCurrentVariable); + } + } + else + { + // env varaible is not well formated + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + // move to next env variable + pszCurrentVariable = pszNextVariable; + } + // append the remaining env variable in hash table + pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToMultiSz, pmszOutput); + +Finished: + if (pszEnvironmentVariables != NULL) + { + FreeEnvironmentStringsW(pszEnvironmentVariables); + pszEnvironmentVariables = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupCommandLine( + STRU* pstrCommandLine +) +{ + HRESULT hr = S_OK; + LPWSTR pszPath = NULL; + LPWSTR pszFullPath = NULL; + STRU strRelativePath; + DWORD dwBufferSize = 0; + FILE *file = NULL; + + DBG_ASSERT(pstrCommandLine); + + pszPath = m_ProcessPath.QueryStr(); + + if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) + { + // let's check whether it is a relative path + if (FAILED(hr = strRelativePath.Copy(m_struPhysicalPath.QueryStr())) || + FAILED(hr = strRelativePath.Append(L"\\")) || + FAILED(hr = strRelativePath.Append(pszPath))) + { + goto Finished; + } + + dwBufferSize = strRelativePath.QueryCCH() + 1; + pszFullPath = new WCHAR[dwBufferSize]; + if (pszFullPath == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (_wfullpath(pszFullPath, + strRelativePath.QueryStr(), + dwBufferSize) == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL) + { + fclose(file); + pszPath = pszFullPath; + } + } + if (FAILED(hr = pstrCommandLine->Copy(pszPath)) || + FAILED(hr = pstrCommandLine->Append(L" ")) || + FAILED(hr = pstrCommandLine->Append(m_Arguments.QueryStr()))) + { + goto Finished; + } + +Finished: + if (pszFullPath != NULL) + { + delete pszFullPath; + } + return hr; +} + +HRESULT +SERVER_PROCESS::PostStartCheck( + VOID +) +{ + HRESULT hr = S_OK; + + BOOL fReady = FALSE; + BOOL fProcessMatch = FALSE; + BOOL fDebuggerAttached = FALSE; + DWORD dwTickCount = 0; + DWORD dwTimeDifference = 0; + DWORD dwActualProcessId = 0; + INT iChildProcessIndex = -1; + + if (CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + + dwTickCount = GetTickCount(); + do + { + DWORD processStatus; + if (GetExitCodeProcess(m_hProcessHandle, &processStatus)) + { + // make sure the process is still running + if (processStatus != STILL_ACTIVE) + { + hr = E_FAIL; + /*pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + m_pStruCommandline->QueryStr(), + hr, + processStatus);*/ + goto Finished; + } + } + // + // dwActualProcessId will be set only when NsiAPI(GetExtendedTcpTable) is supported + // + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); + fDebuggerAttached = IsDebuggerIsAttached(); + + if (!fReady) + { + Sleep(250); + } + + dwTimeDifference = (GetTickCount() - dwTickCount); + } while (fReady == FALSE && + ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttached)); + + // register call back with the created process + if (FAILED(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle))) + { + goto Finished; + } + + // + // check if debugger is attached after startupTimeout. + // + if (!fDebuggerAttached && + CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + if (!g_fNsiApiNotSupported) + { + // + // NsiAPI(GetExtendedTcpTable) is supported. we should check whether processIds matche + // + if (dwActualProcessId == m_dwProcessId) + { + m_dwListeningProcessId = m_dwProcessId; + fProcessMatch = TRUE; + } + + if (!fProcessMatch) + { + // could be the scenario that backend creates child process + if (FAILED(hr = GetChildProcessHandles())) + { + goto Finished; + } + + for (DWORD i = 0; i < m_cChildProcess; ++i) + { + // a child process listen on the assigned port + if (dwActualProcessId == m_dwChildProcessIds[i]) + { + m_dwListeningProcessId = m_dwChildProcessIds[i]; + fProcessMatch = TRUE; + + if (m_hChildProcessHandles[i] != NULL) + { + if (fDebuggerAttached == FALSE && + CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + + if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], + m_hChildProcessHandles[i]))) + { + goto Finished; + } + iChildProcessIndex = i; + } + break; + } + } + } + + if(!fProcessMatch) + { + // + // process that we created is not listening + // on the port we specified. + // + fReady = FALSE; + //pStruErrorMessage->SafeSnwprintf( + // ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, + // m_struAppFullPath.QueryStr(), + // m_pszRootApplicationPath.QueryStr(), + // m_struCommandline.QueryStr(), + // m_dwPort, + // hr); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + } + + if (!fReady) + { + // + // hr is already set by CheckIfServerIsUp + // + if (dwTimeDifference >= m_dwStartupTimeLimitInMS) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + /*pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + m_dwPort, + hr);*/ + } + goto Finished; + } + + if (iChildProcessIndex >= 0) + { + // + // final check to make sure child process listening on HTTP is still UP + // This is needed because, the child process might have crashed/exited between + // the previous call to checkIfServerIsUp and RegisterProcessWait + // and we would not know about it. + // + + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); + + if ((FAILED(hr) || fReady == FALSE)) + { + //pStruErrorMessage->SafeSnwprintf( + // ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + // m_struAppFullPath.QueryStr(), + // m_pszRootApplicationPath.QueryStr(), + // pStruCommandline->QueryStr(), + // m_dwPort, + // hr); + goto Finished; + } + } + + // + // ready to mark the server process ready but before this, + // create and initialize the FORWARDER_CONNECTION + // + if (m_pForwarderConnection == NULL) + { + m_pForwarderConnection = new FORWARDER_CONNECTION(); + if (m_pForwarderConnection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pForwarderConnection->Initialize(m_dwPort); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (!g_fNsiApiNotSupported) + { + m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + m_dwListeningProcessId); + } + + // + // mark server process as Ready + // + m_fReady = TRUE; + +Finished: + if (FAILED(hr)) + { + if (m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + } + return hr; +} + +HRESULT +SERVER_PROCESS::StartProcess( + VOID +) +{ + HRESULT hr = S_OK; + PROCESS_INFORMATION processInformation = {0}; + STARTUPINFOW startupInfo = {0}; +// BOOL fDonePrepareCommandLine = FALSE; + DWORD dwRetryCount = 2; // should we allow customer to config it + DWORD dwCreationFlags = 0; + +// STACK_STRU( strEventMsg, 256); +// STRU strFullProcessPath; +// STRU struRelativePath; +// STRU struApplicationId; +// +// LPCWSTR apsz[1]; + MULTISZ mszNewEnvironment; + ENVIRONMENT_VAR_HASH *pHashTable = NULL; +// + GetStartupInfoW(&startupInfo); + + // + // setup stdout and stderr handles to our stdout handle only if + // the handle is valid. + // + SetupStdHandles(&startupInfo); + + // + // generate process command line. + // + if (FAILED(hr = SetupCommandLine(&m_struCommandLine))) + { + goto Finished; + } + + while (dwRetryCount > 0) + { + dwRetryCount--; + if (FAILED(hr = InitEnvironmentVariablesTable(&pHashTable))) + { + goto Finished; + } + + // + // setup the the port that the backend process will listen on + // + if (FAILED(hr = SetupListenPort(pHashTable))) + { + goto Finished; + } + + // + // get app path + // + if (FAILED(hr = SetupAppPath(pHashTable))) + { + goto Finished; + } + + // + // generate new guid for each process + // + if (FAILED(hr = SetupAppToken(pHashTable))) + { + goto Finished; + } + + // + // setup environment variables for new process + // + if (FAILED(hr = OutputEnvironmentVariables(&mszNewEnvironment, pHashTable))) + { + goto Finished; + } + + dwCreationFlags = CREATE_NO_WINDOW | + CREATE_UNICODE_ENVIRONMENT | + CREATE_SUSPENDED | + CREATE_NEW_PROCESS_GROUP; + + if (!CreateProcessW( + NULL, // applicationName + m_struCommandLine.QueryStr(), + NULL, // processAttr + NULL, // threadAttr + TRUE, // inheritHandles + dwCreationFlags, + mszNewEnvironment.QueryStr(), + m_struPhysicalPath.QueryStr(), // currentDir + &startupInfo, + &processInformation)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + // don't the check return code as we already in error report + /*strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + struCommandLine.QueryStr(), + hr, + 0);*/ + goto Finished; + } + + m_hProcessHandle = processInformation.hProcess; + m_dwProcessId = processInformation.dwProcessId; + + if (m_hJobObject != NULL) + { + if (!AssignProcessToJobObject(m_hJobObject, m_hProcessHandle)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + if (hr != HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)) + { + goto Finished; + } + } + } + + if (ResumeThread(processInformation.hThread) == -1) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // need to make sure the server is up and listening on the port specified. + // + if (FAILED(hr = PostStartCheck())) + { + goto Finished; + } + + // Backend process starts successfully. Set retry counter to 0 + dwRetryCount = 0; + + // + // if (SUCCEEDED(strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, + // m_struAppFullPath.QueryStr(), + // m_dwProcessId, + // m_dwPort))) + // { + // apsz[0] = strEventMsg.QueryStr(); + // + // // + // // not checking return code because if ReportEvent + // // fails, we cannot do anything. + // // + // if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_INFORMATION_TYPE, + // 0, + // ASPNETCORE_EVENT_PROCESS_START_SUCCESS, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // } + // + // // FREB log + //if (ANCMEvents::ANCM_START_APPLICATION_SUCCESS::IsEnabled(context->GetTraceContext())) + //{ + // ANCMEvents::ANCM_START_APPLICATION_SUCCESS::RaiseEvent( + // context->GetTraceContext(), + // NULL, + // apsz[0]); + //} + // } + // + + Finished: + if (processInformation.hThread != NULL) + { + CloseHandle(processInformation.hThread); + processInformation.hThread = NULL; + } + + if (pHashTable != NULL) + { + pHashTable->Clear(); + delete pHashTable; + pHashTable = NULL; + } + } + +// if (FAILED(hr)) +// { +// if (strEventMsg.IsEmpty()) +// { +// if (!fDonePrepareCommandLine) +// { +// strEventMsg.SafeSnwprintf( +// m_struAppFullPath.QueryStr(), +// ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, +// hr); +// } +// else +// { +// strEventMsg.SafeSnwprintf( +// ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, +// m_struAppFullPath.QueryStr(), +// m_pszRootApplicationPath.QueryStr(), +// struCommandLine.QueryStr(), +// hr); +// } +// } +// +// apsz[0] = strEventMsg.QueryStr(); +// +// // not checking return code because if ReportEvent +// // fails, we cannot do anything. +// // +// if (FORWARDING_HANDLER::QueryEventLog() != NULL) +// { +// ReportEventW(FORWARDING_HANDLER::QueryEventLog(), +// EVENTLOG_ERROR_TYPE, +// 0, +// ASPNETCORE_EVENT_PROCESS_START_ERROR, +// NULL, +// 1, +// 0, +// apsz, +// NULL); +// } +// +// // FREB log +// if (ANCMEvents::ANCM_START_APPLICATION_FAIL::IsEnabled(context->GetTraceContext())) +// { +// ANCMEvents::ANCM_START_APPLICATION_FAIL::RaiseEvent( +// context->GetTraceContext(), +// NULL, +// strEventMsg.QueryStr()); +// } +// } + + if (FAILED(hr) || m_fReady == FALSE) + { + if (m_hStdoutHandle != NULL) + { + if (m_hStdoutHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hStdoutHandle); + } + m_hStdoutHandle = NULL; + } + + if (m_fStdoutLogEnabled) + { + m_Timer.CancelTimer(); + } + + if (m_hListeningProcessHandle != NULL) + { + if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hListeningProcessHandle); + } + m_hListeningProcessHandle = NULL; + } + + if (m_hProcessWaitHandle != NULL) + { + UnregisterWait(m_hProcessWaitHandle); + m_hProcessWaitHandle = NULL; + } + + StopProcess(); + + StopAllProcessesInJobObject(); + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetWindowsAuthToken( + HANDLE hToken, + LPHANDLE pTargetTokenHandle +) +{ + HRESULT hr = S_OK; + + if (m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE) + { + if (!DuplicateHandle( GetCurrentProcess(), + hToken, + m_hListeningProcessHandle, + pTargetTokenHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS )) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + +Finished: + + return hr; +} + +HRESULT +SERVER_PROCESS::SetupStdHandles( + LPSTARTUPINFOW pStartupInfo +) +{ + HRESULT hr = S_OK; + SYSTEMTIME systemTime; + SECURITY_ATTRIBUTES saAttr = { 0 }; + + STRU struPath; + //STRU strEventMsg; + //LPCWSTR apsz[1]; + + DBG_ASSERT(pStartupInfo); + + if (m_hStdoutHandle != NULL && m_hStdoutHandle != INVALID_HANDLE_VALUE) + { + if (!CloseHandle(m_hStdoutHandle)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + m_hStdoutHandle = NULL; + } + + hr = UTILITY::ConvertPathToFullPath( + m_struLogFile.QueryStr(), + m_struPhysicalPath.QueryStr(), + &struPath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); + if (FAILED(hr)) + { + goto Finished; + } + + GetSystemTime(&systemTime); + hr = m_struFullLogFile.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", + struPath.QueryStr(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond, + GetCurrentProcessId()); + if (FAILED(hr)) + { + goto Finished; + } + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + m_hStdoutHandle = CreateFileW(m_struFullLogFile.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (m_hStdoutHandle == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (m_fStdoutLogEnabled) + { + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = m_hStdoutHandle; + pStartupInfo->hStdOutput = m_hStdoutHandle; + // start timer to open and close handles regularly. + m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struFullLogFile, 3000, 3000); + } + else + { // only enable stderr + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = m_hStdoutHandle; + pStartupInfo->hStdOutput = INVALID_HANDLE_VALUE; + } + +Finished: + if (FAILED(hr)) + { + // The log file was not created yet in case of failure. No need to clean it + m_struFullLogFile.Reset(); + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = INVALID_HANDLE_VALUE; + pStartupInfo->hStdOutput = INVALID_HANDLE_VALUE; + + if (m_fStdoutLogEnabled) + { + // Log the error + //if (SUCCEEDED(strEventMsg.SafeSnwprintf( + // ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + // m_struFullLogFile.QueryStr(), + // HRESULT_FROM_GETLASTERROR()))) + //{ + // apsz[0] = strEventMsg.QueryStr(); + + // // + // // not checking return code because if ReportEvent + // // fails, we cannot do anything. + // // + // /*if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // { + // ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + // EVENTLOG_WARNING_TYPE, + // 0, + // ASPNETCORE_EVENT_CONFIG_ERROR, + // NULL, + // 1, + // 0, + // apsz, + // NULL); + // }*/ + //} + } + } + return hr; +} + +HRESULT +SERVER_PROCESS::CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady +) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + MIB_TCPTABLE_OWNER_PID *pTCPInfo = NULL; + MIB_TCPROW_OWNER_PID *pOwner = NULL; + DWORD dwSize = 0; + int iResult = 0; + SOCKADDR_IN sockAddr; + SOCKET socketCheck = INVALID_SOCKET; + + DBG_ASSERT(pfReady); + DBG_ASSERT(pdwProcessId); + + *pfReady = FALSE; + // + // it's OK for us to return processID 0 in case we cannot detect the real one + // + *pdwProcessId = 0; + + if (!g_fNsiApiNotSupported) + { + dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + + pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); + if (pTCPInfo == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + dwResult = GetExtendedTcpTable(pTCPInfo, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + if (dwResult != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + + // iterate pTcpInfo struct to find PID/PORT entry + for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++) + { + pOwner = &pTCPInfo->table[dwLoop]; + if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort) + { + *pdwProcessId = pOwner->dwOwningPid; + *pfReady = TRUE; + break; + } + } + } + else + { + // + // We have to open socket to ping the service + // + socketCheck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (socketCheck == INVALID_SOCKET) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + sockAddr.sin_family = AF_INET; + if (!inet_pton(AF_INET, LOCALHOST, &(sockAddr.sin_addr))) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + //sockAddr.sin_addr.s_addr = inet_addr( LOCALHOST ); + sockAddr.sin_port = htons((u_short)dwPort); + + // + // Connect to server. + // if connection fails, socket is not closed, we reuse the same socket + // while retrying + // + iResult = connect(socketCheck, (SOCKADDR *)&sockAddr, sizeof(sockAddr)); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + *pfReady = TRUE; + } + +Finished: + + if (socketCheck != INVALID_SOCKET) + { + iResult = closesocket(socketCheck); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + } + socketCheck = INVALID_SOCKET; + } + + if (pTCPInfo != NULL) + { + HeapFree(GetProcessHeap(), 0, pTCPInfo); + pTCPInfo = NULL; + } + + return hr; +} + +// send signal to the process to let it gracefully shutdown +// if the process cannot shutdown within given time, terminate it +VOID +SERVER_PROCESS::SendSignal( + VOID +) +{ + HRESULT hr = S_OK; + HANDLE hThread = NULL; + + ReferenceServerProcess(); + + m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); + + if (m_hShutdownHandle == NULL) + { + // since we cannot open the process. let's terminate the process + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)SendShutDownSignal, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (hThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + goto Finished; + } + // thread should already exit + CloseHandle(hThread); + hThread = NULL; + +Finished: + if (hThread != NULL) + { + // if the send shutdown message thread is still running, terminate it + DWORD dwThreadStatus = 0; + if (GetExitCodeThread(hThread, &dwThreadStatus)!= 0 && dwThreadStatus == STILL_ACTIVE) + { + TerminateThread(hThread, STATUS_CONTROL_C_EXIT); + } + CloseHandle(hThread); + hThread = NULL; + } + + if (FAILED(hr)) + { + TerminateBackendProcess(); + } + + if (m_hShutdownHandle != NULL && m_hShutdownHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hShutdownHandle); + m_hShutdownHandle = NULL; + } + + DereferenceServerProcess(); +} + + +// +// StopProcess is only called if process crashes OR if the process +// creation failed and calling this counts towards RapidFailCounts. +// +VOID +SERVER_PROCESS::StopProcess( + VOID +) +{ + m_fReady = FALSE; + + m_pProcessManager->IncrementRapidFailCount(); + + for (INT i=0; iNumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0)); + + if (dwError == ERROR_MORE_DATA) + { + hr = E_OUTOFMEMORY; + // some error + goto Finished; + } + + if (processList == NULL || + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0)) + { + hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED); + // some error + goto Finished; + } + + if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES) + { + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + + for (DWORD i=0; iNumberOfProcessIdsInList; i++) + { + dwPid = (DWORD)processList->ProcessIdList[i]; + if (dwPid != dwWorkerProcessPid) + { + HANDLE hProcess = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid); + + BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent); + if (hProcess != NULL) + { + CloseHandle(hProcess); + hProcess = NULL; + } + + if (!returnValue) + { + goto Finished; + } + + if (fDebuggerPresent) + { + break; + } + } + } + +Finished: + + if (processList != NULL) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return fDebuggerPresent; +} + +HRESULT +SERVER_PROCESS::GetChildProcessHandles( + VOID +) +{ + HRESULT hr = S_OK; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL; + DWORD dwPid = 0; + DWORD dwWorkerProcessPid = 0; + DWORD cbNumBytes = 1024; + DWORD dwRetries = 0; + DWORD dwError = NO_ERROR; + + dwWorkerProcessPid = GetCurrentProcessId(); + + do + { + dwError = NO_ERROR; + + if (processList != NULL) + { + HeapFree(GetProcessHeap(), 0, processList); + processList = NULL; + + // resize + cbNumBytes = cbNumBytes * 2; + } + + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if (processList == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + RtlZeroMemory(processList, cbNumBytes); + + if (!QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL)) + { + dwError = GetLastError(); + if (dwError != ERROR_MORE_DATA) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); + + if (dwError == ERROR_MORE_DATA) + { + hr = E_OUTOFMEMORY; + // some error + goto Finished; + } + + if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)) + { + hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED); + // some error + goto Finished; + } + + if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES) + { + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + + for (DWORD i=0; iNumberOfProcessIdsInList; i++) + { + dwPid = (DWORD)processList->ProcessIdList[i]; + if (dwPid != m_dwProcessId && + dwPid != dwWorkerProcessPid ) + { + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + m_dwChildProcessIds[m_cChildProcess] = dwPid; + m_cChildProcess ++; + } + } + +Finished: + + if (processList != NULL) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::StopAllProcessesInJobObject( + VOID +) +{ + HRESULT hr = S_OK; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL; + HANDLE hProcess = NULL; + DWORD dwWorkerProcessPid = 0; + DWORD cbNumBytes = 1024; + DWORD dwRetries = 0; + + dwWorkerProcessPid = GetCurrentProcessId(); + + do + { + if (processList != NULL) + { + HeapFree(GetProcessHeap(), 0, processList); + processList = NULL; + + // resize + cbNumBytes = cbNumBytes * 2; + } + + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if (processList == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + RtlZeroMemory(processList, cbNumBytes); + + if (!QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL)) + { + DWORD dwError = GetLastError(); + if (dwError != ERROR_MORE_DATA) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); + + if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + // some error + goto Finished; + } + + for (DWORD i=0; iNumberOfProcessIdsInList; i++) + { + if (dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i]) + { + hProcess = OpenProcess(PROCESS_TERMINATE, + FALSE, + (DWORD)processList->ProcessIdList[i]); + if (hProcess != NULL) + { + if (!TerminateProcess(hProcess, 1)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + else + { + WaitForSingleObject(hProcess, INFINITE); + } + + if (hProcess != NULL) + { + CloseHandle(hProcess); + hProcess = NULL; + } + } + } + } + +Finished: + + if (processList != NULL) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return hr; +} + +SERVER_PROCESS::SERVER_PROCESS() : + m_cRefs(1), + m_hProcessHandle(NULL), + m_hProcessWaitHandle(NULL), + m_dwProcessId(0), + m_cChildProcess(0), + m_fReady(FALSE), + m_lStopping(0L), + m_hStdoutHandle(NULL), + m_fStdoutLogEnabled(FALSE), + m_hJobObject(NULL), + m_pForwarderConnection(NULL), + m_dwListeningProcessId(0), + m_hListeningProcessHandle(NULL), + m_hShutdownHandle(NULL) +{ + //InterlockedIncrement(&g_dwActiveServerProcesses); + srand(GetTickCount()); + + for (INT i=0; iDereferenceProcessManager(); + m_pProcessManager = NULL; + } + + if (m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + + m_pEnvironmentVarTable = NULL; + // no need to free m_pEnvironmentVarTable, as it references to + // the same hash table hold by configuration. + // the hashtable memory will be freed once onfiguration got recycled + +// InterlockedDecrement(&g_dwActiveServerProcesses); +} + +//static +VOID +CALLBACK +SERVER_PROCESS::ProcessHandleCallback( + _In_ PVOID pContext, + _In_ BOOL +) +{ + SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*) pContext; + pServerProcess->HandleProcessExit(); +} + +HRESULT +SERVER_PROCESS::RegisterProcessWait( + PHANDLE phWaitHandle, + HANDLE hProcessToWaitOn +) +{ + HRESULT hr = S_OK; + NTSTATUS status = 0; + + _ASSERT(phWaitHandle != NULL && *phWaitHandle == NULL); + + *phWaitHandle = NULL; + + // wait thread will dereference. + ReferenceServerProcess(); + + status = RegisterWaitForSingleObject( + phWaitHandle, + hProcessToWaitOn, + (WAITORTIMERCALLBACKFUNC)&ProcessHandleCallback, + this, + INFINITE, + WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD + ); + + if (status < 0) + { + hr = HRESULT_FROM_NT(status); + goto Finished; + } + +Finished: + + if (FAILED(hr)) + { + *phWaitHandle = NULL; + DereferenceServerProcess(); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::HandleProcessExit( VOID ) +{ + HRESULT hr = S_OK; + BOOL fReady = FALSE; + DWORD dwProcessId = 0; + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady); + + if (!fReady) + { + m_pProcessManager->ShutdownProcess(this); + } + + DereferenceServerProcess(); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::SendShutdownHttpMessage( VOID ) +{ + HRESULT hr = S_OK; + HINTERNET hSession = NULL; + HINTERNET hConnect = NULL; + HINTERNET hRequest = NULL; + + STACK_STRU(strHeaders, 256); + STRU strAppToken; + STRU strUrl; + DWORD dwStatusCode = 0; + DWORD dwSize = sizeof(dwStatusCode); + + //LPCWSTR apsz[1]; + //STACK_STRU(strEventMsg, 256); + + hSession = WinHttpOpen(L"", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (hSession == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hConnect = WinHttpConnect(hSession, + L"127.0.0.1", + (USHORT)m_dwPort, + 0); + + if (hConnect == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + if (m_struAppVirtualPath.QueryCCH() > 1) + { + // app path size is 1 means site root, i.e., "/" + // we don't want to add duplicated '/' to the request url + // otherwise the request will fail + strUrl.Copy(m_struAppVirtualPath); + } + strUrl.Append(L"/iisintegration"); + + hRequest = WinHttpOpenRequest(hConnect, + L"POST", + strUrl.QueryStr(), + NULL, + WINHTTP_NO_REFERER, + NULL, + 0); + + if (hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // set timeout + if (!WinHttpSetTimeouts(hRequest, + m_dwShutdownTimeLimitInMS, // dwResolveTimeout + m_dwShutdownTimeLimitInMS, // dwConnectTimeout + m_dwShutdownTimeLimitInMS, // dwSendTimeout + m_dwShutdownTimeLimitInMS)) // dwReceiveTimeout + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // set up the shutdown headers + if (FAILED(hr = strHeaders.Append(L"MS-ASPNETCORE-EVENT:shutdown \r\n")) || + FAILED(hr = strAppToken.Append(L"MS-ASPNETCORE-TOKEN:")) || + FAILED(hr = strAppToken.AppendA(m_straGuid.QueryStr())) || + FAILED(hr = strHeaders.Append(strAppToken.QueryStr()))) + { + goto Finished; + } + + if (!WinHttpSendRequest(hRequest, + strHeaders.QueryStr(), // pwszHeaders + strHeaders.QueryCCH(), // dwHeadersLength + WINHTTP_NO_REQUEST_DATA, + 0, // dwOptionalLength + 0, // dwTotalLength + 0)) // dwContext + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpReceiveResponse(hRequest , NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &dwStatusCode, + &dwSize, + WINHTTP_NO_HEADER_INDEX)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (dwStatusCode != 202) + { + // not expected http status + hr = E_FAIL; + } + + // log + /*if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG, + m_dwProcessId, + dwStatusCode))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST, + NULL, + 1, + 0, + apsz, + NULL); + } + }*/ + +Finished: + if (hRequest) + { + WinHttpCloseHandle(hRequest); + hRequest = NULL; + } + if (hConnect) + { + WinHttpCloseHandle(hConnect); + hConnect = NULL; + } + if (hSession) + { + WinHttpCloseHandle(hSession); + hSession = NULL; + } + return hr; +} + +//static +VOID +SERVER_PROCESS::SendShutDownSignal( + LPVOID lpParam +) +{ + SERVER_PROCESS* pThis = static_cast(lpParam); + DBG_ASSERT(pThis); + pThis->SendShutDownSignalInternal(); +} + +// +// send shutdown message first, if fail then send +// ctrl-c to the backend process to let it gracefully shutdown +// +VOID +SERVER_PROCESS::SendShutDownSignalInternal( + VOID +) +{ + ReferenceServerProcess(); + + if (FAILED(SendShutdownHttpMessage())) + { + // + // failed to send shutdown http message + // try send ctrl signal + // + HWND hCurrentConsole = NULL; + BOOL fFreeConsole = FALSE; + hCurrentConsole = GetConsoleWindow(); + if (hCurrentConsole) + { + // free current console first, as we may have one, e.g., hostedwebcore case + fFreeConsole = FreeConsole(); + } + + if (AttachConsole(m_dwProcessId)) + { + // As we called CreateProcess with CREATE_NEW_PROCESS_GROUP + // call ctrl-break instead of ctrl-c as child process ignores ctrl-c + if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId)) + { + // failed to send the ctrl signal. terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); + } + FreeConsole(); + + if (fFreeConsole) + { + // IISExpress and hostedwebcore w3wp run as background process + // have to attach console back to ensure post app_offline scenario still works + AttachConsole(ATTACH_PARENT_PROCESS); + } + } + else + { + // terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); + } + } + + DereferenceServerProcess(); +} + +VOID +SERVER_PROCESS::TerminateBackendProcess( + VOID +) +{ + //LPCWSTR apsz[1]; + //STACK_STRU(strEventMsg, 256); + + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + // backend process will be terminated, remove the waitcallback + if (m_hProcessWaitHandle != NULL) + { + UnregisterWait(m_hProcessWaitHandle); + + // as we skipped process exit callback (ProcessHandleCallback), + // need to dereference the object otherwise memory leak + DereferenceServerProcess(); + + m_hProcessWaitHandle = NULL; + } + + // cannot gracefully shutdown or timeout, terminate the process + if (m_hProcessHandle != NULL && m_hProcessHandle != INVALID_HANDLE_VALUE) + { + TerminateProcess(m_hProcessHandle, 0); + m_hProcessHandle = NULL; + } + + // log a warning for ungraceful shutdown + /*if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, + m_dwProcessId))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_WARNING_TYPE, + 0, + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, + NULL, + 1, + 0, + apsz, + NULL); + } + }*/ + } +} \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/serverprocess.h b/src/RequestHandler/outofprocess/serverprocess.h new file mode 100644 index 0000000000..04579fc372 --- /dev/null +++ b/src/RequestHandler/outofprocess/serverprocess.h @@ -0,0 +1,282 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define MIN_PORT 1025 +#define MAX_PORT 48000 +#define MAX_RETRY 10 +#define MAX_ACTIVE_CHILD_PROCESSES 16 +#define LOCALHOST "127.0.0.1" +#define ASPNETCORE_PORT_STR L"ASPNETCORE_PORT" +#define ASPNETCORE_PORT_ENV_STR L"ASPNETCORE_PORT=" +#define ASPNETCORE_APP_PATH_ENV_STR L"ASPNETCORE_APPL_PATH=" +#define ASPNETCORE_APP_TOKEN_ENV_STR L"ASPNETCORE_TOKEN=" +#define ASPNETCORE_APP_PATH_ENV_STR L"ASPNETCORE_APPL_PATH=" +#define HOSTING_STARTUP_ASSEMBLIES_ENV_STR L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES" +#define HOSTING_STARTUP_ASSEMBLIES_NAME L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=" +#define HOSTING_STARTUP_ASSEMBLIES_VALUE L"Microsoft.AspNetCore.Server.IISIntegration" +#define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH=" +#define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;" +#define ASPNETCORE_IIS_AUTH_BASIC L"basic;" +#define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" +#define ASPNETCORE_IIS_AUTH_NONE L"none" + +class PROCESS_MANAGER; + +class SERVER_PROCESS +{ +public: + SERVER_PROCESS(); + + HRESULT + Initialize( + _In_ PROCESS_MANAGER *pProcessManager, + _In_ STRU *pszProcessExePath, + _In_ STRU *pszArguments, + _In_ DWORD dwStartupTimeLimitInMS, + _In_ DWORD dwShtudownTimeLimitInMS, + _In_ BOOL fWindowsAuthEnabled, + _In_ BOOL fBasicAuthEnabled, + _In_ BOOL fAnonymousAuthEnabled, + _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables, + _In_ BOOL fStdoutLogEnabled, + _In_ STRU *pstruStdoutLogFile, + _In_ STRU *pszAppPhysicalPath, + _In_ STRU *pszAppPath, + _In_ STRU *pszAppVirtualPath + ); + + HRESULT + StartProcess( VOID ); + + HRESULT + SetWindowsAuthToken( + _In_ HANDLE hToken, + _Out_ LPHANDLE pTargeTokenHandle + ); + + BOOL + IsReady( + VOID + ) + { + return m_fReady; + } + + VOID + StopProcess( + VOID + ); + + DWORD + GetPort() + { + return m_dwPort; + } + + VOID + ReferenceServerProcess( + VOID + ) + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceServerProcess( + VOID + ) + { + _ASSERT(m_cRefs != 0 ); + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + virtual + ~SERVER_PROCESS(); + + static + VOID + CALLBACK + ProcessHandleCallback( + _In_ PVOID pContext, + _In_ BOOL + ); + + HRESULT + HandleProcessExit( + VOID + ); + + FORWARDER_CONNECTION* + QueryWinHttpConnection( + VOID + ) + { + return m_pForwarderConnection; + } + + LPCSTR + QueryGuid() + { + return m_straGuid.QueryStr(); + }; + + VOID + SendSignal( + VOID + ); + +private: + + BOOL + IsDebuggerIsAttached( + VOID + ); + + HRESULT + StopAllProcessesInJobObject( + VOID + ); + + HRESULT + SetupStdHandles( + _Inout_ LPSTARTUPINFOW pStartupInfo + ); + + HRESULT + CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady + ); + + HRESULT + RegisterProcessWait( + _In_ PHANDLE phWaitHandle, + _In_ HANDLE hProcessToWaitOn + ); + + HRESULT + GetChildProcessHandles( + VOID + ); + + HRESULT + SetupListenPort( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable + ); + + HRESULT + SetupAppPath( + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable + ); + + HRESULT + SetupAppToken( + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable + ); + + HRESULT + InitEnvironmentVariablesTable( + ENVIRONMENT_VAR_HASH** pEnvironmentVarTable + ); + + HRESULT + OutputEnvironmentVariables( + MULTISZ* pmszOutput, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable + ); + + HRESULT + SetupCommandLine( + STRU* pstrCommandLine + ); + + HRESULT + PostStartCheck( + VOID + ); + + HRESULT + GetRandomPort( + DWORD* pdwPickedPort, + DWORD dwExcludedPort + ); + + static + VOID + SendShutDownSignal( + LPVOID lpParam + ); + + VOID + SendShutDownSignalInternal( + VOID + ); + + HRESULT + SendShutdownHttpMessage( + VOID + ); + + VOID + TerminateBackendProcess( + VOID + ); + + FORWARDER_CONNECTION *m_pForwarderConnection; + BOOL m_fStdoutLogEnabled; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + + STTIMER m_Timer; + SOCKET m_socket; + + STRU m_struLogFile; + STRU m_struFullLogFile; + STRU m_ProcessPath; + STRU m_Arguments; + STRU m_struAppVirtualPath; // e.g., '/' for site + STRU m_struAppFullPath; // e.g., /LM/W3SVC/4/ROOT/Inproc + STRU m_struPhysicalPath; // e.g., c:/test/mysite + STRU m_struPort; + STRU m_struCommandLine; + + volatile LONG m_lStopping; + volatile BOOL m_fReady; + mutable LONG m_cRefs; + + DWORD m_dwPort; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + DWORD m_cChildProcess; + DWORD m_dwChildProcessIds[MAX_ACTIVE_CHILD_PROCESSES]; + DWORD m_dwProcessId; + DWORD m_dwListeningProcessId; + + STRA m_straGuid; + + HANDLE m_hJobObject; + HANDLE m_hStdoutHandle; + // + // m_hProcessHandle is the handle to process this object creates. + // + HANDLE m_hProcessHandle; + HANDLE m_hListeningProcessHandle; + HANDLE m_hProcessWaitHandle; + HANDLE m_hShutdownHandle; + // + // m_hChildProcessHandle is the handle to process created by + // m_hProcessHandle process if it does. + // + HANDLE m_hChildProcessHandles[MAX_ACTIVE_CHILD_PROCESSES]; + HANDLE m_hChildProcessWaitHandles[MAX_ACTIVE_CHILD_PROCESSES]; + + PROCESS_MANAGER *m_pProcessManager; + ENVIRONMENT_VAR_HASH *m_pEnvironmentVarTable ; +}; \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/websockethandler.cxx b/src/RequestHandler/outofprocess/websockethandler.cxx new file mode 100644 index 0000000000..c64bbe4adb --- /dev/null +++ b/src/RequestHandler/outofprocess/websockethandler.cxx @@ -0,0 +1,1117 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +/*++ + +Abstract: + + Main Handler for websocket requests. + + Initiates websocket connection to backend. + Uses WinHttp API's for backend connections, + and IIS Websocket API's for sending/receiving + websocket traffic. + + Transfers data between the two IO endpoints. + +----------------- +Read Loop Design +----------------- +When a read IO completes successfully on any endpoints, Asp.Net Core Module doesn't +immediately issue the next read. The next read is initiated only after +the read data is sent to the other endpoint. As soon as this send completes, +we initiate the next IO. It should be noted that the send complete merely +indicates the API completion from HTTP, and not necessarily over the network. + +This prevents the need for data buffering at the Asp.Net Core Module level. + +--*/ + +#include "..\precomp.hxx" + +SRWLOCK WEBSOCKET_HANDLER::sm_RequestsListLock; + +LIST_ENTRY WEBSOCKET_HANDLER::sm_RequestsListHead; + +TRACE_LOG * WEBSOCKET_HANDLER::sm_pTraceLog; + +WEBSOCKET_HANDLER::WEBSOCKET_HANDLER() : + _pHttpContext(NULL), + _pWebSocketContext(NULL), + _hWebSocketRequest(NULL), + _pHandler(NULL), + _dwOutstandingIo(0), + _fCleanupInProgress(FALSE), + _fIndicateCompletionToIis(FALSE), + _fReceivedCloseMsg(FALSE) +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::WEBSOCKET_HANDLER"); + + InitializeCriticalSectionAndSpinCount(&_RequestLock, 1000); + InsertRequest(); +} + +VOID +WEBSOCKET_HANDLER::Terminate( + VOID + ) +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::Terminate"); + + RemoveRequest(); + _fCleanupInProgress = TRUE; + + if (_pHttpContext != NULL) + { + _pHttpContext->CancelIo(); + _pHttpContext = NULL; + } + if (_hWebSocketRequest) + { + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + } + + _pWebSocketContext = NULL; + DeleteCriticalSection(&_RequestLock); + + delete this; +} + +//static +HRESULT +WEBSOCKET_HANDLER::StaticInitialize( + BOOL fEnableReferenceCountTracing + ) +/*++ + + Routine Description: + + Initialize structures required for idle connection cleanup. + +--*/ +{ + if (!g_fWebSocketSupported) + { + return S_OK; + } + + if (fEnableReferenceCountTracing) + { + // + // If tracing is enabled, keep track of all websocket requests + // for debugging purposes. + // + InitializeListHead (&sm_RequestsListHead); + sm_pTraceLog = CreateRefTraceLog( 10000, 0 ); + } + + return S_OK; +} + +//static +VOID +WEBSOCKET_HANDLER::StaticTerminate( + VOID + ) +{ + if (!g_fWebSocketSupported) + { + return; + } + + if (sm_pTraceLog) + { + DestroyRefTraceLog(sm_pTraceLog); + sm_pTraceLog = NULL; + } +} + +VOID +WEBSOCKET_HANDLER::InsertRequest( + VOID + ) +{ + if (g_fEnableReferenceCountTracing) + { + AcquireSRWLockExclusive(&sm_RequestsListLock); + InsertTailList(&sm_RequestsListHead, &_listEntry); + ReleaseSRWLockExclusive( &sm_RequestsListLock); + } +} + +//static +VOID +WEBSOCKET_HANDLER::RemoveRequest( + VOID + ) +{ + if (g_fEnableReferenceCountTracing) + { + AcquireSRWLockExclusive(&sm_RequestsListLock); + RemoveEntryList(&_listEntry); + ReleaseSRWLockExclusive( &sm_RequestsListLock); + } +} + +VOID +WEBSOCKET_HANDLER::IncrementOutstandingIo( + VOID + ) +{ + InterlockedIncrement(&_dwOutstandingIo); + if (sm_pTraceLog) + { + WriteRefTraceLog(sm_pTraceLog, _dwOutstandingIo, this); + } +} + +VOID +WEBSOCKET_HANDLER::DecrementOutstandingIo( + VOID + ) +/*++ + Routine Description: + Decrements outstanding IO count. + + This indicates completion to IIS if all outstanding IO + has been completed, and a Cleanup was triggered for this + connection (denoted by _fIndicateCompletionToIis). + +--*/ +{ + LONG dwOutstandingIo = InterlockedDecrement (&_dwOutstandingIo); + + if (sm_pTraceLog) + { + WriteRefTraceLog(sm_pTraceLog, dwOutstandingIo, this); + } + + if (dwOutstandingIo == 0 && _fIndicateCompletionToIis) + { + IndicateCompletionToIIS(); + } +} + +VOID +WEBSOCKET_HANDLER::IndicateCompletionToIIS( + VOID + ) +/*++ + Routine Description: + Indicates completion to IIS. + + This returns a Pending Status, so that forwarding handler has a chance + to do book keeping when request is finally done. + +--*/ +{ + /*DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::IndicateCompletionToIIS");*/ + + _pHandler->SetStatus(FORWARDER_DONE); + + // do not call IndicateCompletion here + // wait for handle close callback and then call IndicateCompletion + // otherwise we may release W3Context too early and cause AV + //_pHttpContext->IndicateCompletion(RQ_NOTIFICATION_PENDING); + // close Websocket handle. This will triger a WinHttp callback + // on handle close, then let IIS pipeline continue. + WinHttpCloseHandle(_hWebSocketRequest); +} + +HRESULT +WEBSOCKET_HANDLER::ProcessRequest( + FORWARDING_HANDLER *pHandler, + IHttpContext *pHttpContext, + HINTERNET hRequest +) +/*++ + +Routine Description: + + Entry point to WebSocket Handler: + + This routine is called after the 101 response was successfully sent to + the client. + This routine get's a websocket handle to winhttp, + websocket handle to IIS's websocket context, and initiates IO + in these two endpoints. + + +--*/ +{ + HRESULT hr = S_OK; + //DWORD dwBuffSize = RECEIVE_BUFFER_SIZE; + + _pHandler = pHandler; + + EnterCriticalSection(&_RequestLock); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::ProcessRequest"); + + // + // Cache the points to IHttpContext3 + // + hr = HttpGetExtendedInterface(g_pHttpServer, + pHttpContext, + &_pHttpContext); + if (FAILED (hr)) + { + goto Finished; + } + + // + // Get pointer to IWebSocketContext for IIS websocket IO. + // + + _pWebSocketContext = (IWebSocketContext *) _pHttpContext-> + GetNamedContextContainer()->GetNamedContext(IIS_WEBSOCKET); + if ( _pWebSocketContext == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); + goto Finished; + } + + // + // Get Handle to Winhttp's websocket context. + // + _hWebSocketRequest = WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade( + hRequest, + (DWORD_PTR) pHandler); + + if (_hWebSocketRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Resize the send & receive buffers to be more conservative (and avoid DoS attacks). + // NOTE: The two WinHTTP options below were added for WinBlue, so we can't + // rely on their existence. + // + + //if (!WinHttpSetOption(_hWebSocketRequest, + // WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE, + // &dwBuffSize, + // sizeof(dwBuffSize))) + //{ + // DWORD dwRet = GetLastError(); + // if ( dwRet != ERROR_WINHTTP_INVALID_OPTION ) + // { + // hr = HRESULT_FROM_WIN32(dwRet); + // goto Finished; + // } + //} + + //if (!WinHttpSetOption(_hWebSocketRequest, + // WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE, + // &dwBuffSize, + // sizeof(dwBuffSize))) + //{ + // DWORD dwRet = GetLastError(); + // if ( dwRet != ERROR_WINHTTP_INVALID_OPTION ) + // { + // hr = HRESULT_FROM_WIN32(dwRet); + // goto Finished; + // } + //} + + // + // Initiate Read on IIS + // + hr = DoIisWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + + // + // Initiate Read on WinHttp + // + + hr = DoWinHttpWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + LeaveCriticalSection(&_RequestLock); + + if (FAILED (hr)) + { + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "Process Request Failed with HR=%08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoIisWebSocketReceive( + VOID +) +/*++ + +Routine Description: + + Initiates a websocket receive on the IIS Websocket Context. + + +--*/ +{ + HRESULT hr = S_OK; + DWORD dwBufferSize = RECEIVE_BUFFER_SIZE; + BOOL fUtf8Encoded; + BOOL fFinalFragment; + BOOL fClose; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoIisWebSocketReceive"); + + IncrementOutstandingIo(); + + hr = _pWebSocketContext->ReadFragment( + &_IisReceiveBuffer, + &dwBufferSize, + TRUE, + &fUtf8Encoded, + &fFinalFragment, + &fClose, + OnReadIoCompletion, + this, + NULL); + if (FAILED(hr)) + { + DecrementOutstandingIo(); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive( + VOID +) +/*++ + +Routine Description: + + Initiates a websocket receive on WinHttp + + +--*/ +{ + HRESULT hr = S_OK; + DWORD dwError = NO_ERROR; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive"); + + IncrementOutstandingIo(); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketReceive( + _hWebSocketRequest, + &_WinHttpReceiveBuffer, + RECEIVE_BUFFER_SIZE, + NULL, + NULL); + + if (dwError != NO_ERROR) + { + DecrementOutstandingIo(); + hr = HRESULT_FROM_WIN32(dwError); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive failed with %08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoIisWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType +) +/*++ + +Routine Description: + + Initiates a websocket send on IIS + +--*/ +{ + HRESULT hr = S_OK; + BOOL fUtf8Encoded = FALSE; + BOOL fFinalFragment = FALSE; + BOOL fClose = FALSE; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoIisWebSocketSend"); + + if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) + { + // + // Query Close Status from WinHttp + // + + DWORD dwError = NO_ERROR; + USHORT uStatus; + DWORD dwReceived = 0; + STACK_STRU(strCloseReason, 128); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketQueryCloseStatus( + _hWebSocketRequest, + &uStatus, + &_WinHttpReceiveBuffer, + RECEIVE_BUFFER_SIZE, + &dwReceived); + + if (dwError != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + + // + // Convert close reason to WCHAR + // + hr = strCloseReason.CopyA((PCSTR)&_WinHttpReceiveBuffer, + dwReceived); + if (FAILED(hr)) + { + goto Finished; + } + + IncrementOutstandingIo(); + // + // Backend end may start close hand shake first + // Need to inidcate no more receive should be called on WinHttp conneciton + // + _fReceivedCloseMsg = TRUE; + _fIndicateCompletionToIis = TRUE; + + // + // Send close to IIS. + // + hr = _pWebSocketContext->SendConnectionClose( + TRUE, + uStatus, + uStatus == 1005 ? NULL : strCloseReason.QueryStr(), + OnWriteIoCompletion, + this, + NULL); + } + else + { + // + // Get equivalant flags for IIS API from buffer type. + // + + WINHTTP_HELPER::GetFlagsFromBufferType(eBufferType, + &fUtf8Encoded, + &fFinalFragment, + &fClose); + + IncrementOutstandingIo(); + + // + // Do the Send. + // + hr = _pWebSocketContext->WriteFragment( + &_WinHttpReceiveBuffer, + &cbData, + TRUE, + fUtf8Encoded, + fFinalFragment, + OnWriteIoCompletion, + this, + NULL); + } + + if (FAILED(hr)) + { + DecrementOutstandingIo(); + } + +Finished: + if (FAILED(hr)) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoWinHttpWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType +) +/*++ + +Routine Description: + + Initiates a websocket send on WinHttp + +--*/ +{ + DWORD dwError = NO_ERROR; + HRESULT hr = S_OK; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend, %d", eBufferType); + + if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) + { + USHORT uStatus; + LPCWSTR pszReason; + STACK_STRA(strCloseReason, 128); + + // + // Get Close status from IIS. + // + hr = _pWebSocketContext->GetCloseStatus(&uStatus, + &pszReason); + + if (FAILED(hr)) + { + goto Finished; + } + + // + // Convert status to UTF8 + // + hr = strCloseReason.CopyWToUTF8Unescaped(pszReason); + if (FAILED(hr)) + { + goto Finished; + } + + IncrementOutstandingIo(); + + // + // Send Close. + // + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown( + _hWebSocketRequest, + uStatus, + strCloseReason.QueryCCH() == 0 ? NULL : (PVOID) strCloseReason.QueryStr(), + strCloseReason.QueryCCH()); + + if (dwError == ERROR_IO_PENDING) + { + // + // Call will complete asynchronously, return. + // ignore error. + // + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING"); + + dwError = NO_ERROR; + } + else + { + if (dwError == NO_ERROR) + { + // + // Call completed synchronously. + // + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful."); + } + } + } + else + { + IncrementOutstandingIo(); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketSend( + _hWebSocketRequest, + eBufferType, + cbData == 0 ? NULL : &_IisReceiveBuffer, + cbData + ); + } + + if (dwError != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwError); + DecrementOutstandingIo(); + goto Finished; + } + +Finished: + if (FAILED(hr)) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend failed with %08x", hr); + } + + return hr; +} + +//static +VOID +WINAPI +WEBSOCKET_HANDLER::OnReadIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ + + Routine Description: + + Completion routine for Read's from IIS pipeline. + +--*/ +{ + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + pvCompletionContext; + + pHandler->OnIisReceiveComplete( + hrError, + cbIO, + fUTF8Encoded, + fFinalFragment, + fClose + ); +} + +//static +VOID +WINAPI +WEBSOCKET_HANDLER::OnWriteIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ + Routine Description: + + Completion routine for Write's from IIS pipeline. + +--*/ +{ + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + pvCompletionContext; + + UNREFERENCED_PARAMETER(fUTF8Encoded); + UNREFERENCED_PARAMETER(fFinalFragment); + UNREFERENCED_PARAMETER(fClose); + + pHandler->OnIisSendComplete( + hrError, + cbIO + ); +} + + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpSendComplete( + WINHTTP_WEB_SOCKET_STATUS * + ) +/*++ + +Routine Description: + Completion callback executed when a send to backend + server completes. + + If the send was successful, issue the next read + on the client's endpoint. + +++*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpSendComplete"); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection (&_RequestLock); + + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + // + // Data was successfully sent to backend. + // Initiate next receive from IIS. + // + + hr = DoIisWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinsockSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpShutdownComplete( + VOID + ) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpShutdownComplete"); + + DecrementOutstandingIo(); + + return S_OK; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpIoError( + WINHTTP_WEB_SOCKET_ASYNC_RESULT *pCompletionStatus +) +{ + HRESULT hr = HRESULT_FROM_WIN32(pCompletionStatus->AsyncResult.dwError); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinHttpIoError HR = %08x, Operation = %d", + hr, pCompletionStatus->AsyncResult.dwResult); + + Cleanup(ServerDisconnect); + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpReceiveComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ) +/*++ + +Routine Description: + + Completion callback executed when a receive completes + on the backend server winhttp endpoint. + + Issue send on the Client(IIS) if the receive was + successful. + + If the receive completed with zero bytes, that + indicates that the server has disconnected the connection. + Issue cleanup for the websocket handler. +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpReceiveComplete"); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + hr = DoIisWebSocketSend( + pCompletionStatus->dwBytesTransferred, + pCompletionStatus->eBufferType + ); + + if (FAILED (hr)) + { + cleanupReason = ClientDisconnect; + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinsockReceiveComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnIisSendComplete( + HRESULT hrCompletion, + DWORD cbIo + ) +/*++ +Routine Description: + + Completion callback executed when a send + completes from the client. + + If send was successful,issue read on the + server endpoint, to continue the readloop. + +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + UNREFERENCED_PARAMETER(cbIo); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisSendComplete"); + + if (FAILED(hrCompletion)) + { + hr = hrCompletion; + cleanupReason = ClientDisconnect; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + + // + // Only call read if no close hand shake was received from backend + // + if (!_fReceivedCloseMsg) + { + // + // Write Completed, initiate next read from backend server. + // + hr = DoWinHttpWebSocketReceive(); + if (FAILED(hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnIisReceiveComplete( + HRESULT hrCompletion, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ +Routine Description: + + Completion routine executed when a receive completes + from the client (IIS endpoint). + + If the receive was successful, initiate a send on + the backend server (winhttp) endpoint. + + If the receive failed, initiate cleanup. + +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnIisReceiveComplete"); + + if (FAILED(hrCompletion)) + { + cleanupReason = ClientDisconnect; + hr = hrCompletion; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + // + // Get Buffer Type from flags. + // + + WINHTTP_HELPER::GetBufferTypeFromFlags(fUTF8Encoded, + fFinalFragment, + fClose, + &BufferType); + + // + // Initiate Send. + // + + hr = DoWinHttpWebSocketSend(cbIO, BufferType); + if (FAILED (hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +VOID +WEBSOCKET_HANDLER::Cleanup( + CleanupReason reason +) +/*++ + +Routine Description: + + Cleanup function for the websocket handler. + + Initiates cancelIo on the two IO endpoints: + IIS, WinHttp client. + +Arguments: + CleanupReason +--*/ +{ + BOOL fLocked = FALSE; + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + + _fCleanupInProgress = TRUE; + + _fIndicateCompletionToIis = TRUE; + + // + // TODO:: Raise FREB event with cleanup reason. + // + + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + + _pHttpContext->CancelIo(); + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } +} diff --git a/src/RequestHandler/outofprocess/websockethandler.h b/src/RequestHandler/outofprocess/websockethandler.h new file mode 100644 index 0000000000..f29c268658 --- /dev/null +++ b/src/RequestHandler/outofprocess/websockethandler.h @@ -0,0 +1,221 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +extern IHttpServer * g_pHttpServer; +class FORWARDING_HANDLER; + +class WEBSOCKET_HANDLER +{ +public: + WEBSOCKET_HANDLER(); + + static + HRESULT + StaticInitialize( + BOOL fEnableReferenceTraceLogging + ); + + static + VOID + StaticTerminate( + VOID + ); + + VOID + Terminate( + VOID + ); + + VOID + TerminateRequest( + VOID + ) + { + Cleanup(ServerStateUnavailable); + } + + HRESULT + ProcessRequest( + FORWARDING_HANDLER *pHandler, + IHttpContext * pHttpContext, + HINTERNET hRequest + ); + + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + VOID + ); + + HRESULT + OnWinHttpSendComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ); + + HRESULT + OnWinHttpShutdownComplete( + VOID + ); + + HRESULT + OnWinHttpReceiveComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ); + + HRESULT + OnWinHttpIoError( + WINHTTP_WEB_SOCKET_ASYNC_RESULT *pCompletionStatus + ); + + +private: + enum CleanupReason + { + CleanupReasonUnknown = 0, + IdleTimeout = 1, + ConnectFailed = 2, + ClientDisconnect = 3, + ServerDisconnect = 4, + ServerStateUnavailable = 5 + }; + + virtual + ~WEBSOCKET_HANDLER() + { + } + + WEBSOCKET_HANDLER(const WEBSOCKET_HANDLER &); + void operator=(const WEBSOCKET_HANDLER &); + + VOID + InsertRequest( + VOID + ); + + VOID + RemoveRequest( + VOID + ); + + static + VOID + WINAPI + OnReadIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ); + + static + VOID + WINAPI + OnWriteIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ); + + VOID + Cleanup( + CleanupReason reason + ); + + HRESULT + DoIisWebSocketReceive( + VOID + ); + + HRESULT + DoWinHttpWebSocketReceive( + VOID + ); + + HRESULT + DoIisWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType + ); + + HRESULT + DoWinHttpWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType + ); + + HRESULT + OnIisSendComplete( + HRESULT hrError, + DWORD cbIO + ); + + HRESULT + OnIisReceiveComplete( + HRESULT hrError, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ); + + VOID + IncrementOutstandingIo( + VOID + ); + + VOID + DecrementOutstandingIo( + VOID + ); + + VOID + IndicateCompletionToIIS( + VOID + ); + +private: + static const + DWORD RECEIVE_BUFFER_SIZE = 4*1024; + + LIST_ENTRY _listEntry; + + IHttpContext3 * _pHttpContext; + + IWebSocketContext * _pWebSocketContext; + + FORWARDING_HANDLER *_pHandler; + + HINTERNET _hWebSocketRequest; + + BYTE _WinHttpReceiveBuffer[RECEIVE_BUFFER_SIZE]; + + BYTE _IisReceiveBuffer[RECEIVE_BUFFER_SIZE]; + + CRITICAL_SECTION _RequestLock; + + LONG _dwOutstandingIo; + + volatile + BOOL _fCleanupInProgress; + + volatile + BOOL _fIndicateCompletionToIis; + + volatile + BOOL _fReceivedCloseMsg; + + static + LIST_ENTRY sm_RequestsListHead; + + static + SRWLOCK sm_RequestsListLock; + + static + TRACE_LOG * sm_pTraceLog; +}; \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/winhttphelper.cxx b/src/RequestHandler/outofprocess/winhttphelper.cxx new file mode 100644 index 0000000000..ce4256a710 --- /dev/null +++ b/src/RequestHandler/outofprocess/winhttphelper.cxx @@ -0,0 +1,176 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "..\precomp.hxx" + +PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE +WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade; + +PFN_WINHTTP_WEBSOCKET_SEND +WINHTTP_HELPER::sm_pfnWinHttpWebSocketSend; + +PFN_WINHTTP_WEBSOCKET_RECEIVE +WINHTTP_HELPER::sm_pfnWinHttpWebSocketReceive; + +PFN_WINHTTP_WEBSOCKET_SHUTDOWN +WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown; + +PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS +WINHTTP_HELPER::sm_pfnWinHttpWebSocketQueryCloseStatus; + +//static +HRESULT +WINHTTP_HELPER::StaticInitialize( + VOID +) +{ + HRESULT hr = S_OK; + + if (!g_fWebSocketSupported) + { + return S_OK; + } + + // + // Initialize the function pointers for WinHttp Websocket API's. + // + + HMODULE hWinHttp = GetModuleHandleA("winhttp.dll"); + if (hWinHttp == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketCompleteUpgrade = (PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE) + GetProcAddress(hWinHttp, "WinHttpWebSocketCompleteUpgrade"); + if (sm_pfnWinHttpWebSocketCompleteUpgrade == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketQueryCloseStatus = (PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS) + GetProcAddress(hWinHttp, "WinHttpWebSocketQueryCloseStatus"); + if (sm_pfnWinHttpWebSocketQueryCloseStatus == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketReceive = (PFN_WINHTTP_WEBSOCKET_RECEIVE) + GetProcAddress(hWinHttp, "WinHttpWebSocketReceive"); + if (sm_pfnWinHttpWebSocketReceive == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketSend = (PFN_WINHTTP_WEBSOCKET_SEND) + GetProcAddress(hWinHttp, "WinHttpWebSocketSend"); + if (sm_pfnWinHttpWebSocketSend == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketShutdown = (PFN_WINHTTP_WEBSOCKET_SHUTDOWN) + GetProcAddress(hWinHttp, "WinHttpWebSocketShutdown"); + if (sm_pfnWinHttpWebSocketShutdown == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + return hr; +} + + +//static +VOID +WINHTTP_HELPER::GetFlagsFromBufferType( + __in WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType, + __out BOOL * pfUtf8Encoded, + __out BOOL * pfFinalFragment, + __out BOOL * pfClose +) +{ + switch (BufferType) + { + case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE: + *pfUtf8Encoded = FALSE; + *pfFinalFragment = TRUE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE: + *pfUtf8Encoded = FALSE; + *pfFinalFragment = FALSE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE: + *pfUtf8Encoded = TRUE; + *pfFinalFragment = TRUE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE: + *pfUtf8Encoded = TRUE; + *pfFinalFragment = FALSE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE: + *pfUtf8Encoded = FALSE; + *pfFinalFragment = FALSE; + *pfClose = TRUE; + + break; + } +} + +//static +VOID +WINHTTP_HELPER::GetBufferTypeFromFlags( + __in BOOL fUtf8Encoded, + __in BOOL fFinalFragment, + __in BOOL fClose, + __out WINHTTP_WEB_SOCKET_BUFFER_TYPE* pBufferType +) +{ + if (fClose) + { + *pBufferType = WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE; + } + else + if (fUtf8Encoded) + { + if (fFinalFragment) + { + *pBufferType = WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE; + } + else + { + *pBufferType = WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE; + } + } + else + { + if (fFinalFragment) + { + *pBufferType = WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE; + } + else + { + *pBufferType = WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE; + } + } + + return; +} \ No newline at end of file diff --git a/src/RequestHandler/outofprocess/winhttphelper.h b/src/RequestHandler/outofprocess/winhttphelper.h new file mode 100644 index 0000000000..d583f6fb10 --- /dev/null +++ b/src/RequestHandler/outofprocess/winhttphelper.h @@ -0,0 +1,91 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +typedef +HINTERNET +(WINAPI * PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE)( + _In_ HINTERNET hRequest, + _In_opt_ DWORD_PTR pContext +); + + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_SEND)( + _In_ HINTERNET hWebSocket, + _In_ WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType, + _In_reads_opt_(dwBufferLength) PVOID pvBuffer, + _In_ DWORD dwBufferLength +); + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_RECEIVE)( + _In_ HINTERNET hWebSocket, + _Out_writes_bytes_to_(dwBufferLength, *pdwBytesRead) PVOID pvBuffer, + _In_ DWORD dwBufferLength, + _Out_range_(0, dwBufferLength) DWORD *pdwBytesRead, + _Out_ WINHTTP_WEB_SOCKET_BUFFER_TYPE *peBufferType +); + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_SHUTDOWN)( + _In_ HINTERNET hWebSocket, + _In_ USHORT usStatus, + _In_reads_bytes_opt_(dwReasonLength) PVOID pvReason, + _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength +); + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS)( + _In_ HINTERNET hWebSocket, + _Out_ USHORT *pusStatus, + _Out_writes_bytes_to_opt_(dwReasonLength, *pdwReasonLengthConsumed) PVOID pvReason, + _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength, + _Out_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD *pdwReasonLengthConsumed +); + +class WINHTTP_HELPER +{ +public: + static + HRESULT + StaticInitialize(); + + static + VOID + GetFlagsFromBufferType( + __in WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType, + __out BOOL * pfUtf8Encoded, + __out BOOL * pfFinalFragment, + __out BOOL * pfClose + ); + + static + VOID + GetBufferTypeFromFlags( + __in BOOL fUtf8Encoded, + __in BOOL fFinalFragment, + __in BOOL fClose, + __out WINHTTP_WEB_SOCKET_BUFFER_TYPE* pBufferType + ); + + static + PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE sm_pfnWinHttpWebSocketCompleteUpgrade; + + static + PFN_WINHTTP_WEBSOCKET_SEND sm_pfnWinHttpWebSocketSend; + + static + PFN_WINHTTP_WEBSOCKET_RECEIVE sm_pfnWinHttpWebSocketReceive; + + static + PFN_WINHTTP_WEBSOCKET_SHUTDOWN sm_pfnWinHttpWebSocketShutdown; + + static + PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS sm_pfnWinHttpWebSocketQueryCloseStatus; +}; \ No newline at end of file diff --git a/src/RequestHandler/precomp.hxx b/src/RequestHandler/precomp.hxx new file mode 100644 index 0000000000..a12c49cde9 --- /dev/null +++ b/src/RequestHandler/precomp.hxx @@ -0,0 +1,119 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#pragma warning( disable : 4091) + +// +// System related headers +// +#define _WINSOCKAPI_ + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// This should remove our issue of compiling for win7 without header files. +// We force the Windows 8 version check logic in iiswebsocket.h to succeed even though we're compiling for Windows 7. +// Then, we set the version defines back to Windows 7 to for the remainder of the compilation. +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT +#define NTDDI_VERSION 0x06020000 +#define WINVER 0x0602 +#define _WIN32_WINNT 0x0602 +#include +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include "..\IISLib\acache.h" +#include "..\IISLib\multisz.h" +#include "..\IISLib\multisza.h" +#include "..\IISLib\base64.h" +#include "..\IISLib\listentry.h" +#include "..\CommonLib\fx_ver.h" +#include "..\CommonLib\debugutil.h" +#include "..\CommonLib\requesthandler.h" +#include "..\CommonLib\aspnetcoreconfig.h" +#include "..\CommonLib\utility.h" +#include "..\CommonLib\application.h" +#include "aspnetcore_event.h" +#include "aspnetcore_msg.h" +#include "disconnectcontext.h" +#include "sttimer.h" +#include "resource.h" +#include ".\inprocess\InProcessHandler.h" +#include ".\inprocess\inprocessapplication.h" +#include ".\outofprocess\responseheaderhash.h" +#include ".\outofprocess\protocolconfig.h" +#include ".\outofprocess\forwarderconnection.h" +#include ".\outofprocess\serverprocess.h" +#include ".\outofprocess\processmanager.h" +#include ".\outofprocess\websockethandler.h" +#include ".\outofprocess\forwardinghandler.h" +#include ".\outofprocess\outprocessapplication.h" +#include ".\outofprocess\winhttphelper.h" + +#ifdef max +#undef max +template inline T max(T a, T b) +{ + return a > b ? a : b; +} +#endif + +#ifdef min +#undef min +template inline T min(T a, T b) +{ + return a < b ? a : b; +} +#endif + +#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module" +#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module" + +inline bool IsSpace(char ch) +{ + switch (ch) + { + case 32: // ' ' + case 9: // '\t' + case 10: // '\n' + case 13: // '\r' + case 11: // '\v' + case 12: // '\f' + return true; + default: + return false; + } +} + +extern BOOL g_fAsyncDisconnectAvailable; +extern BOOL g_fWinHttpNonBlockingCallbackAvailable; +extern BOOL g_fWebSocketSupported; +extern BOOL g_fNsiApiNotSupported; +extern BOOL g_fEnableReferenceCountTracing; +extern DWORD g_dwActiveServerProcesses; +extern DWORD g_OptionalWinHttpFlags; +extern SRWLOCK g_srwLockRH; +extern HINTERNET g_hWinhttpSession; +extern DWORD g_dwTlsIndex; diff --git a/src/RequestHandler/resource.h b/src/RequestHandler/resource.h new file mode 100644 index 0000000000..a8f93c62fb --- /dev/null +++ b/src/RequestHandler/resource.h @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define IDS_INVALID_PROPERTY 1000 +#define IDS_SERVER_ERROR 1001 + +#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 +#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and is listening on port '%d'." +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." +#define ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG L"Application '%s' failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s', ErrorCode = '0x%x' processStatus code '%x'." +#define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but failed to listen on the given port '%d'" +#define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not reponse or did not listen on the given port '%d', ErrorCode = '0x%x'" +#define ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG L"Warning: Could not create stdoutLogFile %s, ErrorCode = %d." +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." +#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application, ErrorCode = '0x%x." +#define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool." +#define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%s' other than the one of running application(s)." +#define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'." +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x." diff --git a/src/RequestHandler/sttimer.h b/src/RequestHandler/sttimer.h new file mode 100644 index 0000000000..1bd4b67543 --- /dev/null +++ b/src/RequestHandler/sttimer.h @@ -0,0 +1,280 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#ifndef _STTIMER_H +#define _STTIMER_H + +class STTIMER +{ +public: + + STTIMER() + : _pTimer( NULL ) + { + fInCanel = FALSE; + } + + virtual + ~STTIMER() + { + if ( _pTimer ) + { + CancelTimer(); + + CloseThreadpoolTimer( _pTimer ); + + _pTimer = NULL; + } + } + + HRESULT + InitializeTimer( + PTP_TIMER_CALLBACK pfnCallback, + VOID * pContext, + DWORD dwInitialWait = 0, + DWORD dwPeriod = 0 + ) + { + _pTimer = CreateThreadpoolTimer( pfnCallback, + pContext, + NULL ); + + if ( !_pTimer ) + { + return HRESULT_FROM_WIN32( GetLastError() ); + } + + if ( dwInitialWait ) + { + SetTimer( dwInitialWait, + dwPeriod ); + } + + return S_OK; + } + + VOID + SetTimer( + DWORD dwInitialWait, + DWORD dwPeriod = 0 + ) + { + FILETIME ftInitialWait; + + if ( dwInitialWait == 0 && dwPeriod == 0 ) + { + // + // Special case. We are preventing new callbacks + // from being queued. Any existing callbacks in the + // queue will still run. + // + // This effectively disables the timer. It can be + // re-enabled by setting non-zero initial wait or + // period values. + // + if (_pTimer != NULL) + { + SetThreadpoolTimer(_pTimer, NULL, 0, 0); + } + + return; + } + + InitializeRelativeFileTime( &ftInitialWait, dwInitialWait ); + + SetThreadpoolTimer( _pTimer, + &ftInitialWait, + dwPeriod, + 0 ); + } + + VOID + CancelTimer() + { + // + // Disable the timer + // + if (fInCanel) + return; + + fInCanel = TRUE; + SetTimer( 0 ); + + // + // Wait until any callbacks queued prior to disabling + // have completed. + // + if (_pTimer != NULL) + WaitForThreadpoolTimerCallbacks( _pTimer, TRUE ); + + _pTimer = NULL; + fInCanel = FALSE; + } + + static + VOID + CALLBACK + TimerCallback( + _In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID Context, + _In_ PTP_TIMER Timer + ) + { + Instance; + Timer; + STRU* pstruLogFilePath = (STRU*)Context; + HANDLE hStdoutHandle = NULL; + SECURITY_ATTRIBUTES saAttr = { 0 }; + HRESULT hr = S_OK; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + hStdoutHandle = CreateFileW(pstruLogFilePath->QueryStr(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hStdoutHandle == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + CloseHandle(hStdoutHandle); + } + +private: + + VOID + InitializeRelativeFileTime( + FILETIME * pft, + DWORD dwMilliseconds + ) + { + LARGE_INTEGER li; + + // + // The pftDueTime parameter expects the time to be + // expressed as the number of 100 nanosecond intervals + // times -1. + // + // To convert from milliseconds, we'll multiply by + // -10000 + // + + li.QuadPart = (LONGLONG)dwMilliseconds * -10000; + + pft->dwHighDateTime = li.HighPart; + pft->dwLowDateTime = li.LowPart; + }; + + TP_TIMER * _pTimer; + BOOL fInCanel; +}; + +class STELAPSED +{ +public: + + STELAPSED() + : _dwInitTime( 0 ), + _dwInitTickCount( 0 ), + _dwPerfCountsPerMillisecond( 0 ), + _fUsingHighResolution( FALSE ) + { + LARGE_INTEGER li; + BOOL fResult; + + _dwInitTickCount = GetTickCount64(); + + fResult = QueryPerformanceFrequency( &li ); + + if ( !fResult ) + { + goto Finished; + } + + _dwPerfCountsPerMillisecond = li.QuadPart / 1000; + + fResult = QueryPerformanceCounter( &li ); + + if ( !fResult ) + { + goto Finished; + } + + _dwInitTime = li.QuadPart / _dwPerfCountsPerMillisecond; + + _fUsingHighResolution = TRUE; + +Finished: + + return; + } + + virtual + ~STELAPSED() + { + } + + LONGLONG + QueryElapsedTime() + { + LARGE_INTEGER li; + + if ( _fUsingHighResolution && QueryPerformanceCounter( &li ) ) + { + DWORD64 dwCurrentTime = li.QuadPart / _dwPerfCountsPerMillisecond; + + if ( dwCurrentTime < _dwInitTime ) + { + // + // It's theoretically possible that QueryPerformanceCounter + // may return slightly different values on different CPUs. + // In this case, we don't want to return an unexpected value + // so we'll return zero. This is acceptable because + // presumably such a case would only happen for a very short + // time window. + // + // It would be possible to prevent this by ensuring processor + // affinity for all calls to QueryPerformanceCounter, but that + // would be undesirable in the general case because it could + // introduce unnecessary context switches and potentially a + // CPU bottleneck. + // + // Note that this issue also applies to callers doing rapid + // calls to this function. If a caller wants to mitigate + // that, they could enforce the affinitization, or they + // could implement a similar sanity check when comparing + // returned values from this function. + // + + return 0; + } + + return dwCurrentTime - _dwInitTime; + } + + return GetTickCount64() - _dwInitTickCount; + } + + BOOL + QueryUsingHighResolution() + { + return _fUsingHighResolution; + } + +private: + + DWORD64 _dwInitTime; + DWORD64 _dwInitTickCount; + DWORD64 _dwPerfCountsPerMillisecond; + BOOL _fUsingHighResolution; +}; + +#endif // _STTIMER_H \ No newline at end of file diff --git a/src/RequestHandler/version.h b/src/RequestHandler/version.h new file mode 100644 index 0000000000..cb2793c49a --- /dev/null +++ b/src/RequestHandler/version.h @@ -0,0 +1,8 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#define FileVersion 7,1,1987,0 +#define FileVersionStr "7.1.1987.0\0" +#define ProductVersion 7,1,1987,0 +#define ProductVersionStr "7.1.1987.0\0" +#define PlatformToolset "v141\0" diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj new file mode 100644 index 0000000000..882330ad6f --- /dev/null +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj @@ -0,0 +1,55 @@ + + + net461 + true + true + AspNetCoreModule.Test + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs new file mode 100644 index 0000000000..2286c63b4d --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -0,0 +1,1385 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.HttpClientHelper; +using Microsoft.Web.Administration; +using System; +using System.IO; +using System.ServiceProcess; +using System.Threading; + +namespace AspNetCoreModule.Test.Framework +{ + public class IISConfigUtility : IDisposable + { + public class Strings + { + public static string AppHostConfigPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "config", "applicationHost.config"); + public static string IIS64BitPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv"); + public static string IIS32BitPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv"); + public static string IISExpress64BitPath = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express"); + public static string IISExpress32BitPath = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express"); + public static string DefaultAppPool = "DefaultAppPool"; + } + + public static string ApppHostTemporaryBackupFileExtention = null; + private ServerType _serverType = ServerType.IIS; + private string _iisExpressConfigPath = null; + + public enum AppPoolBitness + { + enable32Bit, + noChange + } + + public void Dispose() + { + } + + public ServerManager GetServerManager() + { + if (_serverType == ServerType.IISExpress) + { + return new ServerManager( + false, // readOnly + _iisExpressConfigPath // applicationhost.config path for IISExpress + ); + } + else + { + return new ServerManager( + false, // readOnly + Strings.AppHostConfigPath // applicationhost.config path for IIS + ); + } + } + + public IISConfigUtility(ServerType type, string iisExpressConfigPath) + { + _serverType = type; + _iisExpressConfigPath = iisExpressConfigPath; + } + + public static bool BackupAppHostConfig(string fileExtenstion, bool overWriteMode) + { + bool result = true; + string fromfile = Strings.AppHostConfigPath; + string tofile = Strings.AppHostConfigPath + fileExtenstion; + if (File.Exists(fromfile)) + { + try + { + TestUtility.FileCopy(fromfile, tofile, overWrite: overWriteMode); + } + catch + { + result = false; + } + } + return result; + } + + public static void RestoreAppHostConfig(string fileExtenstion, bool overWriteMode) + { + string tofile = Strings.AppHostConfigPath; + string fromfile = Strings.AppHostConfigPath + fileExtenstion; + if (File.Exists(fromfile)) + { + try + { + TestUtility.FileCopy(fromfile, tofile, overWrite:overWriteMode); + } + catch + { + TestUtility.LogInformation("Failed to Restore applicationhost.config"); + throw; + } + } + } + + public static void RestoreAppHostConfig(bool restoreFromMasterBackupFile = true) + { + string masterBackupFileExtension = ".ancmtest.masterbackup"; + string masterBackupFilePath = Strings.AppHostConfigPath + masterBackupFileExtension; + string temporaryBackupFileExtenstion = null; + string temporaryBackupFilePath = null; + string tofile = Strings.AppHostConfigPath; + + string backupFileExentsionForDebug = ".ancmtest.debug"; + string backupFilePathForDebug = Strings.AppHostConfigPath + backupFileExentsionForDebug; + TestUtility.DeleteFile(backupFilePathForDebug); + + // Create a master backup file + if (restoreFromMasterBackupFile) + { + // Create a master backup file if it does not exist + if (!File.Exists(masterBackupFilePath)) + { + if (!File.Exists(tofile)) + { + throw new ApplicationException("Can't find " + tofile); + } + BackupAppHostConfig(masterBackupFileExtension, overWriteMode: false); + } + + if (!File.Exists(masterBackupFilePath)) + { + throw new ApplicationException("Not found master backup file " + masterBackupFilePath); + } + } + + // if applicationhost.config does not exist but master backup file is available, create a new applicationhost.config from the master backup file first + if (!File.Exists(tofile)) + { + CopyAppHostConfig(masterBackupFilePath, tofile); + } + + // Create a temporary backup file with the current applicationhost.config to rollback after test is completed. + if (ApppHostTemporaryBackupFileExtention == null) + { + // retry 10 times until it really creates the temporary backup file + for (int i = 0; i < 10; i++) + { + temporaryBackupFileExtenstion = "." + TestUtility.RandomString(5); + string tempFile = Strings.AppHostConfigPath + temporaryBackupFileExtenstion; + if (File.Exists(tempFile)) + { + // file already exists, try with a different file name + continue; + } + + bool backupSuccess = BackupAppHostConfig(temporaryBackupFileExtenstion, overWriteMode: false); + if (backupSuccess && File.Exists(tempFile)) + { + if (File.Exists(tempFile)) + { + ApppHostTemporaryBackupFileExtention = temporaryBackupFileExtenstion; + break; + } + } + } + + if (ApppHostTemporaryBackupFileExtention == null) + { + throw new ApplicationException("Can't make a temporary backup file"); + } + } + + if (restoreFromMasterBackupFile) + { + // restoring applicationhost.config from the master backup file + CopyAppHostConfig(masterBackupFilePath, tofile); + } + else + { + // Create a temporary backup file to preserve the last state for debugging purpose before rolling back from the temporary backup file + try + { + BackupAppHostConfig(backupFileExentsionForDebug, overWriteMode: true); + } + catch + { + TestUtility.LogInformation("Failed to create a backup file for debugging"); + } + + // restoring applicationhost.config from the temporary backup file + temporaryBackupFilePath = Strings.AppHostConfigPath + ApppHostTemporaryBackupFileExtention; + CopyAppHostConfig(temporaryBackupFilePath, tofile); + + // delete the temporary backup file because it is not used anymore + try + { + TestUtility.DeleteFile(temporaryBackupFilePath); + } + catch + { + TestUtility.LogInformation("Failed to cleanup temporary backup file : " + temporaryBackupFilePath); + } + } + } + + private static void CopyAppHostConfig(string fromfile, string tofile) + { + if (!File.Exists(fromfile) && !File.Exists(tofile)) + { + // IIS is not installed, don't do anything here + return; + } + + if (!File.Exists(fromfile)) + { + throw new ApplicationException("Failed to backup " + tofile); + } + + // try restoring applicationhost.config again after the ininial clean up for better reliability + try + { + TestUtility.FileCopy(fromfile, tofile, true, true); + } + catch + { + // ignore + } + + // try again + if (!File.Exists(tofile) || File.ReadAllBytes(fromfile).Length != File.ReadAllBytes(tofile).Length) + { + // try again + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + TestUtility.FileCopy(fromfile, tofile, true, true); + } + + // verify restoration is done successfully + if (File.ReadAllBytes(fromfile).Length != File.ReadAllBytes(tofile).Length) + { + throw new ApplicationException("Failed to restore applicationhost.config from " + fromfile + " to " + tofile); + } + } + + public void SetAppPoolSetting(string appPoolName, string attribute, object value) + { + TestUtility.LogInformation("Setting Apppool : " + appPoolName + "::" + attribute.ToString() + " <== " + value.ToString()); + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + ConfigurationSection applicationPoolsSection = config.GetSection("system.applicationHost/applicationPools"); + ConfigurationElementCollection applicationPoolsCollection = applicationPoolsSection.GetCollection(); + ConfigurationElement addElement = FindElement(applicationPoolsCollection, "add", "name", appPoolName); + if (addElement == null) throw new InvalidOperationException("Element not found!"); + + switch (attribute) + { + case "privateMemory": + case "memory": + ConfigurationElement recyclingElement = addElement.GetChildElement("recycling"); + ConfigurationElement periodicRestartElement = recyclingElement.GetChildElement("periodicRestart"); + periodicRestartElement[attribute] = value; + break; + case "rapidFailProtectionMaxCrashes": + ConfigurationElement failureElement = addElement.GetChildElement("failure"); + failureElement["rapidFailProtectionMaxCrashes"] = value; + break; + default: + addElement[attribute] = value; + break; + } + serverManager.CommitChanges(); + } + } + + public void RecycleAppPool(string appPoolName) + { + using (ServerManager serverManager = GetServerManager()) + { + serverManager.ApplicationPools[appPoolName].Recycle(); + } + } + + public void StopAppPool(string appPoolName) + { + using (ServerManager serverManager = GetServerManager()) + { + serverManager.ApplicationPools[appPoolName].Stop(); + } + } + + public void StartAppPool(string appPoolName) + { + using (ServerManager serverManager = GetServerManager()) + { + serverManager.ApplicationPools[appPoolName].Start(); + } + } + + public void CreateSite(string siteName, string hostname, string physicalPath, int siteId, int tcpPort, string appPoolName = "DefaultAppPool") + { + TestUtility.LogInformation("Creating web site : " + siteName); + + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites"); + ConfigurationElementCollection sitesCollection = sitesSection.GetCollection(); + ConfigurationElement siteElement = FindElement(sitesCollection, "site", "name", siteName); + if (siteElement != null) + { + sitesCollection.Remove(siteElement); + } + siteElement = sitesCollection.CreateElement("site"); + siteElement["id"] = siteId; + siteElement["name"] = siteName; + ConfigurationElementCollection bindingsCollection = siteElement.GetCollection("bindings"); + + ConfigurationElement bindingElement = bindingsCollection.CreateElement("binding"); + bindingElement["protocol"] = @"http"; + bindingElement["bindingInformation"] = "*:" + tcpPort + ":" + hostname; + bindingsCollection.Add(bindingElement); + + ConfigurationElementCollection siteCollection = siteElement.GetCollection(); + ConfigurationElement applicationElement = siteCollection.CreateElement("application"); + applicationElement["path"] = @"/"; + applicationElement["applicationPool"] = appPoolName; + + ConfigurationElementCollection applicationCollection = applicationElement.GetCollection(); + ConfigurationElement virtualDirectoryElement = applicationCollection.CreateElement("virtualDirectory"); + virtualDirectoryElement["path"] = @"/"; + virtualDirectoryElement["physicalPath"] = physicalPath; + applicationCollection.Add(virtualDirectoryElement); + siteCollection.Add(applicationElement); + sitesCollection.Add(siteElement); + serverManager.CommitChanges(); + } + } + + public void CreateApp(string siteName, string appName, string physicalPath, string appPoolName = "DefaultAppPool") + { + TestUtility.LogInformation("Creating web app : " + siteName + "/" + appName); + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + + ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites"); + + ConfigurationElementCollection sitesCollection = sitesSection.GetCollection(); + + ConfigurationElement siteElement = FindElement(sitesCollection, "site", "name", siteName); + if (siteElement == null) throw new InvalidOperationException("Element not found!"); + + ConfigurationElementCollection siteCollection = siteElement.GetCollection(); + + ConfigurationElement applicationElement = siteCollection.CreateElement("application"); + string appPath = @"/" + appName; + appPath = appPath.Replace("//", "/"); + applicationElement["path"] = appPath; + applicationElement["applicationPool"] = appPoolName; + + ConfigurationElementCollection applicationCollection = applicationElement.GetCollection(); + + ConfigurationElement virtualDirectoryElement = applicationCollection.CreateElement("virtualDirectory"); + virtualDirectoryElement["path"] = @"/"; + virtualDirectoryElement["physicalPath"] = physicalPath; + applicationCollection.Add(virtualDirectoryElement); + siteCollection.Add(applicationElement); + serverManager.CommitChanges(); + } + } + + public void EnableIISAuthentication(string siteName, bool windows, bool basic, bool anonymous) + { + TestUtility.LogInformation("Enable Windows authentication : " + siteName); + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + + ConfigurationSection anonymousAuthenticationSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication", siteName); + anonymousAuthenticationSection["enabled"] = anonymous; + ConfigurationSection basicAuthenticationSection = config.GetSection("system.webServer/security/authentication/basicAuthentication", siteName); + basicAuthenticationSection["enabled"] = basic; + ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); + windowsAuthenticationSection["enabled"] = windows; + serverManager.CommitChanges(); + } + } + + public void EnableOneToOneClientCertificateMapping(string siteName, string userName, string password, string publicKey) + { + TestUtility.LogInformation("Enable one-to-one client certificate mapping authentication : " + siteName); + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + + ConfigurationSection iisClientCertificateMappingAuthenticationSection = config.GetSection("system.webServer/security/authentication/iisClientCertificateMappingAuthentication", siteName); + + // enable iisClientCertificateMappingAuthentication + ConfigurationElementCollection oneToOneMappingsCollection = iisClientCertificateMappingAuthenticationSection.GetCollection("oneToOneMappings"); + iisClientCertificateMappingAuthenticationSection["enabled"] = true; + + // add a new oneToOne mapping collection item + ConfigurationElement addElement = oneToOneMappingsCollection.CreateElement("add"); + addElement["userName"] = userName; + if (password != null) + { + addElement["password"] = password; + } + addElement["certificate"] = publicKey; + oneToOneMappingsCollection.Add(addElement); + + // set sslFlags with SslNegotiateCert + ConfigurationSection accessSection = config.GetSection("system.webServer/security/access", siteName); + accessSection["sslFlags"] = "Ssl, SslNegotiateCert, SslRequireCert"; + + // disable other authentication to avoid any noise affected by other authentications + ConfigurationSection anonymousAuthenticationSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication", siteName); + anonymousAuthenticationSection["enabled"] = false; + ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); + windowsAuthenticationSection["enabled"] = false; + serverManager.CommitChanges(); + } + } + + public void SetCompression(string siteName, bool enabled) + { + TestUtility.LogInformation("Enable Compression : " + siteName); + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + + ConfigurationSection urlCompressionSection = config.GetSection("system.webServer/urlCompression", siteName); + urlCompressionSection["doStaticCompression"] = enabled; + urlCompressionSection["doDynamicCompression"] = enabled; + serverManager.CommitChanges(); + } + } + + public void DisableWindowsAuthentication(string siteName) + { + TestUtility.LogInformation("Enable Windows authentication : " + siteName); + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + + ConfigurationSection anonymousAuthenticationSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication", siteName); + anonymousAuthenticationSection["enabled"] = true; + ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); + windowsAuthenticationSection["enabled"] = false; + serverManager.CommitChanges(); + } + } + + public void SetANCMConfig(string siteName, string appName, string attributeName, object attributeValue) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetWebConfiguration(siteName, appName); + ConfigurationSection aspNetCoreSection = config.GetSection("system.webServer/aspNetCore"); + if (attributeName == "environmentVariable") + { + string name = ((string[])attributeValue)[0]; + string value = ((string[])attributeValue)[1]; + ConfigurationElementCollection environmentVariablesCollection = aspNetCoreSection.GetCollection("environmentVariables"); + ConfigurationElement environmentVariableElement = environmentVariablesCollection.CreateElement("environmentVariable"); + environmentVariableElement["name"] = name; + environmentVariableElement["value"] = value; + var element = FindElement(environmentVariablesCollection, "add", "name", value); + if (element != null) + { + throw new ApplicationException("duplicated collection item"); + } + environmentVariablesCollection.Add(environmentVariableElement); + } + else + { + aspNetCoreSection[attributeName] = attributeValue; + } + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + throw ex; + } + } + + public void ConfigureCustomLogging(string siteName, string appName, int statusCode, int subStatusCode, string path) + { + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetWebConfiguration(siteName, appName); + ConfigurationSection httpErrorsSection = config.GetSection("system.webServer/httpErrors"); + httpErrorsSection["errorMode"] = @"Custom"; + + ConfigurationElementCollection httpErrorsCollection = httpErrorsSection.GetCollection(); + ConfigurationElement errorElement = FindElement(httpErrorsCollection, "error", "statusCode", statusCode.ToString(), "subStatusCode", subStatusCode.ToString()); + if (errorElement != null) + { + httpErrorsCollection.Remove(errorElement); + } + + ConfigurationElement errorElement2 = httpErrorsCollection.CreateElement("error"); + errorElement2["statusCode"] = statusCode; + errorElement2["subStatusCode"] = subStatusCode; + errorElement2["path"] = path; + httpErrorsCollection.Add(errorElement2); + serverManager.CommitChanges(); + } + Thread.Sleep(500); + } + + public bool IsAncmInstalled(ServerType servertype) + { + bool result = true; + if (servertype == ServerType.IIS) + { + if (!File.Exists(InitializeTestMachine.FullIisAspnetcoreSchema_path)) + { + result = false; + } + } + else + { + if (!File.Exists(InitializeTestMachine.IisExpressAspnetcoreSchema_path)) + { + result = false; + } + } + return result; + } + + public static string GetServiceStatus(string serviceName) + { + ServiceController sc = new ServiceController(serviceName); + + switch (sc.Status) + { + case ServiceControllerStatus.Running: + return "Running"; + case ServiceControllerStatus.Stopped: + return "Stopped"; + case ServiceControllerStatus.Paused: + return "Paused"; + case ServiceControllerStatus.StopPending: + return "Stopping"; + case ServiceControllerStatus.StartPending: + return "Starting"; + default: + return "Status Changing"; + } + } + + public bool IsUrlRewriteInstalledForIIS() + { + bool result = true; + var toRewrite64 = Path.Combine(Strings.IIS64BitPath, "rewrite.dll"); + var toRewrite32 = Path.Combine(Strings.IIS32BitPath, "rewrite.dll"); + + if (TestUtility.IsOSAmd64) + { + if (!File.Exists(toRewrite64)) + { + result = false; + } + } + + if (!File.Exists(toRewrite32)) + { + result = false; + } + + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + ConfigurationSection globalModulesSection = config.GetSection("system.webServer/globalModules"); + ConfigurationElementCollection globalModulesCollection = globalModulesSection.GetCollection(); + if (FindElement(globalModulesCollection, "add", "name", "RewriteModule") == null) + { + result = false; + } + + ConfigurationSection modulesSection = config.GetSection("system.webServer/modules"); + ConfigurationElementCollection modulesCollection = modulesSection.GetCollection(); + if (FindElement(modulesCollection, "add", "name", "RewriteModule") == null) + { + result = false; + } + } + return result; + } + + public bool RemoveModule(string moduleName) + { + bool result = true; + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + ConfigurationSection globalModulesSection = config.GetSection("system.webServer/globalModules"); + ConfigurationElementCollection globalModulesCollection = globalModulesSection.GetCollection(); + var globalModule = FindElement(globalModulesCollection, "add", "name", moduleName); + if (globalModule != null) + { + globalModulesCollection.Remove(globalModule); + + } + ConfigurationSection modulesSection = config.GetSection("system.webServer/modules"); + ConfigurationElementCollection modulesCollection = modulesSection.GetCollection(); + var module = FindElement(modulesCollection, "add", "name", moduleName); + if (module != null) + { + modulesCollection.Remove(module); + } + + serverManager.CommitChanges(); + } + return result; + } + + public bool AddModule(string moduleName, string image, string preCondition) + { + RemoveModule(moduleName); + + bool result = true; + using (ServerManager serverManager = GetServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + ConfigurationSection globalModulesSection = config.GetSection("system.webServer/globalModules"); + ConfigurationElementCollection globalModulesCollection = globalModulesSection.GetCollection(); + + ConfigurationElement globalModule = globalModulesCollection.CreateElement("add"); + globalModule["name"] = moduleName; + globalModule["image"] = image; + if (preCondition != null) + { + globalModule["preCondition"] = preCondition; + } + globalModulesCollection.Add(globalModule); + + ConfigurationSection modulesSection = config.GetSection("system.webServer/modules"); + ConfigurationElementCollection modulesCollection = modulesSection.GetCollection(); + ConfigurationElement module = modulesCollection.CreateElement("add"); + module["name"] = moduleName; + modulesCollection.Add(module); + + serverManager.CommitChanges(); + } + return result; + } + + private static ConfigurationElement FindElement(ConfigurationElementCollection collection, string elementTagName, params string[] keyValues) + { + foreach (ConfigurationElement element in collection) + { + if (String.Equals(element.ElementTagName, elementTagName, StringComparison.OrdinalIgnoreCase)) + { + bool matches = true; + + for (int i = 0; i < keyValues.Length; i += 2) + { + object o = element.GetAttributeValue(keyValues[i]); + string value = null; + if (o != null) + { + value = o.ToString(); + } + + if (!String.Equals(value, keyValues[i + 1], StringComparison.OrdinalIgnoreCase)) + { + matches = false; + break; + } + } + if (matches) + { + return element; + } + } + } + return null; + } + + public void CreateAppPool(string poolName, bool alwaysRunning = false) + { + try + { + TestUtility.LogTrace(String.Format("#################### Adding App Pool {0} with startMode = {1} ####################", poolName, alwaysRunning ? "AlwaysRunning" : "OnDemand")); + using (ServerManager serverManager = GetServerManager()) + { + if (serverManager.ApplicationPools[poolName] != null) + { + TestUtility.LogInformation("Removing existing apppool"); + serverManager.ApplicationPools.Remove(serverManager.ApplicationPools[poolName]); + } + serverManager.ApplicationPools.Add(poolName); + ApplicationPool apppool = serverManager.ApplicationPools[poolName]; + apppool.ManagedPipelineMode = ManagedPipelineMode.Integrated; + if (alwaysRunning) + { + apppool.SetAttributeValue("startMode", "AlwaysRunning"); + } + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Create app pool {0} failed. Reason: {1} ####################", poolName, ex.Message)); + } + } + + public void SetIdleTimeoutForAppPool(string appPoolName, int idleTimeoutMinutes) + { + TestUtility.LogTrace(String.Format("#################### Setting idleTimeout to {0} minutes for AppPool {1} ####################", idleTimeoutMinutes, appPoolName)); + try + { + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + appPools[appPoolName].ProcessModel.IdleTimeout = TimeSpan.FromMinutes(idleTimeoutMinutes); + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Setting idleTimeout to {0} minutes for AppPool {1} failed. Reason: {2} ####################", idleTimeoutMinutes, appPoolName, ex.Message)); + } + } + + public void SetMaxProcessesForAppPool(string appPoolName, int maxProcesses) + { + TestUtility.LogTrace(String.Format("#################### Setting maxProcesses to {0} for AppPool {1} ####################", maxProcesses, appPoolName)); + try + { + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + appPools[appPoolName].ProcessModel.MaxProcesses = maxProcesses; + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Setting maxProcesses to {0} for AppPool {1} failed. Reason: {2} ####################", maxProcesses, appPoolName, ex.Message)); + } + } + + public void SetIdentityForAppPool(string appPoolName, string userName, string password) + { + TestUtility.LogTrace(String.Format("#################### Setting userName {0} and password {1} for AppPool {2} ####################", userName, password, appPoolName)); + try + { + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + appPools[appPoolName].ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser; + appPools[appPoolName].ProcessModel.UserName = userName; + appPools[appPoolName].ProcessModel.Password = password; + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Setting userName {0} and password {1} for AppPool {2} failed. Reason: {2} ####################", userName, password, appPoolName, ex.Message)); + } + } + + public void SetStartModeAlwaysRunningForAppPool(string appPoolName, bool alwaysRunning) + { + string startMode = alwaysRunning ? "AlwaysRunning" : "OnDemand"; + + TestUtility.LogTrace(String.Format("#################### Setting startMode to {0} for AppPool {1} ####################", startMode, appPoolName)); + + try + { + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + appPools[appPoolName]["startMode"] = startMode; + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Setting startMode to {0} for AppPool {1} failed. Reason: {2} ####################", startMode, appPoolName, ex.Message)); + } + } + + public void StartAppPoolEx(string appPoolName) + { + StartOrStopAppPool(appPoolName, true); + } + + public void StopAppPoolEx(string appPoolName) + { + StartOrStopAppPool(appPoolName, false); + } + + private void StartOrStopAppPool(string appPoolName, bool start) + { + string action = start ? "Starting" : "Stopping"; + TestUtility.LogTrace(String.Format("#################### {0} app pool {1} ####################", action, appPoolName)); + + try + { + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + if (start) + { + appPools[appPoolName].Start(); + } + else + { + appPools[appPoolName].Stop(); + } + } + } + catch (Exception ex) + { + string message = ex.Message; + TestUtility.LogInformation(String.Format("#################### {0} app pool {1} failed. Reason: {2} ####################", action, appPoolName, ex.Message)); + } + } + + public void VerifyAppPoolState(string appPoolName, Microsoft.Web.Administration.ObjectState state) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + if (appPools[appPoolName].State == state) + TestUtility.LogInformation(String.Format("Verified state for app pool {0} is {1}.", appPoolName, state.ToString())); + else + TestUtility.LogInformation(String.Format("Unexpected state {0} for app pool {1}.", state, appPoolName.ToString())); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Failed to verify state for app pool {0}. Reason: {1} ####################", appPoolName, ex.Message)); + } + } + + public void DeleteAppPool(string poolName) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Deleting App Pool {0} ####################", poolName)); + + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + appPools.Remove(appPools[poolName]); + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Delete app pool {0} failed. Reason: {1} ####################", poolName, ex.Message)); + } + } + + public void DeleteAllAppPools(bool commitDelay = false) + { + TestUtility.LogTrace(String.Format("#################### Deleting all app pools ####################")); + + using (ServerManager serverManager = GetServerManager()) + { + ApplicationPoolCollection appPools = serverManager.ApplicationPools; + while (appPools.Count > 0) + { + appPools.RemoveAt(0); + } + serverManager.CommitChanges(); + } + } + + public void CreateSiteEx(int siteId, string siteName, string poolName, string dirRoot, string Ip, int Port, string host) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + string bindingInfo = ""; + if (Ip == null) + Ip = "*"; + bindingInfo += Ip; + bindingInfo += ":"; + bindingInfo += Port; + bindingInfo += ":"; + if (host != null) + bindingInfo += host; + + TestUtility.LogTrace(String.Format("#################### Adding Site {0} with App Pool {1} with BindingInfo {2} ####################", siteName, poolName, bindingInfo)); + + SiteCollection sites = serverManager.Sites; + Site site = sites.CreateElement(); + site.Id = siteId; + site.SetAttributeValue("name", siteName); + sites.Add(site); + + Application app = site.Applications.CreateElement(); + app.SetAttributeValue("path", "/"); + app.SetAttributeValue("applicationPool", poolName); + site.Applications.Add(app); + + VirtualDirectory vdir = app.VirtualDirectories.CreateElement(); + vdir.SetAttributeValue("path", "/"); + vdir.SetAttributeValue("physicalPath", dirRoot); + + app.VirtualDirectories.Add(vdir); + + Binding b = site.Bindings.CreateElement(); + b.SetAttributeValue("protocol", "http"); + b.SetAttributeValue("bindingInformation", bindingInfo); + + site.Bindings.Add(b); + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Create site {0} failed. Reason: {1} ####################", siteName, ex.Message)); + } + } + + public void StartSite(string siteName) + { + StartOrStopSite(siteName, true); + } + + public void StopSite(string siteName) + { + StartOrStopSite(siteName, false); + } + + private void StartOrStopSite(string siteName, bool start) + { + string action = start ? "Starting" : "Stopping"; + TestUtility.LogTrace(String.Format("#################### {0} site {1} ####################", action, siteName)); + + try + { + using (ServerManager serverManager = GetServerManager()) + { + SiteCollection sites = serverManager.Sites; + if (start) + { + sites[siteName].Start(); + sites[siteName].SetAttributeValue("serverAutoStart", true); + } + else + { + sites[siteName].Stop(); + sites[siteName].SetAttributeValue("serverAutoStart", false); + } + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### {0} site {1} failed. Reason: {2} ####################", action, siteName, ex.Message)); + } + } + + public ObjectState GetSiteState(string siteName) + { + using (ServerManager serverManager = GetServerManager()) + { + SiteCollection sites = serverManager.Sites; + if (sites[siteName] != null) + { + return sites[siteName].State; + } + else + { + return ObjectState.Unknown; + } + } + } + + public void AddApplicationToSite(string siteName, string appPath, string physicalPath, string poolName) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Adding Application {0} with App Pool {1} to Site {2} ####################", appPath, poolName, siteName)); + + SiteCollection sites = serverManager.Sites; + Application app = sites[siteName].Applications.CreateElement(); + app.SetAttributeValue("path", appPath); + app.SetAttributeValue("applicationPool", poolName); + sites[siteName].Applications.Add(app); + + VirtualDirectory vdir = app.VirtualDirectories.CreateElement(); + vdir.SetAttributeValue("path", "/"); + vdir.SetAttributeValue("physicalPath", physicalPath); + + app.VirtualDirectories.Add(vdir); + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Add Application {0} with App Pool {1} to Site {2} failed. Reason: {3} ####################", appPath, poolName, siteName, ex.Message)); + } + } + + public void ChangeApplicationPool(string siteName, int appIndex, string poolName) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Changing Application Pool for App {0} of Site {1} to {2} ####################", appIndex, siteName, poolName)); + + serverManager.Sites[siteName].Applications[appIndex].SetAttributeValue("applicationPool", poolName); + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Changing Application Pool for App {0} of Site {1} to {2} failed. Reason: {3} ####################", appIndex, siteName, poolName, ex.Message)); + } + } + + public void ChangeApplicationPath(string siteName, int appIndex, string path) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Changing Path for App {0} of Site {1} to {2} ####################", appIndex, siteName, path)); + + serverManager.Sites[siteName].Applications[appIndex].SetAttributeValue("path", path); + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Changing Path for App {0} of Site {1} to {2} failed. Reason: {3} ####################", appIndex, siteName, path, ex.Message)); + } + } + + public void RemoveApplication(string siteName, int appIndex) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Deleting App {0} from Site {1} ####################", appIndex, siteName)); + + serverManager.Sites[siteName].Applications.RemoveAt(appIndex); + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Deleting App {0} from Site {1} failed. Reason: {2} ####################", appIndex, siteName, ex.Message)); + } + } + + public string CreateSelfSignedCertificateWithMakeCert(string subjectName, string issuerName = null, string extendedKeyUsage = null) + { + string makecertExeFilePath = TestUtility.GetMakeCertPath(); + + string parameter; + string targetSSLStore = string.Empty; + if (issuerName == null) + { + // if issuer Name is null, you are going to create a root level certificate + parameter = "-r -pe -n \"CN = " + subjectName + "\" -b 12/22/2013 -e 12/23/2020 -ss root -sr localmachine -len 2048 -a sha256"; + targetSSLStore = @"Cert:\LocalMachine\Root"; // => -ss root -sr localmachine + } + else + { + // if issuer Name is *not* null, you are going to create a child evel certificate from the given issuer certificate + switch (extendedKeyUsage) + { + // for web server certificate + case "1.3.6.1.5.5.7.3.1": + parameter = "-pe -n \"CN=" + subjectName + "\" -b 12/22/2013 -e 12/23/" + (System.DateTime.Now.Year + 10).ToString() + " -eku " + extendedKeyUsage + " -is root -ir localmachine -in \"" + issuerName + "\" -len 2048 -ss my -sr localmachine -a sha256"; + targetSSLStore = @"Cert:\LocalMachine\My"; // => -ss my -sr localmachine + break; + + // for client authentication + case "1.3.6.1.5.5.7.3.2": + parameter = "-pe -n \"CN=" + subjectName + "\" -eku " + extendedKeyUsage + " -is root -ir localmachine -in \"" + issuerName + "\" -ss my -sr currentuser -len 2048 -a sha256"; + targetSSLStore = @"Cert:\CurrentUser\My"; // => -ss my -sr currentuser + break; + + default: + throw new NotImplementedException(extendedKeyUsage); + } + } + try + { + TestUtility.RunCommand(makecertExeFilePath, parameter); + } + catch (Exception ex) + { + TestUtility.LogInformation("Failed to run makecert.exe. Makecert.exe is installed with Visual Studio or SDK. Please make sure setting PATH environment to include the directory path of the makecert.exe file"); + throw ex; + } + + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "certificate.ps1") + + " -Command Get-CertificateThumbPrint" + + " -Subject " + subjectName + + " -TargetSSLStore \"" + targetSSLStore + "\""; + + if (issuerName != null) + { + powershellScript += " -IssuerName " + issuerName; + } + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output.Length != 40) + { + throw new ApplicationException("Failed to create a certificate, output: " + output); + } + return output; + } + + public string CreateSelfSignedCertificate(string subjectName) + { + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "certificate.ps1") + + " -Command Create-SelfSignedCertificate" + + " -Subject " + subjectName; + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output.Length != 40) + { + throw new ApplicationException("Failed to create a certificate, output: " + output); + } + return output; + } + + public string ExportCertificateTo(string thumbPrint, string sslStoreFrom = @"Cert:\LocalMachine\My", string sslStoreTo = @"Cert:\LocalMachine\Root", string pfxPassword = null) + { + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "certificate.ps1") + + " -Command Export-CertificateTo" + + " -TargetThumbPrint " + thumbPrint + + " -TargetSSLStore " + sslStoreFrom + + " -ExportToSSLStore " + sslStoreTo; + + if (pfxPassword != null) + { + powershellScript += " -PfxPassword " + pfxPassword; + } + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output != string.Empty) + { + throw new ApplicationException("Failed to export a certificate to RootCA, output: " + output); + } + return output; + } + + public string GetCertificatePublicKey(string thumbPrint, string sslStore = @"Cert:\LocalMachine\My") + { + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "certificate.ps1") + + " -Command Get-CertificatePublicKey" + + " -TargetThumbPrint " + thumbPrint + + " -TargetSSLStore " + sslStore; + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output.Length < 500) + { + throw new ApplicationException("Failed to get certificate public key, output: " + output); + } + return output; + } + + public string DeleteCertificate(string thumbPrint, string sslStore= @"Cert:\LocalMachine\My") + { + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "certificate.ps1") + + " -Command Delete-Certificate" + + " -TargetThumbPrint " + thumbPrint + + " -TargetSSLStore " + sslStore; + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output != string.Empty) + { + throw new ApplicationException("Failed to delete a certificate (thumbprint: " + thumbPrint + ", output: " + output); + } + return output; + } + + public void SetSSLCertificate(int port, string hexIpAddress, string thumbPrint, string sslStore = @"Cert:\LocalMachine\My") + { + // Remove a certificate mapping if it exists + RemoveSSLCertificate(port, hexIpAddress); + + // Configure certificate mapping with the newly created certificate + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "httpsys.ps1") + + " -Command Add-SslBinding" + + " -IpAddress " + hexIpAddress + + " -Port " + port.ToString() + + " Thumbprint \"" + thumbPrint + "\"" + + " -TargetSSLStore " + sslStore; + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output != string.Empty) + { + throw new ApplicationException("Failed to configure certificate, output: " + output); + } + } + + public void RemoveSSLCertificate(int port, string hexIpAddress, string sslStore = @"Cert:\LocalMachine\My") + { + string toolsPath = Path.Combine(InitializeTestMachine.GetSolutionDirectory(), "tools"); + string powershellScript = Path.Combine(toolsPath, "httpsys.ps1") + + " -Command Get-SslBinding" + + " -IpAddress " + hexIpAddress + + " -Port " + port.ToString(); + + string output = TestUtility.RunPowershellScript(powershellScript); + if (output != string.Empty) + { + // Delete a certificate mapping if it exists + powershellScript = Path.Combine(toolsPath, "httpsys.ps1") + " -Command Delete-SslBinding -IpAddress " + hexIpAddress + " -Port " + port.ToString(); + output = TestUtility.RunPowershellScript(powershellScript); + if (output != string.Empty) + { + throw new ApplicationException("Failed to delete certificate, output: " + output); + } + } + } + + public void AddBindingToSite(string siteName, string ipAddress, int port, string host, string protocol = "http") + { + string bindingInfo = ""; + if (ipAddress == null) + ipAddress = "*"; + bindingInfo += ipAddress; + bindingInfo += ":"; + bindingInfo += port; + bindingInfo += ":"; + if (host != null) + bindingInfo += host; + + TestUtility.LogInformation(String.Format("#################### Adding Binding {0} to Site {1} ####################", bindingInfo, siteName)); + + try + { + using (ServerManager serverManager = GetServerManager()) + { + SiteCollection sites = serverManager.Sites; + Binding b = sites[siteName].Bindings.CreateElement(); + b.SetAttributeValue("protocol", protocol); + b.SetAttributeValue("bindingInformation", bindingInfo); + + sites[siteName].Bindings.Add(b); + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Adding Binding {0} to Site {1} failed. Reason: {2} ####################", bindingInfo, siteName, ex.Message)); + } + } + + public void RemoveBindingFromSite(string siteName, BindingInfo bindingInfo) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Removing Binding {0} from Site {1} ####################", bindingInfo.ToBindingString(), siteName)); + + for (int i = 0; i < serverManager.Sites[siteName].Bindings.Count; i++) + { + if (serverManager.Sites[siteName].Bindings[i].BindingInformation.ToString() == bindingInfo.ToBindingString()) + { + serverManager.Sites[siteName].Bindings.RemoveAt(i); + + serverManager.CommitChanges(); + return; + } + } + + TestUtility.LogInformation(String.Format("#################### Remove binding failed because binding was not found ####################")); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Remove binding failed. Reason: {0} ####################", ex.Message)); + } + } + + public void ModifyBindingForSite(string siteName, BindingInfo bindingInfoOld, BindingInfo bindingInfoNew) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Changing Binding {0} for Site {1} to {2} ####################", bindingInfoOld.ToBindingString(), siteName, bindingInfoNew.ToBindingString())); + + for (int i = 0; i < serverManager.Sites[siteName].Bindings.Count; i++) + { + if (serverManager.Sites[siteName].Bindings[i].BindingInformation.ToString() == bindingInfoOld.ToBindingString()) + { + serverManager.Sites[siteName].Bindings[i].SetAttributeValue("bindingInformation", bindingInfoNew.ToBindingString()); + + serverManager.CommitChanges(); + return; + } + } + + TestUtility.LogInformation(String.Format("#################### Modify binding failed because binding was not found ####################")); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Changing binding failed. Reason: {0} ####################", ex.Message)); + } + } + + public void DeleteSite(string siteName) + { + try + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Deleting Site {0} ####################", siteName)); + + SiteCollection sites = serverManager.Sites; + sites.Remove(sites[siteName]); + + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogInformation(String.Format("#################### Delete site {0} failed. Reason: {1} ####################", siteName, ex.Message)); + } + } + + public void DeleteAllSites(bool commitDelay = false) + { + using (ServerManager serverManager = GetServerManager()) + { + TestUtility.LogTrace(String.Format("#################### Deleting all sites ####################")); + + SiteCollection sites = serverManager.Sites; + while (sites.Count > 0) + { + sites.RemoveAt(0); + } + serverManager.CommitChanges(); + } + } + + public void SetDynamicSiteRegistrationThreshold(int threshold) + { + try + { + TestUtility.LogTrace(String.Format("#################### Changing dynamicRegistrationThreshold to {0} ####################", threshold)); + + using (ServerManager serverManager = new ServerManager()) + { + Configuration config = serverManager.GetApplicationHostConfiguration(); + ConfigurationSection webLimitsSection = config.GetSection("system.applicationHost/webLimits"); + webLimitsSection["dynamicRegistrationThreshold"] = threshold; + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + TestUtility.LogTrace(String.Format("#################### Changing dynamicRegistrationThreshold failed. Reason: {0} ####################", ex.Message)); + } + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs new file mode 100644 index 0000000000..fa8f3a11c6 --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -0,0 +1,513 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using Microsoft.Extensions.PlatformAbstractions; +using System.Security.Principal; +using System.Security.AccessControl; + +namespace AspNetCoreModule.Test.Framework +{ + public static class TestFlags + { + public const string SkipTest = "SkipTest"; + public const string UsePrivateANCM = "UsePrivateANCM"; + public const string UseIISExpress = "UseIISExpress"; + public const string UseFullIIS = "UseFullIIS"; + public const string RunAsAdministrator = "RunAsAdministrator"; + public const string MakeCertExeAvailable = "MakeCertExeAvailable"; + public const string WebSocketModuleAvailable = "WebSocketModuleAvailable"; + public const string UrlRewriteModuleAvailable = "UrlRewriteModuleAvailable"; + public const string X86Platform = "X86Platform"; + public const string Wow64BitMode = "Wow64BitMode"; + public const string RequireRunAsAdministrator = "RequireRunAsAdministrator"; + public const string Default = "Default"; + + public static bool Enabled(string flagValue) + { + return InitializeTestMachine.GlobalTestFlags.IndexOf(flagValue, StringComparison.OrdinalIgnoreCase) > -1; + } + } + + public class InitializeTestMachine : IDisposable + { + public const string ANCMTestFlagsEnvironmentVariable = "%ANCMTestFlags%"; + + public static int SiteId = 40000; + public const string PrivateFileName = "aspnetcore_private.dll"; + public static string FullIisAspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", PrivateFileName); + public static string FullIisAspnetcore_path_original = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore.dll"); + public static string FullIisAspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv", PrivateFileName); + public static string IisExpressAspnetcore_path; + public static string IisExpressAspnetcore_X86_path; + + public static string IisExpressAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); + public static string IisExpressAspnetcoreSchema_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); + public static string FullIisAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "config", "schema", "aspnetcore_schema.xml"); + public static int _referenceCount = 0; + private static bool _InitializeTestMachineCompleted = false; + private string _setupScriptPath = null; + + private static bool? _makeCertExeAvailable = null; + public static bool MakeCertExeAvailable + { + get + { + if (_makeCertExeAvailable == null) + { + _makeCertExeAvailable = false; + try + { + string makecertExeFilePath = TestUtility.GetMakeCertPath(); + TestUtility.RunCommand(makecertExeFilePath, null, true, true); + TestUtility.LogInformation("Verified makecert.exe is available : " + makecertExeFilePath); + _makeCertExeAvailable = true; + } + catch + { + // ignore exception + } + } + return (_makeCertExeAvailable == true); + } + } + + public static string TestRootDirectory + { + get + { + return Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "_ANCMTest"); + } + } + + private static string _globalTestFlags = null; + public static string GlobalTestFlags + { + get + { + if (_globalTestFlags == null) + { + WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); + bool isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); + + // check if this test process is started with the Run As Administrator start option + _globalTestFlags = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); + + // + // Check if ANCMTestFlags environment is not defined and the test program was started + // without using the Run As Administrator start option. + // In that case, we have to use the default TestFlags of UseIISExpress and UsePrivateANCM + // + if (!isElevated) + { + if (_globalTestFlags.ToLower().Contains("%" + ANCMTestFlagsEnvironmentVariable.ToLower() + "%")) + { + _globalTestFlags = TestFlags.UsePrivateANCM + ";" + TestFlags.UseIISExpress; + } + } + + // + // convert in lower case + // + _globalTestFlags = _globalTestFlags.ToLower(); + + // + // error handling: UseIISExpress and UseFullIIS can be used together. + // + if (_globalTestFlags.Contains(TestFlags.UseIISExpress.ToLower()) && _globalTestFlags.Contains(TestFlags.UseFullIIS.ToLower())) + { + _globalTestFlags = _globalTestFlags.Replace(TestFlags.UseFullIIS.ToLower(), ""); + } + + // + // adjust the default test context in run time to figure out wrong test context values + // + if (isElevated) + { + // add RunAsAdministrator + if (!_globalTestFlags.Contains(TestFlags.RunAsAdministrator.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.RunAsAdministrator); + _globalTestFlags += ";" + TestFlags.RunAsAdministrator; + } + } + else + { + // add UseIISExpress + if (!_globalTestFlags.Contains(TestFlags.UseIISExpress.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.UseIISExpress); + _globalTestFlags += ";" + TestFlags.UseIISExpress; + } + + // remove UseFullIIS + if (_globalTestFlags.Contains(TestFlags.UseFullIIS.ToLower())) + { + _globalTestFlags = _globalTestFlags.Replace(TestFlags.UseFullIIS.ToLower(), ""); + } + + // remove RunAsAdmistrator + if (_globalTestFlags.Contains(TestFlags.RunAsAdministrator.ToLower())) + { + _globalTestFlags = _globalTestFlags.Replace(TestFlags.RunAsAdministrator.ToLower(), ""); + } + } + + if (MakeCertExeAvailable) + { + // Add MakeCertExeAvailable + if (!_globalTestFlags.Contains(TestFlags.MakeCertExeAvailable.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.MakeCertExeAvailable); + _globalTestFlags += ";" + TestFlags.MakeCertExeAvailable; + } + } + + if (!Environment.Is64BitOperatingSystem) + { + // Add X86Platform + if (!_globalTestFlags.Contains(TestFlags.X86Platform.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.X86Platform); + _globalTestFlags += ";" + TestFlags.X86Platform; + } + } + + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) + { + // Add Wow64bitMode + if (!_globalTestFlags.Contains(TestFlags.Wow64BitMode.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.Wow64BitMode); + _globalTestFlags += ";" + TestFlags.Wow64BitMode; + } + + // remove X86Platform + if (_globalTestFlags.Contains(TestFlags.X86Platform.ToLower())) + { + _globalTestFlags = _globalTestFlags.Replace(TestFlags.X86Platform.ToLower(), ""); + } + } + + if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) + { + // Add WebSocketModuleAvailable + if (!_globalTestFlags.Contains(TestFlags.WebSocketModuleAvailable.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.WebSocketModuleAvailable); + _globalTestFlags += ";" + TestFlags.WebSocketModuleAvailable; + } + } + + if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "rewrite.dll"))) + { + // Add UrlRewriteModuleAvailable + if (!_globalTestFlags.Contains(TestFlags.UrlRewriteModuleAvailable.ToLower())) + { + TestUtility.LogInformation("Added test context of " + TestFlags.UrlRewriteModuleAvailable); + _globalTestFlags += ";" + TestFlags.UrlRewriteModuleAvailable; + } + } + + _globalTestFlags = _globalTestFlags.ToLower(); + } + + return _globalTestFlags; + } + } + + public void InitializeIISServer() + { + // Check if IIS server is installed or not + bool isIISInstalled = true; + if (!File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiscore.dll"))) + { + isIISInstalled = false; + } + + if (!File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "config", "applicationhost.config"))) + { + isIISInstalled = false; + } + + if (!isIISInstalled) + { + throw new ApplicationException("IIS server is not installed"); + } + + // Clean up IIS worker process + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + + // Reset applicationhost.config + TestUtility.LogInformation("Restoring applicationhost.config"); + IISConfigUtility.RestoreAppHostConfig(restoreFromMasterBackupFile: true); + TestUtility.StartW3svc(); + + // check w3svc is running after resetting applicationhost.config + if (IISConfigUtility.GetServiceStatus("w3svc") == "Running") + { + TestUtility.LogInformation("W3SVC service is restarted after restoring applicationhost.config"); + } + else + { + throw new ApplicationException("WWW service can't start"); + } + + if (IISConfigUtility.ApppHostTemporaryBackupFileExtention == null) + { + throw new ApplicationException("Failed to backup applicationhost.config"); + } + } + + public InitializeTestMachine() + { + _referenceCount++; + + // This method should be called only one time + if (_referenceCount == 1) + { + TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Start"); + + _InitializeTestMachineCompleted = false; + + TestUtility.LogInformation("InitializeTestMachine::Start"); + if (Environment.ExpandEnvironmentVariables("%ANCMTEST_DEBUG%").ToLower() == "true") + { + System.Diagnostics.Debugger.Launch(); + } + + // + // Clean up IISExpress processes + // + TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); + + // + // Initalize IIS server + // + + if (TestFlags.Enabled(TestFlags.UseFullIIS)) + { + InitializeIISServer(); + } + + string siteRootPath = TestRootDirectory; + if (!Directory.Exists(siteRootPath)) + { + // + // Create a new directory and set the write permission for the SID of AuthenticatedUser + // + Directory.CreateDirectory(siteRootPath); + DirectorySecurity sec = Directory.GetAccessControl(siteRootPath); + SecurityIdentifier authenticatedUser = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); + sec.AddAccessRule(new FileSystemAccessRule(authenticatedUser, FileSystemRights.Modify | FileSystemRights.Synchronize, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow)); + Directory.SetAccessControl(siteRootPath, sec); + } + + foreach (string directory in Directory.GetDirectories(siteRootPath)) + { + bool successDeleteChildDirectory = true; + try + { + TestUtility.DeleteDirectory(directory); + } + catch + { + successDeleteChildDirectory = false; + TestUtility.LogInformation("Failed to delete " + directory); + } + if (successDeleteChildDirectory) + { + try + { + TestUtility.DeleteDirectory(siteRootPath); + } + catch + { + TestUtility.LogInformation("Failed to delete " + siteRootPath); + } + } + } + + // + // Intialize Private ANCM files for Full IIS server or IISExpress + // + if (TestFlags.Enabled(TestFlags.UsePrivateANCM)) + { + PreparePrivateANCMFiles(); + } + + _InitializeTestMachineCompleted = true; + TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() End"); + } + + for (int i=0; i<120; i++) + { + if (_InitializeTestMachineCompleted) + { + break; + } + else + { + TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Waiting..."); + Thread.Sleep(500); + } + } + if (!_InitializeTestMachineCompleted) + { + throw new ApplicationException("InitializeTestMachine failed"); + } + } + + public void Dispose() + { + _referenceCount--; + + if (_referenceCount == 0) + { + TestUtility.LogInformation("InitializeTestMachine::Dispose() Start"); + TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); + RollbackIISApplicationhostConfigFile(); + TestUtility.LogInformation("InitializeTestMachine::Dispose() End"); + } + } + + private void RollbackIISApplicationhostConfigFile() + { + if (IISConfigUtility.ApppHostTemporaryBackupFileExtention != null) + { + try + { + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + } + catch + { + TestUtility.LogInformation("Failed to stop IIS worker processes"); + } + try + { + IISConfigUtility.RestoreAppHostConfig(restoreFromMasterBackupFile: false); + } + catch + { + TestUtility.LogInformation("Failed to rollback applicationhost.config"); + } + try + { + TestUtility.StartW3svc(); + } + catch + { + TestUtility.LogInformation("Failed to start w3svc"); + } + IISConfigUtility.ApppHostTemporaryBackupFileExtention = null; + } + } + + private void PreparePrivateANCMFiles() + { + var solutionRoot = GetSolutionDirectory(); + string outputPath = string.Empty; + _setupScriptPath = Path.Combine(solutionRoot, "tools"); + + // First try with release build + outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Release"); + + // If release build is not available, try with debug build + if (!File.Exists(Path.Combine(outputPath, "Win32", "aspnetcore.dll")) + || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore.dll")) + || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore_schema.xml"))) + { + outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Debug"); + } + + if (!File.Exists(Path.Combine(outputPath, "Win32", "aspnetcore.dll")) + || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore.dll")) + || !File.Exists(Path.Combine(outputPath, "x64", "aspnetcore_schema.xml"))) + { + throw new ApplicationException("aspnetcore.dll is not available; check if there is any build issue!!!"); + } + + // + // NOTE: + // ANCM schema file can't be overwritten here + // If there is any schema change, that should be updated with installing setup or manually copied with the new schema file. + // + + if (TestFlags.Enabled(TestFlags.UseIISExpress)) + { + // + // Initialize 32 bit IisExpressAspnetcore_path + // + IisExpressAspnetcore_path = Path.Combine(outputPath, "x64", "aspnetcore.dll"); + IisExpressAspnetcore_X86_path = Path.Combine(outputPath, "Win32", "aspnetcore.dll"); + } + else // if use Full IIS server + { + bool updateSuccess = false; + for (int i = 0; i < 3; i++) + { + updateSuccess = false; + try + { + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + TestUtility.ResetHelper(ResetHelperMode.StopW3svcStartW3svc); + Thread.Sleep(1000); + + // Copy private file on Inetsrv directory + TestUtility.FileCopy(Path.Combine(outputPath, "x64", "aspnetcore.dll"), FullIisAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + + if (TestUtility.IsOSAmd64) + { + + // Copy 32bit private file on Inetsrv directory + TestUtility.FileCopy(Path.Combine(outputPath, "Win32", "aspnetcore.dll"), FullIisAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + } + updateSuccess = true; + } + catch + { + updateSuccess = false; + } + if (updateSuccess) + { + break; + } + } + if (!updateSuccess) + { + throw new ApplicationException("Failed to update aspnetcore.dll"); + } + + // update applicationhost.config for IIS server with the new private ASPNET Core file name + if (TestFlags.Enabled(TestFlags.UseFullIIS)) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS, null)) + { + iisConfig.AddModule("AspNetCoreModule", FullIisAspnetcore_path, null); + } + } + } + } + + public static string GetSolutionDirectory() + { + var applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath; + var directoryInfo = new DirectoryInfo(applicationBasePath); + do + { + var solutionFile = new FileInfo(Path.Combine(directoryInfo.FullName, "AspNetCoreModule.sln")); + if (solutionFile.Exists) + { + return directoryInfo.FullName; + } + + directoryInfo = directoryInfo.Parent; + } + while (directoryInfo.Parent != null); + + throw new Exception($"Solution root could not be located using application root {applicationBasePath}."); + } + } +} diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs new file mode 100644 index 0000000000..dd2bd67c5b --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -0,0 +1,966 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text; +using System.IO; +using System.Xml; +using System.Management; +using System.Threading; +using System.Diagnostics; +using Microsoft.Extensions.PlatformAbstractions; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Security.Principal; +using System.Security.AccessControl; +using System.Management.Automation.Runspaces; +using System.Net.Http; +using System.Threading.Tasks; +using System.Net; +using System.Collections.ObjectModel; +using System.Management.Automation; + +namespace AspNetCoreModule.Test.Framework +{ + public enum ResetHelperMode + { + CallIISReset, + StopHttpStartW3svc, + StopWasStartW3svc, + StopW3svcStartW3svc, + KillWorkerProcess, + KillVSJitDebugger, + KillIISExpress + } + + public enum ServerType + { + IISExpress = 0, + IIS = 1, + } + + public class TestUtility + { + public static ILogger _logger = null; + + public static ILogger Logger + { + get + { + if (_logger == null) + { + _logger = new LoggerFactory() + .AddConsole() + .CreateLogger("TestUtility"); + } + return _logger; + } + } + + public TestUtility(ILogger logger) + { + _logger = logger; + } + + /// + /// Retries every 1 sec for 60 times by default. + /// + /// + /// + /// + /// + public static async Task RetryRequest( + Func> retryBlock, + ILogger logger, + CancellationToken cancellationToken = default(CancellationToken), + int retryCount = 60) + { + for (var retry = 0; retry < retryCount; retry++) + { + if (cancellationToken.IsCancellationRequested) + { + logger.LogInformation("Failed to connect, retry canceled."); + throw new OperationCanceledException("Failed to connect, retry canceled.", cancellationToken); + } + + try + { + logger.LogWarning("Retry count {retryCount}..", retry + 1); + var response = await retryBlock().ConfigureAwait(false); + + if (response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + // Automatically retry on 503. May be application is still booting. + logger.LogWarning("Retrying a service unavailable error."); + continue; + } + + return response; // Went through successfully + } + catch (Exception exception) + { + if (retry == retryCount - 1) + { + logger.LogError(0, exception, "Failed to connect, retry limit exceeded."); + throw; + } + else + { + if (exception is HttpRequestException +#if NET451 + || exception is System.Net.WebException +#endif + ) + { + logger.LogWarning("Failed to complete the request : {0}.", exception.Message); + await Task.Delay(1 * 1000); //Wait for a while before retry. + } + } + } + } + + logger.LogInformation("Failed to connect, retry limit exceeded."); + throw new OperationCanceledException("Failed to connect, retry limit exceeded."); + } + + public static void RetryOperation( + Action retryBlock, + Action exceptionBlock, + int retryCount = 3, + int retryDelayMilliseconds = 0) + { + for (var retry = 0; retry < retryCount; ++retry) + { + try + { + retryBlock(); + break; + } + catch (Exception exception) + { + exceptionBlock(exception); + } + + Thread.Sleep(retryDelayMilliseconds); + } + } + + public static bool RetryHelper ( + Func verifier, + T arg, + Action exceptionBlock = null, + int retryCount = 3, + int retryDelayMilliseconds = 1000 + ) + { + for (var retry = 0; retry < retryCount; ++retry) + { + try + { + if (verifier(arg)) + return true; + } + catch (Exception exception) + { + exceptionBlock?.Invoke(exception); + } + Thread.Sleep(retryDelayMilliseconds); + } + return false; + } + + public static bool RetryHelper( + Func verifier, + T1 arg1, + T2 arg2, + Action exceptionBlock = null, + int retryCount = 3, + int retryDelayMilliseconds = 1000 + ) + { + for (var retry = 0; retry < retryCount; ++retry) + { + try + { + if (verifier(arg1, arg2)) + return true; + } + catch (Exception exception) + { + exceptionBlock?.Invoke(exception); + } + Thread.Sleep(retryDelayMilliseconds); + } + return false; + } + + public static bool RetryHelper( + Func verifier, + T1 arg1, + T2 arg2, + T3 arg3, + Action exceptionBlock = null, + int retryCount = 3, + int retryDelayMilliseconds = 1000 + ) + { + for (var retry = 0; retry < retryCount; ++retry) + { + try + { + if (verifier(arg1, arg2, arg3)) + return true; + } + catch (Exception exception) + { + exceptionBlock?.Invoke(exception); + } + LogInformation("ANCMTEST::RetryHelper Retrying " + retry); + Thread.Sleep(retryDelayMilliseconds); + } + return false; + } + + public static void GiveWritePermissionTo(string folder, SecurityIdentifier sid) + { + DirectorySecurity fsecurity = Directory.GetAccessControl(folder); + FileSystemAccessRule writerule = new FileSystemAccessRule(sid, FileSystemRights.Write, AccessControlType.Allow); + fsecurity.AddAccessRule(writerule); + Directory.SetAccessControl(folder, fsecurity); + Thread.Sleep(500); + } + + public static bool IsOSAmd64 + { + get + { + if (Environment.ExpandEnvironmentVariables("%PROCESSOR_ARCHITECTURE%") == "AMD64") + { + return true; + } + else + { + return false; + } + } + } + + public static void LogTrace(string format, params object[] parameters) + { + if (format != null) + { + Logger.LogTrace(format, parameters); + } + } + public static void LogError(string format, params object[] parameters) + { + if (format != null) + { + Logger.LogError(format, parameters); + } + } + public static void LogInformation(string format, params object[] parameters) + { + if (format != null) + { + Logger.LogInformation(format, parameters); + } + } + + public static void DeleteFile(string filePath) + { + if (File.Exists(filePath)) + { + RunCommand("cmd.exe", "/c del \"" + filePath + "\""); + } + if (File.Exists(filePath)) + { + throw new ApplicationException("Failed to delete file: " + filePath); + } + } + + public static void FileMove(string from, string to, bool overWrite = true) + { + if (overWrite) + { + DeleteFile(to); + } + if (File.Exists(from)) + { + if (File.Exists(to) && !overWrite) + { + return; + } + File.Move(from, to); + if (!File.Exists(to)) + { + throw new ApplicationException("Failed to rename from : " + from + ", to : " + to); + } + if (File.Exists(from)) + { + throw new ApplicationException("Failed to rename from : " + from + ", to : " + to); + } + } + else + { + throw new ApplicationException("File not found " + from); + } + } + + public static void FileCopy(string from, string to, bool overWrite = true, bool ignoreExceptionWhileDeletingExistingFile = false) + { + if (overWrite) + { + try + { + DeleteFile(to); + } + catch + { + if (!ignoreExceptionWhileDeletingExistingFile) + { + throw; + } + } + } + + if (File.Exists(from)) + { + if (File.Exists(to) && !overWrite) + { + return; + } + RunCommand("cmd.exe", "/c copy /y \"" + from + "\" \"" + to + "\""); + + if (!File.Exists(to)) + { + throw new ApplicationException("Failed to move from : " + from + ", to : " + to); + } + } + else + { + LogError("File not found " + from); + } + } + + public static void DeleteDirectory(string directoryPath) + { + if (Directory.Exists(directoryPath)) + { + RunCommand("cmd.exe", "/c rd \"" + directoryPath + "\" /s /q"); + } + if (Directory.Exists(directoryPath)) + { + throw new ApplicationException("Failed to delete directory: " + directoryPath); + } + } + + public static void CreateDirectory(string directoryPath) + { + if (!Directory.Exists(directoryPath)) + { + RunCommand("cmd.exe", "/c md \"" + directoryPath + "\""); + } + if (!Directory.Exists(directoryPath)) + { + throw new ApplicationException("Failed to create directory: " + directoryPath); + } + } + + public static void DirectoryCopy(string from, string to) + { + if (Directory.Exists(to)) + { + DeleteDirectory(to); + } + + if (!Directory.Exists(to)) + { + CreateDirectory(to); + } + + if (Directory.Exists(from)) + { + RunCommand("cmd.exe", "/c xcopy \"" + from + "\" \"" + to + "\" /s"); + } + else + { + TestUtility.LogInformation("Directory not found " + from); + } + } + + public static string FileReadAllText(string file) + { + string result = null; + if (File.Exists(file)) + { + result = File.ReadAllText(file); + } + return result; + } + + public static void CreateFile(string file, string[] stringArray) + { + DeleteFile(file); + using (StreamWriter sw = new StreamWriter(file)) + { + foreach (string line in stringArray) + { + sw.WriteLine(line); + } + } + + if (!File.Exists(file)) + { + throw new ApplicationException("Failed to create " + file); + } + } + + public static void KillProcess(string processFileName) + { + string query = "Select * from Win32_Process Where Name = \"" + processFileName + "\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + foreach (ManagementObject obj in processList) + { + obj.InvokeMethod("Terminate", null); + } + Thread.Sleep(1000); + + processList = searcher.Get(); + if (processList.Count > 0) + { + TestUtility.LogInformation("Failed to kill process " + processFileName); + } + } + + public static string GetMakeCertPath() + { + string makecertExeFilePath = "makecert.exe"; + var makecertExeFilePaths = new Dictionary(); + makecertExeFilePaths.Add("default", Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits")); + if (IsOSAmd64) + { + makecertExeFilePaths.Add("wow64mode", Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits")); + } + + foreach (var item in makecertExeFilePaths) + { + string[] files = null; + if (!Directory.Exists(item.Value)) + { + continue; + } + files = Directory.GetFiles(item.Value, "makecert.exe", SearchOption.AllDirectories); + + foreach (string makecert in files) + { + if (makecert.Contains("arm")) + { + // arm process version is skipped here + continue; + } + makecertExeFilePath = makecert; + try + { + TestUtility.RunCommand(makecertExeFilePath, null, true, true); + } + catch + { + continue; + } + break; + } + } + return makecertExeFilePath; + } + + public static int GetNumberOfProcess(string processFileName, int expectedNumber = 1, int retry = 0) + { + int result = 0; + string query = "Select * from Win32_Process Where Name = \"" + processFileName + "\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + result = processList.Count; + for (int i = 0; i < retry; i++) + { + if (result == expectedNumber) + { + break; + } + Thread.Sleep(1000); + processList = searcher.Get(); + result = processList.Count; + } + return result; + } + + public static object GetProcessWMIAttributeValue(string processFileName, string attributeName, string owner = null) + { + string query = "Select * from Win32_Process Where Name = \"" + processFileName + "\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + object result = null; + foreach (ManagementObject obj in processList) + { + string[] argList = new string[] { string.Empty, string.Empty }; + bool found = true; + + if (owner != null) + { + found = false; + int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList)); + if (returnVal == 0) + { + if (argList[0].ToUpper() == owner.ToUpper()) + { + found = true; + } + } + } + if (found) + { + result = obj.GetPropertyValue(attributeName); + break; + } + } + return result; + } + + public static string GetHttpUri(string Url, TestWebSite siteContext) + { + string tempUrl = Url.TrimStart(new char[] { '/' }); + return "http://" + siteContext.HostName + ":" + siteContext.TcpPort + "/" + tempUrl; + } + + public static string XmlParser(string xmlFileContent, string elementName, string attributeName, string childkeyValue) + { + string result = string.Empty; + + XmlDocument serviceStateXml = new XmlDocument(); + serviceStateXml.LoadXml(xmlFileContent); + + XmlNodeList elements = serviceStateXml.GetElementsByTagName(elementName); + foreach (XmlNode item in elements) + { + if (childkeyValue == null) + { + if (item.Attributes[attributeName].Value != null) + { + string newValueFound = item.Attributes[attributeName].Value; + if (result != string.Empty) + { + newValueFound += "," + newValueFound; // make the result value in comma seperated format if there are multiple nodes + } + result += newValueFound; + } + } + else + { + //int groupIndex = 0; + foreach (XmlNode groupNode in item.ChildNodes) + { + /*UrlGroup urlGroup = new UrlGroup(); + urlGroup._requestQueue = requestQueue._requestQueueName; + urlGroup._urlGroupId = groupIndex.ToString(); + + foreach (XmlNode urlNode in groupNode) + urlGroup._urls.Add(urlNode.InnerText.ToUpper()); + + requestQueue._urlGroupIds.Add(groupIndex); + requestQueue._urlGroups.Add(urlGroup); + groupIndex++; */ + } + } + } + return result; + } + + public static string RandomString(long size) + { + var random = new Random((int)DateTime.Now.Ticks); + + var builder = new StringBuilder(); + char ch; + for (int i = 0; i < size; i++) + { + ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); + builder.Append(ch); + } + + return builder.ToString(); + } + + public static bool ResetHelper(ResetHelperMode mode) + { + bool result = false; + switch (mode) + { + case ResetHelperMode.CallIISReset: + result = CallIISReset(); + break; + case ResetHelperMode.StopHttpStartW3svc: + StopHttp(); + result = StartW3svc(); + break; + case ResetHelperMode.StopWasStartW3svc: + StopWas(); + result = StartW3svc(); + break; + case ResetHelperMode.StopW3svcStartW3svc: + StopW3svc(); + result = StartW3svc(); + break; + case ResetHelperMode.KillWorkerProcess: + result = KillWorkerProcess(); + break; + case ResetHelperMode.KillVSJitDebugger: + result = KillVSJitDebugger(); + break; + case ResetHelperMode.KillIISExpress: + result = KillIISExpress(); + break; + }; + return result; + } + + public static bool KillIISExpress() + { + bool result = false; + string query = "Select * from Win32_Process Where Name = \"iisexpress.exe\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + + foreach (ManagementObject obj in processList) + { + string[] argList = new string[] { string.Empty, string.Empty }; + bool foundProcess = true; + if (foundProcess) + { + obj.InvokeMethod("Terminate", null); + result = true; + } + } + return result; + } + + public static bool KillVSJitDebugger() + { + bool result = false; + string query = "Select * from Win32_Process Where Name = \"vsjitdebugger.exe\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + + foreach (ManagementObject obj in processList) + { + string[] argList = new string[] { string.Empty, string.Empty }; + bool foundProcess = true; + if (foundProcess) + { + LogError("Jit Debugger found"); + obj.InvokeMethod("Terminate", null); + result = true; + } + } + return result; + } + + public static bool KillWorkerProcess(string owner = null) + { + bool result = false; + + string query = "Select * from Win32_Process Where Name = \"w3wp.exe\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + + foreach (ManagementObject obj in processList) + { + if (owner != null) + { + string[] argList = new string[] { string.Empty, string.Empty }; + int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList)); + if (returnVal == 0) + { + bool foundProcess = true; + + if (String.Compare(argList[0], owner, true) != 0) + { + foundProcess = false; + } + if (foundProcess) + { + obj.InvokeMethod("Terminate", null); + result = true; + } + } + } + else + { + obj.InvokeMethod("Terminate", null); + result = true; + } + } + return result; + } + + public static bool KillIISExpressProcess(string owner = null) + { + bool result = false; + + string query = "Select * from Win32_Process Where Name = \"iisexpress.exe\""; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + ManagementObjectCollection processList = searcher.Get(); + + foreach (ManagementObject obj in processList) + { + if (owner != null) + { + string[] argList = new string[] { string.Empty, string.Empty }; + int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList)); + if (returnVal == 0) + { + bool foundProcess = true; + + if (String.Compare(argList[0], owner, true) != 0) + { + foundProcess = false; + } + if (foundProcess) + { + obj.InvokeMethod("Terminate", null); + result = true; + } + } + } + else + { + obj.InvokeMethod("Terminate", null); + result = true; + } + } + return result; + } + + public static string RunPowershellScript(string scriptText) + { + IPEndPoint a = new IPEndPoint(0, 443); + + // create Powershell runspace + Runspace runspace = null; + try + { + runspace = RunspaceFactory.CreateRunspace(); + } + catch + { + LogInformation("Failed to instantiate powershell Runspace; if this is Win7, install Powershell 4.0 to fix this problem"); + throw new ApplicationException("Failed to instantiate powershell Runspace"); + } + + // open it + runspace.Open(); + + // create a pipeline and feed it the script text + Pipeline pipeline = runspace.CreatePipeline(); + pipeline.Commands.AddScript(scriptText); + + // add an extra command to transform the script output objects into nicely formatted strings + // remove this line to get the actual objects that the script returns. For example, the script + // "Get-Process" returns a collection of System.Diagnostics.Process instances. + pipeline.Commands.Add("Out-String"); + Collection results = null; + try + { + // execute the script + results = pipeline.Invoke(); + } + catch (System.Exception ex) + { + throw new Exception("Failed to run " + scriptText + " " + ex.ToString()); + } + + // close the runspace + runspace.Close(); + + // convert the script result into a single string + StringBuilder stringBuilder = new StringBuilder(); + foreach (PSObject obj in results) + { + stringBuilder.AppendLine(obj.ToString()); + } + + return stringBuilder.ToString().Trim(new char[] { ' ', '\r', '\n' }); + } + + public static void RunPowershellScript(string scriptText, string expectedResult, int retryCount = 3) + { + bool isReady = false; + string result = string.Empty; + + for (int i = 0; i < retryCount; i++) + { + try + { + result = TestUtility.RunPowershellScript(scriptText); + } + catch + { + result = "ExceptionError"; + } + + if (expectedResult != null) + { + if (expectedResult == result) + { + isReady = true; + break; + } + else + { + System.Threading.Thread.Sleep(1000); + } + } + else + { + isReady = true; + break; + } + } + if (!isReady) + { + throw new ApplicationException("Failed to execute command: " + scriptText + ", expected result: " + expectedResult + ", actual result = " + result); + } + } + + public static int RunCommand(string fileName, string arguments = null, bool checkStandardError = true, bool waitForExit=true) + { + int pid = -1; + Process p = new Process(); + p.StartInfo.FileName = fileName; + if (arguments != null) + { + p.StartInfo.Arguments = arguments; + } + + if (waitForExit) + { + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + } + + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.Start(); + pid = p.Id; + string standardOutput = string.Empty; + string standardError = string.Empty; + if (waitForExit) + { + standardOutput = p.StandardOutput.ReadToEnd(); + standardError = p.StandardError.ReadToEnd(); + p.WaitForExit(); + } + if (checkStandardError && standardError != string.Empty) + { + throw new Exception("Failed to run " + fileName + " " + arguments + ", Error: " + standardError + ", StandardOutput: " + standardOutput); + } + return pid; + } + + public static bool CallIISReset() + { + int result = RunCommand("iisreset", null, false); + return (result != -1); + } + + public static bool StopHttp() + { + int result = RunCommand("net", "stop http /y", false); + return (result != -1); + } + + public static bool StopWas() + { + int result = RunCommand("net", "stop was /y", false); + return (result != -1); + } + + public static bool StartWas() + { + int result = RunCommand("net", "start was", false); + return (result != -1); + } + + public static bool StopW3svc() + { + int result = RunCommand("net", "stop w3svc /y", false); + return (result != -1); + } + + public static bool StartW3svc() + { + int result = RunCommand("net", "start w3svc", false); + return (result != -1); + } + + public static string GetApplicationPath() + { + var applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath; + string solutionPath = InitializeTestMachine.GetSolutionDirectory(); + string applicationPath = string.Empty; + applicationPath = Path.Combine(solutionPath, "test", "AspNetCoreModule.TestSites.Standard"); + return applicationPath; + } + + public static string GetConfigContent(ServerType serverType, string iisConfig) + { + string content = null; + if (serverType == ServerType.IISExpress) + { + content = File.ReadAllText(iisConfig); + } + return content; + } + + public static void ClearApplicationEventLog() + { + using (EventLog eventLog = new EventLog("Application")) + { + eventLog.Clear(); + } + for (int i = 0; i < 5; i++) + { + TestUtility.LogInformation("Waiting 1 seconds for eventlog to clear..."); + Thread.Sleep(1000); + EventLog systemLog = new EventLog("Application"); + if (systemLog.Entries.Count == 0) + { + break; + } + } + } + + public static List GetApplicationEvent(int id, DateTime startFrom) + { + var result = new List(); + TestUtility.LogInformation("Waiting 1 seconds for eventlog to update..."); + Thread.Sleep(1000); + EventLog systemLog = new EventLog("Application"); + foreach (EventLogEntry entry in systemLog.Entries) + { + if (entry.InstanceId == id && entry.TimeWritten >= startFrom) + { + result.Add(entry.ReplacementStrings[0]); + } + } + + return result; + } + + public static string ConvertToPunycode(string domain) + { + Uri uri = new Uri("http://" + domain); + return uri.DnsSafeHost; + } + } +} diff --git a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs new file mode 100644 index 0000000000..83f0b03e32 --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs @@ -0,0 +1,243 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; + +namespace AspNetCoreModule.Test.Framework +{ + public class TestWebApplication : IDisposable + { + private TestWebSite _testSite; + public TestWebSite TestSite + { + get + { + return _testSite; + } + set + { + _testSite = value; + } + } + + public TestWebApplication(string name, string physicalPath, string url = null) + : this(name, physicalPath, null, url) + { + } + + public TestWebApplication(string name, string physicalPath, TestWebSite siteContext, string url = null) + { + _testSite = siteContext; + _name = name; + string temp = physicalPath; + if (physicalPath.Contains("%")) + { + temp = System.Environment.ExpandEnvironmentVariables(physicalPath); + } + _physicalPath = temp; + + if (url != null) + { + _url = url; + } + else + { + string tempUrl = name.Trim(); + if (tempUrl[0] != '/') + { + _url = "/" + tempUrl; + } + else + { + _url = tempUrl; + } + } + BackupFile("web.config"); + } + + public void Dispose() + { + DeleteFile("app_offline.htm"); + RestoreFile("web.config"); + } + + private string _name = null; + public string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } + + private string _physicalPath = null; + public string PhysicalPath + { + get + { + return _physicalPath; + } + set + { + _physicalPath = value; + } + } + + private string _url = null; + public string URL + { + get + { + return _url; + } + set + { + _url = value; + } + } + + public Uri GetUri() + { + return new Uri("http://" + _testSite.HostName + ":" + _testSite.TcpPort.ToString() + URL); + } + + public Uri GetUri(string subPath, int port = -1, string protocol = "http") + { + if (port == -1) + { + port = _testSite.TcpPort; + } + + string tempSubPath = string.Empty; + if (subPath != null) + { + tempSubPath = subPath; + if (!tempSubPath.StartsWith("/")) + { + tempSubPath = "/" + tempSubPath; + } + } + return new Uri(protocol + "://" + _testSite.HostName + ":" + port.ToString() + URL + tempSubPath); + } + + public string _appPoolName = null; + public string AppPoolName + { + get + { + if (_appPoolName == null) + { + _appPoolName = "DefaultAppPool"; + } + return _appPoolName; + } + set + { + _appPoolName = value; + } + } + + public string GetProcessFileName() + { + string filePath = Path.Combine(_physicalPath, "web.config"); + string result = null; + + // read web.config + string fileContent = TestUtility.FileReadAllText(filePath); + + // get the value of processPath attribute of aspNetCore element + if (fileContent != null) + { + result = TestUtility.XmlParser(fileContent, "aspNetCore", "processPath", null); + } + + // split fileName from full path + result = Path.GetFileName(result); + + // append .exe if it wasn't used + if (!result.Contains(".exe")) + { + result = result + ".exe"; + } + return result; + } + + public string GetArgumentFileName() + { + string filePath = Path.Combine(_physicalPath, "web.config"); + string result = null; + + // read web.config + string fileContent = TestUtility.FileReadAllText(filePath); + + // get the value of arguments attribute of aspNetCore element + if (fileContent != null) + { + result = TestUtility.XmlParser(fileContent, "aspNetCore", "arguments", null); + } + + // split fileName from full path + result = Path.GetFileName(result); + return result; + } + + public void BackupFile(string from) + { + string fromfile = Path.Combine(_physicalPath, from); + string tofile = Path.Combine(_physicalPath, fromfile + ".bak"); + TestUtility.FileCopy(fromfile, tofile, overWrite: false); + } + + public void RestoreFile(string from) + { + string fromfile = Path.Combine(_physicalPath, from + ".bak"); + string tofile = Path.Combine(_physicalPath, from); + if (!File.Exists(fromfile)) + { + BackupFile(from); + } + TestUtility.FileCopy(fromfile, tofile); + } + + public string GetDirectoryPathWith(string subPath) + { + return Path.Combine(_physicalPath, subPath); + } + + public void DeleteFile(string file = "app_offline.htm") + { + string filePath = Path.Combine(_physicalPath, file); + TestUtility.DeleteFile(filePath); + } + + public void CreateFile(string[] content, string file = "app_offline.htm") + { + string filePath = Path.Combine(_physicalPath, file); + TestUtility.CreateFile(filePath, content); + } + + public void MoveFile(string from, string to) + { + string fromfile = Path.Combine(_physicalPath, from); + string tofile = Path.Combine(_physicalPath, to); + TestUtility.FileMove(fromfile, tofile); + } + + public void DeleteDirectory(string directory) + { + string directoryPath = Path.Combine(_physicalPath, directory); + TestUtility.DeleteDirectory(directoryPath); + } + + public void CreateDirectory(string directory) + { + string directoryPath = Path.Combine(_physicalPath, directory); + TestUtility.CreateDirectory(directoryPath); + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs new file mode 100644 index 0000000000..21e1c9cfb3 --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -0,0 +1,536 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.Extensions.Logging; +using System.Diagnostics; + +namespace AspNetCoreModule.Test.Framework +{ + public class TestWebSite : IDisposable + { + static private bool _publishedAspnetCoreApp = false; + + public TestWebApplication RootAppContext; + public TestWebApplication AspNetCoreApp; + public TestWebApplication WebSocketApp; + public TestWebApplication URLRewriteApp; + public TestUtility testHelper; + private ILogger _logger; + private int _iisExpressPidBackup = -1; + + private string postfix = string.Empty; + + public void Dispose() + { + TestUtility.LogInformation("TestWebSite::Dispose() Start"); + + if (_iisExpressPidBackup != -1) + { + var iisExpressProcess = Process.GetProcessById(Convert.ToInt32(_iisExpressPidBackup)); + try + { + iisExpressProcess.Kill(); + iisExpressProcess.WaitForExit(); + iisExpressProcess.Close(); + } + catch + { + TestUtility.RunPowershellScript("stop-process -id " + _iisExpressPidBackup); + } + } + TestUtility.LogInformation("TestWebSite::Dispose() End"); + } + + public string _hostName = null; + public string HostName + { + get + { + if (_hostName == null) + { + _hostName = "localhost"; + } + return _hostName; + } + set + { + _hostName = value; + } + } + + public string _siteName = null; + public string SiteName + { + get + { + return _siteName; + } + set + { + _siteName = value; + } + } + + public string _postFix = null; + public string PostFix + { + get + { + return _postFix; + } + set + { + _postFix = value; + } + } + + public int _tcpPort = 8080; + public int TcpPort + { + get + { + return _tcpPort; + } + set + { + _tcpPort = value; + } + } + + private int _workerProcessID = 0; + public int WorkerProcessID + { + get + { + if (_workerProcessID == 0) + { + try + { + if (IisServerType == ServerType.IISExpress) + { + _workerProcessID = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("iisexpress.exe", "Handle", null)); + } + else + { + _workerProcessID = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", null)); + } + } + catch + { + TestUtility.LogInformation("Failed to get process id of w3wp.exe"); + } + } + return _workerProcessID; + } + set + { + _workerProcessID = value; + } + } + + public string HostNameBinding + { + get + { + if (IisServerType == ServerType.IISExpress) + { + return "localhost"; + } + else + { + return ""; + } + } + } + + public ServerType IisServerType { get; set; } + public string IisExpressConfigPath { get; set; } + private int _siteId { get; set; } + private IISConfigUtility.AppPoolBitness _appPoolBitness { get; set; } + + public TestWebSite(IISConfigUtility.AppPoolBitness appPoolBitness, string loggerPrefix = "ANCMTest", bool startIISExpress = true, bool copyAllPublishedFiles = false, bool attachAppVerifier = false) + { + _appPoolBitness = appPoolBitness; + + // + // Initialize IisServerType + // + if (TestFlags.Enabled(TestFlags.UseFullIIS)) + { + IisServerType = ServerType.IIS; + } + else + { + IisServerType = ServerType.IISExpress; + } + + // + // Use localhost hostname for IISExpress + // + + + if (IisServerType == ServerType.IISExpress + && TestFlags.Enabled(TestFlags.Wow64BitMode)) + { + // + // In Wow64/IISExpress test context, always use 32 bit worker process + // + if (_appPoolBitness == IISConfigUtility.AppPoolBitness.noChange) + { + TestUtility.LogInformation("Warning!!! In Wow64, _appPoolBitness should be set with enable32bit"); + _appPoolBitness = IISConfigUtility.AppPoolBitness.enable32Bit; + } + } + + TestUtility.LogInformation("TestWebSite::TestWebSite() Start"); + + string solutionPath = InitializeTestMachine.GetSolutionDirectory(); + + if (IisServerType == ServerType.IIS) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + } + + // initialize logger for TestUtility + _logger = new LoggerFactory() + .AddConsole() + .CreateLogger(string.Format(loggerPrefix)); + + testHelper = new TestUtility(_logger); + + // + // Initialize context variables + // + string siteRootPath = string.Empty; + string siteName = string.Empty; + string postfix = string.Empty; + + // repeat three times until getting the valid temporary directory path + for (int i = 0; i < 3; i++) + { + postfix = Path.GetRandomFileName(); + siteName = loggerPrefix.Replace(" ", "") + "_" + postfix; + siteRootPath = Path.Combine(InitializeTestMachine.TestRootDirectory, siteName); + if (!Directory.Exists(siteRootPath)) + { + break; + } + } + + TestUtility.DirectoryCopy(Path.Combine(solutionPath, "test", "WebRoot"), siteRootPath); + string aspnetCoreAppRootPath = Path.Combine(siteRootPath, "AspNetCoreApp"); + string srcPath = TestUtility.GetApplicationPath(); + + // copy http.config to the test site root directory and initialize iisExpressConfigPath with the path + if (IisServerType == ServerType.IISExpress) + { + IisExpressConfigPath = Path.Combine(siteRootPath, "http.config"); + TestUtility.FileCopy(Path.Combine(solutionPath, "test", "AspNetCoreModule.Test", "http.config"), IisExpressConfigPath); + } + + // + // Currently we use DotnetCore v2.0 + // + string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp2.0", "publish"); + string publishPathOutput = Path.Combine(InitializeTestMachine.TestRootDirectory, "publishPathOutput"); + + // + // Publish aspnetcore app + // + if (_publishedAspnetCoreApp != true) + { + string argumentForDotNet = "publish " + srcPath + " --framework netcoreapp2.0"; + TestUtility.LogInformation("TestWebSite::TestWebSite() StandardTestApp is not published, trying to publish on the fly: dotnet.exe " + argumentForDotNet); + TestUtility.DeleteDirectory(publishPath); + TestUtility.RunCommand("dotnet", argumentForDotNet); + if (!File.Exists(Path.Combine(publishPath, "AspNetCoreModule.TestSites.Standard.dll"))) + { + throw new Exception("Failed to publish"); + } + TestUtility.DirectoryCopy(publishPath, publishPathOutput); + TestUtility.FileCopy(Path.Combine(publishPathOutput, "web.config"), Path.Combine(publishPathOutput, "web.config.bak")); + + // Adjust the arguments attribute value with IISConfigUtility from a temporary site + using (var iisConfig = new IISConfigUtility(IisServerType, IisExpressConfigPath)) + { + string tempSiteName = "ANCMTest_Temp"; + int tempId = InitializeTestMachine.SiteId - 1; + string argumentFileName = (new TestWebApplication("/", publishPathOutput, null)).GetArgumentFileName(); + if (string.IsNullOrEmpty(argumentFileName)) + { + argumentFileName = "AspNetCoreModule.TestSites.Standard.dll"; + } + iisConfig.CreateSite(tempSiteName, HostNameBinding, publishPathOutput, tempId, tempId); + iisConfig.SetANCMConfig(tempSiteName, "/", "arguments", Path.Combine(publishPathOutput, argumentFileName)); + iisConfig.DeleteSite(tempSiteName); + } + _publishedAspnetCoreApp = true; + } + + if (copyAllPublishedFiles) + { + // Copy all the files in the pubishpath to the standardAppRootPath + TestUtility.DirectoryCopy(publishPath, aspnetCoreAppRootPath); + TestUtility.FileCopy(Path.Combine(publishPathOutput, "web.config.bak"), Path.Combine(aspnetCoreAppRootPath, "web.config")); + } + else + { + // Copy only web.config file, which points to the shared publishPathOutput, to the standardAppRootPath + TestUtility.CreateDirectory(aspnetCoreAppRootPath); + TestUtility.FileCopy(Path.Combine(publishPathOutput, "web.config"), Path.Combine(aspnetCoreAppRootPath, "web.config")); + } + + int tcpPort = InitializeTestMachine.SiteId++; + _siteId = tcpPort; + + // + // initialize class member variables + // + string appPoolName = null; + if (IisServerType == ServerType.IIS) + { + appPoolName = "AspNetCoreModuleTestAppPool"; + } + else if (IisServerType == ServerType.IISExpress) + { + appPoolName = "Clr4IntegratedAppPool"; + } + + // Initialize member variables + _hostName = "localhost"; + _siteName = siteName; + _postFix = postfix; + _tcpPort = tcpPort; + + RootAppContext = new TestWebApplication("/", Path.Combine(siteRootPath, "WebSite1"), this); + RootAppContext.RestoreFile("web.config"); + RootAppContext.DeleteFile("app_offline.htm"); + RootAppContext.AppPoolName = appPoolName; + + AspNetCoreApp = new TestWebApplication("/AspNetCoreApp", aspnetCoreAppRootPath, this); + AspNetCoreApp.AppPoolName = appPoolName; + AspNetCoreApp.RestoreFile("web.config"); + AspNetCoreApp.DeleteFile("app_offline.htm"); + + WebSocketApp = new TestWebApplication("/WebSocketApp", Path.Combine(siteRootPath, "WebSocket"), this); + WebSocketApp.AppPoolName = appPoolName; + WebSocketApp.RestoreFile("web.config"); + WebSocketApp.DeleteFile("app_offline.htm"); + + URLRewriteApp = new TestWebApplication("/URLRewriteApp", Path.Combine(siteRootPath, "URLRewrite"), this); + URLRewriteApp.AppPoolName = appPoolName; + URLRewriteApp.RestoreFile("web.config"); + URLRewriteApp.DeleteFile("app_offline.htm"); + + // + // Create site and apps + // + using (var iisConfig = new IISConfigUtility(IisServerType, IisExpressConfigPath)) + { + // Create apppool + if (IisServerType == ServerType.IIS) + { + iisConfig.CreateAppPool(appPoolName); + + // Switch bitness + if (TestUtility.IsOSAmd64 && appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + iisConfig.SetAppPoolSetting(appPoolName, "enable32BitAppOnWin64", true); + } + } + + if (TestFlags.Enabled(TestFlags.UsePrivateANCM) && IisServerType == ServerType.IISExpress) + { + if (TestUtility.IsOSAmd64) + { + if (_appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + iisConfig.AddModule("AspNetCoreModule", (InitializeTestMachine.IisExpressAspnetcore_X86_path), null); + } + else + { + iisConfig.AddModule("AspNetCoreModule", (InitializeTestMachine.IisExpressAspnetcore_path), null); + } + } + else + { + iisConfig.AddModule("AspNetCoreModule", (InitializeTestMachine.IisExpressAspnetcore_path), null); + } + } + + iisConfig.CreateSite(siteName, HostNameBinding, RootAppContext.PhysicalPath, _siteId, TcpPort, appPoolName); + iisConfig.CreateApp(siteName, AspNetCoreApp.Name, AspNetCoreApp.PhysicalPath, appPoolName); + iisConfig.CreateApp(siteName, WebSocketApp.Name, WebSocketApp.PhysicalPath, appPoolName); + iisConfig.CreateApp(siteName, URLRewriteApp.Name, URLRewriteApp.PhysicalPath, appPoolName); + } + + if (startIISExpress) + { + // clean up IISExpress before starting a new instance + TestUtility.KillIISExpressProcess(); + + StartIISExpress(); + + // send a startup request to IISExpress instance to make sure that it is fully ready to use before starting actual test scenarios + TestUtility.RunPowershellScript("( invoke-webrequest http://localhost:" + TcpPort + " ).StatusCode", "200"); + } + TestUtility.LogInformation("TestWebSite::TestWebSite() End"); + } + + public void StartIISExpress() + { + if (IisServerType == ServerType.IIS) + { + return; + } + + // reset workerProcessID + this.WorkerProcessID = 0; + + string cmdline; + string argument = "/siteid:" + _siteId + " /config:" + IisExpressConfigPath; + + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%")) && _appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", "iisexpress.exe"); + } + else + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", "iisexpress.exe"); + } + TestUtility.LogInformation("TestWebSite::TestWebSite() Start IISExpress: " + cmdline + " " + argument); + _iisExpressPidBackup = TestUtility.RunCommand(cmdline, argument, false, false); + } + + public void AttachAppverifier() + { + string cmdline; + string processName = "iisexpress.exe"; + if (IisServerType == ServerType.IIS) + { + processName = "w3wp.exe"; + } + string argument = "-enable Heaps COM RPC Handles Locks Memory TLS Exceptions Threadpool Leak SRWLock -for " + processName; + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%")) && _appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); + } + } + else + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); + } + } + + try + { + TestUtility.LogInformation("Configure Appverifier: " + cmdline + " " + argument); + TestUtility.RunCommand(cmdline, argument, true, false); + } + catch + { + throw new ApplicationException("Failed to configure Appverifier"); + } + } + + public void AttachWinDbg(int processIdOfWorkerProcess) + { + string processName = "iisexpress.exe"; + string debuggerCmdline; + if (IisServerType == ServerType.IIS) + { + processName = "w3wp.exe"; + } + string argument = "-enable Heaps COM RPC Handles Locks Memory TLS Exceptions Threadpool Leak SRWLock -for " + processName; + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%")) && _appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "wow64", "windbg.exe"); + if (!File.Exists(debuggerCmdline)) + { + throw new ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); + } + } + else + { + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"))) + { + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "windbg.exe"); + } + else + { + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x86)", "windbg.exe"); + } + if (!File.Exists(debuggerCmdline)) + { + throw new ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); + } + } + + try + { + TestUtility.RunCommand(debuggerCmdline, " -g -G -p " + processIdOfWorkerProcess.ToString(), true, false); + System.Threading.Thread.Sleep(3000); + } + catch + { + throw new ApplicationException("Failed to attach debuger"); + } + } + + public void DetachAppverifier() + { + try + { + string cmdline; + string processName = "iisexpress.exe"; + string debuggerCmdline; + if (IisServerType == ServerType.IIS) + { + processName = "w3wp.exe"; + } + + string argument = "-disable * -for " + processName; + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%")) && _appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new ApplicationException("Not found :" + cmdline); + } + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "wow64", "windbg.exe"); + if (!File.Exists(debuggerCmdline)) + { + throw new ApplicationException("Not found :" + debuggerCmdline); + } + } + else + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new ApplicationException("Not found :" + cmdline); + } + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "windbg.exe"); + if (!File.Exists(debuggerCmdline)) + { + throw new ApplicationException("Not found :" + debuggerCmdline); + } + } + TestUtility.RunCommand(cmdline, argument, true, false); + } + catch + { + TestUtility.LogInformation("Failed to detach Appverifier"); + } + } + } +} diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs new file mode 100644 index 0000000000..0268367dd0 --- /dev/null +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -0,0 +1,370 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.Framework; +using Microsoft.AspNetCore.Testing.xunit; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace AspNetCoreModule.Test +{ + public class FunctionalTest : FunctionalTestHelper, IClassFixture + { + private const string ANCMTestCondition = TestFlags.SkipTest; + //private const string ANCMTestCondition = TestFlags.RunAsAdministrator; + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + public async void BasicTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + await DoBasicTest(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 1)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0)] + public Task RapidFailsPerMinuteTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfRapidFailsPerMinute) + { + return DoRapidFailsPerMinuteTest(appPoolBitness, valueOfRapidFailsPerMinute); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19, false)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19, true)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4, true)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4, false)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0, false)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0, true)] + public Task ShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime, bool isGraceFullShutdownEnabled) + { + return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime, isGraceFullShutdownEnabled); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 10)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 1)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 1)] + public Task StartupTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int starupTimeLimit) + { + return DoStartupTimeLimitTest(appPoolBitness, starupTimeLimit); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationAfterBackendProcessBeingKilled(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationAfterBackendProcessBeingKilled(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationAfterW3WPProcessBeingKilled(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationAfterW3WPProcessBeingKilled(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationAfterWebConfigUpdated(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationAfterWebConfigUpdated(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationWithURLRewrite(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationWithURLRewrite(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleParentApplicationWithURLRewrite(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleParentApplicationWithURLRewrite(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData("ANCMTestBar", "bar", "bar", IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", "NA", "Microsoft.AspNetCore.Server.IISIntegration", IISConfigUtility.AppPoolBitness.noChange)] + [InlineData("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", "newValue", "newValue", IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData("ASPNETCORE_IIS_HTTPAUTH", "anonymous;", "anonymous;", IISConfigUtility.AppPoolBitness.noChange)] + [InlineData("ASPNETCORE_IIS_HTTPAUTH", "basic;anonymous;", "basic;anonymous;", IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData("ASPNETCORE_IIS_HTTPAUTH", "windows;anonymous;", "windows;anonymous;", IISConfigUtility.AppPoolBitness.noChange)] + [InlineData("ASPNETCORE_IIS_HTTPAUTH", "windows;basic;anonymous;", "windows;basic;anonymous;", IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData("ASPNETCORE_IIS_HTTPAUTH", "ignoredValue", "anonymous;", IISConfigUtility.AppPoolBitness.noChange)] + public Task EnvironmentVariablesTest(string environmentVariableName, string environmentVariableValue, string expectedEnvironmentVariableValue, IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoEnvironmentVariablesTest(environmentVariableName, environmentVariableValue, expectedEnvironmentVariableValue, appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task AppOfflineTestWithRenaming(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoAppOfflineTestWithRenaming(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task AppOfflineTestWithUrlRewriteAndDeleting(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoAppOfflineTestWithUrlRewriteAndDeleting(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "a")] + public Task PostMethodTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + return DoPostMethodTest(appPoolBitness, testData); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task DisableStartUpErrorPageTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoDisableStartUpErrorPageTest(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 2)] + public Task ProcessesPerApplicationTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfProcessesPerApplication) + { + return DoProcessesPerApplicationTest(appPoolBitness, valueOfProcessesPerApplication); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "00:02:00")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "00:02:00")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "00:01:00")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "00:01:00")] + public Task RequestTimeoutTest(IISConfigUtility.AppPoolBitness appPoolBitness, string requestTimeout) + { + return DoRequestTimeoutTest(appPoolBitness, requestTimeout); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task StdoutLogEnabledTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoStdoutLogEnabledTest(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "dotnet.exe", "./")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "dotnet", @".\")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "$env", "")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "$env", "")] + public Task ProcessPathAndArgumentsTest(IISConfigUtility.AppPoolBitness appPoolBitness, string processPath, string argumentsPrefix) + { + return DoProcessPathAndArgumentsTest(appPoolBitness, processPath, argumentsPrefix); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, false)] + public Task ForwardWindowsAuthTokenTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool enabledForwardWindowsAuthToken) + { + return DoForwardWindowsAuthTokenTest(appPoolBitness, enabledForwardWindowsAuthToken); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true, true)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, false, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, true, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, false, true)] + public Task CompressionTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useCompressionMiddleWare, bool enableIISCompression) + { + return DoCompressionTest(appPoolBitness, useCompressionMiddleWare, enableIISCompression); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task CachingTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoCachingTest(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task SendHTTPSRequestTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoSendHTTPSRequestTest(appPoolBitness); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE", "f")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "mS-ASPNETCORE", "fo")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE-", "foo")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "mS-ASPNETCORE-f", "fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE-foo", "foo")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "MS-ASPNETCORE-foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", "bar")] + public Task FilterOutMSRequestHeadersTest(IISConfigUtility.AppPoolBitness appPoolBitness, string requestHeader, string requestHeaderValue) + { + return DoFilterOutMSRequestHeadersTest(appPoolBitness, requestHeader, requestHeaderValue); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, false)] + public Task ClientCertificateMappingTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useHTTPSMiddleWare) + { + return DoClientCertificateMappingTest(appPoolBitness, useHTTPSMiddleWare); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, false, DoAppVerifierTest_StartUpMode.UseGracefulShutdown, DoAppVerifierTest_ShutDownMode.RecycleAppPool, 1)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, false, DoAppVerifierTest_StartUpMode.DontUseGracefulShutdown, DoAppVerifierTest_ShutDownMode.RecycleAppPool, 1)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, false, DoAppVerifierTest_StartUpMode.UseGracefulShutdown, DoAppVerifierTest_ShutDownMode.StopAndStartAppPool, 1)] + public Task AppVerifierTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool shutdownTimeout, DoAppVerifierTest_StartUpMode startUpMode, DoAppVerifierTest_ShutDownMode shutDownMode, int repeatCount) + { + return DoAppVerifierTest(appPoolBitness, shutdownTimeout, startUpMode, shutDownMode, repeatCount); + } + + ////////////////////////////////////////////////////////// + // NOTE: below test scenarios are not valid for Win7 OS + ////////////////////////////////////////////////////////// + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task WebSocketErrorhandlingTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoWebSocketErrorhandlingTest(appPoolBitness); + } + + ////////////////////////////////////////////////////////// + // NOTE: below test scenarios are not valid for Win7 OS + ////////////////////////////////////////////////////////// + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "IIS does not support Websocket on Win7")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "a")] + // Test reliablitiy issue with lenghty data; disabled until the reason of the test issue is figured out + //[InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] + public Task WebSocketTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + return DoWebSocketTest(appPoolBitness, testData); + } + + [ConditionalTheory] + [ANCMTestFlags(ANCMTestCondition)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "WAS does not handle private memory limitation with Job object on Win7")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecylingAppPoolTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecylingAppPoolTest(appPoolBitness); + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs new file mode 100644 index 0000000000..4094e3f477 --- /dev/null +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -0,0 +1,2306 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Sdk; +using System.Diagnostics; +using System.Net; +using System.Threading; +using AspNetCoreModule.Test.WebSocketClient; +using System.Text; +using System.IO; +using System.Security.Principal; +using System.IO.Compression; +using Microsoft.AspNetCore.Testing.xunit; + +namespace AspNetCoreModule.Test +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class ANCMTestFlags : Attribute, ITestCondition + { + private readonly string _attributeValue; + public ANCMTestFlags(string attributeValue) + { + _attributeValue = attributeValue.ToString(); + } + + public bool IsMet + { + get + { + if (_attributeValue == TestFlags.SkipTest) + { + AdditionalInfo = TestFlags.SkipTest + " is set"; + return false; + } + + if (_attributeValue == TestFlags.RequireRunAsAdministrator + && !TestFlags.Enabled(TestFlags.RunAsAdministrator)) + { + AdditionalInfo = _attributeValue + " is not belong to the given global test context(" + InitializeTestMachine.GlobalTestFlags + ")"; + return false; + } + return true; + } + } + + public string SkipReason + { + get + { + return $"Skip condition: ANCMTestFlags: this test case is skipped becauset {AdditionalInfo}."; + } + } + + public string AdditionalInfo { get; set; } + } + + public class FunctionalTestHelper + { + public FunctionalTestHelper() + { + } + + private const int _repeatCount = 3; + + public static async Task DoBasicTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoBasicTest")) + { + string backendProcessId_old = null; + + DateTime startTime = DateTime.Now; + Thread.Sleep(3000); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.NotEqual(backendProcessId_old, backendProcessId); + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + var httpClientHandler = new HttpClientHandler(); + var httpClient = new HttpClient(httpClientHandler) + { + BaseAddress = testSite.AspNetCoreApp.GetUri(), + Timeout = TimeSpan.FromSeconds(5), + }; + + // Invoke given test scenario function + await CheckChunkedAsync(httpClient, testSite.AspNetCoreApp); + } + } + + public static async Task DoRecycleApplicationAfterBackendProcessBeingKilled(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleApplicationAfterBackendProcessBeingKilled")) + { + string backendProcessId_old = null; + const int repeatCount = 3; + for (int i = 0; i < repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1000); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + backendProcess.Kill(); + Thread.Sleep(500); + } + } + } + + public static async Task DoRecycleApplicationAfterW3WPProcessBeingKilled(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleApplicationAfterW3WPProcessBeingKilled")) + { + string appDllFileName = testSite.AspNetCoreApp.GetArgumentFileName(); + + if (testSite.IisServerType == ServerType.IISExpress) + { + TestUtility.LogInformation("This test is not valid for IISExpress server type"); + return; + } + + string backendProcessId_old = null; + const int repeatCount = 3; + for (int i = 0; i < repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1000); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + // get process id of IIS worker process (w3wp.exe) + string userName = testSite.SiteName; + int processIdOfWorkerProcess = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + var workerProcess = Process.GetProcessById(Convert.ToInt32(processIdOfWorkerProcess)); + workerProcess.Kill(); + + Thread.Sleep(500); + + // Verify the application file can be removed after worker process is stopped + testSite.AspNetCoreApp.BackupFile(appDllFileName); + testSite.AspNetCoreApp.DeleteFile(appDllFileName); + testSite.AspNetCoreApp.RestoreFile(appDllFileName); + } + } + } + + public static async Task DoRecycleApplicationAfterWebConfigUpdated(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleApplicationAfterWebConfigUpdated")) + { + string backendProcessId_old = null; + string appDllFileName = testSite.AspNetCoreApp.GetArgumentFileName(); + + const int repeatCount = 3; + for (int i = 0; i < repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1000); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + testSite.AspNetCoreApp.MoveFile("web.config", "_web.config"); + Thread.Sleep(500); + testSite.AspNetCoreApp.MoveFile("_web.config", "web.config"); + + // Verify the application file can be removed after backend process is restarted + testSite.AspNetCoreApp.BackupFile(appDllFileName); + testSite.AspNetCoreApp.DeleteFile(appDllFileName); + testSite.AspNetCoreApp.RestoreFile(appDllFileName); + } + + // restore web.config + testSite.AspNetCoreApp.RestoreFile("web.config"); + + } + } + + public static async Task DoRecycleApplicationWithURLRewrite(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleApplicationWithURLRewrite")) + { + string backendProcessId_old = null; + const int repeatCount = 3; + for (int i = 0; i < repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1100); + + string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; + string backendProcessId = (await SendReceive(testSite.RootAppContext.GetUri(urlForUrlRewrite))).ResponseBody; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + testSite.AspNetCoreApp.MoveFile("web.config", "_web.config"); + Thread.Sleep(500); + testSite.AspNetCoreApp.MoveFile("_web.config", "web.config"); + } + + // restore web.config + testSite.AspNetCoreApp.RestoreFile("web.config"); + + } + } + + public static async Task DoRecycleParentApplicationWithURLRewrite(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleParentApplicationWithURLRewrite")) + { + string backendProcessId_old = null; + const int repeatCount = 3; + for (int i = 0; i < repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1000); + + string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; + string backendProcessId = (await SendReceive(testSite.RootAppContext.GetUri(urlForUrlRewrite))).ResponseBody; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + testSite.RootAppContext.MoveFile("web.config", "_web.config"); + Thread.Sleep(500); + testSite.RootAppContext.MoveFile("_web.config", "web.config"); + } + + // restore web.config + testSite.RootAppContext.RestoreFile("web.config"); + } + } + + public static async Task DoEnvironmentVariablesTest(string environmentVariableName, string environmentVariableValue, string expectedEnvironmentVariableValue, IISConfigUtility.AppPoolBitness appPoolBitness) + { + if (environmentVariableName == null) + { + throw new InvalidDataException("envrionmentVarialbeName is null"); + } + using (var testSite = new TestWebSite(appPoolBitness, "DoEnvironmentVariablesTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + DateTime startTime = DateTime.Now; + Thread.Sleep(500); + + string totalNumber = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody; + Assert.True(totalNumber == (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody); + + iisConfig.SetANCMConfig( + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", + new string[] { "ANCMTestFoo", "foo" } + ); + + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + int expectedValue = Convert.ToInt32(totalNumber) + 1; + string totalResult = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody; + Assert.True(expectedValue.ToString() == (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody); + + bool setEnvironmentVariableConfiguration = true; + + // Set authentication for ASPNETCORE_IIS_HTTPAUTH test scenarios + if (environmentVariableName == "ASPNETCORE_IIS_HTTPAUTH" && environmentVariableValue != "ignoredValue") + { + setEnvironmentVariableConfiguration = false; + bool windows = false; + bool basic = false; + bool anonymous = false; + if (environmentVariableValue.Contains("windows;")) + { + windows = true; + } + if (environmentVariableValue.Contains("basic;")) + { + basic = true; + } + if (environmentVariableValue.Contains("anonymous;")) + { + anonymous = true; + } + iisConfig.EnableIISAuthentication(testSite.SiteName, windows, basic, anonymous); + } + + if (environmentVariableValue == "NA" || environmentVariableValue == null) + { + setEnvironmentVariableConfiguration = false; + } + + // Add a new environment variable + if (setEnvironmentVariableConfiguration) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { environmentVariableName, environmentVariableValue }); + + // Adjust the new expected total number of environment variables + if (environmentVariableName != "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES" && + environmentVariableName != "ASPNETCORE_IIS_HTTPAUTH") + { + expectedValue++; + } + } + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + totalResult = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody; + Assert.True(expectedValue.ToString() == totalResult); + Assert.True("foo" == (await SendReceive(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariablesANCMTestFoo"))).ResponseBody); + Assert.True(expectedEnvironmentVariableValue == (await SendReceive(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariables" + environmentVariableName))).ResponseBody); + + // Verify other common environment variables + string temp = (await SendReceive(testSite.AspNetCoreApp.GetUri("DumpEnvironmentVariables"))).ResponseBody; + Assert.Contains("ASPNETCORE_PORT", temp); + Assert.Contains("ASPNETCORE_APPL_PATH", temp); + Assert.Contains("ASPNETCORE_IIS_HTTPAUTH", temp); + Assert.Contains("ASPNETCORE_TOKEN", temp); + Assert.Contains("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", temp); + + // Verify other inherited environment variables + Assert.Contains("PROCESSOR_ARCHITECTURE", temp); + Assert.Contains("USERNAME", temp); + Assert.Contains("USERDOMAIN", temp); + Assert.Contains("USERPROFILE", temp); + } + + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoAppOfflineTestWithRenaming(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoAppOfflineTestWithRenaming")) + { + string backendProcessId_old = null; + string fileContent = "BackEndAppOffline"; + string appDllFileName = testSite.AspNetCoreApp.GetArgumentFileName(); + + testSite.AspNetCoreApp.CreateFile(new string[] { fileContent }, "App_Offline.Htm"); + + for (int i = 0; i < _repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1100); + + // verify 503 + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: fileContent + "\r\n", expectedResponseStatus: HttpStatusCode.ServiceUnavailable); + + // Verify the application file can be removed under app_offline mode + testSite.AspNetCoreApp.BackupFile(appDllFileName); + testSite.AspNetCoreApp.DeleteFile(appDllFileName); + testSite.AspNetCoreApp.RestoreFile(appDllFileName); + + // rename app_offline.htm to _app_offline.htm and verify 200 + testSite.AspNetCoreApp.MoveFile("App_Offline.Htm", "_App_Offline.Htm"); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + // rename back to app_offline.htm + testSite.AspNetCoreApp.MoveFile("_App_Offline.Htm", "App_Offline.Htm"); + } + } + } + + public static async Task DoAppOfflineTestWithUrlRewriteAndDeleting(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoAppOfflineTestWithUrlRewriteAndDeleting")) + { + string backendProcessId_old = null; + string fileContent = "BackEndAppOffline2"; + string appDllFileName = testSite.AspNetCoreApp.GetArgumentFileName(); + + testSite.AspNetCoreApp.CreateFile(new string[] { fileContent }, "App_Offline.Htm"); + + for (int i = 0; i < _repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTime = DateTime.Now; + Thread.Sleep(1100); + + // verify 503 + string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; + await SendReceive(testSite.RootAppContext.GetUri(urlForUrlRewrite), expectedResponseBody: fileContent + "\r\n", expectedResponseStatus: HttpStatusCode.ServiceUnavailable); + + // Verify the application file can be removed under app_offline mode + testSite.AspNetCoreApp.BackupFile(appDllFileName); + testSite.AspNetCoreApp.DeleteFile(appDllFileName); + testSite.AspNetCoreApp.RestoreFile(appDllFileName); + + // delete app_offline.htm and verify 200 + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + string backendProcessId = (await SendReceive(testSite.RootAppContext.GetUri(urlForUrlRewrite))).ResponseBody; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + // create app_offline.htm again + testSite.AspNetCoreApp.CreateFile(new string[] { fileContent }, "App_Offline.Htm"); + } + } + } + + public static async Task DoPostMethodTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoPostMethodTest")) + { + var postFormData = new[] + { + new KeyValuePair("FirstName", "Mickey"), + new KeyValuePair("LastName", "Mouse"), + new KeyValuePair("TestData", testData), + }; + var expectedResponseBody = "FirstName=Mickey&LastName=Mouse&TestData=" + testData; + await SendReceive(testSite.AspNetCoreApp.GetUri("EchoPostData"), postData: postFormData, expectedResponseBody: expectedResponseBody); + } + } + + public static async Task DoDisableStartUpErrorPageTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + int errorEventId = 1000; + string errorMessageContainThis = "bogus"; // bogus path value to cause 502.3 error + + using (var testSite = new TestWebSite(appPoolBitness, "DoDisableStartUpErrorPageTest")) + { + testSite.AspNetCoreApp.DeleteFile("custom502-3.htm"); + string curstomErrorMessage = "ANCMTest502-3"; + testSite.AspNetCoreApp.CreateFile(new string[] { curstomErrorMessage }, "custom502-3.htm"); + + Thread.Sleep(500); + + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + DateTime startTime = DateTime.Now; + Thread.Sleep(500); + + iisConfig.ConfigureCustomLogging(testSite.SiteName, testSite.AspNetCoreApp.Name, 502, 3, "custom502-3.htm"); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "disableStartUpErrorPage", true); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processPath", errorMessageContainThis); + + var responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseStatus:HttpStatusCode.BadGateway)).ResponseBody; + responseBody = responseBody.Replace("\r", "").Replace("\n", "").Trim(); + Assert.True(responseBody == curstomErrorMessage); + + // verify event error log + Assert.True(TestUtility.RetryHelper((arg1, arg2, arg3) => VerifyApplicationEventLog(arg1, arg2, arg3), errorEventId, startTime, errorMessageContainThis)); + + // try again after setting "false" value + startTime = DateTime.Now; + Thread.Sleep(500); + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "disableStartUpErrorPage", false); + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseStatus:HttpStatusCode.BadGateway)).ResponseBody; + Assert.Contains("808681", responseBody); + + // verify event error log + Assert.True(TestUtility.RetryHelper((arg1, arg2, arg3) => VerifyApplicationEventLog(arg1, arg2, arg3), errorEventId, startTime, errorMessageContainThis)); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoRapidFailsPerMinuteTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfRapidFailsPerMinute) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRapidFailsPerMinuteTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + bool rapidFailsTriggered = false; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "rapidFailsPerMinute", valueOfRapidFailsPerMinute); + + string backendProcessId_old = null; + const int repeatCount = 10; + + DateTime startTime = DateTime.Now; + Thread.Sleep(50); + + for (int i = 0; i < repeatCount; i++) + { + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + DateTime startTimeInsideLooping = DateTime.Now; + Thread.Sleep(50); + + var sendReceiveContext = await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId")); + var statusCode = sendReceiveContext.ResponseStatus; + + if (statusCode != HttpStatusCode.OK.ToString()) + { + Assert.True(i >= valueOfRapidFailsPerMinute, i.ToString() + "is greater than or equals to " + valueOfRapidFailsPerMinute.ToString()); + Assert.True(i < valueOfRapidFailsPerMinute + 3, i.ToString() + "is less than " + (valueOfRapidFailsPerMinute + 3).ToString()); + rapidFailsTriggered = true; + break; + } + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.NotEqual(backendProcessId_old, backendProcessId); + backendProcessId_old = backendProcessId; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + + //Verifying EventID of new backend process is not necesssary and removed in order to fix some test reliablity issues + //Thread.Sleep(3000); + //Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTimeInsideLooping, backendProcessId), "Verifying event log of new backend process id " + backendProcessId); + + backendProcess.Kill(); + Thread.Sleep(3000); + } + Assert.True(rapidFailsTriggered, "Verify 502 Bad Gateway error"); + + // verify event error log + int errorEventId = 1003; + string errorMessageContainThis = "'" + valueOfRapidFailsPerMinute + "'"; // part of error message + Assert.True(TestUtility.RetryHelper((arg1, arg2, arg3) => VerifyApplicationEventLog(arg1, arg2, arg3), errorEventId, startTime, errorMessageContainThis)); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoProcessesPerApplicationTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfProcessesPerApplication) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoProcessesPerApplicationTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + DateTime startTime = DateTime.Now; + Thread.Sleep(3000); + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processesPerApplication", valueOfProcessesPerApplication); + HashSet processIDs = new HashSet(); + + for (int i = 0; i < 20; i++) + { + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + int id = Convert.ToInt32(backendProcessId); + if (!processIDs.Contains(id)) + { + processIDs.Add(id); + } + + if (i == (valueOfProcessesPerApplication - 1)) + { + Assert.Equal(valueOfProcessesPerApplication, processIDs.Count); + } + } + + Assert.Equal(valueOfProcessesPerApplication, processIDs.Count); + + foreach (var id in processIDs) + { + var backendProcess = Process.GetProcessById(id); + Assert.Equal(backendProcess.ProcessName.ToLower().Replace(".exe", ""), testSite.AspNetCoreApp.GetProcessFileName().ToLower().Replace(".exe", "")); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, id.ToString())); + } + + // reset the value with 1 again + processIDs = new HashSet(); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processesPerApplication", 1); + Thread.Sleep(3000); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + Thread.Sleep(500); + + for (int i = 0; i < 20; i++) + { + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + int id = Convert.ToInt32(backendProcessId); + if (!processIDs.Contains(id)) + { + processIDs.Add(id); + } + } + Assert.Single(processIDs); + } + + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoStartupTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int startupTimeLimit) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoStartupTimeLimitTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + int startupDelay = 3; //3 seconds + iisConfig.SetANCMConfig( + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", + new string[] { "ANCMTestStartUpDelay", (startupDelay * 1000).ToString() } + ); + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse("00:01:00")); // 1 minute + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "startupTimeLimit", startupTimeLimit); + + Thread.Sleep(500); + if (startupTimeLimit < startupDelay) + { + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep3000"), HttpStatusCode.BadGateway); + } + else + { + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep3000"), expectedResponseBody: "Running"); + } + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoRequestTimeoutTest(IISConfigUtility.AppPoolBitness appPoolBitness, string requestTimeout) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRequestTimeoutTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse(requestTimeout)); + Thread.Sleep(500); + + if (requestTimeout.ToString() == "00:02:00") + { + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep65000"), expectedResponseBody: "Running", timeout: 70); + } + else if (requestTimeout.ToString() == "00:01:00") + { + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, timeout: 70); + } + else + { + throw new ApplicationException("wrong data"); + } + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime, bool isGraceFullShutdownEnabled) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoShutdownTimeLimitTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + DateTime startTime = DateTime.Now; + + // Make shutdownDelay time with hard coded value such as 20 seconds and test vairious shutdonwTimeLimit, either less than 20 seconds or bigger then 20 seconds + int shutdownDelayTime = 20000; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", valueOfshutdownTimeLimit); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestShutdownDelay", shutdownDelayTime.ToString() }); + string expectedGracefulShutdownResponseStatusCode = "202"; + if (!isGraceFullShutdownEnabled) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "GracefulShutdown", "disabled" }); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestStartupClassName", "StartupWithShutdownDisabled" }); + expectedGracefulShutdownResponseStatusCode = "200"; + } + + string response = (await SendReceive(testSite.AspNetCoreApp.GetUri(""))).ResponseBody; + Assert.True(response == "Running"); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + + // Set a new configuration value to make the backend process being recycled + DateTime startTime2 = DateTime.Now; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", 100); + backendProcess.WaitForExit(30000); + DateTime endTime = DateTime.Now; + var difference = endTime - startTime2; + Assert.True(difference.Seconds >= expectedClosingTime); + Assert.True(difference.Seconds < expectedClosingTime + 3); + string newBackendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.True(backendProcessId != newBackendProcessId); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + // if expectedClosing time is less than the shutdownDelay time, gracefulshutdown is supposed to fail and failure event is expected + if (expectedClosingTime * 1000 + 1000 == shutdownDelayTime) + { + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMGracefulShutdownEvent(arg1, arg2), startTime, backendProcessId)); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMGracefulShutdownEvent(arg1, arg2), startTime, expectedGracefulShutdownResponseStatusCode)); + } + else + { + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMGracefulShutdownFailureEvent(arg1, arg2), startTime, backendProcessId)); + } + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + public static async Task DoStdoutLogEnabledTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoStdoutLogEnabledTest")) + { + testSite.AspNetCoreApp.DeleteDirectory("logs"); + + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + DateTime startTime = DateTime.Now; + Thread.Sleep(3000); + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogFile", @".\logs\stdout"); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + string logPath = testSite.AspNetCoreApp.GetDirectoryPathWith("logs"); + Assert.False(Directory.Exists(logPath)); + + Assert.True(TestUtility.RetryHelper((arg1, arg2, arg3) => VerifyApplicationEventLog(arg1, arg2, arg3), 1004, startTime, @"logs\stdout")); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + testSite.AspNetCoreApp.CreateDirectory("logs"); + + // verify the log file is not created because backend process is not recycled + Assert.True(Directory.GetFiles(logPath).Length == 0); + Assert.True(backendProcessId == (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody); + + // reset web.config to recycle backend process and give write permission to the Users local group to which IIS workerprocess identity belongs + SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null); + TestUtility.GiveWritePermissionTo(logPath, sid); + + startTime = DateTime.Now; + Thread.Sleep(500); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", false); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); + + Assert.True(backendProcessId != (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody); + + // Verify log file is created now after backend process is recycled + Assert.True(TestUtility.RetryHelper(p => { return Directory.GetFiles(p).Length > 0 ? true : false; }, logPath)); + } + + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoProcessPathAndArgumentsTest(IISConfigUtility.AppPoolBitness appPoolBitness, string processPath, string argumentsPrefix) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoProcessPathAndArgumentsTest", copyAllPublishedFiles: true)) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string arguments = argumentsPrefix + testSite.AspNetCoreApp.GetArgumentFileName(); + string tempProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + var tempBackendProcess = Process.GetProcessById(Convert.ToInt32(tempProcessId)); + + // replace $env with the actual test value + if (processPath == "$env") + { + string tempString = Environment.ExpandEnvironmentVariables("%systemdrive%").ToLower(); + processPath = Path.Combine(tempBackendProcess.MainModule.FileName).ToLower().Replace(tempString, "%systemdrive%"); + arguments = testSite.AspNetCoreApp.GetDirectoryPathWith(arguments).ToLower().Replace(tempString, "%systemdrive%"); + } + + DateTime startTime = DateTime.Now; + Thread.Sleep(500); + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processPath", processPath); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "arguments", arguments); + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + Thread.Sleep(500); + + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + } + + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoForwardWindowsAuthTokenTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool enabledForwardWindowsAuthToken) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoForwardWindowsAuthTokenTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string responseBody = string.Empty; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); + string requestHeaders = (await SendReceive(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"))).ResponseBody; + Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders, StringComparison.InvariantCultureIgnoreCase); + + iisConfig.EnableIISAuthentication(testSite.SiteName, windows: true, basic: false, anonymous: false); + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + Thread.Sleep(500); + + requestHeaders = (await SendReceive(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"))).ResponseBody; + if (enabledForwardWindowsAuthToken) + { + + Assert.Contains("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); + + responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"))).ResponseBody; + bool compare = false; + + string expectedValue1 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERDOMAIN%") + "\\" + Environment.ExpandEnvironmentVariables("%USERNAME%"); + if (responseBody.ToLower().Contains(expectedValue1.ToLower())) + { + compare = true; + } + + string expectedValue2 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERNAME%"); + if (responseBody.ToLower().Contains(expectedValue2.ToLower())) + { + compare = true; + } + + Assert.True(compare); + } + else + { + Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders, StringComparison.InvariantCultureIgnoreCase); + + responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"))).ResponseBody; + Assert.Contains("ImpersonateMiddleware-UserName = NoAuthentication", responseBody); + } + } + + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoRecylingAppPoolTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecylingAppPoolTest")) + { + if (testSite.IisServerType == ServerType.IISExpress) + { + TestUtility.LogInformation("This test is not valid for IISExpress server type"); + return; + } + + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + + // allocating 1024,000 KB + await SendReceive(testSite.AspNetCoreApp.GetUri("MemoryLeak1024000")); + + // get backend process id + string pocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + + // get process id of IIS worker process (w3wp.exe) + string userName = testSite.SiteName; + int processIdOfWorkerProcess = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + var workerProcess = Process.GetProcessById(Convert.ToInt32(processIdOfWorkerProcess)); + var backendProcess = Process.GetProcessById(Convert.ToInt32(pocessIdBackendProcess)); + + var privateMemoryKB = workerProcess.PrivateMemorySize64 / 1024; + var virtualMemoryKB = workerProcess.VirtualMemorySize64 / 1024; + var privateMemoryKBBackend = backendProcess.PrivateMemorySize64 / 1024; + var virtualMemoryKBBackend = backendProcess.VirtualMemorySize64 / 1024; + var totalPrivateMemoryKB = privateMemoryKB + privateMemoryKBBackend; + var totalVirtualMemoryKB = virtualMemoryKB + virtualMemoryKBBackend; + + // terminate backend process + backendProcess.Kill(); + backendProcess.Dispose(); + + // terminate IIS worker process + workerProcess.Kill(); + workerProcess.Dispose(); + Thread.Sleep(3000); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + iisConfig.SetAppPoolSetting(testSite.AspNetCoreApp.AppPoolName, "privateMemory", totalPrivateMemoryKB); + + // set 100 for rapidFailProtection counter for both IIS worker process and aspnetcore backend process + iisConfig.SetAppPoolSetting(testSite.AspNetCoreApp.AppPoolName, "rapidFailProtectionMaxCrashes", 100); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "rapidFailsPerMinute", 100); + Thread.Sleep(3000); + + await SendReceive(testSite.RootAppContext.GetUri("small.htm")); + Thread.Sleep(1000); + int x = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + + // Verify that IIS recycling does not happen while there is no memory leak + bool foundVSJit = false; + for (int i = 0; i < 10; i++) + { + // check JitDebugger before continuing + foundVSJit = TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + await SendReceive(testSite.RootAppContext.GetUri("small.htm")); + Thread.Sleep(3000); + } + + int y = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + Assert.True(x == y && !foundVSJit, "worker process is not recycled after 30 seconds"); + + string backupPocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + string newPocessIdBackendProcess = backupPocessIdBackendProcess; + + // Verify IIS recycling happens while there is memory leak + for (int i = 0; i < 10; i++) + { + // check JitDebugger before continuing + foundVSJit = TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + // allocating 2048,000 KB + await SendReceive(testSite.AspNetCoreApp.GetUri("MemoryLeak2048000")); + + newPocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + if (foundVSJit || backupPocessIdBackendProcess != newPocessIdBackendProcess) + { + // worker process is recycled expectedly and backend process is recycled together + break; + } + Thread.Sleep(3000); + } + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + int z = 0; + for (int i = 0; i < 10; i++) + { + z = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + if (x != z) + { + break; + } + else + { + Thread.Sleep(1000); + } + } + z = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + Assert.True(x != z, "worker process is recycled"); + + newPocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.True(backupPocessIdBackendProcess != newPocessIdBackendProcess, "backend process is recycled"); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoCompressionTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useCompressionMiddleWare, bool enableIISCompression) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoCompressionTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string startupClass = "StartupCompressionCaching"; + if (!useCompressionMiddleWare) + { + startupClass = "StartupNoCompressionCaching"; + } + + // set startup class + iisConfig.SetANCMConfig( + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", + new string[] { "ANCMTestStartupClassName", startupClass } + ); + + // enable or IIS compression + // Note: IIS compression, however, will be ignored if AspnetCore compression middleware is enabled. + iisConfig.SetCompression(testSite.SiteName, enableIISCompression); + + // prepare static contents + testSite.AspNetCoreApp.CreateDirectory("wwwroot"); + testSite.AspNetCoreApp.CreateDirectory(@"wwwroot\pdir"); + + testSite.AspNetCoreApp.CreateFile(new string[] { "foohtm" }, @"wwwroot\foo.htm"); + testSite.AspNetCoreApp.CreateFile(new string[] { "barhtm" }, @"wwwroot\pdir\bar.htm"); + testSite.AspNetCoreApp.CreateFile(new string[] { "defaulthtm" }, @"wwwroot\default.htm"); + + SendReceiveContext result = null; + if (!useCompressionMiddleWare && !enableIISCompression) + { + result = await SendReceive(testSite.AspNetCoreApp.GetUri("foo.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("foohtm"), "verify response body"); + Assert.False(result.ResponseHeader.Contains("Content-Encoding"), "verify response header"); + + result = await SendReceive(testSite.AspNetCoreApp.GetUri("pdir/bar.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("barhtm"), "verify response body"); + Assert.False(result.ResponseHeader.Contains("Content-Encoding"), "verify response header"); + + result = await SendReceive(testSite.AspNetCoreApp.GetUri(), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("defaulthtm"), "verify response body"); + Assert.False(result.ResponseHeader.Contains("Content-Encoding"), "verify response header"); + } + else + { + result = await SendReceive(testSite.AspNetCoreApp.GetUri("foo.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result.ResponseHeader, "Content-Encoding")); + + result = await SendReceive(testSite.AspNetCoreApp.GetUri("pdir/bar.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("barhtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result.ResponseHeader, "Content-Encoding")); + + result = await SendReceive(testSite.AspNetCoreApp.GetUri(), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("defaulthtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result.ResponseHeader, "Content-Encoding")); + } + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoCachingTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoCachingTest")) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string startupClass = "StartupCompressionCaching"; + + // set startup class + iisConfig.SetANCMConfig( + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", + new string[] { "ANCMTestStartupClassName", startupClass } + ); + + // enable IIS compression + // Note: IIS compression, however, will be ignored if AspnetCore compression middleware is enabled. + iisConfig.SetCompression(testSite.SiteName, true); + + // prepare static contents + testSite.AspNetCoreApp.CreateDirectory("wwwroot"); + testSite.AspNetCoreApp.CreateDirectory(@"wwwroot\pdir"); + + testSite.AspNetCoreApp.CreateFile(new string[] { "foohtm" }, @"wwwroot\foo.htm"); + testSite.AspNetCoreApp.CreateFile(new string[] { "barhtm" }, @"wwwroot\pdir\bar.htm"); + testSite.AspNetCoreApp.CreateFile(new string[] { "defaulthtm" }, @"wwwroot\default.htm"); + + const int retryCount = 3; + string headerValue = string.Empty; + string headerValue2 = string.Empty; + SendReceiveContext result = null; + for (int i = 0; i < retryCount; i++) + { + result = await SendReceive(testSite.AspNetCoreApp.GetUri("foo.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + headerValue = GetHeaderValue(result.ResponseHeader, "MyCustomHeader"); + Assert.True(result.ResponseBody.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result.ResponseHeader, "Content-Encoding")); + Thread.Sleep(1500); + + result = await SendReceive(testSite.AspNetCoreApp.GetUri("foo.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + headerValue2 = GetHeaderValue(result.ResponseHeader, "MyCustomHeader"); + Assert.True(result.ResponseBody.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result.ResponseHeader, "Content-Encoding")); + if (headerValue == headerValue2) + { + break; + } + } + Assert.Equal(headerValue, headerValue2); + + Thread.Sleep(12000); + result = await SendReceive(testSite.AspNetCoreApp.GetUri("foo.htm"), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result.ResponseHeader, "Content-Encoding")); + string headerValue3 = GetHeaderValue(result.ResponseHeader, "MyCustomHeader"); + Assert.NotEqual(headerValue2, headerValue3); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoSendHTTPSRequestTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest", startIISExpress: false)) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string hostName = ""; + string subjectName = "localhost"; + string ipAddress = "*"; + string hexIPAddress = "0x00"; + int sslPort = InitializeTestMachine.SiteId + 6300; + + // Add https binding and get https uri information + iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); + + // Create a self signed certificate + string thumbPrint = iisConfig.CreateSelfSignedCertificate(subjectName); + + // Export the self signed certificate to rootCA + iisConfig.ExportCertificateTo(thumbPrint, sslStoreTo: @"Cert:\LocalMachine\Root"); + + // Configure http.sys ssl certificate mapping to IP:Port endpoint with the newly created self signed certificage + iisConfig.SetSSLCertificate(sslPort, hexIPAddress, thumbPrint); + + // starting IISExpress was deffered after creating test applications and now it is ready to start it + testSite.StartIISExpress(); + + // Verify http request + var result = await SendReceive(testSite.AspNetCoreApp.GetUri(), requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("Running"), "verify response body"); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + result = await SendReceive(targetHttpsUri, requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("Running"), "verify response body"); + + // Remove the SSL Certificate mapping + iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); + + // Remove the newly created self signed certificate + iisConfig.DeleteCertificate(thumbPrint); + + // Remove the exported self signed certificate on rootCA + iisConfig.DeleteCertificate(thumbPrint, @"Cert:\LocalMachine\Root"); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoFilterOutMSRequestHeadersTest(IISConfigUtility.AppPoolBitness appPoolBitness, string requestHeader, string requestHeaderValue) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest", startIISExpress: false)) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string hostName = ""; + string subjectName = "localhost"; + string ipAddress = "*"; + string hexIPAddress = "0x00"; + int sslPort = InitializeTestMachine.SiteId + 6300; + + // Add https binding and get https uri information + iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); + + // Create a self signed certificate + string thumbPrint = iisConfig.CreateSelfSignedCertificate(subjectName); + + // Export the self signed certificate to rootCA + iisConfig.ExportCertificateTo(thumbPrint, sslStoreTo: @"Cert:\LocalMachine\Root"); + + // Configure http.sys ssl certificate mapping to IP:Port endpoint with the newly created self signed certificage + iisConfig.SetSSLCertificate(sslPort, hexIPAddress, thumbPrint); + + // starting IISExpress was deffered after creating test applications and now it is ready to start it + testSite.StartIISExpress(); + + // Verify http request + var result = await SendReceive(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), requestHeaders: new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }); + string requestHeaders = result.ResponseHeader.Replace(" ", ""); + Assert.DoesNotContain(requestHeader + ":", requestHeaders, StringComparison.InvariantCultureIgnoreCase); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + result = await SendReceive(targetHttpsUri, requestHeaders: new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }); + requestHeaders = result.ResponseHeader.Replace(" ", ""); + Assert.DoesNotContain(requestHeader + ":", requestHeaders, StringComparison.InvariantCultureIgnoreCase); + + // Remove the SSL Certificate mapping + iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); + + // Remove the newly created self signed certificate + iisConfig.DeleteCertificate(thumbPrint); + + // Remove the exported self signed certificate on rootCA + iisConfig.DeleteCertificate(thumbPrint, @"Cert:\LocalMachine\Root"); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoClientCertificateMappingTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useHTTPSMiddleWare) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoClientCertificateMappingTest", startIISExpress: false)) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string hostName = ""; + string rootCN = "ANCMTest" + testSite.PostFix; + string webServerCN = "localhost"; + string kestrelServerCN = "localhost"; + string clientCN = "ANCMClient-" + testSite.PostFix; + + string ipAddress = "*"; + string hexIPAddress = "0x00"; + int sslPort = InitializeTestMachine.SiteId + 6300; + + // Add https binding and get https uri information + iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); + + // Create a root certificate + string thumbPrintForRoot = iisConfig.CreateSelfSignedCertificateWithMakeCert(rootCN); + + // Create a certificate for web server setting its issuer with the root certificate subject name + string thumbPrintForWebServer = iisConfig.CreateSelfSignedCertificateWithMakeCert(webServerCN, rootCN, extendedKeyUsage: "1.3.6.1.5.5.7.3.1"); + string thumbPrintForKestrel = null; + + // Create a certificate for client authentication setting its issuer with the root certificate subject name + string thumbPrintForClientAuthentication = iisConfig.CreateSelfSignedCertificateWithMakeCert(clientCN, rootCN, extendedKeyUsage: "1.3.6.1.5.5.7.3.2"); + + // Configure http.sys ssl certificate mapping to IP:Port endpoint with the newly created self signed certificage + iisConfig.SetSSLCertificate(sslPort, hexIPAddress, thumbPrintForWebServer); + + // Create a new local administrator user + string userName = "tempuser" + TestUtility.RandomString(5); + string password = "AncmTest123!"; + string temp; + temp = TestUtility.RunPowershellScript("net localgroup IIS_IUSRS /Delete " + userName); + temp = TestUtility.RunPowershellScript("net user " + userName + " /Delete"); + temp = TestUtility.RunPowershellScript("net user " + userName + " " + password + " /ADD"); + temp = TestUtility.RunPowershellScript("net localgroup IIS_IUSRS /Add " + userName); + + // Get public key of the client certificate and Configure OnetToOneClientCertificateMapping the public key and disable anonymous authentication and set SSL flags for Client certificate authentication + string publicKey = iisConfig.GetCertificatePublicKey(thumbPrintForClientAuthentication, @"Cert:\CurrentUser\My"); + + bool setPasswordSeperately = false; + if (testSite.IisServerType == ServerType.IISExpress) + { + setPasswordSeperately = true; + iisConfig.EnableOneToOneClientCertificateMapping(testSite.SiteName, ".\\" + userName, null, publicKey); + } + else + { + iisConfig.EnableOneToOneClientCertificateMapping(testSite.SiteName, ".\\" + userName, password, publicKey); + } + + // IISExpress uses a differnt encryption from full IIS version's and it is not easy to override the encryption methong with MWA. + // As a work-around, password is set with updating the config file directly. + if (setPasswordSeperately) + { + // Search userName property and replace it with userName + password + string text = File.ReadAllText(testSite.IisExpressConfigPath); + text = text.Replace(userName + "\"", userName + "\"" + " " + "password=" + "\"" + password + "\""); + File.WriteAllText(testSite.IisExpressConfigPath, text); + } + + // Configure kestrel SSL test environment + if (useHTTPSMiddleWare) + { + // set startup class + string startupClass = "StartupHTTPS"; + iisConfig.SetANCMConfig( + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", + new string[] { "ANCMTestStartupClassName", startupClass } + ); + + // Create a certificate for Kestrel web server and export to TestResources\testcert.pfx + // NOTE: directory name "TestResources", file name "testcert.pfx" and password "testPassword" should be matched to AspnetCoreModule.TestSites.Standard web application + thumbPrintForKestrel = iisConfig.CreateSelfSignedCertificateWithMakeCert(kestrelServerCN, rootCN, extendedKeyUsage: "1.3.6.1.5.5.7.3.1"); + testSite.AspNetCoreApp.CreateDirectory("TestResources"); + string pfxFilePath = Path.Combine(testSite.AspNetCoreApp.GetDirectoryPathWith("TestResources"), "testcert.pfx"); + iisConfig.ExportCertificateTo(thumbPrintForKestrel, sslStoreFrom: "Cert:\\LocalMachine\\My", sslStoreTo: pfxFilePath, pfxPassword: "testPassword"); + Assert.True(File.Exists(pfxFilePath)); + } + + // starting IISExpress was deffered after creating test applications and now it is ready to start it + Uri rootHttpsUri = testSite.RootAppContext.GetUri(null, sslPort, protocol: "https"); + testSite.StartIISExpress(); + TestUtility.RunPowershellScript("( invoke-webrequest " + rootHttpsUri.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").StatusCode", "200"); + + // Verify http request with using client certificate + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + string statusCode = TestUtility.RunPowershellScript("( invoke-webrequest " + targetHttpsUri.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").StatusCode"); + Assert.Equal("200", statusCode); + + // Verify https request with client certificate includes the certificate header "MS-ASPNETCORE-CLIENTCERT" + Uri targetHttpsUriForDumpRequestHeaders = testSite.AspNetCoreApp.GetUri("DumpRequestHeaders", sslPort, protocol: "https"); + string outputRawContent = TestUtility.RunPowershellScript("( invoke-webrequest " + targetHttpsUriForDumpRequestHeaders.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").RawContent.ToString()"); + + Assert.Contains("MS-ASPNETCORE-CLIENTCERT", outputRawContent); + + // Get the value of MS-ASPNETCORE-CLIENTCERT request header again and verify it is matched to its configured public key + Uri targetHttpsUriForCLIENTCERTRequestHeader = testSite.AspNetCoreApp.GetUri("GetRequestHeaderValueMS-ASPNETCORE-CLIENTCERT", sslPort, protocol: "https"); + outputRawContent = TestUtility.RunPowershellScript("( invoke-webrequest " + targetHttpsUriForCLIENTCERTRequestHeader.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").RawContent.ToString()"); + Assert.Contains(publicKey, outputRawContent); + + // Verify non-https request returns 403.4 error + var result = await SendReceive(testSite.AspNetCoreApp.GetUri(), requestHeaders: new string[] { "Accept-Encoding", "gzip" }, expectedResponseStatus: HttpStatusCode.Forbidden); + Assert.Contains("403.4", result.ResponseBody); + + // Verify https request without using client certificate returns 403.7 + result = await SendReceive(targetHttpsUri, requestHeaders: new string[] { "Accept-Encoding", "gzip" }, expectedResponseStatus: HttpStatusCode.Forbidden); + Assert.Contains("403.7", result.ResponseBody); + + // Clean up user + temp = TestUtility.RunPowershellScript("net localgroup IIS_IUSRS /Delete " + userName); + temp = TestUtility.RunPowershellScript("net user " + userName + " /Delete"); + + // Remove the SSL Certificate mapping + iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); + + // Clean up certificates + iisConfig.DeleteCertificate(thumbPrintForRoot, @"Cert:\LocalMachine\Root"); + iisConfig.DeleteCertificate(thumbPrintForWebServer, @"Cert:\LocalMachine\My"); + if (useHTTPSMiddleWare) + { + iisConfig.DeleteCertificate(thumbPrintForKestrel, @"Cert:\LocalMachine\My"); + } + iisConfig.DeleteCertificate(thumbPrintForClientAuthentication, @"Cert:\CurrentUser\My"); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoWebSocketTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoWebSocketTest")) + { + string appDllFileName = testSite.AspNetCoreApp.GetArgumentFileName(); + + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", 10); + } + + DateTime startTime = DateTime.Now; + + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + // Get Process ID + string backendProcessId_old = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + + // Verify WebSocket without setting subprotocol + await SendReceive(testSite.WebSocketApp.GetUri("echo.aspx"), expectedStringsInResponseBody: new string[] { "Socket Open" }); // echo.aspx has hard coded path for the websocket server + + // Verify WebSocket subprotocol + await SendReceive(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), expectedStringsInResponseBody: new string[] { "Socket Open", "mywebsocketsubprotocol" }); // echoSubProtocol.aspx has hard coded path for the websocket server + + // Verify websocket + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + Thread.Sleep(500); + + frameReturned = websocketClient.Close(); + Thread.Sleep(500); + + Assert.True(frameReturned.FrameType == FrameType.Close, "Closing Handshake"); + } + + // send a simple request and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + Thread.Sleep(500); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.Equal(backendProcessId_old, backendProcessId); + + // Verify server side websocket disconnection + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + for (int jj = 0; jj < 3; jj++) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + Assert.True(websocketClient.IsOpened, "Check active connection before starting"); + + // Send a special string to initiate the server side connection closing + websocketClient.SendTextData("CloseFromServer"); + bool connectionClosedFromServer = websocketClient.WaitForWebSocketState(WebSocketState.ConnectionClosed); + + // Verify server side connection closing is done successfully + Assert.True(connectionClosedFromServer, "Closing Handshake initiated from Server"); + + // extract text data from the last frame, which is the close frame + int lastIndex = websocketClient.Connection.DataReceived.Count - 1; + + // Verify text data is matched to the string sent by server + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + } + } + + // send a simple request and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + Thread.Sleep(500); + backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.Equal(backendProcessId_old, backendProcessId); + + // Verify websocket with app_offline.htm + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + for (int jj = 0; jj < 3; jj++) + { + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + Thread.Sleep(1000); + + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + Thread.Sleep(500); + + // put app_offline + testSite.AspNetCoreApp.CreateFile(new string[] { "test" }, "App_Offline.Htm"); + Thread.Sleep(1000); + + // ToDo: This should be replaced when the server can handle this automaticially + // send a websocket data to invoke the server side websocket disconnection after the app_offline + websocketClient.SendTextData("test"); + bool connectionClosedFromServer = websocketClient.WaitForWebSocketState(WebSocketState.ConnectionClosed); + + // Verify server side connection closing is done successfully + Assert.True(connectionClosedFromServer, "Closing Handshake initiated from Server"); + + // extract text data from the last frame, which is the close frame + int lastIndex = websocketClient.Connection.DataReceived.Count - 1; + + // Verify text data is matched to the string sent by server + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + + // Verify the application file can be removed under app_offline mode + testSite.AspNetCoreApp.BackupFile(appDllFileName); + testSite.AspNetCoreApp.DeleteFile(appDllFileName); + testSite.AspNetCoreApp.RestoreFile(appDllFileName); + } + } + + // remove app_offline.htm + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + Thread.Sleep(1000); + + /* + BugBug!!! configuration change does not invoke the shutdown message + because IIS does not trigger the change notification event until all websocket connection is gone. + This scenario should be added back when the issue is resolved. + + // Verify websocket with configuration change notification + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + for (int jj = 0; jj < 10; jj++) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + Thread.Sleep(500); + + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "startupTimeLimit", 11 + jj); + Thread.Sleep(1000); + } + + // ToDo: This should be replaced when the server can handle this automaticially + // send a websocket data to invoke the server side websocket disconnection after the app_offline + websocketClient.SendTextData("test"); + bool connectionClosedFromServer = websocketClient.WaitForWebSocketState(WebSocketState.ConnectionClosed); + + // Verify server side connection closing is done successfully + Assert.True(connectionClosedFromServer, "Closing Handshake initiated from Server"); + + // extract text data from the last frame, which is the close frame + int lastIndex = websocketClient.Connection.DataReceived.Count - 1; + + // Verify text data is matched to the string sent by server + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + } + } + */ + + // send a simple request and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + } + } + + public static async Task DoWebSocketErrorhandlingTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + try + { + using (var testSite = new TestWebSite(appPoolBitness, "DoWebSocketErrorhandlingTest")) + { + // Verify websocket returns 404 when websocket module is not registered + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + // Remove websocketModule + IISConfigUtility.BackupAppHostConfig("DoWebSocketErrorhandlingTest", true); + iisConfig.RemoveModule("WebSocketModule"); + Thread.Sleep(3000); + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true, waitForConnectionOpen:false); + Assert.DoesNotContain("Connection: Upgrade", frameReturned.Content, StringComparison.InvariantCultureIgnoreCase); + + //BugBug: Currently we returns 101 here. + //Assert.DoesNotContain("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + } + } + + // send a simple request again and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + // roback configuration + IISConfigUtility.RestoreAppHostConfig("DoWebSocketErrorhandlingTest", true); + } + } + catch + { + // roback configuration + IISConfigUtility.RestoreAppHostConfig("DoWebSocketErrorhandlingTest", true); + throw; + } + } + + public enum DoAppVerifierTest_ShutDownMode + { + RecycleAppPool, + CreateAppOfflineHtm, + StopAndStartAppPool, + RestartW3SVC, + ConfigurationChangeNotification + } + + public enum DoAppVerifierTest_StartUpMode + { + UseGracefulShutdown, + DontUseGracefulShutdown + } + + public static async Task DoAppVerifierTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool verifyTimeout, DoAppVerifierTest_StartUpMode startUpMode, DoAppVerifierTest_ShutDownMode shutDownMode, int repeatCount = 2) + { + TestWebSite testSite = null; + bool testResult = false; + + testSite = new TestWebSite(appPoolBitness, "DoAppVerifierTest", startIISExpress: false); + if (testSite.IisServerType == ServerType.IISExpress) + { + TestUtility.LogInformation("This test is not valid for IISExpress server type because of IISExpress bug; Once it is resolved, we should activate this test for IISExpress as well"); + return; + } + + // enable AppVerifier + testSite.AttachAppverifier(); + + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + // Prepare https binding + string hostName = ""; + string subjectName = "localhost"; + string ipAddress = "*"; + string hexIPAddress = "0x00"; + int sslPort = InitializeTestMachine.SiteId + 6300; + + // Add https binding and get https uri information + iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); + + // Create a self signed certificate + string thumbPrint = iisConfig.CreateSelfSignedCertificate(subjectName); + + // Export the self signed certificate to rootCA + iisConfig.ExportCertificateTo(thumbPrint, sslStoreTo: @"Cert:\LocalMachine\Root"); + + // Configure http.sys ssl certificate mapping to IP:Port endpoint with the newly created self signed certificage + iisConfig.SetSSLCertificate(sslPort, hexIPAddress, thumbPrint); + + // Set shutdownTimeLimit with 3 seconds and use 5 seconds for delay time to make the shutdownTimeout happen + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", 3); + + int timeoutValue = 3; + if (verifyTimeout) + { + // set requestTimeout + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse("00:01:00")); // 1 minute + + // set startupTimeout + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "startupTimeLimit", timeoutValue); + + // Set shutdownTimeLimit with 3 seconds and use 5 seconds for delay time to make the shutdownTimeout happen + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", timeoutValue); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestShutdownDelay", "10" }); + } + + // starting IISExpress was deffered after creating test applications and now it is ready to start. + testSite.StartIISExpress(); + + if (verifyTimeout) + { + Thread.Sleep(500); + + // initial request which requires more than startup timeout should fails + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep5000"), HttpStatusCode.BadGateway, timeout: 10); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep5000"), expectedResponseBody: "Running", timeout: 10); + + // request which requires more than request timeout should fails + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, timeout: 70); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep50000"), expectedResponseBody: "Running", timeout: 70); + } + + /////////////////////////////////// + // Start test sceanrio + /////////////////////////////////// + if (startUpMode == DoAppVerifierTest_StartUpMode.DontUseGracefulShutdown) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "GracefulShutdown", "disabled" }); + } + + // reset existing worker process process + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + + for (int i = 0; i < repeatCount; i++) + { + // reset worker process id to refresh + testSite.WorkerProcessID = 0; + + // send a startup request to start a new worker process + TestUtility.RunPowershellScript("( invoke-webrequest http://localhost:" + testSite.TcpPort + " ).StatusCode", "200", retryCount: 5); + + // attach debugger to the worker process + testSite.AttachWinDbg(testSite.WorkerProcessID); + TestUtility.RunPowershellScript("( invoke-webrequest http://localhost:" + testSite.TcpPort + " ).StatusCode", "200", retryCount: 30); + + // verify windbg process is started + TestUtility.RunPowershellScript("(get-process -name windbg 2> $null).count", "1", retryCount: 5); + + DateTime startTime = DateTime.Now; + + // Verify http request + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + // Get Process ID + string backendProcessId_old = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + + // Verify WebSocket without setting subprotocol + await SendReceive(testSite.WebSocketApp.GetUri("echo.aspx"), expectedStringsInResponseBody: new string[] { "Socket Open" }); // echo.aspx has hard coded path for the websocket server + + // Verify WebSocket subprotocol + await SendReceive(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), expectedStringsInResponseBody: new string[] { "Socket Open", "mywebsocketsubprotocol" }); // echoSubProtocol.aspx has hard coded path for the websocket server + + string testData = "test"; + + // Verify websocket + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + Thread.Sleep(500); + + frameReturned = websocketClient.Close(); + Thread.Sleep(500); + + Assert.True(frameReturned.FrameType == FrameType.Close, "Closing Handshake"); + } + + // send a simple request and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + Thread.Sleep(500); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.Equal(backendProcessId_old, backendProcessId); + + // Verify server side websocket disconnection + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + Assert.True(websocketClient.IsOpened, "Check active connection before starting"); + + // Send a special string to initiate the server side connection closing + websocketClient.SendTextData("CloseFromServer"); + bool connectionClosedFromServer = websocketClient.WaitForWebSocketState(WebSocketState.ConnectionClosed); + + // Verify server side connection closing is done successfully + Assert.True(connectionClosedFromServer, "Closing Handshake initiated from Server"); + + // extract text data from the last frame, which is the close frame + int lastIndex = websocketClient.Connection.DataReceived.Count - 1; + + // Verify text data is matched to the string sent by server + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + } + + // send a simple request and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + Thread.Sleep(500); + backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; + Assert.Equal(backendProcessId_old, backendProcessId); + + if (startUpMode != DoAppVerifierTest_StartUpMode.DontUseGracefulShutdown) + { + // Verify websocket with app_offline.htm + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + for (int jj = 0; jj < 10; jj++) + { + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + Thread.Sleep(1000); + + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + Thread.Sleep(500); + + // put app_offline + testSite.AspNetCoreApp.CreateFile(new string[] { "test" }, "App_Offline.Htm"); + Thread.Sleep(500); + + // ToDo: remove this when server can handle this automatically + // send a websocket data to invoke the server side websocket disconnection after the app_offline + websocketClient.SendTextData("test"); + bool connectionClosedFromServer = websocketClient.WaitForWebSocketState(WebSocketState.ConnectionClosed); + + // Verify server side connection closing is done successfully + Assert.True(connectionClosedFromServer, "Closing Handshake initiated from Server"); + } + } + + // remove app_offline.htm + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + Thread.Sleep(500); + } + + // Verify websocket again + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + Thread.Sleep(500); + + frameReturned = websocketClient.Close(); + Thread.Sleep(500); + + Assert.True(frameReturned.FrameType == FrameType.Close, "Closing Handshake"); + } + + // send a simple request and verify the response body + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + var result = await SendReceive(targetHttpsUri, requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("Running"), "verify response body"); + + switch (shutDownMode) + { + case DoAppVerifierTest_ShutDownMode.StopAndStartAppPool: + iisConfig.StopAppPool(testSite.AspNetCoreApp.AppPoolName); + Thread.Sleep(5000); + iisConfig.StartAppPool(testSite.AspNetCoreApp.AppPoolName); + break; + case DoAppVerifierTest_ShutDownMode.RestartW3SVC: + TestUtility.ResetHelper(ResetHelperMode.StopWasStartW3svc); + break; + case DoAppVerifierTest_ShutDownMode.CreateAppOfflineHtm: + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + testSite.AspNetCoreApp.CreateFile(new string[] { "test" }, "App_Offline.Htm"); + break; + case DoAppVerifierTest_ShutDownMode.ConfigurationChangeNotification: + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "startupTimeLimit", timeoutValue + 1); + iisConfig.RecycleAppPool(testSite.AspNetCoreApp.AppPoolName); + break; + case DoAppVerifierTest_ShutDownMode.RecycleAppPool: + iisConfig.RecycleAppPool(testSite.AspNetCoreApp.AppPoolName); + break; + } + Thread.Sleep(2000); + + if (verifyTimeout) + { + // Wait for shutdown delay additionally + Thread.Sleep(timeoutValue * 1000); + } + + switch (shutDownMode) + { + case DoAppVerifierTest_ShutDownMode.CreateAppOfflineHtm: + // verify app_offline.htm file works + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "test" + "\r\n", expectedResponseStatus: HttpStatusCode.ServiceUnavailable); + + // remove app_offline.htm file and then recycle apppool + testSite.AspNetCoreApp.MoveFile("App_Offline.Htm", "_App_Offline.Htm"); + iisConfig.RecycleAppPool(testSite.AspNetCoreApp.AppPoolName); + Thread.Sleep(2000); + break; + } + + // verify windbg process is gone, which means there was no unexpected error + TestUtility.RunPowershellScript("(get-process -name windbg 2> $null).count", "0", retryCount: 5); + } + + // clean up https test environment + + // Remove the SSL Certificate mapping + iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); + + // Remove the newly created self signed certificate + iisConfig.DeleteCertificate(thumbPrint); + + // Remove the exported self signed certificate on rootCA + iisConfig.DeleteCertificate(thumbPrint, @"Cert:\LocalMachine\Root"); + } + + // cleanup + if (testSite != null) + { + testSite.DetachAppverifier(); + } + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + + // cleanup windbg process incase it is still running + if (!testResult) + { + TestUtility.RunPowershellScript("stop-process -Name windbg -Force -Confirm:$false 2> $null"); + } + } + + private static string GetHeaderValue(string inputData, string headerName) + { + string result = string.Empty; + foreach (string item in inputData.Split(new char[] { ',', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (item.Contains(headerName)) + { + var tokens = item.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + if (tokens.Length == 2) + { + result = tokens[1].Trim(); + break; + } + } + } + return result; + } + + private static bool VerifySendingWebSocketData(WebSocketClientHelper websocketClient, string testData) + { + bool result = false; + + // + // send complete or partial text data and ping multiple times + // + websocketClient.SendTextData(testData); + websocketClient.SendPing(); + websocketClient.SendTextData(testData); + websocketClient.SendPing(); + websocketClient.SendPing(); + websocketClient.SendTextData(testData, 0x01); // 0x01: start of sending partial data + websocketClient.SendPing(); + websocketClient.SendTextData(testData, 0x80); // 0x80: end of sending partial data + websocketClient.SendPing(); + websocketClient.SendPing(); + websocketClient.SendTextData(testData); + websocketClient.SendTextData(testData); + websocketClient.SendTextData(testData); + websocketClient.SendPing(); + Thread.Sleep(3000); + + // Verify test result + for (int i = 0; i < 3; i++) + { + if (!DoVerifyDataSentAndReceived(websocketClient)) + { + // retrying after 1 second sleeping + Thread.Sleep(1000); + } + else + { + result = true; + break; + } + } + return result; + } + + private static bool DoVerifyDataSentAndReceived(WebSocketClientHelper websocketClient) + { + var result = true; + var sentString = new StringBuilder(); + var recString = new StringBuilder(); + var pingString = new StringBuilder(); + var pongString = new StringBuilder(); + + foreach (Frame frame in websocketClient.Connection.DataSent.ToArray()) + { + if (frame.FrameType == FrameType.Continuation + || frame.FrameType == FrameType.SegmentedText + || frame.FrameType == FrameType.Text + || frame.FrameType == FrameType.ContinuationFrameEnd) + { + sentString.Append(frame.Content); + } + + if (frame.FrameType == FrameType.Ping) + { + pingString.Append(frame.Content); + } + } + + foreach (Frame frame in websocketClient.Connection.DataReceived.ToArray()) + { + if (frame.FrameType == FrameType.Continuation + || frame.FrameType == FrameType.SegmentedText + || frame.FrameType == FrameType.Text + || frame.FrameType == FrameType.ContinuationFrameEnd) + { + recString.Append(frame.Content); + } + + if (frame.FrameType == FrameType.Pong) + { + pongString.Append(frame.Content); + } + } + + if (sentString.Length == recString.Length && pongString.Length == pingString.Length) + { + if (sentString.Length != recString.Length) + { + result = false; + TestUtility.LogInformation("Same size of data sent(" + sentString.Length + ") and received(" + recString.Length + ")"); + } + + if (sentString.ToString() != recString.ToString()) + { + result = false; + TestUtility.LogInformation("Not matched string in sent and received"); + } + if (pongString.Length != pingString.Length) + { + result = false; + TestUtility.LogInformation("Ping received; Ping (" + pingString.Length + ") and Pong (" + pongString.Length + ")"); + } + websocketClient.Connection.DataSent.Clear(); + websocketClient.Connection.DataReceived.Clear(); + } + else + { + TestUtility.LogInformation("Retrying... so far data sent(" + sentString.Length + ") and received(" + recString.Length + ")"); + result = false; + } + return result; + } + + private static async Task CheckChunkedAsync(HttpClient client, TestWebApplication webApp) + { + var response = await client.GetAsync(webApp.GetUri("chunked")); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Chunked", responseText); + Assert.True(response.Headers.TransferEncodingChunked, "/chunked, chunked?"); + Assert.Null(response.Headers.ConnectionClose); + Assert.Null(GetContentLength(response)); + } + catch (XunitException ex) + { + TestUtility.LogInformation(response.ToString()); + TestUtility.LogInformation(responseText); + throw ex; + } + } + + private static string GetContentLength(HttpResponseMessage response) + { + // Don't use response.Content.Headers.ContentLength, it will dynamically calculate the value if it can. + IEnumerable values; + return response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out values) ? values.FirstOrDefault() : null; + } + + private static bool VerifyANCMStartEvent(DateTime startFrom, string includeThis) + { + return VerifyEventLog(1001, startFrom, includeThis); + } + + private static bool VerifyANCMGracefulShutdownEvent(DateTime startFrom, string includeThis) + { + return VerifyEventLog(1006, startFrom, includeThis); + } + + private static bool VerifyANCMGracefulShutdownFailureEvent(DateTime startFrom, string includeThis) + { + return VerifyEventLog(1005, startFrom, includeThis); + } + + private static bool VerifyApplicationEventLog(int eventID, DateTime startFrom, string includeThis) + { + return VerifyEventLog(eventID, startFrom, includeThis); + } + + private static bool VerifyEventLog(int eventId, DateTime startFrom, string includeThis = null) + { + var events = TestUtility.GetApplicationEvent(eventId, startFrom); + Assert.True(events.Count > 0, "Verfiy expected event logs"); + bool findEvent = false; + foreach (string item in events) + { + if (item.Contains(includeThis)) + { + findEvent = true; + break; + } + } + return findEvent; + } + + public class SendReceiveContext : IDisposable + { + public Uri Uri = null; + public string[] RequestHeaders = null; + public string ExpectedResponseBody = null; + public string[] ExpectedStringsInResponseBody = null; + public HttpStatusCode ExpectedResponseStatus; + public int NumberOfRetryCount = 2; + public bool VerifyResponseFlag = true; + public KeyValuePair[] PostData = null; + public int Timeout = 5; // second + + // output variables + public string ResponseBody = null; + public string ResponseHeader = null; + public string ResponseStatus = null; + + public string ResponseHeaderBody + { + get { return ResponseBody + ", " + ResponseHeader; } + } + + public void Dispose() + { + } + } + + private static async Task SendReceive(Uri uri, HttpStatusCode expectedResponseStatus = HttpStatusCode.OK, string[] requestHeaders = null, string expectedResponseBody = null, string[] expectedStringsInResponseBody = null, int timeout = 5, int numberOfRetryCount = 1, bool verifyResponseFlag = true, KeyValuePair[] postData = null) + { + using (SendReceiveContext context = new SendReceiveContext()) + { + context.Uri = uri; + context.RequestHeaders = requestHeaders; + context.ExpectedResponseBody = expectedResponseBody; + context.ExpectedStringsInResponseBody = expectedStringsInResponseBody; + context.ExpectedResponseStatus = expectedResponseStatus; + context.NumberOfRetryCount = numberOfRetryCount; + context.VerifyResponseFlag = verifyResponseFlag; + context.PostData = postData; + context.Timeout = timeout; + return await SendReceive(context); + } + } + + private static async Task ReadContent(HttpResponseMessage response) + { + bool unZipContent = false; + string result = String.Empty; + + IEnumerable values; + if (response.Headers.TryGetValues("Vary", out values)) + { + unZipContent = true; + } + + if (unZipContent) + { + var inputStream = await response.Content.ReadAsStreamAsync(); + + // for debugging purpose + //byte[] temp = new byte[inputStream.Length]; + //inputStream.Read(temp, 0, (int) inputStream.Length); + //inputStream.Position = 0; + + using (var gzip = new GZipStream(inputStream, CompressionMode.Decompress)) + { + var outputStream = new MemoryStream(); + try + { + await gzip.CopyToAsync(outputStream); + } + catch (Exception ex) + { + // Even though "Vary" response header exists, the content is not actually compressed. + // We should ignore this execption until we find a proper way to determine if the body is compressed or not. + if (ex.Message.IndexOf("gzip", StringComparison.InvariantCultureIgnoreCase) >= 0) + { + result = await response.Content.ReadAsStringAsync(); + return result; + } + throw ex; + } + gzip.Close(); + inputStream.Close(); + outputStream.Position = 0; + using (StreamReader reader = new StreamReader(outputStream, Encoding.UTF8)) + { + result = reader.ReadToEnd(); + outputStream.Close(); + } + } + } + else + { + result = await response.Content.ReadAsStringAsync(); + } + return result; + } + + private static async Task SendReceive(SendReceiveContext context) + { + Uri uri = context.Uri; + string[] requestHeaders = context.RequestHeaders; + string expectedResponseBody = context.ExpectedResponseBody; + string[] expectedStringsInResponseBody = context.ExpectedStringsInResponseBody; + HttpStatusCode expectedResponseStatus = context.ExpectedResponseStatus; + int numberOfRetryCount = context.NumberOfRetryCount; + bool verifyResponseFlag = context.VerifyResponseFlag; + KeyValuePair[] postData = context.PostData; + int timeout = context.Timeout; + + string responseText = "NotInitialized"; + string responseStatus = "NotInitialized"; + + var httpClientHandler = new HttpClientHandler(); + httpClientHandler.UseDefaultCredentials = true; + httpClientHandler.AutomaticDecompression = DecompressionMethods.None; + + var httpClient = new HttpClient(httpClientHandler) + { + BaseAddress = uri, + Timeout = TimeSpan.FromSeconds(timeout), + }; + + if (requestHeaders != null) + { + for (int i = 0; i < requestHeaders.Length; i=i+2) + { + httpClient.DefaultRequestHeaders.Add(requestHeaders[i], requestHeaders[i+1]); + } + } + + HttpResponseMessage response = null; + try + { + FormUrlEncodedContent postHttpContent = null; + if (postData != null) + { + postHttpContent = new FormUrlEncodedContent(postData); + } + + if (numberOfRetryCount > 1 && expectedResponseStatus == HttpStatusCode.OK) + { + if (postData == null) + { + response = await TestUtility.RetryRequest(() => + { + return httpClient.GetAsync(string.Empty); + }, TestUtility.Logger, retryCount: numberOfRetryCount); + } + else + { + response = await TestUtility.RetryRequest(() => + { + return httpClient.PostAsync(string.Empty, postHttpContent); + }, TestUtility.Logger, retryCount: numberOfRetryCount); + } + } + else + { + if (postData == null) + { + response = await httpClient.GetAsync(string.Empty); + } + else + { + response = await httpClient.PostAsync(string.Empty, postHttpContent); + } + } + + if (response != null) + { + responseStatus = response.StatusCode.ToString(); + if (verifyResponseFlag) + { + if (expectedResponseBody != null) + { + if (responseText == "NotInitialized") + { + responseText = await ReadContent(response); + } + Assert.Equal(expectedResponseBody, responseText); + } + + if (expectedStringsInResponseBody != null) + { + if (responseText == "NotInitialized") + { + responseText = await ReadContent(response); + } + foreach (string item in expectedStringsInResponseBody) + { + Assert.Contains(item, responseText); + } + } + Assert.Equal(expectedResponseStatus, response.StatusCode); + } + + if (responseText == "NotInitialized") + { + responseText = await ReadContent(response); + } + context.ResponseBody = responseText; + context.ResponseHeader = response.ToString(); + context.ResponseStatus = response.StatusCode.ToString(); + } + } + catch (XunitException) + { + if (response != null) + { + TestUtility.LogInformation(response.ToString()); + } + TestUtility.LogInformation(responseText); + TestUtility.LogInformation(responseStatus); + } + return context; + } + } + +} diff --git a/test/AspNetCoreModule.Test/Http.config b/test/AspNetCoreModule.Test/Http.config new file mode 100644 index 0000000000..1518e1800f --- /dev/null +++ b/test/AspNetCoreModule.Test/Http.config @@ -0,0 +1,1032 @@ + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs b/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs new file mode 100644 index 0000000000..2266931c5b --- /dev/null +++ b/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs @@ -0,0 +1,336 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.Framework; +using System; +using System.Net; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Net.Security; +using System.Threading; +using System.Net.Sockets; + +namespace AspNetCoreModule.Test.HttpClientHelper +{ + public class HttpClientHelper + { + private IPHostEntry _host = Dns.GetHostEntry(Dns.GetHostName()); + private string _ipv4Loopback = "127.0.0.1"; + private string _ipv4One = null; + private string _ipv4Two = null; + private string _ipv6Loopback = "[::1]"; + private string _ipv6One = null; + private string _ipv6Two = null; + + public HttpClientHelper() + { + ReadMachineIpAddressInfo(); + + _Ips = new string[] { _ipv4Loopback, _ipv4One, _ipv6Loopback, _ipv6One, _ipv6Two }; + + _Hosts = new string[] { "foo", "bar", "foobar", "barfoo" }; + + _unusedIp = _ipv6Two; + } + + // callback used to validate the certificate in an SSL conversation + public static bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) + { + return true; + } + + public static int sendRequest(string uri, string hostName, string expectedCN, bool useLegacy, bool displayContent, bool doLogging = true) + { + int status = -1; + + if (doLogging) + { + if (hostName == null) + TestUtility.LogInformation(String.Format("HttpClient::sendRequest() {0} with no hostname", uri)); + else + TestUtility.LogInformation(String.Format("HttpClient::sendRequest() {0} with hostname {1}", uri, hostName)); + } + + ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateRemoteCertificate); + + if (useLegacy) + { + TestUtility.LogInformation(String.Format("Using SSL3")); + ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; + } + + HttpWebRequest myRequest; + myRequest = (HttpWebRequest)WebRequest.Create(uri); + myRequest.Proxy = null; + myRequest.KeepAlive = false; + if (hostName != null) + myRequest.Host = hostName; + + ServicePoint point = myRequest.ServicePoint; + point.ConnectionLeaseTimeout = 0; + + try + { + using (HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse()) + { + using (Stream myStream = myResponse.GetResponseStream()) + { + if (displayContent) + { + using (StreamReader myReader = new StreamReader(myStream)) + { + string text = myReader.ReadToEnd(); + TestUtility.LogInformation("\n\n"); + TestUtility.LogInformation(text); + TestUtility.LogInformation("\n\n"); + } + } + } + status = (int)myResponse.StatusCode; + } + } + catch (WebException ex) + { + if ((HttpWebResponse)ex.Response == null) + status = 0; + else + status = (int)((HttpWebResponse)ex.Response).StatusCode; + } + + return status; + } + + public string IPv4Loopback + { + get { return _ipv4Loopback; } + } + public string IPv4One + { + get { return _ipv4One; } + } + public string IPv4Two + { + get { return _ipv4Two; } + } + public string IPv6Loopback + { + get { return _ipv6Loopback; } + } + public string IPv6One + { + get { return _ipv6One; } + } + public string IPv6Two + { + get { return _ipv6Two; } + } + + private string[] _Ips; + + private string[] _Hosts = { "foo", "bar", "foobar", "barfoo" }; + + private string _unusedIp; + + + private Thread _backgroundRequestThread = null; + + public void ReadMachineIpAddressInfo() + { + foreach (IPAddress ip in _host.AddressList) + { + if (IPAddress.IsLoopback(ip)) + continue; + + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + if (_ipv4One == null) + _ipv4One = ip.ToString(); + else if (_ipv4Two == null) + _ipv4Two = ip.ToString(); + } + else if (ip.AddressFamily == AddressFamily.InterNetworkV6) + { + if (!ip.ToString().Contains("%")) + { + if (_ipv6One == null) + _ipv6One = "[" + ip.ToString() + "]"; + else if (_ipv6Two == null) + _ipv6Two = "[" + ip.ToString() + "]"; + } + } + } + } + + public int SendReceiveStatus(string path = "/", string protocol = "http", string ip = "127.0.0.1", int port = 8080, string host = "localhost", int expectedStatus = 200, int retryCount = 0) + { + string uri = protocol + "://" + ip + ":" + port + path; + int status = HttpClientHelper.sendRequest(uri, host, "CN=NULL", false, false); + for (int i = 0; i < retryCount; i++) + { + if (status == expectedStatus) + { + break; + } + DoSleep(1000); + status = HttpClientHelper.sendRequest(uri, host, "CN=NULL", false, false); + } + return status; + } + + public void DoRequest(string uri, string host = null, string expectedCN = "CN=NULL", bool useLegacy = false, bool displayContent = false) + { + HttpClientHelper.sendRequest(uri, host, expectedCN, useLegacy, displayContent); + } + + private void BackgroundRequestLoop(object req) + { + String[] uriHost = (String[])req; + + while (true) + { + HttpClientHelper.sendRequest(uriHost[0], uriHost[1], "CN=NULL", false, false, false); + Thread.Sleep(5000); + } + } + + public void StartBackgroundRequests(string uri, string host = null) + { + if (_backgroundRequestThread != null && _backgroundRequestThread.ThreadState == System.Threading.ThreadState.Running) + _backgroundRequestThread.Abort(); + + if (host == null) + TestUtility.LogInformation(String.Format("########## Starting background requests to {0} with no hostname ##########", uri)); + else + TestUtility.LogInformation(String.Format("########## Starting background requests to {0} with hostname {1} ##########", uri, host)); + + + ParameterizedThreadStart threadStart = new ParameterizedThreadStart(BackgroundRequestLoop); + _backgroundRequestThread = new Thread(threadStart); + _backgroundRequestThread.IsBackground = true; + _backgroundRequestThread.Start(new string[] { uri, host }); + } + + public void StopBackgroundRequests() + { + TestUtility.LogInformation(String.Format("####################### Stopping background requests #######################")); + + if (_backgroundRequestThread != null && _backgroundRequestThread.ThreadState == System.Threading.ThreadState.Running) + _backgroundRequestThread.Abort(); + + _backgroundRequestThread = null; + } + + public void DoSleep(int sleepMs) + { + TestUtility.LogInformation(String.Format("################## Sleeping for {0} ms ##################", sleepMs)); + Thread.Sleep(sleepMs); + } + } + + public class RequestInfo + { + public string ip; + public int port; + public string host; + public int status; + + public RequestInfo(string ipIn, int portIn, string hostIn, int statusIn) + { + ip = ipIn; + port = portIn; + host = hostIn; + status = statusIn; + } + + public string ToUrlRegistration() + { + if ((ip == null || ip == "*") && (host == null || host == "*")) + return String.Format("HTTP://*:{0}/", port).ToUpper(); + + if (ip == null || ip == "*") + return String.Format("HTTP://{0}:{1}/", host, port).ToUpper(); + + if (host == null || host == "*") + return String.Format("HTTP://{0}:{1}:{0}/", ip, port).ToUpper(); + + return String.Format("HTTP://{0}:{1}:{2}/", host, port, ip).ToUpper(); + } + } + + public class BindingInfo + { + public string ip; + public int port; + public string host; + + public BindingInfo(string ip, int port, string host) + { + this.ip = ip; + this.port = port; + this.host = host; + } + + public int GetBindingType() + { + if (ip == null) + { + if (host == null) + return 5; + else + return 3; + } + else + { + if (host == null) + return 4; + else + return 2; + } + } + + public bool IsSupportedForDynamic() + { + return GetBindingType() == 2 || GetBindingType() == 5; + } + + public bool Match(RequestInfo req) + { + if (ip != null && ip != req.ip) + return false; + if (port != req.port) + return false; + if (host != null && host != req.host) + return false; + + return true; + } + + public string ToBindingString() + { + string bindingInfoString = ""; + bindingInfoString += (ip == null) ? "*" : ip; + bindingInfoString += ":"; + bindingInfoString += port; + bindingInfoString += ":"; + if (host != null) + bindingInfoString += host; + + return bindingInfoString; + } + + public string ToUrlRegistration() + { + if ((ip == null || ip == "*") && (host == null || host == "*")) + return String.Format("HTTP://*:{0}/", port).ToUpper(); + + if (ip == null || ip == "*") + return String.Format("HTTP://{0}:{1}/", host, port).ToUpper(); + + if (host == null || host == "*") + return String.Format("HTTP://{0}:{1}:{0}/", ip, port).ToUpper(); + + return String.Format("HTTP://{0}:{1}:{2}/", host, port, ip).ToUpper(); + } + } +} + diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs new file mode 100644 index 0000000000..5e7d66a9d5 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Text; + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public class Frame + { + private int startingIndex; // This will be initialized as output parameter of GetFrameString() + public int DataLength = 0; // This will be initialized as output parameter of GetFrameString() + + public Frame(byte[] data) + { + Data = data; + FrameType = WebSocketClientUtility.GetFrameType(Data); + Content = WebSocketClientUtility.GetFrameString(Data, out startingIndex, out DataLength); + IsMasked = WebSocketClientUtility.IsFrameMasked(Data); + } + + public FrameType FrameType { get; set; } + public byte[] Data { get; private set; } + + public string TextData + { + get + { + if (DataLength == 0) + { + throw new System.Exception("DataLength is zero"); + } + return Encoding.ASCII.GetString(Data, startingIndex, DataLength); + } + } + + public string Content { get; private set; } + public bool IsMasked { get; private set; } + + public int IndexOfNextFrame + { + get + { + if (startingIndex > 0 && Data.Length > Content.Length + startingIndex) + { + return Content.Length + startingIndex; + } + else + { + return -1; + } + } + } + + override public string ToString() + { + return FrameType + ": " + Content; + } + } +} diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs new file mode 100644 index 0000000000..50fd11aca2 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace AspNetCoreModule.Test.WebSocketClient +{ + //* %x0 denotes a continuation frame + //* %x1 denotes a text frame + //* %x2 denotes a binary frame + //* %x3-7 are reserved for further non-control frames + //* %x8 denotes a connection close + //* %x9 denotes a ping + //* %xA denotes a pong + public enum FrameType + { + NonControlFrame, + Ping, + Pong, + Text, + SegmentedText, + Binary, + SegmentedBinary, + Continuation, + ContinuationControlled, + ContinuationFrameEnd, + Close, + } +} diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs new file mode 100644 index 0000000000..f42edd644c --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public class Frames + { + public static byte[] CLOSE_FRAME = new byte[] { 0x88, 0x85, 0xBD, 0x60, 0x97, 0x72, 0xBE, 0x88, 0xA5, 0x40, 0x8F }; + public static byte[] PING = new byte[] { 0x89, 0x88, 0,0,0,0, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB }; + public static byte[] PONG = new byte[] { 0x8A, 0x08, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB }; + public static byte[] HELLO = new byte[] { 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f }; + public static byte[] FRAME_4096 = new byte[] { 0x81, 0xFE, 0x10, 0x00, 0x88, 0x48, 0x9B, 0xE7, 0xDA, 0x0D, 0xD4, 0xB7, 0xCA, 0x0E, 0xD2, 0xA1, 0xD2, 0x0C, 0xD7, 0xB0, 0xCB, 0x1A, 0xC9, 0xB4, 0xC0, 0x1B, 0xD8, 0xA8, 0xC5, 0x05, 0xDC, 0xBF, 0xC7, 0x19, 0xC2, 0xAD, 0xD0, 0x1B, 0xC9, 0xA8, 0xDF, 0x0E, 0xDF, 0xB4, 0xD8, 0x12, 0xDF, 0xBD, 0xC6, 0x04, 0xCD, 0xB5, 0xD9, 0x05, 0xDA, 0xBD, 0xC5, 0x1B, 0xD9, 0xBF, 0xCD, 0x1F, 0xDF, 0xA2, 0xD0, 0x1A, 0xDA, 0xAC, 0xC0, 0x12, 0xD2, 0xA0, 0xC4, 0x10, 0xDA, 0xB0, 0xC7, 0x11, 0xD8, 0xAE, 0xD8, 0x0B, 0xD9, 0xA5, 0xDA, 0x18, 0xD3, 0xA4, 0xD8, 0x12, 0xCC, 0xA1, 0xDA, 0x1D, 0xD0, 0xA6, 0xCB, 0x11, 0xCE, 0xBE, 0xD2, 0x1B, 0xC9, 0xA9, 0xCB, 0x07, 0xDD, 0xB3, 0xC5, 0x0D, 0xD7, 0xA9, 0xD8, 0x1C, 0xD2, 0xA5, 0xDD, 0x0B, 0xD5, 0xAE, 0xC3, 0x11, 0xCD, 0xAE, 0xCB, 0x0C, 0xCB, 0xB5, 0xC5, 0x12, 0xCE, 0xB7, 0xCD, 0x0D, 0xD2, 0xB0, 0xC3, 0x06, 0xC2, 0xA3, 0xC3, 0x06, 0xCB, 0xAB, 0xC6, 0x02, 0xCB, 0xBE, 0xC4, 0x01, 0xDD, 0xBD, 0xC4, 0x05, 0xD5, 0xA5, 0xDF, 0x02, 0xD7, 0xBD, 0xD1, 0x07, 0xDC, 0xAA, 0xC3, 0x1E, 0xD1, 0xAB, 0xC3, 0x04, 0xCA, 0xAF, 0xCD, 0x02, 0xC2, 0xB0, 0xC1, 0x03, 0xCE, 0xB4, 0xC7, 0x1A, 0xDD, 0xA3, 0xDD, 0x1D, 0xDE, 0xB5, 0xD9, 0x00, 0xDA, 0xA5, 0xCA, 0x12, 0xDE, 0xB1, 0xC0, 0x0C, 0xDA, 0xB4, 0xC9, 0x0D, 0xD5, 0xA6, 0xDB, 0x10, 0xCD, 0xA5, 0xC7, 0x1A, 0xC8, 0xAA, 0xD8, 0x1C, 0xD0, 0xAF, 0xC1, 0x0B, 0xC8, 0xB7, 0xDA, 0x1A, 0xCE, 0xA4, 0xC4, 0x18, 0xDC, 0xA1, 0xCD, 0x0B, 0xCB, 0xA1, 0xC2, 0x0B, 0xC8, 0xAC, 0xCC, 0x0E, 0xD7, 0xB0, 0xD2, 0x0B, 0xDF, 0xBD, 0xD8, 0x07, 0xD6, 0xAF, 0xC7, 0x10, 0xD5, 0xA2, 0xC4, 0x04, 0xD9, 0xAE, 0xC2, 0x03, 0xD4, 0xA3, 0xDA, 0x19, 0xCC, 0xAA, 0xCB, 0x06, 0xD8, 0xA9, 0xCA, 0x09, 0xDF, 0xA3, 0xDA, 0x1E, 0xCA, 0xA8, 0xC7, 0x1E, 0xD5, 0xBF, 0xCC, 0x11, 0xCA, 0xAF, 0xC7, 0x03, 0xCE, 0xBE, 0xCA, 0x02, 0xDA, 0xB4, 0xD9, 0x01, 0xDD, 0xAE, 0xCE, 0x19, 0xCC, 0xA5, 0xC4, 0x12, 0xDC, 0xA9, 0xDC, 0x03, 0xD5, 0xB7, 0xDE, 0x05, 0xCE, 0xA9, 0xD0, 0x1A, 0xDA, 0xB4, 0xD2, 0x18, 0xC3, 0xB5, 0xDC, 0x0D, 0xD6, 0xB4, 0xC9, 0x04, 0xD6, 0xAE, 0xD8, 0x00, 0xD2, 0xBF, 0xD1, 0x05, 0xD7, 0xA2, 0xDA, 0x0B, 0xD4, 0xA2, 0xDF, 0x0D, 0xD8, 0xA8, 0xD8, 0x05, 0xCD, 0xBE, 0xC1, 0x05, 0xD2, 0xB6, 0xDC, 0x0E, 0xD2, 0xA0, 0xC5, 0x07, 0xD5, 0xAE, 0xD0, 0x0E, 0xDA, 0xA9, 0xCD, 0x1F, 0xD4, 0xAB, 0xCF, 0x1F, 0xD9, 0xBF, 0xC9, 0x1B, 0xCE, 0xB7, 0xCA, 0x10, 0xD8, 0xA9, 0xD8, 0x07, 0xDA, 0xA3, 0xD2, 0x1A, 0xDE, 0xB3, 0xCC, 0x0D, 0xC9, 0xA3, 0xD8, 0x0F, 0xDC, 0xB5, 0xCC, 0x18, 0xD0, 0xB3, 0xD1, 0x03, 0xC8, 0xAA, 0xC4, 0x04, 0xCB, 0xA6, 0xC3, 0x1C, 0xDD, 0xB7, 0xC4, 0x09, 0xC8, 0xAD, 0xCD, 0x10, 0xD4, 0xAA, 0xDA, 0x1E, 0xD3, 0xA5, 0xCE, 0x10, 0xD3, 0xB2, 0xC5, 0x0B, 0xD7, 0xAA, 0xC6, 0x02, 0xCB, 0xA1, 0xDE, 0x07, 0xC9, 0xA9, 0xCA, 0x0D, 0xD2, 0xAC, 0xD2, 0x0A, 0xC9, 0xA8, 0xC7, 0x10, 0xD5, 0xA0, 0xCA, 0x10, 0xD9, 0xA4, 0xCB, 0x1A, 0xD3, 0xA8, 0xCC, 0x1E, 0xD4, 0xAF, 0xC1, 0x1C, 0xD9, 0xA5, 0xC4, 0x05, 0xD5, 0xB6, 0xCE, 0x0A, 0xD0, 0xA9, 0xC5, 0x1F, 0xD9, 0xA3, 0xCE, 0x1C, 0xDC, 0xA8, 0xD8, 0x0D, 0xD7, 0xB7, 0xC1, 0x05, 0xD8, 0xA2, 0xC0, 0x0C, 0xD1, 0xA0, 0xD8, 0x09, 0xD8, 0xA0, 0xC9, 0x19, 0xDF, 0xA5, 0xC2, 0x1F, 0xD9, 0xBD, 0xC6, 0x06, 0xCB, 0xA1, 0xD8, 0x0C, 0xD2, 0xAC, 0xC7, 0x12, 0xC9, 0xA3, 0xC0, 0x04, 0xCF, 0xBE, 0xC2, 0x02, 0xD1, 0xA5, 0xDA, 0x0D, 0xC2, 0xAB, 0xDD, 0x1E, 0xDF, 0xB7, 0xD8, 0x0E, 0xDE, 0xB3, 0xCC, 0x04, 0xD8, 0xB2, 0xDF, 0x10, 0xD4, 0xA3, 0xDE, 0x11, 0xC9, 0xB7, 0xC2, 0x11, 0xC2, 0xA8, 0xDF, 0x0D, 0xC9, 0xBD, 0xC6, 0x12, 0xD2, 0xAD, 0xD8, 0x0C, 0xD7, 0xB2, 0xC9, 0x1B, 0xCE, 0xAD, 0xDE, 0x11, 0xDA, 0xB0, 0xC1, 0x11, 0xD9, 0xAA, 0xDE, 0x0E, 0xDC, 0xB3, 0xC5, 0x01, 0xD8, 0xB1, 0xD0, 0x07, 0xCF, 0xAB, 0xC6, 0x0E, 0xDD, 0xA3, 0xCB, 0x1C, 0xDE, 0xB3, 0xC4, 0x1D, 0xDF, 0xA4, 0xCC, 0x00, 0xCE, 0xAC, 0xD1, 0x0A, 0xDD, 0xBF, 0xCB, 0x0E, 0xDE, 0xAF, 0xDB, 0x18, 0xCC, 0xAF, 0xCA, 0x18, 0xCC, 0xAA, 0xD2, 0x02, 0xCC, 0xB6, 0xDB, 0x1F, 0xCF, 0xB7, 0xDD, 0x01, 0xDA, 0xA6, 0xCB, 0x0D, 0xCB, 0xA2, 0xC6, 0x1B, 0xC3, 0xB1, 0xC1, 0x1E, 0xD1, 0xAF, 0xC9, 0x10, 0xD7, 0xA9, 0xD0, 0x10, 0xC8, 0xB0, 0xD0, 0x19, 0xD8, 0xB2, 0xC4, 0x0D, 0xC9, 0xA5, 0xC0, 0x19, 0xDF, 0xB3, 0xCE, 0x0C, 0xDD, 0xA9, 0xD2, 0x1B, 0xCF, 0xAE, 0xDB, 0x0A, 0xDF, 0xA3, 0xD2, 0x07, 0xCB, 0xB1, 0xC3, 0x0F, 0xC8, 0xB0, 0xD9, 0x0D, 0xDF, 0xAB, 0xCA, 0x1E, 0xC8, 0xAD, 0xC9, 0x1E, 0xD5, 0xB5, 0xDB, 0x18, 0xD8, 0xBF, 0xDB, 0x10, 0xD4, 0xA2, 0xCC, 0x02, 0xDF, 0xB1, 0xC6, 0x11, 0xCE, 0xBF, 0xC7, 0x1F, 0xCA, 0xA5, 0xD1, 0x0F, 0xD8, 0xA6, 0xD2, 0x1C, 0xD6, 0xA2, 0xD2, 0x03, 0xD3, 0xBF, 0xC6, 0x04, 0xD7, 0xAD, 0xC4, 0x1B, 0xD8, 0xA9, 0xDA, 0x06, 0xC8, 0xAF, 0xC9, 0x00, 0xC3, 0xA3, 0xC9, 0x1E, 0xCE, 0xA2, 0xCD, 0x05, 0xCF, 0xAB, 0xC6, 0x0A, 0xC3, 0xBE, 0xC4, 0x02, 0xDD, 0xB3, 0xCA, 0x0F, 0xD2, 0xA4, 0xC6, 0x03, 0xD7, 0xB0, 0xDC, 0x0C, 0xD1, 0xAB, 0xC7, 0x1E, 0xDA, 0xB4, 0xDE, 0x01, 0xDE, 0xA2, 0xD9, 0x0C, 0xCF, 0xA5, 0xDB, 0x09, 0xCC, 0xAD, 0xDE, 0x0C, 0xD9, 0xAF, 0xC1, 0x12, 0xDD, 0xB2, 0xD2, 0x1E, 0xCA, 0xB7, 0xC2, 0x10, 0xD0, 0xA6, 0xCB, 0x00, 0xCC, 0xB5, 0xCA, 0x0D, 0xDF, 0xA4, 0xCE, 0x09, 0xDF, 0xBE, 0xC5, 0x00, 0xD1, 0xAA, 0xC7, 0x0B, 0xD9, 0xB6, 0xCB, 0x0A, 0xDF, 0xA8, 0xD8, 0x0F, 0xCF, 0xBD, 0xDB, 0x07, 0xCE, 0xB4, 0xDB, 0x1B, 0xC2, 0xAC, 0xCC, 0x0D, 0xD3, 0xB6, 0xC9, 0x11, 0xD8, 0xAF, 0xDE, 0x00, 0xD3, 0xB5, 0xC5, 0x0C, 0xDA, 0xAF, 0xDF, 0x1D, 0xC2, 0xA6, 0xCD, 0x01, 0xD8, 0xB1, 0xC4, 0x0C, 0xD1, 0xB5, 0xCF, 0x04, 0xDD, 0xB2, 0xC2, 0x11, 0xD1, 0xAD, 0xDD, 0x04, 0xCB, 0xA3, 0xD2, 0x1E, 0xCF, 0xAE, 0xD1, 0x0A, 0xD5, 0xB7, 0xC6, 0x06, 0xCD, 0xBF, 0xDD, 0x10, 0xDC, 0xB1, 0xCB, 0x05, 0xDE, 0xBF, 0xD8, 0x03, 0xD9, 0xAC, 0xCA, 0x06, 0xD2, 0xA9, 0xDD, 0x19, 0xD6, 0xAB, 0xCD, 0x1E, 0xD9, 0xAD, 0xC7, 0x1C, 0xCC, 0xAD, 0xD9, 0x1D, 0xDF, 0xB4, 0xD8, 0x00, 0xC1, 0xAB, 0xDB, 0x06, 0xD3, 0xAF, 0xCF, 0x1B, 0xD4, 0xA8, 0xDD, 0x01, 0xD3, 0xAB, 0xDC, 0x12, 0xCE, 0xB0, 0xC9, 0x03, 0xCF, 0xBD, 0xDF, 0x10, 0xDC, 0xAE, 0xD9, 0x1E, 0xDC, 0xB2, 0xC0, 0x01, 0xCD, 0xB3, 0xC6, 0x10, 0xCD, 0xA0, 0xC2, 0x0E, 0xDE, 0xAA, 0xC0, 0x05, 0xD4, 0xA0, 0xC5, 0x03, 0xCA, 0xB6, 0xD2, 0x0F, 0xC8, 0xA2, 0xC7, 0x09, 0xCB, 0xB1, 0xC0, 0x11, 0xC9, 0xAB, 0xC4, 0x1C, 0xD3, 0xAA, 0xC5, 0x06, 0xC2, 0xB1, 0xCD, 0x07, 0xD5, 0xB1, 0xCE, 0x0F, 0xC8, 0xAC, 0xC1, 0x12, 0xCC, 0xA1, 0xCD, 0x19, 0xCD, 0xA5, 0xD9, 0x19, 0xDE, 0xAA, 0xC0, 0x09, 0xC1, 0xAB, 0xC6, 0x1C, 0xDA, 0xA9, 0xCE, 0x0B, 0xCF, 0xBE, 0xC5, 0x1D, 0xD6, 0xAB, 0xDC, 0x1F, 0xC1, 0xAF, 0xC1, 0x0E, 0xD6, 0xAF, 0xCA, 0x04, 0xDC, 0xB1, 0xD1, 0x0F, 0xCD, 0xB0, 0xC1, 0x05, 0xD4, 0xA3, 0xC7, 0x0A, 0xD3, 0xAB, 0xCF, 0x0D, 0xDD, 0xA0, 0xCE, 0x10, 0xCF, 0xAD, 0xCC, 0x02, 0xD3, 0xB3, 0xDA, 0x1F, 0xDF, 0xA4, 0xC6, 0x1B, 0xD1, 0xA5, 0xC6, 0x0E, 0xCB, 0xBE, 0xCF, 0x10, 0xCA, 0xBD, 0xCF, 0x01, 0xCC, 0xB5, 0xC7, 0x06, 0xD9, 0xA2, 0xC9, 0x0F, 0xD9, 0xA2, 0xDB, 0x10, 0xC9, 0xA8, 0xD1, 0x0B, 0xDD, 0xAA, 0xC1, 0x04, 0xCA, 0xB0, 0xDB, 0x0F, 0xC2, 0xA5, 0xD8, 0x0F, 0xC1, 0xAE, 0xC0, 0x1C, 0xD8, 0xB1, 0xC6, 0x19, 0xDE, 0xA3, 0xDE, 0x12, 0xD8, 0xA0, 0xD9, 0x0D, 0xD1, 0xB7, 0xC6, 0x09, 0xDA, 0xA2, 0xDB, 0x0C, 0xC9, 0xB1, 0xDA, 0x09, 0xC2, 0xAE, 0xC7, 0x09, 0xD4, 0xB3, 0xC0, 0x1B, 0xC3, 0xBE, 0xDD, 0x1F, 0xD9, 0xAF, 0xD0, 0x0A, 0xC9, 0xAE, 0xC1, 0x02, 0xDD, 0xA9, 0xDF, 0x02, 0xD5, 0xA8, 0xCE, 0x1E, 0xCA, 0xA3, 0xCB, 0x00, 0xDF, 0xAA, 0xDA, 0x1E, 0xD4, 0xB2, 0xC3, 0x01, 0xC1, 0xBE, 0xC0, 0x03, 0xCD, 0xB6, 0xD2, 0x1C, 0xDC, 0xB6, 0xC5, 0x01, 0xD5, 0xAF, 0xDD, 0x04, 0xD6, 0xA1, 0xC5, 0x09, 0xD4, 0xAA, 0xCB, 0x1C, 0xCC, 0xA9, 0xDC, 0x07, 0xCA, 0xA4, 0xC6, 0x1F, 0xC2, 0xBD, 0xC3, 0x00, 0xDC, 0xAB, 0xC7, 0x1F, 0xD4, 0xAA, 0xC7, 0x09, 0xCA, 0xB3, 0xDD, 0x1E, 0xC8, 0xA0, 0xC2, 0x01, 0xD3, 0xAC, 0xDD, 0x06, 0xCD, 0xA9, 0xC7, 0x00, 0xC3, 0xAB, 0xCC, 0x0D, 0xDF, 0xB6, 0xC3, 0x06, 0xC3, 0xA9, 0xCD, 0x09, 0xC9, 0xB7, 0xC4, 0x0B, 0xC3, 0xA5, 0xCB, 0x0B, 0xCF, 0xBE, 0xDF, 0x02, 0xC8, 0xA2, 0xC7, 0x07, 0xDD, 0xBF, 0xC4, 0x1B, 0xC3, 0xA0, 0xD2, 0x0B, 0xD1, 0xAC, 0xD0, 0x09, 0xD1, 0xA0, 0xD0, 0x0D, 0xD9, 0xAD, 0xD9, 0x1A, 0xC2, 0xB5, 0xD8, 0x1D, 0xD7, 0xAB, 0xC6, 0x11, 0xCB, 0xB3, 0xC4, 0x11, 0xD9, 0xB0, 0xC0, 0x12, 0xD8, 0xAA, 0xCC, 0x04, 0xCA, 0xAD, 0xDC, 0x05, 0xDE, 0xA4, 0xDC, 0x05, 0xDA, 0xB5, 0xC0, 0x02, 0xD5, 0xB0, 0xD8, 0x06, 0xD3, 0xB5, 0xCF, 0x04, 0xC8, 0xA5, 0xC5, 0x18, 0xC1, 0xBE, 0xD1, 0x06, 0xC2, 0xBE, 0xCB, 0x18, 0xDD, 0xA1, 0xCA, 0x07, 0xC2, 0xA3, 0xD8, 0x02, 0xC8, 0xA6, 0xD0, 0x11, 0xD6, 0xA4, 0xC4, 0x0D, 0xDC, 0xB2, 0xDA, 0x04, 0xDD, 0xB4, 0xDB, 0x18, 0xCC, 0xA3, 0xC6, 0x0F }; + public static byte[] FRAME_5000 = new byte[] { 0x81, 0xFE, 0x13, 0x88, 0x17, 0x84, 0x25, 0xB2, 0x58, 0xC6, 0x71, 0xE0, 0x4E, 0xD5, 0x77, 0xE0, 0x50, 0xD0, 0x69, 0xFA, 0x43, 0xCF, 0x75, 0xFF, 0x5C, 0xCB, 0x60, 0xE1, 0x52, 0xD0, 0x70, 0xFE, 0x5D, 0xCE, 0x71, 0xF3, 0x44, 0xC7, 0x60, 0xF0, 0x58, 0xC8, 0x6D, 0xE8, 0x5E, 0xDE, 0x60, 0xFF, 0x51, 0xCA, 0x6A, 0xF0, 0x44, 0xDE, 0x62, 0xE6, 0x59, 0xC5, 0x67, 0xEB, 0x4E, 0xC1, 0x77, 0xE4, 0x50, 0xCC, 0x6D, 0xE2, 0x40, 0xD5, 0x7C, 0xF6, 0x58, 0xCF, 0x76, 0xFA, 0x54, 0xD4, 0x60, 0xFE, 0x5D, 0xD6, 0x68, 0xE0, 0x52, 0xD0, 0x71, 0xF9, 0x54, 0xDE, 0x6B, 0xE0, 0x55, 0xC3, 0x66, 0xF8, 0x42, 0xC8, 0x71, 0xF3, 0x45, 0xD4, 0x75, 0xFD, 0x58, 0xC8, 0x68, 0xFA, 0x50, 0xDE, 0x77, 0xEA, 0x40, 0xD4, 0x6A, 0xF5, 0x44, 0xC5, 0x74, 0xFC, 0x58, 0xDC, 0x68, 0xEA, 0x53, 0xC3, 0x67, 0xFB, 0x5F, 0xCE, 0x6B, 0xE3, 0x40, 0xC0, 0x71, 0xE6, 0x55, 0xDC, 0x66, 0xE6, 0x50, 0xC8, 0x61, 0xF1, 0x5E, 0xD4, 0x73, 0xFE, 0x45, 0xD2, 0x77, 0xE6, 0x41, 0xC2, 0x68, 0xE7, 0x54, 0xD7, 0x69, 0xFA, 0x5D, 0xC1, 0x7F, 0xEA, 0x5A, 0xC5, 0x67, 0xE6, 0x40, 0xD2, 0x63, 0xE7, 0x4F, 0xDC, 0x62, 0xF6, 0x43, 0xCE, 0x6A, 0xFC, 0x5B, 0xD4, 0x74, 0xFF, 0x45, 0xD0, 0x73, 0xE3, 0x46, 0xDD, 0x74, 0xFB, 0x5A, 0xD2, 0x6F, 0xF1, 0x5B, 0xC3, 0x74, 0xFB, 0x58, 0xC7, 0x75, 0xE5, 0x46, 0xDC, 0x72, 0xEA, 0x4E, 0xCE, 0x67, 0xE6, 0x52, 0xDC, 0x72, 0xE7, 0x59, 0xCA, 0x63, 0xE1, 0x52, 0xCF, 0x66, 0xEA, 0x52, 0xD3, 0x6D, 0xF1, 0x58, 0xC0, 0x77, 0xFC, 0x43, 0xC3, 0x7F, 0xF8, 0x56, 0xD0, 0x73, 0xE7, 0x4F, 0xDD, 0x76, 0xFA, 0x4F, 0xDC, 0x60, 0xFD, 0x4D, 0xCB, 0x6A, 0xEA, 0x56, 0xDC, 0x61, 0xF7, 0x4D, 0xD6, 0x76, 0xE6, 0x47, 0xD0, 0x6C, 0xE7, 0x45, 0xCB, 0x7F, 0xEB, 0x4E, 0xC9, 0x71, 0xE7, 0x44, 0xCF, 0x73, 0xF5, 0x44, 0xD1, 0x64, 0xF5, 0x44, 0xD7, 0x61, 0xE8, 0x58, 0xD1, 0x68, 0xE4, 0x54, 0xD1, 0x6F, 0xFB, 0x56, 0xC7, 0x63, 0xF6, 0x47, 0xDC, 0x74, 0xF8, 0x4F, 0xC2, 0x74, 0xFF, 0x41, 0xD1, 0x63, 0xE3, 0x54, 0xD3, 0x68, 0xF4, 0x46, 0xC9, 0x64, 0xE5, 0x46, 0xCE, 0x63, 0xEA, 0x55, 0xC1, 0x73, 0xF6, 0x54, 0xC8, 0x71, 0xE3, 0x52, 0xD7, 0x77, 0xE7, 0x52, 0xD6, 0x6C, 0xFF, 0x54, 0xD5, 0x60, 0xE7, 0x58, 0xD3, 0x76, 0xF5, 0x5E, 0xC1, 0x77, 0xFD, 0x55, 0xCE, 0x6B, 0xF4, 0x45, 0xD0, 0x6D, 0xE6, 0x5C, 0xC9, 0x6F, 0xF8, 0x55, 0xD4, 0x69, 0xF9, 0x52, 0xD6, 0x67, 0xE8, 0x53, 0xCB, 0x71, 0xE8, 0x52, 0xC8, 0x6C, 0xF4, 0x5B, 0xCB, 0x73, 0xEB, 0x43, 0xC1, 0x75, 0xE4, 0x51, 0xC8, 0x61, 0xF9, 0x5C, 0xD4, 0x66, 0xE2, 0x5F, 0xD1, 0x76, 0xE8, 0x5C, 0xCD, 0x67, 0xE0, 0x53, 0xD7, 0x6E, 0xFF, 0x47, 0xCA, 0x64, 0xF4, 0x5C, 0xC6, 0x6D, 0xE4, 0x45, 0xC8, 0x74, 0xE5, 0x56, 0xD4, 0x63, 0xE6, 0x59, 0xD5, 0x6A, 0xFD, 0x5B, 0xC0, 0x76, 0xF9, 0x44, 0xCD, 0x70, 0xF6, 0x59, 0xC1, 0x73, 0xF3, 0x43, 0xC7, 0x62, 0xE0, 0x5B, 0xDD, 0x7F, 0xFB, 0x5F, 0xCC, 0x7C, 0xE5, 0x52, 0xD2, 0x7F, 0xE4, 0x53, 0xCD, 0x61, 0xFF, 0x53, 0xD3, 0x67, 0xFE, 0x41, 0xD5, 0x68, 0xF1, 0x5F, 0xC1, 0x6D, 0xFF, 0x47, 0xD4, 0x61, 0xEA, 0x5D, 0xCA, 0x6D, 0xE2, 0x46, 0xC3, 0x6D, 0xF7, 0x51, 0xD2, 0x62, 0xEA, 0x5D, 0xDE, 0x7F, 0xF7, 0x55, 0xCD, 0x72, 0xEA, 0x56, 0xD1, 0x72, 0xE7, 0x5B, 0xDC, 0x67, 0xF6, 0x4D, 0xC8, 0x62, 0xFD, 0x44, 0xC6, 0x69, 0xE2, 0x56, 0xCA, 0x72, 0xEA, 0x47, 0xDC, 0x62, 0xE8, 0x5D, 0xD4, 0x71, 0xFA, 0x52, 0xC0, 0x69, 0xFA, 0x43, 0xC2, 0x7D, 0xE2, 0x45, 0xCA, 0x60, 0xE1, 0x51, 0xC0, 0x60, 0xE6, 0x47, 0xD7, 0x60, 0xFA, 0x59, 0xCE, 0x60, 0xFC, 0x5A, 0xDE, 0x6C, 0xF6, 0x58, 0xDC, 0x6E, 0xE5, 0x52, 0xD0, 0x7C, 0xE5, 0x4D, 0xDE, 0x73, 0xFF, 0x52, 0xD3, 0x7C, 0xFC, 0x5D, 0xC0, 0x77, 0xFE, 0x44, 0xC9, 0x6F, 0xE0, 0x5C, 0xC8, 0x71, 0xE4, 0x4E, 0xDD, 0x73, 0xE6, 0x4F, 0xD1, 0x64, 0xE7, 0x54, 0xCC, 0x6A, 0xFE, 0x51, 0xCD, 0x71, 0xE3, 0x4F, 0xD6, 0x61, 0xE0, 0x5B, 0xD5, 0x61, 0xFB, 0x5F, 0xDC, 0x6E, 0xF0, 0x59, 0xD0, 0x69, 0xE6, 0x4D, 0xC0, 0x7D, 0xF0, 0x53, 0xC6, 0x75, 0xF9, 0x41, 0xC1, 0x69, 0xF0, 0x47, 0xC2, 0x62, 0xF8, 0x44, 0xD0, 0x70, 0xE6, 0x5E, 0xC7, 0x6F, 0xFB, 0x42, 0xC9, 0x69, 0xF3, 0x5D, 0xDE, 0x62, 0xFB, 0x40, 0xD2, 0x68, 0xF1, 0x5B, 0xD7, 0x68, 0xE4, 0x55, 0xD0, 0x73, 0xFA, 0x52, 0xC7, 0x71, 0xF1, 0x45, 0xC5, 0x6C, 0xE7, 0x4D, 0xD7, 0x6E, 0xE5, 0x43, 0xCB, 0x62, 0xE0, 0x46, 0xD4, 0x64, 0xE5, 0x4F, 0xC7, 0x60, 0xE6, 0x43, 0xC0, 0x7C, 0xF3, 0x50, 0xDD, 0x77, 0xE2, 0x5F, 0xC7, 0x61, 0xE1, 0x44, 0xCE, 0x6F, 0xFB, 0x46, 0xC9, 0x6F, 0xF7, 0x5C, 0xD4, 0x6C, 0xE5, 0x5A, 0xD1, 0x60, 0xFF, 0x44, 0xDD, 0x6F, 0xF1, 0x4F, 0xDE, 0x6C, 0xFC, 0x54, 0xCD, 0x6B, 0xF3, 0x56, 0xD2, 0x75, 0xE3, 0x5B, 0xCA, 0x7F, 0xFA, 0x50, 0xD7, 0x62, 0xF9, 0x43, 0xC5, 0x6F, 0xF6, 0x41, 0xC7, 0x6B, 0xFE, 0x43, 0xC2, 0x7D, 0xFB, 0x43, 0xC6, 0x73, 0xE1, 0x56, 0xD2, 0x62, 0xFA, 0x4D, 0xCE, 0x61, 0xE2, 0x56, 0xD6, 0x6E, 0xEB, 0x41, 0xDC, 0x63, 0xF3, 0x45, 0xDE, 0x6C, 0xE5, 0x46, 0xC2, 0x77, 0xF3, 0x41, 0xC6, 0x62, 0xE4, 0x4E, 0xC3, 0x7D, 0xF9, 0x44, 0xC3, 0x6D, 0xF9, 0x5B, 0xDE, 0x6E, 0xF8, 0x4F, 0xD1, 0x66, 0xF6, 0x45, 0xD4, 0x74, 0xE5, 0x4D, 0xD3, 0x74, 0xE6, 0x44, 0xDD, 0x66, 0xE7, 0x52, 0xC2, 0x68, 0xEB, 0x53, 0xCC, 0x74, 0xE7, 0x42, 0xC5, 0x63, 0xE2, 0x46, 0xD2, 0x6A, 0xE1, 0x58, 0xDE, 0x7F, 0xE5, 0x54, 0xCB, 0x6F, 0xF4, 0x5B, 0xCE, 0x72, 0xF0, 0x47, 0xC1, 0x77, 0xE6, 0x52, 0xC9, 0x62, 0xF4, 0x5A, 0xC9, 0x62, 0xE2, 0x53, 0xCD, 0x6F, 0xE3, 0x5D, 0xC5, 0x63, 0xF7, 0x5E, 0xDD, 0x60, 0xE6, 0x4D, 0xC2, 0x76, 0xE3, 0x40, 0xC3, 0x6B, 0xE6, 0x5C, 0xD4, 0x60, 0xE3, 0x5D, 0xC8, 0x6E, 0xF7, 0x58, 0xCD, 0x63, 0xF1, 0x43, 0xCE, 0x71, 0xE7, 0x52, 0xD7, 0x72, 0xF9, 0x52, 0xD7, 0x76, 0xE0, 0x4D, 0xDD, 0x70, 0xEB, 0x42, 0xD5, 0x6F, 0xF5, 0x4D, 0xCA, 0x63, 0xFC, 0x53, 0xD0, 0x6D, 0xEA, 0x46, 0xC6, 0x75, 0xF3, 0x44, 0xC7, 0x7F, 0xE3, 0x5A, 0xDC, 0x69, 0xF6, 0x5C, 0xC7, 0x75, 0xE1, 0x40, 0xCA, 0x74, 0xF9, 0x45, 0xC8, 0x6E, 0xEB, 0x4D, 0xDE, 0x61, 0xF5, 0x52, 0xC2, 0x74, 0xFE, 0x5B, 0xDD, 0x70, 0xF1, 0x53, 0xD7, 0x7C, 0xE5, 0x4E, 0xC1, 0x68, 0xE5, 0x52, 0xC2, 0x73, 0xE5, 0x4F, 0xCA, 0x74, 0xE3, 0x54, 0xD3, 0x62, 0xF7, 0x45, 0xD5, 0x67, 0xE6, 0x4D, 0xD0, 0x68, 0xFA, 0x5F, 0xC5, 0x76, 0xFF, 0x5E, 0xC9, 0x6A, 0xF7, 0x47, 0xD1, 0x69, 0xFF, 0x4D, 0xCA, 0x70, 0xE6, 0x53, 0xC3, 0x6C, 0xE0, 0x58, 0xC5, 0x6F, 0xE2, 0x45, 0xD5, 0x69, 0xFF, 0x45, 0xC1, 0x7D, 0xF7, 0x44, 0xC2, 0x6A, 0xF7, 0x59, 0xCE, 0x6A, 0xFE, 0x4E, 0xC8, 0x64, 0xFA, 0x5B, 0xD0, 0x60, 0xF6, 0x40, 0xCC, 0x74, 0xE1, 0x5B, 0xD1, 0x76, 0xFA, 0x45, 0xC0, 0x70, 0xE0, 0x55, 0xC6, 0x6B, 0xF9, 0x4F, 0xC3, 0x70, 0xE7, 0x4D, 0xD4, 0x62, 0xE6, 0x44, 0xD3, 0x76, 0xF1, 0x4D, 0xC6, 0x61, 0xEA, 0x5B, 0xCC, 0x74, 0xF8, 0x58, 0xC1, 0x71, 0xEA, 0x59, 0xCC, 0x6B, 0xF9, 0x47, 0xD3, 0x6E, 0xE5, 0x4E, 0xD4, 0x6E, 0xF7, 0x4D, 0xCF, 0x60, 0xF5, 0x56, 0xD3, 0x64, 0xE2, 0x54, 0xD4, 0x6C, 0xE2, 0x4D, 0xD3, 0x62, 0xE6, 0x5C, 0xC0, 0x72, 0xE6, 0x59, 0xDC, 0x6D, 0xE0, 0x54, 0xD3, 0x60, 0xE4, 0x5A, 0xD3, 0x60, 0xF8, 0x46, 0xDE, 0x7C, 0xF0, 0x54, 0xCF, 0x6C, 0xE1, 0x53, 0xC0, 0x73, 0xEA, 0x4D, 0xDC, 0x69, 0xE1, 0x46, 0xD5, 0x69, 0xE7, 0x43, 0xD6, 0x74, 0xF1, 0x54, 0xC9, 0x61, 0xF7, 0x45, 0xC2, 0x66, 0xF4, 0x5C, 0xDD, 0x7F, 0xE8, 0x4F, 0xC1, 0x74, 0xF3, 0x41, 0xC0, 0x75, 0xF8, 0x55, 0xCE, 0x76, 0xF7, 0x5B, 0xC8, 0x63, 0xE5, 0x5B, 0xCF, 0x75, 0xE8, 0x5D, 0xD3, 0x7C, 0xE3, 0x5F, 0xC1, 0x64, 0xEB, 0x55, 0xD5, 0x68, 0xF3, 0x4E, 0xC8, 0x70, 0xFE, 0x4D, 0xC8, 0x7F, 0xE3, 0x55, 0xC3, 0x64, 0xE6, 0x42, 0xDD, 0x71, 0xF4, 0x4D, 0xC3, 0x71, 0xF3, 0x5D, 0xDE, 0x74, 0xF0, 0x50, 0xC5, 0x76, 0xE3, 0x53, 0xD6, 0x6C, 0xFE, 0x40, 0xD7, 0x62, 0xE8, 0x45, 0xD0, 0x72, 0xF3, 0x5C, 0xDE, 0x7D, 0xF1, 0x41, 0xC2, 0x72, 0xFB, 0x5B, 0xD1, 0x7C, 0xE7, 0x40, 0xC9, 0x77, 0xEB, 0x41, 0xD0, 0x63, 0xE8, 0x44, 0xCD, 0x68, 0xF9, 0x4E, 0xD4, 0x72, 0xF0, 0x45, 0xD0, 0x6A, 0xF4, 0x5C, 0xD0, 0x75, 0xF8, 0x54, 0xCB, 0x63, 0xF3, 0x52, 0xCF, 0x63, 0xFB, 0x43, 0xCA, 0x75, 0xF9, 0x56, 0xCD, 0x61, 0xEA, 0x58, 0xDC, 0x6C, 0xF1, 0x5A, 0xCA, 0x61, 0xF5, 0x5E, 0xD1, 0x74, 0xE0, 0x50, 0xD7, 0x6E, 0xF6, 0x40, 0xCC, 0x72, 0xFA, 0x59, 0xC1, 0x73, 0xFB, 0x54, 0xC1, 0x74, 0xFE, 0x56, 0xC9, 0x64, 0xF8, 0x46, 0xD5, 0x77, 0xFC, 0x5B, 0xCA, 0x7D, 0xFD, 0x5C, 0xDE, 0x76, 0xF8, 0x43, 0xCB, 0x67, 0xF3, 0x53, 0xC9, 0x6B, 0xE6, 0x5A, 0xD3, 0x6F, 0xF9, 0x54, 0xC5, 0x7F, 0xFA, 0x40, 0xD7, 0x62, 0xE5, 0x43, 0xC3, 0x67, 0xE2, 0x56, 0xDC, 0x77, 0xFB, 0x5C, 0xCC, 0x72, 0xFD, 0x5A, 0xC8, 0x6A, 0xFD, 0x53, 0xD4, 0x6D, 0xFD, 0x4E, 0xCC, 0x7D, 0xE6, 0x50, 0xCC, 0x6E, 0xFE, 0x58, 0xD4, 0x77, 0xE1, 0x44, 0xD3, 0x74, 0xFF, 0x59, 0xC9, 0x64, 0xF4, 0x42, 0xC1, 0x6C, 0xF7, 0x56, 0xD2, 0x7C, 0xE3, 0x5B, 0xCF, 0x60, 0xFA, 0x5C, 0xD7 }; + + public static byte[][] GetHandShakeFrame(string url, int websocketVersion) + { + var address = new Uri(url); + + return new[] + { + Encoding.UTF8.GetBytes("GET " + address.PathAndQuery), + Encoding.UTF8.GetBytes(" HTTP/1.1\r\n"), + Encoding.UTF8.GetBytes(string.Format("Host: {0}:{1}", address.Host, address.Port)), + Encoding.UTF8.GetBytes("\r\nUpgrade: WebSocket\r\n"), + Encoding.UTF8.GetBytes("connection: upgrade\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Origin: http://localhost:80\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Version: "+websocketVersion+"\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Protocol: mywebsocketsubprotocol\r\n"), + Encoding.UTF8.GetBytes("\r"), + Encoding.UTF8.GetBytes("\n") + }; + } + + + public static byte[][] GetHandShakeFrameWithAffinityCookie(string url, int websocketVersion, string AffinityCookie) + { + var address = new Uri(url); + + return new[] + { + Encoding.UTF8.GetBytes("GET " + address.PathAndQuery), + Encoding.UTF8.GetBytes(" HTTP/1.1\r\n"), + Encoding.UTF8.GetBytes(string.Format("Host: {0}:{1}", address.Host, address.Port)), + Encoding.UTF8.GetBytes("\r\nUpgrade: WebSocket\r\n"), + Encoding.UTF8.GetBytes("connection: upgrade\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Origin: http://localhost:80\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Version: "+websocketVersion+"\r\n"), + Encoding.UTF8.GetBytes("Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"), + Encoding.UTF8.GetBytes("Cookie: "+AffinityCookie+"\r\n"), + Encoding.UTF8.GetBytes("\r"), + Encoding.UTF8.GetBytes("\n") + }; + } + + } +} diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs new file mode 100644 index 0000000000..76b038c792 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs @@ -0,0 +1,445 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections; +using System.Threading; + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public class WebSocketClientHelper : IDisposable + { + public bool IsOpened { get; private set; } + public WebSocketConnect Connection { get; set; } + public bool StoreData { get; set; } + public bool IsAlwaysReading { get; private set; } + public Uri Address { get; set; } + public byte[][] HandShakeRequest { get; set; } + public WebSocketState WebSocketState { get; set; } + + public WebSocketClientHelper() + { + } + + public void Dispose() + { + if (IsOpened) + { + Close(); + } + } + + public bool WaitForWebSocketState(WebSocketState expectedState, int timeout = 3000) + { + bool result = false; + int RETRYMAX = 300; + int INTERVAL = 100; // ms + if (timeout > RETRYMAX * INTERVAL) + { + throw new Exception("timeout should be less than " + 100 * 300); + } + for (int i=0; i timeout) + { + break; + } + if (this.WebSocketState == expectedState) + { + result = true; + break; + } + else + { + Thread.Sleep(INTERVAL); + } + } + return result; + } + + public Frame Connect(Uri address, bool storeData, bool isAlwaysReading, bool waitForConnectionOpen = true) + { + Address = address; + StoreData = storeData; + + Connection = new WebSocketConnect(); + if (isAlwaysReading) + { + InitiateWithAlwaysReading(); + } + SendWebSocketRequest(WebSocketClientUtility.WebSocketVersion); + + if (waitForConnectionOpen) + { + if (!WaitForWebSocketState(WebSocketState.ConnectionOpen)) + { + throw new Exception("Failed to open a connection"); + } + } + else + { + Thread.Sleep(3000); + } + + if (this.WebSocketState == WebSocketState.ConnectionOpen) + { + IsOpened = true; + } + else + { + IsOpened = false; + } + + Frame openingFrame = null; + + if (!IsAlwaysReading) + openingFrame = ReadData(); + else + openingFrame = Connection.DataReceived[0]; + + return openingFrame; + } + + public Frame Close() + { + CloseConnection(); + + Frame closeFrame = null; + + if (!IsAlwaysReading) + closeFrame = ReadData(); + else + closeFrame = Connection.DataReceived[Connection.DataReceived.Count - 1]; + + IsOpened = false; + return closeFrame; + } + + public void Initiate() + { + string host = Address.DnsSafeHost; + int port = Address.Port; + + Connection = new WebSocketConnect(); + TestUtility.LogInformation("Connecting to {0} on {1}", host, port); + + Connection.TcpClient = new MyTcpClient(host, port); + Connection.Stream = Connection.TcpClient.GetStream(); + IsAlwaysReading = false; + + if (StoreData) + { + Connection.DataSent = new List(); + Connection.DataReceived = new List(); + } + } + + public void InitiateWithAlwaysReading() + { + Initiate(); + Connection.Stream.BeginRead(Connection.InputData, 0, Connection.InputData.Length, ReadDataCallback, Connection); + IsAlwaysReading = true; + } + + public void SendWebSocketRequest(int websocketVersion) + { + HandShakeRequest = Frames.GetHandShakeFrame(Address.AbsoluteUri, websocketVersion); + + byte[] outputData = null; + int offset = 0; + while (offset < HandShakeRequest.Length) + { + outputData = HandShakeRequest[offset++]; + + var result = Connection.Stream.BeginWrite(outputData, 0, outputData.Length, WriteCallback, Connection); + + //jhkim debug + //result.AsyncWaitHandle.WaitOne(); + + TestUtility.LogInformation("Client {0:D3}: Write {1} bytes: {2} ", Connection.Id, outputData.Length, + Encoding.UTF8.GetString(outputData, 0, outputData.Length)); + + //result.AsyncWaitHandle.Close(); + } + } + + public void SendWebSocketRequest(int websocketVersion, string AffinityCookie) + { + HandShakeRequest = Frames.GetHandShakeFrameWithAffinityCookie(Address.AbsoluteUri, websocketVersion, AffinityCookie); + + byte[] outputData = null; + int offset = 0; + while (offset < HandShakeRequest.Length) + { + outputData = HandShakeRequest[offset++]; + + Connection.Stream.BeginWrite(outputData, 0, outputData.Length, WriteCallback, Connection); + TestUtility.LogInformation("Client {0:D3}: Write {1} bytes: {2} ", Connection.Id, outputData.Length, + Encoding.UTF8.GetString(outputData, 0, outputData.Length)); + } + } + + public void ReadDataCallback(IAsyncResult result) + { + WebSocketConnect client = (WebSocketConnect) result.AsyncState; + + if (client.IsDisposed) + return; + + int bytesRead = client.Stream.EndRead(result); // wait until the buffer is filled + int bytesReadIntotal = bytesRead; + ArrayList InputDataArray = new ArrayList(); + byte[] tempBuffer = null; + + if (bytesRead > 0) + { + tempBuffer = WebSocketClientUtility.SubArray(Connection.InputData, 0, bytesRead); + + Frame temp = new Frame(tempBuffer); + + // start looping if there is still remaining data + if (tempBuffer.Length < temp.DataLength) + { + if (client.TcpClient.GetStream().DataAvailable) + { + // add the first buffer to the arrayList + InputDataArray.Add(tempBuffer); + + // start looping appending to the arrayList + while (client.TcpClient.GetStream().DataAvailable) + { + bytesRead = client.TcpClient.GetStream().Read(Connection.InputData, 0, Connection.InputData.Length); + tempBuffer = WebSocketClientUtility.SubArray(Connection.InputData, 0, bytesRead); + InputDataArray.Add(tempBuffer); + bytesReadIntotal += bytesRead; + TestUtility.LogInformation("ReadDataCallback: Looping: Client {0:D3}: bytesReadHere {1} ", Connection.Id, bytesRead); + } + + // create a single byte array with the arrayList + tempBuffer = new byte[bytesReadIntotal]; + int arrayIndex = 0; + foreach (byte[] item in InputDataArray.ToArray()) + { + for (int i = 0; i < item.Length; i++) + { + tempBuffer[arrayIndex] = item[i]; + arrayIndex++; + } + } + } + } + + // Create frame with the tempBuffer + Frame frame = new Frame(tempBuffer); + ProcessReceivedData(frame); + int nextFrameIndex = frame.IndexOfNextFrame; + + while (nextFrameIndex != -1) + { + tempBuffer = tempBuffer.SubArray(frame.IndexOfNextFrame, tempBuffer.Length - frame.IndexOfNextFrame); + frame = new Frame(tempBuffer); + ProcessReceivedData(frame); + nextFrameIndex = frame.IndexOfNextFrame; + } + + if (client.IsDisposed) + return; + + // Start the Async Read to handle the next frame comming from server + client.Stream.BeginRead(client.InputData, 0, client.InputData.Length, ReadDataCallback, client); + } + else + { + client.Dispose(); + } + } + + public Frame ReadData() + { + Frame frame = new Frame(new byte[] { }); + + IAsyncResult result = Connection.Stream.BeginRead(Connection.InputData, 0, Connection.InputData.Length, null, Connection); + + if (result != null) + { + int bytesRead = Connection.Stream.EndRead(result); + if (bytesRead > 0) + { + frame = new Frame(WebSocketClientUtility.SubArray(Connection.InputData, 0, bytesRead)); + + ProcessReceivedData(frame); + + TestUtility.LogInformation("Client {0:D3}: Read Type {1} : {2} ", Connection.Id, frame.FrameType, frame.Content.Length); + } + + } + + return frame; + } + + public void SendTextData(string data) + { + Send(WebSocketClientUtility.GetFramedTextDataInBytes(data)); + } + + public void SendTextData(string data, byte opCode) + { + Send(WebSocketClientUtility.GetFramedTextDataInBytes(data, opCode)); + } + + public void SendHello() + { + Send(Frames.HELLO); + } + + public void SendPing() + { + Send(Frames.PING); + } + + public void SendPong() + { + Send(Frames.PONG); + } + public void SendClose() + { + Send(Frames.CLOSE_FRAME); + } + + public void SendPong(Frame receivedPing) + { + var pong = new byte[receivedPing.Data.Length+4]; + for (int i = 1; i < receivedPing.Data.Length; i++) + { + if(i<2) + pong[i] = receivedPing.Data[i]; + else + pong[i+4] = receivedPing.Data[i]; + } + + pong[0] = 0x8A; + pong[1] = (byte)((int)pong[1] | 128); + + Send(pong); + } + + public void CloseConnection() + { + this.WebSocketState = WebSocketState.ClosingFromClientStarted; + Send(Frames.CLOSE_FRAME); + + if (!WaitForWebSocketState(WebSocketState.ConnectionClosed)) + { + throw new Exception("Failed to close a connection"); + } + } + + public static void WriteCallback(IAsyncResult result) + { + var client = result.AsyncState as WebSocketConnect; + if (client.IsDisposed) + return; + + client.Stream.EndWrite(result); + } + + override public string ToString() + { + return Connection.Id + ": " + WebSocketState.ToString(); + } + + #region Private Methods + + public Frame Send(byte[] outputData) + { + var frame = new Frame(outputData); + ProcessSentData(frame); + if (Connection.TcpClient.Connected) + { + var result = Connection.Stream.BeginWrite(outputData, 0, outputData.Length, WriteCallback, Connection); + TestUtility.LogInformation("Client {0:D3}: Write Type {1} : {2} ", Connection.Id, frame.FrameType, + frame.Content.Length); + } + else + { + TestUtility.LogInformation("Connection is disconnected"); + } + + return frame; + } + + private void ProcessSentData(Frame frame) + { + ProcessData(frame, true); + } + + private void ProcessReceivedData(Frame frame) + { + TestUtility.LogInformation("ReadDataCallback: Client {0:D3}: Read Type {1} : {2} ", Connection.Id, frame.FrameType, frame.DataLength); + if (frame.FrameType == FrameType.NonControlFrame) + { + string content = frame.Content.ToLower(); + if (content.Contains("connection: upgrade") + && content.Contains("upgrade: websocket") + && content.Contains("http/1.1 101 switching protocols")) + { + TestUtility.LogInformation("Connection opened..."); + TestUtility.LogInformation(frame.Content); + WebSocketState = WebSocketState.ConnectionOpen; + } + } + else + { + // Send Pong if the frame was Ping + if (frame.FrameType == FrameType.Ping) + SendPong(frame); + + // Send Close if the frame was Close + if (frame.FrameType == FrameType.Close) + { + if (WebSocketState == WebSocketState.ConnectionClosed) + { + throw new Exception("Connection was already closed"); + } + else + { + if (WebSocketState != WebSocketState.ClosingFromClientStarted) + { + TestUtility.LogInformation("Send back Close frame to responsd server closing..."); + SendClose(); + } + TestUtility.LogInformation(frame.Content); + WebSocketState = WebSocketState.ConnectionClosed; + IsOpened = false; + } + } + } + ProcessData(frame, false); + } + + private void ProcessData(Frame frame, bool isSentData) + { + if (isSentData && StoreData) + StoreDataSent(frame); + else if (StoreData) + StoreDataReceived(frame); + } + + private void StoreDataReceived(Frame frame) + { + Connection.DataReceived.Add(frame); + Connection.TotalDataReceived += frame.Content.Length; + } + + private void StoreDataSent(Frame frame) + { + Connection.DataSent.Add(frame); + Connection.TotalDataSent += frame.Content.Length; + } + + #endregion + } +} diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs new file mode 100644 index 0000000000..4c2e728437 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Text; + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public static class WebSocketClientUtility + { + public static FrameType GetFrameType(byte[] inputData) + { + if(inputData.Length==0) + return FrameType.NonControlFrame; + + byte firstByte = inputData[0]; + + switch (firstByte) + { + case 0x80: + return FrameType.ContinuationFrameEnd; + case 0: + return FrameType.Continuation; + case 0x81: + return FrameType.Text; + case 0x01: + return FrameType.SegmentedText; + case 0x82: + return FrameType.Binary; + case 0x02: + return FrameType.SegmentedBinary; + case 0x88: + return FrameType.Close; + case 0x89: + return FrameType.Ping; + case 0x8A: + return FrameType.Pong; + } + return FrameType.NonControlFrame; + } + + public static string GetFrameString(byte[] inputData) + { + int frameStartingIndex; + int dataLength; + return GetFrameString(inputData, out frameStartingIndex, out dataLength); + } + + public static string GetFrameString(byte[] inputData, out int frameStartingIndex, out int frameDataLength) + { + string content; + + FrameType frameType = GetFrameType(inputData); + int startingIndex = 2; + int dataLength = 0; + + if (frameType != FrameType.NonControlFrame && frameType != FrameType.ContinuationControlled) + { + int frameLength = inputData[1]; + + if (IsFrameMasked(inputData)) + { + frameLength = inputData[1] ^ 128; + + if (frameLength < WebSocketConstants.SMALL_LENGTH_FLAG) + { + startingIndex = 6; + dataLength = inputData[1] ^ 128; + } + else if (frameLength == WebSocketConstants.SMALL_LENGTH_FLAG) + { + startingIndex = 8; + dataLength = (int)GetFrameSize(inputData, 2, 4); + } + else if (frameLength == WebSocketConstants.LARGE_LENGTH_FLAG) + { + startingIndex = 14; + dataLength = (int)GetFrameSize(inputData, 2, 10); + } + } + else + { + if (frameLength < WebSocketConstants.SMALL_LENGTH_FLAG) + { + startingIndex = 2; + dataLength = inputData[1]; + } + else if (frameLength == WebSocketConstants.SMALL_LENGTH_FLAG) + { + startingIndex = 4; + dataLength = (int)GetFrameSize(inputData, 2, 4); + } + else if (frameLength == WebSocketConstants.LARGE_LENGTH_FLAG) + { + startingIndex = 10; + dataLength = (int)GetFrameSize(inputData, 2, 10); + } + } + + content = Encoding.UTF8.GetString(inputData, startingIndex, (inputData.Length - startingIndex < dataLength) ? inputData.Length - startingIndex : dataLength); + } + else + { + startingIndex = 0; + dataLength = 0; + content = Encoding.UTF8.GetString(inputData, 0, inputData.Length); + } + + frameStartingIndex = startingIndex; + frameDataLength = dataLength; + return content; + } + + public static uint GetFrameSize(byte[] inputData, int start, int length) + { + byte[] bytes = SubArray(inputData, 2, length - 2); + + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + if (length > 4) + return BitConverter.ToUInt32(bytes, 0); + else + return BitConverter.ToUInt16(bytes, 0); + } + + public static byte[] GetFramedTextDataInBytes(string data) + { + return GetFramedDataInBytes(0x81, data); + } + public static byte[] GetFramedTextDataInBytes(string data, byte opCode) + { + return GetFramedDataInBytes(opCode, data); + } + + public static byte[] GetFramedBinaryDataInBytes(string data) + { + return GetFramedDataInBytes(0x82, data); + } + + private static byte[] GetFramedDataInBytes(byte dataType, string data) + { + var a = BitConverter.GetBytes(data.Length); + var framelist = GetByteArrayFromNumber(dataType, data.Length); + + + byte[] datalist = Encoding.UTF8.GetBytes(data); + + var frame = JoinTwoArrays(framelist, datalist); + return frame; + } + + + public static byte[] GetByteArrayFromNumber(byte dataType, int number) + { + if (number < 126) + { + return new byte[] {dataType, (byte)(number | 128),0,0,0,0 }; + } + else + { + byte lengthByte = WebSocketConstants.LARGE_LENGTH_BYTE; + int lengthBits = 16; + + if (number < 65536) + { + lengthByte = WebSocketConstants.SMALL_LENGTH_BYTE; + lengthBits = 4; + } + + var framelist = new byte[] { dataType, lengthByte }; + string hexValue = (number).ToString("X"); + hexValue = PrependZeroes(hexValue, lengthBits - hexValue.Length); + + var sizeArray = JoinTwoArrays(StringToByteArray(hexValue), new byte[]{0,0,0,0}); + + return JoinTwoArrays(framelist, sizeArray); + } + } + + public static string PrependZeroes(string hex, int zeroes) + { + for (int i = 0; i < zeroes; i++) + { + hex = "0" + hex; + } + return hex; + } + + public static byte[] StringToByteArray(string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + + public static bool IsFrameMasked(byte[] inputData) + { + bool frameMasked = false; + FrameType frameType = GetFrameType(inputData); + + if (frameType != FrameType.NonControlFrame && inputData[1] > 127) + frameMasked = true; + + return frameMasked; + } + + public static byte[] JoinTwoArrays(byte[] aArray, byte[] bArray) + { + var concat = new byte[aArray.Length + bArray.Length]; + + Buffer.BlockCopy(aArray, 0, concat, 0, aArray.Length); + Buffer.BlockCopy(bArray, 0, concat, aArray.Length, bArray.Length); + + return concat; + + } + + public static T[] SubArray(this T[] data, int index, int length) + { + T[] result = new T[length]; + Array.Copy(data, index, result, 0, length); + return result; + } + + public static string WebSocketUri = null; + public static int WebSocketVersion { get { return 13; } } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs new file mode 100644 index 0000000000..284b65e32a --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.IO; + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public class MyTcpClient : TcpClient + { + public MyTcpClient(string hostname, int port) : base(hostname, port) + { + } + + public bool IsDead { get; set; } + protected override void Dispose(bool disposing) + { + Console.WriteLine("MyClient is disposed"); + IsDead = true; + base.Dispose(disposing); + } + } + + public class WebSocketConnect : IDisposable + { + private static int globalID; + + public WebSocketConnect() + { + Id = ++globalID; + InputData = new byte[10240]; + } + + public byte[] InputData { get; set; } + public bool IsDisposed { get; set; } + + public int Id { get; set; } + + public MyTcpClient TcpClient { get; set; } + public Stream Stream { get; set; } + + public List DataSent { get; set; } + public long TotalDataSent { get; set; } + public List DataReceived { get; set; } + public long TotalDataReceived { get; set; } + + override public string ToString() + { + return Id+""; + + } + + #region IDisposable Members + + /// + /// Dispose this instance. + /// + public void Dispose() + { + Console.WriteLine("Client object is disposed"); + + IsDisposed = true; + if (Stream != null) + Stream.Close(); + + if (TcpClient != null) + TcpClient.Close(); + } + + #endregion + } +} diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs new file mode 100644 index 0000000000..ba5a43daf0 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public static class WebSocketConstants + { + public static int SMALL_LENGTH_FLAG = 126; + public static int LARGE_LENGTH_FLAG = 127; + + public static byte SMALL_LENGTH_BYTE = 0XFE; + public static byte LARGE_LENGTH_BYTE = 0XFF; + + } +} diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs new file mode 100644 index 0000000000..0c25d95f7b --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public enum WebSocketState + { + NonWebSocket, + ConnectionOpen, + ClosingFromClientStarted, + ConnectionClosed + } +} diff --git a/test/AspNetCoreModule.Test/app.config b/test/AspNetCoreModule.Test/app.config new file mode 100644 index 0000000000..49e0f8825c --- /dev/null +++ b/test/AspNetCoreModule.Test/app.config @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj new file mode 100644 index 0000000000..ef1ceb86c1 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -0,0 +1,8 @@ + + + netcoreapp2.0 + + + + + diff --git a/test/AspNetCoreModule.TestSites.Standard/IISSetupFilter.cs b/test/AspNetCoreModule.TestSites.Standard/IISSetupFilter.cs new file mode 100644 index 0000000000..7c9a92a664 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/IISSetupFilter.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.IISIntegration; + +namespace AspnetCoreModule.TestSites.Standard +{ + internal class IISSetupFilter : IStartupFilter + { + private readonly string _pairingToken; + + internal IISSetupFilter(string pairingToken) + { + _pairingToken = pairingToken; + } + + public Action Configure(Action next) + { + return app => + { + app.UseMiddleware(); + app.UseMiddleware(_pairingToken); + next(app); + }; + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs b/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs new file mode 100644 index 0000000000..d3b9aa8b01 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace AspnetCoreModule.TestSites.Standard +{ + public class ImpersonateMiddleware + { + private readonly RequestDelegate next; + public ImpersonateMiddleware(RequestDelegate next) + { + this.next = next; + } + + public async Task Invoke(HttpContext context) + { + var winIdent = context.User.Identity as WindowsIdentity; + if (winIdent == null) + { + await context.Response.WriteAsync("ImpersonateMiddleware-UserName = NoAuthentication"); + await next.Invoke(context); + } + else + { + await WindowsIdentity.RunImpersonated(winIdent.AccessToken, async () => { + string currentUserName = $"{ WindowsIdentity.GetCurrent().Name}"; + await context.Response.WriteAsync("ImpersonateMiddleware-UserName = " + currentUserName); + await next.Invoke(context); + }); + } + } + } +} diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs new file mode 100644 index 0000000000..c7c10424c9 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -0,0 +1,170 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace AspnetCoreModule.TestSites.Standard +{ + public static class Program + { + public static IApplicationLifetime AappLifetime; + public static bool AappLifetimeStopping = false; + public static int GracefulShutdownDelayTime = 0; + + private static X509Certificate2 _x509Certificate2; + + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .Build(); + + string startUpClassString = Environment.GetEnvironmentVariable("ANCMTestStartupClassName"); + IWebHostBuilder builder = null; + if (!string.IsNullOrEmpty(startUpClassString)) + { + if (startUpClassString == "StartupHTTPS") + { + // load .\testresources\testcert.pfx + string pfxPassword = "testPassword"; + if (File.Exists(@".\TestResources\testcert.pfx")) + { + _x509Certificate2 = new X509Certificate2(@".\TestResources\testcert.pfx", pfxPassword); + } + else + { + throw new Exception(@"Certificate file not found: .\TestResources\testcert.pfx of which password should " + pfxPassword); + } + + builder = new WebHostBuilder() + .UseConfiguration(config) + .UseIISIntegration() + .UseKestrel() + .UseStartup(); + } + else if (startUpClassString == "StartupCompressionCaching") + { + builder = new WebHostBuilder() + .UseConfiguration(config) + .UseIISIntegration() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); + } + else if (startUpClassString == "StartupNoCompressionCaching") + { + StartupCompressionCaching.CompressionMode = false; + builder = new WebHostBuilder() + .UseConfiguration(config) + .UseIISIntegration() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); + } + else if (startUpClassString == "StartupHelloWorld") + { + builder = new WebHostBuilder() + .UseConfiguration(config) + .UseIISIntegration() + .UseStartup(); + } + else if (startUpClassString == "StartupNtlmAuthentication") + { + builder = new WebHostBuilder() + .UseConfiguration(config) + .UseIISIntegration() + .UseStartup(); + } + else if (startUpClassString == "StartupWithShutdownDisabled") + { + builder = new WebHostBuilder() + .ConfigureServices(services => + { + const string PairingToken = "TOKEN"; + string paringToken = builder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}"); + services.AddSingleton( + new IISSetupFilter(paringToken) + ); + }) + .UseConfiguration(config) + .UseStartup(); + } + else + { + throw new Exception("Invalid startup class name : " + startUpClassString); + } + } + else + { + builder = new WebHostBuilder() + .UseConfiguration(config) + .UseIISIntegration() + .UseStartup(); + } + + string startupDelay = Environment.GetEnvironmentVariable("ANCMTestStartUpDelay"); + if (!string.IsNullOrEmpty(startupDelay)) + { + Startup.SleeptimeWhileStarting = Convert.ToInt32(startupDelay); + } + + if (Startup.SleeptimeWhileStarting != 0) + { + Thread.Sleep(Startup.SleeptimeWhileStarting); + } + + string shutdownDelay = Environment.GetEnvironmentVariable("ANCMTestShutdownDelay"); + if (!string.IsNullOrEmpty(shutdownDelay)) + { + Startup.SleeptimeWhileClosing = Convert.ToInt32(shutdownDelay); + } + + builder.UseKestrel(); + + var host = builder.Build(); + AappLifetime = (IApplicationLifetime)host.Services.GetService(typeof(IApplicationLifetime)); + + string gracefulShutdownDelay = Environment.GetEnvironmentVariable("GracefulShutdownDelayTime"); + if (!string.IsNullOrEmpty(gracefulShutdownDelay)) + { + GracefulShutdownDelayTime = Convert.ToInt32(gracefulShutdownDelay); + } + AappLifetime.ApplicationStarted.Register( + () => { + Thread.Sleep(1000); + } + ); + AappLifetime.ApplicationStopping.Register( + () => { + AappLifetimeStopping = true; + Thread.Sleep(Startup.SleeptimeWhileClosing / 2); + } + ); + AappLifetime.ApplicationStopped.Register( + () => { + Thread.Sleep(Startup.SleeptimeWhileClosing / 2); + Startup.SleeptimeWhileClosing = 0; // All of SleeptimeWhileClosing is used now + } + ); + try + { + host.Run(); + } + catch + { + // ignore + } + + if (Startup.SleeptimeWhileClosing != 0) + { + Thread.Sleep(Startup.SleeptimeWhileClosing); + } + } + } +} + diff --git a/test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json b/test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json new file mode 100644 index 0000000000..76176b9680 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": true, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39982/", + "sslPort": 44375 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "https://localhost:44375/", + "environmentVariables": { + "ANCMTestStartupClassName": "StartupHTTPS", + "ASPNET_ENVIRONMENT": "HelloWorld" + } + }, + "web": { + "commandName": "web", + "environmentVariables": { + "ASPNET_ENVIRONMENT": "HelloWorld" + } + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs new file mode 100644 index 0000000000..af68d68aa4 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -0,0 +1,351 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AspnetCoreModule.TestSites.Standard +{ + public class Startup + { + public static int SleeptimeWhileClosing = 0; + public static int SleeptimeWhileStarting = 0; + public static List MemoryLeakList = new List(); + + public void ConfigureServices(IServiceCollection services) + { + services.Configure(options => { + }); + } + + private async Task Echo(WebSocket webSocket) + { + var buffer = new byte[1024 * 4]; + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + bool closeFromServer = false; + + while (!result.CloseStatus.HasValue) + { + if ((result.Count == "CloseFromServer".Length && System.Text.Encoding.ASCII.GetString(buffer).Substring(0, result.Count) == "CloseFromServer") + || Program.AappLifetimeStopping == true) + { + // start closing handshake from backend process + await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "ClosingFromServer", CancellationToken.None); + closeFromServer = true; + } + else + { + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + } + + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + + if (closeFromServer) + { + return; + } + + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(minLevel: LogLevel.Warning); + + app.Map("/websocketSubProtocol", subApp => + { + app.UseWebSockets(new WebSocketOptions + { + }); + + subApp.Use(async (context, next) => + { + if (context.WebSockets.IsWebSocketRequest) + { + var webSocket = await context.WebSockets.AcceptWebSocketAsync("mywebsocketsubprotocol"); + await Echo(webSocket); + } + else + { + var wsScheme = context.Request.IsHttps ? "wss" : "ws"; + var wsUrl = $"{wsScheme}://{context.Request.Host.Host}:{context.Request.Host.Port}{context.Request.Path}"; + await context.Response.WriteAsync($"Ready to accept a WebSocket request at: {wsUrl}"); + } + }); + }); + + app.Map("/websocket", subApp => + { + app.UseWebSockets(new WebSocketOptions + { + }); + + subApp.Use(async (context, next) => + { + if (context.WebSockets.IsWebSocketRequest) + { + var webSocket = await context.WebSockets.AcceptWebSocketAsync(""); + await Echo(webSocket); + } + else + { + var wsScheme = context.Request.IsHttps ? "wss" : "ws"; + var wsUrl = $"{wsScheme}://{context.Request.Host.Host}:{context.Request.Host.Port}{context.Request.Path}"; + await context.Response.WriteAsync($"Ready to accept a WebSocket request at: {wsUrl}"); + } + }); + }); + + app.Map("/GetProcessId", subApp => + { + subApp.Run(context => + { + var process = Process.GetCurrentProcess(); + return context.Response.WriteAsync(process.Id.ToString()); + }); + }); + + app.Map("/EchoPostData", subApp => + { + subApp.Run(context => + { + string responseBody = string.Empty; + if (string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) + { + var form = context.Request.ReadFormAsync().GetAwaiter().GetResult(); + int counter = 0; + foreach (var key in form.Keys) + { + StringValues output; + if (form.TryGetValue(key, out output)) + { + responseBody += key + "="; + foreach (var line in output) + { + responseBody += line; + } + if (++counter < form.Count) + { + responseBody += "&"; + } + } + } + } + else + { + responseBody = "NoAction"; + } + return context.Response.WriteAsync(responseBody); + }); + }); + + app.Map("/contentlength", subApp => + { + subApp.Run(context => + { + context.Response.ContentLength = 14; + return context.Response.WriteAsync("Content Length"); + }); + }); + + app.Map("/connectionclose", subApp => + { + subApp.Run(async context => + { + context.Response.Headers[HeaderNames.Connection] = "close"; + await context.Response.WriteAsync("Connnection Close"); + await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering + }); + }); + + app.Map("/notchunked", subApp => + { + subApp.Run(async context => + { + var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + //context.Response.ContentLength = encoding.GetByteCount(document); + context.Response.ContentType = "text/html;charset=UTF-8"; + await context.Response.WriteAsync("NotChunked", encoding, context.RequestAborted); + await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering + }); + }); + + app.Map("/chunked", subApp => + { + subApp.Run(async context => + { + await context.Response.WriteAsync("Chunked"); + await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering + }); + }); + + app.Map("/manuallychunked", subApp => + { + subApp.Run(context => + { + context.Response.Headers[HeaderNames.TransferEncoding] = "chunked"; + return context.Response.WriteAsync("10\r\nManually Chunked\r\n0\r\n\r\n"); + }); + }); + + app.Map("/manuallychunkedandclose", subApp => + { + subApp.Run(context => + { + context.Response.Headers[HeaderNames.Connection] = "close"; + context.Response.Headers[HeaderNames.TransferEncoding] = "chunked"; + return context.Response.WriteAsync("1A\r\nManually Chunked and Close\r\n0\r\n\r\n"); + }); + }); + + app.Map("/ImpersonateMiddleware", subApp => + { + subApp.UseMiddleware(); + subApp.Run(context => + { + return context.Response.WriteAsync(""); + }); + }); + + app.Run(context => + { + string response = "Running"; + string[] paths = context.Request.Path.Value.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string item in paths) + { + string action = string.Empty; + string parameter = string.Empty; + + action = "DoSleep"; + if (item.StartsWith(action)) + { + /* + Process "DoSleep" command here. + For example, if path contains "DoSleep" such as /DoSleep1000, run Thread.Sleep(1000) + */ + int sleepTime = 1000; + if (item.Length > action.Length) + { + parameter = item.Substring(action.Length); + sleepTime = Convert.ToInt32(parameter); + } + Thread.Sleep(sleepTime); + } + + action = "MemoryLeak"; + if (item.StartsWith(action)) + { + parameter = "1024"; + if (item.Length > action.Length) + { + parameter = item.Substring(action.Length); + } + long size = Convert.ToInt32(parameter); + var rnd = new Random(); + byte[] b = new byte[size * 1024]; + b[rnd.Next(0, b.Length)] = byte.MaxValue; + MemoryLeakList.Add(b); + response = "MemoryLeak, size:" + size.ToString() + " KB, total: " + MemoryLeakList.Count.ToString(); + } + + action = "ExpandEnvironmentVariables"; + if (item.StartsWith(action)) + { + if (item.Length > action.Length) + { + parameter = item.Substring(action.Length); + response = Environment.ExpandEnvironmentVariables("%" + parameter + "%"); + } + } + + action = "GetEnvironmentVariables"; + if (item.StartsWith(action)) + { + parameter = item.Substring(action.Length); + response = Environment.GetEnvironmentVariables().Count.ToString(); + } + + action = "DumpEnvironmentVariables"; + if (item.StartsWith(action)) + { + response = String.Empty; + + foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) + { + response += de.Key + ":" + de.Value + "
"; + } + } + + action = "GetRequestHeaderValue"; + if (item.StartsWith(action)) + { + if (item.Length > action.Length) + { + parameter = item.Substring(action.Length); + + if (context.Request.Headers.ContainsKey(parameter)) + { + response = context.Request.Headers[parameter]; + } + else + { + response = ""; + } + } + } + + action = "DumpRequestHeaders"; + if (item.StartsWith(action)) + { + response = String.Empty; + + foreach (var de in context.Request.Headers) + { + response += de.Key + ":" + de.Value + "
"; + } + } + + // This action can be used for testing invalid Transfer-Encoding response header + action = "SetTransferEncoding"; + if (item.StartsWith(action)) + { + if (item.Length > action.Length) + { + // this is the default response which is valid for chunked encoding + response = "10\r\nManually Chunked\r\n0\r\n\r\n"; + parameter = item.Substring(action.Length); + + // valid encoding value: "chunked" or "chunked,gzip" + string encoding = parameter; + var tokens = parameter.Split("_"); + + // if respons body value was also given after "_" delimeter, use the value as response data. + if (tokens.Length == 2) + { + encoding = tokens[0]; + response = tokens[1]; + } + context.Response.Headers[HeaderNames.TransferEncoding] = encoding; + return context.Response.WriteAsync(response); + } + } + } + return context.Response.WriteAsync(response); + }); + } + } +} diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs b/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs new file mode 100644 index 0000000000..fb22ff3c24 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using System; + +namespace AspnetCoreModule.TestSites.Standard +{ + public class StartupCompressionCaching + { + public static bool CompressionMode = true; + + public void ConfigureServices(IServiceCollection services) + { + if (CompressionMode) + { + services.AddResponseCompression(); + } + services.AddResponseCaching(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + if (CompressionMode) + { + app.UseResponseCompression(); + } + app.UseResponseCaching(); + app.UseDefaultFiles(); + app.UseStaticFiles( + new StaticFileOptions() + { + OnPrepareResponse = context => + { + // + // FYI, below line can be simplified with + // context.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=10"; + // + context.Context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue() + { + Public = true, + MaxAge = TimeSpan.FromSeconds(10) + }; + context.Context.Response.Headers.Append("MyCustomHeader", DateTime.Now.Second.ToString()); + var accept = context.Context.Request.Headers[HeaderNames.AcceptEncoding]; + if (!StringValues.IsNullOrEmpty(accept)) + { + context.Context.Response.Headers.Append(HeaderNames.Vary, HeaderNames.AcceptEncoding); + } + context.Context.Response.ContentType = "text/plain"; + } + } + ); + } + } +} diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs b/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs new file mode 100644 index 0000000000..7399bac55d --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace AspnetCoreModule.TestSites.Standard +{ + public class StartupHelloWorld + { + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(minLevel: LogLevel.Warning); + + app.Run(ctx => + { + return ctx.Response.WriteAsync("Hello World"); + }); + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs new file mode 100644 index 0000000000..bab2f1e32f --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace AspnetCoreModule.TestSites.Standard +{ + public class StartupNtlmAuthentication + { + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(minLevel: LogLevel.Warning); + + app.Use(async (context, next) => + { + try + { + await next(); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + throw; + } + context.Response.Clear(); + context.Response.StatusCode = 500; + await context.Response.WriteAsync(ex.ToString()); + } + }); + + app.Use((context, next) => + { + if (context.Request.Path.Equals("/Anonymous")) + { + return context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated); + } + + if (context.Request.Path.Equals("/Restricted")) + { + if (context.User.Identity.IsAuthenticated) + { + return context.Response.WriteAsync(context.User.Identity.AuthenticationType); + } + else + { + return context.ChallengeAsync(); + } + } + + if (context.Request.Path.Equals("/Forbidden")) + { + return context.ForbidAsync(); + + } + + if (context.Request.Path.Equals("/AutoForbid")) + { + return context.ChallengeAsync(); + } + + if (context.Request.Path.Equals("/RestrictedNegotiate")) + { + if (string.Equals("Negotiate", context.User.Identity.AuthenticationType, System.StringComparison.Ordinal)) + { + return context.Response.WriteAsync("Negotiate"); + } + else + { + return context.ChallengeAsync("Negotiate"); + } + } + + if (context.Request.Path.Equals("/RestrictedNTLM")) + { + if (string.Equals("NTLM", context.User.Identity.AuthenticationType, System.StringComparison.Ordinal)) + { + return context.Response.WriteAsync("NTLM"); + } + else + { + return context.ChallengeAsync("NTLM"); + } + } + + return context.Response.WriteAsync("Running NTLM"); + }); + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/TestMiddleWareBeforeIISMiddleWare.cs b/test/AspNetCoreModule.TestSites.Standard/TestMiddleWareBeforeIISMiddleWare.cs new file mode 100644 index 0000000000..f2b4e87e08 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/TestMiddleWareBeforeIISMiddleWare.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using System; +using System.Security.Principal; +using System.Threading.Tasks; + + +namespace AspnetCoreModule.TestSites.Standard + +{ + public class TestMiddleWareBeforeIISMiddleWare + { + private readonly RequestDelegate next; + + public TestMiddleWareBeforeIISMiddleWare(RequestDelegate next) + { + this.next = next; + } + + public async Task Invoke(HttpContext context) + { + // if the given request is shutdown message from ANCM and the value of GracefulShutdown environment variable is set, + // the shutdown message is handled by this middleware instead of IISMiddleware. + + if (HttpMethods.IsPost(context.Request.Method) && + context.Request.Path.ToString().EndsWith("/iisintegration") && + string.Equals("shutdown", context.Request.Headers["MS-ASPNETCORE-EVENT"], StringComparison.OrdinalIgnoreCase)) + { + string shutdownMode = Environment.GetEnvironmentVariable("GracefulShutdown"); + if (!string.IsNullOrWhiteSpace(shutdownMode) && shutdownMode.ToLower().StartsWith("disabled")) + { + //ignore shutdown Message returning 200 instead of 202 because the gracefulshutdown is disabled + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsync("Called ShutdownMessage with disabled of GracefulShutdown"); + return; + } + } + await next.Invoke(context); + } + } +} diff --git a/test/AspNetCoreModule.TestSites.Standard/TestResources/testCert.pfx b/test/AspNetCoreModule.TestSites.Standard/TestResources/testCert.pfx new file mode 100644 index 0000000000..7118908c2d Binary files /dev/null and b/test/AspNetCoreModule.TestSites.Standard/TestResources/testCert.pfx differ diff --git a/test/AspNetCoreModule.TestSites.Standard/web.config b/test/AspNetCoreModule.TestSites.Standard/web.config new file mode 100644 index 0000000000..21a7c68e2d --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/web.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/WebRoot/ErrorHandling_NotExisting/web.config b/test/WebRoot/ErrorHandling_NotExisting/web.config new file mode 100644 index 0000000000..4004a72fcc --- /dev/null +++ b/test/WebRoot/ErrorHandling_NotExisting/web.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/WebRoot/URLRewrite/article.aspx b/test/WebRoot/URLRewrite/article.aspx new file mode 100644 index 0000000000..a7356985d9 --- /dev/null +++ b/test/WebRoot/URLRewrite/article.aspx @@ -0,0 +1,25 @@ +<%@ Page Language="C#" %> + + + + +URL Rewrite Module Test + + +

URL Rewrite Module Test Page

+ + + + + + + + + + + + + +
Server VariableValue
Original URL: <%= Request.ServerVariables["HTTP_X_ORIGINAL_URL"] %>
Final URL: <%= Request.ServerVariables["SCRIPT_NAME"] + "?" + Request.ServerVariables["QUERY_STRING"] %>
+ + \ No newline at end of file diff --git a/test/WebRoot/URLRewrite/default.htm b/test/WebRoot/URLRewrite/default.htm new file mode 100644 index 0000000000..082904a8e1 --- /dev/null +++ b/test/WebRoot/URLRewrite/default.htm @@ -0,0 +1,11 @@ + + + + +IIS Windows + + + +URLRewrite + + \ No newline at end of file diff --git a/test/WebRoot/URLRewrite/web.config b/test/WebRoot/URLRewrite/web.config new file mode 100644 index 0000000000..79d3fa79ff --- /dev/null +++ b/test/WebRoot/URLRewrite/web.config @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/WebRoot/URLRewrite/web.config.bak b/test/WebRoot/URLRewrite/web.config.bak new file mode 100644 index 0000000000..7e914efb5c --- /dev/null +++ b/test/WebRoot/URLRewrite/web.config.bak @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/WebRoot/WebSite1/bkg-blu.jpg b/test/WebRoot/WebSite1/bkg-blu.jpg new file mode 100644 index 0000000000..4eff83ac8e Binary files /dev/null and b/test/WebRoot/WebSite1/bkg-blu.jpg differ diff --git a/test/WebRoot/WebSite1/iis.png b/test/WebRoot/WebSite1/iis.png new file mode 100644 index 0000000000..29f1c90859 Binary files /dev/null and b/test/WebRoot/WebSite1/iis.png differ diff --git a/test/WebRoot/WebSite1/iisstart.htm b/test/WebRoot/WebSite1/iisstart.htm new file mode 100644 index 0000000000..53fa9d0858 --- /dev/null +++ b/test/WebRoot/WebSite1/iisstart.htm @@ -0,0 +1,32 @@ + + + + +IIS Windows + + + +
+IIS +
+ + \ No newline at end of file diff --git a/test/WebRoot/WebSite1/msweb-brand.png b/test/WebRoot/WebSite1/msweb-brand.png new file mode 100644 index 0000000000..c8d842af18 Binary files /dev/null and b/test/WebRoot/WebSite1/msweb-brand.png differ diff --git a/test/WebRoot/WebSite1/small.htm b/test/WebRoot/WebSite1/small.htm new file mode 100644 index 0000000000..64c3ecd7ab --- /dev/null +++ b/test/WebRoot/WebSite1/small.htm @@ -0,0 +1 @@ +small \ No newline at end of file diff --git a/test/WebRoot/WebSite1/test.asp b/test/WebRoot/WebSite1/test.asp new file mode 100644 index 0000000000..87caddf4e2 --- /dev/null +++ b/test/WebRoot/WebSite1/test.asp @@ -0,0 +1,13 @@ + + + +<% +if request.form("cars") = "volvo" then +Response.write ("Hello " & Request.form("title") & " " & Request.form("FirstName") & " " & Request.form("LastName")) +Response.write ("
How are you today?") +Response.Write("
you have a " & Request.form("cars")) +Response.write("
comment " & Request.form("textarea")) +end if +%> + + diff --git a/test/WebRoot/WebSite1/w-brand.png b/test/WebRoot/WebSite1/w-brand.png new file mode 100644 index 0000000000..3fe9d7832f Binary files /dev/null and b/test/WebRoot/WebSite1/w-brand.png differ diff --git a/test/WebRoot/WebSite1/web.config b/test/WebRoot/WebSite1/web.config new file mode 100644 index 0000000000..07ce3d2015 --- /dev/null +++ b/test/WebRoot/WebSite1/web.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/WebRoot/WebSite1/web.config.bak b/test/WebRoot/WebSite1/web.config.bak new file mode 100644 index 0000000000..07ce3d2015 --- /dev/null +++ b/test/WebRoot/WebSite1/web.config.bak @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/ChatHandler.ashx b/test/WebRoot/WebSocket/ChatHandler.ashx new file mode 100644 index 0000000000..714fbecac2 --- /dev/null +++ b/test/WebRoot/WebSocket/ChatHandler.ashx @@ -0,0 +1,70 @@ +<%@ WebHandler Language="C#" Class="ChatStartHandler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Collections.Generic; +using System.Collections.Concurrent; + +public class ChatStartHandler : IHttpHandler +{ + + static int clientCount=0; + + public void ProcessRequest(HttpContext context) + { + + context.AcceptWebSocketRequest(async wsContext => + { + ArraySegment buffer = new ArraySegment(new byte[1024]); + WebSocket socket = wsContext.WebSocket; + ChatList.ActiveChatSessions.TryAdd(clientCount++, socket); + + // set up the loop + while (true) + { + Thread.Sleep(100); + var input = await socket.ReceiveAsync(buffer, CancellationToken.None); + + if (input.CloseStatus != null) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + else + { + + foreach (KeyValuePair kvp in ChatList.ActiveChatSessions) + { + WebSocket ws = kvp.Value; + if (ws.State == WebSocketState.Open) + { + var outputBuffer = new ArraySegment(buffer.Array, 0, input.Count); + await ws.SendAsync(outputBuffer, input.MessageType, input.EndOfMessage, CancellationToken.None); + } + } + } + } + }); + //}, new System.Web.WebSockets.AspNetWebSocketOptions { Subprotocol = "ECHO" }); + } + + public bool IsReusable + { + get + { + return false; + } + } + +} + +public static class ChatList +{ + public static ConcurrentDictionary ActiveChatSessions = new ConcurrentDictionary(); +} diff --git a/test/WebRoot/WebSocket/EchoHandler.ashx b/test/WebRoot/WebSocket/EchoHandler.ashx new file mode 100644 index 0000000000..d3cd0cb77c --- /dev/null +++ b/test/WebRoot/WebSocket/EchoHandler.ashx @@ -0,0 +1,50 @@ +<%@ WebHandler Language="C#" Class="Handler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; + +public class Handler : IHttpHandler +{ + public void ProcessRequest(HttpContext context) + { + context.AcceptWebSocketRequest(async wsContext => + { + //ArraySegment buffer = new ArraySegment(new byte[1024*1024]); + ArraySegment buffer = new ArraySegment(new byte[1024]); + WebSocket socket = wsContext.WebSocket; + + // set up the loop + while (true) + { + var input = await socket.ReceiveAsync(buffer, CancellationToken.None); + //Thread.Sleep(100); + + if (input.CloseStatus != null) + { + await socket.CloseAsync(input.CloseStatus.Value, input.CloseStatusDescription, CancellationToken.None); + break; + } + else + { + var outputBuffer = new ArraySegment(buffer.Array, 0, input.Count); + await socket.SendAsync(outputBuffer, input.MessageType, input.EndOfMessage, CancellationToken.None); + } + } + }); + } + + public bool IsReusable + { + get + { + return false; + } + } + +} \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/ChatHandler.ashx b/test/WebRoot/WebSocket/OtherExamples/ChatHandler.ashx new file mode 100644 index 0000000000..99bea256d3 --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/ChatHandler.ashx @@ -0,0 +1,69 @@ +<%@ WebHandler Language="C#" Class="ChatStartHandler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Collections.Generic; +using System.Collections.Concurrent; + +public class ChatStartHandler : IHttpHandler +{ + + static int clientCount=0; + + public void ProcessRequest(HttpContext context) + { + + context.AcceptWebSocketRequest(async wsContext => + { + ArraySegment buffer = new ArraySegment(new byte[1024]); + WebSocket socket = wsContext.WebSocket; + ChatList.ActiveChatSessions.TryAdd(clientCount++, socket); + + // set up the loop + while (true) + { + var input = await socket.ReceiveAsync(buffer, CancellationToken.None); + + if (input.CloseStatus != null) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + else + { + + foreach (KeyValuePair kvp in ChatList.ActiveChatSessions) + { + WebSocket ws = kvp.Value; + if (ws.State == WebSocketState.Open) + { + var outputBuffer = new ArraySegment(buffer.Array, 0, input.Count); + await ws.SendAsync(outputBuffer, input.MessageType, input.EndOfMessage, CancellationToken.None); + } + } + } + } + }); + //}, new System.Web.WebSockets.AspNetWebSocketOptions { Subprotocol = "ECHO" }); + } + + public bool IsReusable + { + get + { + return false; + } + } + +} + +public static class ChatList +{ + public static ConcurrentDictionary ActiveChatSessions = new ConcurrentDictionary(); +} diff --git a/test/WebRoot/WebSocket/OtherExamples/Default.aspx b/test/WebRoot/WebSocket/OtherExamples/Default.aspx new file mode 100644 index 0000000000..1b6e9f6de0 --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/Default.aspx @@ -0,0 +1,70 @@ +<%@ Page Language="C#" AutoEventWireUp="true" %> + + + + + +

#active websocket connection <%= "foo" %>

+ +

Web Socket Echo Demo (run from Minefield!)

+ + + + + + +

+ + +This text will be sent on the socket: + + + +

+ + + + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/Default.htm b/test/WebRoot/WebSocket/OtherExamples/Default.htm new file mode 100644 index 0000000000..7c3b07ba22 --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/Default.htm @@ -0,0 +1,70 @@ +<%@ Page Language="C#" AutoEventWireUp="true" %> + + + + + +

#active websocket connection <%= "foo" %>

+ +

Web Socket Echo Demo (run from Minefield!)

+ + + + + + +

+ + +This text will be sent on the socket: + + + +

+ + + + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/DownloaderHandler.ashx b/test/WebRoot/WebSocket/OtherExamples/DownloaderHandler.ashx new file mode 100644 index 0000000000..b5cf323933 --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/DownloaderHandler.ashx @@ -0,0 +1,112 @@ +<%@ WebHandler Language="C#" Class="Handler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Collections.Generic; +using System.Linq; + +public class Handler : IHttpHandler +{ + const int BUFFER = 1024*5; + bool StopFalg = false; + + public void ProcessRequest(HttpContext context) + { + if (!context.IsWebSocketRequest) + { + HttpRequest Request = context.Request; + HttpResponse Response = context.Response; + Response.Write("Simple Http"); + return; + } + + context.AcceptWebSocketRequest(async wsContext => + { + + // set up the loop + WebSocket socket = wsContext.WebSocket; + WebSocketMessageType responseType = WebSocketMessageType.Text; + int returnSize = 1024*1024; + + Thread.Sleep(500); + + Task.Run(() => + { + Recieve(socket); + }); + + Thread.Sleep(500); + + while (socket.State == WebSocketState.Open) + { + int bytesLeft = returnSize; + var tempString = string.Empty; + + while (bytesLeft > BUFFER) + { + tempString = RandomString(BUFFER); + bytesLeft -= BUFFER; + await socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(tempString)), responseType, false, CancellationToken.None); + Thread.Sleep(500); + } + + if (bytesLeft <= BUFFER && bytesLeft >= 0) + { + tempString = RandomString(bytesLeft); + await socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(tempString)), responseType, true, CancellationToken.None); + Thread.Sleep(500); + } + + if (StopFalg) break; + } + }); + } + + async Task Recieve(WebSocket webSocket) + { + ArraySegment buffer = new ArraySegment(new byte[1024]); + while (webSocket.State == WebSocketState.Open) + { + WebSocketReceiveResult input = await webSocket.ReceiveAsync(buffer, CancellationToken.None); + if (input.CloseStatus == WebSocketCloseStatus.NormalClosure) + { + StopFalg = true; + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + } + } + + + public bool IsReusable + { + get + { + return false; + } + } + + public string RandomString(int size) + { + if (size < 1) + return string.Empty; + + Random random = new Random((int)DateTime.Now.Ticks); + StringBuilder builder = new StringBuilder(); + char ch; + for (int i = 0; i < size; i++) + { + ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); + builder.Append(ch); + } + + return builder.ToString(); + } + +} \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/GetWebSocketConnectionCount.ashx b/test/WebRoot/WebSocket/OtherExamples/GetWebSocketConnectionCount.ashx new file mode 100644 index 0000000000..c474915a50 --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/GetWebSocketConnectionCount.ashx @@ -0,0 +1,28 @@ +<%@ WebHandler Language="C#" Class="GetWebSocketConnectionCount" %> + +using System; +using System.Web; + +public class GetWebSocketConnectionCount : IHttpHandler { + + public void ProcessRequest (HttpContext context) { + + System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(@"System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + object mc = assembly.CreateInstance("System.Web.WebSockets.AspNetWebSocketManager"); + Type t = mc.GetType(); + System.Reflection.BindingFlags bf =System.Reflection.BindingFlags.Static| System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public; + //MethodInfo mi = t.GetProperty + object temp1 = t.GetField("Current", bf).GetValue(null); + object temp2 = t.GetField("_activeSockets", bf).GetValue(temp1); + + context.Response.ContentType = "text/plain"; + context.Response.Write("Active WebSocket Connections="+((System.Collections.Generic.HashSet)temp2).Count); + } + + public bool IsReusable { + get { + return false; + } + } + +} \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/HandleBinaryEcho.ashx b/test/WebRoot/WebSocket/OtherExamples/HandleBinaryEcho.ashx new file mode 100644 index 0000000000..aab07dd02e --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/HandleBinaryEcho.ashx @@ -0,0 +1,166 @@ +<%@ WebHandler Language="C#" Class="Handler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Collections.Generic; +using System.Linq; + +public class Handler : IHttpHandler +{ + const int BUFFER = 1000 * 1000; + + public void ProcessRequest(HttpContext context) + { + if (!context.IsWebSocketRequest) + { + HttpRequest Request = context.Request; + HttpResponse Response = context.Response; + Response.Write("Simple Http"); + return; + } + + context.AcceptWebSocketRequest(async wsContext => + { + + // set up the loop + WebSocket socket = wsContext.WebSocket; + + ////determine return type + List query = wsContext.SecWebSocketProtocols.ToList(); + + WebSocketMessageType responseType = WebSocketMessageType.Text; + if (query[0].Split('-')[0] == WebSocketMessageType.Binary.ToString()) + { + responseType = WebSocketMessageType.Binary; + } + WebSocketMessageType requestType = WebSocketMessageType.Text; + if (query[0].Split('-')[1] == WebSocketMessageType.Binary.ToString()) + { + requestType = WebSocketMessageType.Binary; + } + int returnSize = Int32.Parse(query[0].Split('-')[2]); + int requestSize = Int32.Parse(query[0].Split('-')[3]); + bool canSend = Boolean.Parse(query[0].Split('-')[4]); + bool canReceive = Boolean.Parse(query[0].Split('-')[5]); + + + + + + if (canSend && !canReceive) + { + await Send(socket, responseType, returnSize); + } + + else if (canReceive && !canSend) + { + await Recieve(socket, requestType); + } + + else if (canReceive && canSend) + { + + Task.Run(() => + { + Recieve(socket, requestType); + }); + + while (socket.State == WebSocketState.Open) + { + int bytesLeft = returnSize; + var tempString = string.Empty; + + while (bytesLeft > BUFFER) + { + tempString = RandomString(BUFFER); + bytesLeft -= BUFFER; + await socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(tempString)), responseType, false, CancellationToken.None); + Thread.Sleep(200); + } + + if (bytesLeft <= BUFFER && bytesLeft >= 0) + { + tempString = RandomString(bytesLeft); + await socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(tempString)), responseType, true, CancellationToken.None); + Thread.Sleep(500); + } + } + } + else + { + + Task.Run(() => + { + Recieve(socket, requestType); + }); + } + }); + } + + async Task Recieve(WebSocket webSocket, WebSocketMessageType messageType) + { + ArraySegment buffer = new ArraySegment(new byte[1024]); + while (webSocket.State == WebSocketState.Open) + { + WebSocketReceiveResult input = await webSocket.ReceiveAsync(buffer, CancellationToken.None); + if (input.CloseStatus == WebSocketCloseStatus.NormalClosure) + { + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + } + } + + async Task Send(WebSocket socket, WebSocketMessageType responseType, int returnSize) + { + while (socket.State == WebSocketState.Open) + { + int bytesLeft = returnSize; + + while (bytesLeft > BUFFER) + { + bytesLeft -= BUFFER; + await socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(RandomString(BUFFER))), responseType, false, CancellationToken.None); + Thread.Sleep(200); + } + + if (bytesLeft <= BUFFER && bytesLeft >= 0) + { + await socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(RandomString(bytesLeft))), responseType, true, CancellationToken.None); + Thread.Sleep(500); + } + } + } + + public bool IsReusable + { + get + { + return false; + } + } + + public string RandomString(int size) + { + if (size < 1) + return string.Empty; + + Random random = new Random((int)DateTime.Now.Ticks); + StringBuilder builder = new StringBuilder(); + char ch; + for (int i = 0; i < size; i++) + { + ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); + builder.Append(ch); + } + + return builder.ToString(); + } + +} \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/Handler.ashx b/test/WebRoot/WebSocket/OtherExamples/Handler.ashx new file mode 100644 index 0000000000..02f969a3df --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/Handler.ashx @@ -0,0 +1,49 @@ +<%@ WebHandler Language="C#" Class="Handler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; + +public class Handler : IHttpHandler +{ + public void ProcessRequest(HttpContext context) + { + context.AcceptWebSocketRequest(async wsContext => + { + ArraySegment buffer = new ArraySegment(new byte[1024*1024]); + WebSocket socket = wsContext.WebSocket; + + // set up the loop + while (true) + { + var input = await socket.ReceiveAsync(buffer, CancellationToken.None); + Thread.Sleep(100); + + if (input.CloseStatus != null) + { + await socket.CloseAsync(input.CloseStatus.Value, input.CloseStatusDescription, CancellationToken.None); + break; + } + else + { + var outputBuffer = new ArraySegment(buffer.Array, 0, input.Count); + await socket.SendAsync(outputBuffer, input.MessageType, input.EndOfMessage, CancellationToken.None); + } + } + }); + } + + public bool IsReusable + { + get + { + return false; + } + } + +} \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/UploadHandler.ashx b/test/WebRoot/WebSocket/OtherExamples/UploadHandler.ashx new file mode 100644 index 0000000000..99d7a90162 --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/UploadHandler.ashx @@ -0,0 +1,49 @@ +<%@ WebHandler Language="C#" Class="Handler" %> + +using System; +using System.Web; +using System.Net.WebSockets; +using System.Web.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Collections.Generic; +using System.Linq; + +public class Handler : IHttpHandler +{ + public void ProcessRequest(HttpContext context) + { + + context.AcceptWebSocketRequest(async wsContext => + { + + // set up the loop + WebSocket socket = wsContext.WebSocket; + + ArraySegment buffer = new ArraySegment(new byte[1024]); + + while (socket.State == WebSocketState.Open) + { + WebSocketReceiveResult input = await socket.ReceiveAsync(buffer, CancellationToken.None); + if (input.CloseStatus == WebSocketCloseStatus.NormalClosure) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + } + + }); + } + + + public bool IsReusable + { + get + { + return false; + } + } + +} \ No newline at end of file diff --git a/test/WebRoot/WebSocket/OtherExamples/web.config b/test/WebRoot/WebSocket/OtherExamples/web.config new file mode 100644 index 0000000000..439329d7ec --- /dev/null +++ b/test/WebRoot/WebSocket/OtherExamples/web.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/chat.aspx b/test/WebRoot/WebSocket/chat.aspx new file mode 100644 index 0000000000..96448b0b18 --- /dev/null +++ b/test/WebRoot/WebSocket/chat.aspx @@ -0,0 +1,62 @@ +<%@ Page Language="C#" AutoEventWireUp="true" %> + + +

#active websocket connection <%= "foo" %>

+

Web Socket Echo Demo (run from Minefield!)

+ + + + +

+This text will be sent on the socket: + + +

+ + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/chatplus.html b/test/WebRoot/WebSocket/chatplus.html new file mode 100644 index 0000000000..ca903f4e63 --- /dev/null +++ b/test/WebRoot/WebSocket/chatplus.html @@ -0,0 +1,366 @@ + + + + + + + + + + + +

WebSocket Sample Application

+

Ready to connect...

+
+ + + +
+

+
+ + + + + +
+

Communication Log

+ + + + + + + + + +
FromToData
+ + + + + + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/echo.aspx b/test/WebRoot/WebSocket/echo.aspx new file mode 100644 index 0000000000..c055ee8300 --- /dev/null +++ b/test/WebRoot/WebSocket/echo.aspx @@ -0,0 +1,65 @@ +<%@ Page Language="C#" AutoEventWireUp="true" %> + + +

#active websocket connection <%= "foo" %>

+

Web Socket Echo Demo (run from Minefield!)

+ + + + +

+This text will be sent on the socket: + + +

+ + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/echoAspnetCore.aspx b/test/WebRoot/WebSocket/echoAspnetCore.aspx new file mode 100644 index 0000000000..3fe9deaf28 --- /dev/null +++ b/test/WebRoot/WebSocket/echoAspnetCore.aspx @@ -0,0 +1,62 @@ +<%@ Page Language="C#" AutoEventWireUp="true" %> + + +

#active websocket connection <%= "foo" %>

+

Web Socket Echo Demo (run from Minefield!)

+ + + + +

+This text will be sent on the socket: + + +

+ + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/echoSubProtocol.aspx b/test/WebRoot/WebSocket/echoSubProtocol.aspx new file mode 100644 index 0000000000..f349a3f96e --- /dev/null +++ b/test/WebRoot/WebSocket/echoSubProtocol.aspx @@ -0,0 +1,65 @@ +<%@ Page Language="C#" AutoEventWireUp="true" %> + + +

#active websocket connection <%= "foo" %>

+

Web Socket Echo Demo (run from Minefield!)

+ + + + +

+This text will be sent on the socket: + + +

+ + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/images/select_arrow.gif b/test/WebRoot/WebSocket/images/select_arrow.gif new file mode 100644 index 0000000000..781c8bc433 Binary files /dev/null and b/test/WebRoot/WebSocket/images/select_arrow.gif differ diff --git a/test/WebRoot/WebSocket/images/select_arrow_down.gif b/test/WebRoot/WebSocket/images/select_arrow_down.gif new file mode 100644 index 0000000000..0be8124c20 Binary files /dev/null and b/test/WebRoot/WebSocket/images/select_arrow_down.gif differ diff --git a/test/WebRoot/WebSocket/images/select_arrow_over.gif b/test/WebRoot/WebSocket/images/select_arrow_over.gif new file mode 100644 index 0000000000..0620571c81 Binary files /dev/null and b/test/WebRoot/WebSocket/images/select_arrow_over.gif differ diff --git a/test/WebRoot/WebSocket/web.config b/test/WebRoot/WebSocket/web.config new file mode 100644 index 0000000000..5204b66b4a --- /dev/null +++ b/test/WebRoot/WebSocket/web.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/WebRoot/WebSocket/web.config.bak b/test/WebRoot/WebSocket/web.config.bak new file mode 100644 index 0000000000..5204b66b4a --- /dev/null +++ b/test/WebRoot/WebSocket/web.config.bak @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/WebRoot/applicationhost.config.txt b/test/WebRoot/applicationhost.config.txt new file mode 100644 index 0000000000..6e64698b92 --- /dev/null +++ b/test/WebRoot/applicationhost.config.txt @@ -0,0 +1,1233 @@ + + + + + + + +

+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/WebRoot/parent/web.config b/test/WebRoot/parent/web.config new file mode 100644 index 0000000000..5c7fb464da --- /dev/null +++ b/test/WebRoot/parent/web.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/WebSocketClientEXE/App.config b/test/WebSocketClientEXE/App.config new file mode 100644 index 0000000000..8324aa6ff1 --- /dev/null +++ b/test/WebSocketClientEXE/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/WebSocketClientEXE/Program.cs b/test/WebSocketClientEXE/Program.cs new file mode 100644 index 0000000000..ea45603a33 --- /dev/null +++ b/test/WebSocketClientEXE/Program.cs @@ -0,0 +1,53 @@ +using AspNetCoreModule.Test.Framework; +using AspNetCoreModule.Test.WebSocketClient; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace WebSocketClientEXE +{ + class Program + { + static void Main(string[] args) + { + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + if (args.Length == 0) + { + TestUtility.LogInformation("Usage: WebSocketClientEXE http://localhost:40000/aspnetcoreapp/websocket"); + return; + } + string url = "http://localhost:40000/aspnetcoreapp/websocket"; + if (args[0].Contains("http:")) + { + url = args[0]; + } + var frameReturned = websocketClient.Connect(new Uri(url), true, true); + TestUtility.LogInformation(frameReturned.Content); + TestUtility.LogInformation("Type any data and Enter key ('Q' to quit): "); + + while (true) + { + Thread.Sleep(500); + if (!websocketClient.IsOpened) + { + TestUtility.LogInformation("Connection closed..."); + break; + } + + string data = Console.ReadLine(); + if (data.Trim().ToLower() == "q") + { + frameReturned = websocketClient.Close(); + TestUtility.LogInformation(frameReturned.Content); + break; + } + websocketClient.SendTextData(data); + } + } + } + } +} diff --git a/test/WebSocketClientEXE/Properties/AssemblyInfo.cs b/test/WebSocketClientEXE/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ed897b706e --- /dev/null +++ b/test/WebSocketClientEXE/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("WebSocketClientEXE")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WebSocketClientEXE")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[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("4062ea94-75f5-4691-86dc-c8594ba896de")] + +// 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("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/WebSocketClientEXE/TestUtility.cs b/test/WebSocketClientEXE/TestUtility.cs new file mode 100644 index 0000000000..852bda62bc --- /dev/null +++ b/test/WebSocketClientEXE/TestUtility.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace AspNetCoreModule.Test.Framework +{ + public class TestUtility + { + public static void LogInformation(string format, params object[] parameters) + { + Console.WriteLine(format, parameters); + } + } +} \ No newline at end of file diff --git a/test/WebSocketClientEXE/WebSocketClientEXE.csproj b/test/WebSocketClientEXE/WebSocketClientEXE.csproj new file mode 100644 index 0000000000..486d2188af --- /dev/null +++ b/test/WebSocketClientEXE/WebSocketClientEXE.csproj @@ -0,0 +1,66 @@ + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Frame.cs + + + Frames.cs + + + FrameType.cs + + + WebSocketClientHelper.cs + + + WebSocketClientUtility.cs + + + WebSocketConnect.cs + + + WebSocketConstants.cs + + + WebSocketState.cs + + + + + + + + + + \ No newline at end of file diff --git a/test/stresstestwebroot/app_offline.htm b/test/stresstestwebroot/app_offline.htm new file mode 100644 index 0000000000..30d74d2584 --- /dev/null +++ b/test/stresstestwebroot/app_offline.htm @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/test/stresstestwebroot/web.config b/test/stresstestwebroot/web.config new file mode 100644 index 0000000000..a6fc3cdcf5 --- /dev/null +++ b/test/stresstestwebroot/web.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tools/certificate.ps1 b/tools/certificate.ps1 new file mode 100644 index 0000000000..52c8817796 --- /dev/null +++ b/tools/certificate.ps1 @@ -0,0 +1,499 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. + +<############################################################################## + Example + + ############################################################################### + # Create a new root certificate on "Cert:\LocalMachine\My" and export it to "Cert:\LocalMachine\Root" + # FYI, you can do the same thing with one of the following commands: + # %sdxroot\tools\amd64\MakeCert.exe -r -pe -n "CN=ANCMTest_Root" -b 12/22/2013 -e 12/23/2020 -ss root -sr localmachine -len 2048 -a sha256 + # $thumbPrint = (New-SelfSignedCertificate -DnsName "ANCMTest_Root", "ANCMTest_Roo3" -CertStoreLocation "cert:\LocalMachine\My").Thumbprint + ############################################################################### + $rootSubject = "ANCMTest_Root" + $thumbPrint = .\certificate.ps1 -Command Create-SelfSignedCertificate -Subject $rootSubject + .\certificate.ps1 -Command Export-CertificateTo -TargetThumbPrint $thumbPrint -TargetSSLStore "Cert:\LocalMachine\My" -ExportToSSLStore "Cert:\LocalMachine\Root" + .\certificate.ps1 -Command Get-CertificateThumbPrint -Subject $rootSubject -TargetSSLStore "Cert:\LocalMachine\Root" + + ############################################################################### + # Create a new certificate setting issuer with the root certicate's subject name on "Cert:\LocalMachine\My" and export it to "Cert:\LocalMachine\Root" + # FYI, you can do the same thing with one of the following commands: + # %sdxroot\tools\amd64\MakeCert.exe -pe -n "CN=ANCMTestWebServer" -b 12/22/2013 -e 12/23/2020 -eku 1.3.6.1.5.5.7.3.1 -is root -ir localmachine -in $rootSubject -len 2048 -ss my -sr localmachine -a sha256 + # %sdxroot\tools\amd64\MakeCert.exe -pe -n "CN=ANCMTest_Client" -eku 1.3.6.1.5.5.7.3.2 -is root -ir localmachine -in ANCMTest_Root -ss my -sr currentuser -len 2048 -a sha256 + ############################################################################### + $childSubject = "ANCMTest_Client" + $thumbPrint2 = .\certificate.ps1 -Command Create-SelfSignedCertificate -Subject $childSubject -IssuerName $rootSubject + ("Result: $thumbPrint2") + .\certificate.ps1 -Command Export-CertificateTo -TargetThumbPrint $thumbPrint2 -TargetSSLStore "Cert:\LocalMachine\My" -ExportToSSLStore "Cert:\CurrentUser\My" + + .\certificate.ps1 -Command Export-CertificateTo -TargetThumbPrint $thumbPrint2 -TargetSSLStore "Cert:\LocalMachine\My" -ExportToSSLStore C:\gitroot\AspNetCoreModule\tools\test.pfx -PfxPassword test + + + # Clean up + .\certificate.ps1 -Command Delete-Certificate -TargetThumbPrint $thumbPrint2 -TargetSSLStore "Cert:\LocalMachine\My" + .\certificate.ps1 -Command Delete-Certificate -TargetThumbPrint $thumbPrint2 -TargetSSLStore "Cert:\CurrentUser\Root" + .\certificate.ps1 -Command Delete-Certificate -TargetThumbPrint $thumbPrint -TargetSSLStore "Cert:\LocalMachine\My" + .\certificate.ps1 -Command Delete-Certificate -TargetThumbPrint $thumbPrint -TargetSSLStore "Cert:\LocalMachine\Root" + +###############################################################################> + + +Param( + [parameter(Mandatory=$true , Position=0)] + [ValidateSet("Create-SelfSignedCertificate", + "Delete-Certificate", + "Export-CertificateTo", + "Get-CertificateThumbPrint", + "Get-CertificatePublicKey")] + [string] + $Command, + + [parameter()] + [string] + $Subject, + + [parameter()] + [string] + $IssuerName, + + [Parameter()] + [string] + $FriendlyName = "", + + [Parameter()] + [string[]] + $AlternativeNames = "", + + [Parameter()] + [string] + $TargetSSLStore = "", + + [Parameter()] + [string] + $ExportToSSLStore = "", + + [Parameter()] + [string] + $PfxPassword = "", + + [Parameter()] + [string] + $TargetThumbPrint = "" +) + +function Create-SelfSignedCertificate($_subject, $_friendlyName, $_alternativeNames, $_issuerName) { + + if (-not $_subject) + { + return ("Error!!! _subject is required") + } + + # + # $_issuerName should be set with the value subject and its certificate path will be root path + if (-not $_issuerName) + { + $_issuerName = $_subject + } + + # + # Create $subjectDn and $issuerDn + $subjectDn = new-object -com "X509Enrollment.CX500DistinguishedName" + $subjectDn.Encode( "CN=" + $_subject, $subjectDn.X500NameFlags.X500NameFlags.XCN_CERT_NAME_STR_NONE) + $issuerDn = new-object -com "X509Enrollment.CX500DistinguishedName" + $issuerDn.Encode("CN=" + $_issuerName, $subjectDn.X500NameFlags.X500NameFlags.XCN_CERT_NAME_STR_NONE) + + # + # Create a new Private Key + $key = new-object -com "X509Enrollment.CX509PrivateKey" + $key.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider" + # XCN_AT_SIGNATURE, The key can be used for signing + $key.KeySpec = 2 + $key.Length = 2048 + # MachineContext 0: Current User, 1: Local Machine + $key.MachineContext = 1 + $key.Create() + + # + # Create a cert object with the newly created private key + $cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate" + $cert.InitializeFromPrivateKey(2, $key, "") + $cert.Subject = $subjectDn + $cert.Issuer = $issuerDn + $cert.NotBefore = (get-date).AddMinutes(-10) + $cert.NotAfter = $cert.NotBefore.AddYears(2) + + #Use Sha256 + $hashAlgorithm = New-Object -ComObject X509Enrollment.CObjectId + $hashAlgorithm.InitializeFromAlgorithmName(1,0,0,"SHA256") + $cert.HashAlgorithm = $hashAlgorithm + + # + # Key usage should be set for non-root certificate + if ($_issuerName -ne $_subject) + { + # + # Extended key usage + $clientAuthOid = New-Object -ComObject "X509Enrollment.CObjectId" + $clientAuthOid.InitializeFromValue("1.3.6.1.5.5.7.3.2") + $serverAuthOid = new-object -com "X509Enrollment.CObjectId" + $serverAuthOid.InitializeFromValue("1.3.6.1.5.5.7.3.1") + $ekuOids = new-object -com "X509Enrollment.CObjectIds.1" + $ekuOids.add($clientAuthOid) + $ekuOids.add($serverAuthOid) + $ekuExt = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage" + $ekuExt.InitializeEncode($ekuOids) + $cert.X509Extensions.Add($ekuext) + + # + #Set Key usage + $keyUsage = New-Object -com "X509Enrollment.cx509extensionkeyusage" + # XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE + $flags = 0x20 + # XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE + $flags = $flags -bor 0x80 + $keyUsage.InitializeEncode($flags) + $cert.X509Extensions.Add($keyUsage) + } + + # + # Subject alternative names + if ($_alternativeNames -ne $null) { + $names = new-object -com "X509Enrollment.CAlternativeNames" + $altNames = new-object -com "X509Enrollment.CX509ExtensionAlternativeNames" + foreach ($n in $_alternativeNames) { + $name = new-object -com "X509Enrollment.CAlternativeName" + # Dns Alternative Name + $name.InitializeFromString(3, $n) + $names.Add($name) + } + $altNames.InitializeEncode($names) + $cert.X509Extensions.Add($altNames) + } + + $cert.Encode() + + #$locator = $(New-Object "System.Guid").ToString() + $locator = [guid]::NewGuid().ToString() + $enrollment = new-object -com "X509Enrollment.CX509Enrollment" + $enrollment.CertificateFriendlyName = $locator + $enrollment.InitializeFromRequest($cert) + $certdata = $enrollment.CreateRequest(0) + $enrollment.InstallResponse(2, $certdata, 0, "") + + # Wait for certificate to be populated + $end = $(Get-Date).AddSeconds(1) + do { + $Certificates = Get-ChildItem Cert:\LocalMachine\My + foreach ($item in $Certificates) + { + if ($item.FriendlyName -eq $locator) + { + $CACertificate = $item + } + } + } while ($CACertificate -eq $null -and $(Get-Date) -lt $end) + + $thumbPrint = "" + if ($CACertificate -and $CACertificate.Thumbprint) + { + $thumbPrint = $CACertificate.Thumbprint.Trim() + } + return $thumbPrint +} + +function Delete-Certificate($_targetThumbPrint, $_targetSSLStore = $TargetSSLStore) { + + if (-not $_targetThumbPrint) + { + return ("Error!!! _targetThumbPrint is required") + } + + if (Test-Path "$_targetSSLStore\$_targetThumbPrint") + { + Remove-Item "$_targetSSLStore\$_targetThumbPrint" -Force -Confirm:$false + } + + if (Test-Path "$_targetSSLStore\$_targetThumbPrint") + { + return ("Error!!! Failed to delete a certificate of $_targetThumbPrint") + } +} + +function Export-CertificateTo($_targetThumbPrint, $_exportToSSLStore, $_password) +{ + if (-not $_targetThumbPrint) + { + return ("Error!!! _targetThumbPrint is required") + } + + if (-not (Test-Path "$TargetSSLStore\$_targetThumbPrint")) + { + return ("Error!!! Export failed. Can't find target certificate: $TargetSSLStore\$_targetThumbPrint") + } + + $cert = Get-Item "$TargetSSLStore\$_targetThumbPrint" + $tempExportFile = "$env:temp\_tempCertificate.cer" + if (Test-Path $tempExportFile) + { + Remove-Item $tempExportFile -Force -Confirm:$false + } + + $isThisWin7 = $false + $exportToSSLStoreName = $null + $exportToSSLStoreLocation = $null + $targetSSLStoreName = $null + $targetSSLStoreLocation = $null + + if ((Get-Command Export-Certificate 2> out-null) -eq $null) + { + $isThisWin7 = $true + } + + # if _exportToSSLStore points to a .pfx file + if ($exportToSSLStore.ToLower().EndsWith(".pfx")) + { + if (-not $_password) + { + return ("Error!!! _password is required") + } + + if ($isThisWin7) + { + if ($TargetSSLStore.ToLower().Contains("my")) + { + $targetSSLStoreName = "My" + } + elseif ($_exportToSSLStore.ToLower().Contains("root")) + { + $targetSSLStoreName = "Root" + } + else + { + throw ("Unsupported store name " + $TargetSSLStore) + } + if ($TargetSSLStore.ToLower().Contains("localmachine")) + { + $targetSSLStoreLocation = "LocalMachine" + } + else + { + throw ("Unsupported store location name " + $TargetSSLStore) + } + + &certutil.exe @('-exportpfx', '-p', $_password, $targetSSLStoreName, $_targetThumbPrint, $_exportToSSLStore) | out-null + + if ( Test-Path $_exportToSSLStore ) + { + # Succeeded to export to .pfx file + return + } + else + { + return ("Error!!! Can't export $TargetSSLStore\$_targetThumbPrint to $tempExportFile") + } + } + else + { + $securedPassword = ConvertTo-SecureString -String $_password -Force AsPlainText + $exportedPfxFile = Export-PfxCertificate -FilePath $_exportToSSLStore -Cert $TargetSSLStore\$_targetThumbPrint -Password $securedPassword + if ( ($exportedPfxFile -ne $null) -and (Test-Path $exportedPfxFile.FullName) ) + { + # Succeeded to export to .pfx file + return + } + else + { + return ("Error!!! Can't export $TargetSSLStore\$_targetThumbPrint to $tempExportFile") + } + } + } + + if ($isThisWin7) + { + # Initialize variables for Win7 + if ($_exportToSSLStore.ToLower().Contains("my")) + { + $exportToSSLStoreName = [System.Security.Cryptography.X509Certificates.StoreName]::My + } + elseif ($_exportToSSLStore.ToLower().Contains("root")) + { + $exportToSSLStoreName = [System.Security.Cryptography.X509Certificates.StoreName]::Root + } + else + { + throw ("Unsupported store name " + $_exportToSSLStore) + } + if ($_exportToSSLStore.ToLower().Contains("localmachine")) + { + $exportToSSLStoreLocation = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine + } + elseif ($_exportToSSLStore.ToLower().Contains("currentuser")) + { + $exportToSSLStoreLocation = [System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser + } + else + { + throw ("Unsupported store location name " + $_exportToSSLStore) + } + + # Export-Certificate is not available. + $isThisWin7 = $true + $certificate = Get-Item "$TargetSSLStore\$_targetThumbPrint" + $base64certificate = @" +-----BEGIN CERTIFICATE----- +$([Convert]::ToBase64String($certificate.Export('Cert'), [System.Base64FormattingOptions]::InsertLineBreaks))) +-----END CERTIFICATE----- +"@ + Set-Content -Path $tempExportFile -Value $base64certificate | Out-Null + } + else + { + Export-Certificate -Cert $cert -FilePath $tempExportFile | Out-Null + if (-not (Test-Path $tempExportFile)) + { + return ("Error!!! Can't export $TargetSSLStore\$_targetThumbPrint to $tempExportFile") + } + } + + if ($isThisWin7) + { + [Reflection.Assembly]::Load("System.Security, Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a") | Out-Null + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempExportFile) + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($exportToSSLStoreName,$exportToSSLStoreLocation) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) | Out-Null + $store.Add($cert) | Out-Null + } + else + { + # clean up destination SSL store + Delete-Certificate $_targetThumbPrint $_exportToSSLStore + if (Test-Path "$_exportToSSLStore\$_targetThumbPrint") + { + return ("Error!!! Can't delete already existing one $_exportToSSLStore\$_targetThumbPrint") + } + Import-Certificate -CertStoreLocation $_exportToSSLStore -FilePath $tempExportFile | Out-Null + } + + Sleep 3 + if (-not (Test-Path "$_exportToSSLStore\$_targetThumbPrint")) + { + return ("Error!!! Can't copy $TargetSSLStore\$_targetThumbPrint to $_exportToSSLStore") + } +} + +function Get-CertificateThumbPrint($_subject, $_issuerName, $_targetSSLStore) +{ + if (-not $_subject) + { + return ("Error!!! _subject is required") + } + if (-not $_targetSSLStore) + { + return ("Error!!! _targetSSLStore is required") + } + + if (-not (Test-Path "$_targetSSLStore")) + { + return ("Error!!! Can't find target store") + } + + $targetCertificate = $null + + $Certificates = Get-ChildItem $_targetSSLStore + foreach ($item in $Certificates) + { + $findItem = $false + # check subject name first + if ($item.Subject.ToLower() -eq "CN=$_subject".ToLower()) + { + $findItem = $true + } + + # check issuerName as well + if ($_issuerName -and $item.Issuer.ToLower() -ne "CN=$_issuerName".ToLower()) + { + $findItem = $false + } + + if ($findItem) + { + $targetCertificate = $item + break + } + } + $result = "" + if ($targetCertificate) + { + $result = $targetCertificate.Thumbprint + } + else + { + ("Error!!! Can't find target certificate") + } + return $result +} + +function Get-CertificatePublicKey($_targetThumbPrint) +{ + if (-not $_targetThumbPrint) + { + return ("Error!!! _targetThumbPrint is required") + } + + if (-not (Test-Path "$TargetSSLStore\$_targetThumbPrint")) + { + return ("Error!!! Can't find target certificate") + } + + $cert = Get-Item "$TargetSSLStore\$_targetThumbPrint" + $byteArray = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) + $publicKey = [System.Convert]::ToBase64String($byteArray).Trim() + + return $publicKey +} + +# Error handling and initializing default values +if (-not $TargetSSLStore) +{ + $TargetSSLStore = "Cert:\LocalMachine\My" +} +else +{ + if ($Command -eq "Create-SelfSignedCertificate") + { + return ("Error!!! Create-SelfSignedCertificate should use default value for -TargetSSLStore if -Issuer is not provided") + } +} + +if (-not $ExportToSSLStore) +{ + $ExportToSSLStore = "Cert:\LocalMachine\Root" +} + +switch ($Command) +{ + "Create-SelfSignedCertificate" + { + return Create-SelfSignedCertificate $Subject $FriendlyName $AlternativeNames $IssuerName + } + "Delete-Certificate" + { + return Delete-Certificate $TargetThumbPrint + } + "Export-CertificateTo" + { + return Export-CertificateTo $TargetThumbPrint $ExportToSSLStore $PfxPassword + } + "Get-CertificateThumbPrint" + { + return Get-CertificateThumbPrint $Subject $IssuerName $TargetSSLStore + } + "Get-CertificatePublicKey" + { + return Get-CertificatePublicKey $TargetThumbPrint + } + default + { + throw "Unknown command" + } +} diff --git a/tools/httpsys.ps1 b/tools/httpsys.ps1 new file mode 100644 index 0000000000..af2254e96f --- /dev/null +++ b/tools/httpsys.ps1 @@ -0,0 +1,394 @@ +# Copyright (c) .NET Foundation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. + +############################################################################## +# Example +# $result = .\httpsys.ps1 -Command Get-SslBinding -IpAddress "0x00" -Port 46300 +# .\httpsys.ps1 -Command Add-SslBinding -IpAddress "0x00" -Port 46300 Thumbprint $result.CertificateHash +# .\httpsys.ps1 -Command Delete-SslBinding -IpAddress "0x00" -Port 46300 +############################################################################## + +Param ( + [parameter(Mandatory=$true , Position=0)] + [ValidateSet("Add-SslBinding", + "Delete-SslBinding", + "Get-SslBinding")] + [string] + $Command, + + [parameter()] + [string] + $IpAddress, + + [parameter()] + [string] + $Port, + + [parameter()] + [string] + $Thumbprint, + + [parameter()] + [string] + $TargetSSLStore, + + [parameter()] + [string] + $AppId, + + [parameter()] + [System.Net.IPEndPoint] + $IpEndPoint + ) + + +# adjust parameter variables +if (-not $IpEndPoint) +{ + if ($IpAddress -and $Port) + { + $IpEndPoint = New-Object "System.Net.IPEndPoint" -ArgumentList $IpAddress,$Port + } +} + +if (-not $TargetSSLStore) +{ + $TargetSSLStore = "Cert:\LocalMachine\My" +} + +$StoreName = ($TargetSSLStore.Split("\") | Select-Object -Last 1).Trim() + +$Certificate = Get-Item "$TargetSSLStore\$Thumbprint" + +if (-not $AppId) +{ + # Assign a random GUID for $AppId + $AppId = [guid]::NewGuid() +} + +$cs = ' +namespace Microsoft.IIS.Administration.Setup { +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.ComponentModel; + + public class Http { + public const int HTTP_INITIALIZE_CONFIG = 2; + public const int HTTP_SERVICE_CONFIG_SSLCERT_INFO = 1; + + [DllImport("httpapi.dll", CharSet = CharSet.Auto, PreserveSig = true)] + public static extern uint HttpDeleteServiceConfiguration(IntPtr ServiceHandle, int ConfigId, ref HTTP_SERVICE_CONFIG_SSL_SET pConfigInformation, int ConfigInformationLength, IntPtr pOverlapped); + + [DllImport("httpapi.dll", CharSet = CharSet.Auto, PreserveSig = true)] + public static extern uint HttpInitialize(HTTPAPI_VERSION version, uint flags, IntPtr pReserved); + + [DllImport("httpapi.dll", EntryPoint = "HttpQueryServiceConfiguration", + CharSet = CharSet.Unicode, ExactSpelling = true, + CallingConvention = CallingConvention.StdCall)] + public static extern uint HttpQueryServiceConfiguration( + IntPtr serviceHandle, + HTTP_SERVICE_CONFIG_ID configID, + ref HTTP_SERVICE_CONFIG_SSL_QUERY pInputConfigInfo, + UInt32 InputConfigInfoLength, + IntPtr pOutputConfigInfo, + UInt32 OutputConfigInfoLength, + [In, Out] ref UInt32 pReturnLength, + IntPtr pOverlapped + ); + + [DllImport("httpapi.dll", CharSet = CharSet.Auto, PreserveSig = true)] + public static extern uint HttpSetServiceConfiguration(IntPtr ServiceHandle, int ConfigId, ref HTTP_SERVICE_CONFIG_SSL_SET pConfigInformation, int ConfigInformationLength, IntPtr pOverlapped); + + [DllImport("httpapi.dll", CharSet = CharSet.Auto, PreserveSig = true)] + public static extern uint HttpTerminate(uint flags, IntPtr pReserved); + + public static HTTP_SERVICE_CONFIG_SSL_SET MarshalConfigSslSet(IntPtr ptr) { + return (HTTP_SERVICE_CONFIG_SSL_SET)Marshal.PtrToStructure(ptr, typeof(HTTP_SERVICE_CONFIG_SSL_SET)); + } + } + + public enum HTTP_SERVICE_CONFIG_ID { + HttpServiceConfigIPListenList, + HttpServiceConfigSSLCertInfo, + HttpServiceConfigUrlAclInfo, + HttpServiceConfigMax + } + + public enum HTTP_SERVICE_CONFIG_QUERY_TYPE { + HttpServiceConfigQueryExact, + HttpServiceConfigQueryNext, + HttpServiceConfigQueryMax + } + + [StructLayout(LayoutKind.Sequential)] + public struct HTTPAPI_VERSION { + public ushort HttpApiMajorVersion; + public ushort HttpApiMinorVersion; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct HTTP_SERVICE_CONFIG_SSL_KEY { + public IntPtr pIpPort; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct HTTP_SERVICE_CONFIG_SSL_QUERY { + public HTTP_SERVICE_CONFIG_QUERY_TYPE QueryDesc; + public IntPtr KeyDesc; + public Int32 dwToken; + } + + [StructLayout(LayoutKind.Sequential)] + public struct HTTP_SERVICE_CONFIG_SSL_SET { + public IntPtr KeyDesc; + public uint SslHashLength; + public IntPtr pSslHash; + public Guid AppId; + [MarshalAs(UnmanagedType.LPWStr)] + public string pSslCertStoreName; + public int DefaultCertCheckMode; + public int DefaultRevocationFreshnessTime; + public int DefaultRecovationUrlRetrievalTimeout; + [MarshalAs(UnmanagedType.LPWStr)] + public string pDefaultSslCtlIdentifier; + [MarshalAs(UnmanagedType.LPWStr)] + public string pDefaultSslCtlStoreName; + public int DefaultFlags; + } +} +' + +$SUCCESS = 0 +function InitializeInterop() { + try { + [Microsoft.IIS.Administration.Setup.Http] | Out-Null + } + catch { + Add-Type $cs + } +} + +function GetIpEndpointBytes($_ipEndpoint) { + $socketAddress = $_ipEndpoint.Serialize() + $ipBytes = [System.Array]::CreateInstance([System.Byte], $socketAddress.Size) + for ($i = 0; $i -lt $socketAddress.Size; $i++) { + $ipBytes[$i] = $socketAddress[$i] + } + return $ipBytes +} + +function GetBindingInfo($sslConfig) { + $hash = [System.Array]::CreateInstance([System.Byte], [int]($sslConfig.SslHashLength)) + [System.Runtime.InteropServices.Marshal]::Copy($sslConfig.pSslHash, $hash, 0, $sslConfig.SslHashLength) + + $socketAddressLength = 16 + $sa = [System.Array]::CreateInstance([System.Byte], $socketAddressLength) + [System.Runtime.InteropServices.Marshal]::Copy($sslConfig.KeyDesc, $sa, 0, $socketAddressLength) + $socketAddress = New-Object "System.Net.SocketAddress" -ArgumentList ([System.Net.Sockets.AddressFamily]::InterNetwork, $socketAddressLength) + for ($i = 0; $i -lt $sa.Length; $i++) { + $socketAddress[$i] = $sa[$i] + } + + $ep = New-Object "System.Net.IPEndPoint" -ArgumentList ([ipaddress]::Any, 0) + $endpoint = [System.Net.IPEndPoint]$ep.Create($socketAddress) + + $ret = @{} + $ret.CertificateHash = [System.BitConverter]::ToString($hash).Replace("-", "") + $ret.AppId = $sslConfig.AppId + $ret.IpEndpoint = $endpoint + return $ret +} + +function InitializeHttpSys() { + $v = New-Object "Microsoft.IIS.Administration.Setup.HTTPAPI_VERSION" + $V.HttpApiMajorVersion = 1 + $v.HttpApiMinorVersion = 0 + + $result = [Microsoft.IIS.Administration.Setup.Http]::HttpInitialize($v, [Microsoft.IIS.Administration.Setup.Http]::HTTP_INITIALIZE_CONFIG, [System.IntPtr]::Zero) + + if ($result -ne $SUCCESS) { + Write-Warning "Error initializing Http API" + throw [System.ComponentModel.Win32Exception] $([System.int32]$result) + } + + return $result +} + +function TerminateHttpSys() { + return [Microsoft.IIS.Administration.Setup.Http]::HttpTerminate([Microsoft.IIS.Administration.Setup.Http]::HTTP_INITIALIZE_CONFIG, [System.IntPtr]::Zero) +} + +function Add-SslBinding($_ipEndpoint, $_certificate, $_appId) { + if ($_ipEndpoint -eq $null) { + throw "Ip Endpoint required." + } + + if ($_certificate -eq $null) { + throw "Certificate required." + } + + if ($appId -eq $null) { + throw "App id required." + } + + <# FYI, [System.Guid]::Parse() is not supported in lower version of powershell + if (-not($_appId -is [System.Guid])) { + $_appId = [System.Guid]::Parse($_appId) + } + #> + + setSslConfiguration $_ipEndpoint $_certificate $_appId +} + +function Delete-SslBinding($_ipEndpoint) { + + if ($_ipEndpoint -eq $null) { + throw "Ip Endpoint required." + } + + setSslConfiguration $_ipEndpoint $null $([System.Guid]::Empty) +} + +function Get-SslBinding($_ipEndpoint) { + + if ($_ipEndpoint -eq $null) { + throw "Ip Endpoint required." + } + + $bufferSize = 4096 + try { + InitializeHttpSys| Out-Null + + $ipBytes = [System.Byte[]]$(GetIpEndpointBytes($_ipEndpoint)) + $hIp = [System.Runtime.InteropServices.GCHandle]::Alloc($ipBytes, [System.Runtime.InteropServices.GCHandleType]::Pinned) + $pIp = $hIp.AddrOfPinnedObject() + + $queryParam = New-Object "Microsoft.IIS.Administration.Setup.HTTP_SERVICE_CONFIG_SSL_QUERY" + $queryParam.QueryDesc = [Microsoft.IIS.Administration.Setup.HTTP_SERVICE_CONFIG_QUERY_TYPE]::HttpServiceConfigQueryExact + $queryParam.dwToken = 0 + $queryParam.KeyDesc = $pIp + + $returnLen = 0 + $pReturnSet = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($bufferSize) + + $result = [Microsoft.IIS.Administration.Setup.Http]::HttpQueryServiceConfiguration( + [System.IntPtr]::Zero, + [Microsoft.IIS.Administration.Setup.HTTP_SERVICE_CONFIG_ID]::HttpServiceConfigSSLCertInfo, + [ref] $queryParam, + [uint32]([System.Runtime.InteropServices.Marshal]::SizeOf($queryParam)), + $pReturnSet, + $bufferSize, + [ref] $returnLen, + [System.IntPtr]::Zero) + + if ($result -eq 2) { + # File not found + return $null + } + if ($result -ne $SUCCESS) { + Write-Warning "Error reading Ssl Cert Configuration" + throw [System.ComponentModel.Win32Exception] $([System.int32]$result) + } + $sslConfig = [Microsoft.IIS.Administration.Setup.Http]::MarshalConfigSslSet($pReturnSet) + return GetBindingInfo $sslConfig + } + finally { + if ($hIp -ne $null) { + $hIp.Free() + $hIp = $null + } + if ($pReturnSet -ne [System.IntPtr]::Zero) { + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($pReturnSet) + $pReturnSet = [System.IntPtr]::Zero + } + TerminateHttpSys | Out-Null + } +} + +function setSslConfiguration($_ipEndpoint, $_certificate, $_appId) { + + try { + InitializeHttpSys| Out-Null + + $sslSet = New-Object "Microsoft.IIS.Administration.Setup.HTTP_SERVICE_CONFIG_SSL_SET" + $sslSetSize = [System.Runtime.InteropServices.Marshal]::SizeOf($sslSet) + + $ipBytes = [System.Byte[]]$(GetIpEndpointBytes($_ipEndpoint)) + $hIp = [System.Runtime.InteropServices.GCHandle]::Alloc($ipBytes, [System.Runtime.InteropServices.GCHandleType]::Pinned) + $pIp = $hIp.AddrOfPinnedObject() + + $sslSet.KeyDesc = $pIp # IntPtr + $sslSet.SslHashLength = 0 + $sslSet.pSslHash = [System.IntPtr]::Zero + $sslSet.pSslCertStoreName = [System.IntPtr]::Zero + $sslSet.AppId = $_appId + + if ($_certificate -ne $null) { + # Create binding + + $certBytes = $_certificate.GetCertHash() + $hCertBytes = [System.Runtime.InteropServices.GCHandle]::Alloc($certBytes, [System.Runtime.InteropServices.GCHandleType]::Pinned) + $pCertBytes = $hCertBytes.AddrOfPinnedObject() + + $sslSet.SslHashLength = 20 + $sslSet.pSslHash = $pCertBytes + $sslSet.pSslCertStoreName = $StoreName + + $result = [Microsoft.IIS.Administration.Setup.Http]::HttpSetServiceConfiguration([System.IntPtr]::Zero, + [Microsoft.IIS.Administration.Setup.Http]::HTTP_SERVICE_CONFIG_SSLCERT_INFO, + [ref]$sslSet, + $sslSetSize, + [System.IntPtr]::Zero) + } + else { + #Delete binding + $result = [Microsoft.IIS.Administration.Setup.Http]::HttpDeleteServiceConfiguration([System.IntPtr]::Zero, + [Microsoft.IIS.Administration.Setup.Http]::HTTP_SERVICE_CONFIG_SSLCERT_INFO, + [ref]$sslSet, + $sslSetSize, + [System.IntPtr]::Zero) + } + + if ($result -ne $SUCCESS) { + Write-Warning "Error setting Ssl Cert Configuration" + throw [System.ComponentModel.Win32Exception] $([System.int32]$result) + } + } + finally { + if ($hIp -ne $null) { + $hIp.Free() + $hIp = $null + } + if ($hCertBytes -ne $null) { + $hCertBytes.Free() + $hCertBytes = $null + } + TerminateHttpSys| Out-Null + } +} + +InitializeInterop +switch ($Command) +{ + "Add-SslBinding" + { + return Add-SslBinding $IpEndPoint $Certificate $AppId + } + "Delete-SslBinding" + { + return Delete-SslBinding $IpEndpoint + } + "Get-SslBinding" + { + return Get-SslBinding $IpEndpoint + } + default + { + throw "Unknown command" + } +} \ No newline at end of file diff --git a/tools/installancm.ps1 b/tools/installancm.ps1 new file mode 100644 index 0000000000..b54a830d0e --- /dev/null +++ b/tools/installancm.ps1 @@ -0,0 +1,471 @@ +<# +.SYNOPSIS + Installs asnetcore to IISExpress and IIS directory +.DESCRIPTION + Installs asnetcore to IISExpress and IIS directory +.PARAMETER Rollback + Default: $false + Rollback the updated files with the original files +.PARAMETER ForceToBackup + Default: $false + Force to do the initial backup again (this parameter is meaningful only when you want to replace the existing backup file) +.PARAMETER Extract + Default: $false + Search ANCM nugetfile and extract the file to the path of the ExtractFilesTo parameter value +.PARAMETER PackagePath + Default: $PSScriptRoot\..\..\artifacts + Root path where ANCM nuget package is placed +.PARAMETER ExtractFilesTo + Default: $PSScriptRoot\..\..\artifacts" + Output path where aspentcore.dll file is extracted + +Example: + .\installancm.ps1 "C:\Users\jhkim\AppData\Local\Temp\ihvufnf1.atw\ancm\Debug" + +#> +[cmdletbinding()] +param( + [Parameter(Mandatory=$false, Position = 0)] + [string] $ExtractFilesTo="$PSScriptRoot\..\artifacts\build\AspNetCore\bin\Debug", + [Parameter(Mandatory=$false, Position = 1)] + [string] $PackagePath="$PSScriptRoot\..\artifacts\build", + [Parameter(Mandatory=$false)] + [switch] $Rollback=$false, + [Parameter(Mandatory=$false)] + [switch] $ForceToBackup=$false, + [Parameter(Mandatory=$false)] + [switch] $Extract=$false +) + +function Get-ANCMNugetFilePath() { + + $NugetFilePath = Get-ChildItem $PackagePath -Recurse -Filter Microsoft.AspNetCore.AspNetCoreModule*.nupkg | Select-Object -Last 1 + return ($NugetFilePath.FullName) +} + +function Check-TargetFiles() { + $functionName = "Check-TargetFiles" + $LogHeader = "[$ScriptFileName::$functionName]" + $result = $true + + if (-not $isIISExpressInstalled -and -not $isIISInstalled) + { + Say ("$LogHeader Both IIS and IISExpress does not have aspnetcore.dll file") + $result = $false + } + + if ($isIISExpressInstalled) + { + if (-not (Test-Path $aspnetCorex64To)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCorex64To") + $result = $false + } + if (-not (Test-Path $aspnetCoreSchemax64To)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreSchemax64To") + $result = $false + } + if ($is64BitMachine) + { + if (-not (Test-Path $aspnetCoreWin32To)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreWin32To") + $result = $false + } + if (-not (Test-Path $aspnetCoreSchemaWin32To)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreSchemaWin32To") + $result = $false + } + } + } + + if ($isIISInstalled) + { + if (-not (Test-Path $aspnetCorex64IISTo)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCorex64IISTo") + $result = $false + } + if (-not (Test-Path $aspnetCoreSchemax64IISTo)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreSchemax64IISTo") + $result = $false + } + if ($is64BitMachine) + { + if (-not (Test-Path $aspnetCoreWin32IISTo)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreWin32IISTo") + $result = $false + } + } + } + + return $result +} + +function Check-ExtractedFiles() { + $functionName = "Check-ExtractedFiles" + $LogHeader = "[$ScriptFileName::$functionName]" + $result = $true + + if (-not (Test-Path $aspnetCorex64From)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCorex64From") + $result = $false + } + if (-not (Test-Path $aspnetCoreWin32From)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreWin32From") + $result = $false + } + if (-not (Test-Path $aspnetCoreSchemax64From)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreSchemax64From") + $result = $false + } + if (-not (Test-Path $aspnetCoreSchemaWin32From)) + { + Say ("$LogHeader Error!!! Failed to find the file $aspnetCoreSchemaWin32From") + $result = $false + } + return $result +} + +function Extract-ANCMFromNugetPackage() { + $result = $true + + $functionName = "Extract-ANCMFromNugetPackage" + $LogHeader = "[$ScriptFileName::$functionName]" + + $backupAncmNugetFilePath = Join-Path $TempExtractFilesTo (get-item $ancmNugetFilePath).Name + if (Test-Path $backupAncmNugetFilePath) + { + Say ("$LogHeader Found backup file at $backupAncmNugetFilePath") + if ((get-item $ancmNugetFilePath).LastWriteTime -eq (get-item $backupAncmNugetFilePath).LastWriteTime) + { + if (Check-ExtractedFiles) + { + Say ("$LogHeader Skip to extract ANCM files because $ancmNugetFilePath is matched to the backup file $backupAncmNugetFilePath.") + return $result + } + } + } + + Add-Type -Assembly System.IO.Compression.FileSystem + if (Test-Path $TempExtractFilesTo) + { + remove-item $TempExtractFilesTo -Force -Recurse -Confirm:$false | out-null + } + if (Test-Path $TempExtractFilesTo) + { + Say ("$LogHeader Error!!! Failed to delete $TempExtractFilesTo") + $result = $false + return $result + } + else + { + new-item -Type directory $TempExtractFilesTo | out-null + } + if (-not (Test-Path $TempExtractFilesTo)) + { + Say ("$LogHeader Error!!! Failed to create $TempExtractFilesTo") + $result = $false + return $result + } + + # + Say ("$LogHeader Extract the ancm nuget file $ancmNugetFilePath to $TempExtractFilesTo ...") + [System.IO.Compression.ZipFile]::ExtractToDirectory($ancmNugetFilePath, $TempExtractFilesTo) + + Say ("$LogHeader Create the backup file of the nuget file to $backupAncmNugetFilePath") + copy-item $ancmNugetFilePath $backupAncmNugetFilePath + + return $result +} + +function Update-ANCM() { + + $functionName = "Update-ANCM -Rollback:$" + $Rollback.ToString() + $LogHeader = "[$ScriptFileName::$functionName]" + + if ($isIISExpressInstalled) + { + if ($is64BitMachine) + { + Say ("$LogHeader Start updating ANCM files for IISExpress for amd64 machine...") + Update-File $aspnetCorex64From $aspnetCorex64To + Update-File $aspnetCoreWin32From $aspnetCoreWin32To + Update-File $aspnetCoreSchemax64From $aspnetCoreSchemax64To + Update-File $aspnetCoreSchemaWin32From $aspnetCoreSchemaWin32To + } + else + { + Say ("$LogHeader Start updating ANCM files for IISExpress for x86 machine...") + Update-File $aspnetCoreWin32From $aspnetCorex64To + Update-File $aspnetCoreSchemaWin32From $aspnetCoreSchemax64To + } + } + else + { + Say ("$LogHeader Can't find aspnetcore.dll for IISExpress. Skipping updating ANCM files for IISExpress") + } + + if ($isIISInstalled) + { + if ($is64BitMachine) + { + Say ("$LogHeader Start updating ANCM files for IIS for amd64 machine...") + Update-File $aspnetCorex64From $aspnetCorex64IISTo + Update-File $aspnetCoreWin32From $aspnetCoreWin32IISTo + Update-File $aspnetCoreSchemax64From $aspnetCoreSchemax64IISTo + } + else + { + Say ("$LogHeader Start updating ANCM files for IIS for x86 machine...") + Update-File $aspnetCoreWin32IISFrom $aspnetCorex64IISTo + Update-File $aspnetCoreSchemaWin32From $aspnetCoreSchemax64IISTo + } + } + else + { + Say ("$LogHeader Can't find aspnetcore.dll for IIS. Skipping updating ANCM files for IIS server") + } +} + +function Update-File([string]$SourceFilePath, [string]$DestinationFilePath) { + + $Source = $SourceFilePath + $Destination = $DestinationFilePath + + $BackupFilePath = $Destination + ".ancm_backup" + if ($Rollback) + { + $Source = $BackupFilePath + } + + $functionName = "Update-File -Rollback:$" + $Rollback.ToString() + $LogHeader = "[$ScriptFileName::$functionName]" + + if ($ForceToBackup) + { + if (Test-Path $BackupFilePath) + { + $backupFileRemoved = $false + if ( ((get-item $DestinationFilePath).CreationTime -gt (get-item $BackupFilePath).CreationTime) -and ((get-item $DestinationFilePath).CreationTime -gt (get-item $SourceFilePath).CreationTime) ) + { + $backupFileRemoved = $true + Say (' Delete the existing "$BackupFilePath" because "$DestinationFilePath" is newer than both "$BackupFilePath" and "$SourceFilePath"') + Remove-Item $BackupFilePath -Force -Confirm:$false + } + else + { + Say-Verbose (' Skipping to delete the existing backupfile because "$DestinationFilePath" is not newer than $BackupFilePath"') + } + } + if ($backupFileRemoved -and (Test-Path $BackupFilePath)) + { + throw ("$LogHeader Can't delete $BackupFilePath") + } + } + + # Do the initial back up before updating file + if (-Not (Test-Path $BackupFilePath)) + { + Say (" Create a backup $BackupFilePath") + Copy-Item $Destination $BackupFilePath -Force + + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Destination) -DifferenceObject $(Get-Content $BackupFilePath)) + if (-not $fileMatched) + { + throw ("$LogHeader File not matched!!! $Destination $BackupFilePath") + } + } + if (-Not (Test-Path $BackupFilePath)) + { + throw ("$LogHeader Can't backup $Source to $BackupFilePath") + } + + # Copy file from Source to Destination if those files are different each other + if (-Not (Test-Path $Destination)) + { + throw ("$LogHeader Can't find $Destination") + } + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destination)) + if (-not $fileMatched) + { + Say (" Copying $Source to $Desting...") + Copy-Item $Source $Destination -Force + + # check file is correctly copied + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destination)) + if (-not $fileMatched) + { + throw ("$LogHeader File not matched!!! $Source $Destination") + } + else + { + Say-Verbose ("$LogHeader File matched!!! $Source to $Destination") + } + } + else + { + Say (" Skipping $Destination that is already identical to $Source ") + } +} + +function Say($str) { + Write-Host $str +} + +function Say-Verbose($str) { + Write-Verbose $str +} + +####################################################### +# Start execution point +####################################################### + +$EXIT_FAIL = 1 +$EXIT_SUCCESS = 0 + +$ScriptFileName = "installancm.ps1" +$LogHeader = "[$ScriptFileName]" + +if ($Extract -and (-Not $Rollback)) +{ + if (-not (Test-Path $PackagePath)) + { + Say ("$LogHeader Error!!! Failed to find the directory $PackagePath") + exit $EXIT_FAIL + } + + $ancmNugetFilePath = Get-ANCMNugetFilePath + if (-not (Test-Path $ancmNugetFilePath)) + { + Say ("$LogHeader Error!!! Failed to find AspNetCoreModule nupkg file under $PackagePath nor its child directories") + exit $EXIT_FAIL + } +} + +if (-Not $Rollback) +{ + if (-not (Test-Path $ExtractFilesTo)) + { + Say ("$LogHeader Error!!! Failed to find the directory $ExtractFilesTo") + exit $EXIT_FAIL + } +} + +$TempExtractFilesTo = $ExtractFilesTo + "\.ancm" +$ExtractFilesRootPath = "" +if ($Extract) +{ + $ExtractFilesRootPath = $TempExtractFilesTo + "\ancm\Debug" +} +else +{ + $ExtractFilesRootPath = $ExtractFilesTo +} + +# Try with solution output path +$aspnetCorex64From = $ExtractFilesRootPath + "\x64\aspnetcore.dll" +$aspnetCoreWin32From = $ExtractFilesRootPath + "\Win32\aspnetcore.dll" +$aspnetCoreSchemax64From = $ExtractFilesRootPath + "\x64\aspnetcore_schema.xml" +$aspnetCoreSchemaWin32From = $ExtractFilesRootPath + "\Win32\aspnetcore_schema.xml" + +$aspnetCorex64To = "$env:ProgramFiles\IIS Express\aspnetcore.dll" +$aspnetCoreWin32To = "${env:ProgramFiles(x86)}\IIS Express\aspnetcore.dll" +$aspnetCoreSchemax64To = "$env:ProgramFiles\IIS Express\config\schema\aspnetcore_schema.xml" +$aspnetCoreSchemaWin32To = "${env:ProgramFiles(x86)}\IIS Express\config\schema\aspnetcore_schema.xml" + +$aspnetCorex64IISTo = "$env:windir\system32\inetsrv\aspnetcore.dll" +$aspnetCoreWin32IISTo = "$env:windir\syswow64\inetsrv\aspnetcore.dll" +$aspnetCoreSchemax64IISTo = "$env:windir\system32\inetsrv\config\schema\aspnetcore_schema.xml" + +# if this is not solution output path, use nuget package directory structure +if (-not (Test-Path $aspnetCorex64From)) +{ + $aspnetCorex64From = $ExtractFilesRootPath + "\runtimes\win7-x64\native\aspnetcore.dll" + $aspnetCoreWin32From = $ExtractFilesRootPath + "\runtimes\win7-x86\native\aspnetcore.dll" + $aspnetCoreSchemax64From = $ExtractFilesRootPath + "\aspnetcore_schema.xml" + $aspnetCoreSchemaWin32From = $ExtractFilesRootPath + "\aspnetcore_schema.xml" + + $aspnetCorex64To = "$env:ProgramFiles\IIS Express\aspnetcore.dll" + $aspnetCoreWin32To = "${env:ProgramFiles(x86)}\IIS Express\aspnetcore.dll" + $aspnetCoreSchemax64To = "$env:ProgramFiles\IIS Express\config\schema\aspnetcore_schema.xml" + $aspnetCoreSchemaWin32To = "${env:ProgramFiles(x86)}\IIS Express\config\schema\aspnetcore_schema.xml" + + $aspnetCorex64IISTo = "$env:windir\system32\inetsrv\aspnetcore.dll" + $aspnetCoreWin32IISTo = "$env:windir\syswow64\inetsrv\aspnetcore.dll" + $aspnetCoreSchemax64IISTo = "$env:windir\system32\inetsrv\config\schema\aspnetcore_schema.xml" +} + +$is64BitMachine = $env:PROCESSOR_ARCHITECTURE.ToLower() -eq "amd64" +$isIISExpressInstalled = Test-Path $aspnetCorex64To +$isIISInstalled = Test-Path $aspnetCorex64IISTo + +# Check expected files are available on IIS/IISExpress directory +if (-not (Check-TargetFiles)) +{ + Say ("$LogHeader Error!!! Failed to update ANCM files because AspnetCore.dll is not installed on IIS/IISExpress directory.") + exit $EXIT_FAIL +} + +if ($Extract) +{ + # Extrack nuget package when $DoExtract is true + if (-not (Extract-ANCMFromNugetPackage)) + { + Say ("$LogHeader Error!!! Failed to extract ANCM file") + exit $EXIT_FAIL + } +} + +# clean up IIS and IISExpress worker processes and IIS services +Say ("$LogHeader Stopping w3wp.exe process") +Stop-Process -Name w3wp -ErrorAction Ignore -Force -Confirm:$false + +Say ("$LogHeader Stopping iisexpress.exe process") +Stop-Process -Name iisexpress -ErrorAction Ignore -Force -Confirm:$false + +$w3svcGotStopped = $false +$w3svcWindowsServce = Get-Service W3SVC -ErrorAction Ignore +if ($w3svcWindowsServce -and $w3svcWindowsServce.Status -eq "Running") +{ + Say ("$LogHeader Stopping w3svc service") + $w3svcGotStopped = $true + Stop-Service W3SVC -Force -ErrorAction Ignore + Say ("$LogHeader Stopping w3logsvc service") + Stop-Service W3LOGSVC -Force -ErrorAction Ignore +} + +if ($Rollback) +{ + Say ("$LogHeader Rolling back ANCM files...") +} +else +{ + Say ("Updating ANCM files...") +} +Update-ANCM + +# Recover w3svc service +if ($w3svcGotStopped) +{ + Say ("$LogHeader Starting w3svc service") + Start-Service W3SVC -ErrorAction Ignore + $w3svcServiceStopped = $false + + $w3svcWindowsServce = Get-Service W3SVC -ErrorAction Ignore + if ($w3svcWindowsServce.Status -ne "Running") + { + Say ("$LogHeader Error!!! Failed to start w3svc service.") + exit $EXIT_FAIL + } +} + +Say ("$LogHeader Finished!!!") +exit $EXIT_SUCCESS diff --git a/tools/stresstest.ps1 b/tools/stresstest.ps1 new file mode 100644 index 0000000000..981c6fcf44 --- /dev/null +++ b/tools/stresstest.ps1 @@ -0,0 +1,96 @@ +########################################################## +# NOTE: +# For running test automation, following prerequisite required: +# +# 1. On Win7, powershell should be upgraded to 4.0 +# https://social.technet.microsoft.com/wiki/contents/articles/21016.how-to-install-windows-powershell-4-0.aspx +# 2. url-rewrite should be installed +# 3. makecert.exe tools should be available +########################################################## + +# Replace aspnetcore.dll with the latest version +copy C:\gitroot\AspNetCoreModule\artifacts\build\AspNetCore\bin\Release\x64\aspnetcore.dll "C:\Program Files\IIS Express" +copy C:\gitroot\AspNetCoreModule\artifacts\build\AspNetCore\bin\Release\x64\aspnetcore.pdb "C:\Program Files\IIS Express" + + +# Enable appverif for IISExpress.exe +appverif /verify iisexpress.exe + +# Set the AspNetCoreModuleTest environment variable with the following command +cd C:\gitroot\AspNetCoreModule\test\AspNetCoreModule.Test +dotnet restore +dotnet build +$aspNetCoreModuleTest="C:\gitroot\AspNetCoreModule\test\AspNetCoreModule.Test\bin\Debug\net46" + +if (Test-Path (Join-Path $aspNetCoreModuleTest aspnetcoremodule.test.dll)) +{ + # Clean up applicationhost.config of IISExpress + del $env:userprofile\documents\iisexpress\config\applicationhost.config -Confirm:$false -Force + Start-Process "C:\Program Files\IIS Express\iisexpress.exe" + Sleep 3 + Stop-Process -Name iisexpress + + # Create sites + (1..50) | foreach { md ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) 2> out-null } + (1..50) | foreach { copy C:\gitroot\AspNetCoreModule\test\StressTestWebRoot\web.config ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) } + (1..50) | foreach { + $path = ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) + $appPath = "/foo"+$_ + & "C:\Program Files\IIS Express\appcmd.exe" add app /site.name:"WebSite1" /path:$appPath /physicalPath:$path + } + + <#(1..50) | foreach { + $configpath = ("WebSite1/foo" + $_) + $value = "C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ + ".exe" + & "C:\Program Files\IIS Express\appcmd.exe" set config $configpath -section:system.webServer/aspNetCore /processPath:$value + } + (1..50) | foreach { copy C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo.exe ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ +".exe") } + (1..50) | foreach { + $configpath = ("WebSite1/foo" + $_) + $value = "%AspNetCoreModuleTest%\AspnetCoreApp_HelloWeb\foo" + $_ + ".exe" + & "C:\Program Files\IIS Express\appcmd.exe" set config $configpath -section:system.webServer/aspNetCore /processPath:$value /apphostconfig:%AspNetCoreModuleTest%\config\applicationhost.config + + $value = "%AspNetCoreModuleTest%\AspnetCoreApp_HelloWeb\AutobahnTestServer.dll" + & "C:\Program Files\IIS Express\appcmd.exe" set config $configpath -section:system.webServer/aspNetCore /arguments:$value /apphostconfig:%AspNetCoreModuleTest%\config\applicationhost.config + } + #> + + # Start IISExpress with running the below command + &"C:\Program Files\Debugging Tools for Windows (x64)\windbg.exe" /g /G "C:\Program Files\IIS Express\iisexpress.exe" + + + # 6. Start stress testing + (1..10000) | foreach { + if ($_ % 2 -eq 0) + { + ("Recycling backend only") + stop-process -name dotnet + (1..50) | foreach { del ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ + "\app_offline.htm") -confirm:$false -Force 2> out-null } + stop-process -name dotnet + } + else + { + ("Recycling backedn + enabling appoffline ....") + stop-process -name dotnet + (1..50) | foreach { copy C:\gitroot\AspNetCoreModule\test\StressTestWebRoot\app_offline.htm ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) } + } + Sleep 1 + + (1..10) | foreach { + (1..50) | foreach { + invoke-webrequest ("http://localhost:8080/foo"+$_) > $null + } + } + } + + + # Stress test idea + # 1. Use Web Stress Tester + # 2. Run stop-process -name dotnet + # 3. Hit Q command to IISExpress console window + # 4. Use app_offline.htm + # 5. Save dummy web.config +} + +// bp aspnetcore!FORWARDING_HANDLER::FORWARDING_HANDLER +// bp aspnetcore!FORWARDING_HANDLER::~FORWARDING_HANDLER \ No newline at end of file