From dff0db80ca2092f1f6e6dbcd327d4e8d83075729 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Thu, 25 Aug 2016 12:23:56 -0700 Subject: [PATCH 001/107] Initial upload --- .gitattributes | 51 + .gitignore | 48 + AspNetCoreModule | 1 + AspNetCoreModule.sln | 41 + Build/Build.Settings | 88 + Build/Config.Definitions.Props | 21 + Build/Version.props | 9 + Build/build.msbuild | 29 + LICENSE.txt | 23 + build.cmd | 1 + src/AspNetCore/AspNetCore.vcxproj | 237 ++ src/AspNetCore/Inc/application.h | 292 ++ src/AspNetCore/Inc/applicationmanager.h | 159 ++ src/AspNetCore/Inc/aspnetcoreconfig.h | 184 ++ src/AspNetCore/Inc/aspnetcoreutils.h | 25 + src/AspNetCore/Inc/bldver.h | 61 + src/AspNetCore/Inc/debugutil.h | 82 + src/AspNetCore/Inc/filewatcher.h | 96 + src/AspNetCore/Inc/forwarderconnection.h | 157 ++ src/AspNetCore/Inc/forwardinghandler.h | 445 +++ src/AspNetCore/Inc/path.h | 95 + src/AspNetCore/Inc/processmanager.h | 193 ++ src/AspNetCore/Inc/protocolconfig.h | 105 + src/AspNetCore/Inc/proxymodule.h | 61 + src/AspNetCore/Inc/resource.h | 18 + src/AspNetCore/Inc/responseheaderhash.h | 110 + src/AspNetCore/Inc/serverprocess.h | 257 ++ src/AspNetCore/Inc/sttimer.h | 243 ++ src/AspNetCore/Inc/websockethandler.h | 217 ++ src/AspNetCore/Inc/winhttphelper.h | 91 + src/AspNetCore/Source.def | 4 + src/AspNetCore/Src/application.cxx | 143 + src/AspNetCore/Src/applicationmanager.cxx | 179 ++ src/AspNetCore/Src/aspnetcore_msg.mc | 66 + src/AspNetCore/Src/aspnetcoreconfig.cxx | 479 ++++ src/AspNetCore/Src/aspnetcoreutils.cxx | 55 + src/AspNetCore/Src/dllmain.cpp | 259 ++ src/AspNetCore/Src/filewatcher.cxx | 441 +++ src/AspNetCore/Src/forwarderconnection.cxx | 39 + src/AspNetCore/Src/forwardinghandler.cxx | 2970 ++++++++++++++++++++ src/AspNetCore/Src/path.cxx | 442 +++ src/AspNetCore/Src/precomp.hxx | 136 + src/AspNetCore/Src/processmanager.cxx | 292 ++ src/AspNetCore/Src/protocolconfig.cxx | 48 + src/AspNetCore/Src/proxymodule.cxx | 110 + src/AspNetCore/Src/responseheaderhash.cxx | 100 + src/AspNetCore/Src/serverprocess.cxx | 1823 ++++++++++++ src/AspNetCore/Src/websockethandler.cxx | 1084 +++++++ src/AspNetCore/Src/winhttphelper.cxx | 176 ++ src/AspNetCore/aspnetcore_schema.xml | 40 + src/AspNetCore/aspnetcoremodule.rc | Bin 0 -> 6094 bytes src/AspNetCore/resource.h | Bin 0 -> 1106 bytes src/AspNetCore/version.h | 12 + src/IISLib/IISLib.vcxproj | 175 ++ src/IISLib/acache.cxx | 443 +++ src/IISLib/acache.h | 115 + src/IISLib/ahutil.cpp | 1671 +++++++++++ src/IISLib/ahutil.h | 258 ++ src/IISLib/base64.cpp | 482 ++++ src/IISLib/base64.h | 42 + src/IISLib/buffer.h | 271 ++ src/IISLib/datetime.h | 14 + src/IISLib/dbgutil.h | 102 + src/IISLib/hashfn.h | 325 +++ src/IISLib/hashtable.h | 666 +++++ src/IISLib/listentry.h | 163 ++ src/IISLib/macros.h | 63 + src/IISLib/multisz.cpp | 469 ++++ src/IISLib/multisz.h | 225 ++ src/IISLib/multisza.cpp | 406 +++ src/IISLib/multisza.h | 226 ++ src/IISLib/ntassert.h | 32 + src/IISLib/percpu.h | 305 ++ src/IISLib/precomp.h | 18 + src/IISLib/prime.h | 85 + src/IISLib/pudebug.h | 736 +++++ src/IISLib/reftrace.c | 229 ++ src/IISLib/reftrace.h | 87 + src/IISLib/rwlock.h | 193 ++ src/IISLib/stringa.cpp | 1767 ++++++++++++ src/IISLib/stringa.h | 515 ++++ src/IISLib/stringu.cpp | 1267 +++++++++ src/IISLib/stringu.h | 427 +++ src/IISLib/tracelog.c | 235 ++ src/IISLib/tracelog.h | 105 + src/IISLib/treehash.h | 850 ++++++ src/IISLib/util.cxx | 74 + 87 files changed, 25379 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 160000 AspNetCoreModule create mode 100644 AspNetCoreModule.sln create mode 100644 Build/Build.Settings create mode 100644 Build/Config.Definitions.Props create mode 100644 Build/Version.props create mode 100644 Build/build.msbuild create mode 100644 LICENSE.txt create mode 100644 build.cmd create mode 100644 src/AspNetCore/AspNetCore.vcxproj create mode 100644 src/AspNetCore/Inc/application.h create mode 100644 src/AspNetCore/Inc/applicationmanager.h create mode 100644 src/AspNetCore/Inc/aspnetcoreconfig.h create mode 100644 src/AspNetCore/Inc/aspnetcoreutils.h create mode 100644 src/AspNetCore/Inc/bldver.h create mode 100644 src/AspNetCore/Inc/debugutil.h create mode 100644 src/AspNetCore/Inc/filewatcher.h create mode 100644 src/AspNetCore/Inc/forwarderconnection.h create mode 100644 src/AspNetCore/Inc/forwardinghandler.h create mode 100644 src/AspNetCore/Inc/path.h create mode 100644 src/AspNetCore/Inc/processmanager.h create mode 100644 src/AspNetCore/Inc/protocolconfig.h create mode 100644 src/AspNetCore/Inc/proxymodule.h create mode 100644 src/AspNetCore/Inc/resource.h create mode 100644 src/AspNetCore/Inc/responseheaderhash.h create mode 100644 src/AspNetCore/Inc/serverprocess.h create mode 100644 src/AspNetCore/Inc/sttimer.h create mode 100644 src/AspNetCore/Inc/websockethandler.h create mode 100644 src/AspNetCore/Inc/winhttphelper.h create mode 100644 src/AspNetCore/Source.def create mode 100644 src/AspNetCore/Src/application.cxx create mode 100644 src/AspNetCore/Src/applicationmanager.cxx create mode 100644 src/AspNetCore/Src/aspnetcore_msg.mc create mode 100644 src/AspNetCore/Src/aspnetcoreconfig.cxx create mode 100644 src/AspNetCore/Src/aspnetcoreutils.cxx create mode 100644 src/AspNetCore/Src/dllmain.cpp create mode 100644 src/AspNetCore/Src/filewatcher.cxx create mode 100644 src/AspNetCore/Src/forwarderconnection.cxx create mode 100644 src/AspNetCore/Src/forwardinghandler.cxx create mode 100644 src/AspNetCore/Src/path.cxx create mode 100644 src/AspNetCore/Src/precomp.hxx create mode 100644 src/AspNetCore/Src/processmanager.cxx create mode 100644 src/AspNetCore/Src/protocolconfig.cxx create mode 100644 src/AspNetCore/Src/proxymodule.cxx create mode 100644 src/AspNetCore/Src/responseheaderhash.cxx create mode 100644 src/AspNetCore/Src/serverprocess.cxx create mode 100644 src/AspNetCore/Src/websockethandler.cxx create mode 100644 src/AspNetCore/Src/winhttphelper.cxx create mode 100644 src/AspNetCore/aspnetcore_schema.xml create mode 100644 src/AspNetCore/aspnetcoremodule.rc create mode 100644 src/AspNetCore/resource.h create mode 100644 src/AspNetCore/version.h create mode 100644 src/IISLib/IISLib.vcxproj create mode 100644 src/IISLib/acache.cxx create mode 100644 src/IISLib/acache.h create mode 100644 src/IISLib/ahutil.cpp create mode 100644 src/IISLib/ahutil.h create mode 100644 src/IISLib/base64.cpp create mode 100644 src/IISLib/base64.h create mode 100644 src/IISLib/buffer.h create mode 100644 src/IISLib/datetime.h create mode 100644 src/IISLib/dbgutil.h create mode 100644 src/IISLib/hashfn.h create mode 100644 src/IISLib/hashtable.h create mode 100644 src/IISLib/listentry.h create mode 100644 src/IISLib/macros.h create mode 100644 src/IISLib/multisz.cpp create mode 100644 src/IISLib/multisz.h create mode 100644 src/IISLib/multisza.cpp create mode 100644 src/IISLib/multisza.h create mode 100644 src/IISLib/ntassert.h create mode 100644 src/IISLib/percpu.h create mode 100644 src/IISLib/precomp.h create mode 100644 src/IISLib/prime.h create mode 100644 src/IISLib/pudebug.h create mode 100644 src/IISLib/reftrace.c create mode 100644 src/IISLib/reftrace.h create mode 100644 src/IISLib/rwlock.h create mode 100644 src/IISLib/stringa.cpp create mode 100644 src/IISLib/stringa.h create mode 100644 src/IISLib/stringu.cpp create mode 100644 src/IISLib/stringu.h create mode 100644 src/IISLib/tracelog.c create mode 100644 src/IISLib/tracelog.h create mode 100644 src/IISLib/treehash.h create mode 100644 src/IISLib/util.cxx diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..97b827b758 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,51 @@ +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs text=auto diff=csharp +*.vb text=auto +*.resx text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto +*.vbproj text=auto +*.fsproj text=auto +*.dbproj text=auto +*.sln text=auto eol=crlf +*.sh eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..efccce79d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +[Oo]bj/ +[Bb]in/ +TestResults/ +.nuget/ +*.sln.ide/ +_ReSharper.*/ +packages/ +artifacts/ +PublishProfiles/ +*.user +*.suo +*.cache +*.docstates +_ReSharper.* +nuget.exe +project.lock.json +*net45.csproj +*net451.csproj +*k10.csproj +*.psess +*.vsp +*.pidb +*.userprefs +*DS_Store +*.ncrunchsolution +*.*sdf +*.ipch +*.bin +*.vs/ +.testPublish/ + +*.obj +*.tlog +*.CppClean.log + +src/*/Debug/ +src/*/x64/Debug/ +src/*/Release/ +src/*/x64/Release/ + +*.aps +*.pdb +*.lib +*.idb + +src/AspNetCore/aspnetcore_msg.h +src/AspNetCore/aspnetcore_msg.rc +src/AspNetCore/version.h \ No newline at end of file diff --git a/AspNetCoreModule b/AspNetCoreModule new file mode 160000 index 0000000000..c9cae1691f --- /dev/null +++ b/AspNetCoreModule @@ -0,0 +1 @@ +Subproject commit c9cae1691f80951e40985fe149db6e094064f080 diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln new file mode 100644 index 0000000000..986f3e555a --- /dev/null +++ b/AspNetCoreModule.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" + ProjectSection(ProjectDependencies) = postProject + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.Build.0 = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.Build.0 = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Build/Build.Settings b/Build/Build.Settings new file mode 100644 index 0000000000..c1752abb93 --- /dev/null +++ b/Build/Build.Settings @@ -0,0 +1,88 @@ + + + + + $(MSBuildThisFileDirectory)..\ + Debug + Win32 + v120 + v140 + v120 + $(SolutionDir)$(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/Version.props b/Build/Version.props new file mode 100644 index 0000000000..27a5f2db48 --- /dev/null +++ b/Build/Version.props @@ -0,0 +1,9 @@ + + + + 7 + 1 + 1968 + -RTM + + diff --git a/Build/build.msbuild b/Build/build.msbuild new file mode 100644 index 0000000000..59c022e99c --- /dev/null +++ b/Build/build.msbuild @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000..3293bc0ea5 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,23 @@ +ASP.NET Core Module + +Copyright (c) Microsoft Corporation +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/build.cmd b/build.cmd new file mode 100644 index 0000000000..0d744a8940 --- /dev/null +++ b/build.cmd @@ -0,0 +1 @@ +msbuild "%~dp0\Build\build.msbuild" /v:minimal /maxcpucount /nodeReuse:false %* \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj new file mode 100644 index 0000000000..b510857a23 --- /dev/null +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -0,0 +1,237 @@ + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {439824F9-1455-4CC4-BD79-B44FA0A16552} + Win32Proj + AspNetCoreModule + AspNetCore + aspnetcore + $(SolutionDir)bin\$(Configuration)\$(Platform)\ + true + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + false + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ..\IISLib;.\Inc + + + 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 + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ..\IISLib;.\Inc + + + 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 + + + + + Level3 + Create + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + ..\IISLib;inc + precomp.hxx + + + Windows + true + 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) + + + + + Level3 + Create + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + ..\IISLib;inc + + + Windows + true + 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 + + + + + + + + + + + + + + + + + + false + false + Document + mc %(FullPath) + mc %(FullPath) + %(Filename).rc;%(Filename).h;MSG0409.bin + %(Filename).rc;%(Filename).h;MSG0409.bin + Compiling Event Messages ... + Compiling Event Messages ... + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + + + + + + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h new file mode 100644 index 0000000000..c06e763937 --- /dev/null +++ b/src/AspNetCore/Inc/application.h @@ -0,0 +1,292 @@ +// Copyright (c) Microsoft Corporation. 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 APPLICATION_KEY +{ +public: + + APPLICATION_KEY( + VOID + ) : INLINE_STRU_INIT(m_struKey) + { + } + + HRESULT + Initialize( + _In_ LPCWSTR pszKey + ) + { + return m_struKey.Copy(pszKey); + } + + BOOL + GetIsEqual( + const APPLICATION_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 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 = FALSE; + LARGE_INTEGER li = {0}; + CHAR *pszBuff = NULL; + HANDLE 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 == NULL ) + { + goto Finished; + } + + if(!GetFileSizeEx( handle, &li )) + { + goto Finished; + } + + fResult = TRUE; + + 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 ) + { + CloseHandle(handle); + handle = NULL; + } + + if( pszBuff != NULL ) + { + delete[] pszBuff; + pszBuff = NULL; + } + + return fResult; + } + + mutable LONG m_cRefs; + STRA m_Contents; + STRU m_Path; +}; + +class APPLICATION_MANAGER; + +class APPLICATION +{ +public: + + APPLICATION() : m_pProcessManager(NULL), m_pApplicationManager(NULL), m_cRefs(1), + m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL) + { + } + + APPLICATION_KEY * + QueryApplicationKey() + { + return &m_applicationKey; + } + + VOID + SetAppOfflineFound( + BOOL found + ) + { + m_fAppOfflineFound = found; + } + + BOOL + AppOfflineFound() + { + return m_fAppOfflineFound; + } + + HRESULT + GetProcess( + _In_ IHttpContext *context, + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ SERVER_PROCESS **ppServerProcess + ) + { + return m_pProcessManager->GetProcess( context, pConfig, ppServerProcess ); + } + + HRESULT + Recycle() + { + HRESULT hr = S_OK; + m_pProcessManager->ShutdownAllProcesses(); + return hr; + } + + VOID + ReferenceApplication() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceApplication() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + APP_OFFLINE_HTM* QueryAppOfflineHtm() + { + return m_pAppOfflineHtm; + } + + STRU* + QueryApplicationPhysicalPath() + { + return &m_strAppPhysicalPath; + } + + ~APPLICATION(); + + HRESULT + Initialize( + _In_ APPLICATION_MANAGER *pApplicationManager, + _In_ LPCWSTR pszApplication, + _In_ LPCWSTR pszPhysicalPath + ); + + VOID + UpdateAppOfflineFileHandle(); + + HRESULT + StartMonitoringAppOffline(); + +private: + + STRU m_strAppPhysicalPath; + mutable LONG m_cRefs; + APPLICATION_KEY m_applicationKey; + PROCESS_MANAGER* m_pProcessManager; + APPLICATION_MANAGER *m_pApplicationManager; + BOOL m_fAppOfflineFound; + APP_OFFLINE_HTM *m_pAppOfflineHtm; + FILE_WATCHER_ENTRY *m_pFileWatcherEntry; +}; + +class APPLICATION_HASH : + public HASH_TABLE +{ + +public: + + APPLICATION_HASH() + {} + + APPLICATION_KEY * + ExtractKey( + APPLICATION *pApplication + ) + { + return pApplication->QueryApplicationKey(); + } + + DWORD + CalcKeyHash( + APPLICATION_KEY *key + ) + { + return key->CalcKeyHash(); + } + + BOOL + EqualKeys( + APPLICATION_KEY *key1, + APPLICATION_KEY *key2 + ) + { + return key1->GetIsEqual(key2); + } + + VOID + ReferenceRecord( + APPLICATION *pApplication + ) + { + pApplication->ReferenceApplication(); + } + + VOID + DereferenceRecord( + APPLICATION *pApplication + ) + { + pApplication->DereferenceApplication(); + } + +private: + + APPLICATION_HASH(const APPLICATION_HASH &); + void operator=(const APPLICATION_HASH &); +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h new file mode 100644 index 0000000000..e1c48c69cd --- /dev/null +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. 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 + +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 + GetApplication( + _In_ IHttpContext* pContext, + _In_ LPCWSTR pszApplication, + _Out_ APPLICATION ** ppApplication + ); + + static + VOID + RecycleOnFileChange( + APPLICATION_MANAGER* manager, + APPLICATION* application + ); + + HRESULT + RecycleApplication( + _In_ LPCWSTR pszApplication + ); + + HRESULT + Get502ErrorPage( + _Out_ HTTP_DATA_CHUNK** ppErrorPage + ); + + ~APPLICATION_MANAGER() + { + if(m_pApplicationHash != NULL) + { + m_pApplicationHash->Clear(); + delete m_pApplicationHash; + m_pApplicationHash = 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_pApplicationHash == NULL) + { + m_pApplicationHash = new APPLICATION_HASH(); + if(m_pApplicationHash == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pApplicationHash->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_pApplicationHash(NULL), m_pFileWatcher(NULL), m_pHttp502ErrorPage(NULL), m_pstrErrorInfo( + " \ + \ + \ + \ + 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: \ + http://go.microsoft.com/fwlink/?LinkID=808681

\ +
\ +
\ +
") + { + InitializeSRWLock(&m_srwLock); + } + + FILE_WATCHER *m_pFileWatcher; + APPLICATION_HASH *m_pApplicationHash; + static APPLICATION_MANAGER *sm_pApplicationManager; + SRWLOCK m_srwLock; + HTTP_DATA_CHUNK *m_pHttp502ErrorPage; + LPSTR m_pstrErrorInfo; +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h new file mode 100644 index 0000000000..00da93e519 --- /dev/null +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. 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_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 MAX_RAPID_FAILS_PER_MINUTE 100 +#define MILLISECONDS_IN_ONE_SECOND 1000 +#define MIN_PORT 1025 +#define MAX_PORT 48000 + +#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer * g_pHttpServer; + + +class ASPNETCORE_CONFIG : IHttpStoredContext +{ +public: + + virtual + ~ASPNETCORE_CONFIG(); + + VOID + CleanupStoredContext() + { + delete this; + } + + static + HRESULT + GetConfig( + _In_ IHttpContext *pHttpContext, + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + ); + + MULTISZ* + QueryEnvironmentVariables( + VOID + ) + { + return &m_mszEnvironment; + } + + 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* + QueryProcessPath( + VOID + ) + { + return &m_struProcessPath; + } + + BOOL + QueryStdoutLogEnabled() + { + return m_fStdoutLogEnabled; + } + + BOOL + QueryForwardWindowsAuthToken() + { + return m_fForwardWindowsAuthToken; + } + + BOOL + QueryDisableStartUpErrorPage() + { + return m_fDisableStartUpErrorPage; + } + + STRU* + QueryStdoutLogFile() + { + return &m_struStdoutLogFile; + } + +private: + + // + // private constructor + // + + ASPNETCORE_CONFIG(): + m_fStdoutLogEnabled( FALSE ) + { + } + + HRESULT + Populate( + IHttpContext *pHttpContext + ); + + DWORD m_dwRequestTimeoutInMS; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + MULTISZ m_mszEnvironment; + DWORD m_dwRapidFailsPerMinute; + STRU m_struApplication; + STRU m_struArguments; + STRU m_struProcessPath; + BOOL m_fStdoutLogEnabled; + STRU m_struStdoutLogFile; + DWORD m_dwProcessesPerApplication; + BOOL m_fForwardWindowsAuthToken; + BOOL m_fDisableStartUpErrorPage; + MULTISZ m_mszRecycleOnFileChangeFiles; +}; diff --git a/src/AspNetCore/Inc/aspnetcoreutils.h b/src/AspNetCore/Inc/aspnetcoreutils.h new file mode 100644 index 0000000000..ef5739c93e --- /dev/null +++ b/src/AspNetCore/Inc/aspnetcoreutils.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class ASPNETCORE_UTILS +{ +public: + + static + HRESULT + ReplacePlaceHolderWithValue( + _Inout_ LPWSTR pszStr, + _In_ LPWSTR pszPlaceholder, + _In_ DWORD cchPlaceholder, + _In_ DWORD dwValue, + _In_ DWORD dwNumDigitsInValue, + _Out_ BOOL *pfReplaced + ); + +private: + ASPNETCORE_UTILS() + { + } +}; \ No newline at end of file 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/debugutil.h b/src/AspNetCore/Inc/debugutil.h new file mode 100644 index 0000000000..7378462efb --- /dev/null +++ b/src/AspNetCore/Inc/debugutil.h @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. 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/AspNetCore/Inc/filewatcher.h b/src/AspNetCore/Inc/filewatcher.h new file mode 100644 index 0000000000..b1939a9e84 --- /dev/null +++ b/src/AspNetCore/Inc/filewatcher.h @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. 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) +#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; + +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; + CRITICAL_SECTION m_csSyncRoot; +}; + +class FILE_WATCHER_ENTRY +{ +public: + FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); + virtual ~FILE_WATCHER_ENTRY(); + + OVERLAPPED _overlapped; + + HRESULT + Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION* pApplication, + _In_ HANDLE hImpersonationToken + ); + + HRESULT Monitor(); + + VOID StopMonitor(); + + HRESULT + HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion + ); + +private: + DWORD _dwSignature; + BUFFER _buffDirectoryChanges; + HANDLE _hImpersonationToken; + HANDLE _hDirectory; + FILE_WATCHER* _pFileMonitor; + APPLICATION* _pApplication; + STRU _strFileName; + STRU _strDirectoryName; + LONG _lStopMonitorCalled; + SRWLOCK _srwLock; +}; diff --git a/src/AspNetCore/Inc/forwarderconnection.h b/src/AspNetCore/Inc/forwarderconnection.h new file mode 100644 index 0000000000..a3f5dfdabe --- /dev/null +++ b/src/AspNetCore/Inc/forwarderconnection.h @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. 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/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h new file mode 100644 index 0000000000..06fde91cee --- /dev/null +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -0,0 +1,445 @@ +#pragma once + +/*++ + +Copyright (c) 2013 Microsoft Corporation + +Module Name: + + forwardinghandler.h + +Abstract: + + Handler for handling URLs from out-of-box. + +--*/ + +#include "forwarderconnection.h" +#include "protocolconfig.h" +#include "serverprocess.h" +#include "application.h" +#include "tracelog.h" +#include "websockethandler.h" + +enum FORWARDING_REQUEST_STATUS +{ + FORWARDER_START, + FORWARDER_SENDING_REQUEST, + FORWARDER_RECEIVING_RESPONSE, + FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, + FORWARDER_DONE +}; + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer * g_pHttpServer; +extern BOOL g_fAsyncDisconnectAvailable; +extern PCWSTR g_pszModuleName; +extern HMODULE g_hModule; +extern HMODULE g_hWinHttpModule; +extern DWORD g_dwTlsIndex; +extern DWORD g_OptionalWinHttpFlags; + +enum MULTI_PART_POSITION +{ + MULTI_PART_IN_BOUNDARY, + MULTI_PART_IN_HEADER, + MULTI_PART_IN_CHUNK, + MULTI_PART_IN_CHUNK_END +}; + +class ASYNC_DISCONNECT_CONTEXT; + +#define FORWARDING_HANDLER_SIGNATURE ((DWORD)'FHLR') +#define FORWARDING_HANDLER_SIGNATURE_FREE ((DWORD)'fhlr') + +class FORWARDING_HANDLER +{ +public: + + FORWARDING_HANDLER( + __in IHttpContext * pW3Context + ); + + static void * operator new(size_t size); + + static void operator delete(void * pMemory); + + VOID + ReferenceForwardingHandler( + VOID + ) const; + + VOID + DereferenceForwardingHandler( + VOID + ) const; + + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler(); + + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ); + + IHttpTraceContext * + QueryTraceContext() + { + return m_pW3Context->GetTraceContext(); + } + + IHttpContext * + QueryHttpContext( + VOID + ) + { + return m_pW3Context; + } + + static + VOID + CALLBACK + OnWinHttpCompletion( + HINTERNET hRequest, + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength + ) + { + FORWARDING_HANDLER * pThis = static_cast(reinterpret_cast(dwContext)); + DBG_ASSERT(pThis->m_Signature == FORWARDING_HANDLER_SIGNATURE); + pThis->OnWinHttpCompletionInternal(hRequest, + dwInternetStatus, + lpvStatusInformation, + dwStatusInformationLength); + } + + static + HRESULT + StaticInitialize( + BOOL fEnableReferenceCountTracing + ); + + static + VOID + StaticTerminate(); + + static + PCWSTR + QueryErrorFormat() + { + return sm_strErrorFormat.QueryStr(); + } + + static + HANDLE + QueryEventLog() + { + return sm_hEventLog; + } + + VOID + TerminateRequest( + bool fClientInitiated + ); + + static HINTERNET sm_hSession; + + HRESULT + SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD cchHeaders + ); + + HRESULT + OnSharedRequestEntity( + ULONGLONG ulOffset, + LPCBYTE pvBuffer, + DWORD cbBuffer + ); + + VOID + SetStatus( + FORWARDING_REQUEST_STATUS status + ) + { + m_RequestStatus = status; + } + +private: + + virtual + ~FORWARDING_HANDLER( + VOID + ); + + // + // Begin OnMapRequestHandler phases. + // + + HRESULT + CreateWinHttpRequest( + __in const IHttpRequest * pRequest, + __in const PROTOCOL_CONFIG * pProtocol, + __in HINTERNET hConnect, + __inout STRU * pstrUrl, + const STRU& strDestination, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess + ); + + // + // End OnMapRequestHandler phases. + // + + VOID + RemoveRequest(); + + HRESULT + GetHeaders( + const PROTOCOL_CONFIG * pProtocol, + PCWSTR pszDestination, + PCWSTR * ppszHeaders, + DWORD * pcchHeaders, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess + ); + + HRESULT + DoReverseRewrite( + __in IHttpResponse *pResponse + ); + + BYTE * + GetNewResponseBuffer( + DWORD dwBufferSize + ); + + VOID + FreeResponseBuffers(); + + VOID + OnWinHttpCompletionInternal( + HINTERNET hRequest, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + 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(); + + HRESULT + OnWebSocketWinHttpSendComplete( + HINTERNET hRequest, + LPVOID pvStatus, + DWORD hrCompletion, + DWORD cbCompletion, + bool * pfAnotherCompletionExpected + ); + + HRESULT + OnWebSocketWinHttpReceiveComplete( + HINTERNET hRequest, + LPVOID pvStatus, + DWORD hrCompletion, + DWORD cbCompletion, + bool * pfAnotherCompletionExpected + ); + + HRESULT + OnWebSocketIisSendComplete( + DWORD hrCompletion, + DWORD cbCompletion + ); + + HRESULT + OnWebSocketIisReceiveComplete( + DWORD hrCompletion, + DWORD cbCompletion + ); + + HRESULT + DoIisWebSocketReceive( + VOID + ); + + VOID + TerminateWebsocket( + VOID + ); + + DWORD m_Signature; + mutable LONG m_cRefs; + + IHttpContext * m_pW3Context; + IHttpContext * m_pChildRequestContext; + + // + // WinHTTP request handle is protected using a read-write lock. + // + SRWLOCK m_RequestLock; + HINTERNET m_hRequest; + + APP_OFFLINE_HTM *m_pAppOfflineHtm; + APPLICATION *m_pApplication; + + bool m_fHandleClosedDueToClient; + bool m_fResponseHeadersReceivedAndSet; + BOOL m_fDoReverseRewriteHeaders; + DWORD m_msStartTime; + + DWORD m_BytesToReceive; + DWORD m_BytesToSend; + + BYTE * m_pEntityBuffer; + DWORD m_cchLastSend; + + static const SIZE_T INLINE_ENTITY_BUFFERS = 8; + DWORD m_cEntityBuffers; + BUFFER_T m_buffEntityBuffers; + + DWORD m_cBytesBuffered; + DWORD m_cMinBufferLimit; + + PCSTR m_pszOriginalHostHeader; + + FORWARDING_REQUEST_STATUS m_RequestStatus; + + ASYNC_DISCONNECT_CONTEXT * m_pDisconnect; + + PCWSTR m_pszHeaders; + DWORD m_cchHeaders; + + bool m_fWebSocketEnabled; + + STRU m_strFullUri; + + ULONGLONG m_cContentLength; + + WEBSOCKET_HANDLER * m_pWebSocket; + + static PROTOCOL_CONFIG sm_ProtocolConfig; + + static STRU sm_strErrorFormat; + + static HANDLE sm_hEventLog; + + static ALLOC_CACHE_HANDLER * sm_pAlloc; + + // + // Reference cout tracing for debugging purposes. + // + static TRACE_LOG * sm_pTraceLog; +}; + +class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext +{ + public: + ASYNC_DISCONNECT_CONTEXT() + { + m_pHandler = NULL; + } + + VOID + CleanupStoredContext() + { + DBG_ASSERT(m_pHandler == NULL); + delete this; + } + + VOID + NotifyDisconnect() + { + FORWARDING_HANDLER *pInitialValue = (FORWARDING_HANDLER*) + InterlockedExchangePointer((PVOID*) &m_pHandler, NULL); + + if (pInitialValue != NULL) + { + pInitialValue->TerminateRequest(TRUE); + pInitialValue->DereferenceForwardingHandler(); + } + } + + VOID + SetHandler( + FORWARDING_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->ReferenceForwardingHandler(); + InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler); + } + + VOID + ResetHandler( + VOID + ) + { + FORWARDING_HANDLER *pInitialValue = (FORWARDING_HANDLER*) + InterlockedExchangePointer( (PVOID*)&m_pHandler, NULL); + + if (pInitialValue != NULL) + { + pInitialValue->DereferenceForwardingHandler(); + } + } + + private: + ~ASYNC_DISCONNECT_CONTEXT() + {} + + FORWARDING_HANDLER * m_pHandler; +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/path.h b/src/AspNetCore/Inc/path.h new file mode 100644 index 0000000000..05545acfd5 --- /dev/null +++ b/src/AspNetCore/Inc/path.h @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class PATH +{ +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 + ); + +private: + + PATH() {} + ~PATH() {} + + static + CHAR + ToHexDigit( + UINT nDigit + ) + { + return static_cast(nDigit > 9 ? nDigit - 10 + 'A' : nDigit + '0'); + } +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/processmanager.h b/src/AspNetCore/Inc/processmanager.h new file mode 100644 index 0000000000..8cd7fd8e0f --- /dev/null +++ b/src/AspNetCore/Inc/processmanager.h @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. 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 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_ IHttpContext *context, + _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 ) + { + 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]->StopProcess(); + 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/AspNetCore/Inc/protocolconfig.h b/src/AspNetCore/Inc/protocolconfig.h new file mode 100644 index 0000000000..d9d730c544 --- /dev/null +++ b/src/AspNetCore/Inc/protocolconfig.h @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "aspnetcoreconfig.h" + +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/AspNetCore/Inc/proxymodule.h b/src/AspNetCore/Inc/proxymodule.h new file mode 100644 index 0000000000..7cf3486fb4 --- /dev/null +++ b/src/AspNetCore/Inc/proxymodule.h @@ -0,0 +1,61 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "forwardinghandler.h" + +class CProxyModule : public CHttpModule +{ + public: + + CProxyModule(); + + ~CProxyModule(); + + 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: + + FORWARDING_HANDLER * m_pHandler; +}; + +class CProxyModuleFactory : 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..b4d8d2dd88 --- /dev/null +++ b/src/AspNetCore/Inc/resource.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. 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"Process '%d' started 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"Failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Process was created with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Failed to start process with commandline '%s', ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Process was created with commandline '%s' but did not listen on the given port '%d'" +#define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Process was created with commandline '%s' but either crashed or did not reponse within given time 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'." diff --git a/src/AspNetCore/Inc/responseheaderhash.h b/src/AspNetCore/Inc/responseheaderhash.h new file mode 100644 index 0000000000..7ef127366b --- /dev/null +++ b/src/AspNetCore/Inc/responseheaderhash.h @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. 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 &); +}; + +extern RESPONSE_HEADER_HASH * g_pResponseHeaderHash; \ No newline at end of file diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h new file mode 100644 index 0000000000..2132a3a21b --- /dev/null +++ b/src/AspNetCore/Inc/serverprocess.h @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. 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 LOCALHOST "127.0.0.1" +#define ASPNETCORE_PORT_STR L"ASPNETCORE_PORT" +#define ASPNETCORE_PORT_PLACEHOLDER L"%ASPNETCORE_PORT%" +#define ASPNETCORE_PORT_PLACEHOLDER_CCH 17 +#define ASPNETCORE_DEBUG_PORT_STR L"ASPNETCORE_DEBUG_PORT" +#define ASPNETCORE_DEBUG_PORT_PLACEHOLDER L"%ASPNETCORE_DEBUG_PORT%" +#define ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH 23 +#define MAX_ACTIVE_CHILD_PROCESSES 16 + +class PROCESS_MANAGER; +class FORWARDER_CONNECTION; + +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_ MULTISZ *pszEnvironment, + _In_ BOOL fStdoutLogEnabled, + _In_ STRU *pstruStdoutLogFile + ); + + + HRESULT + StartProcess( + _In_ IHttpContext *context + ); + + HRESULT + SetWindowsAuthToken( + _In_ HANDLE hToken, + _Out_ LPHANDLE pTargeTokenHandle + ); + + BOOL + IsReady( + VOID + ) + { + return m_fReady; + } + + VOID + StopProcess( + VOID + ); + + DWORD + GetPort() + { + return m_dwPort; + } + + DWORD + GetDebugPort() + { + return m_dwDebugPort; + } + + VOID + ReferenceServerProcess( + VOID + ) + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceServerProcess( + VOID + ) + { + _ASSERT(m_cRefs != 0 ); + + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + virtual + ~SERVER_PROCESS(); + + HRESULT + HandleProcessExit( + VOID + ); + + FORWARDER_CONNECTION* + QueryWinHttpConnection( + VOID + ) + { + return m_pForwarderConnection; + } + + static + VOID + CALLBACK + TimerCallback( + _In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID Context, + _In_ PTP_TIMER Timer + ); + + LPCWSTR + QueryFullLogPath() + { + return m_struFullLogFile.QueryStr(); + } + + LPCSTR + QueryGuid() + { + return m_straGuid.QueryStr(); + } + + DWORD + QueryProcessGroupId() + { + return m_dwProcessId; + } + + VOID + SendSignal( + VOID + ); + +private: + + BOOL + IsDebuggerIsAttached( + VOID + ); + + HRESULT + StopAllProcessesInJobObject( + VOID + ); + + HRESULT + SetupStdHandles( + _In_ IHttpContext *context, + _In_ LPSTARTUPINFOW pStartupInfo + ); + + HRESULT + CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ BOOL *pfReady + ); + + HRESULT + CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady + ); + + HRESULT + RegisterProcessWait( + _In_ PHANDLE phWaitHandle, + _In_ HANDLE hProcessToWaitOn + ); + + HRESULT + GetChildProcessHandles( + ); + + DWORD + GenerateRandomPort( + VOID + ) + { + return (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1; + } + + DWORD + GetNumberOfDigits( + _In_ DWORD dwNumber + ) + { + DWORD digits = 0; + + if( dwNumber == 0 ) + { + digits = 1; + goto Finished; + } + + while( dwNumber > 0) + { + dwNumber = dwNumber / 10; + digits ++; + } + Finished: + return digits; + } + + FORWARDER_CONNECTION *m_pForwarderConnection; + HANDLE m_hJobObject; + BOOL m_fStdoutLogEnabled; + STRU m_struLogFile; + STRU m_struFullLogFile; + STTIMER m_Timer; + HANDLE m_hStdoutHandle; + volatile BOOL m_fStopping; + volatile BOOL m_fReady; + CRITICAL_SECTION m_csLock; + SOCKET m_socket; + DWORD m_dwPort; + DWORD m_dwDebugPort; + STRU m_ProcessPath; + STRU m_Arguments; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + MULTISZ m_Environment; + mutable LONG m_cRefs; + HANDLE m_hProcessWaitHandle; + DWORD m_cChildProcess; + HANDLE m_hChildProcessWaitHandles[MAX_ACTIVE_CHILD_PROCESSES]; + DWORD m_dwProcessId; + DWORD m_dwListeningProcessId; + STRA m_straGuid; + HANDLE m_CancelEvent; + + // + // m_hProcessHandle is the handle to process this object creates. + // + + HANDLE m_hProcessHandle; + HANDLE m_hListeningProcessHandle; + + // + // m_hChildProcessHandle is the handle to process created by + // m_hProcessHandle process if it does. + // + + HANDLE m_hChildProcessHandles[MAX_ACTIVE_CHILD_PROCESSES]; + DWORD m_dwChildProcessIds[MAX_ACTIVE_CHILD_PROCESSES]; + PROCESS_MANAGER *m_pProcessManager; +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/sttimer.h b/src/AspNetCore/Inc/sttimer.h new file mode 100644 index 0000000000..917ee7ecf9 --- /dev/null +++ b/src/AspNetCore/Inc/sttimer.h @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#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; + } + +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/AspNetCore/Inc/websockethandler.h b/src/AspNetCore/Inc/websockethandler.h new file mode 100644 index 0000000000..70d3139b6d --- /dev/null +++ b/src/AspNetCore/Inc/websockethandler.h @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +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; + + static + LIST_ENTRY sm_RequestsListHead; + + static + SRWLOCK sm_RequestsListLock; + + static + TRACE_LOG * sm_pTraceLog; +}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/winhttphelper.h b/src/AspNetCore/Inc/winhttphelper.h new file mode 100644 index 0000000000..b301a76cf2 --- /dev/null +++ b/src/AspNetCore/Inc/winhttphelper.h @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. 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/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/application.cxx b/src/AspNetCore/Src/application.cxx new file mode 100644 index 0000000000..5db1445318 --- /dev/null +++ b/src/AspNetCore/Src/application.cxx @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +APPLICATION::~APPLICATION() +{ + if (m_pFileWatcherEntry != NULL) + { + m_pFileWatcherEntry->StopMonitor(); + delete m_pFileWatcherEntry; + m_pFileWatcherEntry = NULL; + } + + if (m_pProcessManager != NULL) + { + m_pProcessManager->ShutdownAllProcesses(); + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } +} + +HRESULT +APPLICATION::Initialize( + _In_ APPLICATION_MANAGER* pApplicationManager, + _In_ LPCWSTR pszApplication, + _In_ LPCWSTR pszPhysicalPath +) +{ + HRESULT hr = S_OK; + + DBG_ASSERT(pszPhysicalPath != NULL); + DBG_ASSERT(pApplicationManager != NULL); + DBG_ASSERT(pszPhysicalPath != NULL); + m_strAppPhysicalPath.Copy(pszPhysicalPath); + + m_pApplicationManager = pApplicationManager; + + hr = m_applicationKey.Initialize(pszApplication); + if (FAILED(hr)) + { + goto Finished; + } + + 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; + } + } + + if (m_pFileWatcherEntry == NULL) + { + m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pApplicationManager->GetFileWatcher()); + if (m_pFileWatcherEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + UpdateAppOfflineFileHandle(); + +Finished: + + if (FAILED(hr)) + { + if (m_pFileWatcherEntry != NULL) + { + delete m_pFileWatcherEntry; + m_pFileWatcherEntry = NULL; + } + + if (m_pProcessManager != NULL) + { + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } + } + + return hr; +} + +HRESULT +APPLICATION::StartMonitoringAppOffline() +{ + HRESULT hr = S_OK; + + hr = m_pFileWatcherEntry->Create(m_strAppPhysicalPath.QueryStr(), L"app_offline.htm", this, NULL); + + return hr; +} + +VOID +APPLICATION::UpdateAppOfflineFileHandle() +{ + STRU strFilePath; + PATH::ConvertPathToFullPath(L".\\app_offline.htm", m_strAppPhysicalPath.QueryStr(), &strFilePath); + APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; + + if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr()) && GetLastError() == ERROR_FILE_NOT_FOUND) + { + m_fAppOfflineFound = FALSE; + } + else + { + m_fAppOfflineFound = TRUE; + APP_OFFLINE_HTM *pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); + + DBG_ASSERT(pNewAppOfflineHtm != NULL); + + if (pNewAppOfflineHtm->Load()) + { + // + // loaded new app offline htm + // + pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); + + // + // send shutdown signal to the app + // + if (m_pProcessManager != NULL) + { + m_pProcessManager->SendShutdownSignal(); + } + } + } + + if (pOldAppOfflineHtm != NULL) + { + pOldAppOfflineHtm->DereferenceAppOfflineHtm(); + pOldAppOfflineHtm = NULL; + } +} \ No newline at end of file diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx new file mode 100644 index 0000000000..785182220c --- /dev/null +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation. 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::GetApplication( + _In_ IHttpContext* pContext, + _In_ LPCWSTR pszApplication, + _Out_ APPLICATION ** ppApplication +) +{ + HRESULT hr = S_OK; + APPLICATION *pApplication = NULL; + APPLICATION_KEY key; + BOOL fExclusiveLock = FALSE; + + + *ppApplication = NULL; + + hr = key.Initialize(pszApplication); + if (FAILED(hr)) + { + goto Finished; + } + + m_pApplicationHash->FindKey(&key, ppApplication); + + if (*ppApplication == NULL) + { + + pApplication = new APPLICATION(); + if (pApplication == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + AcquireSRWLockExclusive(&m_srwLock); + fExclusiveLock = TRUE; + m_pApplicationHash->FindKey(&key, ppApplication); + + if (*ppApplication != NULL) + { + // someone else created the application + delete pApplication; + pApplication = NULL; + goto Finished; + } + + hr = pApplication->Initialize(this, pszApplication, pContext->GetApplication()->GetApplicationPhysicalPath()); + if (FAILED(hr)) + { + goto Finished; + } + + hr = m_pApplicationHash->InsertRecord( pApplication ); + + if (FAILED(hr)) + { + goto Finished; + } + ReleaseSRWLockExclusive(&m_srwLock); + fExclusiveLock = FALSE; + + pApplication->StartMonitoringAppOffline(); + + *ppApplication = pApplication; + pApplication = NULL; + } + +Finished: + + if (fExclusiveLock == TRUE) + ReleaseSRWLockExclusive(&m_srwLock); + + if (FAILED(hr)) + { + if (pApplication != NULL) + { + pApplication->DereferenceApplication(); + pApplication = NULL; + } + } + + return hr; +} + +VOID +APPLICATION_MANAGER::RecycleOnFileChange( + APPLICATION_MANAGER*, +APPLICATION* +) +{ + g_pHttpServer->RecycleProcess(L"Asp.Net Core Module Recycle Process on File Change Notification"); +} + +HRESULT +APPLICATION_MANAGER::RecycleApplication( + _In_ LPCWSTR pszApplication +) +{ + HRESULT hr = S_OK; + APPLICATION_KEY key; + + hr = key.Initialize(pszApplication); + if (FAILED(hr)) + { + goto Finished; + } + AcquireSRWLockExclusive(&m_srwLock); + m_pApplicationHash->DeleteKey(&key); + ReleaseSRWLockExclusive(&m_srwLock); + +Finished: + + return hr; +} + +HRESULT +APPLICATION_MANAGER::Get502ErrorPage( + _Out_ HTTP_DATA_CHUNK** ppErrorPage +) +{ + HRESULT hr = S_OK; + BOOL fExclusiveLock = FALSE; + HTTP_DATA_CHUNK *pHttp502ErrorPage = NULL; + + DBG_ASSERT(ppErrorPage != NULL); + + //on-demand create the error page + if (m_pHttp502ErrorPage != NULL) + { + *ppErrorPage = m_pHttp502ErrorPage; + } + else + { + AcquireSRWLockExclusive(&m_srwLock); + fExclusiveLock = TRUE; + if (m_pHttp502ErrorPage != NULL) + { + *ppErrorPage = m_pHttp502ErrorPage; + } + else + { + size_t maxsize = 5000; + pHttp502ErrorPage = new HTTP_DATA_CHUNK(); + if (pHttp502ErrorPage == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + pHttp502ErrorPage->DataChunkType = HttpDataChunkFromMemory; + pHttp502ErrorPage->FromMemory.pBuffer = (PVOID)m_pstrErrorInfo; + + pHttp502ErrorPage->FromMemory.BufferLength = (ULONG)strnlen(m_pstrErrorInfo, maxsize); //(ULONG)(wcslen(m_pstrErrorInfo)); // *sizeof(WCHAR); + m_pHttp502ErrorPage = pHttp502ErrorPage; + *ppErrorPage = m_pHttp502ErrorPage; + } + } + +Finished: + if (fExclusiveLock) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + + if (FAILED(hr)) + { + if (pHttp502ErrorPage != NULL) + { + delete pHttp502ErrorPage; + } + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcore_msg.mc b/src/AspNetCore/Src/aspnetcore_msg.mc new file mode 100644 index 0000000000..6e009c8b50 --- /dev/null +++ b/src/AspNetCore/Src/aspnetcore_msg.mc @@ -0,0 +1,66 @@ +;/*++ +; +;Copyright (c) 2014 Microsoft Corporation +; +;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 +. + +; +;#endif // _ASPNETCORE_MODULE_MSG_H_ +; \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx new file mode 100644 index 0000000000..04f747894e --- /dev/null +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -0,0 +1,479 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() +{ + // + // the destructor will be called once IIS decides to recycle the module context (i.e., application) + // + if (!m_struApplication.IsEmpty()) + { + APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); + } +} + +HRESULT +ASPNETCORE_CONFIG::GetConfig( + _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(g_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( pHttpContext ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = pHttpApplication->GetModuleContextContainer()-> + SetModuleContext( pAspNetCoreConfig, g_pModuleId ); + if( FAILED( hr ) ) + { + if( hr == HRESULT_FROM_WIN32( ERROR_ALREADY_ASSIGNED ) ) + { + delete pAspNetCoreConfig; + + pAspNetCoreConfig = (ASPNETCORE_CONFIG*) pHttpApplication-> + GetModuleContextContainer()-> + GetModuleContext( g_pModuleId ); + + _ASSERT( pAspNetCoreConfig != NULL ); + + hr = S_OK; + } + else + { + goto Finished; + } + } + else + { + hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetAppConfigPath()); + if (FAILED(hr)) + { + goto Finished; + } + } + + *ppAspNetCoreConfig = pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + +Finished: + + if( pAspNetCoreConfig != NULL ) + { + delete pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + } + + return hr; +} + +VOID ReverseMultisz( MULTISZ * pmszInput, + LPCWSTR pszStr, + MULTISZ * pmszOutput ) +{ + if(pszStr == NULL) return; + + ReverseMultisz( pmszInput, pmszInput->Next( pszStr ), pmszOutput ); + + pmszOutput->Append( pszStr ); +} + +HRESULT +ASPNETCORE_CONFIG::Populate( + IHttpContext *pHttpContext +) +{ + HRESULT hr = S_OK; + STACK_STRU ( strSiteConfigPath, 256); + STRU strEnvName; + STRU strEnvValue; + STRU strFullEnvVar; + IAppHostAdminManager *pAdminManager = NULL; + IAppHostElement *pAspNetCoreElement = NULL; + IAppHostElement *pEnvVarList = NULL; + IAppHostElementCollection *pEnvVarCollection = NULL; + IAppHostElement *pEnvVar = NULL; + //IAppHostElement *pRecycleOnFileChangeFileList = NULL; + //IAppHostElementCollection *pRecycleOnFileChangeFileCollection = NULL; + //IAppHostElement *pRecycleOnFileChangeFile = NULL; + ULONGLONG ullRawTimeSpan = 0; + ENUM_INDEX index; + STRU strExpandedEnvValue; + MULTISZ mszEnvironment; + MULTISZ mszEnvironmentListReverse; + MULTISZ mszEnvNames; + LPWSTR pszEnvName; + LPCWSTR pcszEnvName; + LPCWSTR pszEnvString; + STRU strFilePath; + + pAdminManager = g_pHttpServer->GetAdminManager(); + + hr = strSiteConfigPath.Copy( pHttpContext->GetApplication()->GetAppConfigPath() ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = pAdminManager->GetAdminSection( CS_ASPNETCORE_SECTION, + strSiteConfigPath.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_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; + } + + hr = GetElementStringProperty( pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME, + &strEnvName); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = GetElementStringProperty( pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE, + &strEnvValue); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = strFullEnvVar.Append(strEnvName); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = strFullEnvVar.Append(L"="); + if( FAILED( hr ) ) + { + goto Finished; + } + + pszEnvName = strFullEnvVar.QueryStr(); + while( pszEnvName != NULL && *pszEnvName != '\0') + { + *pszEnvName = towupper( *pszEnvName ); + pszEnvName++; + } + + if( !mszEnvNames.FindString( strFullEnvVar ) ) + { + if( !mszEnvNames.Append( strFullEnvVar ) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + hr = STRU::ExpandEnvironmentVariables( strEnvValue.QueryStr(), &strExpandedEnvValue ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = strFullEnvVar.Append(strExpandedEnvValue); + if( FAILED( hr ) ) + { + goto Finished; + } + + if( !mszEnvironment.Append(strFullEnvVar) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + strExpandedEnvValue.Reset(); + strFullEnvVar.Reset(); + + pEnvVar->Release(); + pEnvVar = NULL; + } + + // basically the following logic is to select + + ReverseMultisz( &mszEnvironment, + mszEnvironment.First(), + &mszEnvironmentListReverse ); + + pcszEnvName = mszEnvNames.First(); + while(pcszEnvName != NULL) + { + pszEnvString = mszEnvironmentListReverse.First(); + while( pszEnvString != NULL ) + { + if(wcsstr(pszEnvString, pcszEnvName) != NULL) + { + if(!m_mszEnvironment.Append(pszEnvString)) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + break; + } + pszEnvString = mszEnvironmentListReverse.Next(pszEnvString); + } + pcszEnvName = mszEnvNames.Next(pcszEnvName); + } + // + // let's disable this feature for now + // + // get all files listed in recycleOnFileChange + /* + hr = GetElementChildByName( pAspNetCoreElement, + CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE, + &pRecycleOnFileChangeFileList ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = pRecycleOnFileChangeFileList->get_Collection( &pRecycleOnFileChangeFileCollection ); + if( FAILED( hr ) ) + { + goto Finished; + } + + for( hr = FindFirstElement( pRecycleOnFileChangeFileCollection, &index, &pRecycleOnFileChangeFile ) ; + SUCCEEDED( hr ) ; + hr = FindNextElement( pRecycleOnFileChangeFileCollection, &index, &pRecycleOnFileChangeFile ) ) + { + if( hr == S_FALSE ) + { + hr = S_OK; + break; + } + + hr = GetElementStringProperty( pRecycleOnFileChangeFile, + CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE_PATH, + &strFilePath); + if( FAILED( hr ) ) + { + goto Finished; + } + + if(!m_mszRecycleOnFileChangeFiles.Append( strFilePath )) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + strFilePath.Reset(); + pRecycleOnFileChangeFile->Release(); + pRecycleOnFileChangeFile = 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( pRecycleOnFileChangeFileCollection != NULL ) + { + pRecycleOnFileChangeFileCollection->Release(); + pRecycleOnFileChangeFileCollection = NULL; + } + + if( pRecycleOnFileChangeFileList != NULL ) + { + pRecycleOnFileChangeFileList->Release(); + pRecycleOnFileChangeFileList = NULL; + } + + if( pRecycleOnFileChangeFile != NULL ) + { + pRecycleOnFileChangeFile->Release(); + pRecycleOnFileChangeFile = NULL; + }*/ + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreutils.cxx b/src/AspNetCore/Src/aspnetcoreutils.cxx new file mode 100644 index 0000000000..5aa00994a6 --- /dev/null +++ b/src/AspNetCore/Src/aspnetcoreutils.cxx @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +// +// ReplacePlaceHolderWithValue replaces a placeholder found in +// pszStr with dwValue. +// If replace is successful, pfReplaced is TRUE else FALSE. +// + +HRESULT +ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + _Inout_ LPWSTR pszStr, + _In_ LPWSTR pszPlaceholder, + _In_ DWORD cchPlaceholder, + _In_ DWORD dwValue, + _In_ DWORD dwNumDigitsInValue, + _Out_ BOOL *pfReplaced +) +{ + HRESULT hr = S_OK; + LPWSTR pszPortPlaceHolder = NULL; + + DBG_ASSERT( pszStr != NULL ); + DBG_ASSERT( pszPlaceholder != NULL ); + DBG_ASSERT( pfReplaced != NULL ); + + *pfReplaced = FALSE; + + if((pszPortPlaceHolder = wcsstr(pszStr, pszPlaceholder)) != NULL) + { + if( swprintf_s( pszPortPlaceHolder, + cchPlaceholder, + L"%u", + dwValue ) == -1 ) + { + hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ); + goto Finished; + } + + if( wmemcpy_s( pszPortPlaceHolder + dwNumDigitsInValue, + cchPlaceholder, + L" ", + cchPlaceholder - dwNumDigitsInValue ) != 0 ) + { + hr = HRESULT_FROM_WIN32( EINVAL ); + goto Finished; + } + + *pfReplaced = TRUE; + } +Finished: + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp new file mode 100644 index 0000000000..f34057dbb5 --- /dev/null +++ b/src/AspNetCore/Src/dllmain.cpp @@ -0,0 +1,259 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" +#include + +//DECLARE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); + +HTTP_MODULE_ID g_pModuleId = NULL; +IHttpServer * g_pHttpServer = NULL; +BOOL g_fAsyncDisconnectAvailable = FALSE; +BOOL g_fWinHttpNonBlockingCallbackAvailable = FALSE; +PCWSTR g_pszModuleName = NULL; +HINSTANCE g_hModule; +HINSTANCE g_hWinHttpModule; +BOOL g_fWebSocketSupported = FALSE; + +DWORD g_dwTlsIndex = TLS_OUT_OF_INDEXES; +BOOL g_fEnableReferenceCountTracing = FALSE; +DWORD g_dwAspNetCoreDebugFlags = 0; +BOOL g_fNsiApiNotSupported = FALSE; +DWORD g_dwActiveServerProcesses = 0; +DWORD g_OptionalWinHttpFlags = 0; //specify additional WinHTTP options when using WinHttpOpenRequest API. + +DWORD g_dwDebugFlags = 0; +PCSTR g_szDebugLabel = "ASPNET_CORE_MODULE"; + +BOOL WINAPI DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hModule = hModule; + DisableThreadLibraryCalls(hModule); + break; + default: + break; + } + + return TRUE; +} + +VOID +LoadGlobalConfiguration( +VOID +) +{ + HKEY hKey; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\IIS Extensions\\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; + } +} + +HRESULT +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; + CProxyModuleFactory * pFactory = NULL; + + CREATE_DEBUG_PRINT_OBJECT; + + LoadGlobalConfiguration(); + + // + // 7.0 is 0,7 + // + if (dwServerVersion > MAKELONG(0, 7)) + { + g_fAsyncDisconnectAvailable = TRUE; + } + + // + // 8.0 is 0,8 + // + if (dwServerVersion >= MAKELONG(0, 8)) + { + // IISOOB:36641 Enable back WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS for Win8. + // g_fWinHttpNonBlockingCallbackAvailable = TRUE; + g_fWebSocketSupported = TRUE; + } + + hr = WINHTTP_HELPER::StaticInitialize(); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND)) + { + g_fWebSocketSupported = FALSE; + } + else + { + goto Finished; + } + } + + g_pModuleId = pModuleInfo->GetId(); + g_pszModuleName = pModuleInfo->GetName(); + g_pHttpServer = pHttpServer; + + // + // WinHTTP does not create enough threads, ask it to create more. + // Starting in Windows 7, this setting is ignored because WinHTTP + // uses a thread pool. + // + SYSTEM_INFO si; + GetSystemInfo(&si); + DWORD dwThreadCount = (si.dwNumberOfProcessors * 3 + 1) / 2; + WinHttpSetOption(NULL, + WINHTTP_OPTION_WORKER_THREAD_COUNT, + &dwThreadCount, + sizeof(dwThreadCount)); + + // + // Create the factory before any static initialization. + // The CProxyModuleFactory::Terminate method will clean any + // static object initialized. + // + pFactory = new CProxyModuleFactory; + if (pFactory == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pModuleInfo->SetRequestNotifications( + pFactory, + RQ_EXECUTE_REQUEST_HANDLER, + 0); + if (FAILED(hr)) + { + goto Finished; + } + + pFactory = NULL; + g_pResponseHeaderHash = new RESPONSE_HEADER_HASH; + if (g_pResponseHeaderHash == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = g_pResponseHeaderHash->Initialize(); + if (FAILED(hr)) + { + goto Finished; + } + + hr = ALLOC_CACHE_HANDLER::StaticInitialize(); + if (FAILED(hr)) + { + 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 (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..5298a86dbe --- /dev/null +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -0,0 +1,441 @@ +// Copyright (c) Microsoft Corporation. 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) +{ + InitializeCriticalSection(&this->m_csSyncRoot); +} + +FILE_WATCHER::~FILE_WATCHER() +{ + DeleteCriticalSection(&this->m_csSyncRoot); +} + +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 fRet = FALSE; + DWORD cbCompletion = 0; + OVERLAPPED * pOverlapped = NULL; + DWORD dwErrorStatus; + ULONG_PTR completionKey; + + pFileMonitor = (FILE_WATCHER*)pvArg; + while (TRUE) + { + fRet = GetQueuedCompletionStatus( + pFileMonitor->m_hCompletionPort, + &cbCompletion, + &completionKey, + &pOverlapped, + INFINITE); + + dwErrorStatus = fRet ? ERROR_SUCCESS : GetLastError(); + + if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) + { + continue; + } + + if (pOverlapped != NULL) + { + FileWatcherCompletionRoutine( + dwErrorStatus, + cbCompletion, + pOverlapped); + } + pOverlapped = NULL; + cbCompletion = 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); + + DBG_ASSERT(pMonitorEntry != NULL); + pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, + cbCompletion); +} + + +FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : + _pFileMonitor(pFileMonitor), + _hDirectory(INVALID_HANDLE_VALUE), + _hImpersonationToken(NULL), + _pApplication(NULL), + _lStopMonitorCalled(0) +{ + _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; + } +} + + +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; + + // 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) + { + hr = S_OK; + goto Finished; + } + + if (cbCompletion == 0) + { + // + // There could be a FCN overflow + // Let assume the file got changed instead of checking files + // Othersie we have to cache the file info + // + + fFileChanged = TRUE; + hr = HRESULT_FROM_WIN32(dwCompletionStatus); + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); + _ASSERT(pNotificationInfo != NULL); + + while (pNotificationInfo != NULL) + { + // + // check whether the monitored file got changed + // + if (wcscmp(pNotificationInfo->FileName, _strFileName.QueryStr()) == 0) + { + fFileChanged = TRUE; + break; + } + // + // Advance to next notification + // + if (pNotificationInfo->NextEntryOffset == 0) + { + pNotificationInfo = NULL; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*) + ((PBYTE)pNotificationInfo + + pNotificationInfo->NextEntryOffset); + } + } + + RtlZeroMemory(_buffDirectoryChanges.QueryPtr(), _buffDirectoryChanges.QuerySize()); + } + // + //continue monitoring + // + StopMonitor(); + + if (fFileChanged) + { + // + // so far we only monitoring app_offline + // + _pApplication->UpdateAppOfflineFileHandle(); + } + + hr = Monitor(); + +Finished: + return hr; +} + +HRESULT +FILE_WATCHER_ENTRY::Monitor(VOID) +{ + HRESULT hr = S_OK; + BOOL fRet = FALSE; + DWORD cbRead; + + AcquireSRWLockExclusive(&_srwLock); + + ZeroMemory(&_overlapped, sizeof(_overlapped)); + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + _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; + } + + // + // Resize change buffer to something "reasonable" + // + fRet = _buffDirectoryChanges.Resize(4096); + if (!fRet) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + + fRet = ReadDirectoryChangesW(_hDirectory, + _buffDirectoryChanges.QueryPtr(), + _buffDirectoryChanges.QuerySize(), + FALSE, // watch sub dirs. set to False now as only monitoring app_offline + FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS & ~FILE_NOTIFY_CHANGE_ATTRIBUTES, + &cbRead, + &_overlapped, + NULL); + + if (!fRet) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + InterlockedExchange(&_lStopMonitorCalled, 0); + +Finished: + + 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* pApplication, + _In_ HANDLE hImpersonationToken +) +{ + HRESULT hr = S_OK; + BOOL fRet = FALSE; + + if (pszDirectoryToMonitor == NULL || + pszFileNameToMonitor == NULL || + pApplication == NULL) + { + _ASSERT(FALSE); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + // + //remember the application + // + _pApplication = pApplication; + + if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) + { + goto Finished; + } + + if (FAILED(hr = _strDirectoryName.Copy(pszDirectoryToMonitor))) + { + 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; + } + } + // + // Start monitoring + // + hr = Monitor(); + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/forwarderconnection.cxx b/src/AspNetCore/Src/forwarderconnection.cxx new file mode 100644 index 0000000000..9e01b0a065 --- /dev/null +++ b/src/AspNetCore/Src/forwarderconnection.cxx @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. 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(FORWARDING_HANDLER::sm_hSession, + L"127.0.0.1", + (USHORT) dwPort, + 0); + if (m_hConnection == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx new file mode 100644 index 0000000000..6a6f44f685 --- /dev/null +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -0,0 +1,2970 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#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 STR_ANCM_CHILDREQUEST "ANCM_WasCreateProcessFailure" + +HINTERNET FORWARDING_HANDLER::sm_hSession = NULL; +STRU FORWARDING_HANDLER::sm_strErrorFormat; +HANDLE FORWARDING_HANDLER::sm_hEventLog = NULL; +ALLOC_CACHE_HANDLER * FORWARDING_HANDLER::sm_pAlloc = NULL; +TRACE_LOG * FORWARDING_HANDLER::sm_pTraceLog = NULL; +PROTOCOL_CONFIG FORWARDING_HANDLER::sm_ProtocolConfig; + +FORWARDING_HANDLER::FORWARDING_HANDLER( + __in IHttpContext * pW3Context +) : m_Signature ( FORWARDING_HANDLER_SIGNATURE ), + m_cRefs ( 1 ), + m_pW3Context ( pW3Context ), + m_pChildRequestContext ( NULL ), + m_hRequest ( NULL ), + m_fHandleClosedDueToClient( FALSE ), + m_fResponseHeadersReceivedAndSet( FALSE ), + m_fDoReverseRewriteHeaders ( FALSE ), + m_msStartTime ( 0 ), + m_BytesToReceive ( 0 ), + m_BytesToSend ( 0 ), + m_pEntityBuffer ( NULL ), + m_cchLastSend ( 0 ), + m_cEntityBuffers ( 0 ), + m_cBytesBuffered ( 0 ), + m_cMinBufferLimit ( 0 ), + m_pszOriginalHostHeader ( NULL ), + m_RequestStatus ( FORWARDER_START ), + m_pDisconnect ( NULL ), + m_pszHeaders ( NULL ), + m_cchHeaders ( 0 ), + m_fWebSocketEnabled ( FALSE ), + m_cContentLength ( 0 ), + m_pWebSocket ( NULL ), + m_pApplication( NULL ), + m_pAppOfflineHtm( NULL ) +{ + InitializeSRWLock(&m_RequestLock); +} + +FORWARDING_HANDLER::~FORWARDING_HANDLER( + VOID +) +{ + // + // Destructor has started. + // + m_Signature = FORWARDING_HANDLER_SIGNATURE_FREE; + + // + // 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); + + FreeResponseBuffers(); + + if (m_pWebSocket) + { + m_pWebSocket->Terminate(); + m_pWebSocket = NULL; + } + + if (m_pChildRequestContext != NULL) + { + m_pChildRequestContext->ReleaseClonedContext(); + m_pChildRequestContext = NULL; + } + + // + // The m_pDisconnect must have happened by now the m_pServer + // is the only cleanup left. + // + + RemoveRequest(); + + if (m_hRequest != NULL) + { + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + } + + if(m_pApplication != NULL) + { + m_pApplication->DereferenceApplication(); + m_pApplication = NULL; + } + + if( m_pAppOfflineHtm != NULL ) + { + m_pAppOfflineHtm->DereferenceAppOfflineHtm(); + m_pAppOfflineHtm = NULL; + } + + m_pW3Context = NULL; +} + +// static +void * FORWARDING_HANDLER::operator new(size_t) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc == NULL) + { + return NULL; + } + return sm_pAlloc->Alloc(); +} + +// static +void FORWARDING_HANDLER::operator delete(void * pMemory) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc != NULL) + { + sm_pAlloc->Free(pMemory); + } +} + +VOID +FORWARDING_HANDLER::ReferenceForwardingHandler( + VOID +) const +{ + LONG cRefs = InterlockedIncrement(&m_cRefs); + if (sm_pTraceLog != NULL) + { + WriteRefTraceLog(sm_pTraceLog, + cRefs, + this); + } +} + +VOID +FORWARDING_HANDLER::DereferenceForwardingHandler( + VOID +) const +{ + DBG_ASSERT(m_cRefs != 0 ); + + LONG cRefs = 0; + if ( (cRefs = InterlockedDecrement(&m_cRefs) ) == 0) + { + delete this; + } + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLog(sm_pTraceLog, + cRefs, + this); + } +} + +HRESULT +FORWARDING_HANDLER::SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD +) +{ + HRESULT hr; + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + 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 = g_pResponseHeaderHash->GetIndex(strHeaderName.QueryStr()); + if (headerIndex == UNKNOWN_INDEX) + { + if (_strnicmp(strHeaderName.QueryStr(), "Sec-WebSocket", 13) != 0 ) + { + // + // Perf Opt: Avoid setting websocket headers, since IIS websocket module + // will anyways set these later in the pipeline. + // + + 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; +} + +HRESULT +FORWARDING_HANDLER::GetHeaders( + const PROTOCOL_CONFIG * pProtocol, + PCWSTR pszDestination, + PCWSTR * ppszHeaders, + DWORD * pcchHeaders, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess +) +{ + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + HRESULT hr = S_OK; + PCSTR pszCurrentHeader; + USHORT cchCurrentHeader; + PCSTR pszFinalHeader; + DWORD cchFinalHeader; + STACK_STRA( strTemp, 64); + HTTP_REQUEST_HEADERS *pHeaders; + PCSTR ppHeadersToBeRemoved; + MULTISZA mszMsAspNetCoreHeaders; + + // + // Update host header if so configured + // + if (!pProtocol->QueryPreserveHostHeader()) + { + STACK_STRA( straTemp, 256 ); + if (FAILED(hr = straTemp.CopyW(pszDestination)) || + FAILED(hr = pRequest->SetHeader(HttpHeaderHost, + straTemp.QueryStr(), + static_cast(straTemp.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( pAspNetCoreConfig->QueryForwardWindowsAuthToken() && + (_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 + { + 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, + const STRU& strDestination, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + 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; + } + + hr = GetHeaders(pProtocol, + strDestination.QueryStr(), + &m_pszHeaders, + &m_cchHeaders, + pAspNetCoreConfig, + pServerProcess); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + return hr; +} + +REQUEST_NOTIFICATION_STATUS +FORWARDING_HANDLER::OnExecuteRequestHandler( +VOID +) +{ + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; + HRESULT hr = S_OK; + bool fRequestLocked = FALSE; + ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; + FORWARDER_CONNECTION *pConnection = NULL; + STACK_STRU( strDestination, 32); + STACK_STRU( strUrl, 2048); + STACK_STRU( struEscapedUrl, 2048); + STACK_STRU( strDescription, 128); + HINTERNET hConnect = NULL; + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + IHttpResponse *pResponse = m_pW3Context->GetResponse(); + PROTOCOL_CONFIG *pProtocol = &sm_ProtocolConfig; + APPLICATION_MANAGER *pApplicationManager = NULL; + SERVER_PROCESS *pServerProcess = NULL; + USHORT cchHostName = 0; + BOOL fSecure = FALSE; + BOOL fProcessStartFailure = FALSE; + HTTP_DATA_CHUNK *pDataChunk = NULL; + + DBG_ASSERT(m_RequestStatus == FORWARDER_START); + + // + // Take a reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + + m_pszOriginalHostHeader = pRequest->GetHeader(HttpHeaderHost, &cchHostName); + + // read per site aspNetCore configuration. + hr = ASPNETCORE_CONFIG::GetConfig(m_pW3Context, &pAspNetCoreConfig); + if (FAILED(hr)) + { + // configuration error. + goto Failure; + } + + // override Protocol related config from aspNetCore config + pProtocol->OverrideConfig(pAspNetCoreConfig); + + // + // parse original url + // + if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + &fSecure, + &strDestination, + &strUrl))) + { + goto Failure; + } + + if (FAILED(hr = PATH::EscapeAbsPath(pRequest, &struEscapedUrl))) + { + goto Failure; + } + + m_fDoReverseRewriteHeaders = pProtocol->QueryReverseRewriteHeaders(); + + IHttpConnection * pClientConnection = m_pW3Context->GetConnection(); + if (pClientConnection == NULL || + !pClientConnection->IsConnected()) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + + m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); + + // + // Find the application that is supposed to service this request. + // + + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = pApplicationManager->GetApplication( m_pW3Context, + m_pW3Context->GetApplication()->GetAppConfigPath(), + &m_pApplication ); + if (FAILED(hr)) + { + goto Failure; + } + + m_pAppOfflineHtm = m_pApplication->QueryAppOfflineHtm(); + if (m_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->ReferenceAppOfflineHtm(); + } + + if (m_pApplication->AppOfflineFound() && m_pAppOfflineHtm != NULL) + { + + HTTP_DATA_CHUNK DataChunk; + + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = (PVOID)m_pAppOfflineHtm->m_Contents.QueryStr(); + DataChunk.FromMemory.BufferLength = m_pAppOfflineHtm->m_Contents.QueryCB(); + + if (FAILED(hr = pResponse->WriteEntityChunkByReference(&DataChunk))) + { + goto Finished; + } + pResponse->SetStatus(503, "Service Unavailable", 0, hr); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); // no need to check return hresult + + goto Finished; + } + + hr = m_pApplication->GetProcess( m_pW3Context, + pAspNetCoreConfig, + &pServerProcess); + if (FAILED(hr)) + { + fProcessStartFailure = TRUE; + goto Failure; + } + + if (pServerProcess == NULL) + { + 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(); + + // + // 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, + strDestination, + pAspNetCoreConfig, + pServerProcess); + + if (FAILED(hr)) + { + goto Failure; + } + + // + // Register for connection disconnect notification with http.sys. + // N.B. This feature is currently disabled due to synchronization conditions. + // + + // disabling this disconnect notification as it causes synchronization/AV issue + // will re-enable it in the future after investigation + + //if (g_fAsyncDisconnectAvailable) + //{ + // m_pDisconnect = static_cast( + // pClientConnection->GetModuleContextContainer()-> + // GetConnectionModuleContext(g_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, + // g_pModuleId); + // DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); + // if (FAILED(hr)) + // { + // goto Failure; + // } + // } + + // // + // // Issue: There is a window of opportunity to miss on the disconnect + // // notification if it happens before the SetHandler() call is made. + // // It is suboptimal for performance, but should functionally be OK. + // // + + // m_pDisconnect->SetHandler(this); + //} + + // + // Read lock on the WinHTTP handle to protect from server closing + // the handle while it is in use. + // + + AcquireSRWLockShared(&m_RequestLock); + fRequestLocked = TRUE; + + 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; + + // + // 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); + + // + // WinHttpSendRequest can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + 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"); + DereferenceForwardingHandler(); + 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: + + // + // Reset status for consistency. + // + m_RequestStatus = FORWARDER_DONE; + + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + // + // Finish the request on failure. + // + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + + if (hr == HRESULT_FROM_WIN32(WSAECONNRESET)) + { + pResponse->SetStatus(400, "Bad Request", 0, hr); + goto Finished; + } + else if (fProcessStartFailure && !pAspNetCoreConfig->QueryDisableStartUpErrorPage()) + { + PCSTR pszANCMHeader; + DWORD cchANCMHeader; + BOOL fCompletionExpected = FALSE; + + if (FAILED(m_pW3Context->GetServerVariable(STR_ANCM_CHILDREQUEST, + &pszANCMHeader, + &cchANCMHeader))) // first time failure + { + if (SUCCEEDED(hr = m_pW3Context->CloneContext( + CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, + &m_pChildRequestContext + )) && + SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( + STR_ANCM_CHILDREQUEST, + L"1")) && + SUCCEEDED(hr = m_pW3Context->ExecuteRequest( + TRUE, // fAsync + m_pChildRequestContext, + EXECUTE_FLAG_DISABLE_CUSTOM_ERROR, // by pass Custom Error module + NULL, // pHttpUser + &fCompletionExpected))) + { + if (!fCompletionExpected) + { + retVal = RQ_NOTIFICATION_CONTINUE; + } + else + { + retVal = RQ_NOTIFICATION_PENDING; + } + goto Finished; + } + // + // fail to create child request, fall back to default 502 error + // + } + else + { + if (SUCCEEDED(pApplicationManager->Get502ErrorPage(&pDataChunk))) + { + if (FAILED(hr = pResponse->WriteEntityChunkByReference(pDataChunk))) + { + goto Finished; + } + pResponse->SetStatus(502, "Bad Gateway", 5, hr); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); + goto Finished; + } + } + } + // + // default error behavior + // + 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); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + + strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + +Finished: + + if (pConnection != NULL) + { + pConnection->DereferenceForwarderConnection(); + pConnection = NULL; + } + + if( pServerProcess != NULL ) + { + pServerProcess->DereferenceServerProcess(); + pServerProcess = NULL; + } + + if (fRequestLocked) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + if (retVal != RQ_NOTIFICATION_PENDING) + { + // + // Remove request so that load-balancing algorithms like minCurrentRequests/minAverageResponseTime + // get the correct time when we received the last byte of response, rather than when we received + // ack from client about last byte of response - which could be much later. + // + RemoveRequest(); + } + + DereferenceForwardingHandler(); + // + // Do not use this object after dereferencing it, it may be gone. + // + + return retVal; +} + +VOID +FORWARDING_HANDLER::RemoveRequest() +{ + if (m_pDisconnect != NULL) + { + m_pDisconnect->ResetHandler(); + m_pDisconnect = NULL; + } +} + +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_CONTINUE; + BOOL fLocked = FALSE; + bool fClientError = FALSE; + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + + if ( sm_pTraceLog != NULL ) + { + WriteRefTraceLogEx( sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnAsyncCompletion Enter", + reinterpret_cast(static_cast(cbCompletion)), + reinterpret_cast(static_cast(hrCompletionStatus)) ); + } + + // + // Take a reference so that object does not go away as a result of + // async completion. + // + // Read lock on the WinHTTP handle to protect from server closing + // the handle while it is in use. + // + ReferenceForwardingHandler(); + + // + // OnAsyncCompletion can be called on a Winhttp io completion thread. + // Hence we need to check the TLS before we acquire the shared lock. + // + + 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) + { + if (m_RequestStatus == FORWARDER_DONE) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + goto Finished; + } + + fClientError = m_fHandleClosedDueToClient; + goto Failure; + } + else if (m_RequestStatus == 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 + // + + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + } + + // + // Begins normal completion handling. There is already a shared acquired + // for protecting the WinHTTP request handle from being closed. + // + switch (m_RequestStatus) + { + 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: + + // + // Reset status for consistency. + // + m_RequestStatus = FORWARDER_DONE; + + // + // 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); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + (VOID) strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + + if( hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + pResponse->ResetConnection(); + goto Finished; + } + } + + // + // Finish the request on failure. + // + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + +Finished: + + if (fLocked) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + if (retVal != RQ_NOTIFICATION_PENDING) + { + // + // Remove request so that load-balancing algorithms like minCurrentRequests/minAverageResponseTime + // get the correct time when we received the last byte of response, rather than when we received + // ack from client about last byte of response - which could be much later. + // + RemoveRequest(); + } + + DereferenceForwardingHandler(); + + // + // Do not use this object after dereferencing it, it may be gone. + // + + return retVal; +} + +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" + + // + // 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 Failure; + } + } + else + { + m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; + + // + // WinHttpReceiveResponse can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + if (!WinHttpReceiveResponse(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DereferenceForwardingHandler(); + 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; + + // + // WinHttpWriteData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + if (!WinHttpWriteData(m_hRequest, + m_pEntityBuffer + cbOffset, + cbCompletion, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DereferenceForwardingHandler(); + 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. + // + // WinHttpQueryDataAvailable can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + if (!WinHttpQueryDataAvailable(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DereferenceForwardingHandler(); + 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; + } + } + + // + // WinHttpReadData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + if (!WinHttpReadData(m_hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DereferenceForwardingHandler(); + goto Failure; + } + } + +Failure: + + return hr; +} + +VOID +FORWARDING_HANDLER::OnWinHttpCompletionInternal( +HINTERNET hRequest, +DWORD dwInternetStatus, +LPVOID lpvStatusInformation, +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 fIsCompletionThread = FALSE; + bool fClientError = FALSE; + bool fAnotherCompletionExpected = FALSE; + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + BOOL fDerefForwardingHandler = TRUE; + + UNREFERENCED_PARAMETER(dwStatusInformationLength); + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal Enter", + reinterpret_cast(static_cast(dwInternetStatus)), + NULL); + } + + // + // If the request is upgraded to websocket, route the completion + // to the websocket handler. We don't need to take the request lock, + // since for websocket request, the parent request handle is already + // closed. + // + + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + 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; + } + + fDerefForwardingHandler = FALSE; + fAnotherCompletionExpected = TRUE; + + goto Finished; + } + + // + // 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. + // + // Take a reference so that object does not go away as a result of + // async completion - release one reference when async operation is + // initiated, two references if async operation is not initiated + // + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + fIsCompletionThread = TRUE; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + } + + if (m_hRequest == NULL) + { + fClientError = m_fHandleClosedDueToClient; + goto Failure; + } + + if (!m_pWebSocket) + { + DBG_ASSERT(hRequest == m_hRequest); + } + + 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. + // + fDerefForwardingHandler = FALSE; + 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; + fDerefForwardingHandler = FALSE; + fAnotherCompletionExpected = TRUE; + 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; + + 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); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + } + +Finished: + if (fIsCompletionThread) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + if (fDerefForwardingHandler) + { + DereferenceForwardingHandler(); + } + // + // Do not use this object after dereferencing it, it may be gone. + // + + // + // 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. + // + + + if (!fAnotherCompletionExpected ) + { + // + // Since we use TLS to guard WinHttp operation, call PostCompletion instead of + // IndicateCompletion to allow cleaning up the TLS before thread reuse. + // + m_pW3Context->PostCompletion(0); + // + // No code executed after posting the completion. + // + } +} + +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; + + // + // WinHttpReceiveResponse can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + if (!WinHttpReceiveResponse(hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DereferenceForwardingHandler(); + 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; +} + +// 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 Failure; + } + + hr = sm_pAlloc->Initialize(sizeof(FORWARDING_HANDLER), + 64); // nThreshold + if (FAILED(hr)) + { + goto Failure; + } + + // + // Open the session handle, specify random user-agent that will be + // overwritten by the client + // + sm_hSession = WinHttpOpen(L"", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + WINHTTP_FLAG_ASYNC); + if (sm_hSession == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + // + // 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(sm_hSession, + FORWARDING_HANDLER::OnWinHttpCompletion, + ( WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST ), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + // + // Make sure we see the redirects (rather than winhttp doing it + // automatically) + // + DWORD dwRedirectOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; + if (!WinHttpSetOption(sm_hSession, + WINHTTP_OPTION_REDIRECT_POLICY, + &dwRedirectOption, + sizeof(dwRedirectOption))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + // Initialize Application Manager + APPLICATION_MANAGER *pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if(pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = pApplicationManager->Initialize(); + if(FAILED(hr)) + { + goto Failure; + } + + // Initialize PROTOCOL_CONFIG + sm_ProtocolConfig.Initialize(); + + if (FAILED(hr = sm_strErrorFormat.Resize(256))) + { + goto Failure; + } + + if (LoadString(g_hModule, + IDS_INVALID_PROPERTY, + sm_strErrorFormat.QueryStr(), + sm_strErrorFormat.QuerySizeCCH()) == 0) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + sm_strErrorFormat.SyncWithBuffer(); + + // If RegisterEventSource failed, we cannot do any thing about it + // No need to check whether the returned handle is valid + + if (g_pHttpServer->IsCommandLineLaunch()) + { + sm_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); + } + else + { + sm_hEventLog = RegisterEventSource(NULL, ASPNETCORE_EVENT_PROVIDER); + } + + g_dwTlsIndex = TlsAlloc(); + if (g_dwTlsIndex == TLS_OUT_OF_INDEXES) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + if ( fEnableReferenceCountTracing ) + { + sm_pTraceLog = CreateRefTraceLog( 10000, 0 ); + } + + return S_OK; + +Failure: + + StaticTerminate(); + + return hr; +} + +// static +VOID +FORWARDING_HANDLER::StaticTerminate( + VOID +) +/*++ + +Routine Description: + + Global termination routine for FORWARDING_HANDLERs + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + // + // Delete all the statics + // + + APPLICATION_MANAGER::Cleanup(); + + // + // wait for all server processes to go away + // for a max of 10 seconds. + // + + DWORD tickCount = GetTickCount(); + + while( g_dwActiveServerProcesses > 0 ) + { + if( (GetTickCount() - tickCount) > 10000 ) + { + break; + } + Sleep(250); + } + + if (sm_hSession != NULL) + { + WinHttpCloseHandle(sm_hSession); + sm_hSession = NULL; + } + + if (sm_hEventLog != NULL) + { + DeregisterEventSource(sm_hEventLog); + sm_hEventLog = NULL; + } + + if (g_dwTlsIndex != TLS_OUT_OF_INDEXES) + { + DBG_REQUIRE(TlsFree(g_dwTlsIndex)); + g_dwTlsIndex = TLS_OUT_OF_INDEXES; + } + + sm_strErrorFormat.Reset(); + + if (sm_pTraceLog != NULL) + { + DestroyRefTraceLog(sm_pTraceLog); + sm_pTraceLog = NULL; + } + + if (sm_pAlloc != NULL) + { + delete sm_pAlloc; + sm_pAlloc = NULL; + } +} + +VOID +CopyMultiSzToOutput( + IGlobalRSCAQueryProvider * pProvider, + PCWSTR pszList, + DWORD * pcbData +) +{ + PBYTE pvData; + DWORD cbData = 0; + PCWSTR pszListCopy = pszList; + while (*pszList != L'\0') + { + cbData += (static_cast(wcslen(pszList)) + 1)*sizeof(WCHAR); + pszList += wcslen(pszList) + 1; + } + cbData += sizeof(WCHAR); + if (FAILED(pProvider->GetOutputBuffer(cbData, + &pvData))) + { + return; + } + memcpy(pvData, + pszListCopy, + cbData); + *pcbData = cbData; +} + +struct AFFINITY_LOOKUP_CONTEXT +{ + DWORD timeout; + PCWSTR pszServer; + BUFFER * pHostNames; + DWORD cbData; +}; + +struct CACHE_CONTEXT +{ + PCSTR pszHostName; + IGlobalRSCAQueryProvider *pProvider; + __field_bcount_part(cbBuffer, cbData) + PBYTE pvData; + DWORD cbData; + DWORD cbBuffer; +}; + +VOID +FORWARDING_HANDLER::TerminateRequest( + bool fClientInitiated +) +{ + AcquireSRWLockExclusive(&m_RequestLock); + + if (m_hRequest != NULL) + { + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + + m_fHandleClosedDueToClient = fClientInitiated; + } + + // + // If the request is a websocket request, initiate cleanup. + // + + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + } + + ReleaseSRWLockExclusive(&m_RequestLock); +} + +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; iCopy(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 +PATH::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 +PATH::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 +PATH::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 +PATH::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 +PATH::FindInMultiString( + PCWSTR pszMultiString, + PCWSTR pszStringToFind +) +{ + while (*pszMultiString != L'\0') + { + if (wcscmp(pszMultiString, pszStringToFind) == 0) + { + return TRUE; + } + pszMultiString += wcslen(pszMultiString) + 1; + } + + return FALSE; +} + +// static +bool +PATH::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 +PATH::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 +PATH::IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc +) +{ + HRESULT hr = S_OK; + STACK_STRU( strTempPath, MAX_PATH ); + + 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 +PATH::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; +} \ 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..a8425be8df --- /dev/null +++ b/src/AspNetCore/Src/precomp.hxx @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// System related headers +// +#define NTDDI_VERSION 0x06020000 +#define _WIN32_WINNT 0x0602 +#define _WINSOCKAPI_ +#include +#include +#include + +//#include +#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 + +#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module" +#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module" + +#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))) + +#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 "treehash.h" + +#include "dbgutil.h" +#include "ahutil.h" +#include "multisz.h" +#include "multisza.h" +#include "base64.h" +#include "sttimer.h" +#include +#include +#include +#include +#include + +#include "aspnetcoreutils.h" +#include "..\aspnetcore_msg.h" +#include "aspnetcoreconfig.h" +#include "serverprocess.h" +#include "processmanager.h" +#include "filewatcher.h" +#include "application.h" +#include "applicationmanager.h" +#include "resource.h" +#include "path.h" +#include "debugutil.h" +#include "protocolconfig.h" +#include "responseheaderhash.h" +#include "forwarderconnection.h" +#include "winhttphelper.h" +#include "websockethandler.h" +#include "forwardinghandler.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 BOOL g_fAsyncDisconnectAvailable; +extern BOOL g_fWinHttpNonBlockingCallbackAvailable; +extern PVOID g_pModuleId; +extern BOOL g_fWebSocketSupported; +extern BOOL g_fEnableReferenceCountTracing; +extern DWORD g_dwActiveServerProcesses; +extern DWORD g_OptionalWinHttpFlags; \ No newline at end of file diff --git a/src/AspNetCore/Src/processmanager.cxx b/src/AspNetCore/Src/processmanager.cxx new file mode 100644 index 0000000000..7a8b20aea8 --- /dev/null +++ b/src/AspNetCore/Src/processmanager.cxx @@ -0,0 +1,292 @@ +// Copyright (c) Microsoft Corporation. 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_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_ IHttpContext *context, + _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 **ppSelectedServerProcess = 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;iIsReady() ) + { + m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *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. + // + + 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 ) + { + m_ppServerProcessList[dwProcessIndex] = new SERVER_PROCESS(); + if( m_ppServerProcessList[dwProcessIndex] == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + this->ReferenceProcessManager(); + hr = m_ppServerProcessList[dwProcessIndex]->Initialize( + this, + pConfig->QueryProcessPath(), + pConfig->QueryArguments(), + pConfig->QueryStartupTimeLimitInMS(), + pConfig->QueryShutdownTimeLimitInMS(), + pConfig->QueryEnvironmentVariables(), + pConfig->QueryStdoutLogEnabled(), + pConfig->QueryStdoutLogFile() + ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = m_ppServerProcessList[dwProcessIndex]->StartProcess(context); + if( FAILED( hr ) ) + { + goto Finished; + } + } + + if( !m_ppServerProcessList[dwProcessIndex]->IsReady() ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + } + +Finished: + + if( FAILED(hr) ) + { + if(m_ppServerProcessList[dwProcessIndex] != NULL ) + { + m_ppServerProcessList[dwProcessIndex]->DereferenceServerProcess(); + m_ppServerProcessList[dwProcessIndex] = NULL; + } + } + + if( fSharedLock ) + { + ReleaseSRWLockShared( &m_srwLock ); + fSharedLock = FALSE; + } + + if( fExclusiveLock ) + { + ReleaseSRWLockExclusive( &m_srwLock ); + fExclusiveLock = FALSE; + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/protocolconfig.cxx b/src/AspNetCore/Src/protocolconfig.cxx new file mode 100644 index 0000000000..83e2648925 --- /dev/null +++ b/src/AspNetCore/Src/protocolconfig.cxx @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. 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/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx new file mode 100644 index 0000000000..1e59d18a89 --- /dev/null +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + + +__override +HRESULT +CProxyModuleFactory::GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator +) +{ + CProxyModule *pModule = new (pAllocator) CProxyModule(); + if (pModule == NULL) + { + return E_OUTOFMEMORY; + } + + *ppModule = pModule; + return S_OK; +} + +__override +VOID +CProxyModuleFactory::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(); + + if (g_pResponseHeaderHash != NULL) + { + g_pResponseHeaderHash->Clear(); + delete g_pResponseHeaderHash; + g_pResponseHeaderHash = NULL; + } + + ALLOC_CACHE_HANDLER::StaticTerminate(); + + delete this; +} + +CProxyModule::CProxyModule( +) : m_pHandler(NULL) +{ +} + +CProxyModule::~CProxyModule() +{ + if (m_pHandler != NULL) + { + m_pHandler->DereferenceForwardingHandler(); + m_pHandler = NULL; + } +} + +__override +REQUEST_NOTIFICATION_STATUS +CProxyModule::OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * +) +{ + m_pHandler = new FORWARDING_HANDLER(pHttpContext); + if (m_pHandler == NULL) + { + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY); + return RQ_NOTIFICATION_FINISH_REQUEST; + } + + return m_pHandler->OnExecuteRequestHandler(); +} + +__override +REQUEST_NOTIFICATION_STATUS +CProxyModule::OnAsyncCompletion( + IHttpContext *, + DWORD dwNotification, + BOOL fPostNotification, + IHttpEventProvider *, + IHttpCompletionInfo * pCompletionInfo +) +{ + UNREFERENCED_PARAMETER(dwNotification); + UNREFERENCED_PARAMETER(fPostNotification); + DBG_ASSERT(dwNotification == RQ_EXECUTE_REQUEST_HANDLER); + DBG_ASSERT(fPostNotification == FALSE); + + return m_pHandler->OnAsyncCompletion( + pCompletionInfo->GetCompletionBytes(), + pCompletionInfo->GetCompletionStatus()); +} \ No newline at end of file diff --git a/src/AspNetCore/Src/responseheaderhash.cxx b/src/AspNetCore/Src/responseheaderhash.cxx new file mode 100644 index 0000000000..161095042c --- /dev/null +++ b/src/AspNetCore/Src/responseheaderhash.cxx @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +RESPONSE_HEADER_HASH * g_pResponseHeaderHash = NULL; + +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/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx new file mode 100644 index 0000000000..4c0ca9b1b5 --- /dev/null +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -0,0 +1,1823 @@ +// Copyright (c) Microsoft Corporation. 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, + MULTISZ *pszEnvironment, + BOOL fStdoutLogEnabled, + STRU *pstruStdoutLogFile +) +{ + HRESULT hr = S_OK; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 }; + + m_pProcessManager = pProcessManager; + m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS; + m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS; + m_fStdoutLogEnabled = fStdoutLogEnabled; + + hr = m_ProcessPath.Copy(*pszProcessExePath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = m_struLogFile.Copy(*pstruStdoutLogFile); + if (FAILED(hr)) + { + goto Finished; + } + + hr = m_Arguments.Copy(*pszArguments); + if (FAILED(hr)) + { + goto Finished; + } + + hr = m_Environment.Copy(*pszEnvironment); + if (FAILED(hr)) + { + goto Finished; + } + + if (m_hJobObject == NULL) + { + m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES + NULL); // LPCTSTR lpName + if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) + { + m_hJobObject = NULL; + // ignore job object creation error. + } + + 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; + } + } + } + +Finished: + return hr; +} + + +HRESULT +SERVER_PROCESS::StartProcess( + IHttpContext *context +) +{ + HRESULT hr = S_OK; + PROCESS_INFORMATION processInformation = {0}; + STARTUPINFOW startupInfo = {0}; + WCHAR *pszCommandLine = NULL; + DWORD dwCommandLineLen = 0; + DWORD dwNumDigitsInPort = 0; + DWORD dwNumDigitsInDebugPort = 0; + LPWSTR pszCurrentEnvironment = NULL; + DWORD dwCurrentEnvSize = 0; + DWORD dwCreationFlags = 0; + BOOL fReady = FALSE; + DWORD dwTickCount = 0; + STACK_STRU( strAspNetCorePortEnvVar, 32); + STACK_STRU( strAspNetCoreDebugPortEnvVar, 32); + MULTISZ mszNewEnvironment; + MULTISZ mszEnvCopy; + DWORD cChildProcess = 0; + DWORD dwTimeDifference = 0; + STACK_STRU( strEventMsg, 256); + BOOL fDebugPortEnvSet = FALSE; + BOOL fReplacedEnv = FALSE; + BOOL fPortInUse = FALSE; + LPCWSTR pszRootApplicationPath = NULL; + BOOL fDebuggerAttachedToChildProcess = FALSE; + STRU strFullProcessPath; + STRU struApplicationId; + RPC_STATUS rpcStatus; + UUID logUuid; + PSTR pszLogUuid = NULL; + BOOL fRpcStringAllocd = FALSE; + STRU struGuidEnv; + STRU finalCommandLine; + BOOL fDonePrepareCommandLine = FALSE; + // + // process id of the process listening on port we randomly generated. + // + DWORD dwActualProcessId = 0; + WCHAR* pszPath = NULL; + WCHAR pszFullPath[_MAX_PATH]; + LPCWSTR apsz[1]; + + GetStartupInfoW(&startupInfo); + + // + // setup stdout and stderr handles to our stdout handle only if + // the handle is valid. + // + + SetupStdHandles(context, &startupInfo); + + // + // generate new guid for each process + // + + 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; + + hr = m_straGuid.Copy( pszLogUuid ); + if(FAILED(hr)) + { + goto Finished; + } + + // + // Generate random port that the new process will listen on. + // + + if(g_fNsiApiNotSupported) + { + m_dwPort = GenerateRandomPort(); + } + else + { + DWORD cRetry = 0; + do + { + // + // ignore dwActualProcessId because here we are + // determing whether the randomly generated port is + // in use by any other process. + // + + m_dwPort = GenerateRandomPort(); + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fPortInUse); + } while( fPortInUse && ++cRetry < MAX_RETRY ); + + if( cRetry > MAX_RETRY ) + { + hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); + goto Finished; + } + } + + dwNumDigitsInPort = GetNumberOfDigits( m_dwPort ); + hr = strAspNetCorePortEnvVar.SafeSnwprintf( L"%s=%u", ASPNETCORE_PORT_STR, m_dwPort ); + if( FAILED ( hr ) ) + { + goto Finished; + } + + // + // Generate random debug port that the new process will listen on. + // this will only be used if ASPNETCORE_DEBUG_PORT placeholder is found + // in the aspNetCore config. + // + + if(g_fNsiApiNotSupported) + { + while((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); + } + else + { + DWORD cRetry = 0; + do + { + while((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); + hr = CheckIfServerIsUp(m_dwDebugPort, &dwActualProcessId, &fPortInUse); + } while( fPortInUse && ++cRetry < MAX_RETRY ); + + if( cRetry > MAX_RETRY ) + { + hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); + goto Finished; + } + } + + dwNumDigitsInDebugPort = GetNumberOfDigits( m_dwDebugPort ); + hr = strAspNetCoreDebugPortEnvVar.SafeSnwprintf( L"%s=%u", + ASPNETCORE_DEBUG_PORT_STR, + m_dwDebugPort ); + if( FAILED ( hr ) ) + { + goto Finished; + } + + // + // Create environment for new process + // + + struApplicationId.Copy( L"ASPNETCORE_APPL_PATH=" ); + + PCWSTR pszAppPath = NULL; + + // let's find the app path. IIS does not support nested sites + // we can seek for the fourth '/' if it exits + // MACHINE/WEBROOT/APPHOST//. + pszAppPath = context->GetApplication()->GetAppConfigPath(); + DWORD dwCounter = 0; + DWORD dwPosition = 0; + while (pszAppPath[dwPosition] != NULL) + { + if (pszAppPath[dwPosition] == '/') + { + dwCounter++; + if (dwCounter == 4) + break; + } + dwPosition++; + } + if (dwCounter == 4) + { + struApplicationId.Append(pszAppPath + dwPosition); + } + else + { + struApplicationId.Append(L"/"); + } + + mszNewEnvironment.Append( struApplicationId ); + + struGuidEnv.Copy( L"ASPNETCORE_TOKEN=" ); + struGuidEnv.AppendA( m_straGuid.QueryStr(), m_straGuid.QueryCCH() ); + + mszNewEnvironment.Append( struGuidEnv ); + + pszRootApplicationPath = context->GetRootContext()->GetApplication()->GetApplicationPhysicalPath(); + + // + // generate process command line. + // + dwCommandLineLen = (DWORD)wcslen(pszRootApplicationPath) + m_ProcessPath.QueryCCH() + m_Arguments.QueryCCH() + 4; + + pszCommandLine = new WCHAR[ dwCommandLineLen ]; + if( pszCommandLine == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + pszPath = m_ProcessPath.QueryStr(); + + if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) + { + // let's check whether it is a relative path + WCHAR pszRelativePath[_MAX_PATH]; + + if (swprintf_s(pszRelativePath, + _MAX_PATH, + L"%s\\%s", + pszRootApplicationPath, + pszPath) == -1) + { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + goto Finished; + } + + if (_wfullpath(pszFullPath, + pszRelativePath, + _MAX_PATH) == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + FILE *file = NULL; + if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL) + { + fclose(file); + pszPath = pszFullPath; + } + } + + if (swprintf_s(pszCommandLine, + dwCommandLineLen, + L"\"%s\" %s", + pszPath, + m_Arguments.QueryStr()) == -1) + { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + goto Finished; + } + + // + // replace %ASPNETCORE_PORT% with port number + // + + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + pszCommandLine, + ASPNETCORE_PORT_PLACEHOLDER, + ASPNETCORE_PORT_PLACEHOLDER_CCH, + m_dwPort, + dwNumDigitsInPort, + &fReplacedEnv ); + if( FAILED( hr ) ) + { + goto Finished; + } + + // + // append AspNetCorePort to env variables. + // + + mszNewEnvironment.Append( strAspNetCorePortEnvVar ); + + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + pszCommandLine, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, + m_dwDebugPort, + dwNumDigitsInDebugPort, + &fReplacedEnv ); + if( FAILED( hr ) ) + { + goto Finished; + } + + if(fReplacedEnv) + { + // + // append debug port to environment only if + // ASPNETCORE_DEBUG_PORT placeholder is present. + // + + mszNewEnvironment.Append( strAspNetCoreDebugPortEnvVar ); + fDebugPortEnvSet = TRUE; + } + + // + // append the environment variables from web.config/aspNetCore section. + // append takes in length of string without the last null char + // this allows user to override current environment variables + // + + if(!mszEnvCopy.Copy(m_Environment)) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + LPWSTR multisz = mszEnvCopy.QueryStr(); + + while( *multisz != '\0' ) + { + // + // replace %ASPNETCORE_PORT% placeholder if present. + // + + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + multisz, + ASPNETCORE_PORT_PLACEHOLDER, + ASPNETCORE_PORT_PLACEHOLDER_CCH, + m_dwPort, + dwNumDigitsInPort, + &fReplacedEnv ); + if( FAILED( hr ) ) + { + goto Finished; + } + + // + // replace %ASPNETCORE_DEBUG_PORT% placeholder if present. + // if this placeholder is present, add this placeholder=value as + // an environment variable as well. + // + + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + multisz, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, + m_dwDebugPort, + dwNumDigitsInDebugPort, + &fReplacedEnv ); + if( FAILED( hr ) ) + { + goto Finished; + } + + if( fReplacedEnv && !fDebugPortEnvSet ) + { + mszNewEnvironment.Append( strAspNetCoreDebugPortEnvVar ); + } + + mszNewEnvironment.Append( multisz ); + multisz += wcslen( multisz) + 1; + } + + // + // Append the current env. + // copy takes in number of bytes including the double null terminator + // + pszCurrentEnvironment = GetEnvironmentStringsW(); + if (pszCurrentEnvironment == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + + // + // determine length of current environment block + // + + do + { + while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); + } while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); + + mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize + 1 ); + + + dwCreationFlags = CREATE_NO_WINDOW | + CREATE_UNICODE_ENVIRONMENT | + CREATE_SUSPENDED; // | + //CREATE_NEW_PROCESS_GROUP; + + + finalCommandLine.Copy( pszCommandLine ); + fDonePrepareCommandLine = TRUE; + + if (!CreateProcessW( + NULL, // applicationName + finalCommandLine.QueryStr(), + NULL, // processAttr + NULL, // threadAttr + TRUE, // inheritHandles + dwCreationFlags, + mszNewEnvironment.QueryStr(), + pszRootApplicationPath, // currentDir + &startupInfo, + &processInformation) ) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + // don't check return code as we already in error report + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + finalCommandLine.QueryStr(), + hr); + goto Finished; + } + + m_hProcessHandle = processInformation.hProcess; + m_dwProcessId = processInformation.dwProcessId; + + if( m_hJobObject != NULL ) + { + if( !AssignProcessToJobObject( m_hJobObject, + processInformation.hProcess ) ) + { + 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; + } + + if( CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0 ) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttachedToChildProcess = FALSE; + } + + // + // since servers like tomcat would startup even if there was a port + // collision, need to make sure the server is up and listening on + // the port specified. + // + + dwTickCount = GetTickCount(); + do + { + DWORD processStatus; + + if (GetExitCodeProcess(m_hProcessHandle, &processStatus)) + { + if (processStatus != STILL_ACTIVE) + { + hr = E_FAIL; + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + finalCommandLine.QueryStr(), + hr); + goto Finished; + } + } + + if(g_fNsiApiNotSupported) + { + hr = CheckIfServerIsUp( m_dwPort, &fReady ); + } + else + { + hr = CheckIfServerIsUp( m_dwPort, &dwActualProcessId, &fReady ); + } + + fDebuggerAttachedToChildProcess = IsDebuggerIsAttached(); + + if( !fReady ) + { + Sleep(250); + } + + dwTimeDifference = (GetTickCount() - dwTickCount); + } while( fReady == FALSE && + (( dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttachedToChildProcess) ); + + hr = RegisterProcessWait( &m_hProcessWaitHandle, + m_hProcessHandle ); + + if( FAILED( hr ) ) + { + goto Finished; + } + + // + // check if debugger is attached after startupTimeout. + // + if( !fDebuggerAttachedToChildProcess && + CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0 ) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttachedToChildProcess = FALSE; + } + + hr = GetChildProcessHandles(); + if( FAILED(hr ) ) + { + goto Finished; + } + + BOOL processMatch = FALSE; + if(dwActualProcessId == m_dwProcessId) + { + m_dwListeningProcessId = m_dwProcessId; + processMatch = TRUE; + } + + for(DWORD i=0; i < m_cChildProcess; ++i) + { + if( !processMatch && dwActualProcessId == m_dwChildProcessIds[i]) + { + m_dwListeningProcessId = m_dwChildProcessIds[i]; + processMatch = TRUE; + } + + if( m_hChildProcessHandles[i] != NULL ) + { + if( fDebuggerAttachedToChildProcess == FALSE && CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttachedToChildProcess) == 0 ) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttachedToChildProcess = FALSE; + } + + hr = RegisterProcessWait( &m_hChildProcessWaitHandles[i], + m_hChildProcessHandles[i] ); + if( FAILED( hr ) ) + { + goto Finished; + } + + cChildProcess ++; + } + } + + if(fReady == FALSE) + { + // + // hr is already set by CheckIfServerIsUp + // + + if( dwTimeDifference >= m_dwStartupTimeLimitInMS ) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + finalCommandLine.QueryStr(), + m_dwPort, + hr); + } + + goto Finished; + } + + if( !g_fNsiApiNotSupported && !processMatch ) + { + // + // process that we created is not listening + // on the port we specified. + // + fReady = FALSE; + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, + finalCommandLine.QueryStr(), + m_dwPort, + hr); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + + if( cChildProcess > 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. + // + + if(g_fNsiApiNotSupported) + { + hr = CheckIfServerIsUp( m_dwPort, &fReady ); + } + else + { + hr = CheckIfServerIsUp( m_dwPort, &dwActualProcessId, &fReady ); + } + + if( (FAILED(hr) || fReady == FALSE) ) + { + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + finalCommandLine.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->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + + 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; + + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, + 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); + } + } + +Finished: + if ( FAILED(hr) ) + { + if (strEventMsg.IsEmpty()) + { + if (!fDonePrepareCommandLine) + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, + hr); + else + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + finalCommandLine.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); + } + } + + if ( fRpcStringAllocd ) + { + RpcStringFreeA((BYTE **)&pszLogUuid); + pszLogUuid = NULL; + } + + if( processInformation.hThread != NULL ) + { + CloseHandle(processInformation.hThread); + processInformation.hThread = NULL; + } + + if(pszCurrentEnvironment != NULL ) + { + FreeEnvironmentStringsW( pszCurrentEnvironment ); + pszCurrentEnvironment = NULL; + } + + if( pszCommandLine != NULL ) + { + delete[] pszCommandLine; + pszCommandLine = NULL; + } + + 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; + } + + for(DWORD i=0;iGetApplication()->GetApplicationPhysicalPath(), + &struAbsLogFilePath ); + if(FAILED(hr)) + { + goto Finished; + } + + GetSystemTime(&systemTime); + hr = struLogFileName.SafeSnwprintf( L"%s_%d_%d%d%d%d%d%d.log", + struAbsLogFilePath.QueryStr(), + GetCurrentProcessId(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond ); + if(FAILED(hr)) + { + goto Finished; + } + + m_hStdoutHandle = CreateFileW( struLogFileName.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if( m_hStdoutHandle == INVALID_HANDLE_VALUE ) + { + fStdoutLoggingFailed = TRUE; + m_hStdoutHandle = NULL; + + if( SUCCEEDED( strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + struLogFileName.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); + } + } + } + + if( !fStdoutLoggingFailed ) + { + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = m_hStdoutHandle; + pStartupInfo->hStdOutput = m_hStdoutHandle; + + m_struFullLogFile.Copy( struLogFileName ); + + // start timer to open and close handles regularly. + m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000); + } + } + + if( (!m_fStdoutLogEnabled || fStdoutLoggingFailed) && + m_pProcessManager->QueryNULHandle() != NULL && + m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE ) + { + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = m_pProcessManager->QueryNULHandle(); + pStartupInfo->hStdOutput = m_pProcessManager->QueryNULHandle(); + } + +Finished: + + return hr; +} + +VOID +CALLBACK +SERVER_PROCESS::TimerCallback( + IN PTP_CALLBACK_INSTANCE Instance, + IN PVOID Context, + IN PTP_TIMER Timer +) +{ + Instance; + Timer; + SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*) 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( pServerProcess->QueryFullLogPath(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if( hStdoutHandle == INVALID_HANDLE_VALUE ) + { + hr = HRESULT_FROM_GETLASTERROR(); + } + + CloseHandle( hStdoutHandle ); +} + +HRESULT +SERVER_PROCESS::CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ BOOL *fReady +) +{ + HRESULT hr = S_OK; + int iResult = 0; + SOCKADDR_IN sockAddr; + BOOL fLocked = FALSE; + + _ASSERT( fReady != NULL ); + + *fReady = FALSE; + + EnterCriticalSection( &m_csLock ); + fLocked = TRUE; + + if( m_socket == INVALID_SOCKET || m_socket == NULL) + { + m_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if( m_socket == 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(m_socket, (SOCKADDR *) &sockAddr, sizeof (sockAddr)); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32( WSAGetLastError() ); + goto Finished; + } + + // + // Connected successfully, close socket. + // + + iResult = closesocket( m_socket ); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32( WSAGetLastError() ); + goto Finished; + } + + m_socket = NULL; + *fReady = TRUE; + +Finished: + + if( fLocked ) + { + LeaveCriticalSection( &m_csLock ); + fLocked = FALSE; + } + + 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; + + DBG_ASSERT( pfReady ); + DBG_ASSERT( pdwProcessId ); + + *pfReady = FALSE; + *pdwProcessId = 0; + + 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; + } + } + +Finished: + + if( pTCPInfo != NULL ) + { + HeapFree( GetProcessHeap(), 0, pTCPInfo ); + pTCPInfo = NULL; + } + + return hr; +} + +// send ctrl-c signnal to the process to let it graceful shutdown +// if the process cannot shutdown within given time, terminate it +// todo: allow user to config this shutdown timeout + +VOID +SERVER_PROCESS::SendSignal( + VOID +) +{ + HANDLE hProc; + BOOL fIsSuccess = FALSE; + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + + hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); + if (hProc != INVALID_HANDLE_VALUE) + { + fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); + + if (!fIsSuccess) + { + if (AttachConsole(m_dwProcessId)) + { + fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); + FreeConsole(); + CloseHandle(m_hProcessHandle); + m_hProcessHandle = INVALID_HANDLE_VALUE; + } + + if (!fIsSuccess) + { + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, + m_dwProcessId ))) + { + apsz[0] = strEventMsg.QueryStr(); + // log a warning for ungraceful shutdown + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, + NULL, + 1, + 0, + apsz, + NULL); + } + } + } + } + + if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) + { + // cannot gracefule shutdown or timeout + // terminate the process + TerminateProcess(m_hProcessHandle, 0); + } + } + if (hProc != INVALID_HANDLE_VALUE) + { + CloseHandle(hProc); + hProc = INVALID_HANDLE_VALUE; + } +} + +// +// 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( ! 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_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_CancelEvent( NULL ), + m_hProcessHandle( NULL ), + m_hProcessWaitHandle( NULL ), + m_dwProcessId( 0 ), + m_cChildProcess( 0 ), + m_socket( INVALID_SOCKET ), + m_fReady( FALSE ), + m_fStopping( FALSE ), + m_hStdoutHandle( NULL ), + m_fStdoutLogEnabled( FALSE ), + m_hJobObject( NULL ), + m_pForwarderConnection( NULL ), + m_dwListeningProcessId( 0 ), + m_hListeningProcessHandle( NULL ) +{ + InterlockedIncrement(&g_dwActiveServerProcesses); + srand((unsigned int)time(NULL)); + InitializeCriticalSection( &m_csLock ); + + for(INT i=0;iDereferenceProcessManager(); + m_pProcessManager = NULL; + } + + if(m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + + DeleteCriticalSection( &m_csLock ); + + InterlockedDecrement(&g_dwActiveServerProcesses); +} + +VOID +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, + (WAITORTIMERCALLBACK)&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() +{ + HRESULT hr = S_OK; + BOOL fReady = FALSE; + + CheckIfServerIsUp( m_dwPort, &fReady ); + + if( !fReady ) + { + if( !m_fStopping ) + { + m_fStopping = TRUE; + m_pProcessManager->ShutdownProcess( this ); + } + } + + DereferenceServerProcess(); + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/websockethandler.cxx b/src/AspNetCore/Src/websockethandler.cxx new file mode 100644 index 0000000000..54670607c2 --- /dev/null +++ b/src/AspNetCore/Src/websockethandler.cxx @@ -0,0 +1,1084 @@ +// Copyright (c) Microsoft Corporation. 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 ), + _pHandler ( NULL ), + _dwOutstandingIo ( 0 ), + _fCleanupInProgress ( FALSE ), + _fIndicateCompletionToIis ( 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(); + + _pWebSocketContext = NULL; + _pHttpContext = NULL; + + if (_hWebSocketRequest) + { + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = 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); + + _pHttpContext->IndicateCompletion(RQ_NOTIFICATION_PENDING); +} + +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(); + + // + // 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; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpSendComplete"); + + EnterCriticalSection (&_RequestLock); + + if (_fCleanupInProgress) + { + goto Finished; + } + + // + // Data was successfully sent to backend. + // Initiate next receive from IIS. + // + + hr = DoIisWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + 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; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpReceiveComplete"); + + EnterCriticalSection(&_RequestLock); + + if (_fCleanupInProgress) + { + goto Finished; + } + + hr = DoIisWebSocketSend( + pCompletionStatus->dwBytesTransferred, + pCompletionStatus->eBufferType + ); + + if (FAILED (hr)) + { + cleanupReason = ClientDisconnect; + goto Finished; + } + +Finished: + + 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; + CleanupReason cleanupReason = CleanupReasonUnknown; + + UNREFERENCED_PARAMETER(cbIo); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisSendComplete"); + + EnterCriticalSection(&_RequestLock); + + if (FAILED (hrCompletion)) + { + hr = hrCompletion; + cleanupReason = ClientDisconnect; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + + // + // Write Completed, initiate next read from backend server. + // + + hr = DoWinHttpWebSocketReceive(); + if (FAILED (hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + +Finished: + + 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; + CleanupReason cleanupReason = CleanupReasonUnknown; + WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnIisReceiveComplete"); + + EnterCriticalSection(&_RequestLock); + + if (FAILED (hrCompletion)) + { + cleanupReason = ClientDisconnect; + hr = hrCompletion; + goto Finished; + } + + 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: + + 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 +--*/ +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); + + EnterCriticalSection(&_RequestLock); + + if (_fCleanupInProgress) + { + goto Finished; + } + + _fCleanupInProgress = TRUE; + + _fIndicateCompletionToIis = TRUE; + + // + // TODO:: Raise FREB event with cleanup reason. + // + + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + + _pHttpContext->CancelIo(); + +Finished: + LeaveCriticalSection(&_RequestLock); +} diff --git a/src/AspNetCore/Src/winhttphelper.cxx b/src/AspNetCore/Src/winhttphelper.cxx new file mode 100644 index 0000000000..8407c45830 --- /dev/null +++ b/src/AspNetCore/Src/winhttphelper.cxx @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. 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/AspNetCore/aspnetcore_schema.xml b/src/AspNetCore/aspnetcore_schema.xml new file mode 100644 index 0000000000..5c445142e2 --- /dev/null +++ b/src/AspNetCore/aspnetcore_schema.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCore/aspnetcoremodule.rc b/src/AspNetCore/aspnetcoremodule.rc new file mode 100644 index 0000000000000000000000000000000000000000..3a6973bab1ac1a7ecd6b13d4a64ba0af27c0d173 GIT binary patch literal 6094 zcmds*eNP)l5QpdQO8pL3M^zfN0t{)@N>wES#ziG2vW=BUm5{+;kce??1C5l=p7#0e zczw6Fk0uB;P_6FvZFb&vW_EV}{K>Cv4|71ZKs)wy&VF8tNyE4w$_Fx))%7wVN>31H%m~ zSM1DwD}k-pHETvdodZ1xu$5BSdA@|%p~cO^EKygP)sU}`p0o>R==3`~VB~zJ^%gDP zgRwnZvJ2y&7vYo6Pl5O*X#A9(cCZJ=P1~{8c4$qgwQL^>O?JA*$hPfKg!cm_D_z+J zN7L%8s9A&c&jQswMjMPCP}iBsz~v(jSEs)V#oz4*=32nLD1|s=wl$OGVeXo^iaQtU z%&L^wQytq?WG>FVKtpx((U?JGK&Y(QUwkUnpsBpSR_JfAn?D!&zCxOHc+K~2nHTmV zzWMd}e3and_*=Ih6M7p9{Lk~#P2j~r&VM;SxjtM!W&C|9ej=iTy~w zjK8%7`?;U}iu9v?Vn6Z|`;mS*e_w{5oIj^u&flUvM_9$p?cn(u(dY46r=z0wroAHm zwTXh;#E5+&ts+H-)*r+TU-_7#VjHesBafumBI--7+AQ)#9VkXFhs*+O8Z%=&sx0Ip zI`jV|n)tb&>U#Z-G|KR8+!@~p9tTq%b6LvhJi#&sc%+}|4lfJ%G+T)peoQvgLE=2? zlLYa^Rl9?K9P+P`iL`=`G^Sp%gO@0iEzZd9G_I8ytzB%eHV32eb8@c}#^)(#_+yjx z+gR=vHkxABtn%m{`Ry?o+OqYL=1$0Y>hSy$FK^+`eqwLn_j^ksfwHn1nbHB-Ldt!P z$uU%C>raqkj10HKuNB~hD$D351PuJu+Y}*^8SGK3PxV|v&d0q+?DKGbD zWrh1_S(>T9!35nZd+pEmXP4e7`gZ@0zOx)#K3l{pN!IL_Akh*36jQ#Yn4`tf`MSeC zDxygT75Kiix7>lc@c169>(V=6T$xQ}$(?IC#Qv8`}FTE}Lp_S0w4+@I2rElakj zG4+Zza$hp0F=<|t_ir63z0?leMs9;Bx0L++j+V1HlE#b<8Xe8_dzWaDT4ft-^5-$T zrrUSX-xT#cmdL}aL_o#KqQ&Rj<}J)H|e@KdIEGAnOwhD@(cYct&P zJ=a?vp?5jqbe4ODt5~X$!|92uv!w~1H)z*OWqR5UL!6l4yIJrk`A}w0?OXQgDJ}V# zUZvjiDW<$77T!=FK}`|nI{a6WLoua6Z&HYnIzM%m`(9^62EjSjYub9F(L0#8uYfMn zz5!mZYkI@c8<*m>xV!{o79y9QU?aH77`qJrvw&p?Z+Uz_qAfZ)?*uLTOCw#>Un^+J zGZVeIG(*PHWNlh1Xj7E@EV!A<)dflsbwEia7o@VA5fCnyc3oVL(5ETUHxXa2%3s9lVxXy z*)y}}Y(L&hD(Mw#C|0VPLN!?p=QUJSohZ>8wy6zjOA2(Rp?aEVtSKwgj9iD@RC8Tx zL{!x_7U)I;-Lod-XY74)Loz+8=0pSHjLZmcXQe|MEMMK#3pLlo4-RR@*)ezn-LdyU z@nTc{_9aY{o*^wSs7CA@r>^# zoSegD4L0m;d3Iu*GJS33#QmBxrc#CKn2948G(9d+>WEB)U&L6N_f9zz>cVMjI0Rg0uZ|MxH(9JaW`WN8_+Rt_VAG*Cx(>nSCecdZ6n%pyK WTCAViSXt%7zUV&RRhF~=J^loSeVa4@ literal 0 HcmV?d00001 diff --git a/src/AspNetCore/version.h b/src/AspNetCore/version.h new file mode 100644 index 0000000000..9bb9d9d200 --- /dev/null +++ b/src/AspNetCore/version.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +// This file is auto-generated + + +#define FileVersion 7,1,1968,0 +#define FileVersionStr "7.1.1968.0\0" +#define ProductVersion 7,1,1968,0 +#define ProductVersionStr "7.1.1968.0\0" +#define PlatformToolset "v140\0" diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj new file mode 100644 index 0000000000..eeaf5e04f5 --- /dev/null +++ b/src/IISLib/IISLib.vcxproj @@ -0,0 +1,175 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} + Win32Proj + IISLib + IISLib + + + + StaticLibrary + true + v120 + Unicode + + + StaticLibrary + true + v120 + Unicode + + + StaticLibrary + false + v120 + true + Unicode + + + StaticLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + 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..c602c5fafd --- /dev/null +++ b/src/IISLib/acache.cxx @@ -0,0 +1,443 @@ +// Copyright (c) Microsoft Corporation. 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..49a9c8a996 --- /dev/null +++ b/src/IISLib/acache.h @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. 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..9d3f0e4a64 --- /dev/null +++ b/src/IISLib/ahutil.cpp @@ -0,0 +1,1671 @@ +// Copyright (c) Microsoft Corporation. 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..ab523f4f98 --- /dev/null +++ b/src/IISLib/ahutil.h @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft Corporation. 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..f5088552f2 --- /dev/null +++ b/src/IISLib/base64.cpp @@ -0,0 +1,482 @@ +// Copyright (c) Microsoft Corporation. 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..cc2f9ec509 --- /dev/null +++ b/src/IISLib/base64.h @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. 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..46e22da7f8 --- /dev/null +++ b/src/IISLib/buffer.h @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft Corporation. 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..f48acf0043 --- /dev/null +++ b/src/IISLib/datetime.h @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. 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..e3db88530d --- /dev/null +++ b/src/IISLib/dbgutil.h @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. 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..00d4f1c5d7 --- /dev/null +++ b/src/IISLib/hashfn.h @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. 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..e6c75823f2 --- /dev/null +++ b/src/IISLib/hashtable.h @@ -0,0 +1,666 @@ +// Copyright (c) Microsoft Corporation. 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..469dba1a2e --- /dev/null +++ b/src/IISLib/listentry.h @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. 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..2e66540428 --- /dev/null +++ b/src/IISLib/macros.h @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. 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..d8a4c1d0a6 --- /dev/null +++ b/src/IISLib/multisz.cpp @@ -0,0 +1,469 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#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; +} diff --git a/src/IISLib/multisz.h b/src/IISLib/multisz.h new file mode 100644 index 0000000000..6ea1787c73 --- /dev/null +++ b/src/IISLib/multisz.h @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. 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..fa31e7ba0c --- /dev/null +++ b/src/IISLib/multisza.cpp @@ -0,0 +1,406 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#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; +} diff --git a/src/IISLib/multisza.h b/src/IISLib/multisza.h new file mode 100644 index 0000000000..49a264fa9c --- /dev/null +++ b/src/IISLib/multisza.h @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. 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..8e7f6ab660 --- /dev/null +++ b/src/IISLib/ntassert.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. 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..f3cc02ae27 --- /dev/null +++ b/src/IISLib/percpu.h @@ -0,0 +1,305 @@ +// Copyright (c) Microsoft Corporation. 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..b723dba261 --- /dev/null +++ b/src/IISLib/precomp.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. 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 "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..fb287fcd8a --- /dev/null +++ b/src/IISLib/prime.h @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. 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..cada8c84ea --- /dev/null +++ b/src/IISLib/pudebug.h @@ -0,0 +1,736 @@ +// Copyright (c) Microsoft Corporation. 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..ba75aa5bd1 --- /dev/null +++ b/src/IISLib/reftrace.c @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation. 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..46f5ce26fd --- /dev/null +++ b/src/IISLib/reftrace.h @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. 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..3d4f5c5537 --- /dev/null +++ b/src/IISLib/rwlock.h @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. 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..16e34a21ef --- /dev/null +++ b/src/IISLib/stringa.cpp @@ -0,0 +1,1767 @@ +// Copyright (c) Microsoft Corporation. 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..01c3c5254b --- /dev/null +++ b/src/IISLib/stringa.h @@ -0,0 +1,515 @@ +// Copyright (c) Microsoft Corporation. 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..419d94dbca --- /dev/null +++ b/src/IISLib/stringu.cpp @@ -0,0 +1,1267 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#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; +} diff --git a/src/IISLib/stringu.h b/src/IISLib/stringu.h new file mode 100644 index 0000000000..66c760db7e --- /dev/null +++ b/src/IISLib/stringu.h @@ -0,0 +1,427 @@ +// Copyright (c) Microsoft Corporation. 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..e85861c7c1 --- /dev/null +++ b/src/IISLib/tracelog.c @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft Corporation. 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..feed945d54 --- /dev/null +++ b/src/IISLib/tracelog.h @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. 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..e09c9845e1 --- /dev/null +++ b/src/IISLib/treehash.h @@ -0,0 +1,850 @@ +// Copyright (c) Microsoft Corporation. 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..f1a7f33935 --- /dev/null +++ b/src/IISLib/util.cxx @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. 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); +} + From f558c5de9963138d4bfcd4ff6df9966655e65cce Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Tue, 20 Sep 2016 15:47:50 -0700 Subject: [PATCH 002/107] Update the filewatcher Add a reference counter to avoid AV in case web.config was changed --- src/AspNetCore/Inc/application.h | 6 - src/AspNetCore/Inc/applicationmanager.h | 8 - src/AspNetCore/Inc/filewatcher.h | 34 ++++- src/AspNetCore/Inc/resource.h | 12 +- src/AspNetCore/Src/application.cxx | 7 +- src/AspNetCore/Src/applicationmanager.cxx | 19 +-- src/AspNetCore/Src/aspnetcoreconfig.cxx | 5 +- src/AspNetCore/Src/filewatcher.cxx | 171 ++++++++++++---------- src/AspNetCore/Src/forwardinghandler.cxx | 17 +-- src/AspNetCore/Src/serverprocess.cxx | 25 +++- 10 files changed, 170 insertions(+), 134 deletions(-) diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h index c06e763937..058bf8c731 100644 --- a/src/AspNetCore/Inc/application.h +++ b/src/AspNetCore/Inc/application.h @@ -202,12 +202,6 @@ public: return m_pAppOfflineHtm; } - STRU* - QueryApplicationPhysicalPath() - { - return &m_strAppPhysicalPath; - } - ~APPLICATION(); HRESULT diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index e1c48c69cd..3a6f934113 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -39,17 +39,9 @@ public: HRESULT GetApplication( _In_ IHttpContext* pContext, - _In_ LPCWSTR pszApplication, _Out_ APPLICATION ** ppApplication ); - static - VOID - RecycleOnFileChange( - APPLICATION_MANAGER* manager, - APPLICATION* application - ); - HRESULT RecycleApplication( _In_ LPCWSTR pszApplication diff --git a/src/AspNetCore/Inc/filewatcher.h b/src/AspNetCore/Inc/filewatcher.h index b1939a9e84..16d3942a2f 100644 --- a/src/AspNetCore/Inc/filewatcher.h +++ b/src/AspNetCore/Inc/filewatcher.h @@ -4,6 +4,7 @@ #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 @@ -53,14 +54,12 @@ public: private: HANDLE m_hCompletionPort; HANDLE m_hChangeNotificationThread; - CRITICAL_SECTION m_csSyncRoot; }; class FILE_WATCHER_ENTRY { public: FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); - virtual ~FILE_WATCHER_ENTRY(); OVERLAPPED _overlapped; @@ -72,6 +71,33 @@ public: _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(); @@ -83,6 +109,8 @@ public: ); private: + virtual ~FILE_WATCHER_ENTRY(); + DWORD _dwSignature; BUFFER _buffDirectoryChanges; HANDLE _hImpersonationToken; @@ -92,5 +120,7 @@ private: STRU _strFileName; STRU _strDirectoryName; LONG _lStopMonitorCalled; + mutable LONG _cRefs; + BOOL _fIsValid; SRWLOCK _srwLock; }; diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index b4d8d2dd88..066c9792f5 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -7,12 +7,12 @@ #define IDS_SERVER_ERROR 1001 #define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 -#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Process '%d' started successfully and is listening on port '%d'." +#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"Failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Process was created with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Failed to start process with commandline '%s', ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Process was created with commandline '%s' but did not listen on the given port '%d'" -#define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Process was created with commandline '%s' but either crashed or did not reponse within given time or did not listen on the given port '%d', ErrorCode = '0x%x'" +#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 : %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'." diff --git a/src/AspNetCore/Src/application.cxx b/src/AspNetCore/Src/application.cxx index 5db1445318..ff2a82d3f3 100644 --- a/src/AspNetCore/Src/application.cxx +++ b/src/AspNetCore/Src/application.cxx @@ -7,8 +7,11 @@ APPLICATION::~APPLICATION() { 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(); - delete m_pFileWatcherEntry; m_pFileWatcherEntry = NULL; } @@ -76,7 +79,7 @@ Finished: { if (m_pFileWatcherEntry != NULL) { - delete m_pFileWatcherEntry; + m_pFileWatcherEntry->DereferenceFileWatcherEntry(); m_pFileWatcherEntry = NULL; } diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx index 785182220c..75c4d3d12e 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -8,7 +8,6 @@ APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; HRESULT APPLICATION_MANAGER::GetApplication( _In_ IHttpContext* pContext, - _In_ LPCWSTR pszApplication, _Out_ APPLICATION ** ppApplication ) { @@ -16,11 +15,15 @@ APPLICATION_MANAGER::GetApplication( APPLICATION *pApplication = NULL; APPLICATION_KEY key; BOOL fExclusiveLock = FALSE; - + PCWSTR pszApplicationId = NULL; *ppApplication = NULL; + + DBG_ASSERT(pContext != NULL); + DBG_ASSERT(pContext->GetApplication() != NULL); + pszApplicationId = pContext->GetApplication()->GetApplicationId(); - hr = key.Initialize(pszApplication); + hr = key.Initialize(pszApplicationId); if (FAILED(hr)) { goto Finished; @@ -50,7 +53,7 @@ APPLICATION_MANAGER::GetApplication( goto Finished; } - hr = pApplication->Initialize(this, pszApplication, pContext->GetApplication()->GetApplicationPhysicalPath()); + hr = pApplication->Initialize(this, pszApplicationId, pContext->GetApplication()->GetApplicationPhysicalPath()); if (FAILED(hr)) { goto Finished; @@ -88,14 +91,6 @@ Finished: return hr; } -VOID -APPLICATION_MANAGER::RecycleOnFileChange( - APPLICATION_MANAGER*, -APPLICATION* -) -{ - g_pHttpServer->RecycleProcess(L"Asp.Net Core Module Recycle Process on File Change Notification"); -} HRESULT APPLICATION_MANAGER::RecycleApplication( diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index 04f747894e..e4633806ac 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -79,7 +79,9 @@ ASPNETCORE_CONFIG::GetConfig( } else { - hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetAppConfigPath()); + // 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; @@ -382,6 +384,7 @@ ASPNETCORE_CONFIG::Populate( } pcszEnvName = mszEnvNames.Next(pcszEnvName); } + // // let's disable this feature for now // diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx index 5298a86dbe..545d76bc63 100644 --- a/src/AspNetCore/Src/filewatcher.cxx +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -7,12 +7,15 @@ FILE_WATCHER::FILE_WATCHER() : m_hCompletionPort(NULL), m_hChangeNotificationThread(NULL) { - InitializeCriticalSection(&this->m_csSyncRoot); } FILE_WATCHER::~FILE_WATCHER() { - DeleteCriticalSection(&this->m_csSyncRoot); + if (m_hChangeNotificationThread != NULL) + { + CloseHandle(m_hChangeNotificationThread); + m_hChangeNotificationThread = NULL; + } } HRESULT @@ -76,29 +79,34 @@ Win32 error --*/ { FILE_WATCHER * pFileMonitor; - BOOL fRet = FALSE; + BOOL fSuccess = FALSE; DWORD cbCompletion = 0; OVERLAPPED * pOverlapped = NULL; DWORD dwErrorStatus; ULONG_PTR completionKey; pFileMonitor = (FILE_WATCHER*)pvArg; + DBG_ASSERT(pFileMonitor != NULL); + while (TRUE) { - fRet = GetQueuedCompletionStatus( + fSuccess = GetQueuedCompletionStatus( pFileMonitor->m_hCompletionPort, &cbCompletion, &completionKey, &pOverlapped, INFINITE); - dwErrorStatus = fRet ? ERROR_SUCCESS : GetLastError(); + DBG_ASSERT(fSuccess); + DebugPrint(1, "FILE_WATCHER::ChangeNotificationThread"); + dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) { continue; } + DBG_ASSERT(pOverlapped != NULL); if (pOverlapped != NULL) { FileWatcherCompletionRoutine( @@ -138,10 +146,26 @@ None { FILE_WATCHER_ENTRY * pMonitorEntry; pMonitorEntry = CONTAINING_RECORD(pOverlapped, FILE_WATCHER_ENTRY, _overlapped); - + pMonitorEntry->DereferenceFileWatcherEntry(); DBG_ASSERT(pMonitorEntry != NULL); - pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, - cbCompletion); + + pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion); + + if (pMonitorEntry->QueryIsValid()) + { + // + // Continue monitoring + // + pMonitorEntry->Monitor(); + } + else + { + // + // Marked by application distructor + // Deference the entry to delete it + // + pMonitorEntry->DereferenceFileWatcherEntry(); + } } @@ -150,7 +174,9 @@ FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : _hDirectory(INVALID_HANDLE_VALUE), _hImpersonationToken(NULL), _pApplication(NULL), - _lStopMonitorCalled(0) + _lStopMonitorCalled(0), + _cRefs(1), + _fIsValid(TRUE) { _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE; InitializeSRWLock(&_srwLock); @@ -201,6 +227,12 @@ HRESULT 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 @@ -211,32 +243,31 @@ HRESULT // if (_lStopMonitorCalled) { - hr = S_OK; 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) - { - // - // There could be a FCN overflow - // Let assume the file got changed instead of checking files - // Othersie we have to cache the file info - // - + { fFileChanged = TRUE; - hr = HRESULT_FROM_WIN32(dwCompletionStatus); } else { pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); - _ASSERT(pNotificationInfo != NULL); + DBG_ASSERT(pNotificationInfo != NULL); while (pNotificationInfo != NULL) { // // check whether the monitored file got changed // - if (wcscmp(pNotificationInfo->FileName, _strFileName.QueryStr()) == 0) + if (wcsncmp(pNotificationInfo->FileName, + _strFileName.QueryStr(), + pNotificationInfo->FileNameLength/sizeof(WCHAR)) == 0) { fFileChanged = TRUE; break; @@ -255,13 +286,7 @@ HRESULT pNotificationInfo->NextEntryOffset); } } - - RtlZeroMemory(_buffDirectoryChanges.QueryPtr(), _buffDirectoryChanges.QuerySize()); } - // - //continue monitoring - // - StopMonitor(); if (fFileChanged) { @@ -271,9 +296,8 @@ HRESULT _pApplication->UpdateAppOfflineFileHandle(); } - hr = Monitor(); - Finished: + ReleaseSRWLockExclusive(&_srwLock); return hr; } @@ -285,69 +309,23 @@ FILE_WATCHER_ENTRY::Monitor(VOID) DWORD cbRead; AcquireSRWLockExclusive(&_srwLock); - + ReferenceFileWatcherEntry(); ZeroMemory(&_overlapped, sizeof(_overlapped)); - if (_hDirectory != INVALID_HANDLE_VALUE) - { - CloseHandle(_hDirectory); - _hDirectory = INVALID_HANDLE_VALUE; - } - _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; - } - - // - // Resize change buffer to something "reasonable" - // - fRet = _buffDirectoryChanges.Resize(4096); - if (!fRet) - { - hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); - goto Finished; - } - - fRet = ReadDirectoryChangesW(_hDirectory, + if(!ReadDirectoryChangesW(_hDirectory, _buffDirectoryChanges.QueryPtr(), _buffDirectoryChanges.QuerySize(), - FALSE, // watch sub dirs. set to False now as only monitoring app_offline + FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS & ~FILE_NOTIFY_CHANGE_ATTRIBUTES, &cbRead, &_overlapped, - NULL); - - if (!fRet) + NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); } - InterlockedExchange(&_lStopMonitorCalled, 0); - -Finished: - ReleaseSRWLockExclusive(&_srwLock); return hr; - } VOID @@ -386,7 +364,7 @@ FILE_WATCHER_ENTRY::Create( pszFileNameToMonitor == NULL || pApplication == NULL) { - _ASSERT(FALSE); + DBG_ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto Finished; } @@ -406,6 +384,15 @@ FILE_WATCHER_ENTRY::Create( 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(), @@ -430,6 +417,32 @@ FILE_WATCHER_ENTRY::Create( _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 // diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 6a6f44f685..f5a0ad00a4 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -359,18 +359,10 @@ FORWARDING_HANDLER::SetStatusAndHeaders( DWORD headerIndex = g_pResponseHeaderHash->GetIndex(strHeaderName.QueryStr()); if (headerIndex == UNKNOWN_INDEX) { - if (_strnicmp(strHeaderName.QueryStr(), "Sec-WebSocket", 13) != 0 ) - { - // - // Perf Opt: Avoid setting websocket headers, since IIS websocket module - // will anyways set these later in the pipeline. - // - - hr = pResponse->SetHeader(strHeaderName.QueryStr(), - strHeaderValue.QueryStr(), - static_cast(strHeaderValue.QueryCCH()), - FALSE); // fReplace - } + hr = pResponse->SetHeader(strHeaderName.QueryStr(), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + FALSE); // fReplace } else { @@ -1074,7 +1066,6 @@ VOID } hr = pApplicationManager->GetApplication( m_pW3Context, - m_pW3Context->GetApplication()->GetAppConfigPath(), &m_pApplication ); if (FAILED(hr)) { diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 4c0ca9b1b5..3b8c9c5252 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -129,6 +129,7 @@ SERVER_PROCESS::StartProcess( WCHAR* pszPath = NULL; WCHAR pszFullPath[_MAX_PATH]; LPCWSTR apsz[1]; + PCWSTR pszAppPath = NULL; GetStartupInfoW(&startupInfo); @@ -243,8 +244,6 @@ SERVER_PROCESS::StartProcess( struApplicationId.Copy( L"ASPNETCORE_APPL_PATH=" ); - PCWSTR pszAppPath = NULL; - // let's find the app path. IIS does not support nested sites // we can seek for the fourth '/' if it exits // MACHINE/WEBROOT/APPHOST//. @@ -277,7 +276,7 @@ SERVER_PROCESS::StartProcess( mszNewEnvironment.Append( struGuidEnv ); - pszRootApplicationPath = context->GetRootContext()->GetApplication()->GetApplicationPhysicalPath(); + pszRootApplicationPath = context->GetApplication()->GetApplicationPhysicalPath(); // // generate process command line. @@ -486,8 +485,11 @@ SERVER_PROCESS::StartProcess( // don't check return code as we already in error report strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, finalCommandLine.QueryStr(), - hr); + hr, + 0); goto Finished; } @@ -537,8 +539,11 @@ SERVER_PROCESS::StartProcess( hr = E_FAIL; strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, finalCommandLine.QueryStr(), - hr); + hr, + processStatus); goto Finished; } } @@ -632,6 +637,8 @@ SERVER_PROCESS::StartProcess( hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, finalCommandLine.QueryStr(), m_dwPort, hr); @@ -649,6 +656,8 @@ SERVER_PROCESS::StartProcess( fReady = FALSE; strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, finalCommandLine.QueryStr(), m_dwPort, hr); @@ -678,6 +687,8 @@ SERVER_PROCESS::StartProcess( { strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, finalCommandLine.QueryStr(), m_dwPort, hr); @@ -724,6 +735,7 @@ SERVER_PROCESS::StartProcess( if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, + pszAppPath, m_dwProcessId, m_dwPort))) { @@ -754,11 +766,14 @@ Finished: { if (!fDonePrepareCommandLine) strEventMsg.SafeSnwprintf( + pszAppPath, ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, hr); else strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, finalCommandLine.QueryStr(), hr); } From 21b683b733686188dd8ecab3c2eea9e936d81171 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Tue, 20 Sep 2016 16:57:03 -0700 Subject: [PATCH 003/107] MC wasn't getting invoked for release builds --- AspNetCoreModule | 1 - src/AspNetCore/AspNetCore.vcxproj | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) delete mode 160000 AspNetCoreModule diff --git a/AspNetCoreModule b/AspNetCoreModule deleted file mode 160000 index c9cae1691f..0000000000 --- a/AspNetCoreModule +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c9cae1691f80951e40985fe149db6e094064f080 diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index b510857a23..e3d552cc05 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -206,15 +206,16 @@ - false - false Document mc %(FullPath) - mc %(FullPath) - %(Filename).rc;%(Filename).h;MSG0409.bin - %(Filename).rc;%(Filename).h;MSG0409.bin 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 From 210686a4052a3349c18ff853982d9febc00146ce Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Sep 2016 17:30:41 -0700 Subject: [PATCH 004/107] Making build use Sake --- .gitignore | 3 +- build.cmd | 3 +- build.ps1 | 67 +++++++++++++++++++++++++++++++ src/AspNetCore/AspNetCore.vcxproj | 8 ++-- src/IISLib/IISLib.vcxproj | 8 ++-- 5 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 build.ps1 diff --git a/.gitignore b/.gitignore index efccce79d1..50c65f5468 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ src/*/x64/Release/ src/AspNetCore/aspnetcore_msg.h src/AspNetCore/aspnetcore_msg.rc -src/AspNetCore/version.h \ No newline at end of file +src/AspNetCore/version.h +.build \ No newline at end of file diff --git a/build.cmd b/build.cmd index 0d744a8940..7d4894cb4a 100644 --- a/build.cmd +++ b/build.cmd @@ -1 +1,2 @@ -msbuild "%~dp0\Build\build.msbuild" /v:minimal /maxcpucount /nodeReuse:false %* \ No newline at end of file +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000000..8f2f99691a --- /dev/null +++ b/build.ps1 @@ -0,0 +1,67 @@ +$ErrorActionPreference = "Stop" + +function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) +{ + while($true) + { + try + { + Invoke-WebRequest $url -OutFile $downloadLocation + break + } + catch + { + $exceptionMessage = $_.Exception.Message + Write-Host "Failed to download '$url': $exceptionMessage" + if ($retries -gt 0) { + $retries-- + Write-Host "Waiting 10 seconds before retrying. Retries left: $retries" + Start-Sleep -Seconds 10 + + } + else + { + $exception = $_.Exception + throw $exception + } + } + } +} + +cd $PSScriptRoot + +$repoFolder = $PSScriptRoot +$env:REPO_FOLDER = $repoFolder + +$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" +if ($env:KOREBUILD_ZIP) +{ + $koreBuildZip=$env:KOREBUILD_ZIP +} + +$buildFolder = ".build" +$buildFile="$buildFolder\KoreBuild.ps1" + +if (!(Test-Path $buildFolder)) { + Write-Host "Downloading KoreBuild from $koreBuildZip" + + $tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid() + New-Item -Path "$tempFolder" -Type directory | Out-Null + + $localZipFile="$tempFolder\korebuild.zip" + + DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder) + + New-Item -Path "$buildFolder" -Type directory | Out-Null + copy-item "$tempFolder\**\build\*" $buildFolder -Recurse + + # Cleanup + if (Test-Path $tempFolder) { + Remove-Item -Recurse -Force $tempFolder + } +} + +&"$buildFile" $args \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index e3d552cc05..fb903b5862 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -32,27 +32,27 @@ DynamicLibrary true - v120 + v140 Unicode DynamicLibrary true - v120 + v140 Unicode false DynamicLibrary false - v120 + v140 true Unicode DynamicLibrary false - v120 + v140 true Unicode diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj index eeaf5e04f5..5f6181152e 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -28,26 +28,26 @@ StaticLibrary true - v120 + v140 Unicode StaticLibrary true - v120 + v140 Unicode StaticLibrary false - v120 + v140 true Unicode StaticLibrary false - v120 + v140 true Unicode From 50eb68ad3274526009c6b43b3987da8bd7acc8f1 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Fri, 26 Aug 2016 12:25:00 -0700 Subject: [PATCH 005/107] Add build instructions --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..4bf6632c3f --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# ASP.NET Core Module + +The ASP.NET Core Module is an IIS Module which is responsible for process +management of ASP.NET Core http listeners and to proxy requests to the process +that it manages. + +## Pre-requisites for building + +### Windows 8.1+ or Windows Server 2012 R2+ + +### Visual C++ Build Tools + +[Download](http://download.microsoft.com/download/D/2/3/D23F4D0F-BA2D-4600-8725-6CCECEA05196/vs_community_ENU.exe) +and install Visual Studio 2015. In Visual Studio 2015 C++ tooling is no longer +installed by default, you must chose "Custom" install and select Visual C++. + +![Visual C++](https://cloud.githubusercontent.com/assets/4734691/18014419/b06e589a-6b77-11e6-9393-4eed32186ca3.png) + +Optionally, if you don't want to install Visual Studio you can just install the +[Visual C++ build tools](http://landinghub.visualstudio.com/visual-cpp-build-tools). + +### MSBuild + +If you have installed Visual Studio, you should already have MSBuild. If you +installed the Visual C++ build tools, you will need to download and install +[Microsoft Build Tools 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48159) + +Once you have installed MSBuild, you can add it your path. The default location +for MSBuild is `%ProgramFiles(x86)%\MSBuild\14.0\Bin` + +### Windows Software Development Kit for Windows 8.1 + +[Download](http://download.microsoft.com/download/B/0/C/B0C80BA3-8AD6-4958-810B-6882485230B5/standalonesdk/sdksetup.exe) +and install the Windows SDK for Windows 8.1. From the Feature list presented, +ensure you select *Windows Software Development Kit*. + +If chose to install from the command prompt, you can run the following command. +```` +.\sdksetup.exe /features OptionId.WindowsDesktopSoftwareDevelopmentKit +```` + +## How to build + + +```powershell + +# Clean +.\build.cmd /target:clean + +# Build +.\build.cmd + +# Build 64-bit +.\build.cmd /property:platform=x64 + +# Build in Release Configuration +.\build.cmd /property:configuration=release +``` From 0327e3b9935c85aacdf2090b72c91f5d3de1a7a7 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 21 Sep 2016 15:02:10 -0700 Subject: [PATCH 006/107] Change output directory to work with KoreBuild --- Build/Build.Settings | 2 +- src/AspNetCore/AspNetCore.vcxproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Build/Build.Settings b/Build/Build.Settings index c1752abb93..ae2912343a 100644 --- a/Build/Build.Settings +++ b/Build/Build.Settings @@ -8,7 +8,7 @@ v120 v140 v120 - $(SolutionDir)$(Configuration)\$(Platform)\ + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ $(OutputPath) aspnetcore diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index fb903b5862..bcd8be1fd8 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -25,7 +25,6 @@ AspNetCoreModule AspNetCore aspnetcore - $(SolutionDir)bin\$(Configuration)\$(Platform)\ true From 3e62285de5bf16269faea65d621b4b6d8d0f711f Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Sep 2016 17:30:41 -0700 Subject: [PATCH 007/107] Making build use Sake --- .gitignore | 3 +- build.cmd | 3 +- build.ps1 | 67 +++++++++++++++++++++++++++++++ src/AspNetCore/AspNetCore.vcxproj | 8 ++-- src/IISLib/IISLib.vcxproj | 8 ++-- 5 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 build.ps1 diff --git a/.gitignore b/.gitignore index efccce79d1..50c65f5468 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ src/*/x64/Release/ src/AspNetCore/aspnetcore_msg.h src/AspNetCore/aspnetcore_msg.rc -src/AspNetCore/version.h \ No newline at end of file +src/AspNetCore/version.h +.build \ No newline at end of file diff --git a/build.cmd b/build.cmd index 0d744a8940..7d4894cb4a 100644 --- a/build.cmd +++ b/build.cmd @@ -1 +1,2 @@ -msbuild "%~dp0\Build\build.msbuild" /v:minimal /maxcpucount /nodeReuse:false %* \ No newline at end of file +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000000..8f2f99691a --- /dev/null +++ b/build.ps1 @@ -0,0 +1,67 @@ +$ErrorActionPreference = "Stop" + +function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) +{ + while($true) + { + try + { + Invoke-WebRequest $url -OutFile $downloadLocation + break + } + catch + { + $exceptionMessage = $_.Exception.Message + Write-Host "Failed to download '$url': $exceptionMessage" + if ($retries -gt 0) { + $retries-- + Write-Host "Waiting 10 seconds before retrying. Retries left: $retries" + Start-Sleep -Seconds 10 + + } + else + { + $exception = $_.Exception + throw $exception + } + } + } +} + +cd $PSScriptRoot + +$repoFolder = $PSScriptRoot +$env:REPO_FOLDER = $repoFolder + +$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" +if ($env:KOREBUILD_ZIP) +{ + $koreBuildZip=$env:KOREBUILD_ZIP +} + +$buildFolder = ".build" +$buildFile="$buildFolder\KoreBuild.ps1" + +if (!(Test-Path $buildFolder)) { + Write-Host "Downloading KoreBuild from $koreBuildZip" + + $tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid() + New-Item -Path "$tempFolder" -Type directory | Out-Null + + $localZipFile="$tempFolder\korebuild.zip" + + DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder) + + New-Item -Path "$buildFolder" -Type directory | Out-Null + copy-item "$tempFolder\**\build\*" $buildFolder -Recurse + + # Cleanup + if (Test-Path $tempFolder) { + Remove-Item -Recurse -Force $tempFolder + } +} + +&"$buildFile" $args \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index e3d552cc05..fb903b5862 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -32,27 +32,27 @@ DynamicLibrary true - v120 + v140 Unicode DynamicLibrary true - v120 + v140 Unicode false DynamicLibrary false - v120 + v140 true Unicode DynamicLibrary false - v120 + v140 true Unicode diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj index eeaf5e04f5..5f6181152e 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -28,26 +28,26 @@ StaticLibrary true - v120 + v140 Unicode StaticLibrary true - v120 + v140 Unicode StaticLibrary false - v120 + v140 true Unicode StaticLibrary false - v120 + v140 true Unicode From c6b3bb1243ffcc3a923e1f860cba8217388e542b Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 21 Sep 2016 15:02:10 -0700 Subject: [PATCH 008/107] Change output directory to work with KoreBuild --- Build/Build.Settings | 2 +- src/AspNetCore/AspNetCore.vcxproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Build/Build.Settings b/Build/Build.Settings index c1752abb93..ae2912343a 100644 --- a/Build/Build.Settings +++ b/Build/Build.Settings @@ -8,7 +8,7 @@ v120 v140 v120 - $(SolutionDir)$(Configuration)\$(Platform)\ + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ $(OutputPath) aspnetcore diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index fb903b5862..bcd8be1fd8 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -25,7 +25,6 @@ AspNetCoreModule AspNetCore aspnetcore - $(SolutionDir)bin\$(Configuration)\$(Platform)\ true From 191e14b2764706579b045e9f94892ff7bc27c82b Mon Sep 17 00:00:00 2001 From: pan-wang Date: Fri, 23 Sep 2016 10:59:40 -0700 Subject: [PATCH 009/107] fix build warning and a memory leak --- src/AspNetCore/Inc/applicationmanager.h | 7 +++++++ src/AspNetCore/Src/applicationmanager.cxx | 4 ++++ src/AspNetCore/Src/serverprocess.cxx | 9 ++++++--- src/IISLib/multisz.cpp | 5 +++++ src/IISLib/multisza.cpp | 2 ++ src/IISLib/stringu.cpp | 4 ++++ 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index 3a6f934113..41163a66b6 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -66,6 +66,13 @@ public: delete m_pFileWatcher; m_pFileWatcher = NULL; } + + if(m_pHttp502ErrorPage != NULL) + { + delete m_pHttp502ErrorPage; + m_pHttp502ErrorPage = NULL; + } + } FILE_WATCHER* diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx index 75c4d3d12e..b99d645e41 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -151,6 +151,10 @@ APPLICATION_MANAGER::Get502ErrorPage( pHttp502ErrorPage->FromMemory.pBuffer = (PVOID)m_pstrErrorInfo; pHttp502ErrorPage->FromMemory.BufferLength = (ULONG)strnlen(m_pstrErrorInfo, maxsize); //(ULONG)(wcslen(m_pstrErrorInfo)); // *sizeof(WCHAR); + if(m_pHttp502ErrorPage != NULL) + { + delete m_pHttp502ErrorPage; + } m_pHttp502ErrorPage = pHttp502ErrorPage; *ppErrorPage = m_pHttp502ErrorPage; } diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 3b8c9c5252..de3ea301ca 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -129,7 +129,7 @@ SERVER_PROCESS::StartProcess( WCHAR* pszPath = NULL; WCHAR pszFullPath[_MAX_PATH]; LPCWSTR apsz[1]; - PCWSTR pszAppPath = NULL; + PCWSTR pszAppPath = NULL; GetStartupInfoW(&startupInfo); @@ -457,8 +457,11 @@ SERVER_PROCESS::StartProcess( while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); } while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); - mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize + 1 ); - + DBG_ASSERT(dwCurrentEnvSize > 0); + // + // environment block ends with \0\0, we don't want include the last \0 for appending + // + mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize-1 ); dwCreationFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT | diff --git a/src/IISLib/multisz.cpp b/src/IISLib/multisz.cpp index d8a4c1d0a6..d7ae9f3fdb 100644 --- a/src/IISLib/multisz.cpp +++ b/src/IISLib/multisz.cpp @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. 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 @@ -467,3 +470,5 @@ Finished: return hr; } + +#pragma warning(default:4267) \ No newline at end of file diff --git a/src/IISLib/multisza.cpp b/src/IISLib/multisza.cpp index fa31e7ba0c..07479fac31 100644 --- a/src/IISLib/multisza.cpp +++ b/src/IISLib/multisza.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. 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 @@ -404,3 +405,4 @@ Finished: return hr; } +#pragma warning(default:4267) \ No newline at end of file diff --git a/src/IISLib/stringu.cpp b/src/IISLib/stringu.cpp index 419d94dbca..4d92a4d023 100644 --- a/src/IISLib/stringu.cpp +++ b/src/IISLib/stringu.cpp @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. 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( @@ -1265,3 +1267,5 @@ Exit: return hr; } + +#pragma warning(default:4267) From 9982e73d3e9907a73db00e24626bd5c98c5e1ab4 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Mon, 26 Sep 2016 15:07:33 -0700 Subject: [PATCH 010/107] Change project structure to accomodate test projects --- AspNetCoreModule.sln | 21 ++++++++++++++++++++- global.json | 3 +++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 global.json diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index 986f3e555a..a8ba10859c 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" ProjectSection(ProjectDependencies) = postProject @@ -10,26 +10,41 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCor EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{02F461DC-5166-4E88-AAD5-CF110016A647}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2097C03C-E2F7-4396-B3BC-4335F1B87B5E}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FDD2EDF8-1B62-4978-9815-9D95260B8B91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Release|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = Release|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.ActiveCfg = Release|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Any CPU.ActiveCfg = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.ActiveCfg = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.Build.0 = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Release|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = Release|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.ActiveCfg = Release|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Any CPU.ActiveCfg = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.ActiveCfg = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.Build.0 = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|Win32 @@ -38,4 +53,8 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {439824F9-1455-4CC4-BD79-B44FA0A16552} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} + EndGlobalSection EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000000..0c827e1b26 --- /dev/null +++ b/global.json @@ -0,0 +1,3 @@ +{ + "projects": [ "test" ] +} \ No newline at end of file From d245ded51d615246d5770ed6c1af1336a1e0b248 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Mon, 26 Sep 2016 17:08:07 -0700 Subject: [PATCH 011/107] Produce fake nupkg for testing --- makefile.shade | 20 ++++++++++++++++++++ nuget/AspNetCore.nuspec | 19 +++++++++++++++++++ src/AspNetCore/AspNetCore.vcxproj | 5 +++++ 3 files changed, 44 insertions(+) create mode 100644 makefile.shade create mode 100644 nuget/AspNetCore.nuspec diff --git a/makefile.shade b/makefile.shade new file mode 100644 index 0000000000..6be672789f --- /dev/null +++ b/makefile.shade @@ -0,0 +1,20 @@ +default BASE_DIR_LOCAL='${Directory.GetCurrentDirectory()}' +default BUILD_DIR_LOCAL='${Path.Combine(BASE_DIR_LOCAL, "artifacts", "build")}' +var VERSION='0.1' +var FULL_VERSION='0.1' + +use-standard-lifecycle +k-standard-goals + +#make-nupkg target='package' + log info='Make nuget package containing ASP.NET Core Module' + @{ + var nugetExePath = Environment.GetEnvironmentVariable("KOREBUILD_NUGET_EXE"); + if (string.IsNullOrEmpty(nugetExePath)) + { + nugetExePath = Path.Combine(BASE_DIR_LOCAL, ".build", "nuget.exe"); + } + + var nuspecPath = Path.Combine(BASE_DIR_LOCAL, "nuget", "AspNetCore.nuspec"); + ExecClr(nugetExePath, "pack " + nuspecPath + " -OutputDirectory " + BUILD_DIR_LOCAL + " -prop VERSION=1.0.0-" + BuildNumber); + } diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec new file mode 100644 index 0000000000..c4298b6822 --- /dev/null +++ b/nuget/AspNetCore.nuspec @@ -0,0 +1,19 @@ + + + + Microsoft.AspNetCore.AspNetCoreModule + Microsoft ASP.NET Core Module + $VERSION$ + Microsoft + Microsoft + http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm + © Microsoft Corporation. All rights reserved. + http://www.asp.net/ + true + ASP.NET Core Module + en-US + + + + + \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index bcd8be1fd8..bc85154a61 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -231,6 +231,11 @@ + + + PreserveNewest + + From 4a79573def7b230b52bc218326c3127b6b52b4f9 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 30 Sep 2016 17:18:29 -0700 Subject: [PATCH 012/107] Jhkim/add installancm ps1 (#15) Jhkim/add installancm ps1 --- nuget/AspNetCore.nuspec | 1 + tools/installancm.ps1 | 442 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 443 insertions(+) create mode 100644 tools/installancm.ps1 diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index c4298b6822..f19743a8c5 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/tools/installancm.ps1 b/tools/installancm.ps1 new file mode 100644 index 0000000000..a2b5bdd0e8 --- /dev/null +++ b/tools/installancm.ps1 @@ -0,0 +1,442 @@ +<# +.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", + [Parameter(Mandatory=$false, Position = 1)] + [string] $PackagePath="$PSScriptRoot\..\artifacts", + [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]$DestineFilePath) { + + $Source = $SourceFilePath + $Destine = $DestineFilePath + + $BackupFilePath = $Destine + ".ancm_backup" + if ($Rollback) + { + $Source = $BackupFilePath + } + + $functionName = "Update-File -Rollback:$" + $Rollback.ToString() + $LogHeader = "[$ScriptFileName::$functionName]" + + if ($ForceToBackup) + { + if (Test-Path $BackupFilePath) + { + Say (" Delete the existing backup file: $BackupFilePath") + Remove-Item $BackupFilePath -Force -Confirm:$false + } + if (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 $Destine $BackupFilePath -Force + + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Destine) -DifferenceObject $(Get-Content $BackupFilePath)) + if (-not $fileMatched) + { + throw ("$LogHeader File not matched!!! $Destine $BackupFilePath") + } + } + if (-Not (Test-Path $BackupFilePath)) + { + throw ("$LogHeader Can't backup $Source to $BackupFilePath") + } + + # Copy file from Source to Destine if those files are different each other + if (-Not (Test-Path $Destine)) + { + throw ("$LogHeader Can't find $Destine") + } + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destine)) + if (-not $fileMatched) + { + Say (" Copying $Source to $Desting...") + Copy-Item $Source $Destine -Force + + # check file is correctly copied + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destine)) + if (-not $fileMatched) + { + throw ("$LogHeader File not matched!!! $Source $Destine") + } + else + { + Say-Verbose ("$LogHeader File matched!!! $Source to $Destine") + } + } + else + { + Say (" Skipping the file $Destine 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 +} +$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" + +$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 From 4ab0cdb8fadd186e699a6abe323cd104f3ab94cc Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 30 Sep 2016 17:29:08 -0700 Subject: [PATCH 013/107] fixed typo --- tools/installancm.ps1 | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/installancm.ps1 b/tools/installancm.ps1 index a2b5bdd0e8..5005d5156b 100644 --- a/tools/installancm.ps1 +++ b/tools/installancm.ps1 @@ -26,9 +26,9 @@ Example: [cmdletbinding()] param( [Parameter(Mandatory=$false, Position = 0)] - [string] $ExtractFilesTo="$PSScriptRoot\..\artifacts", + [string] $ExtractFilesTo="$PSScriptRoot\..\artifacts\build\AspNetCore\bin\Debug", [Parameter(Mandatory=$false, Position = 1)] - [string] $PackagePath="$PSScriptRoot\..\artifacts", + [string] $PackagePath="$PSScriptRoot\..\artifacts\build", [Parameter(Mandatory=$false)] [switch] $Rollback=$false, [Parameter(Mandatory=$false)] @@ -235,12 +235,12 @@ function Update-ANCM() { } } -function Update-File([string]$SourceFilePath, [string]$DestineFilePath) { +function Update-File([string]$SourceFilePath, [string]$DestinationFilePath) { $Source = $SourceFilePath - $Destine = $DestineFilePath + $Destination = $DestinationFilePath - $BackupFilePath = $Destine + ".ancm_backup" + $BackupFilePath = $Destination + ".ancm_backup" if ($Rollback) { $Source = $BackupFilePath @@ -266,12 +266,12 @@ function Update-File([string]$SourceFilePath, [string]$DestineFilePath) { if (-Not (Test-Path $BackupFilePath)) { Say (" Create a backup $BackupFilePath") - Copy-Item $Destine $BackupFilePath -Force + Copy-Item $Destination $BackupFilePath -Force - $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Destine) -DifferenceObject $(Get-Content $BackupFilePath)) + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Destination) -DifferenceObject $(Get-Content $BackupFilePath)) if (-not $fileMatched) { - throw ("$LogHeader File not matched!!! $Destine $BackupFilePath") + throw ("$LogHeader File not matched!!! $Destination $BackupFilePath") } } if (-Not (Test-Path $BackupFilePath)) @@ -279,31 +279,31 @@ function Update-File([string]$SourceFilePath, [string]$DestineFilePath) { throw ("$LogHeader Can't backup $Source to $BackupFilePath") } - # Copy file from Source to Destine if those files are different each other - if (-Not (Test-Path $Destine)) + # Copy file from Source to Destination if those files are different each other + if (-Not (Test-Path $Destination)) { - throw ("$LogHeader Can't find $Destine") + throw ("$LogHeader Can't find $Destination") } - $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destine)) + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destination)) if (-not $fileMatched) { Say (" Copying $Source to $Desting...") - Copy-Item $Source $Destine -Force + Copy-Item $Source $Destination -Force # check file is correctly copied - $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destine)) + $fileMatched = $null -eq (Compare-Object -ReferenceObject $(Get-Content $Source) -DifferenceObject $(Get-Content $Destination)) if (-not $fileMatched) { - throw ("$LogHeader File not matched!!! $Source $Destine") + throw ("$LogHeader File not matched!!! $Source $Destination") } else { - Say-Verbose ("$LogHeader File matched!!! $Source to $Destine") + Say-Verbose ("$LogHeader File matched!!! $Source to $Destination") } } else { - Say (" Skipping the file $Destine that is already identical to $Source ") + Say (" Skipping the file $Destination that is already identical to $Source ") } } From b9fda4964975ea3acd2bb665d2a81cb5726e386b Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 3 Oct 2016 14:49:16 -0700 Subject: [PATCH 014/107] allowing flow app_offline to remote client (#17) --- src/AspNetCore/Src/forwardinghandler.cxx | 37 +++++++++++++++++++++++- src/AspNetCore/version.h | 12 -------- 2 files changed, 36 insertions(+), 13 deletions(-) delete mode 100644 src/AspNetCore/version.h diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index f5a0ad00a4..92a72cde08 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1080,8 +1080,43 @@ VOID if (m_pApplication->AppOfflineFound() && m_pAppOfflineHtm != NULL) { + HTTP_DATA_CHUNK DataChunk; + PCSTR pszANCMHeader; + DWORD cchANCMHeader; + BOOL fCompletionExpected = FALSE; - HTTP_DATA_CHUNK DataChunk; + if (FAILED(m_pW3Context->GetServerVariable(STR_ANCM_CHILDREQUEST, + &pszANCMHeader, + &cchANCMHeader))) // first time failure + { + if (SUCCEEDED(hr = m_pW3Context->CloneContext( + CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, + &m_pChildRequestContext + )) && + SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( + STR_ANCM_CHILDREQUEST, + L"1")) && + SUCCEEDED(hr = m_pW3Context->ExecuteRequest( + TRUE, // fAsync + m_pChildRequestContext, + EXECUTE_FLAG_DISABLE_CUSTOM_ERROR, // by pass Custom Error module + NULL, // pHttpUser + &fCompletionExpected))) + { + if (!fCompletionExpected) + { + retVal = RQ_NOTIFICATION_CONTINUE; + } + else + { + retVal = RQ_NOTIFICATION_PENDING; + } + goto Finished; + } + // + // fail to create child request, fall back to default 502 error + // + } DataChunk.DataChunkType = HttpDataChunkFromMemory; DataChunk.FromMemory.pBuffer = (PVOID)m_pAppOfflineHtm->m_Contents.QueryStr(); diff --git a/src/AspNetCore/version.h b/src/AspNetCore/version.h deleted file mode 100644 index 9bb9d9d200..0000000000 --- a/src/AspNetCore/version.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - - -// This file is auto-generated - - -#define FileVersion 7,1,1968,0 -#define FileVersionStr "7.1.1968.0\0" -#define ProductVersion 7,1,1968,0 -#define ProductVersionStr "7.1.1968.0\0" -#define PlatformToolset "v140\0" From 64ab88fa661a1274fd3c99a3afc8ff59fd4a6ee7 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Tue, 4 Oct 2016 13:51:48 -0700 Subject: [PATCH 015/107] fix build warning (#18) --- src/AspNetCore/AspNetCore.vcxproj | 14 +++++++++----- src/AspNetCore/Src/precomp.hxx | 4 +++- src/AspNetCore/Src/serverprocess.cxx | 4 +++- src/IISLib/IISLib.vcxproj | 4 ++++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index bc85154a61..ce71ac6ea4 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -25,7 +25,7 @@ AspNetCoreModule AspNetCore aspnetcore - true + false @@ -80,6 +80,7 @@ precomp.hxx $(IntDir)$(TargetName).pch ..\IISLib;.\Inc + ProgramDatabase Windows @@ -97,6 +98,7 @@ precomp.hxx $(IntDir)$(TargetName).pch ..\IISLib;.\Inc + ProgramDatabase Windows @@ -108,17 +110,18 @@ Level3 - Create + NotUsing MaxSpeed true true WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) ..\IISLib;inc precomp.hxx + MultiThreadedDLL Windows - true + false true true Source.def @@ -128,17 +131,18 @@ Level3 - Create + NotUsing MaxSpeed true true WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) precomp.hxx ..\IISLib;inc + MultiThreadedDLL Windows - true + false true true Source.def diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index a8425be8df..8ba7f0270f 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once +#pragma warning( disable : 4091) // // System related headers @@ -133,4 +134,5 @@ extern PVOID g_pModuleId; extern BOOL g_fWebSocketSupported; extern BOOL g_fEnableReferenceCountTracing; extern DWORD g_dwActiveServerProcesses; -extern DWORD g_OptionalWinHttpFlags; \ No newline at end of file +extern DWORD g_OptionalWinHttpFlags; +#pragma warning( error : 4091) \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index de3ea301ca..b8e440470c 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -57,12 +57,14 @@ SERVER_PROCESS::Initialize( { 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 = diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj index 5f6181152e..8ff54a4c75 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -76,6 +76,7 @@ Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true + ProgramDatabase Windows @@ -90,6 +91,7 @@ Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true + ProgramDatabase Windows @@ -106,6 +108,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true + MultiThreadedDLL Windows @@ -124,6 +127,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true + MultiThreadedDLL Windows From ef194559122bb67297da9bc1d1807033f9d5f609 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Wed, 26 Oct 2016 13:44:02 -0700 Subject: [PATCH 016/107] sync up the code with IIS OOB branch (#21) --- src/AspNetCore/Inc/processmanager.h | 2 +- src/AspNetCore/Src/filewatcher.cxx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AspNetCore/Inc/processmanager.h b/src/AspNetCore/Inc/processmanager.h index 8cd7fd8e0f..b91e8e6bfb 100644 --- a/src/AspNetCore/Inc/processmanager.h +++ b/src/AspNetCore/Inc/processmanager.h @@ -160,7 +160,7 @@ private: m_ppServerProcessList[i] != NULL ) { // shutdown pServerProcess if not already shutdown. - m_ppServerProcessList[i]->StopProcess(); + m_ppServerProcessList[i]->SendSignal(); m_ppServerProcessList[i]->DereferenceServerProcess(); m_ppServerProcessList[i] = NULL; } diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx index 545d76bc63..bf147c15c4 100644 --- a/src/AspNetCore/Src/filewatcher.cxx +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -199,6 +199,7 @@ FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() } } +#pragma warning(disable:4100) HRESULT FILE_WATCHER_ENTRY::HandleChangeCompletion( @@ -301,11 +302,12 @@ Finished: return hr; } +#pragma warning( error : 4100 ) + HRESULT FILE_WATCHER_ENTRY::Monitor(VOID) { HRESULT hr = S_OK; - BOOL fRet = FALSE; DWORD cbRead; AcquireSRWLockExclusive(&_srwLock); From df997dcb3e504d3e1635f03533a5d3a2d6185305 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 26 Oct 2016 14:08:58 -0700 Subject: [PATCH 017/107] Update LICENSE information --- LICENSE.txt | 2 +- nuget/AspNetCore.nuspec | 2 +- src/AspNetCore/AspNetCore.vcxproj | 4 ++-- src/AspNetCore/Inc/application.h | 2 +- src/AspNetCore/Inc/applicationmanager.h | 4 ++-- src/AspNetCore/Inc/aspnetcoreconfig.h | 2 +- src/AspNetCore/Inc/aspnetcoreutils.h | 2 +- src/AspNetCore/Inc/debugutil.h | 2 +- src/AspNetCore/Inc/filewatcher.h | 2 +- src/AspNetCore/Inc/forwarderconnection.h | 2 +- src/AspNetCore/Inc/path.h | 2 +- src/AspNetCore/Inc/processmanager.h | 2 +- src/AspNetCore/Inc/protocolconfig.h | 2 +- src/AspNetCore/Inc/proxymodule.h | 2 +- src/AspNetCore/Inc/resource.h | 2 +- src/AspNetCore/Inc/responseheaderhash.h | 2 +- src/AspNetCore/Inc/serverprocess.h | 2 +- src/AspNetCore/Inc/sttimer.h | 2 +- src/AspNetCore/Inc/websockethandler.h | 2 +- src/AspNetCore/Inc/winhttphelper.h | 2 +- src/AspNetCore/Src/application.cxx | 2 +- src/AspNetCore/Src/applicationmanager.cxx | 2 +- src/AspNetCore/Src/aspnetcoreconfig.cxx | 2 +- src/AspNetCore/Src/aspnetcoreutils.cxx | 2 +- src/AspNetCore/Src/dllmain.cpp | 2 +- src/AspNetCore/Src/filewatcher.cxx | 2 +- src/AspNetCore/Src/forwarderconnection.cxx | 2 +- src/AspNetCore/Src/forwardinghandler.cxx | 2 +- src/AspNetCore/Src/path.cxx | 2 +- src/AspNetCore/Src/precomp.hxx | 2 +- src/AspNetCore/Src/processmanager.cxx | 2 +- src/AspNetCore/Src/protocolconfig.cxx | 2 +- src/AspNetCore/Src/proxymodule.cxx | 2 +- src/AspNetCore/Src/responseheaderhash.cxx | 2 +- src/AspNetCore/Src/serverprocess.cxx | 2 +- src/AspNetCore/Src/websockethandler.cxx | 2 +- src/AspNetCore/Src/winhttphelper.cxx | 2 +- src/IISLib/acache.cxx | 2 +- src/IISLib/acache.h | 2 +- src/IISLib/ahutil.cpp | 2 +- src/IISLib/ahutil.h | 2 +- src/IISLib/base64.cpp | 2 +- src/IISLib/base64.h | 2 +- src/IISLib/buffer.h | 2 +- src/IISLib/datetime.h | 2 +- src/IISLib/dbgutil.h | 2 +- src/IISLib/hashfn.h | 2 +- src/IISLib/hashtable.h | 2 +- src/IISLib/listentry.h | 2 +- src/IISLib/macros.h | 2 +- src/IISLib/multisz.cpp | 2 +- src/IISLib/multisz.h | 2 +- src/IISLib/multisza.cpp | 2 +- src/IISLib/multisza.h | 2 +- src/IISLib/ntassert.h | 2 +- src/IISLib/percpu.h | 2 +- src/IISLib/precomp.h | 2 +- src/IISLib/prime.h | 2 +- src/IISLib/pudebug.h | 2 +- src/IISLib/reftrace.c | 2 +- src/IISLib/reftrace.h | 2 +- src/IISLib/rwlock.h | 2 +- src/IISLib/stringa.cpp | 2 +- src/IISLib/stringa.h | 2 +- src/IISLib/stringu.cpp | 2 +- src/IISLib/stringu.h | 2 +- src/IISLib/tracelog.c | 2 +- src/IISLib/tracelog.h | 2 +- src/IISLib/treehash.h | 2 +- src/IISLib/util.cxx | 2 +- 70 files changed, 72 insertions(+), 72 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 3293bc0ea5..5001ba1910 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ ASP.NET Core Module -Copyright (c) Microsoft Corporation +Copyright (c) .NET Foundation All rights reserved. MIT License diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index f19743a8c5..6a77bef462 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -7,7 +7,7 @@ Microsoft Microsoft http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm - © Microsoft Corporation. All rights reserved. + © .NET Foundation. All rights reserved. http://www.asp.net/ true ASP.NET Core Module diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index ce71ac6ea4..423cf6e188 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -194,8 +194,8 @@ 0 - - + + diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h index 058bf8c731..b8b6d548bc 100644 --- a/src/AspNetCore/Inc/application.h +++ b/src/AspNetCore/Inc/application.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index 41163a66b6..ee2b0542dc 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once @@ -138,7 +138,7 @@ private:
\

Troubleshooting steps:

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

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

\ diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h index 00da93e519..b05491b4ec 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/aspnetcoreutils.h b/src/AspNetCore/Inc/aspnetcoreutils.h index ef5739c93e..2f54e180a3 100644 --- a/src/AspNetCore/Inc/aspnetcoreutils.h +++ b/src/AspNetCore/Inc/aspnetcoreutils.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/debugutil.h b/src/AspNetCore/Inc/debugutil.h index 7378462efb..aee17b4fba 100644 --- a/src/AspNetCore/Inc/debugutil.h +++ b/src/AspNetCore/Inc/debugutil.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/filewatcher.h b/src/AspNetCore/Inc/filewatcher.h index 16d3942a2f..6ae853708e 100644 --- a/src/AspNetCore/Inc/filewatcher.h +++ b/src/AspNetCore/Inc/filewatcher.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/forwarderconnection.h b/src/AspNetCore/Inc/forwarderconnection.h index a3f5dfdabe..232e239888 100644 --- a/src/AspNetCore/Inc/forwarderconnection.h +++ b/src/AspNetCore/Inc/forwarderconnection.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/path.h b/src/AspNetCore/Inc/path.h index 05545acfd5..a553ccfe05 100644 --- a/src/AspNetCore/Inc/path.h +++ b/src/AspNetCore/Inc/path.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/processmanager.h b/src/AspNetCore/Inc/processmanager.h index b91e8e6bfb..1cc8a6ab54 100644 --- a/src/AspNetCore/Inc/processmanager.h +++ b/src/AspNetCore/Inc/processmanager.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/protocolconfig.h b/src/AspNetCore/Inc/protocolconfig.h index d9d730c544..f7d915d6a9 100644 --- a/src/AspNetCore/Inc/protocolconfig.h +++ b/src/AspNetCore/Inc/protocolconfig.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/proxymodule.h b/src/AspNetCore/Inc/proxymodule.h index 7cf3486fb4..f05438c5c1 100644 --- a/src/AspNetCore/Inc/proxymodule.h +++ b/src/AspNetCore/Inc/proxymodule.h @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index 066c9792f5..8b2b9f9844 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/responseheaderhash.h b/src/AspNetCore/Inc/responseheaderhash.h index 7ef127366b..b7781e45b9 100644 --- a/src/AspNetCore/Inc/responseheaderhash.h +++ b/src/AspNetCore/Inc/responseheaderhash.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index 2132a3a21b..487f1767ea 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/sttimer.h b/src/AspNetCore/Inc/sttimer.h index 917ee7ecf9..ebed8510a1 100644 --- a/src/AspNetCore/Inc/sttimer.h +++ b/src/AspNetCore/Inc/sttimer.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #ifndef _STTIMER_H diff --git a/src/AspNetCore/Inc/websockethandler.h b/src/AspNetCore/Inc/websockethandler.h index 70d3139b6d..c8abce55e3 100644 --- a/src/AspNetCore/Inc/websockethandler.h +++ b/src/AspNetCore/Inc/websockethandler.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Inc/winhttphelper.h b/src/AspNetCore/Inc/winhttphelper.h index b301a76cf2..d583f6fb10 100644 --- a/src/AspNetCore/Inc/winhttphelper.h +++ b/src/AspNetCore/Inc/winhttphelper.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Src/application.cxx b/src/AspNetCore/Src/application.cxx index ff2a82d3f3..0312c3f32f 100644 --- a/src/AspNetCore/Src/application.cxx +++ b/src/AspNetCore/Src/application.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx index b99d645e41..6d288af7b3 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index e4633806ac..6e65fb555a 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/aspnetcoreutils.cxx b/src/AspNetCore/Src/aspnetcoreutils.cxx index 5aa00994a6..3d2f9612aa 100644 --- a/src/AspNetCore/Src/aspnetcoreutils.cxx +++ b/src/AspNetCore/Src/aspnetcoreutils.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index f34057dbb5..d71c296888 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx index bf147c15c4..c3d66fd5c3 100644 --- a/src/AspNetCore/Src/filewatcher.cxx +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/forwarderconnection.cxx b/src/AspNetCore/Src/forwarderconnection.cxx index 9e01b0a065..f3b04abbc8 100644 --- a/src/AspNetCore/Src/forwarderconnection.cxx +++ b/src/AspNetCore/Src/forwarderconnection.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 92a72cde08..6e4f37c0d2 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/path.cxx b/src/AspNetCore/Src/path.cxx index 71ec779eca..0c6bb0fc5f 100644 --- a/src/AspNetCore/Src/path.cxx +++ b/src/AspNetCore/Src/path.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index 8ba7f0270f..c1c9eef0a1 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/AspNetCore/Src/processmanager.cxx b/src/AspNetCore/Src/processmanager.cxx index 7a8b20aea8..35fd35d6e4 100644 --- a/src/AspNetCore/Src/processmanager.cxx +++ b/src/AspNetCore/Src/processmanager.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/protocolconfig.cxx b/src/AspNetCore/Src/protocolconfig.cxx index 83e2648925..85fd86aa61 100644 --- a/src/AspNetCore/Src/protocolconfig.cxx +++ b/src/AspNetCore/Src/protocolconfig.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index 1e59d18a89..d19f4488ff 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/responseheaderhash.cxx b/src/AspNetCore/Src/responseheaderhash.cxx index 161095042c..02653c29b7 100644 --- a/src/AspNetCore/Src/responseheaderhash.cxx +++ b/src/AspNetCore/Src/responseheaderhash.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index b8e440470c..28e6ded07c 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/AspNetCore/Src/websockethandler.cxx b/src/AspNetCore/Src/websockethandler.cxx index 54670607c2..f95a98c0e2 100644 --- a/src/AspNetCore/Src/websockethandler.cxx +++ b/src/AspNetCore/Src/websockethandler.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. /*++ diff --git a/src/AspNetCore/Src/winhttphelper.cxx b/src/AspNetCore/Src/winhttphelper.cxx index 8407c45830..6985c6aed1 100644 --- a/src/AspNetCore/Src/winhttphelper.cxx +++ b/src/AspNetCore/Src/winhttphelper.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/IISLib/acache.cxx b/src/IISLib/acache.cxx index c602c5fafd..d68813edbc 100644 --- a/src/IISLib/acache.cxx +++ b/src/IISLib/acache.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/IISLib/acache.h b/src/IISLib/acache.h index 49a9c8a996..048df2b507 100644 --- a/src/IISLib/acache.h +++ b/src/IISLib/acache.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/ahutil.cpp b/src/IISLib/ahutil.cpp index 9d3f0e4a64..dae2027f33 100644 --- a/src/IISLib/ahutil.cpp +++ b/src/IISLib/ahutil.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/IISLib/ahutil.h b/src/IISLib/ahutil.h index ab523f4f98..5694b21b7e 100644 --- a/src/IISLib/ahutil.h +++ b/src/IISLib/ahutil.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/base64.cpp b/src/IISLib/base64.cpp index f5088552f2..b8b6a0bf74 100644 --- a/src/IISLib/base64.cpp +++ b/src/IISLib/base64.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/IISLib/base64.h b/src/IISLib/base64.h index cc2f9ec509..469b074d73 100644 --- a/src/IISLib/base64.h +++ b/src/IISLib/base64.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/buffer.h b/src/IISLib/buffer.h index 46e22da7f8..1d68155387 100644 --- a/src/IISLib/buffer.h +++ b/src/IISLib/buffer.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/datetime.h b/src/IISLib/datetime.h index f48acf0043..fd09b7a6a0 100644 --- a/src/IISLib/datetime.h +++ b/src/IISLib/datetime.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/dbgutil.h b/src/IISLib/dbgutil.h index e3db88530d..45c26777a9 100644 --- a/src/IISLib/dbgutil.h +++ b/src/IISLib/dbgutil.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/hashfn.h b/src/IISLib/hashfn.h index 00d4f1c5d7..a7bfeda2cf 100644 --- a/src/IISLib/hashfn.h +++ b/src/IISLib/hashfn.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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__ diff --git a/src/IISLib/hashtable.h b/src/IISLib/hashtable.h index e6c75823f2..9319e5643d 100644 --- a/src/IISLib/hashtable.h +++ b/src/IISLib/hashtable.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/listentry.h b/src/IISLib/listentry.h index 469dba1a2e..80b70e97a9 100644 --- a/src/IISLib/listentry.h +++ b/src/IISLib/listentry.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/macros.h b/src/IISLib/macros.h index 2e66540428..960f663a98 100644 --- a/src/IISLib/macros.h +++ b/src/IISLib/macros.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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 diff --git a/src/IISLib/multisz.cpp b/src/IISLib/multisz.cpp index d7ae9f3fdb..775ec4cd0c 100644 --- a/src/IISLib/multisz.cpp +++ b/src/IISLib/multisz.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. diff --git a/src/IISLib/multisz.h b/src/IISLib/multisz.h index 6ea1787c73..f65c151d4f 100644 --- a/src/IISLib/multisz.h +++ b/src/IISLib/multisz.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/multisza.cpp b/src/IISLib/multisza.cpp index 07479fac31..54717edf05 100644 --- a/src/IISLib/multisza.cpp +++ b/src/IISLib/multisza.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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) diff --git a/src/IISLib/multisza.h b/src/IISLib/multisza.h index 49a264fa9c..d575ec9423 100644 --- a/src/IISLib/multisza.h +++ b/src/IISLib/multisza.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/ntassert.h b/src/IISLib/ntassert.h index 8e7f6ab660..6d2f3b9a30 100644 --- a/src/IISLib/ntassert.h +++ b/src/IISLib/ntassert.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/percpu.h b/src/IISLib/percpu.h index f3cc02ae27..5d3c563935 100644 --- a/src/IISLib/percpu.h +++ b/src/IISLib/percpu.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/precomp.h b/src/IISLib/precomp.h index b723dba261..0dc0cd7b52 100644 --- a/src/IISLib/precomp.h +++ b/src/IISLib/precomp.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #include diff --git a/src/IISLib/prime.h b/src/IISLib/prime.h index fb287fcd8a..6a6a88ed78 100644 --- a/src/IISLib/prime.h +++ b/src/IISLib/prime.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/pudebug.h b/src/IISLib/pudebug.h index cada8c84ea..7b0e35da0f 100644 --- a/src/IISLib/pudebug.h +++ b/src/IISLib/pudebug.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/reftrace.c b/src/IISLib/reftrace.c index ba75aa5bd1..c1b2e13a6e 100644 --- a/src/IISLib/reftrace.c +++ b/src/IISLib/reftrace.c @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #include diff --git a/src/IISLib/reftrace.h b/src/IISLib/reftrace.h index 46f5ce26fd..e90ca0444a 100644 --- a/src/IISLib/reftrace.h +++ b/src/IISLib/reftrace.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/rwlock.h b/src/IISLib/rwlock.h index 3d4f5c5537..dc7ccf834b 100644 --- a/src/IISLib/rwlock.h +++ b/src/IISLib/rwlock.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/stringa.cpp b/src/IISLib/stringa.cpp index 16e34a21ef..29da773bca 100644 --- a/src/IISLib/stringa.cpp +++ b/src/IISLib/stringa.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" diff --git a/src/IISLib/stringa.h b/src/IISLib/stringa.h index 01c3c5254b..39737f4a69 100644 --- a/src/IISLib/stringa.h +++ b/src/IISLib/stringa.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/stringu.cpp b/src/IISLib/stringu.cpp index 4d92a4d023..15da79a7fe 100644 --- a/src/IISLib/stringu.cpp +++ b/src/IISLib/stringu.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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) diff --git a/src/IISLib/stringu.h b/src/IISLib/stringu.h index 66c760db7e..6f27c5421d 100644 --- a/src/IISLib/stringu.h +++ b/src/IISLib/stringu.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/tracelog.c b/src/IISLib/tracelog.c index e85861c7c1..f7b2da5e43 100644 --- a/src/IISLib/tracelog.c +++ b/src/IISLib/tracelog.c @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #include diff --git a/src/IISLib/tracelog.h b/src/IISLib/tracelog.h index feed945d54..ed34bcffc9 100644 --- a/src/IISLib/tracelog.h +++ b/src/IISLib/tracelog.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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_ diff --git a/src/IISLib/treehash.h b/src/IISLib/treehash.h index e09c9845e1..baa50726ce 100644 --- a/src/IISLib/treehash.h +++ b/src/IISLib/treehash.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once diff --git a/src/IISLib/util.cxx b/src/IISLib/util.cxx index f1a7f33935..bde325025e 100644 --- a/src/IISLib/util.cxx +++ b/src/IISLib/util.cxx @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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" From 93ace1b84bd79382233cb39b858611d2db05552e Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 26 Oct 2016 14:29:08 -0700 Subject: [PATCH 018/107] Add Contributing information --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 4bf6632c3f..16466f1898 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,18 @@ If chose to install from the command prompt, you can run the following command. # Build in Release Configuration .\build.cmd /property:configuration=release ``` + +## Contributions + +Check out the [contributing](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) +page to see the best places to log issues and start discussions. + +This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/) +to clarify expected behavior in our community. +For more information see the [.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). + +### .NET Foundation + +This project is supported by the [.NET Foundation](http://www.dotnetfoundation.org). + + From ae727d64a9e129d2311ed5e2e86d1ba62a5fa8d6 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Wed, 26 Oct 2016 16:40:19 -0700 Subject: [PATCH 019/107] Add download link --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 16466f1898..53b75fd97b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ The ASP.NET Core Module is an IIS Module which is responsible for process management of ASP.NET Core http listeners and to proxy requests to the process that it manages. +## Installing the latest ASP.NET Core Module +The ASP.NET Core Module for IIS can be installed on servers without installing the .NET Core runtime. You can download the [Windows (Server Hosting) installer](https://go.microsoft.com/fwlink/?linkid=832756) and run the following command from an Administrator command prompt: +``DotNetCore.1.1.0.Preview1-WindowsHosting.exe OPT_INSTALL_LTS_REDIST=0 OPT_INSTALL_FTS_REDIST=0`` + ## Pre-requisites for building ### Windows 8.1+ or Windows Server 2012 R2+ From 4569d6d6d53aafd355da6acc91056f84e9f284a7 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 31 Oct 2016 13:03:13 -0700 Subject: [PATCH 020/107] fix issue #26 AspNetCoreModule needs to abort failed responses (#34) --- src/AspNetCore/Inc/forwardinghandler.h | 1 + src/AspNetCore/Src/dllmain.cpp | 2 +- src/AspNetCore/Src/forwardinghandler.cxx | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h index 06fde91cee..bc655526be 100644 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -27,6 +27,7 @@ enum FORWARDING_REQUEST_STATUS FORWARDER_SENDING_REQUEST, FORWARDER_RECEIVING_RESPONSE, FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, + FORWARDER_RESET_CONNECTION, FORWARDER_DONE }; diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index d71c296888..c69c435682 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -51,7 +51,7 @@ VOID HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\AspNetCore Module\\Parameters", + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", 0, KEY_READ, &hKey) == NO_ERROR) diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 6e4f37c0d2..0fe0ea9e2c 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1591,6 +1591,12 @@ Return Value: retVal = RQ_NOTIFICATION_PENDING; goto Finished; } + else if (m_RequestStatus == FORWARDER_RESET_CONNECTION) + { + hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); + goto Failure; + + } // // Begins normal completion handling. There is already a shared acquired @@ -2185,6 +2191,12 @@ None Failure: + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + m_RequestStatus = FORWARDER_RESET_CONNECTION; + goto Finished; + } + m_RequestStatus = FORWARDER_DONE; pResponse->DisableKernelCache(); From d870f75eec0a6dfb6ca3b16aa18094e728973527 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 7 Nov 2016 15:54:50 -0800 Subject: [PATCH 021/107] Fixed some minor issues --- tools/installancm.ps1 | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/installancm.ps1 b/tools/installancm.ps1 index 5005d5156b..df664e2e87 100644 --- a/tools/installancm.ps1 +++ b/tools/installancm.ps1 @@ -253,10 +253,19 @@ function Update-File([string]$SourceFilePath, [string]$DestinationFilePath) { { if (Test-Path $BackupFilePath) { - Say (" Delete the existing backup file: $BackupFilePath") - Remove-Item $BackupFilePath -Force -Confirm:$false + $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 (Test-Path $BackupFilePath) + if ($backupFileRemoved -and (Test-Path $BackupFilePath)) { throw ("$LogHeader Can't delete $BackupFilePath") } @@ -303,7 +312,7 @@ function Update-File([string]$SourceFilePath, [string]$DestinationFilePath) { } else { - Say (" Skipping the file $Destination that is already identical to $Source ") + Say (" Skipping $Destination that is already identical to $Source ") } } From 40ccbb3940b51ed380cddd6cc5867257818fd712 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 8 Nov 2016 15:37:11 -0800 Subject: [PATCH 022/107] Fix the non-standard nuget package --- nuget/AspNetCore.nuspec | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index 6a77bef462..d0e81a6730 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -1,5 +1,5 @@ - + Microsoft.AspNetCore.AspNetCoreModule Microsoft ASP.NET Core Module @@ -7,14 +7,17 @@ Microsoft Microsoft http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm - © .NET Foundation. All rights reserved. + © Microsoft Corporation. All rights reserved. http://www.asp.net/ true ASP.NET Core Module en-US + Microsoft.AspNetCore.AspNetCoreModule - - + + + + \ No newline at end of file From e119603cddbb57e1d09db054ef93ac71b4256ba8 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 8 Nov 2016 15:45:54 -0800 Subject: [PATCH 023/107] Merge the change --- nuget/AspNetCore.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index d0e81a6730..d27be34c78 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -1,5 +1,5 @@ - + Microsoft.AspNetCore.AspNetCoreModule Microsoft ASP.NET Core Module @@ -7,7 +7,7 @@ Microsoft Microsoft http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm - © Microsoft Corporation. All rights reserved. + © .NET Foundation. All rights reserved. http://www.asp.net/ true ASP.NET Core Module From 1bbc32f26e51386e401bb7ecb673400a6e1ece1c Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 8 Nov 2016 16:01:30 -0800 Subject: [PATCH 024/107] Fixed a build issue regarding directory path --- nuget/AspNetCore.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index d27be34c78..e2ab6009a8 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -15,8 +15,8 @@ Microsoft.AspNetCore.AspNetCoreModule - - + + From 5788765983a72c2550a1ce0ba82c87ec4c37e030 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 8 Nov 2016 16:57:50 -0800 Subject: [PATCH 025/107] Fixed after the ancm nuget package change --- nuget/AspNetCore.nuspec | 1 + tools/installancm.ps1 | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index e2ab6009a8..25f72175f8 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -17,6 +17,7 @@ + diff --git a/tools/installancm.ps1 b/tools/installancm.ps1 index df664e2e87..b54a830d0e 100644 --- a/tools/installancm.ps1 +++ b/tools/installancm.ps1 @@ -369,6 +369,8 @@ else { $ExtractFilesRootPath = $ExtractFilesTo } + +# Try with solution output path $aspnetCorex64From = $ExtractFilesRootPath + "\x64\aspnetcore.dll" $aspnetCoreWin32From = $ExtractFilesRootPath + "\Win32\aspnetcore.dll" $aspnetCoreSchemax64From = $ExtractFilesRootPath + "\x64\aspnetcore_schema.xml" @@ -383,6 +385,24 @@ $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 From 4e96bbdfe501285bb57561e5b56ce43368019a32 Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 17 Nov 2016 10:37:10 -0800 Subject: [PATCH 026/107] Update nupkg layout to include both architectures together. --- nuget/AspNetCore.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index 25f72175f8..3392bdd549 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -1,4 +1,4 @@ - + Microsoft.AspNetCore.AspNetCoreModule @@ -15,8 +15,8 @@ Microsoft.AspNetCore.AspNetCoreModule - - + + From a4963aa6da4b6b840445ec7bf53803f70b4ea79b Mon Sep 17 00:00:00 2001 From: pan-wang Date: Wed, 4 Jan 2017 15:57:09 -0800 Subject: [PATCH 027/107] fix issue #50 app_offline.htm is case sensitive (#54) --- src/AspNetCore/Src/filewatcher.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx index c3d66fd5c3..96e321ef60 100644 --- a/src/AspNetCore/Src/filewatcher.cxx +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -266,7 +266,7 @@ HRESULT // // check whether the monitored file got changed // - if (wcsncmp(pNotificationInfo->FileName, + if (_wcsnicmp(pNotificationInfo->FileName, _strFileName.QueryStr(), pNotificationInfo->FileNameLength/sizeof(WCHAR)) == 0) { From 839437ef8cb4aa3df8ad3a9c85a0033bc0ebf21f Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 6 Jan 2017 11:08:43 -0800 Subject: [PATCH 028/107] Fixing issue #44 (#56) --- .gitignore | 4 +++- src/AspNetCore/Src/dllmain.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 50c65f5468..e3bd9b7abe 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,6 @@ src/*/x64/Release/ src/AspNetCore/aspnetcore_msg.h src/AspNetCore/aspnetcore_msg.rc src/AspNetCore/version.h -.build \ No newline at end of file +.build + +AspNetCoreModule.VC.db \ No newline at end of file diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index c69c435682..0797c3efe7 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -113,6 +113,7 @@ VOID } HRESULT +__stdcall RegisterModule( DWORD dwServerVersion, IHttpModuleRegistrationInfo * pModuleInfo, From 87f808cc9d6904f01da2f97996203a243ccb460c Mon Sep 17 00:00:00 2001 From: pan-wang Date: Tue, 17 Jan 2017 13:43:37 -0800 Subject: [PATCH 029/107] fixing the AV for graceful shutdown on Win7, set forwardWindowsAuthToken default to true and other minor issues --- src/AspNetCore/Inc/applicationmanager.h | 2 +- src/AspNetCore/Inc/serverprocess.h | 1 - src/AspNetCore/Src/processmanager.cxx | 1 - src/AspNetCore/Src/serverprocess.cxx | 83 +++++++++++++------------ src/AspNetCore/aspnetcore_schema.xml | 2 +- 5 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index ee2b0542dc..c3767ec076 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -141,7 +141,7 @@ private:
  • Enable logging the application process' stdout messages
  • \
  • Attach a debugger to the application process and inspect
  • \

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

    \ + https://go.microsoft.com/fwlink/?LinkID=808681 \
    \
    \ ") diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index 487f1767ea..1c45602082 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -237,7 +237,6 @@ private: DWORD m_dwProcessId; DWORD m_dwListeningProcessId; STRA m_straGuid; - HANDLE m_CancelEvent; // // m_hProcessHandle is the handle to process this object creates. diff --git a/src/AspNetCore/Src/processmanager.cxx b/src/AspNetCore/Src/processmanager.cxx index 35fd35d6e4..26f96becc0 100644 --- a/src/AspNetCore/Src/processmanager.cxx +++ b/src/AspNetCore/Src/processmanager.cxx @@ -232,7 +232,6 @@ PROCESS_MANAGER::GetProcess( goto Finished; } - this->ReferenceProcessManager(); hr = m_ppServerProcessList[dwProcessIndex]->Initialize( this, pConfig->QueryProcessPath(), diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 28e6ded07c..6b1e0f4af6 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -29,6 +29,8 @@ SERVER_PROCESS::Initialize( m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS; m_fStdoutLogEnabled = fStdoutLogEnabled; + m_pProcessManager->ReferenceProcessManager(); + hr = m_ProcessPath.Copy(*pszProcessExePath); if (FAILED(hr)) { @@ -1208,7 +1210,7 @@ Finished: return hr; } -// send ctrl-c signnal to the process to let it graceful shutdown +// send ctrl-c signnal to the process to let it gracefully shutdown // if the process cannot shutdown within given time, terminate it // todo: allow user to config this shutdown timeout @@ -1217,7 +1219,7 @@ SERVER_PROCESS::SendSignal( VOID ) { - HANDLE hProc; + HANDLE hProc = INVALID_HANDLE_VALUE; BOOL fIsSuccess = FALSE; LPCWSTR apsz[1]; STACK_STRU(strEventMsg, 256); @@ -1225,54 +1227,61 @@ SERVER_PROCESS::SendSignal( hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); if (hProc != INVALID_HANDLE_VALUE) { - fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); + fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); if (!fIsSuccess) { if (AttachConsole(m_dwProcessId)) { - fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); + fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); FreeConsole(); - CloseHandle(m_hProcessHandle); - m_hProcessHandle = INVALID_HANDLE_VALUE; - } - - if (!fIsSuccess) - { - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, - m_dwProcessId ))) - { - apsz[0] = strEventMsg.QueryStr(); - // log a warning for ungraceful shutdown - if (FORWARDING_HANDLER::QueryEventLog() != NULL) - { - ReportEventW(FORWARDING_HANDLER::QueryEventLog(), - EVENTLOG_INFORMATION_TYPE, - 0, - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, - NULL, - 1, - 0, - apsz, - NULL); - } - } } + } + } + + if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) + { + //Backend process will be terminated, remove the waitcallback + if (m_hProcessWaitHandle != NULL) + { + UnregisterWait(m_hProcessWaitHandle); + m_hProcessWaitHandle = NULL; } - if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) + // cannot gracefully shutdown or timeout, terminate the process + TerminateProcess(m_hProcessHandle, 0); + + // log a warning for ungraceful shutdown + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, + m_dwProcessId))) { - // cannot gracefule shutdown or timeout - // terminate the process - TerminateProcess(m_hProcessHandle, 0); + 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); + } } } + if (hProc != INVALID_HANDLE_VALUE) { CloseHandle(hProc); hProc = INVALID_HANDLE_VALUE; } + if (m_hProcessHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hProcessHandle); + m_hProcessHandle = INVALID_HANDLE_VALUE; + } } // @@ -1640,7 +1649,6 @@ Finished: SERVER_PROCESS::SERVER_PROCESS() : m_cRefs( 1 ), - m_CancelEvent( NULL ), m_hProcessHandle( NULL ), m_hProcessWaitHandle( NULL ), m_dwProcessId( 0 ), @@ -1681,13 +1689,6 @@ SERVER_PROCESS::~SERVER_PROCESS() m_hProcessWaitHandle = NULL; } - if( m_CancelEvent != NULL ) - { - SetEvent( m_CancelEvent ); - CloseHandle( m_CancelEvent ); - m_CancelEvent = NULL; - } - for(INT i=0;i - + From 66b98a1c042c376d777bf0b3a0baa69c583e628f Mon Sep 17 00:00:00 2001 From: pan-wang Date: Tue, 17 Jan 2017 15:37:20 -0800 Subject: [PATCH 030/107] update 502.5 error page --- src/AspNetCore/Inc/applicationmanager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index c3767ec076..f254c8db5f 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -141,7 +141,7 @@ private:
  • 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

    \ + https://go.microsoft.com/fwlink/?LinkID=808681 \
    \ \ ") From 044648a21346d3c6782c13a4fcc4854c8782f7e2 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 23 Jan 2017 15:33:17 -0800 Subject: [PATCH 031/107] fixing graceful shutdown on Win7 and IISExpress --- src/AspNetCore/Inc/serverprocess.h | 2 +- src/AspNetCore/Src/serverprocess.cxx | 970 ++++++++++++++------------- 2 files changed, 494 insertions(+), 478 deletions(-) diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index 1c45602082..458b13575c 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -219,7 +219,7 @@ private: STRU m_struFullLogFile; STTIMER m_Timer; HANDLE m_hStdoutHandle; - volatile BOOL m_fStopping; + volatile LONG m_lStopping; volatile BOOL m_fReady; CRITICAL_SECTION m_csLock; SOCKET m_socket; diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 6b1e0f4af6..20faa4dde6 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -60,7 +60,7 @@ SERVER_PROCESS::Initialize( m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES NULL); // LPCTSTR lpName #pragma warning( disable : 4312) - // 0xdeadbeef is used by Antares + // 0xdeadbeef is used by Antares if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) { m_hJobObject = NULL; @@ -94,8 +94,8 @@ SERVER_PROCESS::StartProcess( ) { HRESULT hr = S_OK; - PROCESS_INFORMATION processInformation = {0}; - STARTUPINFOW startupInfo = {0}; + PROCESS_INFORMATION processInformation = { 0 }; + STARTUPINFOW startupInfo = { 0 }; WCHAR *pszCommandLine = NULL; DWORD dwCommandLineLen = 0; DWORD dwNumDigitsInPort = 0; @@ -105,13 +105,13 @@ SERVER_PROCESS::StartProcess( DWORD dwCreationFlags = 0; BOOL fReady = FALSE; DWORD dwTickCount = 0; - STACK_STRU( strAspNetCorePortEnvVar, 32); - STACK_STRU( strAspNetCoreDebugPortEnvVar, 32); + STACK_STRU(strAspNetCorePortEnvVar, 32); + STACK_STRU(strAspNetCoreDebugPortEnvVar, 32); MULTISZ mszNewEnvironment; MULTISZ mszEnvCopy; DWORD cChildProcess = 0; DWORD dwTimeDifference = 0; - STACK_STRU( strEventMsg, 256); + STACK_STRU(strEventMsg, 256); BOOL fDebugPortEnvSet = FALSE; BOOL fReplacedEnv = FALSE; BOOL fPortInUse = FALSE; @@ -164,8 +164,8 @@ SERVER_PROCESS::StartProcess( fRpcStringAllocd = true; - hr = m_straGuid.Copy( pszLogUuid ); - if(FAILED(hr)) + hr = m_straGuid.Copy(pszLogUuid); + if (FAILED(hr)) { goto Finished; } @@ -174,7 +174,7 @@ SERVER_PROCESS::StartProcess( // Generate random port that the new process will listen on. // - if(g_fNsiApiNotSupported) + if (g_fNsiApiNotSupported) { m_dwPort = GenerateRandomPort(); } @@ -191,18 +191,18 @@ SERVER_PROCESS::StartProcess( m_dwPort = GenerateRandomPort(); hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fPortInUse); - } while( fPortInUse && ++cRetry < MAX_RETRY ); + } while (fPortInUse && ++cRetry < MAX_RETRY); - if( cRetry > MAX_RETRY ) + if (cRetry > MAX_RETRY) { hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); goto Finished; } } - dwNumDigitsInPort = GetNumberOfDigits( m_dwPort ); - hr = strAspNetCorePortEnvVar.SafeSnwprintf( L"%s=%u", ASPNETCORE_PORT_STR, m_dwPort ); - if( FAILED ( hr ) ) + dwNumDigitsInPort = GetNumberOfDigits(m_dwPort); + hr = strAspNetCorePortEnvVar.SafeSnwprintf(L"%s=%u", ASPNETCORE_PORT_STR, m_dwPort); + if (FAILED(hr)) { goto Finished; } @@ -213,31 +213,31 @@ SERVER_PROCESS::StartProcess( // in the aspNetCore config. // - if(g_fNsiApiNotSupported) + if (g_fNsiApiNotSupported) { - while((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); + while ((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); } else { DWORD cRetry = 0; do { - while((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); + while ((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); hr = CheckIfServerIsUp(m_dwDebugPort, &dwActualProcessId, &fPortInUse); - } while( fPortInUse && ++cRetry < MAX_RETRY ); + } while (fPortInUse && ++cRetry < MAX_RETRY); - if( cRetry > MAX_RETRY ) + if (cRetry > MAX_RETRY) { hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); goto Finished; } } - dwNumDigitsInDebugPort = GetNumberOfDigits( m_dwDebugPort ); - hr = strAspNetCoreDebugPortEnvVar.SafeSnwprintf( L"%s=%u", - ASPNETCORE_DEBUG_PORT_STR, - m_dwDebugPort ); - if( FAILED ( hr ) ) + dwNumDigitsInDebugPort = GetNumberOfDigits(m_dwDebugPort); + hr = strAspNetCoreDebugPortEnvVar.SafeSnwprintf(L"%s=%u", + ASPNETCORE_DEBUG_PORT_STR, + m_dwDebugPort); + if (FAILED(hr)) { goto Finished; } @@ -246,7 +246,7 @@ SERVER_PROCESS::StartProcess( // Create environment for new process // - struApplicationId.Copy( L"ASPNETCORE_APPL_PATH=" ); + struApplicationId.Copy(L"ASPNETCORE_APPL_PATH="); // let's find the app path. IIS does not support nested sites // we can seek for the fourth '/' if it exits @@ -273,12 +273,12 @@ SERVER_PROCESS::StartProcess( struApplicationId.Append(L"/"); } - mszNewEnvironment.Append( struApplicationId ); + mszNewEnvironment.Append(struApplicationId); - struGuidEnv.Copy( L"ASPNETCORE_TOKEN=" ); - struGuidEnv.AppendA( m_straGuid.QueryStr(), m_straGuid.QueryCCH() ); + struGuidEnv.Copy(L"ASPNETCORE_TOKEN="); + struGuidEnv.AppendA(m_straGuid.QueryStr(), m_straGuid.QueryCCH()); - mszNewEnvironment.Append( struGuidEnv ); + mszNewEnvironment.Append(struGuidEnv); pszRootApplicationPath = context->GetApplication()->GetApplicationPhysicalPath(); @@ -287,8 +287,8 @@ SERVER_PROCESS::StartProcess( // dwCommandLineLen = (DWORD)wcslen(pszRootApplicationPath) + m_ProcessPath.QueryCCH() + m_Arguments.QueryCCH() + 4; - pszCommandLine = new WCHAR[ dwCommandLineLen ]; - if( pszCommandLine == NULL ) + pszCommandLine = new WCHAR[dwCommandLineLen]; + if (pszCommandLine == NULL) { hr = E_OUTOFMEMORY; goto Finished; @@ -341,14 +341,14 @@ SERVER_PROCESS::StartProcess( // replace %ASPNETCORE_PORT% with port number // - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - pszCommandLine, - ASPNETCORE_PORT_PLACEHOLDER, - ASPNETCORE_PORT_PLACEHOLDER_CCH, - m_dwPort, - dwNumDigitsInPort, - &fReplacedEnv ); - if( FAILED( hr ) ) + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + pszCommandLine, + ASPNETCORE_PORT_PLACEHOLDER, + ASPNETCORE_PORT_PLACEHOLDER_CCH, + m_dwPort, + dwNumDigitsInPort, + &fReplacedEnv); + if (FAILED(hr)) { goto Finished; } @@ -357,28 +357,28 @@ SERVER_PROCESS::StartProcess( // append AspNetCorePort to env variables. // - mszNewEnvironment.Append( strAspNetCorePortEnvVar ); + mszNewEnvironment.Append(strAspNetCorePortEnvVar); - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - pszCommandLine, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, - m_dwDebugPort, - dwNumDigitsInDebugPort, - &fReplacedEnv ); - if( FAILED( hr ) ) + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + pszCommandLine, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, + m_dwDebugPort, + dwNumDigitsInDebugPort, + &fReplacedEnv); + if (FAILED(hr)) { goto Finished; } - if(fReplacedEnv) + if (fReplacedEnv) { // // append debug port to environment only if // ASPNETCORE_DEBUG_PORT placeholder is present. // - mszNewEnvironment.Append( strAspNetCoreDebugPortEnvVar ); + mszNewEnvironment.Append(strAspNetCoreDebugPortEnvVar); fDebugPortEnvSet = TRUE; } @@ -388,7 +388,7 @@ SERVER_PROCESS::StartProcess( // this allows user to override current environment variables // - if(!mszEnvCopy.Copy(m_Environment)) + if (!mszEnvCopy.Copy(m_Environment)) { hr = E_OUTOFMEMORY; goto Finished; @@ -396,49 +396,49 @@ SERVER_PROCESS::StartProcess( LPWSTR multisz = mszEnvCopy.QueryStr(); - while( *multisz != '\0' ) + while (*multisz != '\0') { // // replace %ASPNETCORE_PORT% placeholder if present. // - - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - multisz, - ASPNETCORE_PORT_PLACEHOLDER, - ASPNETCORE_PORT_PLACEHOLDER_CCH, - m_dwPort, - dwNumDigitsInPort, - &fReplacedEnv ); - if( FAILED( hr ) ) + + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + multisz, + ASPNETCORE_PORT_PLACEHOLDER, + ASPNETCORE_PORT_PLACEHOLDER_CCH, + m_dwPort, + dwNumDigitsInPort, + &fReplacedEnv); + if (FAILED(hr)) { goto Finished; } - + // // replace %ASPNETCORE_DEBUG_PORT% placeholder if present. // if this placeholder is present, add this placeholder=value as // an environment variable as well. // - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - multisz, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, - m_dwDebugPort, - dwNumDigitsInDebugPort, - &fReplacedEnv ); - if( FAILED( hr ) ) + hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( + multisz, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER, + ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, + m_dwDebugPort, + dwNumDigitsInDebugPort, + &fReplacedEnv); + if (FAILED(hr)) { goto Finished; } - if( fReplacedEnv && !fDebugPortEnvSet ) + if (fReplacedEnv && !fDebugPortEnvSet) { - mszNewEnvironment.Append( strAspNetCoreDebugPortEnvVar ); + mszNewEnvironment.Append(strAspNetCoreDebugPortEnvVar); } - mszNewEnvironment.Append( multisz ); - multisz += wcslen( multisz) + 1; + mszNewEnvironment.Append(multisz); + multisz += wcslen(multisz) + 1; } // @@ -465,28 +465,28 @@ SERVER_PROCESS::StartProcess( // // environment block ends with \0\0, we don't want include the last \0 for appending // - mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize-1 ); + mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize - 1); dwCreationFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT | - CREATE_SUSPENDED; // | - //CREATE_NEW_PROCESS_GROUP; + CREATE_SUSPENDED | + CREATE_NEW_PROCESS_GROUP; - finalCommandLine.Copy( pszCommandLine ); + finalCommandLine.Copy(pszCommandLine); fDonePrepareCommandLine = TRUE; if (!CreateProcessW( - NULL, // applicationName - finalCommandLine.QueryStr(), - NULL, // processAttr - NULL, // threadAttr - TRUE, // inheritHandles - dwCreationFlags, - mszNewEnvironment.QueryStr(), - pszRootApplicationPath, // currentDir - &startupInfo, - &processInformation) ) + NULL, // applicationName + finalCommandLine.QueryStr(), + NULL, // processAttr + NULL, // threadAttr + TRUE, // inheritHandles + dwCreationFlags, + mszNewEnvironment.QueryStr(), + pszRootApplicationPath, // currentDir + &startupInfo, + &processInformation)) { hr = HRESULT_FROM_WIN32(GetLastError()); // don't check return code as we already in error report @@ -503,26 +503,26 @@ SERVER_PROCESS::StartProcess( m_hProcessHandle = processInformation.hProcess; m_dwProcessId = processInformation.dwProcessId; - if( m_hJobObject != NULL ) + if (m_hJobObject != NULL) { - if( !AssignProcessToJobObject( m_hJobObject, - processInformation.hProcess ) ) + if (!AssignProcessToJobObject(m_hJobObject, + processInformation.hProcess)) { - hr = HRESULT_FROM_WIN32( GetLastError() ); - if( hr != HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) ) + 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; - } - if( CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0 ) + if (ResumeThread(processInformation.hThread) == -1) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0) { // some error occurred - assume debugger is not attached; fDebuggerAttachedToChildProcess = FALSE; @@ -555,30 +555,30 @@ SERVER_PROCESS::StartProcess( } } - if(g_fNsiApiNotSupported) + if (g_fNsiApiNotSupported) { - hr = CheckIfServerIsUp( m_dwPort, &fReady ); + hr = CheckIfServerIsUp(m_dwPort, &fReady); } else { - hr = CheckIfServerIsUp( m_dwPort, &dwActualProcessId, &fReady ); + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); } fDebuggerAttachedToChildProcess = IsDebuggerIsAttached(); - if( !fReady ) + if (!fReady) { Sleep(250); } dwTimeDifference = (GetTickCount() - dwTickCount); - } while( fReady == FALSE && - (( dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttachedToChildProcess) ); + } while (fReady == FALSE && + ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttachedToChildProcess)); - hr = RegisterProcessWait( &m_hProcessWaitHandle, - m_hProcessHandle ); + hr = RegisterProcessWait(&m_hProcessWaitHandle, + m_hProcessHandle); - if( FAILED( hr ) ) + if (FAILED(hr)) { goto Finished; } @@ -586,60 +586,60 @@ SERVER_PROCESS::StartProcess( // // check if debugger is attached after startupTimeout. // - if( !fDebuggerAttachedToChildProcess && - CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0 ) + if (!fDebuggerAttachedToChildProcess && + CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0) { // some error occurred - assume debugger is not attached; fDebuggerAttachedToChildProcess = FALSE; } hr = GetChildProcessHandles(); - if( FAILED(hr ) ) + if (FAILED(hr)) { goto Finished; } BOOL processMatch = FALSE; - if(dwActualProcessId == m_dwProcessId) + if (dwActualProcessId == m_dwProcessId) { m_dwListeningProcessId = m_dwProcessId; processMatch = TRUE; } - for(DWORD i=0; i < m_cChildProcess; ++i) + for (DWORD i = 0; i < m_cChildProcess; ++i) { - if( !processMatch && dwActualProcessId == m_dwChildProcessIds[i]) + if (!processMatch && dwActualProcessId == m_dwChildProcessIds[i]) { m_dwListeningProcessId = m_dwChildProcessIds[i]; processMatch = TRUE; } - if( m_hChildProcessHandles[i] != NULL ) + if (m_hChildProcessHandles[i] != NULL) { - if( fDebuggerAttachedToChildProcess == FALSE && CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttachedToChildProcess) == 0 ) + if (fDebuggerAttachedToChildProcess == FALSE && CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttachedToChildProcess) == 0) { // some error occurred - assume debugger is not attached; fDebuggerAttachedToChildProcess = FALSE; } - hr = RegisterProcessWait( &m_hChildProcessWaitHandles[i], - m_hChildProcessHandles[i] ); - if( FAILED( hr ) ) + hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], + m_hChildProcessHandles[i]); + if (FAILED(hr)) { goto Finished; } - cChildProcess ++; + cChildProcess++; } } - if(fReady == FALSE) + if (fReady == FALSE) { // // hr is already set by CheckIfServerIsUp // - if( dwTimeDifference >= m_dwStartupTimeLimitInMS ) + if (dwTimeDifference >= m_dwStartupTimeLimitInMS) { hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); strEventMsg.SafeSnwprintf( @@ -654,7 +654,7 @@ SERVER_PROCESS::StartProcess( goto Finished; } - if( !g_fNsiApiNotSupported && !processMatch ) + if (!g_fNsiApiNotSupported && !processMatch) { // // process that we created is not listening @@ -672,7 +672,7 @@ SERVER_PROCESS::StartProcess( goto Finished; } - if( cChildProcess > 0 ) + if (cChildProcess > 0) { // // final check to make sure child process listening on HTTP is still UP @@ -681,16 +681,16 @@ SERVER_PROCESS::StartProcess( // and we would not know about it. // - if(g_fNsiApiNotSupported) + if (g_fNsiApiNotSupported) { - hr = CheckIfServerIsUp( m_dwPort, &fReady ); + hr = CheckIfServerIsUp(m_dwPort, &fReady); } else { - hr = CheckIfServerIsUp( m_dwPort, &dwActualProcessId, &fReady ); + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); } - if( (FAILED(hr) || fReady == FALSE) ) + if ((FAILED(hr) || fReady == FALSE)) { strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, @@ -707,29 +707,29 @@ SERVER_PROCESS::StartProcess( // ready to mark the server process ready but before this, // create and initialize the FORWARDER_CONNECTION // - if(m_pForwarderConnection != NULL) + if (m_pForwarderConnection != NULL) { m_pForwarderConnection->DereferenceForwarderConnection(); m_pForwarderConnection = NULL; } - if(m_pForwarderConnection == NULL) + if (m_pForwarderConnection == NULL) { m_pForwarderConnection = new FORWARDER_CONNECTION(); - if(m_pForwarderConnection == NULL) + if (m_pForwarderConnection == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - hr = m_pForwarderConnection->Initialize( m_dwPort ); - if(FAILED(hr)) + hr = m_pForwarderConnection->Initialize(m_dwPort); + if (FAILED(hr)) { goto Finished; } } - if(!g_fNsiApiNotSupported) + if (!g_fNsiApiNotSupported) { m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, FALSE, m_dwListeningProcessId); } @@ -742,7 +742,7 @@ SERVER_PROCESS::StartProcess( if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, - pszAppPath, + pszAppPath, m_dwProcessId, m_dwPort))) { @@ -767,22 +767,22 @@ SERVER_PROCESS::StartProcess( } Finished: - if ( FAILED(hr) ) + if (FAILED(hr)) { if (strEventMsg.IsEmpty()) { if (!fDonePrepareCommandLine) strEventMsg.SafeSnwprintf( - pszAppPath, - ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, - hr); + pszAppPath, + ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, + hr); else strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), - hr); + ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + pszAppPath, + pszRootApplicationPath, + finalCommandLine.QueryStr(), + hr); } apsz[0] = strEventMsg.QueryStr(); @@ -804,66 +804,66 @@ Finished: } } - if ( fRpcStringAllocd ) + if (fRpcStringAllocd) { RpcStringFreeA((BYTE **)&pszLogUuid); pszLogUuid = NULL; } - if( processInformation.hThread != NULL ) + if (processInformation.hThread != NULL) { CloseHandle(processInformation.hThread); processInformation.hThread = NULL; } - if(pszCurrentEnvironment != NULL ) + if (pszCurrentEnvironment != NULL) { - FreeEnvironmentStringsW( pszCurrentEnvironment ); + FreeEnvironmentStringsW(pszCurrentEnvironment); pszCurrentEnvironment = NULL; } - if( pszCommandLine != NULL ) + if (pszCommandLine != NULL) { delete[] pszCommandLine; pszCommandLine = NULL; } - if( FAILED( hr ) || m_fReady == FALSE) + if (FAILED(hr) || m_fReady == FALSE) { - if(m_hStdoutHandle != NULL) + if (m_hStdoutHandle != NULL) { - if( m_hStdoutHandle != INVALID_HANDLE_VALUE ) + if (m_hStdoutHandle != INVALID_HANDLE_VALUE) { - CloseHandle( m_hStdoutHandle ); + CloseHandle(m_hStdoutHandle); } m_hStdoutHandle = NULL; } - if( m_fStdoutLogEnabled ) + if (m_fStdoutLogEnabled) { m_Timer.CancelTimer(); } - if(m_hListeningProcessHandle != NULL) + if (m_hListeningProcessHandle != NULL) { - if( m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE) { - CloseHandle( m_hListeningProcessHandle ); + CloseHandle(m_hListeningProcessHandle); } m_hListeningProcessHandle = NULL; } - if( m_hProcessWaitHandle != NULL ) + if (m_hProcessWaitHandle != NULL) { - UnregisterWait( m_hProcessWaitHandle ); + UnregisterWait(m_hProcessWaitHandle); m_hProcessWaitHandle = NULL; } - for(DWORD i=0;iGetApplication()->GetApplicationPhysicalPath(), - &struAbsLogFilePath ); - if(FAILED(hr)) + hr = PATH::ConvertPathToFullPath(m_struLogFile.QueryStr(), + context->GetApplication()->GetApplicationPhysicalPath(), + &struAbsLogFilePath); + if (FAILED(hr)) { goto Finished; } GetSystemTime(&systemTime); - hr = struLogFileName.SafeSnwprintf( L"%s_%d_%d%d%d%d%d%d.log", - struAbsLogFilePath.QueryStr(), - GetCurrentProcessId(), - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond ); - if(FAILED(hr)) + hr = struLogFileName.SafeSnwprintf(L"%s_%d_%d%d%d%d%d%d.log", + struAbsLogFilePath.QueryStr(), + GetCurrentProcessId(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond); + if (FAILED(hr)) { goto Finished; } - m_hStdoutHandle = CreateFileW( struLogFileName.QueryStr(), - FILE_WRITE_DATA, - FILE_SHARE_READ, - &saAttr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL ); - if( m_hStdoutHandle == INVALID_HANDLE_VALUE ) + m_hStdoutHandle = CreateFileW(struLogFileName.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (m_hStdoutHandle == INVALID_HANDLE_VALUE) { fStdoutLoggingFailed = TRUE; m_hStdoutHandle = NULL; - if( SUCCEEDED( strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, - struLogFileName.QueryStr(), - HRESULT_FROM_GETLASTERROR() ) ) ) + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + struLogFileName.QueryStr(), + HRESULT_FROM_GETLASTERROR()))) { apsz[0] = strEventMsg.QueryStr(); @@ -999,23 +999,23 @@ SERVER_PROCESS::SetupStdHandles( } } - if( !fStdoutLoggingFailed ) + if (!fStdoutLoggingFailed) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; pStartupInfo->hStdError = m_hStdoutHandle; pStartupInfo->hStdOutput = m_hStdoutHandle; - m_struFullLogFile.Copy( struLogFileName ); + m_struFullLogFile.Copy(struLogFileName); // start timer to open and close handles regularly. m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000); } } - if( (!m_fStdoutLogEnabled || fStdoutLoggingFailed) && + if ((!m_fStdoutLogEnabled || fStdoutLoggingFailed) && m_pProcessManager->QueryNULHandle() != NULL && - m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE ) + m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; @@ -1038,28 +1038,28 @@ SERVER_PROCESS::TimerCallback( { Instance; Timer; - SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*) Context; + SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*)Context; HANDLE hStdoutHandle = NULL; - SECURITY_ATTRIBUTES saAttr = {0}; + SECURITY_ATTRIBUTES saAttr = { 0 }; HRESULT hr = S_OK; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; + saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; - hStdoutHandle = CreateFileW( pServerProcess->QueryFullLogPath(), - FILE_READ_DATA, - FILE_SHARE_WRITE, - &saAttr, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL ); - if( hStdoutHandle == INVALID_HANDLE_VALUE ) + hStdoutHandle = CreateFileW(pServerProcess->QueryFullLogPath(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hStdoutHandle == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_GETLASTERROR(); } - CloseHandle( hStdoutHandle ); + CloseHandle(hStdoutHandle); } HRESULT @@ -1073,17 +1073,17 @@ SERVER_PROCESS::CheckIfServerIsUp( SOCKADDR_IN sockAddr; BOOL fLocked = FALSE; - _ASSERT( fReady != NULL ); + _ASSERT(fReady != NULL); *fReady = FALSE; - EnterCriticalSection( &m_csLock ); + EnterCriticalSection(&m_csLock); fLocked = TRUE; - if( m_socket == INVALID_SOCKET || m_socket == NULL) + if (m_socket == INVALID_SOCKET || m_socket == NULL) { - m_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); - if( m_socket == INVALID_SOCKET ) + m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_socket == INVALID_SOCKET) { hr = HRESULT_FROM_WIN32(WSAGetLastError()); goto Finished; @@ -1091,13 +1091,13 @@ SERVER_PROCESS::CheckIfServerIsUp( } sockAddr.sin_family = AF_INET; - if( !inet_pton(AF_INET, LOCALHOST, &(sockAddr.sin_addr))) - { - hr = HRESULT_FROM_WIN32(WSAGetLastError()); - goto Finished; - } + 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 ); + sockAddr.sin_port = htons((u_short)dwPort); // // Connect to server. @@ -1105,10 +1105,10 @@ SERVER_PROCESS::CheckIfServerIsUp( // while retrying // - iResult = connect(m_socket, (SOCKADDR *) &sockAddr, sizeof (sockAddr)); - if (iResult == SOCKET_ERROR) + iResult = connect(m_socket, (SOCKADDR *)&sockAddr, sizeof(sockAddr)); + if (iResult == SOCKET_ERROR) { - hr = HRESULT_FROM_WIN32( WSAGetLastError() ); + hr = HRESULT_FROM_WIN32(WSAGetLastError()); goto Finished; } @@ -1116,10 +1116,10 @@ SERVER_PROCESS::CheckIfServerIsUp( // Connected successfully, close socket. // - iResult = closesocket( m_socket ); - if (iResult == SOCKET_ERROR) + iResult = closesocket(m_socket); + if (iResult == SOCKET_ERROR) { - hr = HRESULT_FROM_WIN32( WSAGetLastError() ); + hr = HRESULT_FROM_WIN32(WSAGetLastError()); goto Finished; } @@ -1128,9 +1128,9 @@ SERVER_PROCESS::CheckIfServerIsUp( Finished: - if( fLocked ) + if (fLocked) { - LeaveCriticalSection( &m_csLock ); + LeaveCriticalSection(&m_csLock); fLocked = FALSE; } @@ -1150,40 +1150,40 @@ SERVER_PROCESS::CheckIfServerIsUp( MIB_TCPROW_OWNER_PID *pOwner = NULL; DWORD dwSize = 0; - DBG_ASSERT( pfReady ); - DBG_ASSERT( pdwProcessId ); + DBG_ASSERT(pfReady); + DBG_ASSERT(pdwProcessId); *pfReady = FALSE; *pdwProcessId = 0; - - dwResult = GetExtendedTcpTable(NULL, - &dwSize, - FALSE, - AF_INET, - TCP_TABLE_OWNER_PID_LISTENER, - 0); + + 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 ); + hr = HRESULT_FROM_WIN32(dwResult); goto Finished; } pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); - if(pTCPInfo == NULL) + if (pTCPInfo == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - - dwResult = GetExtendedTcpTable(pTCPInfo, - &dwSize, - FALSE, - AF_INET, - TCP_TABLE_OWNER_PID_LISTENER, - 0); + + dwResult = GetExtendedTcpTable(pTCPInfo, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); if (dwResult != NO_ERROR) { - hr = HRESULT_FROM_WIN32( dwResult ); + hr = HRESULT_FROM_WIN32(dwResult); goto Finished; } @@ -1191,7 +1191,7 @@ SERVER_PROCESS::CheckIfServerIsUp( for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++) { pOwner = &pTCPInfo->table[dwLoop]; - if( ntohs((USHORT)pOwner->dwLocalPort) == dwPort ) + if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort) { *pdwProcessId = pOwner->dwOwningPid; *pfReady = TRUE; @@ -1201,9 +1201,9 @@ SERVER_PROCESS::CheckIfServerIsUp( Finished: - if( pTCPInfo != NULL ) + if (pTCPInfo != NULL) { - HeapFree( GetProcessHeap(), 0, pTCPInfo ); + HeapFree(GetProcessHeap(), 0, pTCPInfo); pTCPInfo = NULL; } @@ -1221,53 +1221,69 @@ SERVER_PROCESS::SendSignal( { HANDLE hProc = INVALID_HANDLE_VALUE; BOOL fIsSuccess = FALSE; + BOOL fFreeConsole = FALSE; LPCWSTR apsz[1]; STACK_STRU(strEventMsg, 256); + ReferenceServerProcess(); + hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); if (hProc != INVALID_HANDLE_VALUE) { - fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); + // free current console first, as we migh have one, e.g., hostedwebcore case + fFreeConsole = FreeConsole(); - if (!fIsSuccess) + if (AttachConsole(m_dwProcessId)) { - if (AttachConsole(m_dwProcessId)) - { - fIsSuccess = GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessId); - FreeConsole(); - } - } - } - - if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) - { - //Backend process will be terminated, remove the waitcallback - if (m_hProcessWaitHandle != NULL) - { - UnregisterWait(m_hProcessWaitHandle); - m_hProcessWaitHandle = NULL; + // call ctrl-break instead of ctrl-c as child process may ignore ctrl-c + fIsSuccess = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId); + FreeConsole(); } - // cannot gracefully shutdown or timeout, terminate the process - TerminateProcess(m_hProcessHandle, 0); - - // log a warning for ungraceful shutdown - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, - m_dwProcessId))) + if (fFreeConsole) { - apsz[0] = strEventMsg.QueryStr(); - if (FORWARDING_HANDLER::QueryEventLog() != NULL) + // IISExpress and hostedwebcore w3wp run as background process + // have to attach console back to ensure post app_offline scenario still work + AttachConsole(ATTACH_PARENT_PROCESS); + } + } + + if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) + { + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + //Backend process will be terminated, remove the waitcallback + if (m_hProcessWaitHandle != NULL) { - ReportEventW(FORWARDING_HANDLER::QueryEventLog(), - EVENTLOG_WARNING_TYPE, - 0, - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, - NULL, - 1, - 0, - apsz, - NULL); + UnregisterWait(m_hProcessWaitHandle); + m_hProcessWaitHandle = NULL; + } + + // cannot gracefully shutdown or timeout, terminate the process + TerminateProcess(m_hProcessHandle, 0); + + // as we skipped process exit callback (ProcessHandleCallback), + // need to dereference the object otherwise memory leak + DereferenceServerProcess(); + + // 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); + } } } } @@ -1281,7 +1297,9 @@ SERVER_PROCESS::SendSignal( { CloseHandle(m_hProcessHandle); m_hProcessHandle = INVALID_HANDLE_VALUE; - } + } + + DereferenceServerProcess(); } // @@ -1298,32 +1316,32 @@ SERVER_PROCESS::StopProcess( m_pProcessManager->IncrementRapidFailCount(); - for(INT i=0;iNumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0 ) ); + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0)); - if( dwError == ERROR_MORE_DATA ) + if (dwError == ERROR_MORE_DATA) { hr = E_OUTOFMEMORY; // some error goto Finished; } - if( processList == NULL || - ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0 ) ) + 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 ) + if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES) { - hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } - - for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + + for (DWORD i = 0; iNumberOfProcessIdsInList; i++) { dwPid = (DWORD)processList->ProcessIdList[i]; - if( dwPid != dwWorkerProcessPid ) + if (dwPid != dwWorkerProcessPid) { - HANDLE hProcess = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid - ); - BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); - if( ! returnValue ) + HANDLE hProcess = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent); + if (!returnValue) { goto Finished; } - if( fDebuggerPresent ) + if (fDebuggerPresent) { break; } @@ -1432,7 +1450,7 @@ SERVER_PROCESS::IsDebuggerIsAttached( Finished: - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1440,7 +1458,7 @@ Finished: return fDebuggerPresent; } -HRESULT +HRESULT SERVER_PROCESS::GetChildProcessHandles( VOID ) @@ -1459,7 +1477,7 @@ SERVER_PROCESS::GetChildProcessHandles( { dwError = NO_ERROR; - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); processList = NULL; @@ -1468,77 +1486,77 @@ SERVER_PROCESS::GetChildProcessHandles( cbNumBytes = cbNumBytes * 2; } - processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, - cbNumBytes - ); - if( processList == NULL ) + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if (processList == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); - if( !QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, - NULL) ) + if (!QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL)) { dwError = GetLastError(); - if( dwError != ERROR_MORE_DATA ) + if (dwError != ERROR_MORE_DATA) { hr = HRESULT_FROM_WIN32(dwError); goto Finished; } } - } while( dwRetries++ < 5 && - processList != NULL && - ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); - if( dwError == ERROR_MORE_DATA ) + if (dwError == ERROR_MORE_DATA) { hr = E_OUTOFMEMORY; // some error goto Finished; } - if( processList == NULL || ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ) + 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 ) + if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES) { - hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } - - for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + + for (DWORD i = 0; iNumberOfProcessIdsInList; i++) { dwPid = (DWORD)processList->ProcessIdList[i]; - if( dwPid != m_dwProcessId && - dwPid != dwWorkerProcessPid ) + if (dwPid != m_dwProcessId && + dwPid != dwWorkerProcessPid) { - m_hChildProcessHandles[m_cChildProcess] = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid - ); + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); m_dwChildProcessIds[m_cChildProcess] = dwPid; - m_cChildProcess ++; + m_cChildProcess++; } } Finished: - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1548,7 +1566,7 @@ Finished: HRESULT SERVER_PROCESS::StopAllProcessesInJobObject( - VOID + VOID ) { HRESULT hr = S_OK; @@ -1562,7 +1580,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( do { - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); processList = NULL; @@ -1571,66 +1589,66 @@ SERVER_PROCESS::StopAllProcessesInJobObject( cbNumBytes = cbNumBytes * 2; } - processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, - cbNumBytes - ); - if( processList == NULL ) + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if (processList == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); - if( !QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, - NULL) ) + if (!QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL)) { DWORD dwError = GetLastError(); - if( dwError != ERROR_MORE_DATA ) + if (dwError != ERROR_MORE_DATA) { hr = HRESULT_FROM_WIN32(dwError); goto Finished; } } - } while( dwRetries++ < 5 && - processList != NULL && - ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); - if( 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++ ) + + for (DWORD i = 0; iNumberOfProcessIdsInList; i++) { - if( dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i] ) + if (dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i]) { - hProcess = OpenProcess( PROCESS_TERMINATE, - FALSE, - (DWORD)processList->ProcessIdList[i] ); - if( hProcess != NULL ) + hProcess = OpenProcess(PROCESS_TERMINATE, + FALSE, + (DWORD)processList->ProcessIdList[i]); + if (hProcess != NULL) { - if( !TerminateProcess(hProcess, 1) ) + if (!TerminateProcess(hProcess, 1)) { hr = HRESULT_FROM_GETLASTERROR(); } else { - WaitForSingleObject( hProcess, INFINITE ); + WaitForSingleObject(hProcess, INFINITE); } - if( hProcess != NULL ) + if (hProcess != NULL) { - CloseHandle( hProcess ); + CloseHandle(hProcess); hProcess = NULL; } } @@ -1639,7 +1657,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( Finished: - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1647,27 +1665,27 @@ Finished: return hr; } -SERVER_PROCESS::SERVER_PROCESS() : - m_cRefs( 1 ), - m_hProcessHandle( NULL ), - m_hProcessWaitHandle( NULL ), - m_dwProcessId( 0 ), - m_cChildProcess( 0 ), - m_socket( INVALID_SOCKET ), - m_fReady( FALSE ), - m_fStopping( FALSE ), - m_hStdoutHandle( NULL ), - m_fStdoutLogEnabled( FALSE ), - m_hJobObject( NULL ), - m_pForwarderConnection( NULL ), - m_dwListeningProcessId( 0 ), - m_hListeningProcessHandle( NULL ) +SERVER_PROCESS::SERVER_PROCESS() : + m_cRefs(1), + m_hProcessHandle(NULL), + m_hProcessWaitHandle(NULL), + m_dwProcessId(0), + m_cChildProcess(0), + m_socket(INVALID_SOCKET), + m_fReady(FALSE), + m_lStopping(0L), + m_hStdoutHandle(NULL), + m_fStdoutLogEnabled(FALSE), + m_hJobObject(NULL), + m_pForwarderConnection(NULL), + m_dwListeningProcessId(0), + m_hListeningProcessHandle(NULL) { InterlockedIncrement(&g_dwActiveServerProcesses); srand((unsigned int)time(NULL)); - InitializeCriticalSection( &m_csLock ); + InitializeCriticalSection(&m_csLock); - for(INT i=0;iDereferenceProcessManager(); m_pProcessManager = NULL; } - if(m_pForwarderConnection != NULL) + if (m_pForwarderConnection != NULL) { m_pForwarderConnection->DereferenceForwarderConnection(); m_pForwarderConnection = NULL; } - DeleteCriticalSection( &m_csLock ); + DeleteCriticalSection(&m_csLock); InterlockedDecrement(&g_dwActiveServerProcesses); } -VOID +VOID ProcessHandleCallback( _In_ PVOID pContext, _In_ BOOL ) { - SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*) pContext; + SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*)pContext; pServerProcess->HandleProcessExit(); } @@ -1788,31 +1806,31 @@ SERVER_PROCESS::RegisterProcessWait( HRESULT hr = S_OK; NTSTATUS status = 0; - _ASSERT( phWaitHandle != NULL && *phWaitHandle == NULL ); + _ASSERT(phWaitHandle != NULL && *phWaitHandle == NULL); *phWaitHandle = NULL; // wait thread will dereference. ReferenceServerProcess(); - status = RegisterWaitForSingleObject( - phWaitHandle, - hProcessToWaitOn, - (WAITORTIMERCALLBACK)&ProcessHandleCallback, - this, - INFINITE, - WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD - ); + status = RegisterWaitForSingleObject( + phWaitHandle, + hProcessToWaitOn, + (WAITORTIMERCALLBACK)&ProcessHandleCallback, + this, + INFINITE, + WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD + ); - if( status < 0 ) + if (status < 0) { - hr = HRESULT_FROM_NT( status ); + hr = HRESULT_FROM_NT(status); goto Finished; } Finished: - if( FAILED( hr ) ) + if (FAILED(hr)) { *phWaitHandle = NULL; DereferenceServerProcess(); @@ -1826,19 +1844,17 @@ SERVER_PROCESS::HandleProcessExit() { HRESULT hr = S_OK; BOOL fReady = FALSE; - - CheckIfServerIsUp( m_dwPort, &fReady ); - - if( !fReady ) + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { - if( !m_fStopping ) - { - m_fStopping = TRUE; - m_pProcessManager->ShutdownProcess( this ); - } - } + CheckIfServerIsUp(m_dwPort, &fReady); - DereferenceServerProcess(); + if (!fReady) + { + m_pProcessManager->ShutdownProcess(this); + } + + DereferenceServerProcess(); + } return hr; } \ No newline at end of file From 135aa12529ae91f0699bca277216a65dd973e149 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 10 Feb 2017 14:35:51 -0800 Subject: [PATCH 032/107] Remove MAX_PATH buffersize to support long file path (#69) This is already reviewed by Pan. --- src/AspNetCore/Src/path.cxx | 2 +- src/AspNetCore/Src/serverprocess.cxx | 33 ++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/AspNetCore/Src/path.cxx b/src/AspNetCore/Src/path.cxx index 0c6bb0fc5f..a4ef464539 100644 --- a/src/AspNetCore/Src/path.cxx +++ b/src/AspNetCore/Src/path.cxx @@ -348,7 +348,7 @@ PATH::IsPathUnc( ) { HRESULT hr = S_OK; - STACK_STRU( strTempPath, MAX_PATH ); + STRU strTempPath; if ( pszPath == NULL || pfIsUnc == NULL ) { diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 20faa4dde6..c63362548e 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -118,6 +118,7 @@ SERVER_PROCESS::StartProcess( LPCWSTR pszRootApplicationPath = NULL; BOOL fDebuggerAttachedToChildProcess = FALSE; STRU strFullProcessPath; + STRU struRelativePath; STRU struApplicationId; RPC_STATUS rpcStatus; UUID logUuid; @@ -131,9 +132,10 @@ SERVER_PROCESS::StartProcess( // DWORD dwActualProcessId = 0; WCHAR* pszPath = NULL; - WCHAR pszFullPath[_MAX_PATH]; + WCHAR* pszFullPath = NULL; LPCWSTR apsz[1]; PCWSTR pszAppPath = NULL; + DWORD dwBufferSize = 0; GetStartupInfoW(&startupInfo); @@ -299,21 +301,24 @@ SERVER_PROCESS::StartProcess( if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) { // let's check whether it is a relative path - WCHAR pszRelativePath[_MAX_PATH]; - - if (swprintf_s(pszRelativePath, - _MAX_PATH, - L"%s\\%s", - pszRootApplicationPath, - pszPath) == -1) + if (FAILED(hr = struRelativePath.Copy(pszRootApplicationPath)) || + FAILED(hr = struRelativePath.Append(L"\\")) || + FAILED(hr = struRelativePath.Append(pszPath))) { - hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + goto Finished; + } + + dwBufferSize = struRelativePath.QueryCCH() + 1; + pszFullPath = new WCHAR[dwBufferSize]; + if (pszFullPath == NULL) + { + hr = E_OUTOFMEMORY; goto Finished; } if (_wfullpath(pszFullPath, - pszRelativePath, - _MAX_PATH) == NULL) + struRelativePath.QueryStr(), + dwBufferSize) == NULL) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto Finished; @@ -828,6 +833,12 @@ Finished: pszCommandLine = NULL; } + if (pszFullPath != NULL) + { + delete[] pszFullPath; + pszFullPath = NULL; + } + if (FAILED(hr) || m_fReady == FALSE) { if (m_hStdoutHandle != NULL) From b4e3ccf67f6d45ea9c8b24cccd611dcd70370888 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 14 Feb 2017 11:15:45 -0800 Subject: [PATCH 033/107] added test cases to Dev branch (#72) --- AspNetCoreModule.sln | 30 + NuGet.config | 8 + .../AspNetCoreModule.Test.ForVS.csproj | 155 ++ .../AspNetCoreModule.Test.xproj | 20 + ...vironmentVariableTestConditionAttribute.cs | 41 + .../Framework/IISConfigUtility.cs | 1074 +++++++++++++ .../Framework/InitializeTestMachine.cs | 244 +++ .../Framework/TestUtility.cs | 830 ++++++++++ .../Framework/TestWebApplication.cs | 234 +++ .../Framework/TestWebSite.cs | 244 +++ test/AspNetCoreModule.Test/FunctionalTest.cs | 293 ++++ .../FunctionalTestHelper.cs | 1375 +++++++++++++++++ test/AspNetCoreModule.Test/Http.config | 1032 +++++++++++++ .../HttpClientHelper/HttpClientHelper.cs | 336 ++++ .../WebSocketClientHelper/Frame.cs | 44 + .../WebSocketClientHelper/FrameType.cs | 27 + .../WebSocketClientHelper/Frames.cs | 60 + .../WebSocketClientHelper.cs | 370 +++++ .../WebSocketClientUtility.cs | 231 +++ .../WebSocketClientHelper/WebSocketConnect.cs | 76 + .../WebSocketConstants.cs | 15 + .../WebSocketClientHelper/WebSocketState.cs | 12 + test/AspNetCoreModule.Test/app.config | 31 + test/AspNetCoreModule.Test/project.json | 35 + .../project.json.compileerror.txt | 38 + .../AspnetCoreModule.TestSites.Standard.xproj | 18 + .../ImpersonateMiddleware.cs | 36 + .../Program.cs | 123 ++ .../Properties/launchSettings.json | 26 + .../Startup.cs | 311 ++++ .../StartupCompressionCaching.cs | 61 + .../StartupHelloWorld.cs | 22 + .../StartupNtlmAuthentication.cs | 92 ++ .../project.json | 46 + .../web.config | 9 + .../ErrorHandling_NotExisting/web.config | 9 + test/WebRoot/URLRewrite/article.aspx | 25 + test/WebRoot/URLRewrite/default.htm | 11 + test/WebRoot/URLRewrite/web.config | 29 + test/WebRoot/URLRewrite/web.config.bak | 25 + test/WebRoot/WebSite1/bkg-blu.jpg | Bin 0 -> 289940 bytes test/WebRoot/WebSite1/iis.png | Bin 0 -> 98757 bytes test/WebRoot/WebSite1/iisstart.htm | 32 + test/WebRoot/WebSite1/msweb-brand.png | Bin 0 -> 2698 bytes test/WebRoot/WebSite1/small.htm | 1 + test/WebRoot/WebSite1/test.asp | 13 + test/WebRoot/WebSite1/w-brand.png | Bin 0 -> 10165 bytes test/WebRoot/WebSite1/web.config | 6 + test/WebRoot/WebSite1/web.config.bak | 6 + test/WebRoot/WebSocket/ChatHandler.ashx | 70 + test/WebRoot/WebSocket/EchoHandler.ashx | 50 + .../WebSocket/OtherExamples/ChatHandler.ashx | 69 + .../WebSocket/OtherExamples/Default.aspx | 70 + .../WebSocket/OtherExamples/Default.htm | 70 + .../OtherExamples/DownloaderHandler.ashx | 112 ++ .../GetWebSocketConnectionCount.ashx | 28 + .../OtherExamples/HandleBinaryEcho.ashx | 166 ++ .../WebSocket/OtherExamples/Handler.ashx | 49 + .../OtherExamples/UploadHandler.ashx | 49 + .../WebSocket/OtherExamples/web.config | 6 + test/WebRoot/WebSocket/chat.aspx | 62 + test/WebRoot/WebSocket/echo.aspx | 65 + test/WebRoot/WebSocket/echoAspnetCore.aspx | 62 + test/WebRoot/WebSocket/echoSubProtocol.aspx | 65 + test/WebRoot/WebSocket/web.config | 6 + test/WebRoot/WebSocket/web.config.bak | 6 + test/WebRoot/applicationhost.config.txt | 1233 +++++++++++++++ test/WebRoot/parent/web.config | 14 + 68 files changed, 10008 insertions(+) create mode 100644 NuGet.config create mode 100644 test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj create mode 100644 test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj create mode 100644 test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs create mode 100644 test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs create mode 100644 test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs create mode 100644 test/AspNetCoreModule.Test/Framework/TestUtility.cs create mode 100644 test/AspNetCoreModule.Test/Framework/TestWebApplication.cs create mode 100644 test/AspNetCoreModule.Test/Framework/TestWebSite.cs create mode 100644 test/AspNetCoreModule.Test/FunctionalTest.cs create mode 100644 test/AspNetCoreModule.Test/FunctionalTestHelper.cs create mode 100644 test/AspNetCoreModule.Test/Http.config create mode 100644 test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs create mode 100644 test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs create mode 100644 test/AspNetCoreModule.Test/app.config create mode 100644 test/AspNetCoreModule.Test/project.json create mode 100644 test/AspNetCoreModule.Test/project.json.compileerror.txt create mode 100644 test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj create mode 100644 test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/Program.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json create mode 100644 test/AspNetCoreModule.TestSites.Standard/Startup.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/project.json create mode 100644 test/AspNetCoreModule.TestSites.Standard/web.config create mode 100644 test/WebRoot/ErrorHandling_NotExisting/web.config create mode 100644 test/WebRoot/URLRewrite/article.aspx create mode 100644 test/WebRoot/URLRewrite/default.htm create mode 100644 test/WebRoot/URLRewrite/web.config create mode 100644 test/WebRoot/URLRewrite/web.config.bak create mode 100644 test/WebRoot/WebSite1/bkg-blu.jpg create mode 100644 test/WebRoot/WebSite1/iis.png create mode 100644 test/WebRoot/WebSite1/iisstart.htm create mode 100644 test/WebRoot/WebSite1/msweb-brand.png create mode 100644 test/WebRoot/WebSite1/small.htm create mode 100644 test/WebRoot/WebSite1/test.asp create mode 100644 test/WebRoot/WebSite1/w-brand.png create mode 100644 test/WebRoot/WebSite1/web.config create mode 100644 test/WebRoot/WebSite1/web.config.bak create mode 100644 test/WebRoot/WebSocket/ChatHandler.ashx create mode 100644 test/WebRoot/WebSocket/EchoHandler.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/ChatHandler.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/Default.aspx create mode 100644 test/WebRoot/WebSocket/OtherExamples/Default.htm create mode 100644 test/WebRoot/WebSocket/OtherExamples/DownloaderHandler.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/GetWebSocketConnectionCount.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/HandleBinaryEcho.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/Handler.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/UploadHandler.ashx create mode 100644 test/WebRoot/WebSocket/OtherExamples/web.config create mode 100644 test/WebRoot/WebSocket/chat.aspx create mode 100644 test/WebRoot/WebSocket/echo.aspx create mode 100644 test/WebRoot/WebSocket/echoAspnetCore.aspx create mode 100644 test/WebRoot/WebSocket/echoSubProtocol.aspx create mode 100644 test/WebRoot/WebSocket/web.config create mode 100644 test/WebRoot/WebSocket/web.config.bak create mode 100644 test/WebRoot/applicationhost.config.txt create mode 100644 test/WebRoot/parent/web.config diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index a8ba10859c..e2ab3a60d5 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -12,6 +12,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{02F461DC-5166-4E88-AAD5-CF110016A647}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspNetCoreModule.Test", "test\AspNetCoreModule.Test\AspNetCoreModule.Test.xproj", "{4DDA7560-AA29-4161-A5EA-A7E8F3997321}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2097C03C-E2F7-4396-B3BC-4335F1B87B5E}" ProjectSection(SolutionItems) = preProject global.json = global.json @@ -19,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FDD2EDF8-1B62-4978-9815-9D95260B8B91}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspnetCoreModule.TestSites.Standard", "test\AspNetCoreModule.TestSites.Standard\AspnetCoreModule.TestSites.Standard.xproj", "{030225D8-4EE8-47E5-B692-2A96B3B51A38}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,6 +53,30 @@ Global {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.Build.0 = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|Win32 + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Win32.ActiveCfg = Debug|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Win32.Build.0 = Debug|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|x64.ActiveCfg = Debug|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|x64.Build.0 = Debug|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Any CPU.Build.0 = Release|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Win32.ActiveCfg = Release|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Win32.Build.0 = Release|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|x64.ActiveCfg = Release|Any CPU + {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|x64.Build.0 = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Win32.ActiveCfg = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Win32.Build.0 = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|x64.ActiveCfg = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|x64.Build.0 = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.Build.0 = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Win32.ActiveCfg = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Win32.Build.0 = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.ActiveCfg = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -56,5 +84,7 @@ Global GlobalSection(NestedProjects) = preSolution {439824F9-1455-4CC4-BD79-B44FA0A16552} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} + {4DDA7560-AA29-4161-A5EA-A7E8F3997321} = {02F461DC-5166-4E88-AAD5-CF110016A647} + {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {02F461DC-5166-4E88-AAD5-CF110016A647} EndGlobalSection EndGlobal diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000000..4e531e985d --- /dev/null +++ b/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj new file mode 100644 index 0000000000..dd35d6f469 --- /dev/null +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj @@ -0,0 +1,155 @@ + + + + + Debug + AnyCPU + {FB383E4C-A762-4FEA-BEFF-B3E6F4FA40C5} + Library + Properties + AspNetCoreModule.FunctionalTest + AspNetCoreModule.FunctionalTest + v4.5.1 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\dotnet-test-xunit.2.2.0-preview2-build1029\lib\net451\dotnet-test-xunit.exe + True + + + ..\..\..\..\Users\jhkim\.nuget\packages\Microsoft.AspNetCore.Server.IntegrationTesting\0.3.0-preview1-22821\lib\net451\Microsoft.AspNetCore.Server.IntegrationTesting.dll + + + ..\..\packages\Microsoft.AspNetCore.Server.Testing.0.2.0-alpha1-21873\lib\net451\Microsoft.AspNetCore.Server.Testing.dll + True + + + ..\..\packages\Microsoft.AspNetCore.Testing.1.2.0-preview1-22815\lib\net451\Microsoft.AspNetCore.Testing.dll + True + + + ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.1.2.0-preview1-22821\lib\netstandard1.0\Microsoft.Extensions.Configuration.Abstractions.dll + True + + + ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.2.0-preview1-22821\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + True + + + ..\..\packages\Microsoft.Extensions.FileProviders.Abstractions.1.1.0-preview1-final\lib\netstandard1.0\Microsoft.Extensions.FileProviders.Abstractions.dll + True + + + ..\..\packages\Microsoft.Extensions.FileProviders.Embedded.1.1.0-preview1-final\lib\net451\Microsoft.Extensions.FileProviders.Embedded.dll + True + + + ..\..\packages\Microsoft.Extensions.Logging.1.2.0-preview1-22821\lib\netstandard1.1\Microsoft.Extensions.Logging.dll + True + + + ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.2.0-preview1-22821\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll + True + + + ..\..\packages\Microsoft.Extensions.Logging.Console.1.2.0-preview1-22821\lib\net451\Microsoft.Extensions.Logging.Console.dll + True + + + ..\..\packages\Microsoft.Extensions.PlatformAbstractions.1.2.0-preview1-22821\lib\net451\Microsoft.Extensions.PlatformAbstractions.dll + True + + + ..\..\packages\Microsoft.Extensions.Primitives.1.2.0-preview1-22821\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll + True + + + ..\..\packages\Microsoft.Net.Http.Headers.1.2.0-preview1-22821\lib\netstandard1.1\Microsoft.Net.Http.Headers.dll + True + + + ..\..\packages\Microsoft.Web.Administration.7.0.0.0\lib\net20\Microsoft.Web.Administration.dll + True + + + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + ..\..\packages\System.Buffers.4.3.0\lib\netstandard1.1\System.Buffers.dll + True + + + + + + + + + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + True + + + ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.2.0-beta4-build3444\lib\net45\xunit.core.dll + True + + + ..\..\packages\xunit.extensibility.execution.2.2.0-beta4-build3444\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj new file mode 100644 index 0000000000..b1ed5dc42f --- /dev/null +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4dda7560-aa29-4161-a5ea-a7e8f3997321 + AspNetCoreModule.Test + .\obj + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs b/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs new file mode 100644 index 0000000000..c8691bc91a --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Testing.xunit; + +namespace AspNetCoreModule.Test.Framework +{ + /// + /// Skip test if a given environment variable is not enabled. To enable the test, set environment variable + /// to "true" for the test process. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class EnvironmentVariableTestConditionAttribute : Attribute, ITestCondition + { + private readonly string _environmentVariableName; + + public EnvironmentVariableTestConditionAttribute(string environmentVariableName) + { + _environmentVariableName = environmentVariableName; + } + + public bool IsMet + { + get + { + return string.Compare(Environment.GetEnvironmentVariable(_environmentVariableName), "true", ignoreCase: true) == 0; + } + } + + public string SkipReason + { + get + { + return $"To run this test, set the environment variable {_environmentVariableName}=\"true\". {AdditionalInfo}"; + } + } + + public string AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs new file mode 100644 index 0000000000..aba16ea281 --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -0,0 +1,1074 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.HttpClientHelper; +using Microsoft.Web.Administration; +using System; +using System.IO; +using System.Management; +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"; + } + + 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 = null) + { + _serverType = type; + _iisExpressConfigPath = iisExpressConfigPath; + } + + public static void BackupAppHostConfig() + { + string fromfile = Strings.AppHostConfigPath; + string tofile = Strings.AppHostConfigPath + ".ancmtest.bak"; + if (File.Exists(fromfile)) + { + TestUtility.FileCopy(fromfile, tofile, overWrite: false); + } + } + + public static void RestoreAppHostConfig() + { + string fromfile = Strings.AppHostConfigPath + ".ancmtest.bak"; + string tofile = Strings.AppHostConfigPath; + + if (!File.Exists(fromfile) && !File.Exists(tofile)) + { + // IIS is not installed, don't do anything here + return; + } + + // backup first if the backup file is not available + if (!File.Exists(fromfile)) + { + BackupAppHostConfig(); + } + + // try again after the ininial clean up + if (File.Exists(fromfile)) + { + 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); + } + + if (File.ReadAllBytes(fromfile).Length != File.ReadAllBytes(tofile).Length) + { + throw new System.ApplicationException("Failed to restore applicationhost.config"); + } + } + } + + 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 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 + ":"; + 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 EnableWindowsAuthentication(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"] = false; + ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); + windowsAuthenticationSection["enabled"] = true; + + 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) + { + 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 System.ApplicationException("duplicated collection item"); + } + environmentVariablesCollection.Add(environmentVariableElement); + } + else + { + aspNetCoreSection[attributeName] = attributeValue; + } + + serverManager.CommitChanges(); + } + } + + 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); + } + + private static bool? _isIISInstalled = null; + public static bool? IsIISInstalled + { + get + { + if (_isIISInstalled == null) + { + bool result = true; + if (!File.Exists(Path.Combine(Strings.IIS64BitPath, "iiscore.dll"))) + { + result = false; + } + if (!File.Exists(Path.Combine(Strings.IIS64BitPath, "config", "applicationhost.config"))) + { + result = false; + } + _isIISInstalled = result; + } + return _isIISInstalled; + } + } + + public bool IsAncmInstalled(ServerType servertype) + { + bool result = true; + if (servertype == ServerType.IIS) + { + if (!File.Exists(InitializeTestMachine.IISAspnetcoreSchema_path)) + { + result = false; + } + } + else + { + if (!File.Exists(InitializeTestMachine.IISExpressAspnetcoreSchema_path)) + { + result = false; + } + } + return result; + } + + public 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()) + { + 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(); + + serverManager.CommitChanges(); + } + } + 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 void AddBindingToSite(string siteName, string Ip, int Port, string host) + { + string bindingInfo = ""; + if (Ip == null) + Ip = "*"; + bindingInfo += Ip; + 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", "http"); + 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..f72d57c4b3 --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -0,0 +1,244 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using Microsoft.Extensions.PlatformAbstractions; + +namespace AspNetCoreModule.Test.Framework +{ + public class InitializeTestMachine : IDisposable + { + // + // By default, we don't use the private AspNetCoreFile + // + public static bool UsePrivateAspNetCoreFile = false; + + public static int SiteId = 40000; + public static string Aspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore_private.dll"); + public static string Aspnetcore_path_original = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore.dll"); + public static string Aspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv", "aspnetcore_private.dll"); + public static string IISExpressAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); + public static string IISAspnetcoreSchema_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; + + public InitializeTestMachine() + { + _referenceCount++; + + if (_referenceCount == 1) + { + TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Start"); + + _InitializeTestMachineCompleted = false; + + TestUtility.LogInformation("InitializeTestMachine::Start"); + if (Environment.ExpandEnvironmentVariables("%ANCMDebug%").ToLower() == "true") + { + System.Diagnostics.Debugger.Launch(); + } + + TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + // cleanup before starting + string siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest"); + try + { + if (IISConfigUtility.IsIISInstalled == true) + { + IISConfigUtility.RestoreAppHostConfig(); + } + } + catch + { + TestUtility.LogInformation("Failed to restore applicationhost.config"); + } + + if (!Directory.Exists(siteRootPath)) + { + Directory.CreateDirectory(siteRootPath); + } + + 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); + } + } + } + + if (InitializeTestMachine.UsePrivateAspNetCoreFile) + { + PreparePrivateANCMFiles(); + + // update applicationhost.config for IIS server + if (IISConfigUtility.IsIISInstalled == true) + { + + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + iisConfig.AddModule("AspNetCoreModule", Aspnetcore_path, null); + } + } + } + + _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 System.ApplicationException("InitializeTestMachine failed"); + } + } + + public void Dispose() + { + _referenceCount--; + + if (_referenceCount == 0) + { + TestUtility.LogInformation("InitializeTestMachine::Dispose() Start"); + TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); + + if (InitializeTestMachine.UsePrivateAspNetCoreFile) + { + if (IISConfigUtility.IsIISInstalled == true) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + try + { + iisConfig.AddModule("AspNetCoreModule", Aspnetcore_path_original, null); + } + catch + { + TestUtility.LogInformation("Failed to restore aspnetcore.dll path!!!"); + } + } + } + } + TestUtility.LogInformation("InitializeTestMachine::Dispose() End"); + } + } + + private void PreparePrivateANCMFiles() + { + var solutionRoot = GetSolutionDirectory(); + string outputPath = string.Empty; + _setupScriptPath = Path.Combine(solutionRoot, "tools"); + + // First try with debug build + outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Debug"); + + // If debug build does is not available, try with release 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", "Release"); + } + + 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, "src", "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; build aspnetcore.dll for both x86 and x64 and then try again!!!"); + } + + // create an extra private copy of the private file on IISExpress directory + if (InitializeTestMachine.UsePrivateAspNetCoreFile) + { + bool updateSuccess = false; + + for (int i = 0; i < 3; i++) + { + updateSuccess = false; + try + { + TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); + TestUtility.ResetHelper(ResetHelperMode.StopW3svcStartW3svc); + Thread.Sleep(1000); + TestUtility.FileCopy(Path.Combine(outputPath, "x64", "aspnetcore.dll"), Aspnetcore_path); + if (TestUtility.IsOSAmd64) + { + TestUtility.FileCopy(Path.Combine(outputPath, "Win32", "aspnetcore.dll"), Aspnetcore_X86_path); + } + updateSuccess = true; + } + catch + { + updateSuccess = false; + } + if (updateSuccess) + { + break; + } + } + if (!updateSuccess) + { + throw new System.ApplicationException("Failed to update aspnetcore.dll"); + } + } + } + + 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..f0422d2aeb --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -0,0 +1,830 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; +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.Net.Http; +using System.Threading.Tasks; +using System.Net; + +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); + } + 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); + } + } + public static void LogError(string format, params object[] parameters) + { + if (format != null) + { + Logger.LogError(format); + } + } + public static void LogInformation(string format, params object[] parameters) + { + if (format != null) + { + Logger.LogInformation(format); + } + } + + 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 == false) + { + 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 == false) + { + 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 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 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; + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs new file mode 100644 index 0000000000..d3d041effa --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs @@ -0,0 +1,234 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; + +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 GetHttpUri() + { + return new Uri("http://" + _testSite.HostName + ":" + _testSite.TcpPort.ToString() + URL); + } + + public Uri GetHttpUri(string subPath) + { + string tempSubPath = subPath; + if (!tempSubPath.StartsWith("/")) + { + tempSubPath = "/" + tempSubPath; + } + return new Uri("http://" + _testSite.HostName + ":" + _testSite.TcpPort.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 processPath 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(tofile)) + { + 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..504962b5ec --- /dev/null +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -0,0 +1,244 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using 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)); + iisExpressProcess.Kill(); + iisExpressProcess.WaitForExit(); + } + + 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 int _tcpPort = 8080; + public int TcpPort + { + get + { + return _tcpPort; + } + set + { + _tcpPort = value; + } + } + + public TestWebSite(IISConfigUtility.AppPoolBitness appPoolBitness, string loggerPrefix = "ANCMTest", ServerType serverType = ServerType.IIS) + { + TestUtility.LogInformation("TestWebSite::TestWebSite() Start"); + + string solutionPath = InitializeTestMachine.GetSolutionDirectory(); + + if (serverType == 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; + + // repeat three times until getting the valid temporary directory path + for (int i = 0; i < 3; i++) + { + string postfix = Path.GetRandomFileName(); + siteName = loggerPrefix.Replace(" ", "") + "_" + postfix; + siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", siteName); + if (!Directory.Exists(siteRootPath)) + { + break; + } + } + + TestUtility.DirectoryCopy(Path.Combine(solutionPath, "test", "WebRoot"), siteRootPath); + string aspnetCoreAppRootPath = Path.Combine(siteRootPath, "AspNetCoreApp"); + string srcPath = TestUtility.GetApplicationPath(); + + // + // Currently we use only DotnetCore v1.1 + // + string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp1.1", "publish"); + + // + // Publish aspnetcore app + // + if (_publishedAspnetCoreApp != true) + { + string argumentForDotNet = "publish " + srcPath; + TestUtility.LogInformation("TestWebSite::TestWebSite() StandardTestApp is not published, trying to publish on the fly: dotnet.exe " + argumentForDotNet); + TestUtility.RunCommand("dotnet", argumentForDotNet); + _publishedAspnetCoreApp = true; + } + + // check published files + bool checkPublishedFiles = false; + string[] publishedFiles = Directory.GetFiles(publishPath); + foreach (var item in publishedFiles) + { + if (Path.GetFileName(item) == "web.config") + { + checkPublishedFiles = true; + } + } + + if (!checkPublishedFiles) + { + throw new System.ApplicationException("web.config is not available in " + publishPath); + } + + // Copy the pubishpath to standardAppRootPath + TestUtility.DirectoryCopy(publishPath, aspnetCoreAppRootPath); + + int tcpPort = InitializeTestMachine.SiteId++; + int siteId = tcpPort; + + // + // initialize class member variables + // + string appPoolName = null; + if (serverType == ServerType.IIS) + { + appPoolName = siteName; + } + else if (serverType == ServerType.IISExpress) + { + appPoolName = "Clr4IntegratedAppPool"; + } + + // Initialize member variables + _hostName = "localhost"; + _siteName = siteName; + _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"); + + // copy http.config to the test site root directory and initialize iisExpressConfigPath with the path + string iisExpressConfigPath = null; + if (serverType == ServerType.IISExpress) + { + iisExpressConfigPath = Path.Combine(siteRootPath, "http.config"); + TestUtility.FileCopy(Path.Combine(solutionPath, "test", "AspNetCoreModule.Test", "http.config"), iisExpressConfigPath); + } + + // + // Create site and apps + // + using (var iisConfig = new IISConfigUtility(serverType, iisExpressConfigPath)) + { + if (serverType == ServerType.IIS) + { + iisConfig.CreateAppPool(appPoolName); + bool is32bit = (appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit); + iisConfig.SetAppPoolSetting(appPoolName, "enable32BitAppOnWin64", is32bit); + } + iisConfig.CreateSite(siteName, RootAppContext.PhysicalPath, siteId, this.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 (serverType == ServerType.IISExpress) + { + 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); + } + + TestUtility.LogInformation("TestWebSite::TestWebSite() End"); + } + } +} diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs new file mode 100644 index 0000000000..9a8b2ee960 --- /dev/null +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -0,0 +1,293 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.Framework; +using Microsoft.AspNetCore.Testing.xunit; +using System.Threading.Tasks; +using Xunit; + +namespace AspNetCoreModule.Test +{ + public class FunctionalTest : FunctionalTestHelper, IClassFixture + { + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(ServerType.IISExpress, IISConfigUtility.AppPoolBitness.noChange)] + [InlineData(ServerType.IISExpress, IISConfigUtility.AppPoolBitness.enable32Bit)] + public Task BasicTestOnIISExpress(ServerType serverType, IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoBasicTest(serverType, appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(ServerType.IIS, IISConfigUtility.AppPoolBitness.noChange)] + [InlineData(ServerType.IIS, IISConfigUtility.AppPoolBitness.enable32Bit)] + public Task BasicTestOnIIS(ServerType serverType, IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoBasicTest(serverType, appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0)] + public Task ShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime) + { + return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "a")] + public Task WebSocketTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + return DoWebSocketTest(appPoolBitness, testData); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationAfterBackendProcessBeingKilled(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationAfterBackendProcessBeingKilled(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationAfterW3WPProcessBeingKilled(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationAfterW3WPProcessBeingKilled(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationAfterWebConfigUpdated(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationAfterWebConfigUpdated(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleApplicationWithURLRewrite(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleApplicationWithURLRewrite(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecycleParentApplicationWithURLRewrite(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecycleParentApplicationWithURLRewrite(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task EnvironmentVariablesTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoEnvironmentVariablesTest(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task AppOfflineTestWithRenaming(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoAppOfflineTestWithRenaming(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task AppOfflineTestWithUrlRewriteAndDeleting(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoAppOfflineTestWithUrlRewriteAndDeleting(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task DisableStartUpErrorPageTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoDisableStartUpErrorPageTest(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task StdoutLogEnabledTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoStdoutLogEnabledTest(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task RecylingAppPoolTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoRecylingAppPoolTest(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task CachingTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoCachingTest(appPoolBitness); + } + } +} diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs new file mode 100644 index 0000000000..d1cc5e0f24 --- /dev/null +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -0,0 +1,1375 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using 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; + +namespace AspNetCoreModule.Test +{ + public class FunctionalTestHelper + { + private const int _repeatCount = 3; + + public enum ReturnValueType + { + ResponseBody, + ResponseBodyAndHeaders, + ResponseStatus, + None + } + + public static async Task DoBasicTest(ServerType serverType, IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoBasicTest", serverType)) + { + string backendProcessId_old = null; + + DateTime startTime = DateTime.Now; + + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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.GetHttpUri(), + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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); + } + } + } + + public static async Task DoRecycleApplicationAfterWebConfigUpdated(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleApplicationAfterWebConfigUpdated")) + { + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 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(500); + + string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; + string backendProcessId = await GetResponse(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), HttpStatusCode.OK); + 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 GetResponse(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), HttpStatusCode.OK); + 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(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoEnvironmentVariablesTest")) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + DateTime startTime = DateTime.Now; + Thread.Sleep(500); + + string totalNumber = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetEnvironmentVariables"), HttpStatusCode.OK); + Assert.True(totalNumber == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetEnvironmentVariables"), HttpStatusCode.OK))); + + 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; + Assert.True(expectedValue.ToString() == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetEnvironmentVariables"), HttpStatusCode.OK))); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestBar", "bar" }); + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + + expectedValue++; + Assert.True("foo" == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ExpandEnvironmentVariablesANCMTestFoo"), HttpStatusCode.OK))); + Assert.True("bar" == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ExpandEnvironmentVariablesANCMTestBar"), HttpStatusCode.OK))); + } + + 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"; + 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 VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); + + // rename app_offline.htm to _app_offline.htm and verify 200 + testSite.AspNetCoreApp.MoveFile("App_Offline.Htm", "_App_Offline.Htm"); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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"; + 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(500); + + // verify 503 + string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; + await VerifyResponseBody(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); + + // delete app_offline.htm and verify 200 + testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); + string backendProcessId = await GetResponse(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), HttpStatusCode.OK); + 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 VerifyPostResponseBody(testSite.AspNetCoreApp.GetHttpUri("EchoPostData"), postFormData, expectedResponseBody, HttpStatusCode.OK); + } + } + + 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(ServerType.IIS)) + { + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri(), HttpStatusCode.BadGateway); + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri(), HttpStatusCode.BadGateway); + Assert.True(responseBody.Contains("808681")); + + // 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(ServerType.IIS)) + { + 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 statusCode = await GetResponseStatusCode(testSite.AspNetCoreApp.GetHttpUri("GetProcessId")); + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 503 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(ServerType.IIS)) + { + DateTime startTime = DateTime.Now; + + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processesPerApplication", valueOfProcessesPerApplication); + HashSet processIDs = new HashSet(); + + for (int i = 0; i < 20; i++) + { + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + int id = Convert.ToInt32(backendProcessId); + if (!processIDs.Contains(id)) + { + processIDs.Add(id); + } + } + Assert.Equal(1, processIDs.Count); + } + + 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(ServerType.IIS)) + { + 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 VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("DoSleep3000"), HttpStatusCode.BadGateway); + } + else + { + await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri("DoSleep3000"), "Running", HttpStatusCode.OK); + } + } + 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(ServerType.IIS)) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse(requestTimeout)); + Thread.Sleep(500); + + if (requestTimeout.ToString() == "00:02:00") + { + await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri("DoSleep65000"), "Running", HttpStatusCode.OK, timeout:70); + } + else if (requestTimeout.ToString() == "00:01:00") + { + await VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("DoSleep65000"), HttpStatusCode.BadGateway, 70); + } + else + { + throw new System.ApplicationException("wrong data"); + } + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoShutdownTimeLimitTest")) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + // Set new value (10 second) to make the backend process get the Ctrl-C signal and measure when the recycle happens + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", valueOfshutdownTimeLimit); + iisConfig.SetANCMConfig( + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", + new string[] { "ANCMTestShutdownDelay", "20000" } + ); + + await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); + + // Set a new value such as 100 to make the backend process being recycled + DateTime startTime = DateTime.Now; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", 100); + backendProcess.WaitForExit(30000); + DateTime endTime = DateTime.Now; + var difference = endTime - startTime; + Assert.True(difference.Seconds >= expectedClosingTime); + Assert.True(difference.Seconds < expectedClosingTime + 3); + Assert.True(backendProcessId != await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK)); + await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + } + + 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(ServerType.IIS)) + { + DateTime startTime = DateTime.Now; + Thread.Sleep(500); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogFile", @".\logs\stdout"); + + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK))); + + // 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK))); + + // 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")) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + string arguments = argumentsPrefix + testSite.AspNetCoreApp.GetArgumentFileName(); + string tempProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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(ServerType.IIS)) + { + string result = string.Empty; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); + string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("DumpRequestHeaders"), HttpStatusCode.OK); + Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); + + iisConfig.EnableWindowsAuthentication(testSite.SiteName); + + Thread.Sleep(500); + + // check JitDebugger before continuing + TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); + Thread.Sleep(500); + + requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("DumpRequestHeaders"), HttpStatusCode.OK); + if (enabledForwardWindowsAuthToken) + { + string expectedHeaderName = "MS-ASPNETCORE-WINAUTHTOKEN"; + Assert.True(requestHeaders.ToUpper().Contains(expectedHeaderName)); + + result = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ImpersonateMiddleware"), HttpStatusCode.OK); + bool compare = false; + + string expectedValue1 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERDOMAIN%") + "\\" + Environment.ExpandEnvironmentVariables("%USERNAME%"); + if (result.ToLower().Contains(expectedValue1.ToLower())) + { + compare = true; + } + + string expectedValue2 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERNAME%"); + if (result.ToLower().Contains(expectedValue2.ToLower())) + { + compare = true; + } + + Assert.True(compare); + } + else + { + Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); + + result = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ImpersonateMiddleware"), HttpStatusCode.OK); + Assert.True(result.Contains("ImpersonateMiddleware-UserName = NoAuthentication")); + } + } + + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoRecylingAppPoolTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoRecylingAppPoolTest")) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + + // allocating 1024,000 KB + await VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("MemoryLeak1024000"), HttpStatusCode.OK); + + // get backend process id + string pocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + + // 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 VerifyResponseStatus(testSite.RootAppContext.GetHttpUri("small.htm"), HttpStatusCode.OK); + 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 VerifyResponseStatus(testSite.RootAppContext.GetHttpUri("small.htm"), HttpStatusCode.OK); + Thread.Sleep(3000); + } + + int y = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); + Assert.True(x == y && foundVSJit == false, "worker process is not recycled after 30 seconds"); + + string backupPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("MemoryLeak2048000"), HttpStatusCode.OK); + + newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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 GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + 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(ServerType.IIS)) + { + 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"); + + string result = string.Empty; + if (!useCompressionMiddleWare && !enableIISCompression) + { + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.False(result.Contains("Content-Encoding"), "verify response header"); + + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("barhtm"), "verify response body"); + Assert.False(result.Contains("Content-Encoding"), "verify response header"); + + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("defaulthtm"), "verify response body"); + Assert.False(result.Contains("Content-Encoding"), "verify response header"); + } + else + { + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("barhtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("defaulthtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "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(ServerType.IIS)) + { + 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"); + + string result = string.Empty; + + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + string headerValue = GetHeaderValue(result, "MyCustomHeader"); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + Thread.Sleep(2000); + + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + string headerValue2 = GetHeaderValue(result, "MyCustomHeader"); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + Assert.Equal(headerValue, headerValue2); + + Thread.Sleep(12000); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + string headerValue3 = GetHeaderValue(result, "MyCustomHeader"); + Assert.NotEqual(headerValue2, headerValue3); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + + public static async Task DoWebSocketTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoWebSocketTest")) + { + DateTime startTime = DateTime.Now; + + await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + + // Get Process ID + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + + // Verify WebSocket without setting subprotocol + await VerifyResponseBodyContain(testSite.WebSocketApp.GetHttpUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server + + // Verify WebSocket subprotocol + await VerifyResponseBodyContain(testSite.WebSocketApp.GetHttpUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // echoSubProtocol.aspx has hard coded path for the websocket server + + // Verify process creation ANCM event log + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); + + // Verify websocket + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetHttpUri("websocket"), true, true); + Assert.True(frameReturned.Content.Contains("Connection: Upgrade")); + Assert.True(frameReturned.Content.Contains("HTTP/1.1 101 Switching Protocols")); + Thread.Sleep(500); + + VerifySendingWebSocketData(websocketClient, testData); + + Thread.Sleep(500); + frameReturned = websocketClient.Close(); + Assert.True(frameReturned.FrameType == FrameType.Close, "Closing Handshake"); + } + + // send a simple request again and verify the response body + await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + } + } + + 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) == false) + { + // 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.GetHttpUri("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) + { + TestUtility.LogInformation(response.ToString()); + TestUtility.LogInformation(responseText); + throw; + } + } + + 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 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; + } + + private static async Task VerifyResponseStatus(Uri uri, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) + { + await SendReceive(uri, null, null, null, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData: null, timeout: timeout); + } + + private static async Task VerifyResponseBody(Uri uri, string expectedResponseBody, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) + { + await SendReceive(uri, null, expectedResponseBody, null, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData:null, timeout:timeout); + } + + private static async Task VerifyPostResponseBody(Uri uri, KeyValuePair[] postData, string expectedResponseBody, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) + { + await SendReceive(uri, null, expectedResponseBody, null, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData, timeout); + } + + private static async Task VerifyResponseBodyContain(Uri uri, string[] expectedStrings, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) + { + await SendReceive(uri, null, null, expectedStrings, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData: null, timeout: timeout); + } + + private static async Task GetResponse(Uri uri, HttpStatusCode expectedResponseStatus, ReturnValueType returnValueType = ReturnValueType.ResponseBody, int timeout = 5, int numberOfRetryCount = 1, bool verifyResponseFlag = true) + { + return await SendReceive(uri, null, null, null, expectedResponseStatus, returnValueType, numberOfRetryCount, verifyResponseFlag, postData:null, timeout:timeout); + } + + private static async Task GetResponseAndHeaders(Uri uri, string[] requestHeaders, HttpStatusCode expectedResponseStatus, ReturnValueType returnValueType = ReturnValueType.ResponseBodyAndHeaders, int timeout = 5, int numberOfRetryCount = 1, bool verifyResponseFlag = true) + { + return await SendReceive(uri, requestHeaders, null, null, expectedResponseStatus, returnValueType, numberOfRetryCount, verifyResponseFlag, postData: null, timeout: timeout); + } + + private static async Task GetResponseStatusCode(Uri uri) + { + return await SendReceive(uri, null, null, null, HttpStatusCode.OK, ReturnValueType.ResponseStatus, numberOfRetryCount:1, verifyResponseFlag:false, postData:null, timeout:5); + } + + 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(); + var outputStream = new MemoryStream(); + using (var gzip = new GZipStream(inputStream, CompressionMode.Decompress)) + { + await gzip.CopyToAsync(outputStream); + 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(Uri uri, string[] requestHeaders, string expectedResponseBody, string[] expectedStringsInResponseBody, HttpStatusCode expectedResponseStatus, ReturnValueType returnValueType, int numberOfRetryCount, bool verifyResponseFlag, KeyValuePair < string, string>[] postData, int timeout) + { + string result = null; + string responseText = "NotInitialized"; + string responseStatus = "NotInitialized"; + + var httpClientHandler = new HttpClientHandler(); + httpClientHandler.UseDefaultCredentials = true; + + 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.True(responseText.Contains(item)); + } + } + Assert.Equal(expectedResponseStatus, response.StatusCode); + } + + switch (returnValueType) + { + case ReturnValueType.ResponseBody: + case ReturnValueType.ResponseBodyAndHeaders: + if (responseText == "NotInitialized") + { + responseText = await ReadContent(response); + } + result = responseText; + if (returnValueType == ReturnValueType.ResponseBodyAndHeaders) + { + result += ", " + response.ToString(); + } + break; + case ReturnValueType.ResponseStatus: + result = response.StatusCode.ToString(); + break; + } + } + } + catch (XunitException) + { + if (response != null) + { + TestUtility.LogInformation(response.ToString()); + } + TestUtility.LogInformation(responseText); + TestUtility.LogInformation(responseStatus); + throw; + } + return result; + } + } + +} 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..b776fd130a --- /dev/null +++ b/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs @@ -0,0 +1,336 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using 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..f4bde38b26 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public class Frame + { + private int startingIndex; // This will be initialized as output parameter of GetFrameString() + public int DataLength; // 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 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..c3033ade4b --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +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..4f9f78f75f --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace 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..1410bda6e8 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs @@ -0,0 +1,370 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using AspNetCoreModule.Test.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections; +using System.Threading; +using Xunit; + +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 Frame Connect(Uri address, bool storeData, bool isAlwaysReading) + { + Address = address; + StoreData = storeData; + + Connection = new WebSocketConnect(); + if (isAlwaysReading) + { + InitiateWithAlwaysReading(); + } + SendWebSocketRequest(WebSocketClientUtility.WebSocketVersion); + Thread.Sleep(3000); + + Frame openingFrame = null; + + if (!IsAlwaysReading) + openingFrame = ReadData(); + else + openingFrame = Connection.DataReceived[0]; + + Thread.Sleep(1000); + + IsOpened = true; + return openingFrame; + } + + public Frame Close() + { + CloseConnection(); + Thread.Sleep(1000); + + 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); + int nextFrameIndex = 0; + while (nextFrameIndex != -1) + { + TestUtility.LogInformation("ReadDataCallback: Client {0:D3}: Read Type {1} : {2} ", Connection.Id, frame.FrameType, bytesReadIntotal); + ProcessReceivedData(frame); + + // Send Pong if the frame was Ping + if (frame.FrameType == FrameType.Ping) + SendPong(frame); + + nextFrameIndex = frame.IndexOfNextFrame; + if (nextFrameIndex != -1) + { + tempBuffer = tempBuffer.SubArray(frame.IndexOfNextFrame, tempBuffer.Length - frame.IndexOfNextFrame); + frame = new Frame(tempBuffer); + } + } + + // Send Pong if the frame was Ping + if (frame.FrameType == FrameType.Ping) + SendPong(frame); + + 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 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() + { + Connection.Done = true; + Send(Frames.CLOSE_FRAME); + } + + 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) + { + if (frame.Content.Contains("Connection: Upgrade") + && frame.Content.Contains("Upgrade: Websocket") + && frame.Content.Contains("HTTP/1.1 101 Switching Protocols")) + WebSocketState = WebSocketState.ConnectionOpen; + if (frame.FrameType == FrameType.Close) + WebSocketState = WebSocketState.ConnectionClosed; + 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..7c484af7de --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.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..df0e99789b --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.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 bool Done { 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..413b0a3c69 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace 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..79a711fa56 --- /dev/null +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace AspNetCoreModule.Test.WebSocketClient +{ + public enum WebSocketState + { + NonWebSocket, + ConnectionOpen, + ConnectionClosed + } +} diff --git a/test/AspNetCoreModule.Test/app.config b/test/AspNetCoreModule.Test/app.config new file mode 100644 index 0000000000..d36d43e498 --- /dev/null +++ b/test/AspNetCoreModule.Test/app.config @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.Test/project.json b/test/AspNetCoreModule.Test/project.json new file mode 100644 index 0000000000..4fb9bb1370 --- /dev/null +++ b/test/AspNetCoreModule.Test/project.json @@ -0,0 +1,35 @@ +{ + "buildOptions": { + "warningsAsErrors": true, + "copyToOutput": { + "include": [ + "Http.config" + ] + } + }, + "testRunner": "xunit", + "dependencies": { + "Microsoft.Web.Administration": "7.0.0", + "dotnet-test-xunit": "2.2.0-*", + "Microsoft.AspNetCore.Testing": "1.2.0-*", + "Microsoft.Extensions.Logging": "1.2.0-*", + "Microsoft.Extensions.Logging.Console": "1.2.0-*", + "Microsoft.Extensions.PlatformAbstractions": "1.2.0-*", + "Microsoft.Net.Http.Headers": "1.1.0-*", + "xunit": "2.2.0-*" + }, + "frameworks": { + "net451": { + "frameworkAssemblies": { + "System.Data": "", + "System.IO.Compression.FileSystem": "", + "System.Management": "", + "System.Net.Http": "", + "System.Net.Http.WebRequest": "", + "System.Runtime": "", + "System.ServiceProcess": "", + "System.Xml": "" + } + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/project.json.compileerror.txt b/test/AspNetCoreModule.Test/project.json.compileerror.txt new file mode 100644 index 0000000000..e3833d3d0b --- /dev/null +++ b/test/AspNetCoreModule.Test/project.json.compileerror.txt @@ -0,0 +1,38 @@ +{ + "version": "1.1.0-*", + "dependencies": { + "Microsoft.AspNetCore.WebSockets.Server": "*", + "Microsoft.AspNetCore.Server.IISIntegration": "1.2.0-*", + "Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*", + "Microsoft.AspNetCore.Server.WebListener": "1.2.0-*", + "Microsoft.AspNetCore.WebUtilities": "1.2.0-*", + "Microsoft.Extensions.Configuration.CommandLine": "1.2.0-*", + "Microsoft.Extensions.Configuration.Json": "1.2.0-*", + "Microsoft.Extensions.Logging.Console": "1.2.0-*", + "Microsoft.Net.Http.Headers": "1.2.0-*" + }, + "buildOptions": { + "emitEntryPoint": true + }, + "publishOptions": { + "include": [ + "web.config" + ] + }, + "frameworks": { + "netcoreapp1.1": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.1.0-*", + "type": "platform" + } + } + } + }, + "tools": { + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + }, + "scripts": { + "postpublish": "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj b/test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj new file mode 100644 index 0000000000..9ad0d6bf8b --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 030225d8-4ee8-47e5-b692-2a96b3b51a38 + .\obj + .\bin\ + + + 2.0 + 49212 + + + \ 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..dd0c68166a --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.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..2bcffdbb43 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -0,0 +1,123 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Net.Http.Server; +using System; +using System.IO; +using System.Threading; + +namespace AspnetCoreModule.TestSites.Standard +{ + public static class Program + { + 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 == "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 + { + throw new System.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); + } + + // Switch between Kestrel and WebListener for different tests. Default to Kestrel for normal app execution. + if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.WebListener", System.StringComparison.Ordinal)) + { + if (string.Equals(builder.GetSetting("environment") ?? + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), + "NtlmAuthentication", System.StringComparison.Ordinal)) + { + // Set up NTLM authentication for WebListener as follows. + // For IIS and IISExpress use inetmgr to setup NTLM authentication on the application or + // modify the applicationHost.config to enable NTLM. + builder.UseWebListener(options => + { + options.ListenerSettings.Authentication.AllowAnonymous = true; + options.ListenerSettings.Authentication.Schemes = + AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM; + }); + } + else + { + builder.UseWebListener(); + } + } + else + { + builder.UseKestrel(); + } + + var host = builder.Build(); + + host.Run(); + 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..b4fe3e5a15 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json @@ -0,0 +1,26 @@ +{ + "iisSettings": { + "windowsAuthentication": true, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39982/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ANCMTestStartupClassName": "StartupCompression", + "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..ab01b39ab8 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -0,0 +1,311 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +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 => { + // Considering the default value of ForwardWindowsAuthentication is true, + // the below line is not required at present, however keeping in case the default value is changed later. + options.ForwardWindowsAuthentication = true; + }); + } + + private async Task Echo(WebSocket webSocket) + { + var buffer = new byte[1024 * 4]; + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + while (!result.CloseStatus.HasValue) + { + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + 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 + { + ReplaceFeature = true + }); + + 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 + { + ReplaceFeature = true + }); + + 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 + "
    "; + } + } + } + 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..d7db198bab --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.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..6e64632365 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using 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..034b27e1f8 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.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.Authentication.ChallengeAsync(); + } + } + + if (context.Request.Path.Equals("/Forbidden")) + { + return context.Authentication.ForbidAsync(Microsoft.AspNetCore.Http.Authentication.AuthenticationManager.AutomaticScheme); + } + + if (context.Request.Path.Equals("/AutoForbid")) + { + return context.Authentication.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.Authentication.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.Authentication.ChallengeAsync("NTLM"); + } + } + + return context.Response.WriteAsync("Running NTLM"); + }); + } + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/project.json b/test/AspNetCoreModule.TestSites.Standard/project.json new file mode 100644 index 0000000000..cf5023ba58 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/project.json @@ -0,0 +1,46 @@ +{ + "dependencies": { + "Microsoft.AspNetCore.WebSockets.Server": "0.1.0-rc2-final", + "Microsoft.AspNetCore.Diagnostics": "1.0.1-*", + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.1-*", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1-*", + "Microsoft.AspNetCore.Server.WebListener": "1.0.1-*", + "Microsoft.AspNetCore.Server.Kestrel.Https": "1.0.1-*", + "Microsoft.AspNetCore.ResponseCompression": "*", + "Microsoft.AspNetCore.ResponseCaching": "*", + "Microsoft.AspNetCore.StaticFiles": "*", + "Microsoft.Extensions.Configuration.CommandLine": "1.0.1-*", + "Microsoft.Extensions.Logging.Console": "1.0.1-*" + }, + + "buildOptions": { + "emitEntryPoint": true, + "copyToOutput": [ + ] + }, + + "publishOptions": { + "include": [ + "web.config" + ] + }, + + "frameworks": { + "netcoreapp1.1": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.1-*", + "type": "platform" + } + } + } + }, + + "tools": { + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + }, + + "scripts": { + "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + } +} 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 0000000000000000000000000000000000000000..4eff83ac8ecd1c45201eac9d75a9998d1830fdd6 GIT binary patch literal 289940 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

    Q^ab?B?668y%-)L8k znuV`;z7WT>L*ce}eX4-WF(psXmd9tK96GeD{2O3!cRSA6Z8d-{X$>5a=02=<%fqM= z9h0~qqcNZ8FgG{f2hoClHmrGG!^P-#-4BQGlp5dA6B5iYFgv0e3TM zd@BDB=JL$t{O>-sfv5)79+JQst${LRE?rTNh=i=lHdadTX4?F510P+X9mqVbJ6y13Bob?P4`yPU+ z0j4$rU(RoQZFxl(t-h(RLN!3F+OGf8LJGn*>Bmr_9pJgDc4*h*|-X!e?lK z4vgo8O`mYoQpgTI*I=V{(YuyHY%i}iW_lCXGh#skJ$}EG)4xJiIRUE2t;t8oT#R9H_Rux?ex7}tg z<;hRmL`l2KFI)A`Qm~R|bXRKns<5JZy`SM{oePtM=*wkVYE={vu?}5`eloAhGy1#1 zK8$|Fvmgy*WhEgrqG(02LrrHlxAp}m%Ympkj@`vr70$Tp+EdVbwe?G&&UGZj-g_R} z2t+}cgfIad2k@DA3aIHVgHPnGB1m-tuTIASY&;7e>q5ROX8nn<}kO9<=ik*g0_+iV!1fE1MLizyyefK7n9Xs~=GCgzpZ<>_Sb#ubQqyP5&^jFA&5k*x5vg-Oxc|3{|$;Mhoo|jZ;Z<$``(L$2=oI zDu|rkP~4a~|GU|-VjNW2(9K2^nbpT1ra^b%3f%3dudgpgR#9NwKm{A`4|gjzMLRpu zYM|OoNm*nTjhTO|j9KRV%*E|U`vzhT8SQywmRCRzZqEa=j9P>)R}N>95fM8Gy95?J zjbr!1((z}tA)0BN0eLbhz6LL}Xew)pkZojz+qQ>V&3s7^tDUN_P+DgUl8U$0O#a%a zP@tP!IurrY1U>esOc2xBNfLl)cmuXwYTn|I>rAY{CUpedenSS4gaRU24K0ON2fAri zDStIrehcoMG}lWgXqyizXAuGI2#+E~gAi&q5un>9p_(j&It4&$r(xz2p0yfxFmhSO zZ!9i?XWs&*oyn=V)MnoQZg!7NszM%qT=9(g-TF~U=kNCQRW4nM!`G!fu0Px@q2Aly%+<>h$hk}t9qQOlR;3<+y3ZzLvlLhW-@Ix4ma@4n+(uxo zx}~fa3WUurB)xq8DnQp3V^Vt4A$UGLCs2|l?eMde!m26oH8S8>Wf(ywNm6xOOP4PH z_c`aS>iBgqtV@q>dnRcl2Oanq%u zVT7I!m9<&PI+-R@6Xq8#1bHmYaaQvHK(HjBsIp5teVKrFe+0mM%O$luUS5(G{3Q+$ z#eq_vr1!wu4*1j$6b)$Q@MU=^WqPvf0V5`wlOb*bT{FAoC&^WP#!&N3Ibu!zH`RFV zIo)0mjJ6w;Qr6v{IhW84iezz)S)&&IM}1RFoXn_^8>mLh(zm*>-JR3ZBUnmi-Z}j`EA@a~fuhj~S5R zZoGQBeSUrnT!;KH0K3FFSzh*2;BJgycO~vt5_r3tHj@?4``3&}dp+HJA3h-B<7@o{ ztm-FF)sIzM5}+#L6{aP0z`v=09^9U1phvb-RU<*R^SQ(fTct}N2Tg>wX93R8csE5? zozR%m)exxH1vEuzxD$gn((UCc&`di0Uc0CVqKb7WmG8!2u#XK6w@Rz97Mor zrG?Ka{$WYuJP7S7M#jlT(g(viz{{>Z-OKkYV@sE2DRXx}wo3l<6+=9SzuN)0oAXF2 z?wPE)HAXR?fmi&st9q(**Wd4W1gMIzyqLQDV!twrZdQdpEvrV@9xO{RxfTNkR;wSR zg_g3aPd);CcVWpC`q^4!%Lc;AqzYL}0m?!cka)?WER2}%I-`SDG&^Oqps= zqKJsy@qJHJt{}qQsq}Jiv+OuFP-5rK4BE%118@#(#(ty^Z0q=qQ;0q2%qQHL!f7Sb zD)d(QBg|`*@u%7j8O6ach8maz;6yB{R0Z9FIIH61w<>sQ&H+z}D{z#XTTUs(CLfw6 z2pJg#i9(%LQQvbtA{4sI)~>A)x)I)kN4|VMvc!qlm`zV0R9jGc?4jbYk97NiOsWlRuCT4a7`g2*Pd0uAB!`C5i-vPEw^nR3opC%%3Hw$lU z%H1(kjx6LX&GGxaH@GyJ&+6l;jf#hKd8POyQ@1ZC?qp4PAzWMM3c&nF$LlKOv9b%M zRrpJqCuNtp14IcLduS~+jN-D;1Qg|bluFE1j$TAAjm1+(QouyWC$T{g3s?aMXZj&z zc1?L+%_UPo-c^y~b#QBnD(3w1zU4W&so|@vr#B96Ys;VWT_NHmZS;r8ew&*drbC!1 zLAMG)rUDmg4l)6}pc_niHnZ?SNO&nWadEkrIs63FN)vAt{i`3p{K&bvspo-=y3%f@ zyn6CqX8UK>O!e~F_8%;sSwf;!(6N=WCvbIJ=>aS_Tkh8c1VV<|xVcSRFK1hou+~(Ncm~xsMJ<>9^ByOrwxDC#EL9#Azkawzo9b(OlvvYRmAgVVo4u&J3O+bZ#qiSUm1V7p%V^c-0_c`_34s&s)Y}3T zdC)BwAMF`i+Kzy2WOhy?07S+L8(>7^YjNarlZ`iNZ3H2**O!+E=Cx(tb{^0fpPxOoMQKK^4p0`06#$I58?tq2-~1^+&(rk3ujRl0X4HahI@==K zPMf$J-b~y2CvZ1P!=vO^hOhK*%%&R~v^BdGP$*9&?UJpyS|>g5tPr~i>)<{TFl$8J zFQvFX|0yW@U9zibP${-%|I$io=C|9BYF0Aqni^L=x8t7aL$(%9=lElC2@(sGYC{b7 z^nmx=i*pj)l0R~9N5eYdEKQ^nMwJcVWJA_sDc8&iR9D+Y%c z4_g1;5Sv;40*VBR<$>-TGq1_qY8PZCiD$zu+Nic@j6s=n9L7Wf92PT%&9U zuy`f<61mJG3@eqK$PIFTMbWVK#RQ1)Zu_Ugc6Es z7Wl$@KG3b0&`nD{luAwWsVXn!uV9K`EhvWh9BIv8)pQE-m`nU2al_jIW1f28%xfwc`z)&Z0I19>ygP+FyXX zO=B(CH)qYVH^CDYT6uGR3FQlpf@#ht8ys#Tl6zqzK4OO29IV!uy#ntUxe{qdFH zfZ#D*hQPc=;S0Ij3o8n0w4SM?Kq)mYvPA?+-+NOSR^hg(XAzcN;5U<8n=L?VfV-%^ ziL2hMjXE zS8i_h*wQ3iH4nOJS1O6cog~-E&b=;XBWn6-Ab?spGVP=pgM&)NbG7(IHU=eELcxw& zQ<3sG^`UaJrnzVtKjQn@LXs9Xh_7RId zvFNiY$gLR^A&EMv5Zz&}Inq+2Or8;@5*y=z(Tcx2K>}K=FL0u9l@aQ_V_h;rM%FQ6 z%V_>fPg2SBRr5qhyMS^E6bC_yk(pf{BJCukMHtsEQWj6QBC-|;x>*#&>=H$}K^A=G z@~`*A-4&%e^~WXmFAH@#QV3<%uS4a8#McErJ;JX| z7fF$GyQ*KCR4e1XFNjoJps&j}s}qQsZPs-eF&tN1bex97^0yY4bAfIUu7nYEbLp`V<<_t#L|YY9N^mnxs(K^`!^$76>E_m)4s7cSp)4t5 zgc4x`LsWEDo>vmJ)Rjnm4OLMIhPsJeE#%33z=S05$ce7NQFJ2f)}h0)H*Spkk$(oC zPl!jMB#p3M%=QbNx4tgR=f@1h%r+`CcVUhDsOEzrCe;AC7`0b|RmK4S zFE=m0R7}?%uKlrDX-X$X*z~^H;xLYbHq7*}hw<}*Du z7g4yg4=J7A6u&Xm*^a}N%bvcmJz8eRwvB*|6wHB)H2-EaYg$o3rC>*kCBiULo+c4!Vg}(^Qp|P4X)EGJ?0oeB!Dsq4dmB5qww0q2zeBkaJ{w zqc|es;VBz-1W;qSvZlaB`?wWrgnCoVG%HQ07l}DbYt@ayBQ+eW=&8{#k60H=`F(Tl zwRGv{yoM!&gQ3DNnI};)gp8V0=%YQrZ!9)5^&Dm1k3iT|b&NkDoO2GwXyfX*V-4*g zJYJKae;>eCsdkWiX`#>Xtv!+@V=bDQ-20M1yQ=8Rip{>PXG$MKmO6}h>p;>70CC1& zVioAKZwY#$S(UJi;}RR0=V;=xwhv1e!f=PPra&EeTDnnmsuQp$|>+bnj|6h6Cv z75LXgiOC&xL;Ge}8+ZtfVgVarSO07ZIqaO>f=p*9Gi|lsI3?&dwfuG)U1!pVNn;Ab z<1u4FH$gWq`elp)E$Y((BzCBSO$(*K>XE}(XpxA8No=w5ee-gc%ovYFL|21CBRMzH zfEZ>EkL!PYj?x_8%KNK5)`mMJ#e0*wI~-;DE1NLL{o%4~swg0am(RKQf>byw?Aw>Z z`+$U27CS;b%8X^cD-UBO#}BMvBUd*0B?@l>O|j#SO^;R zePc=@QOh1#s17^U&PfPTP3Hy0hJeO+_FMK8^RfVpu=`{dZiH(5pr1QR1zV3vv84&h zn1w6jg42p30=f1BYqtJWBW<__C6i@Ihc4JBk{kaYce|$eMU6&}|X~%V%0@1oApCJZy@X?cLm3 z3a%v#Q1gP&F;D_sHRD|o!e)gnA$FqpY;r3`BUtR^m2@X6cp&Czhk;c(XF`+T`9A0M*|C*s>g z+)8jaXlzj{&~%(_7duLK6xrGkW}=%mZ+nEZ;iDhO;D#B~7fpV5dKEl&Q{@5-Hzbkx z4&f+HZg#V?pIav88WyHsRz;zV)ir+lY}SPp-FXyAX*&>iEpLsI7Gh5pP5u!#!+VFm z28$tiFK7r&xGicC?o6HtEa5*k3xu}GYu~s%;=ubJ^AZo*fy&DXz;w% zvzPqOOHd!~rhq$YemkpQ7VcZEpc}P0-Lij$)6*@VJC3^jfxNrs9AHf!U@bY)=Zh#v z3l-3N%xYw+WN6GH{7MozDE_24FqqM#CLI4LGULQw1x6Y+uWP zXG1*y>ESl?9gIVH!BG@Y%J~z7dNxyZgoVGI;92cOJe=D_#v|jGv7Br`47v$9%*AD> zIrylC7su>-FTH?-ZrdGTn+x+rtH*6}XHS`vN({02C%M_1)y9^IA1tRUB`Y&(Zo~|< z4Y3iX!jQ9dfVNV2liIit`t?01n$(l$Au;t@4iv0tdlcvSta?!=(Ag&Kv0xE|8C}b zjOlu{O8t=nGlxLaEyXd=A@WU|JDp<4<9sI6-V>_6bd*>5>|2&o)ErNMH=v$^&Q%H% zx?LIZEL>^zx^d;r19frcuqxa3>@p0m_u3vb9fO@5NW9%hghg=z-xipHhUecJecOOY zGH16AaFV6g3@ovDm@0v#s-X*FOQ&4`@7Br2kkG9#0C-|YA*f<0MjGu@jdXMCwI|bX zVd0qDG%nRVlr-@yp^!1wwnLP|5(@<||JcUZ3Ef{`B5MfEB7M zqOm0?4NzSdECf9|i5=;hf&O<{{_pbxJs0z=5K|s^>m)oyD|liurevaLDpmZaDXE1e z8hO{}h}(Y&gwFNvGp?2)OAF_7bDPAzMd-4E*>Z>2m4mlz_iz`!+DJ!BiswjBHu>dF zI;=+TLP$O9oj@%+iP90wTkBQxju-q=7I=YuBGR+E;?EN6UsazU-1M8YjEC%Yp%Cs{ ze=QfrfD3ubHP#DTIiG?+m&B1-td7Y(r7;~tIEQv6OY%Cf{6oj-as|3gQ{O1R0w`5} zN#^M$&dkdiS|`g_=Z`Jb(yRy;CVvpRxiR8yS0o;D0kQg`uh5qHkgL$1vxk&+P0c7# zPyN3A7c0GJ)pTaI--GE{bMa5(L2^B_#m*^NnE2D}QocMH^wWQE4bcEh`z|0mN`Ex# z0L4c=cWw)~d|j5$j~Vf+{1%Tg$9&fnJf0PceJ=?dAW39UXDZgC$U?~ug+~ZJuG!p} znT|WQw49o&lXMd<4lqQ44+zYLWyiFWI8rr4m)l&0G>USov;rcGL0F*S*rIr4z8=Z*z9(N7{IOAur3~>hbN9OblIqHs#&< z=m~k*$`wmcV&M0j?@z-Q>Uyb!#SsDH$x)Wi|AZk;Rls8UxY42+EU$S}2aWAySDiSe19ENspgQ4V+A}3f2Qouw8tH^HEqPWQP#~7xG+6C6eMP zXz+Ud@3KgB@lF>8zN0HMuTwTsdQ&NaT`vrU;|Hy`@d#|hUg0!2$%BB!WLA0@CLxk$dPiY>eiQ~0lK{v zZQf(~Pl>yU1$oJ1T&p~ zu(rK-8zx&I_VoM|I_}Dn(Zu{X1a9k$r$8|RsCfdVuf3K8vgK=gQ{v!tg^1K|h`#AS zw}D#*6}gBW>nVrQT4E4e>OyuRD2I-&dkCSMlqcEv&2ciq27D}#J@ZfNl6$JEQPc*F zbg@yhCqFzrrWHEp7|s@dGNwpro(;Oj{{32ix8n1)aG?Vj`8{adKaRTz1R7eU4c0p} z!QD^?ZX^y%PLN5^%RHD%bgnQNlg3DoM~(||smaNhdv6N5`QpwwKlF^FyAj&|-i$qo z$fcz?YKKae3EIzJ*;%M>kiy{FKoVJSw|*<5_Hm$&&7N2r3Pzd+Zlf)wAj&>OfXdMh zN5L{jTCR{}V`T*N<4hyWGm*PYM(D8_B(>Xd*gQtEIwEj4H+x4Cw21S|6$aVTmyVfW z(5)e14Mas(mO=@B;=uhv6GgerR0bIn$j;5iASlQjls76c$vrKmVyXwk)MQ`pb;j}U z_2bXtteQ|gBQk^yACA4K;6D`FafjhSue3ydlkc7F6&AQuzS4rJ}1(YDjK+#o7cGpVGSx2ES3M zWfEKOrMouhA9S_A8!WZ55kMid+(t_(Cxc1fDX$aUY=rHlWwmN)50{9Z=Esk8;|^eY z)|$61RM27;3n`>|^q{{{Lo1OV5}MTOW2S8K?4uf+fy&JlvupXNe)gj$GkLk0B`r5s zDbJ#sv@hEp%H=RBSDj5epany{el65ulWK|Z&05zm(uukqAq*x} za)nSZLgoo`8n{ifGKy8jX2&QU2W0LLz0|WaYK`dZE{CoC_a<51lvRjV;t4$~^r4zV ztSu&WIo;}6BSf>%IOj4C8fZww=5nzj-t`IHya%y5_Y}tQ`42UkBg&aq_Ec_$kQ9a1 z{bP$c)u*yTi#o$`$p@9yv-f)!SS5;R?MOBtV;Sdq_ogz4@dT5QRz=Dc5xjMEKIXT_; ziVl$B-ySy zcf&EXY)xk&zm~;L8#c|Mw|w3PcZ;ET73ILwD<_~Ud9=S%m@m4)#<;mrNo-x37i*pd z!ywUS3#UohN%STKW;t$eQnxAy+pDtIPyn0gq}pi2G#EHm z?bEAkTiK34#e27v8?Bi;4c3thif+U1c-&=O{8(FIiempkO@cOW#_#5mGN@PrB&&{U zE;d-)17kRbx&=LNX$4j{{bSsK$$#AA#+5dcbE|=}N0>ZW*SG*$rAyo3dfqPy(rx`> zcr>93z%K_n2*v!`_LcEYr$7DN(Up*ouqSq!%Z!$*nI_2w^yUu=Z$AHHZk&9KH_rz` zDkpL8={O3WV>;K;rCdT9!--Zdgj%P^-NGo&qV!>Y6k2~Sb6Fm8(EfsU8Y5NiAh>N3 zr_#sRfV(2U>&LX)q0o{fJZ6=iW-brHlYv{w2r z$-ahXPZhgzmEWy|krpN*i-+Q5g4=}D5nNoK%C)Tn8$)r^?<>y39@F6J5P-G06fr}S zOObpz3J{1`-ys7Fm<}qFfE9N`%nmF9GX~HNof!yq=^AzU=`P@mXc{15T5n+7L;CA_ zDwwvOav%|yev#-IsnS!IZPl$5;%Je_Tt)L= zz!fch3q`R4yP4DjT7ii3%+*S!X3T_5bN$v0>Mp~uS9pL8ZbJlbVZz8=(#4mf9>y!c z#X~D#QSIs5SWlvWo$cU^^k>@qcR#G-)dG6sZq&*f86zD0pia}&Q3$=lOAF16$$TpB z0?{JJaAspPH6k?-P6w`DN>r$ClxtDH+Qbby|2f|3PLM8*u9z_cb>Y_qXV4J;vlZ)r zt_3C8!ysgJI>SZHSR2VQGYY0IDQNO_S^oR{fRh;_CV1ABI-s5@hLW3#o0K@4o+ZDY zAG8E^eUWN%@OuM%(YtBc&Sb5yc2xQSA{o9@CLv7=1;tJS9IV)=HA)ClwonBDn09sr zgPc5FbZ5hG1?O(ypPvai4*y)OD2h+AU=eg2t=^-8?s}+VrfEY*g+0ge6V426drw z;=>lFa_G=nnU%4jJm{73&{7}JU@c^#=$=+KTXj+^pe&|28nXzR@myK zHvd{H*viz23+*+@pY_y=x(Hkre z&-F%T!Gd;|uI6HbCjMj`x4$p+qt!nsj3fKHEY9TYS=Jw(_q;3dhx)roGaB}T0TGtF zUH#Wp{x~y}&^BRrvfSrLSvW$5q*=kS%g%1wK`&vLs{p?Lm=2m!NsxX5FEZhY0cvUp zEPEJ8yow-GTqJIqN2uLid69Jqd zE4=nTXdJulY)uynX~x{@hAV}0_`}@{C&>=xWGFL6tO!&I(6|pNMQTNhhjh^w1^Jl6 zCeFf;EtOt5)Ci$(xz~2P^H`B=2XUIGB;atXwtGT~MVJdTbD2?Gy%ACfo{_|;!Lfe0jkU3A9}y+{%9S@Z2-3*zF`kaw|JYPkv=2y4&o^6SYZXw zU-=k{?u=~i$xl;u)M}SrLW?4jxKw7Yuvuy!)um#4+K}kp6cgL*I6Z6l@SSkCn0@to zK{sl*llZwTiyd@}I-dV$%aklT9*K-yd!8XU+5%uSNx%&;Ja|M@C?3o5c7tB^xEHgBLj%%eU z+rD)GlCt#3yZm|C5n-~V(X#JCGuxVx2*&eME z+<^mqpZso^63^3`F648xiUQqIYaST`f9EEmJGP3T8@!BQ^))N*WQU6$cl7efs%P1) z;@+$raSE*;5c#V`Y+ZlTD5()P2;H+~-t-(kkv1s5xh>u;VgB!&K zH+_lE3dKlYSQ`Y5)~q#%*P$ES7A}kW>=E?qpwG33072E@YfUrwT^?lWpq>WLg1Zp# z=&?fn*5ROA{=Llo!wUv7cPs(jRBmqYFINnC#XfF|su`?Yb7uvVni|X-;_FLYz`H{$ zB+Y65sNLOB+PWygG)1cp(WK!ywb}R(4HHt<<)>Ber*K2Kb|clh&U*1q2y$l+I#`Z7 zz8VQBH3TtTMX3hbrnB}# ziuKEYy}~dx?#pI)`FUNO~k{L4pF{OyaZSi=H_DheTV6Xc%3I&BtP zH4e%(86GVN2dUDyl7a1uBy8#@cv7I|^$hN$9&5Us6bE9K61i#`mEkqB5*G@AP<~vj zM)bW3Gt$W_8hH+KhFBa`@6}RjWJ`Ty1T<@1U%|l{vrCwb2>~7Asnh~E$Bm@k3p>oZ zlXmxY;lEj!!nY!)tgGNTxBY3Q8L$T^j3O_k{n8_bGupi%W}O185lKk#FuoidtAZZ3 zc86@|8;yc(k6@p^?WQVF8iw`$nIYy~n#sQwECbgDBP}?BDD)=}w;8GepvYsgM!)BH zb#2QjqvAOt8w>{hGTZYcHUcKc<*gdw-G&}bH=~p?MMXXCY^nq9Z68c$i#)AH7oRZ5 z6m=gNc#D99aIaMCt4?^~<*s|T+daLolm3ojK%-EPO;wBfYUO;r5H=DD7jEm=#uz4U zjcAP_*{U``Lj=fN___W-G5|ET$Cbld8@+1pGivba*C~9xFw~A2R6058NO}<@HL(ZL!YwhN6A(whlQGtJ7 zRSEXE=6cok_jH%HU?oAbmL|500x6C;A&y+Y_rTrGw45?S9#?DF1^vLeO(cRbCV~^j zQ9_8_ywrdBg%iXrM@X)M*Es|Vv1i>2d8LuRLR5op@NT$}a%aL3VP{)eduKr#r`|}+ zESAhS0xf2Hjx7Rld+M5J!QDosjAH*TJK>BN(aCi$P5iq2f?}{o!3^(?yD@O3QEO0} zZe2iF?yq8+U8R4STh=0|35vC*?;$j0ktEJJTY)ZlI`*H5T4G0$$IBOgQO@jH}l z#?2AK1K2+7M2Zq;xfEFRmD;=^!_4{pn-9tf9hbmcaBHjP~7P_BgI;iK&6mot8sY8B1^&$ z5O*iP%BtVsG@a3e3K*uKOGUu*7J8ygf*0spX_p?cjiZEp0QzEO3r!6}Woj7`ZfFjd zY)B&ZVKAS47;H$Pfkr0-^No(Ft03+L-9!dWbp2Wu;A1u?LZbA{v71b95Ir)oct=2I zST7!(ktz_3GM^uGozxOJeohKj6vz5X<%TJnVl|r$s*?GY@ZC zcEmV{A|z4YFn2w&TA0VMog;E+wraF?^c zKX+jhw*l$9&CSc!`NCYu0{7KlMl}|Zv5<@(*DxtP|LBWZY3NjK-;)p@;u z=Yrc#;MVn|{4ri{BP%L6g6(RRvUdlLl8&D83+-mbv8C3+Ueo+O zE7{U5CGjMvYfz{dDB4wMVXF|8vsamUx+7~PR^}YbqdXR~H!4c0fVQ{tbHT&t6Tc%l zEbG=Pw2i?zk`ws5MGXWybOSAWx`6jzE(<>A0V`pjxa~`J(_5AHo#r#OEox zDbcLd6>1#7+lAZAl{jaciRRl^G&_}0Qr){K_p?t}#!#HVn(9w;$8FbGI)7?O8-tvSmM8*_asoqeTXNukOASa84-0t%$ z)V2k9op4)LQ61+?J20tP{pP+)Wj6F7nsAz~Uk))QPmpb}kF0S`H{g$U2Hfv&gW!Gj%3Q?8iM8=A#@eno+@@zexaRdHuV+uas1Wo~ilO|| zy)XG7D()ND;wD%-cS62^3ittbUM`c7Lq!DISE}I!x*N?p(?xsPEh2S_8rCmMGcLR= z%l@o^7qY_k|JYU(G;NT9r4mq;rN0Ai+b4D(B0$KRJb~%h^2|Tq^(kr_9FgVijKzN; zEdPjtXh-ly-M@}dZ!!eEmWv!xCAx!jQDT_`kZ3gNJOG!STZeA^8VnM|M;lYANTVeR_7>&8dYy69coBd}I4hZYmxG?yJRglFi9Aj>jH`+*_yOu>-OB%E1e+ zJ_`Jqq~P0|w15IEmSkAZD(d_bNHdg?l&Fx6^PHKs1;v2A1aJw^;4=y2l51n1a+2g_ z;gY+$f^YXGu5_F{<*tHm$ihn{ptLuDUU7Et{N2(U#_W^S5Ihf>Q|3TCkeZWh$5so9jiCW80AvTZjzl_P;(Nwp8e>**qP}FkRy=Y<97rkL6stNOGXo+`@N% zEEE(KUr`}u8fsvb(;h_CUDZDl-sv(;PYzMz2Hn=9m|IbNpW-=1*Q^MayA06#ndVY{ zmuFy{spq*Y3%=QMhBFnnYA*L6Om?Aa09V7m_=Dn1VSW{~hGbEnmjo~WBH0ne7Vm71 zQ7DDU4RoVCdX$UX;F(O^^k23f-MUdGJ8%(q;G>yQ=8C7xj`?mp7u zrkgHi@R1sLmPmtVGdk21W6LQBL=k*JpY2ir*aY0EdZKQ=sDv-r~c{dDqNQ3=fJlWP}vD- z4&XSxNBQ}4`MWX5G14$EY%mSU$KtvQ(_(68hKz~OIGuE-14>C1@6(~H^0noCW1#n$ zwpf$d^#OjgaYS^?^azmv@o78cN=`x89>pm7_+GPV-ZaGB!pk*|lxn9oZIRJ-IFU_z zYiQ>~72VWW-|X#(9&rx1ZI}>|mEbs$1}<mjT;%4eR8f#i z>0SC%eH99eZ!2k>`fO9CFxlMPY*HC@&6nETdut#SbeT{|yATlpO|zwzS_kTR-v7A- zZ0T?z(~5q+dbbJqrf*bfTRS`@?sn1#fpk9-MEdUcf0v7H7}X#GY6$p;yN$70k0e?W zhB*l&0Az$EDZdZo#^dmkMXemvQ zQ~Dz#M9nD(0hO^qtttl92``r+ue0?E%PQ@&WG1;#z)}fyH_0f*#qnvNe7ngZw z$&3{^E1aPeXKc1+n0{<(57fiyFSN(TnWFQq>vNu>jM+3I!=t0ZGVl5FI=(WM`O;~Y z`bzN^Nb-01^J!35mlC~-t)>K^vIVjw{3z8oEZ#(9OYSBpV?Z>FGtqVX;IO7~Estsw zOj>PzL>{+4+%1fbh^2cU!pj_1zrGgTFha%(cVm1PX6c2y(Lfk?R(6YCNTkP?Ujk4N zcb5g-J|L*t12gv}X<}tLeE>F*InZ)dTfcPDZ@|8JQ3BhcAx6WHsIQM?Vox-*=-?(u zKM^ASx!3Ct!3)aK7Ve}39t-xKwT-OUApktPCtEEc#tj7Cjg0VhmYa0COyr`)4#(QY ziCh_3Mb#(#*qNtIpUfHzy^Ronr*n1_4+F(pe*_B_hkup)&(QlT1dOx}x;0#sXDuZ* zr4MSJ-DKD@NGK{$cFM{Tq`&~&2coDEna8mluzS3}C>OxxPvKE4fp+e;_47H!KQ(-? zjd6?ag7!QnwiE8wGN5EV-6CqRu^w*6{m!Jg-gyl#<>x%BKbw5TZLp6Xf7=i@Ewv8n z4k0bmq!faQ4|Prg)YYoYU<~0&xMy=Kze#&(z4h?%$Pe5+W?l&pwwWQ|=A>rsDh}aK z9JIF)_XW$fs4!*9MNPg^n+cpuA>2)IwnXSs|5Bcbu3vhosJd^r*<_|Od!|Q)Ll-h1 z;pW-K(lsz5iRL{VgaVQpr^_C4g0?^{isO$*g^`|FJA&GG;Rw;cmU?tG$-V=}pqwP8 zRAT4iv~=T6-7A zB5qHTS9U;XB0aSMPiplS*TnWp@^RV<}E+v9w@GC-ji8LCsH3L@N&`3kqSEXiF{ zpB+R~cL;z9Vojwqs94<*aE8S6(B#e@SjIspWI~2_&CNV5a}IPZ<+qb72$`#pByLodHt;#}v`1m-T?IO1Tn=Ugczkh+(2QX*;1~ z_Y=l8*uHwCCjB<^pqq-UE951TJm^MFH5KJsZ~3KSKhqU!-2)2HtD*U z>)W#T>@imre{#~&jZJRMC8Y7@!biAgeJe+O1F+IH)#s)MLg%B zA2+~uPQZ?+#bjc_`CzQ|B_4i7RDydhUCHo_GaXe$`w&}4DN)c$+*XPXx1hs-KIvEU z0Fl&(Hm19;B`_=Wf$RuTF_DFUMI~^xBJNd}BS?=9W_<~ArY3|cYwgYre!g}Gyh}_0 zlV<`cDBQa#RyLbiRhb|EY0Aw7@3utQVPXUJ6rV#NfW_C$ak$$Nph)~c=wz77*Dm82 zbi-iLfpe&hcnY0#9E-7%ntSEBz`Fha+8|8JaKC)rAh;hLMt#(ApbHp8Sfn+#Bv9lRXM)a34 z&2^#)<2I2up&Ja7SCvp;O}L?{l2ex*QdM*d&Oif&rnFZ|DkTf_Bj5SNB9hg8Nfm_) zQ8$zd(P?+M`KF&Vj!!B=T-C!brNF@aib~R^%ZozXTxkY3Ha85t1}JZTxSND4dz_Cy zRTQY92L(pP0n|Aj4j9d1lqH;CqiBF6*F1bSmmtH^N&+e8*! z;cojFc*?q~?Y(iOWuK#HgKk#Zy^aSo%CA^srrTbk*xhrSgp-Q9lc(7+nqgD;y@((3 zym|~Wpe9y8{WT0?!&I6oSyulGD6lk3%*4uVXKfu%Waidh-zm`f^2bZ%z)-uO_x)0E} zr2uWAsg6tttSv{cx(Xdy!N7%L=*D`$Gt0Gf>GG2+(M%6KJe?t@2^}+dvl0q@hr3>y zxlGpvN9St^$3)C1JhueL?lp+=DG8NbT7grK~|8yC^t-d)(IaCoBKVCfCbOL>?zo_Zdi=HX^)Rf!Q zkG^l&+*kW+&<*_<2UaxiS)bsUe8)&s)=E1xey7PNk<`B>(J}^kTMrcOXct}sgIHCf zygMJ2y0WW|u<>r&ynz}C89Lg)L*-n7`YwDMxlrDS>V)f1%VU(B^mZvoNjs ztxu`Ab>B2hoqZ@@9PF>2!n}D9xO;{Elqogvj4kb3B?bc)dbl)Fw!px>gQyFXn2Dmj z92`JxUoO8>%d*bTTq=>#avUqCe=dZq^P06y=wt=?u1Dh?XtpTj3oVA*a0Kr_xmjS^ zD1KoS7DoDq*^SiTGzU?KP*YS(3%J<<@qFHikD($NJdRqCv8DMnf~?ez{@gDgWybhg zIgbIg<170|7y?UetJu5}#6IDQI?n`ZXFHVBAT876)F5a%tIX;z3uR29S|Rmi_!@4@ zyuLM8D-F(5|A*nsJ5tEwV=UV0`}n z=nVS^T=hj?UgYkxhFAJnxn3j%ng^OnG&{Qw*+_979JIVgq46LOS{ZR3<=R>z0h>tkSm9tD*I-xugYFD~+2%b}!$Q=C&bl<)xtWV8bh z4V2W4N@BCr->EAw5OfS5U1ta?c&{wpO2voS#u!@=kESi-PY(Xm3~lBRM{kukImHj0 z@2%6X%R=AEAS+hlVlxUT!&~wPM=h)V@AqF{-I8s%8@tcKR%HIB^-;|zPUiGsMb z6?$mXD;Jo$-OzxkfJ9`HY7*h$knpa$J;wiFXydKKDm@N~eBFX?E{&IIG2KnWCq*FG zeCWRs<(50fiwUb&>lt0!{(Rhy)!2{0-5}Ck;@TeK>;^55Th8OzmMblo*6_E3bxdZ_ zcMMUT%6#Ow!v>09(7C3Xq;cmdg#A&v}={^Nb4{$?l*hSE{M9s9Uf?9qO}n zQ*MPpNoK1gL6DTkfc=Le1=3W28n)dg%c`*yZ8{vD`ge=sh$Cjl;~Rd4Yy(#LAQ9~T zzwto&xrJnN;!>BkgnjB3Bf8Yhgbn%?dKj!RARJlz!`g?Mc*FefsT#DtAK9Q zuvwP)s3t?m7yaaZ&^1>Ol=IoOE@)Z4KyCTB?RK?Cr11w{{8? zP9YTKUoCSYHlr@A<%}A?WplvcCp3$Dy-b>t`&jQTW$z;fdjLUj#ORMxr@nXB<+2#4 zyaSbaI{th|g-;si8&VpsL{C1?0-@IiH@Bj6GB+x`s{H(+$IO^o$%}Nk3d;rQ(I}Bg zdHiEdNl>W$93gUmDS{=#n%~4;-{Tyl${8Ntwy++hqt}&jit{7rl zfKN)`=c*O~dI*lAqct!TFSJC{^(^YMqk)S8B-HR~LuHSswzYy=gn+e*3Z{flqDUY~ zhEo=-mCEh7oE2Jd+ayd-%Pm(V7U*mt;Sr}S6%6o~Zp*iv-U#=A=dy5F7TZd4_+kGK z;%@9ooirWQR*VX8Hy95pEu-_>z_p&*ANQKBEGVlGQjQO*h&ME3*$-T{kPX)e+()Eo zkuxd7Vg^+!QjXfFSXV&=%R*z)cCC;Q4QZ%>fT$H}t5|0EBdt_#js|CJjkm0WTkHLI ztRvcgoL-B|)wc(3`^}5;Hrjfp<-;#>;cRx#PQ5tp7;d+u=v1=7QWza?xsf5lPwF4C zy$N$+P5nYsv#JHf9Z*~a(V6)fEt0`g@P0F+m0#aV)dXD4X7k7*f!iv^>At~g^0H5Z z=s!x5=6YwwPz@a{%4~|`L`*+|Hpe<_7MF#w6G~cW}T@=-Pn(wfdo1ZmSg@M`8V@CvoK-85sKdmttBLdOV zW}E}Yv)V~?ok&`_m4>oZT3!dWr)YLMFC*Du6>O3!Cs1s0R6|o?zCjbM{q{|$Kg~C^-6NVz zvTXe>cdHLUOR9=<-}9K!(5YAzu@z>TZSsQ0`Sh^vo}hVM*dO9XJ`VVaA?U%+rgZ6> zIFkLvnTmN50rYJ7yP-dNywG?{gj+VZ`V1*=5Os2)NEJ63>KE#nA*oMeqs{?!IkAk= zgVPwGnG(P!5nCy(W0y8GUnV2Rg$n%?ii)gM0rT?b3i|tv3(8)r!);xzA(e2OcW2;$ zeced_{+rK%3fLJ*4rP9Tw;;gz0#@M}JBo!&;X9lSQOW8I>*hPHXU!jXmKT&TktnuC z-XBim<3@w9LP#kz+2^#Dv&7RDi6ZQ6O-L9wW_nI7R0 z3_T45+Fi4U1V9B#fBqKS?OM8&95YTH$e-`1)GD{Xj~t5%cS9vd*=|sXU>KW+(2x9W z3{>i=0TuhP@-GgQwbn2YK3Yw@VBN%h?Pey2ppmPMa|e^83$vaPgGO zjlEDL#ncf-f`VC!)(eV!C%DbYq!{Sn)=8NRD@gjDPmif{$R{1m3PFC2AkN3R@G!&| zI71J{yMa1$`{uF$?;&n25T0+MG-OzerCCeYD52^M?7@K49)^jNUelFHe{e3rPfnem zQw;kQ(VLv2XvMQrL1ZD^w(CVju21yZO#{wJE#hY%s=B)2kYarC8prtpBT0TO%YVDD zy&mI4d^`VCoau1WyKr;>Yr3P~nq$*5#BS+GL_GU!#a!%YE)fJgJwPSd2E>`t9GT}J zIoumb9h65&Y~V^OK((6rF3!<0A0WCi#u!lo%M#t{%>mT~Y-gH=g&CLIEdyjMx#aOQDpw!?mCiEOS^+_XoTgFUWkSQ&uQzwal4erlwRK$qDRmj5@DHxI^ z3QsyyRyBfV7mBb-?21SV&H`%Ii{q$1Tb(d6OU!;Yvkjgkv5+5Gfp^~-yUVTR6_ zluBoYlD2TpkQ%xpgaXYzO5MzWc`H(s*?ngcBkQo;f${ajX1PYVK^3Nc$zRTQ=pRq& zyLs2uw?SK%q*_-C8e1G=rJbYe`n@;=@`1YpQ7)V=%S0;DGdES#0?}43fl#wmVs7X^ zRXVs$Zn$l{cUv$|p54jx*asbc-j)pAc^9YHA?VdFy6s*_9JvrVNR-hB(Kje-QVn@) zD$z1a_a4m^v@(n1tbSuGRu+3G!S-&U9_#*=s6-+vhV%r+DT%|aA9$2be9iE&@yH41 zMuGxkaW{7FrawGzY8<6_i8K7UTneDu^q1f5EYo}9ZrI-`9>G1lM_Y|4pU75-Ez627 z;Nl%^gcO2B6wM#86xc|eD|n42D%=-ab{=NBB8J?k1kj+I&;j=-QC>p<}@S{lT4i(s2)PaTlUCW)gQ+~5-t2oXbuWiBXx z#O|g2ke;gN)vxWM*5je2m;)MPh;R^cVYCqm8fOO!$)gAVyCO+x+-K)3Kz(lXZG^vD z=UTm{ki2+rR-~R?aPH(EXSX>(xq&*@r|gzaEEPQ$sJ>`}xMQT0HB8~^O#^o`aAVT5 z$#O@XG@nc>4|ijb+ddZYJor?x3eSuJulCUiKTAvE_AiLYNyQOklF2YY) z#4})_?K%S;WSK9LhUkynU_r28g*Yg6BZV$FW#A6VmAs~^iXpoem7`dC`+8r88wMwz zKl+S2qA^WSjnZdJIZV+m;m%mYy*vWBU@9jlEaj2hGal{KMms1J!b1vumX1U z;W7ah(rj?aOY;?XK~9HcG60};8^q$C+D`$f6-sMN=k|4he_7CpaZmrYR@(W@5L4b) z%qAm)OtIfh={_x1%%2SRvaOI~PqvN!e$@<`i)t8l>sSsZ`GImN+J#e=dGl6PnW%H6 z$%s-r{ruf}q#+XdyG12*8+XK`PqPQ$I6t0lT?jtj`^=tD@Ettm?pn>y?FC)rVNkBM z9z=V2uHC%*%#_;Qo>4m)09in$znEqWxtUzs`ZRbViT7?mY^ljY2<)W`+$PKbk9KA| z?zJi2qr^ka63BS@h}>5;UR7EzkQ~tamzHm#vaH}X{TT<*8sy)Gf?~zX1`J{Tjumpw z4B_A(uakgxUfUS=H-rzT53MVnN<6}M0X>SPp;sODr?VTfGm-IxR=ZQDLDq<6WYjI_ z^s!kwI%)(7w~D=v2N=wyS#0NSEHrY|A$lkscd1+gJQw9zC$DWy6vDjQo|C$O2(DMF zbcZ|luu@0lj%UB6kA`%3=#=|aNOg;~DW>~Pw! zLs~(N!<*KvMu^FJkf7!JxxCh=lDq^6Ul{7zzg>ferOpYBCq+M|9lE3Si~h?W7&!UF zb2&}$Y0qG8OgP0h#EACnouF9MdhU}F({_(;D>m(`U|yrI{meJx1Jre#>%h;LBwpF@@L9^h*O|cN-+{c8VuGlj%Kb*!Zfzk4!;Z%8AW5mKtMHrD87T)J1ovE)WtrHU-~p54s2vz_P1y)*L50}#k8AlKOZo8rn5E~G*mjIyO|mRP$W_GEe))#Lcg}KeAMWe;o>PM z{aNFxFm4^sV2C58A=X6*+R>ufj(&hVUQf4$k)(jYvWIL7coQ~`W~L~9V4Se=lMWcY zaSmO>8rvSE`1(JGY?{YSwsVB(8H^-!Y7O1D)wZI7sx(e8=!UX35^j{_v$?fKjySG( zWQBv2*xiJjAn_=pPYPio(^sYg#I}xtf!TbW#nY4aDm{`dmudU+xRhLzBNr`CHPop4 z8L6Fjf@@g{=HGRg-)6}?&jWNt@8as$OOfcGeHgL3gl7Bit32-T5sEd02*Gs~Ji7p| zw5*ar<&HHC2i()GBkR%*8TV0~s>8hp0b3?bVRByXDh(iz-MffJ)>ETly>nR0m`iOp z=xzO<$+n^(c9@BGqpi%#6>57a(|ZTEDE$?o%q9;Li*#_K$gIT~7$~~U-w{lTvn;~d zRBcurmUzrM(Q&Yrl^tp-0dyfjbU_CcE?|sZRFk()yNU|gIt8op1U9Xv2|qbm9IH~2 zkJUip4GL|S=(ANIObORmG?w^8W^waXSo_vcVUMB$x>*zB_pg9L#R$KrwvZpqe6yZ9 zx2TO`Pk8}lK2-ik%|67*-uBA)gn`|y-KskNddEydS z&p?Uh2Cq-3_}9m;=3>p>cp5DW=MPy(MrPcY#dWVC;~jKmzf#RqNxf>ldx<=q1q`Z^ zrCMdo(SwnjaX%1FlDpC&!bETUEe367--{T~^Wjg|QV{HpsQ%v8zdUp+?sIYaI`H|R67`uE^vZXf~bCyUJ7->KX zWA~N9q|t&|%*_;2cO2Hy2>SCEvuc1Ua~e!@Z?1IO;5X;b2f1h)mSCO^e0Xe&Cx}$D zwZL-_TQ>Tx{a!9h9O2K%=f5P*v=*<(TG0hD6#|$QB2P6nb9fSjh}EE1J3qR1R1i|5pfQa2wNdXx< zL-yZUQ~nMzzJx;BAuYIDr0ce5D|Z}BLdtKI^6Zql$vPN&TCz1O}L_EvUJ z%}(gdi2gB)5|S_%B1^4;$>h!|ds*6*Dht_AjrXI(N{(a3ESYBG5K_+-aZHrYU_dWs zhWbLg>^4|be0{Z#{HLL@x3S+m+?z(pkmk7tM!ewps%iN*PLxEm>Qyh zkXqP?F*X{R#-Xs5Evp74o`+iFVd?zr+l)%XVJLX=CW^HUbs1mv>cEx6NmwWL6zdTu zj=DA8i%9OsHZkaitM*tK4UDs3o9Qdr+?1WR9PY`1Or7Z;hSSR2Ar2HQ6=UA2h3QR^ z^s1hD9DtHN@v8v`J2pv2b#MouGq^w9vvWf{~l52dwTh38=jKwiC5 zK1J+eFImn*S(|Pv8nF{qD8!mq6%VkQvivC5c%K#K3Prvt1-ZaV9E!yuyMX%a7TRfo z(D^>)S`)ppAt-^pt=JP^W=dq57s(|geI&{>=e{D&-VIASzg)q#W!((){&4|u(6HNj zJS(Ww&*W0hEv5v}KN(Mo;UB(5ci91eCz!7J@Wmsc?1A2lU4WbeCW9G3IM)^mBaGS> zt+wW%p1WmpPSKd)bcAk|kPV%b+snyT&J2ik4M6}h3oXo#+|E@uH$KM_;^vkqZ^z7B zO497Jt&P6_iS+JAZ2sEEy*?N?dZJ@@7cTSn?(07x@7*}MF@wZb8<^iMo5=`*dI`5@ zDO@`0u7-9sE}_nP$BK#CNG->^WJhJa%Er|^Ls+bk6lYmN3{#4jhz)h~RTx8lDT>`q z8>i=usM0wmCRlK5L-!r|WW{*7R&5$Bw=&ykI)#)5QwWVtd42LEdU6EhoK@Jw zgpx~lokD{61lcGs1lbfN-*NGz4XODDxBa6Q!w8ZMtgg_doI;<>R8*8$40NAD4p!+` zLSff9b}l`u5(+ziYD(Hf@kk|eFYCjbwwe2~^%GVX(yW^g3WD=hwjqO`#?YL#J(pTJRtn%E-Q|Yc9qCq!p$K@7Sof!fUCL`9v2PJEr(q|SBOdGx+ELrby?^e-GEO5DRA=qZu@KfM_w_py~kVKXZpL}Nopvl0wbV=S77Nn zwJ;R~6WOuI-nZ1&ow+tFrj8)jq%0Sub%n8}t3QC976&&Pd2(o_1c4Wt1>s_e6(!?> zU=N&9W1yD$IY#?Fsa|oULB7utBSaN@4M(&n`cn6$`Hzk#_3p_oUZ1}WIQoOz;)MTE z{op*SFD`vNxu9*zkc4r}eHY?5YyiW$yK7>zu|9YuKS^w^2}V=#xOFugirojrBZB z*bR5XS=b2Wbfi>Jl`h=v5z3kZUWlNIihixS4SRxJ-Q1utkma5~?QCj@VIs%`!Vd@` z;t(YI5LVYsrCFsRUEC*IfD0s`WlIcFZPBP`Ti4VC3ET?1vt3dH#Q{-2&E8glEkStt z^0HM63mH+cRc)owv7&_#mcEWNJTwk&M?ft&Z$E#oZNKH=5tPKSD~4`tfHQI)x{nn$ zZ(=03TnNnk4786Mlz&R!Vp5+7cPvKI)2JQD;*D1r?yB{7*7e?CmDW zvkD9%s!>E99-;V+Y%gz_$BlNs<==CL2VRnoRz29ufBdp6zng;_jp4{))I^dm0UkvI z&Bf4fAtuVb!92qE_S>bLMUE1Ga=_Aca|)c$&65BX;2$P z17RER(6gly-7MZP<|Ff#%a|4osiT3)Zv6rE>dIM)U6YM+pY$0~S!1#@v6LviZz&m0 z2c1BO29|6tB=2%z*#mAHGI47nuixz2hU5Chft|qR>~`3`L)@T)-Q1K1#!JZoO0GA} zm=8<71)ONt!MBQVvn(gwrQ`uyVwf3d?K$hf0qm1kF0Ty3Xv6l&4+MZE-P4|j=^%U$ zK6$6-y(!RP^tUPsse;u(j*_-<&my<+ zx=M}M`ebG_VoPD+FP1Sv_F-r0K4B%mGw*=Ij8j_?*so}Z=p)ZCL;^6tZg2EheZw^A=U$;ZaB)W`%F7_8+a-6&d4*77e}d zbmRKhpHaBmdvWXe#8-Dq1FK7w@?g?COm6{q@ypWO|D|INWyJ^wELM|}z#x`_m`4IB zB2O;OmCA>z6kEH=rAIk#Zf?M2_L*<0p8gg4+>l#PDa;Xn15qca^+x9TAb2{E=IR`UwEyAD^BX~h<*s<6RsuCPki~&=2!>7bgd&*Tz zK&5U&KE?Sw?3+^jafjlal``gkRI@cZULcH6hfCR%DZ{r z9@|8BMs$i>2+AmQDxIW0{W{1k27n_~TisO;{ z-Et`Y1eUIUQA?Qx?R1zSahdqWeTBG7)6Xb6xfwOa@?dB)?CZ?LI7y;e*s-(jzjsnKs z{E-FIIx)v}OE)*0=@Ogi-w-!YeQCC7|9*>?|5}`>gsS*0ak7loQj}EvT6qVZge&-u zMO-kAE#>}tiXw@gvQIT1Af#7$y_DAHF}MR6*`6S`hvGbUQ2HsUW#OY;9M=|cH-)N2 zHsee^(^XeGLjMCyUT(6e!sQ2Ag!eYSYUVugsPeh(`b2mEu3q6%ybEuxZEUm#7S23DH=0(2DbJZ2n1n70Al_U_tVxd(fZwLlq> zWWb$^pd!6(D^{qI_oEmcC_bqq5(D;DxOOUy^OZ4`V6)$eG3EXnok_z47o~OOgEA?jbm>3VH=P*1H z`*wsTx5a3rtIyLFX}qsOwn0C7MyT=Vxz{s+kErn$D$8h7GjF(RAzrhPr$ux3h)z;5 z6G2q(k6$2<;YH@iZwnN+dmw#cs5=$8QQF%=0aHlbn(;&`A%XM+_))EHSA{#d;^u z)=lC*$8$boaBd7}Kopn)!;|wfLq=If`VG4=Q8BVL*T5zauG|BuGL~O(+t2f@gMGka z22mh^oa;F^S{ULW6yC|%J;m~#8+@lJoKUCQve9zgcuNX_nBw=&&hRJfal;Z&IrqF= z)Q~8N(g`wh)TkUV`M=%2TT(YSHmfGQrgOc8oU7xrnF*MNiy8`LMukVnriviZuUZ9@ zsq6sJ8<0+!N|6CytKf@>Kw0%>Q&b~Hf2PcKRVM4TLc#-Xo7R4n(gPk@n>gkWZbYV_ zgb&GUi}%7b1+8`0IR47%W3jH)q0SCOS$B$vD>DZ7Y1m)FuQUw9|uE z`~FFvxvl-Z-jN}9t5q#NREJ?j$Wb(bwqf9N30s!$?Z_F9YBN0QBL+i!^CJ*}*t~Mj_<$1XlP@+0r8CccWRLJ3lG3uv#QEl+{N(#3t}% zeZ(&dKjxZ41Rd*&^1Pc~mh<*yufJP>D-D0MJkDE?d_y%txoSN3(dVFBH4hU9!nfVUgUKZ4CjBf& z8xzu{wQ!6X*t+6lk)J6ToHG+$+=krV+`=R#T$KDiUzTObu;ZK=P%q%R>R-TGuQ6A0 zo)V?bh(s8>?Ra{@PK@pwzuyrd_^^k(8)hN?ZkuGZvl~Witl%kVQvnFGxgnIWrB@`k z?3iKjPS^*e=;sBINVMnGDEW*(vROoI?K)&uP~btCXmO?#!AT{Ke1N%p-l&yKyS=rP z)jbA?LL*scwI2G1yLGXUikIZU`U%JGCx$jrW2v8}&`_0Pb@NN75=RK5h92fjQsi2v2e4unOq zyBS1~U{nhQbPM+}uX^5nPX4l)ib`!JHlZ}La@YMZFbhRYLCa>O7=1b)CToc}?Q9VM z0A22J-HjE6aj*q^pi^m5ZL<*Jp6JK%K_?ll$tKz3YMXUWpN(@2k{nc$u+ry8-wMzo zGSvlJDUJh8(t~o(cU5uoBvD>2&wf%wmF?8Mc{#~;T_$1EWhRg%8Zl_ql&ud;o2TDF z$thE=R-G->JoH=VKh;QCC%yoMU!9(rgVf}JmV3QfZ+5T&i{6EL1H0{e)SVt~`|}0+ zH=jPWK@phK?TAEhjy|8KN+p#f%$Kq36-Pebq8?RF+-bTiS{W|alBRX!6L_#Lv&5lL z+j6F~FpswT=}>4zxhGStc<8{N3!QH-2ara1QK7O2c5{0243SLylGs4PO$?mR+d-Z6;k9 zm12mK%a0?Dwv1$7_H!gJiP>}))cau&W5XasAk9&T z+CP9eW<~h?A$pV6?W8EZsv!wn`$p!Y2`BeR-`=RF*D%uMQiI#1^ueGLzC?TzCXY~_ zKL`*y$#@XuvpjS*Y{{lSxb4?GkJvlzj zr!k{Fi(_;`dOx5+h_vs3>R&;)uTV}H5sZ17S?CugezGag;D!6t z1hC4wOyMnAo)`7@Z$hdtP(rcf3AaZ&xalsNI_pq)!gN`}OmG7S#V1Rxd-V`w5X~=kHW*Ai3LvJA-Kw-d6M0VNxFns_6m0>EI zBh{XAIiqsndkz6Vdf;ky^)pc16T67q&q zTN}Fz_S_RhB&~ZK9|&a~#*S z;OTD8(&drFfNsmj$J~KK7-qJ5sG5w`o6DRBr%{fmiJ;pxJ_(kOXI#}!5NJrbSFZIF zsC7{}&d_XP0Et`+xd_az^!k_37KP_JGR;+Agro zTg6w?FkH`NG$9G0&_%UoL%&1osLONo(cDLKoZ2=o7jlJdE7)cBjuqvA62|-M9ZhLy2=zUhaE@ zL=VfJ9Gf5P|7jST>Y~mjl(8K*kc?b^p4NZHoG$bg(!B+f`tAy`Qgdk`1@ya$TT!;_ zZtfR7PvD#3ZUG}XxJuY>#NDn5-IlI#x2MD16jr%T>{dOI*=3rxROpnE&3fmA zdL2R<5>6$pG*j5x(V!4XkDPbe4&*6`v+S~j>>=!N47aCAqrQkeJ4Q=^?G$H9<9Z*2 zSm-%lxNVk&Ul&g@13HP2Ni$0QykNHtm455!DqAAChDQ$ry@rEh2=I>29}Uj*AhMN_ zpqrob!a+AyRI+fwqvdHN?i9j1OvG{#LPbVfS0j(w9hr6+-EFZ%1>oRe#4cVYJV>0> z@RhlCg^FGYnZxjQvDe%>AOH zyRq9yBGyir2|e?AhmC}ul7?~j1v{*X^p13H1$yHM!O&h6c@{tBJjuhb)58T7Kwu9#xw(C@O(*yF{FS8BSRQ&k)f?L@Wz0 z+j69qLC!8FbxeIUv_M@a!HKy@k30&dwX92^Q9%oJjAbEL z^7pSlN^aQ#!=3&kW`sqL7UI(9E(|utg|y~WCzfBzcv~sYat5PoSxrANwU0f zUCLpKIUh1qg-Bx}L#>76ukdg5ffWkm%hBmn#|c^()j$8RxxYtqMCn-v0SW%!>!HZi z|EbVX=uN{?%Xr}n@|!R=*CI8*OIdP5))J*#SlSws01Wh{$&aOhw*{1^tMuJcTzXXs zg%k$(aw#a*tthA;O#*!@dw5)oLY(L)*Ki{rba^DvQKgAubOxb0b{HZ|Hbi$fPx$zC zS;e=rZx8MFzAhXCM+~FA)ZYzbXGP(AnQW)j5YBlr<@1U0Zn`%&e$%(PGH2oRFd<)B zB|mydFxb%>PkP3qRWd*;oQo-R_kMM7O`Zbq)M6ADuI4DYh^wkTtonc2>x66X1g&qO zwqNq|{aSd3+gh!)V}fQC_y5# z$#=5xN|3rE{bNs@?`n*v+vpX7FA?mkk%osDR1{EMg&DKymv!HVFE-*=h_Az3R~o71 zfQKR5frh_g@)S~%4;4)U){Ah2U`+D7;Dg& zUWO20X>BIOsIZP>%qlpVU5QhBGGsiIXm@jKlZJ(=Ml-!cEI9s)IT?%C0KE zeGUPOw`z9u(BMN@_|Fw)Kx9FBo@qS7{-9fQWc3f)2A+5HVO2}Trhu53luD;|e4ntp zuV8ZtlDq`{#v`POYr}Z0>0rnfc9VpK*vD;QxAHfr+=)Zb6`#^In+|W+d^J96=ed@9 z@muj*<9ZH}0aY@ypI1YD+|Z!vukm-Izb07_y5S!yL+Tr9DFXk*BA?~@Xa;0sBWPOT zi5TA1BMd2RSu%G;A9JMcyDJ9WN&&kBWMkM|xmWy~#aRX;Wm8<0>Q{5jbCYRQs8?$$ z!MkGgGB-d+^_0wbSo)SF1f*^dQZOt2xFfUEiKfb~%4|ADVe8*={_?!&WQCZ9;bP<= zh88HG_1l&wG-OvEngMsVQ zTNr~}YI)`7cXQW1{PzFBA%~r@3kX$1OvvSS(m z!ARCFX)LCvt?aw6YcIjgFrOP^6@Sg$T5cY8Z=!U^_1CFMkz0Jq~8008}oi`Bz`a(eBCQEK;YI%2+J5LgG zf<%&~L`xIKsOtY#qJ`klQJsYl(MXcLSu8x2Ima#9g|Fl+=&?h~mwcZw-9gv0BIc=^ zwi4ypp^V?^E@wSs)kTO_(t96bB$rlMJd&?v`R`*cGYhp;TjzX7ARi52>)2&7PR>pf zVXpBYqC?f%_t@r;E}^a(*h$;##&Nl%_ubqUO2N!5LL}1Oi>^#nIV z87`yozW*o7Ruq!B#0BvE6Az_?kkJaeh*a@P-1JmFy_CuY9N2+v(!XY-fWtJHZMv6T z!3v404ppdcxjD9FRKxv{>36VPD6>^;ZZ5RT(%ezjiljoGKUc>H!16&2HzLNz4aUFm z7s*BQT=>3oVIPWwt7Mvyd|ei!_QPsO*!u6PJ;A`ucz`m@^(2feVfB%t;!S#Y%zer- zUz?bi*BOE+wjmL=^sZI4ZE_fQv%J?S(sd$gN%^-0@?6(pVIplpMw00Tu5sbzvXr8^ zv*I}<)8g18A3!&18!rkGpaSr~uj6+zMYhnW(Y!S%6XdkVs5~>`FeQ!G8~Ss{Z$+js zlKao#!25J=H5D30)}Q0riAm*24%VvM|R81Yj`O?W_0R> zPp+o86`rELL`dUQCo+CQc0j)=tr9NG>1ai1jJWk^j29Fc#a(!~4hi4x$fU;lEZq$h zHa%N%uR3P}^U|=^wKT6LOGP>wCjw%p)?odYKgn{1+Bi)DM@fo&acR`TGyg`|?f~?P zlY|Z04sO;O-x#=ILkcHAw#m#o!h4?ZgCP{bttVW3Vm}o0#Y3<@5qQ)h21dyxt^@ZmdWxCmJg=)O%x(E$_ZVQozE2vS{J*^~lW$6&1@A%C|Es ziF{Dbo}iYYlnlZ1g0mU+ClmUU<#@IjsJ25KcMk6mX&N|ubH(&KaM92jA-Vm8Q!>9b z%56WwsC<7=Q&*7^3BF<6qUsQm22|a|TvPaI=3;o5PZn%iGkbKh<}~05>`B-Pv9AVG z=36^-T0oi%$SaqQ_loYs-`M<`1Wz)Kg9JotB>$hiFHvk(*STepzW)QC{e^X!q-_{H zNV04Uf!qI{{k34rnk>s2EL{||vryhilC?v8=+{d`?wO!cn55ZWu(K6c;4$1n9Favp z6XRCebcJs#A{S4RGFjzv>mtL^l9#(}GHFe3H)cEZ13EmSY3%{uYg=~||+;s6XN zPh#Zz#P${FqTp9V`u7|x&UUTNr^Y$NmrdL30O5>K;h&b2rbPWwFt9z$qJ^zpA-HVh zWJ3G*_v6TET0pSblOEC285$Mv7k#vf=B|+S`@z$8d&#gx#Jre5r~|UAM;IpN;fDv} z0yUeHMQYdKE5Y7g;Smtj+f0SE!9sWBUkS9xQT7eUHGn{bX9U@y=Gt?*RQU|oJ4mUe zU_dP=di<3x>fNI^fR603(M?y9i8haS^Dn$v@c2bn+E;Y9DG3JS*OisBlxIZ^JYY&g_q0qgqU`emLUiL6ir>^TC$mj8&T-sr z7&66};;YR^t2O70QnJA}5C}~^5q(?IB&W~ZQXdMT5raZ=ow2(0u{fdP_g}hTH>8b5 zY>o5nX1%JNh6+>@++QPNoKwlZh-&ZMaVSZuD!Lz#5?N5i#Mpk<0s;rWV1cdqlp6ut=&=#Q>HhLhnXb^ay#)5#bPr{I3p6w>f(2ULmxo9 ze`_9OsNV-$6tSlzfG_NKE9#~RhC^{72#k$so>c{1piVc=gK+mEs z5s~s5BZ`WwSh)-PaS5^zmy`oA;?lZ#r7f@#hTiFc>`g_xL9TxnG>cyOQ<5+@b$7f7 zxL@WnM7qy>>QLoI;Xa}xVF(*&{a4^XPove~vgy&$I(7*IpINYtj6pvPA;K!vrxUFT zh718OLcJP4JDk8nJ93J2`6*`OylS}>6X=^cDF}{CbU=rd_UJ^#h$Ww?lO3DDDjkDA z&N}F%pOMHSCgsYa7F-NuB66>qt>E{KpZgSwi-9Zkw)L5;5gPDeoxP6U=wgV5<`eds z_~g$UJ3wH6-;w|};T!g*pEnznR|-4L!J+4ZZrzKW8Y!@)m_Ya8tILw``i#-5x^QW> zmEng-x{-}b$YL%=)afR$XN&k-{cW(;ZA3Nkr}$c;R$c;-u%isw@+Empat`)4%Z(<5 zWWf3O{?r&|+Pc15y?D2(Dj5yGx^@;{F1x?l2v>zw*MWvTVm0(e0WCP+PP8538=Oj4 zOS1)svjjc!^^W;qVOF5MQ+7FhG@z^lZpME|e?}?0&v&Vc%04#~2&fXo$bEWblD0X3 zVpY-H#GFMs-CDb-R)8DaMyl5p4XD)U@~lpbX7(}?L0br&$M`Poa41aH;J@R=yP?t; zP!7k?KXNhcK6*>IDPnQPrLO3l)}AH@I* z2Gvz_E8xV@!?ySLyE$alvAbK1rX5i~{_*v@{fk$0YYi*gv67+Mnz!FgkDl2vyr^#X z+D2mxHfaIv&@)Y>he4Ct*r0FSsv$$`Mx~zGm2z0@Di&@!0;cK6DzGUE=VhYOl`Tge zzP2gQA^`&X%%B$LZauNvtWrD}vPY#lhkO?^1I-|g!_3A~+s+(2OcyYDei4oSfsc5A zp13auEbFkF)<9^}Sfj*Yw5!7oX>)k~mRDkF%peTsAwp`XL1D?p6HBQOD$>TP;#Gum z8VhQyI_Y=D`4%YGLh~-Ru*NMh2KFD-B$1W0plJQd=;y0&^}CTilhU)uRHK8{5v{Mj ztj}Uz4vli3?w^K4nz0)3yD6C%=FeY#H>cZGpXOt%%&5^YH{Up*mNyxOxhhsZiL++z zJ+A;~X)hwmxK3EZgga`Q6(c`Nwgqh?D#=%Y6}MfB;WG&qXAD%9fc&V@gVO;VBD?sd zaKdHPbtv@Ea`Y_7L?3d84pjk;EF-1L0R=m~-&JU;>5AX}7=_@16)A z1BIYXI@A|%bTew?ws?6Vc1Yi1ZEk{bh5U;bfW&xCq{+e=duh!Hlu~p0^|hhS@k!KB zReBGavI(v3SLAV8LyaBK=TUxS^|47Wcaef3XTew7b^IZgpODv5x-%n|ILHcgISNJc zwZ!TlA`;J|NHNL11X(=s3PgwsoMc|#6fEO1)qY5aB^LH%OT-IgDp{}6-Wi1_@XMbQ zBe7Mfh+h#S%ua6zxtGHrlw+A2_E5x5= z1ba(ay(XAD9BV=R3n!y>P0Tfg^V26GvY*zLJp@u0oy^b0{>nG6$Pn~x+x4n$9W-@xTR8}E27d}Js0^y&K%rR2rAC>Hr;VGiibKbxg+gzQ z6`Ww9=o{R@G_LA!0)G(_-cy!GDI>Yjx@>Nga>(=YFxX0Qt1rY zMvgW*G7Ue55z=Kw=Vd`|NXRnW%(^W9@t0dV@FsC$!jiG|`eLk^Bt&UJww9AN$%?8r zNzLjaELlPev@%mEyTzvYD7-cGvOBZml6?rC(_p9qj&^X?XC-F$g9Nu{Z_UQhCLMId z=5C^(l@=;|9tA)N?H-wEgzp`5(V{fsrOn=h1Tef$)Ax_${DFdS1;CBN;ZgW`ss6CH zxPdM8!roh&FH(6VVEv&2dd1x79-u2OVwH(%ZPh2tVrD(59^5Kr$eX2(v-Sku9f8mR zo`v@kk=9zQCB_+z&RM(|e%x*T=i8qUH)9}HCN+6ZznrBBE6M-980#P1^<#J3&-647 z5utb<%J0@_yZkrp6Xtv@HscB~nw0_N%^s`bqdSae%bI2LR;DfAS`F3$7-gD|Nc!s*BgkLI&fn1 zis#XiC^UE+bDChl6;9e)m=?M_4|U}38dzydO`d`j$HMuZ%qcy0RO5Oyqs5M&5AksR z_UZ?I6F#aHi_2WJJQmSPG8>t=s;7#$1qeT%bnMr^ezy@yLsV)E&`!>5xabmysZLPO z9}&hjM|Y;jX6W?>zKF~3CS#}r#=F&h{@vLdQM+ZcKZTj8w3$;NDvYyS3g9!C1U61_ z8w+cfN_qoLhPfFrR@><&92#XyVOUxij{$324k{B12T8?Pea=GljG8k6&DiRW_meqq1jzL~(hl0?#dC9Zw3WWvH z%*Q_@S>>Kg+mB?Wpq@3hL#h#E!m&%=O@B}rE$asdW@~$Z2V^i&{HV+A5%j(* z_v1rShGkUPwTRvUN%2z-e|+NB9Q?E??H?-drjsM)WF3s=S~Fp;ZER+4UPWjWWU8l8 zqNFI{m`BDhh2N)?V-69BG}BAZ1Z+!YrNH{OKyMghW5Tk^_qSiD-NLhciwcCC?68OR z)Doc?EXd4_9YMwXh*)lIs{!RO-8tSm;0QEDI(_k(d=KU(wJKj4)s8iH48Ix(AcbGP zT`%d}(V&@t(pnYJ9w}bN5M0|6)BB`7uh5EFhlRAsvx7`mx=s#plf&&A7q@IO*5NeV zpV6N1=#cJPPQ>K~P?KL9sNd%5oEE%q%yH2d>a0eo9$X{+wU)2zVmL1FM}D}ymU+>l zSHGLTr$;klSAHf`VFl$S?XsCpsTCBK(zeKzbWvzqs}Qgd-hg+A&rspmUz9+V zXQPlLn2yh55o@Hk@In#f3eI(h1qfCIcUdkh={5MF zyl5pk!0*q{Hm3af(6nRatG9fWV!`^J_0Woxxd5AIiJge)uI9BPIRQ{j0zTxyI$Oe0f|Cu{pN~ycifpkL#9kUyempj9L$Z@#@n4wLB=bZd9*X*7!J%iExJA zc&sbtIU^KxVBwL`MxQx^gWRVMx+t{j5yH6{C(}pj)9<@FgRV=d--6-1A_V` zQKgh(zcr$xQT8J#hM`v1$R6DcJ+;WZ{3rpZ+viXR2N$Is5n{XRDH`8i&JiJ__uzFv9)63rztoVS8;0tmvYI0ls0<73 zwOOP@LtC^lQ9}x?==*(coTjgF3}wFYsJtl8w2V($RCvf`E%qAl6*+6cA9!=kI%JBO z`4ScGf5O~YQ7B;ML}ew&KV%^3lL73sDvE|2?499jct#e#m?E8C*Ch<0pu0y_$oDwY z#OWD~mM0rfN0U`Jc!f9)GB$h%^a6!bvzyZ-T3L|wk499a1XWvQk4Jjc#AkbJ9>;@2 zQsbiUUJ3;QK~JRDAXEwFJER&n|3C?1p{H}SrUzep=%fv~c#pm=q%1^!q_G(X%@yhiWM0v2!6IS&@sM!gz0U2f$=p)x&W^cwH-){NuI>+WM_DfoQfL=vj33B>XLcWj zvA9c8pcJvFck(_>F?ZW$IOt^xj`Y4tMn);vTJk|d9wpPwXC53GrizdrpjUUgm-6-Xb(ENQU0iE6|z z7p&)iN=&FZFJN_A0!7p)HR^rc`H%JWnN^A|ON2FoqPE|y!5Cc-k4@>z$1#!}S#S;f zZpgnb@ltWSU-c*5z94Lq5fS{ewOrg!ED|_$(14GLD=xyzrf8`p=zf}E+M=a1R**2B z<4(2QRkdX5hp)}O-3*H8OW|K7Rd0;7u4ktMGb7b*HJ6zszyQrQzsf<70{p_KC{jP3 zgyTVUrL_w9wI`<=ePqV3iFZBR0G+Ub@am3-7dpKBZa^d8w>QH2?occchJ%a!CA;KQ@i)|~!s>xgME_Q++q;*Gr1eRu zhh4hx*zm0$|J`Q_QtIlWmLl4Q7W<-^(pByoaIz|l;3e=_mAwNbl| zbgSs1wpDXGB^R6e{J?kE-PVp_95J@$`1R4xUIeTHy)(?_xRacu*6{Mk26fQl+AJG~CaqXCys8Q|G@i_N^vkp##Mke}x^r{y!VE=Y z-``HRAzyaAF)yMm7tI)kj&o#tL~!tEWl=G9p4(~--0)Q~?>sliSzlT21+n%RlEgx6 zchl;sA2r571sc#FW*a!)^7!vQEn4xSt{75Z54P!vaAxx@xYwE;4PPFH|B$Ek$uug} zj&4_t;b`$?AK)KMU)A-OUb`RY+XEw`e&P|?Jw zz)I00u1ut9E6{*|aL6E|Eu)w>2ZhJP`{dM=@QK5(AmDifv5MB7$0cIDYv_cPY-kEp zp2f*+<1>uyOrV^*qNA#A+Q*}d0>@kH<_RKynp?Aqjm4cvJPl3?&2p5!LcnFY4QpQ` zGPAsRR|MEs5sKHgPm1nzAmds%aE~(JpTZ8|2z>(h(V4lTa)nq@^dOORuh{$8^ayJs z7iV({dSPhfeRiQ>sR0HJx#guwG7dXfIHc#JWm;tYWMMD&0rsO2dZsiFm(Y2V{L>=D z_g9i1zB?9lxsmjTV99JRgkygf9HKdX^p+{lDm%-b2FNseU&Xn9A46^rJ7Lj((z<*u z&17T|4M}y~df%l@i-2D&S7N!ci?IqK?)iBK+tOqUD40#W98_WZL_(SQtRtC_ z4O^4Cs$RRD@qx9&u5xKb%*aYX(s2uxF$3yU-Amz<#&d}dWH{ljtAeiW)_WiO{=eyI z89}nNxuis0Kg*9c*(!3cBR8U{-I$WDJ!Cp*VFf&YOcX>qqNCpWzHvK6UIfurTbiGOWFX;%Qw9LXN-5{@kcYAUKl@ zkl0O(l-?lHOo~F`BnD$`c?-U$d2JN)X%odUZY^)tjvGV9u2KAPem52fUiKyY1|d&( z@(@w`UVb--FJq_+Ut+=GHnAK=L%xx!!Bjs?kIR$4x)u`S+ZA;X>YQ$}>I#Wn+~#+i zZ-i7W#H-pn%S<0-mr0adtite@%%#b|z2tmfNBGJVLoCR~RHk0;rYp}D)+7Q*$_$d~uZs4ANAXJj z)JI8Lbn=-Xk?ggJ_1me%Uak%nl2U3RBYLw=U)QA9HV(&4v15Cp(ELKXRk+ijOd5^Q z@G1zm?-6BHTK*(0H47Hh(@hBS31pZm!&jr0tUz%;BwBWN0P zUJb1nEeyX4O?{ljzZM8%a`xnH2#<~+%wN~ad9F% zxdmNbv_V2qNDPO$`|X{sSEFl{eAs!n162uCGGhLAJFWoJq0DAVYTRL?$}6if}RRx-p$;5A^X> zTu(E0qms0Usl(jI2Xnd=^75uOm%1Q%j&x8-mC*0TMIC?m*F?}YPk1Gk&c9NyD+5PU zdGA~blIh^!3xKT1Dx<}rC~O3Uy5ff+c>||G69>JcY|jG(r$@=QV8O~%c11&Xf$bS1 zvy=dy?V4(EN7kv@3@vuyMW0=n9R*GZ?jnAl>>c5OVC~xnboQeh_T!PRa)=;H;pkjx z$h{=mnB@`p>qm*!YYRHC)Y&7Vnj45lFd&>vngE*a_y7!}*lP}HQ9K2&vNem`J;sfi zmag4~xrvu^YQNjDRtB%tW%V47&_zMV%gMhZ{1(f2CEotU=yzNFg2+b)m+f`BXDTQy zL9YF7+V~&l5T3Q^%xmAeCbm~+P0-KE)-I)|x^Y#(VeXGk|EWe=biddK z=|@;k0D){$`7VNa`DUtdChJ?4@h3uC0I5dN1lneD0YwxXGL>z`Up*+vpCgs1p9%VJ z%aTX}t5?1PjgKGROT!_cw1PfO#N!U-gnM+D*Fy$zVLdzKs5h_v1uJjxSw>8L&x300 z`{=uYqQt4!?)BRSVpDWMDSg?xhhWr>Q4JQ;o(ysk(^c*^If$ED4A?`VIIvB+B3q3f zb-y!CuSU%JliF(4sjo&Y(C>_d_B~Qxif6g!qO^cKDsZIJ4qZD|LZpt|>mnPA_cgFy zcJd5G0sy-7M;s=%wFgY4+yW=4=@wccm0zNs;2~7>LGVd8lwk=i3Ym@K5oiVLVRV20 zziC_BP3%BRnnuj}IUsbpF~8(MG41fe$1;bNCYb0G47f1AR1spDGT^iFh?ClgNGOR{t9g?4vc^c%XOmt*1@q} zK3z&xjcbdvG%To0;_KbYZ@zQ6C7$P(7j(fibMqFY?W|n-_p#H zq`Io0qbCx2oMTyNcc;xmIx~;nJVMa=^gc~rIDf8*LNF$1qH+zn?)1Sn+Ff`}X4dd7l zEAcUE>k(*mC0S_r1;^xn_i!=FTr>_`4Fw>L!67 zUOc1RQMi1;sg^A7)Aa8?6$oGWAJlVz$n;`G%u^s#NZ507f|`rCF+QrY{7$8l&=IT( z9t4b9$>W2i9BSu;#X*SP4Mwi^0cOr%kNkZ?`utSXg5DI>T_1e!T*NsYN{cD% zP03ogUJd4W%crEkp#xSbGM4$PaVQ8yg9t*D^@ASv4xJNH;Bbc1I|n7cyzD#qBLrhjYAqcvEgp=({%X$5cw^Gru!PS#<&?j+(S31afF6F+~|-=FtOA91xLD)h!W%l3av^NiJa zH5XMi-Om;1##HDKwv7ltRlrxOSiZZ^(5|#o5ZQlhaeiavncP|nEuuD=#apmP1LMqKzRum&)dM!|?w^sEeS-^I!)QrTn zTPdZq&4x@*SWuH)$`7;Ga{z-Jn^r}p3grEfyYC|u@F^?Z5q@U3eC5s5qyN}A0I};R z>~O`Xejl+L)IgD3vAYd*sU+P5!p_9H4o>lIK$tzIi5PLJE((d#-FW-VetSi-#OMg?S?Ypn7&k8A zhm!KL<(wYdyyNq2GfWZRQGvXz`hyoWAwV%33Rk95STOvJ$uPU3IJF4k1u!OMkN>8H zm6f6hGL%NV>uf5Ds%E4GboxM_A}nyXF&ZW3zjEsB`RX=O4cPbH_qpU6kNS5^{{V6qikA|TWthPtAG#sf5`Y~I_ z*`d2eP%l3@&3`L>bnZjNun2^4~@)vB08Albn&J<0cNE`8}m`Ejgylfr|<- z43ZoBJv3v%gnJ$1VR(-|n8}rTc8(QJulwL`l+Ue1CRn}9-M06YFnv*AbBupB9~}S* zXxuUAm7;@3BeKoSSw4V>g-^ zjZ2b>hQi}Jyb6BPKh3D(p7>L*g2-QX6DQ6m_1FrjpJ~>hd-i?zyFBEQ03(D~)ebpGUL^r4s`?NWVNfHnU4J(g8iD#T5qau3ugFzHxS1y@cHWUr;9#j4g zHv>M4MA%p=h%#j86&h-qzJDflG8-K1;1HD|;sAtM%bS_ioYrO=gZV9=*dJ-cCtP)Y z6BmF5LB;tNdq=3Co^V{xf!!<57?)jm;N*$Pz|g~$JBn^0WrH9a+khG{XD3pQvuH{F zO2Gam;>3A&;nS|BCvCU|7zr~fT3_UuQDwPImRu~+B-{BAJcaL)W-dY8gl2k~uLz@? zf_?3$eO3q#JM}{&R-wZ?dup}CM$BNYh}slwzwvII|K|IkduEn@>MuqPOD>HHY3k32 zni9mVN2#nO`=l|=B=Ezd>rBZjSfT~VyiBurV>SX3TP!PT8Hd9;{D=}NIrk{prYS5* z>1#9~4#5P}7G?tnOL+Q>*-%xQS}>YsxO2v5Td;u!lVhiDo0q%o*j(!I^^x6e9=BvX zEi))Kt-6;-jaaO(3vC$6@fGX_skTN%@r#$uwKuS-_${q4y~0>WZ=qYwRI}DB!*1 zy<#vN>Q(o+O6P<$PrwCX=h)#HG;L{U|5MUe!Q%R>lSW{Gq;5V#{l*EI`Db~ik1h(g z0llz(4a6f`SYL+p%v+YMQik;`e3tDqA|LlC7-ch3hOce^gnFtv+(hIz`J9@hNtCcO zN;Rspg5dgQuDobHWge<;{UdR;p@=;kT0uyVpi0k`U`&bi_5HDdVpJv;3nBE$6r@M~ zKhEX6X}T5z0q7zMm%HtwAO6(*L2K@3PdGUFBX`C?e2GgfO3@VK$gf7D^nXVhpZ`6* zg8S*VpcSCd;P%hm_S;o1fA4G)2bd)&mcgknqQw=)U}{%7th<>p@8rR^C4G~H5stpP z$(GG~S_MZxtfCfOsNGCi2nnmC6|Y&UTu@Fj6k@wWiXdsfz_pEf;ut*L#t{{*FR$!EexfSVN<(>+8%3+)*srn+t zh2^<+>$(0qjTl%5KkjU%1DmEt)wanZ$$G_vIDwI43sHbu1UWR?Ar{BO2q<;U7D5!L zmh{Rk6{#hEqRO)J+sXh3N;~X!IFV~go`7>TQ8)zQP)!2b_4c4MqG()4?G^hpPbk#! zc=pCw8~j zwqBv67C9`K-fMG*0j>{WmSCc2fZ-+}?-}NnjbP&#nV8iDU$A!WeJBytXPjUq?RV z6Y96WgPZJUM^jT9+jwRuvOf4O<`#bY-!y483^sUYV|pX^#}b|_FEWA(j9qsWdd!2} z*0`hgX+3FYmKwTQ8T&=j!I=Z+U^Sjo0mf=419kZgSP$v8ER}lZBCFH$i{{bnT#6k>Qy7o`IrpfN=fW-%1JhaoD(k6?az=%+ zz||DZL>&=DP5~q^QEDf-vmoPi7T$Q7*T_h1Q0fX#s=<6{HZwd;66`3#t*KOX zX)W;9kT$LAlRbjn5HcnuA1l39viug7S>O%yJ_eXdcKn@d5be&V?<)@x#$LIl=~uoq zxI-EqG#3)uE61VmmGB<1hq}G)bNsLZoe>F7W}Z1#cpDya zX#08Bxio0KN~2s?YA+=;3?(q6LP&YEAIhX06;wkaqaT8RX&>R8jgqgnyNO4CbVqok zk$Q$*LPfaF3wuJtBpYVis10+=QnW*oH5K-!>CJw`f@eX|ENxF?xzVkGhIxpSC;xuf z(<1ZJ4PWDl%Zvs$780i?`SoaXjVPKcr z5slo0lY4~FD`402S;n#l51%%=7M~!W1>J%;&>JVHEn~q6#!-3wNqMX~jQ870Beeko z(ud3Zz-IO-rz5hr->omTc3{n1jCCFwf7>n!-LoO9h;2BPPwph6 zusoj^ZePL7!H{MzZN+nF40w!Ss_2i+{*_PSj-_shxzL6D5?{wdb=>*y>1>m?Q-z)n zyJb05BNb{&=AsrH8N8win#J}ydfYxwTL$lRCGxL)&!ZFah!#VaiKU+<3M`>sVTlrI zd8*n?#UsD;2A8xNtafFVfJk>T(!3&cBU759Nk8x2lP%3&<2iSu`SD6c1V`Q1*b zxZ!~HMozbC(M-d1XLB~Jjx=azeCNx-DwppN5>`?2O;K!!tYPl2W4P;M;WAl8-UfSI z3L+I#x4fkGt$5%r8(M{2_~d=!zx(u8OM>FnwA?|Eo4Tba;^%^A9r-9Zp{1;5lg}L$ z;%!~D1k991U=n(R=d@~cBqTdcj0O|psK_~~H=`AyjtNxNPVaT+>iO{5MuyE$~(%ZPcw#(j*LuO_(d7?%Ivf6Vepl8NN zqtdcAT5)%lG9fzQ7yE((S8=dj1-Q3gM1x`K6|^sNdZ5}oC{{6*YMHTsdA4`Pnjq%Rb89hv?1-+lTwF<7Gz@3d(c_YMISwOb-J-B)aF1*mKA zvf|ZkZE7U&FJKs}-UF!ZR*_&?2W!ts_b3%j)s4J?m4L2bJV->#VXQYNK+zv!kT|{X z=0(Qw0vq#fk3Q-_ZFY@ZpmA9fkit`m=+aA7if?vTH$J!caF0vG%?gH%kgbs59TD=j z1hp|D$lg)AaW)br)*vXmTveD9Nv34**gpD!LUy1zC=F5&4pa0>`?c4`Y9;WX{CyBG zZ!He>nV`F@Zy27ggP+t zB@FEZL;s>4#)3xY7XD&^UQGmr-O2xEX{6itm|Hk7TwaNt(N4eFWCZ)ROjn{#=`fU;M_TrqSPa(#h??&Va&0l+>o>q%`alx$-< z1cTjTtKkeIQsCy7|L~4+vl4lAhS6O=Lx?5~hY9Y{A?AXuJB&XCe4MQjk>9OBFsIW! ztc&06BsSVEK1N9=T)MPc?sZptc2S^1CGQ=M8mPrT4968KBk969paviruLe}Z??!s^ zK-#llVU6Fi2o()D7BHfClu}O)MO!uXNhdE)<-hxMzw;GjjtrPcNM&|nSZD$d3>Ir} z3JHW_4y~b#JzbqUY~Sbs6P~bQ7tnm?i8;Y9Phtlw*{AOXK~wy2PB?4x1YG{c&Lxpu zN-cDgJu176(sZUmj-=!J<$axk`KU-v%_<0jF`**-hhU)0m)u;_2mB;pixxZTPqztj zW~n5F@*VcH&!yLjqB__V%cGtMM>}Gyj>Y$hx7)E7hq5nV5=|v*M^mq`6C1=hoT(b3 z#)yy+!Lor}9ul52-k_ftuxN*PSduDwIGW|d~Mng>_#4 zF~$%<2UW|VUc0*1`??@@ujmq4>_px_o0U2!j9fr7aNHL)ey=U1m(z6YYiL&b;;}(F zU^x9{(ZCIl9zQI;72uGP7p>4cRRBjvgH8M81S71N1b!iog zq*RKLfou6@D@BX~U4&k&EJiQ;*;ZL}ywkNB8wbJ`{T8Gs5h#YJ&}k>F&*oyiG3;>))_;c)_@Z1mn((>xoPK3Sj|=6k9b2igc|u`&4J^)aR(KM-V^-+c@2VbwW^Soe=;yE9oe@nuz!B5JO&8 zZC_%F8f&{G)h(pWtLE&b`W;UJ2ug8La zIHEV3`mvxTP0XUUNg@|b%umXnU^jB%jDn_c4!CtGLQyr1QIY51>?`W-m(As22C8Y0jG|9g8E%*PP!mrin zU`bPu*7~6Nm$6+8rE{__PoWDuwUr_Eh0G%g%7-w_Ni4|gIoLK{KxFi~x??Fvw7A46 zy?TM5{{C-SvxkyufdA~+%1aQLW98RAmMxFt1$#fcjkBhw|k zu+?D(2V$w?>$5{U8ZM$WMLqpK=V$SMZE-*!7(9gLRd0tbncf}=x7!~TOML(42F%k; zEltVjhM{n6+d>ToDP`c?8hRUYDGN}Ldnvdm0rb^U>VJHG67%h{8?;fI%h##Q_qNw- zhE;~+>*K4GrxDa%_6I8=^8?>G-7u!#VJSa##N)JO=?VsCUK3?T$!-G$wM#QR#ODoI z3A$rpZg;YnOVLClQd-u&tD#!>p5J0tm+gie*bOLDf2>F{s zy8wffCa zofcdL9W{u$FrVowENn`l$0ay^YI$eCq=s}T5e5p82NCi&1>2CkE8TWzq@7%^$cDTS zBsw#s^P|6scKco5xR=1UjJsjuh3$wZTKvs!r%{6p$Abidfl7xl&RCP-ImEgsuqV0m zOJ#TAw)Bo7ZjOjR0${E zzW7KZou-Amt<0==n-=czZXFymAwdcxPMuBdM-!@0AmWB&!Vm!3l@NY6q~|E;dp#FF zbjo+^mcr~p_HeOl7^{f=;Hmz#KSs>4m@e{ovSH9JVh7=Q=bU{1*Q2AtD8qxbS&_uv zksmuKOi|Jn=}C6!3Z;xfwr&OEVq+vG4xxWxZsQJ;-R=3GJ|ePcTpw4j?LZtRGDx1wege0XpXRMb#;TT*FcDy-54Q3a!LS?6J~mho(cB_MH1bdYiX zlSVp)i?(gUwpfsJDfkvm6@WEM+hd1_M*h_`y|IW(U#JljGv6X}h$WtGe!7zh9-@7K zbsfn?a8S5dmsq%X?K4Ec9S88M=T^?+@Le8!nWK$AZWVC7uZU+~swg^iqi|TMLJ9F9LnlKyKqMk%R=1;fo4Q__(5pt>2A*Wm}8%Wqn7vLSK_5 zUt2%ZPx0F!p&DeXu;FNgXPGpbAok8GRj6bnq*Oe4xS5v-qD9j1D(kzr0^xa@#UF61 z@5V)doq(;41GXfp%N51xZ?i5*|M&-k)QGGu73flAB-SLH>G=gY-zuIAHICp^ zBu>~vl2RjVxrerj`7)`28oc{7sXge#+y@JN52pma;mfO9dcnh97hF61WX;6BW$N%i z4aX*>1cBNl8XU4}oi3n>jJ7_{V<%m-t64%I95l-DgVh{o~J2m31S5xRE@B*!lL9jE`12V6X%ql%%V0M;ihig)C)ILD69yXT2}U>!Sd=dWa*0?nfG6^+ zXZ?K8I~YaO&QNHt?Hun`XHlWkN6j6fpF3Ze#-Co1ZU+@2DGlnRYvg7ZH&Y$t@ovu4 zVHJ8j{$~V@)t}nv8Tc|etnwVCFY^^)pB)m2K^mb{Bk)4kQZn>LEcz6+X_mtRSXcm3 zr~wMDLJ{8SQ0578-j)SDTYIta8alw()Y%62PY-2a_X{kC!Zd@!Kko5a?au<&Gt|f= zVh0Q`-(dxvGv^o>e*?&r#k@*HNuB>cK5g}9ks+3&9LY5z&{ousR?19dF{1RKFmNBA zxw6mQ-{X&1JfD2HTNtXtk(J;hsUxhLbq3EWq z!iH^ZSUQugP!$D2-wvXZ_GG!>(yQ156rU$6xPDtmuMP>)CMWPb@R#-pSvrJbxe7a~ zB_x_5%vNDAI!m?|W@b8gZS)?tXS9Q>$z1LG=6#-1qj&@Ta2f6X`1RQj{gI1GVr3oi z9Bh%P@0MKsh({?Hhl??<<}WmohU+j!w(8mu<|qEziqse*h!`?s|2uAn4iQ-2r?DQZ zgi5qG1U>+1a9}p8k0Va$5>Q95n^-W`ynnfw`gBPfprq)hZv%2m!%74oMw}#W$6k8L zkpDr{If@dAt(W(S|0ZRJ1g6+c8xpn1jAIc$ClC`2(DM$}W2g;!9%MSIAt`X;p3FTd z^xdnYcV-0=r!DMZXcTw;6`%Epe@G%1Z~dR4Of7W?Mo-VnBn?*Iq826fcquC;6j>Br2kkp)SQTSV z6~5!r%NT>P&A6W7h=Kw5hMUuG@kI?sMI53(<{+4qjx;+9EVMP{16hX12W(Ma_zVgJ zBcI*9A|=9XU(xzmn4yHRkbq}FU#W^ggKf9w23c&=V7pCx=rJ7SWIVppqyI(l>~1Ny zv&NOJdK{sX5i!C0+7xKP{a7jUWP6utCU^$AJqp51gO9t5%l{Ns(>A-P@Mn_FYHTom zJBlXq?8P--JM<{*V@5PzOT*>sEK^F)U9x4e!tMpB7&+PGdltx>%}tfZei}FuiGoTj zgK6JD2gJM+TUwHcD6-$(8uJNjc*OBD}pIxf>6kMxf8nt(E zCBW5FjC*X(;mAYO17x>-h4CgoXuGy1KCSld>t?RHPYC5gFxltO6~I5%_vclE%sJm* zmHR-E+ht{#jtWk=Q$Ydx~+YGM^_iCcq5sF@pD)W?!bjZsk8aLAtW!6+I9(Lt+R&RHrY&GZK< zOR#uQ(r#}@nt=Z-KIp+k!CL5ZBH$)a$*h>9wKHG}sr#w^@aRUGN&nSV`s0dN_C#Jl zixJwoAj66Gh42Prk&p@mY+43?=L<-C7V=I2SHK;Hf)X)kz3w)HA^m&qP5mQI(t#+| zwnKnjr*DE^)TSM}<`3-8>`uMG(PAkAy9+;!Mv+auF+R5ut0Yra6FjVrnK^4iW!*so zY9${;^3Hy7I8Bgr>{e8p?IY|8ntDqVCTltQ-JBO=I;MB&cSF^=A5OPB^Ue-4yxdJ> zkZmcd#w31QCcs96m9U`BvZZ`NC&h#Hxi)d3q-F;Ct2=2q28X~`3&bS<-KRTgBei!` z3a7EOeG;_#M^Ql-@^O+p&_t^x&s40gC;Bo?EVJ%sOR^mp9f!GGmFtiD@VIpU2)5&X z)^!gmhk2Si_Rv8OEGz1ifFewOPjQ=Fn?q>~LyvrUeKc{QiYlQtR1!01!mkTr2Vd{H zS7}R8@2t~Woh8aoZ!9WgZ$6K=BQBkIWKaGKKT|N$4X)61L0Xls3yBffJN<<+YljaS zvw&W>X?SgX z{~Cq<{2&W~PZ%J!>*u05o>NGmwuK#}4SG`$TpqWQ6U5;xJ7u0}S21FA1p>RSrSja< z^jl5e1%c=i9qJ_^UYbgm>pLG2-B_`7M%X;86p{k}c4lb8MnEb5t8So4{`Zfp*Fu=m zurUgTQCd#QTNN06;d06r6FABVBl5EeQys3kHMWv zwz|0L5QN%i0f6(g>;VyI7c0HqGUKI8P}*{rN-RHu-G04p4#$?z2E~d*7PmfiDmoE3 zKzl6jtDW{aNI^@lvb@;w>%@2sy7Hh}y@Q=tD3n-PzotXkGu?T+p{&aN5(SJBq94hI zY8lJ=00zO&b?|GgQAuXH9+_IWzItK0=}$(~QSwV&%{Cw9VXvxkzyE}pdWpui*TkkR z7gQ)fk|j7q8;@%GhbmAHjU-dwU38UJ0j2VQhxW1U?(o+Z$QA2yhXQV&@uNa@imlNr ztZ8T5l{DI^Kt3#bfH;Q?p>7rDPvAJ1%fIv_9vqVAO=JyOe^4n~1aq-t%nR-OdkV>`lpL$;{b z?kjg)H5^eyBYDi|EmIi|EHWrwt_UGYq8=YA+lk?2Awer9({XJ3(>PGc?+c9*-PNDrcu2p$N4xok&vc zOM0hSYU;_o-9Jp^P*WrjkwbW)k0x@VO9Sf$1>tMRA0S;fB7~A5n{>8EH_E8^aIYQ1 zEqqpb6@bHB)4=mX4@R?&5&Q0uwEu9II86g*%m)>>p55~m)!-qKG8!lfGNXcq@t2H0 zw>?r7zUnFAt#c{>NI}itRPDGNgG>{rXPm2`mKp=~=!}~zOHj#tg^e_5gsSf~JntD| zW$U&>05yc6^hMT@db##1DJyjyyAS5v=^Um5-vwWyP{V$*IU}!7+OrA2OO3aPMEUURz@hGC_Q3FVf-)}tIM}p__Cx7*4R$NK{dv= zUO`G0%$CxyvqYAGacwZ2^N9yHTgM1~_j8G1nWj3YA*eg_`!{lHS>Ih?Fy!TXw1-}I zq-IaN8(zj=5rZOM{PcY}liSfEB-lDudwj`>6ax(iWuq4<=trX4R&Sq1UJZ8H>ome+uv zx8&>j)Tg!@h4zgu1P@-S5UNxPND5ofV^wa~A2(ox2vh{}H5~RDgg>N~M%$mXiOSz( ztJ2lvUKtgl>{uWibv+f?nN^cijR!66l+>#&aoIRt-0b9cyWf*%U+(8kMLo1T2b8dk z25n?ws!A&)+J{%9^ZIg(*h_pTPXDI+Z|MuJ*QV`bKrcw!2`^PV4U{s5(8I{tgcbT~ z$ad-~&hF!#>72@AUeEPhhG=iXeK8bzB_A$%-T64DwvOp8tYuO>0*_9n5L1S(=qOkO zijNo#&cI(5B1bYX)K0hk4*1NhW`}KV?`jCA-5ooyB6_LsHch;Z=}h$WG<8^uUXjr$ zl(E?ThJpoRP((AK7@w>b9{$*h$;SEy)uc){H~H_8puH+n`3r}wcqPM`bOG^JF~xVKMmwF-G4wmU z7L=(Kl+#}3ck_wGdr*0-#K+^K#Sje$GaKNk4`=?asygldZGXJlkwVj1Q#2Qp99R&= zh^^hy#{(Z~<#RX?-D07Khl9eIzSo}ZHTZRaz7SCx3vAw&vuqh?Erv&8JoFA7_xk#? z9%#4&?JFf|j*1W*Gcm|AGv6nJex&FP%Q)g;=riSadp9kmo|GC_U7&*o%ULjg08aLL z&C_`M!FWhn?McmlrrM?OPrhNAJO}3X%rm`D&#ZN+=Aa9qVwXO`I`}Si-dL92$>JtH zs38%12ui?}S1Hv@{1rU<(L|9s@!z9G!jxYzs4iQkESbwH4Vr51-DCq6hmcj`#l_G9 zm289g%j)@LPr{+Gfaa`s5%ZG&3%7?_K6*wkT4@wkS=;(97$a^Cy+koX4pqq=X?#yj zjGOS4@lu_~Q9{R{72MqwCp3=s^t(L@zEjRm#%U;^8^n5w#f*dbT4)}jh*s1fm1Vq< z>z_b7IYpvKUzH-Np4t^=XnKr1Cq!lT6|CPE?>b_T#QxRWImfT>fpuFj37QEO3jUoK=-wh^C46-!#(L zmYK=U7sT52>_Oc1>=@ZEC9CN%$845#n+$!)1%;awF9HClT&|ZRuTx~x%qDkjk ziGL8w(01^w`MFO3xv1 zB(bt)099HrT+8iURSkvf<1pKF{Tz8>>6kV_g3xL+p+f@JZVMh=Vjoo<9r_4G{bI$< zf{{l;x0hQx_F#3^piodpSWaH_9RS5JAF`(Q!Swiy!(=#?rjS z(#v(KxdK5+det?-(lTTI{3U!IwcYSclZt?XOA zRjtA9)yYv*Gvk~AcgpGFNnn7AEN67)TZg#S%z_<`Uia2-{7;khOFCpfQI>fsApk8X z#@p$T-T2CKO826)tkuwak-E1N?%I4#t4!oz=PsL;eEQmk|L#)=ermNDrR+f)FVO)H zpr%rVEtv?C>yha0mK1K3%V|;P11|95YE4u%O?wSnJFznmsG>x1tm7C1&$#6y@JvbDDyhyz=a6qi>6Onp)SrP2w& zebt3m{~x?|`tsF4-lZk^drHJ%_;>%!BrO&Q(X=d5Gaj+zs)p=_Sy;?WNDDqn*~|ZX zw3l8sUbkrC%PVy~`&hWVc(Ur*2;D%kkDbll3Dgb4V<#BUXx8EoO;%Y7eNe$Tq~jyW zbSxSZmDr!E!AZHuz(CT~H)CCq??yB6G>tBML|Ivm=q`RYUTpSw!qGf7;#r+;*p2bA z9Gv-0kGj#ZtB|vH#}$Nogf3D}^>U}^EucCPBt3Wk?tFAe@WWLNlaDk1nEWfJKv2$Wy47lTSsoR*hq7vUy zMH?K56L1(l4}FW7lY3TIB4`CZrKwTIr+(j8PbWkz#|#?7VL##Sv9YrSJ{tSo zh=Z_v{l~%2kRzUp+%ecN)3F#+MMywde%GU9_W38)Or(s$_1SX?nQZmhDKb z%^X8rCnymU8#MKWk03NOE>7VtxO^Tq?yM6y zWpTDkTLqo01L5^_Bl;}0!FFasmu6z@h1++)JpFscqu%xu8gmuwh66}lW$QQFt8@nz zHrGh^BV4^Uf+ySy#VDJ{IqlS2H3;jv&6=cm&zb0$j?lWxT9@W(^YML0Ar7`KisW!A z22N2M4sW4J6>wl|ty-jUapO~=L~(_Nw{`nI&rmyYnC@iG?Xr>`i1|bwKQC;akm@%A z8{+XtL|A#ODlvoKk79H+h;akgjyE=T;?wGMLu+&ut@-jXTu+>vG3;_3eKj@sJXYIV z5uWYQ7T(%)?Mg-H!>?4zP-Y>~E@n{Wn{n1pH*)Yp$cl;KF9HAG?{4GxeE@t0Z*Xnc zZZ5MCE#ecc+#I*jS&A~q>A&bgO4FF*J~GDrb?7IU1KNO zwMeR0Q6#uk(3ZOTW9gJti(xR=OkN1QG4F3NI`zqWIjExbQ|${jP?Jjjd2~p)^WQ&F zc;~zSs+qg0g$YA50E>8xt#lJ@EN4b*_1aNAomkEmn8lBp{R*q=%5{MYr`JAh!IHJ^0szplGuNisTVK+?Rhb)m;k^r-F%aQDq;#&P)Dg z0D8rB(>{EtKkMbxyPiuO?c(^b7{(XI+wBIwzM^ROl)0!qt0A&xSK)B}!PD=CgTdLGQc5vM0SiY*`i~>leIq}|<_&i`&UobU z|3AXoCQ`ZP7Ygw+jyl_htKu4|ICdRfxJi{2f$=sSGfHnCjTiU%MFQMlW)is6m*4<& zl7>6L!j~1dUUYzyBP_BLjNsI`>pW5N%HzC_Uo{RtJMk3?=jEzQk(|(rw;{dFmh=sU z;=tl|%)tEO)^P1o&!ZPX(rtX!Y>8y|7;wlegAb`=U?Q!SWpbr`zDk6^hl*!(W>nH} z*kuo)`T+QXt)wL3HvJr>>kaVPjs_0)n*c*J<93>;YWXblyG38}@@feJ$U+BV>?g^| z>0UQ4U~cOj6uKCwrF^k9akOzrdMK!`0C?wcpC$~+TgoVGH0w7VX!!_w>{F(^t03ubZ%F>qy4dSvT9tn-6a*>3~OQi{Kn*J*QHj)!Bp6j zLzo~P#TX7TI?X3R1=X!OSUF_8hbGos8!|hIS0^o& zuqc0gZZuVmNMnp{#O0Nbx3@iXvIROFem;pW(IHK7QsjIlhh0za+?Zic?gcQ5MrBQC zVM;BLoEdK8cpKr^WMX3(b5o=_;LS}GrD&|4$G$|)=LpW71fTmj6#A+(#!5^=&-M0# zGMWk<`Me%y{E1Vowa`|Q9hI6M_Q)$spAa*?2oZ>^yih<&r{Zd6YEs@O{@XZ_X=&aA z?x`e5G16L}6Fo}EC5$p;!kbyR3NNzu6FqU1G@}ERrLNR#p|*<%ZD(~y%J1WMi}ZgrC>n7e3nMoHuE?ZAch_$o z{_j3b^okSdA~x22(_^9!jzlX#!X>oFxXs*cfFIM+TUvWEj-}Z+hjS==mIs8D2SC>Z zI9JNcXMopTpzqNY#u*eIFA`L-+jc{T{VB8zON3cSkTX&>f-4XFKvqmBuLvPCOOM6f!sE)QaH*MzE* zNh0a~-FctrCMKICMAVhPDEA9pNYoN8_$TQ1xp3fmexb1vZP3&p>BOXNs>X2?KtKH4 zc|C_9dgljVoJFLsrysUr+vTutv1VH+SZPJ*54+Ef3ejLU7y<9pIEaDul`AN2t+q)G zK8|BiVQ-)dYt=Ud6OOv;uc8T^DzA5lGAkU&(m{mT&GYP{khzp_sl~fkGMdn=NeyA_ zIKCY<_UiEX>9q;$$4dD17Uf?m}NLRlp!$;MOeq2SKU#Bw%bSs8rh6H8%U zH;V~`XBz2!nr7~{!Ke5dr5s5i_o9TT@hb=8Xs8;%3A4Y=P6xqP2aE;rKMY@fmdi0P zT!B-ESvl)NiH`8Xo!S24za9}n3%bxdcd(Rycg~NZZl$=5_z8AH3v>gKV@z`;@Wr2= zvsv$Ry$B-IF(<2whj(HT_JFl2-qG6WHq^Nf7bB@F7DgO+xw^cQx8jDKkdrHlYF)6B zL6bGk6ZGRkF7HJULot~V$P|XvmN;$2_aXUu(`om6Y2S>>iM1%Q1dB5}fnJnkI;`(4 zD5^MLxNx#^i{`JJ``PhI|pAulv`Usav)}|nE z>eU^diuBQf9h^|0xAdAz{-Gwp5q(-89g>R29V%u;RFSnNY?W#0^IN@cVnwy z^n1N;l3`XCe;^#Nc+vmuEY^dPAk?~TjhhG+*z z8Zgkh0ky_%Y?vl}HuZyvXEhpNlQ=@z@f43p%twQSDcr4-o)VAkurTNen3YU2iY|2N zX^U$_*>1J+pJpwpb^0nY=$CVHI8e@Nq3ydZ==(+-<+O0N?X~X-$U>IeV+{rEcb#HE z(7lvgA*-iD8a`3u(|;s@4` z-Vdp+`SP>=lT^*Kk)Al&o{4k8gIs##(XOB7VKGSN6LR9WYHUi4Wy}th(vgpQnbo4F zcQ^(rQVc~O<^rjpu<;^qa@IWo_F`VSSXDfIh|_%v_;cpVKsll@Ur-VesKN>KngWHa z3K?k@0Bd7p{%kLK+**|?YL-AXrh*V z7x;8tDk>q!=u$@6g77>zgoE9f4)T){&1^XPKBIeR2TknG5+!>1-TKvfXYnBl>pQAF zQ->k*G||-YhYsw;SH3=6IdcuEuNJA6UP?8J=O0g5WI%~xu0ZXt1VT*0=uGU)OQNYN5He2e%`LKSv|l{;R1wRoE{dPo?FpNekI4|y?g|_-Db6E zyu^UA3eTu7PNP4D%dWRb>&h>`+dh)RG5YuT|L)WMJ}sBQY9UF1iigpRMsCap&l>$v z62ibST7z$s8tTv3W$reG(^+^MI~Cc*{hbJFKtvM+uJI}T9+@wXMmdt$lp5>p~Utb8rpiwMKc9fkXODs@p%4EplB!Fgn0@iq&#+s?(6lx%xN z$`^33?Y3cVkI=S%KXu!f#=UpEVs~dT(uw6zMMl;398fafpe*rtovimfvEh7pb-$B8 zqw@jv4|-)x1rKaoQEwOLd(6kfuW&vox`jKTy5Wll_Ck|3M+_7Z1viX0LFsT|hyxl0 zL*z~JFK1JW&e!_b;p1K`Q}^}sfe-syu+MVf&f6vQ+S&ETM|$blxQjxdh}27|>vH+i zZ$%p<_IN5}nFOJp;&?Tpkd!HvPd;(DPtzklDceI-qh6Gh<*Ps(WR%#zVcyCV?M9Jo zHPNY)A`;$)YVz=GOcx8z`kaev@_uy6j51yyM0 z4gZ8db}ejW^}T3Kf~12=l@#*dT)KNQMuc5Zo$4|QVpSWajI|M)Oijnzh;OZOkHqRI zuve|)NiKb4z20%?`#DFvn-d_V!}Jt@BZP&WE@wZx3hh)z)UG0%&WkiCxl&YDCBl`+ zkaXdM1hG~g4I9I}puJDieYz{5<1nxiH!|+^M!FNMZAL22H;yWi9-Jl_A7};Bj80U} zMd|Cg7A3G#fl7p=>`@MWvVOTB+Mb-R=Xx4&-^Ifps6Ud1TA3y{cWy!@bXH2{q!VUp zu!uRfi#{ZWVg29s;nygwIl|^5hQz-ZFoeH$HQ#}??e3O6{x!EZHO^F5o-|Ur;cPB4Uy^&g{&x1 zm30ZX)tT6ySGsKl_1t3mi*iRJX!9c#K7*#$`i=oK-&aWHP`MR%8G>B}D!% z?W83ASr(>d2}omtwaM#KGLKc2lu($R=~+cD;?PIQwkSEJzl8lvf}^tG^;F_5)DdnT zU7l7G(FrfV{|R$d;J-0qh9L%5ScQmhRasVgDOr3NUmnHaw2W`6jU&cnS2nr908fz> z{`p~de`P-v#t^+;nCJ>MoLTOuN94?ok8VK+A?TL$#PZHl>O8X<4VQoR30lK(grBD_V(S zp+P422Qefj*_%Syomo>1Uj-P0wmcZ|cP7r9pfHCQI_Wfz_8x=HWe5$H?X}o|$|{J7 z0Mf-vTuL~Kr*&HvQM-mRe3_e;%;dxR-SRsdhxIrD?_f7n#R_E_UZv?rA+VCNEOcic?yosI+S01v zzAG&t0CW%Th`m|dvZxP?d<88z^YCx-lV`if_Hqd>^jBC=e2qV)Wv?^B~_ZJ^nW^r}f;2;;I7(A>;9IK&tK* z+%Er=O|pLSITAOciA)D1-`&OpgpX0k7>nDA7-=xKCLZEY1?@oujp>=Ubp*!S} zZ&N1R2EVdJ<@e@9F{uq=w{_g;@9(Unyd1h+o>MEf?t)v_El_N$M$bKLhXMAQrt)p29GE2&CSk4@Fwuz~Vqf&F{WwReoX63`Lt}wQ zVZMV5`jKpWY;0pk+#;V5hX{~B1IjUCg#9E6gn!^!|DhG*z;9|*>cr~Tw-L9o!WC|{ z=L??o!Kfkw$pgV$TZ(5j1bb-@hgys9k*e;Wi7Z!oRckgPfckujpP61b zf_#(6#AKluvS*oF#zl#ImTY6LuU-Wf#M zA`je@9jg$60n{@~8U#1+nTeAoajOp0@sndaf&M-16zR9@M;4zAY*qxVg9FkY~T>%eYWz*Y|)PbxkNNG-*me-XE&Xf0?P<65*sNT6 zTsx%2QJ$T^aXmfwU*=DT%=&Qx-Nd46kNgg))}a0XJaFEFwxx=Qnam8utrG^*C6xfs zN3h$b$PxBs*xB@>nm#NSJ+1=c(4zp;aUhl)gt}B`^)ScwOJIUxI1pWS_q#m=d$N!= z7aX0du92e>5)m=U@)wjkU|?e4{kV%cuMdkEWR{-y>Sp`sknnGMH6(S64}|h1iJ7EG zx1<~xTK9h#lq4+R{}Fgmux3?=28o(3LD7T17<5-hBA$%8?#wl$>(n?hKkLUDT*u^o z>`%zXV`E}pEYfOAZ|7pG!Pl2>w|Fxs3dyb~!|f?6KVY*V&N~##MV|>aS^y&dzvN4y z!F(IXM@Iz>)RpqHR{!|250k06oee;I+4b0dKvQmM9aeNkJ-XjxvXslG+n{5-L+dbb z33F>lXCI*FqbR|DbWxaqQA%2f5SCfiw}lB5pe37$R(r?0Lj@ zlcOG046PTi4oc>=b8yp}fMqkGxeT+6#qvdAdOTcvVB%+zmlaW|Ql}?gp;@CUoZ(s7 zZJPK>sEJmZVX3u5IYv2rC5(4XsWPTidoe<$6+a!-gO%E#}qK9)yKT3T5Ks#OyAM_3zSxN9m= z;u577Q%iSKtn7USz zEmK%DxUbE z=-r}8ed9@44&l$c&=ioPP;?=wWl@*bT6&GoNVjQP-wHV%DOE#_TCqk2&BINeX*q3b zl`vsH!&Fq@*h7{Y&&PV;_Ceqsp&)75CBnRyk2{uo_XL*!G;E@WKD02@QN-o8=RQsU z?)TSThiLB|ug06crWg~ygR$Zxuz(=5<*`;vzmUfUI-Xu$8b%AqDN+XbaNm`O+x-2R z`v@f{<13%OsyaM1FVlJuwMeIW4A*l#*VE=K#si9Dm4ZAB*>mW~6$#tRD4tJUEw{jQ z?p)gFOgtBp9R@lZ%g#KKOfe$r(L_7pANvtE#;wp+g*ho5sD|HdKjEb$lXUMbw&Hx) z39Wq)x5}g(CMwEtqChIOL22%Ow}zo3tU73UQ};hSOwTmZ`?8b9h@|YnH5D)x;O3O` z4hz8+g%l*rXcS&r8&8E?p@NRG;dF5rxR@t#T`>9!l*==F)g-OwP5+1ObgDI|>@z--b*HcRa zDVjSe24?QeSF|k~KU-G6MRER92M-qEdhjKcZoT=EXS%-PYC_Sr{&(fF8}KZ2!{_1P zF9p|5b;|ei=dVXQ7u2<7T3mMfWOnq_U6r-T(u1!6d#Yj=4Zh zAXb9g9$LZqG34up5t~xUO=@X<6;Fa_H?zf3O7bVrKbVGGLrpDWhj*0ZtTvopR$vv+beJ0>F^-`1jj?xKfV5w9;Lm~v z3^rr{L8}!XR_yj%Si!G+5?!`)2JMPB$p z*Vo%NF~@sQTy3sL>ENP{dn6j2f?&7%#Q&ZhNi%zr?=c!CxpNU;y5Jw>93(B!5h5a7 zaLYwQi2=s-*kY3yJ3;-dR~HTGA;_lu4ZqsGtfVT2SS32YSr|As9qB|`bFkJ`wKcu;t=C<=0|gb^)2AWgaK;-nf)#Hg@kk^6_ediR zA}f?&xXq|sMkTq*0*myBXZvhA$~5(Si{vdL3QN6U$b{2DhiK@zc>eEVX=Gy&Y&d3z zdrG8B4PEN;Ny9Cl67mRc;Ro#(jnOtmb9!>#3Pw6Vyv*cg}w-T)JJ zUjGGDWWF3Vm<9odWV~fq(KHOVw9AAGD~Hd}wwNcCvy4u@cGlltJ>FYC-tOK#~ZOE8pM8h1}K;(m_8^7zA?6pQ&1H7uws;UBBe7fPq~R5FQ$OH$ca zDGkDmOC$$E8wJ5M!uL`BzO#R16w4KLb3NB{P*c9VCujxDLEY(w>G3@$UO(A==17#rvHES2Btap0}kMUbW$z zU8ouXhL}m_Z;mp6FhEl|HW&m*8Gt%rMS%B3@|VUlhCx-S5rbj_;Br=EktZgw=*?U$ zyb5M~xD_`A+z#19cF}9T6~Y&_M>t?robf6KcRj~{*eTm%S2&uu_m(kb+!qYVgeb|X zvK$!A>20*~3?%szf|r7U{;*dL@&@jua?(pq~X&NSq+s5U3r)~imhr>RU z1TLPaU>5zW37SVEi&Ku77q_OCK5O9p2nI=&<#N?{`SHvmq8NUcOZ=u{yo}jb5%J&aq$B07;aQx7;TP1LSxaNz8VxV$p|dL^V1&% zR?sjI;)h06K!1>3xYsTj=EBy!>~H;L1z`_}*lID?b3NyOa6~w$eAR7{bc0J_RR&(M zBs*|zvNvxq3+E4=$|0}_a?g_s>Pk?#7>pynzW+}f6eJ)2=<1Y`Fp!V;yP*msOTQb{ zlB!t?4vF_e!pg`H<}sH}x4(1x^5?xxDrZzzZFz(^e}h7Wt@mk@Ax_ zhiQ8H+Txj{Emab#PNU_jcV?olyJVm(RC%AF^?yRht29W0!FLMb_2C+Qkf5)=ZOtl- zTGQ2%g9k$W!`7&7y=@-n_uXxCZrrqG$UT)*FbZ=M*y8cRPo<5-KW%IRK6GNY7N=04kg#C z+eKjyB{+)i1Xej_(KhV<-NxSs2|o}@-$SI`80 zzgyHQYR!n|N()HV2eGwFikNx$?T3~;Jhz=qQSuvLre}`Gv6iua?c^f56OZ$~ye` z$60mHElS*=BCoRPO8PbBuIGBL=P)LwkM!3)+@Doh@h}T404C-4(D>Tu$5rS6rTwv+lGHI+}=s1p&tr-mdmFTj~jO2oU6W@KO~zrd+=Qgn(-kA z2;$wmW63+?5!aBbS$9T8oNlH}H_PSLE2ET_7z&oeyA_O9r{xpVzj~#SPSm8;Vuk1T zn2=jhZl;Fy9bgnxceyRmHb zV7D7?IZK^`s6XUMvPz9uzx-|%&d#_vNRsvXDxfEv&`S0I7kVYz?!K`ftR=-?Y=O`dy2hIx*eMzgVsdOw{&@!)8 zhaJcz$rVudaG(dvaIqmc8!tRE^M9!4;UdWOT#rspkduaE$_Z-LQp@?>U>Vd`gBA6~ zMkWwyPnLjdQ3@1_lFk}LlQZf%0F0sPyzSIZ(S*;A3ae{wZi~rek8&sUxT!TrCw@2j zjM1vELhdf4qTdbeDPybe=2ruK&l-S5PPh1%6W^gvJ9sFu?%q%xmBoHH7+Xc@KKhp0 z`!lr7&aC*w@J0hL=tOBw{;ExnI23f2khhX?iE&#-c}mc-aJSK}gM@Fe8kzn^$n-PW zxcdxU(NE%2Vf!gQp_CiW%rn<>J@ro`AB-awhA7Yh{XR6~rW8$!7)1LJJeAmZ)^Rf( zhGAS%Gcr6YyWutl+oWNT$>bN#uHZ22r$TH~;976){|WH{iY;c$Jp3{KS;u3_D8 zMKn#kjY#gg?&s^-W(jA<3naZ0S+tVc_Rynh+cb^IG`^s}1;Lk)$p`%2yr%LP(HzEM z7*Egyv%H>H=r@!Ww*P(+D1izB5AKS781zX*Hq@169IHuFS#ITZW>|1_4m*lyXz+0z z;Sc}t!p`+vPmM#*VWxs=KxD4(N+3kqq%`SvZ6}}d!~vKQVF*YpOI}`7*N*HBCJZB7 zl`UDRjd2K>Bna6b@31wMKw~j z@4<6O_NPNZ{r0Sed1TqTAb($kr8Y?oJ@#)A&h+e%@N6YZ*^TLXzud;BXu84L^~D}( zcgYM#X`3dMfrx+@6>%0Ry8!5kqps&V?HzGV*hkDOmo7rg+Bpt`q?%%)(oDr`FSxoj7s3D(1|I*<-j!%auIgHE zSLO8m|KEKn@0S{q)L=jo65xt`d!5^z*dR0^gofArn2!4)&Pk?+mp-OgxhCuc@RxyCR zMs_i9PhZ`i{n_FXrsoNWVtAk&79OD7eiM7stEUQs7zmr=V^yX^gDNHl;Ln{*4=A2r z@MymN?am3oO3Y!f=6<(#sc84N9-3nipkg&T$pxMoS}spcH-<=4MY9k#j4TpUii7&VMS{=1cTa1uxdv?UwNn12mFA4 zu1QtYrj(Qx(}@u+ia%I<5vw~M98l+8q`Xr6`Lku>oUf?I-=jOly|Px{{MxV{T&{=~ z3|4p@9!G?$9AYitL8{h7Q&t^&YAFK-K@wgHs;JPECRuJ-OSQHqL7N5nYYQa#9! zn3IlpFG&YiK1ZeR!YEwO8RT`(FFUcyRc*G++RT@-Qwj0m779nyn~ z&j+k#zmarFn6jfDXm&=!{_M}&Ki%>w*b+vwdO+3J$pPWA+=9E!xKMiIaG-+*;>h-L zwnNg zvYf05MY1aN4pm>-OnjpR<(x+Pd0LhV(*fQhbML)^`=aIbJ~b`{Z_|r9w?(76>Z|qx z;u%`hyrOGNj;g~hBuisGWrwLAjbCVJT?afF9bGu09y(_wyoa&v&vp(v4oYnRjaZc_ z$=*`!vcJ=R&E+{6O-u~KiqFmG(222am$O}qRZzdoT*}4a+)-i1>PMHKb*tIt~ zoFm^*;XaX>{Y0J^U6GVrzp4cQIcCg6w=tMPLOEmCT@BVfAgZ;*_t=uY2o;eR^y6mv zs+u=SrC}xLrx*5Tf8OoEK%>+ODFWbcq=+FO1kwTFPN()^@hsfg{tcm{Uu2_v%F&AL zKjC0bbyT>#b+i_iX4q;Ok03pukfEdpbksyX=`CHK3PSVmp2Y_0| zPVrafeYZNx_1F2y8ipv~3#E88DHR^=z|0LLl~}N4LZQgvPUhCFC+Uotm*Q@G;H|md zO-biZM>H)&qJoyFTV-tbtz>g%Ua^hI06RDx2V&IQk*jAmSm)BZ^of?Su)|C^#1Phv z6|Rm;ex8<3QD_^b1z1_eB%j{Q?y?xhBkamRp-c-Eb5Q>3y1R|ih4CRP&t95u%K_(?qR=m4vjSIfJ7-5nwrdXrU9nA z+KR96@in6{*CK;$vXvo<6kGkurWL}_v9dK4K0e$YDn|;Z~nq+rH?$7=-_ckab zk*Yy-ssN6PAQ?wv&jD$3Qf9M{DjA5?;IRjOL$|S*cK2#?iw;;!A5pKtZio5Hww?{9 zBTc7#53C}3f}*L)4swgBwr%sah4eii1`Wz+R&0iZYX;DIHG^l-)F|EN}AwCt($9znjSJ8pqI!tytB;XRqjo8ag9wyghk;_Gh=u;{?>D{w({U!(nQV7FBel z3PQNx6>%^#jmcVUuruH#&H(u|6x{#k@efx2th~7Pan)ksBql7KI1UFKOALc}LNe`N zqwLLoW=tz{r*Fv2JK1%Apa}8k6nltRXLipc0kIbV!J<@Vo&GrIr6H*bAuB6fjgwZ? z%{r&2Qh+zE+M{|bhT$DKfGbtWb&tMS6(pQF(XiC3pe5@I({WeS50tS(FvZ}`hGYYK zq(9**1uChJfVK+J9lL3Zy70FZdTRK+f5QI!ou`{ueV4IS9|}z?GRW?Nl#9=ZE zJ;6ggQggq&M%r~s0OE97mY>iz)43Ws9x5@^Q`c5uPt+&!j?_P)blZe_2}JItbZ~Q& zyR9&hC~p#8PzF^hL4X)O^D|wEq~5PsQmSSJw|*pAOI&BY>f;o{0lOPwfA(kR^KNkB zJ@II1FZgo8cfbv-o8M-b6mgsev|Va8HVje3;0oJ?l(&5ubQX{JNA`M_m(BQz2gc4{ z@VoJ?_*qjxve$l*x#l1pcS*KFx)Q~;a4qEjoleU_U$RWMJ19yR%HA~wFYY=kTg%zq zz*LA67mdvU6Wp16ga>z9T`4k8FByV#O~9vY;AH10g^6XpG=HaXm&$5mngYAyZGZM> z0I4!p9l>DIPZ`8@Q;UYMJKZQ*3%12z&GrAfDFa`~RgqK-s3-vyK1HP@gm4|7YcHle zoS35P`99wiuT^vD=nX*oiQi(bEiMNQK5t3`8w<0(C%6j+&Mr4WZt3N%Kr*8%3#Z;# zCY~}S+dL@FgtJMSZEwqLc zVs8|rU&%X`%9EH@p}Y^Fu!J{4Y4r2`JGg!}maNIK0)h8ue@1mo^f*U!(k5(!M-<|D z7>wNlaBY!~_N>ZyC>>+mB#{T@>|NYB-LSqMdJuzktSdB#j-rv9!<__k4sZfEs7&or`3f_mU+WPrRiAf6faX?mTV@))DrxsX^# zro>5^yMa|g$YQsJ2VbLc{n^i82XBA&XMeVTAgUdPy6uS@#hejx?fsy+>cORWFwBb! zHV5hF$k4_LDC#qyrb{=ZV?oCWKLbgIv+@A!l0o?(^KdfV=R~3aaZ~CNZ?qN%(qII< z|1lP(te$G|$)rsFdJR}s-R9gtC_X_nGEd}ZB8~K?&riMXmGoewo~6nGN`5UfZS2m? ziJ5|dv5IG|kQ_ARu-?lUyu^dCa^)|>iVRRXD<5$z;u@EtnS|((941hV;eo$qTI|pM z1ResVU*w9#z)uL-dZEeB*cRt1(*{lX1uaRjNr|Ef7cfz}vIsHmD@gSRfwmU@Bv94z^# z*gbe>MJgee58`3O8+=XtS72ydw((uW5k(Oe;lJp1Cyx4AYDx*wlUe)d64Z8!-Jkt= zzXwujcD_PEo#2h(Ij8vp3MzV{i;^1gPJ*>xH?d&pi&d;2#WW)TZIEAAS5ZNkqpCJD zwWjN2k`yRkK*_AEo6kV?H*0ijY+11P*MmE+(LDeQ%6nfQc%jM z3)8>}h=bX@23aw!U#DB*RT2BFp$1hH)8Q3PGf>cuf&vSH5AW83MrI22)$u-)9z;5@ zI;$@+^ExDr#9fr%VW5W7-AItaU?au~G*L|68n@|-z zmHpXU=n1?D>Fq@lY-Z7>nvcOzmLRn7k^4hDn5c&1-dx2p9sO?Hq|}b$yFuwEPJrGI zgO{u7kub6y+SR~{EiPxJGOo($)}FvuMeO2wTP__E$QCsmiXFF%D`GE3v8#Me1HtV0 zC7udn&<9;Fv)J||_{t_4)3Ob#(2XO=jo9BDYMB1U+Wzd%{tO}X)J^9B4F|Q3?|d zOPkhw%svy*5LAGsyxP~;$d=w}zZ{>$tV=;Rm$I64WW^WDqlVSKHmB2aT8jOK;Yk*j z0<%^9*Yj2LC~I0;a5jeO%@Tv5P9!A_I8_xM3aNmr0<)4`Sh9=MX~MF4Xr&^7u#p|E zs!Ne2O;H{;q$`3!n+dpElSsiQnT8&YdU<7+no9jD*X_BS`}6isnAZbs)c{(fi1H?p zaR=(+V-fa_&^vh^Qh;+OvFE@L2iVCfq?dPup%f6g~_3X3@-&h0%P3%M4o9w zrlzjCSd#}C!mv^{8N>l-%WK>kud-ZEG9uKJHJl)snbRWMjcHg!1%dDHJ$239CfWtTs@_fn~d_ zz6gA#TxEy4^fXYwLm_ija5v*@ak@$TXy*xcAY#Kb_x7gyb8>oCmfel15;tk+s;w*}EdstdILNCQybH zh1Y55XenKe=P|01j;vKJ*15gr9L9v$L+AF#fm;wDYx^TNHqnawoD$JpKoI>I%q|>3 zst7$SiXc12YXG+zR9HKqr_Wc-)n5J7vpaMC&3iNcnPo@Rh97o0d^_Sv_AoX9qzYEt zV#!D=64-jl61dJC5>E2s{*rg}ZC$hIVDlTySqwegxe;h426Rbi=88ok^r%FJ7qcwj zbWs}dZU{y}0^R!25Jjf_kujuX?Rc!AOuv1CBW+dtvp+G)1-&M2@OjPFq0qEV0%93s zBoVPy$6O+}qckvvXMKdi4$=PBI)~c%9CRF!AGW)7;7_ggeVBrYc(L!dP)(G#5|8(hglY~8OEsKB}vm# zp1YAQNq5^cRp&j!SH3AnK^>zo+ON;zT47|{%shg)F0Tk6+!P|Ke4d?c4C@;LcN$UHJqmjAKDvBl>0&&fLJ6_U@ZK+yKJ<@HiuD~t~?e`qVr2iJHKu=ul&;CrdxR|%H zT3fgr*+#T~I@(Lh8Xyb~cEMQ6*nmR=?zcIUHPtJa`W}JRsG7e%nK6AF@V}44f5-VS z?~b=9m&*IB>#Y||<8rt&iS&r7YdVN>JCAGYQbC=gpEitaC*bRlEDJwRr(19yYZwA) zOgSlYtVd=^Dqg_}yO427ZQqZWDu-D62OZl@tt}oHsY#Vs=Bj#MP~8SK|8`5e-p7+e zE5#WgG4y7w-Tfm|M~-;HPMQ5_aCCmh6Oq@-%U7E}yFa7XXKFVm!vWXwDMIPQF_?$_ zn3E&QefhWtKb^r~x;?>p{5gVt{r_RcVdmxR%Fl(%!Y%~hJ1JykTGR$s3j2c5sOo?( z{SZ#N=RSiK#aYguvcB_SIM*naD&6DBOJ7MysY>Mq$+~vB`jwzS_F2I$kBP9T(;Ba0 z1vT|m;n9~)mGUxD4z-tX!<^(>abM0?G(#n?(ywS)Y?Ye1ia~x}Z>Vc@1AltLzGY!5 z=DEyq%>uZ&QyJcrzPp5TaPaZGAEG!`Qsq>Nw2vzvDUE^yBMq&AZ3E;pYA4 zT1>sug4wPV&-Ipv6%qiG3n6SXS&uxz**hzmMa;DxNp zWODglt~qwkw;ygT#pSW3ob^DNUba<1>|ArYu^Q_VgY4#aON@CA9G3dp42%cfHy7ko zpJ5rwK!*Bm5LPRdghRUT)mbCBr2@lUlqaf|NiQ^t%EJLZMDnhn%7k^o5&I|9fpEL; zkt_|}mc{``>R|Bi&;D%pRIt(c)7LkJsB~wEM^v9M>`Zb`GfA~JxF^|e8TD*85BoAP zj1wMn$oL*<#`ND|=3zRKr*lG7c6r6xiRdK!+sRjnbvvS3kWjH2>M0YS)@i0pvFHzM zaEJ3Mx(%0D8k|7oHA)bP>Sn*`pRh^5PfHx;0Jt+FhPV{yUi(7p2aG#Hx%p zc2~Z4JRp!sF|wz-_Gj=#c-UUX-Fda6Vt;mqZDsnRZdQhVOC8M8ikuq5E$c_oQT#sa z>!qW)<#w2_Wnr+61Q_pw&6T{r@5dwBeo4yRR9{lAwrwgD2fME@#VRor_TFNcE>gK` z<^m*-wT6zszeB0F=}0?2M+Hm>{6NX(k*nD@Wf-uEH|AA@bNkuD<&9o>VSfCW3zzjQ z3s0w$Q%y{!?IPm&-UV{B!Ujv(o03GEEdcNANTmt5CK4&k zC|}0Dzc-zXul0`bE~pFSRVqd>=j8D&qKr>xNG1!osY+{=>tvCipWn_lM5FmW0E-Qj z$ZL<)wKrIMf%Wq9vtNn%yb4INJmARNbe4jY=}0DLxVYPBLrEg_`+>OG%5ZiM67j@J zMmE)EIik01(W2TyMX|M_DLjIU2LtYmo$$iFTj>4_&#)YBDQJeX!NrqxYli~>Dc=to zBakLusHmGd(O(Yj^=lC)jYymySV4zZhico#0~COJ57At+YacGzrvE#h8!Q~|tH(sH z8J(0__og2GGus=kg(HAW$t&bzyj$3AyqRn40)3$vq2^5W6jmn57}K_`K^piIm(SQ{ z&rCSH(p}mlZ8HslRwBis1D9&lBXMhqTR0=)Y%FR-8aYN|W>w8% z9~}B%vAS5YdC_#(A6X^)ZWWnp!rWLr%oGvk*6*mjJKgqYWV7(kE`Y;h3B6GSNyDC< zkHzR@Q^Kzd#WtA4gH5t(b+eV!NG3WavS6Va=lEqb&-hyLxzO9`;)6f~U4S@Q)+z|R(m(kv`^hDFJw)w>Fy z8!KD-?M3c<7XZ=ck{=;VL|H7W)z2^nZlk>cA~Rz@{Zy`s7ZV&8qMM|pK*sAN2}u>@O_ib z_2WA|NuzW=rF)hB0!IU(jx+v!%$MG#vlsoTx-f5amXDjX)r=I!K|cK)4arce;j2FB zZYqynE`p+tpA43kf?_VjnZZk9F53=I*UmDebU0n06Wd6?VjNLlLz-7cp+!K?nw`9d z*)9E##%0e$;bv@w<$+kg>rIee=4nTAZzUPob zb$i4KeiVRe$*9L&J@(~>G3N&^$W z3xhYUe{bDy`DZ$3PB5R%rSp!-FNo>mfd74*vrT8X#jCdWyZwnxn4^tk(kHQDJ%4ed zm&ORQTn?8$iBRifuwuYqChS`lH{o=GLI@U=?nhd{{&RAhS zOjB?G^_riRlg2aoZAqY(f>^Qf*5;*UOq46E#kKaRIAyXHkLoXISPg3JYI?~&^+n}a z2?gl7n72NSW1vy?XG?5=cb5;gn1W8^a}IFxNz4C@Tf(3e;Q)dZ38D3t1*)mtHW5@< zmio!4ZEg{oZNP7j8)BH@Fyp`DIppo!+cdV}C4H`JV!(AXu*c;Z+QtQ{4AqAVEN)f$X>Qsh-NB&U@R&^x=y`5L859aT58?4Y zb{aEj3JL9f*=&Uz#ISt>R<4%>yEz)luH`>PBWDV^85`E9h&Y!2=AB}Bb^~mG-tlY^ zx<4>-4H$g!1CW?8z%q)beOr9OpNwcK#j>2>RTm#%0zT6StR^%t{5j^=R#a}9QcK@vQryxb)Sk5*6V0K!TkH|E4s|DqU=3} zkX)oSQLfw`Onz2q3rRnQpkEolHoOaA%_rnpK)LQOT4vD>WsSZxECRsH9p#e)mVh?} zj-!TEP^UHE$MI;z+4bn+*&snj;WVb`h^ry?+K3XhIoY#UsLb?i10wf`w6}kD8jo$H zJ2+zgBvVIY5*4FmEJFz7lbI1ALrYM+9;-R|C0Bxp-wTjpUS*y8hb@?UZYfS@*V`rA z^uOcb0EbSJUGq%a23*VXHrU}%9>tt&B?!@5hd(=F9O4AE(`zfq<6PXcNN@L+Ks|W4>~g#)>F6Pwog%*R>>OibCEq({@%J2ah_?khJV09b0viB?Gv#=R-)2iO|+h^$dn|8J_n|!}JNp?5HTNr^uY)maUlVb}_n*g(- z>W=SB3ERb{r$cA0n^rQm3nK8)m|Yr4DZ{^|;Ak@G-f5yy=PsIK#`NED{@=$r+qAOK z+s5m=!E3df-sdt$0}kx7RuT5?@@;$i-S}NqjVsA+$+(Usz57a?Zr4rxyF&teNm^CW z0CcA<-GmGQ>MFHytfUOnS(YnQ4eO0lpwnRsb&D((T&zuy&Hs8_?W=9}EkReRbYjeW8tj<4ZHFE zZq9Kyaeg-%I#jMW-Ly<{T9%V7v@Nw|c<^gf-2zz@8f&A#n7{F>4S_-3x;=%qwhjS| zWiOJeQ*0BATW7XWytnq1nQ45wBbS$Qvp74@0$rD#bZtQf<4IHi335qpT7@?#UgS-j zx_*Q1{?F}Mbp0mfcrbD&Z9naV?~+`01%`1*P+9p6$_3Tmn6o*O8OYQRW25>MN&`MF zKR=G3F=kBj0Uw9M=~5S|y3Si_9<*JVv2G3;uF0?LNUX#Dd$DhFIog^XgKl233qlhC zOY}#=lb7Hw*YxYudGEEj9`t^HkRi=7@pNLbClj@?I^|MCT}!sGsK`KlCS78f^#PJw zSfMH;e~Y!&c)C1sPgY1A?m1T{)X5Tt(1@E-bs)+{L(Pj6!B(-?2$qd0Mx=AXLt~i} zs;{S$U%+qgVa`AAUU@MBS*>i)@s85_^WH?@HUPzlND!M1&Txh$6Hy-|_1Y-jxPA_t zC9Di;(%yR3H^cO;`HM0dZbfeq5O(v`_cF5Zu4$6*=W_m75)*xgBEOYKlH)^uPL)(5rF*2)zDk*ac zYtytj7NUA4GvYyQy!K{MS=>S0pPi1|UkReYGvBn+ZD77NCqfV2O6lqM6D2-lXe+CP zAhq>%E%Q)B`MG^QUb0Qk4Hjm1*V{GjP!Xy^SuD7g{O9iGYvn@pxU-j9f$hXq! zsyr|XOJ~_kXd}Pg&?5vv!WX!#8u#;o^ z`1~1+8-P2t@bA1)+QT*LG0mv!hRQ%eqlxJS1v#;%R_o@MAhVUCkqV2Cm zSjEQc834@Z=pi7qCQ{lh=m9pGPR|tM4i-+GaCkEg4}pg$m_Mp0=^_u@^xdW z#s6=d{G*xhPoBlccwFhlU2=Tk;Knf|x%uN5FbCD9#EVHM{Az?M1&q5NW9Dop3L?{x zpns#%ujhz#*+L-?HQrSd|OfDb08SuLY~{?Eec+OK6bV*gxFlHwBlP{91>iV-4HOpV{w8!YvuZvAQ> z3(5JToet4r&z>gvjUQkm0bR>x+5I>YnbeN!B|-=jPk5%=)M#>kTdVCtoJ-Z*LUtia z3Szd}C--V@9`Nl^Z<{j*-h^)K__a)C*;zK5Qp47UgKxu7uEvudG88_o=?&GH!(eI) zR)gAOJ&UwVMm)^(VLl7f>BN(%U!d1z&Tb9Zamq04jZyGws?KAB(_%0_=Wx9?KiyV} zm$-_%ch6q8=TDb~%O$Q2Q*HHh2KY$=B?fhiUBTs@cO>P0ft}31{Z!~GnDAHrOdCtg zwT8%oQ^_eZe|N;rSnD38OIQ2#cUj$BfUK?n?6lMPv^3exqrZX#u4q^UuMRs??awZT z;dVRud{Y%vK$yCYnu8untiSeWU3xo)@O1S{CFvcPZ@N1LZD}Yg_uXv9hTl`pk^B+| z{5jt3-|_I@VLm`t7@D5g+n;ZoGIsbuAZY^#{1&r;WL2!giq#!~HV#2bzb?~5890)k zmuAUQDNlzxKOBd-0xNr~7Cd#Au(ugNJ)6PA(1vJpR*OwdjN9+9^gv8y*)05aucWs$ zrzUL~pl<8-j_e>b(0ZEIicQjIY3Y1U8m6HKNNvp{Ml8le_hFBd_M({bVhz!V*=_DW zYW~-84_b>Y8!Xk9*cg%N%@I)UuP@*YtI4snW-TIsq4EiwgEe+zUGQQ=B{*vEa*RJo z-(ZR$%M8p1d`#1cmuyoi5VY^$A$*5mPWp2Mc38~o5aZtbJ;vQULtYvBvUZ@jXpOTq z@})x_J1Wqp+2zx}F$xuU50RbA9HE25#@Cw{;Bq90kSOjh#JruFjqwvrH>9vx0B za5g-n&sM#{crxr-Xl{sT-AFW;qHhaxvSy9Q4o5d?RW6Ko!M^B1%^?nO!)&dCtYWvl z--fu{xo4Z`WgYU_5Ct%-h^$<4ssOaTsjD7qaOVm`-+EXUi{~-WT^^A5VO-0})sbi4 zIeIC{1Ll8+!@uKvoFSJ_hEw@Z?)=;#apxTBDo>V%=xnq=u(>JA@bWi|gt@JT&l7z8 z-)ULS-AFHG+jPB@60^xs-Cb7T%H}I}j?5DV6WqQTbCCWB2C>f|xMl)pen(fjQNEs%j`O+*>0W<(0eQ@=U{y9{PK?xyU!KG@mG1?-Um z+&DPW8UH=EwN@5>j%GM#LqrG+8$a~~cf_$N!yJdw;MK&yasK*qKEQmQi!rgPaUM2| ziWEWFX3s8#%v8pODiY7%F5V4|A~4!og&wt&QJ^}0PeDKK4Dxi2jXnQ-{w_jBJly2T zBaP5NvWBzS@p`t$tLf<8ZSxYC_*kZjbjSZ=7+Q6YmlgLD*23zp?RVX?XMB4#MOBv* zQsZUo?<@!UAQRqe8Wn_S17IN0+<{?CWlV8lvos&Q)=z375Tmid{)}o-`X6CO2R*1? zCO1X)8g%BblRfbOH|*D!J*nt1uzGz8587F5-Y!7^$R5q(WhY5Mw8g_bz2oy`$bQV{ zLT?{Gh29$3-EVrVHtPn400A#ZHfbY~8(j<=Gxi$Qf1^UMEa$3iKe_1Vlu}H9s_$qJ zm+>uV5Liesd3lsk>N|l4+lcRe3f^e5fNvyI3xJTc%A4mgi(VwS!}^7;diof zH_a2P(J%}(W-qml+U*o1JCs2iYBx<%AB6~wtieZ}Qc@K!b z3K_3|MVHy5kB&jlanp*c2>bC%@MfGNSvZ|e%X^p%g|d$5z_ew(1LuXq9gn11Ju4@Ze4~Lfx;Mx%SPn<4FdulY>f@U7Kj@qzzuDtsXbOEQVAg~ycT>G=KyDY zl##nVj|5-;TGd;DDzR-9fR{7%Q&uKLOP-r66i>A#gzD=YbDZ(-;o1?^Bt*BSUY%>OuyPf=A$g0#MeoH({1CK_3(4<= zT(K-XeV+0yjd_1_!-iZ&%Zzw-pZO;}QNJM8Sd8*-$(NacYSp3lc|o*<(&7OVxRq39 zj7my6tmDkE{@Pq{)r*cRXlB!P3@_lo>~Rl*-kN6wKfcwoJF4IFz_3I4HVek4Nx;fC zSqJm?Nhi+fMkDnsWkbAtpy-vfMVRy6G*nl-f-iJ7kGzzF?D;lF6#yJ4_B9J4$+@)!)BdA+> z!9KUHR_QA%#PA4GabGDi2q@a!q@POtFVRLzC7d^s)q&;=bW>4FR=56? z&*#{-5|$;&1>Jn8v@Lhr?jH>DX<&n#Gh{w#H=rc96axhkKkPF@ zpWpaw`m+uI2KG7;4T7grpJoz!h$QW5hP8E=eHONzUZtUp4lmiJ|2t=!&hxzQ#(v26 z${S@t3XWD^Gq3z_LcUm5QP+gAzIUf-SO_f7$7^z^bECLIbnHuF>Oa<6?r7vZ)ff4n2l78VM?uKtE79 z?VT=tu*yN<+R}ramByKcJDLaPP>hXFX>}rW!Nt894{2rn5X7<3hmifWeUEKh3*B;d zFdyF*ENpHnEsWxKFCjk6`0rfkZT>jS7=B}BGyG%d)uP}J*|70cEpKcD{oO_Rv)8EB zk@PbOpNp(z#=K;UfBlinx>6AA`7;)gN_RGB-XH#T`8ljmeq~3QHt{k-fYskobVWv& znn_Q{x!;(d3hybmnRx%U`-VW()jis_^{UzBU%9DqjOwpKqz@0!59e$`zQWYrK=e<8 zLg3)s-J+vgbQT$B3w~&Sv|uYqM7Tm|q<6-G(RWR4c3zm5_hQ*|6DuxfZ(5EljmrR{ z8KMLoJdRH=F%)=pO(go($`|KX<1kN$bGGSpS{CPQ)9!i^D}2)0r3P~o$kHcN_F^34 zL7=i=$_){6SVS~V`272q_m=a+{E=D|Zgb%!JS2}14#DL)g~TR13j|`xR1#$2uiwTx z0T>lSauOEaOYZ5ZBU{sRq5$5W@I1B4o>5|k(E25Mb{9*Cfz$R41(LaeBvs&b-j7D zteL%W##XaJ?x(%4_J!w0<%V1YMDiz#%CyvJ_RVfDt%Ur{MEETW^(g2` z0{F`lyw{DI*Yr)#arMvQHS2k;04rcfG{7Spul`9geew-7;2y}`+?q-5*JNm1<=a@3 z@$>Q5jG9zcY@#D#%0mH$nQCHS#!Y6W@GVvdTsKz&y@uLm;8gRI%|I6V{`|3L8=TUv zqb2i)h)#%hDbiW88zZAehT*kTReJFv=hiEPBNMiJ=1ZZse;QdX#gB5K@@9Hd z_RQ%|Xv!pMx3713&V}BNhp#_}d44ngV0HhL7Vz$6O8m_#+DVDVOuD)hUOFV47E-cY zZH(>t{iU+Wkme)8%BFOB8`AGso}-teeMIqwcF??b9nY~H1IN9 zot_46X3HFv+NLgs=_=!X7=nkG0coRa_+qGGfiQ#*Z;mi*0lOu(pkW!u=)>*}spt_Tr2q{-t!QG1YHt1q+>pAO@+d$oYIv76W=LxI^waE zplf((3%Z_AhBMjF3BWON+67O$J0z6U6~x;t-I2A;Yn%{>ie${=8f!<6fDm(pivg+y z2f=M9HG%3mQ!9Hnl_hufzy-lae0p7ePtBFO(!b*55#z^l==}OKYotF`+4c=Vvdw;!MbWa;-WDJab~kc+U|B+C}#3k&B(!+ z9L{{oE~xQyx0MiD8J_L>a?Lp)g(X#7;m)DtdEGxbmgM?|w3~6q zdIFceNBT2OpHL=IvChYQdnVRU^t1YLLyx! zbtou_TZJAvVuYn)=%r`SVq{zNyPdI#>Eq&h`#8)8VC+?Prv(D$w1h^yhuy_iltwR> z`gU}kPCPA3W0di`WZX~rd6*{RUAypJVoH0Tz=u{9k{&EHoUs{YP%}pEes1Kl(Uz!I zc&t>#72h^Bs!7A5r2SG*v1n$2HW-VV*HY;n<*Sy)8yq|EXEzS+SkSHgL3d<>6`R1| ze#q*$DRDqPWe{gdLgE!+ZX6!3oiq&U$cF~9tM~CzAAXp+i$*UBA%Iz-c{B0nxW`|n z5L$?A%$~Uqz+uLb(?Wh`>P!jd*^u?An}#AO#$AdHHpM^fES9nGBCeLDXZ+<-wapS+ z01=)=YunQCCH z%&hTI60WKSuF64vn26JtxCh)a)AO=f-YI#Pg<2OKNJoatDbNX>s(4XZiKgWayq)^8 z?^FqQ2y3bNC?mN@(3ETYk5L|v^(UuE(Wt$g<-;I?a2!gEDFC)=H8iP(7jG4s=eIdf zHB&8s+$e}WI-X^*re$uS>S*W3A&ep+}UOQoQi78Pz$uGn(Y&PmG9 ztM@ZS5%OvqYY0gc*SLHx5@ZONg5g(XSeS1&+2BM8$36+jRG){lbWF;N4e_d(yo4lg z4iJ3zy@*r@FA3hLaCgz*;B;Nyey-Lc3f<9b?w^2A5d7eDhOCjro1TXG-WOv&Lqob9 zG`7D8*;8qIxdU8?$-(%XBZ(n;F!{Oz2623b{Cm&+DImJI6F?D8PQ(-L=eU8|@LJ>7 zv;MfbJ$=VQ&IzN}^>sc!UK%WX9Okb_0cr4sd?U(nlu=r&o<7AMMlg)%5`>#9T?t`X z%Vk+UFa9<=07CLZ+FP02HfbIa;ns0w{bQCh{)Ef9)=;3v!ohFNx?7lwOUo3gyVf&* zI8};@p=_qZ5Owb|2ruj*cT~5p5|qbdHH(PfVlyn}M2OK_UjVzXs&5M#BHdpWI3VB{ z=io9Zv6>#hwr?u+>%?O{#CX+S}7OQM?_f@A7s=l5Z zpoC*kg2klyct6KHpG&-*dz&8SbeIg^&^#E_@7B_+-|A{>!Jyp;B>B@J!8Sn<)lwD% zrlu)VK^&sWS9Mh7lx9pA;``u~RqW1l^(;A(y@%1kK#bg7Dj>j0-&AoQYdN?=n}9|k zUDh*EZ}9fVgiMT$TKr*_U<35+&p34CrdVr(x}uGIV55|xaRF>l1UB@Fn&leQN9*ba z$$K*!2iX{ZN1#{d0R-8m?^rIwXHCLUdWT$>!89k|jItWW-JDp{NT0?fR_DH5Emj$f_v=D4YiF+Z!c? z5*@eImoj{7nuRrz+26$KrO*%nOt#|PFa_PFO;&bckW7z#%-#xJwg4tVgo9(^aCi9c z(ozOMl#LO&aiJbbnhpl2z!@B1YO#eJqXyo9WHDzScp2vTqu@E)6vxZyeYv^b2C$pa zgdxtSQMkkIair7KANylA-ji6MYL#gpLdp%TuPYBN)-yHDUS|9PaPCG!KOa`S(~XbO z)WX5N*X@ajtQuG zD17@x8!-6Ruu42oA;{(5q2D?wZPL>m=grLn?r~9n*mV$R3IADln|pv;eM%{f3!z=h zF_Objm6GaEh7182t&#$+RD2|daYX$eTq_Pg5&Q|>o+8Z)7Qn$Dlm_+?EI&k}HT004HP+^u8)IjTc~WN<7Y9Sl`f-@&kK^IL<9s*(=q;l( zQf;`+?v1(`j3Z_Wgh+xDIGr=BB^?shD@`*J2mI-m@OrXap2lS(UcuR~3+*}E^y@S|hi97xp#)=Ap}QUs>l~TySY6M*LZ_xk z&NV&r%;(aoZP!CSYNcNs=7hvszrMnUoGy4Hd_|nx(y|!F2G0|HVQE(8Bl~gd+J=G` z2ZL`Otsz0c?&RcCG8cTYS;#)6HJddctCU50I@U#|hxvi(p^z$BXU!IO%pzum_s^Un z&EC1ClW&jWsMPCN3xd7C`Fqb^7|b0P+sh{vD}cHfV+Uytx}vMS&cs9U5r5z`RAD{x z?yRg>45P)?lfoZVjlxye;WWtxb0jzhqGzhIdQzy2E||}JeYdvH49w?3Z(Nq?bn!~` z+{2V+IK%x@CR5Y{rZ@AuWtQV(?Pq=Dr)80MNC4Wi&hNn?g{W&?euxhl@}medvwF}A zZF?Bi6rX$CywDbj)a-bIxKn6AmBSlLR1++0zi zy=Ktf8Ea4VJz|ib1HEYaty9L@uRf&}8_C>Kh%kasF}bK|6XsT%YCb=wGO}(w1&3eb1}lP!`4bG^OjqhSN09#w9n9Kj zOEHR=EL)JC^1+{OO|~6;)A)nArIWlBu!QfFqRqPG^advN9`Y7;CUGL;=Gx5;noo*+ zQ8e{U4C9JtD@aZbg5K!Ok%Pztg5c;_g;$^BLCZ%2J#)LMHhd!!54uu#W?Vwvj)$-R z9^hOP$rv??=tciMzT5Ifma0AY^~2M`&xPg)Rzx5LzvJsh!(&)%K;Iv%b8}a6n|n5d z#0~R$9<}O|pKM@jb-X$5;?eY(QsoJ%$2s4gRJtSU@r7ZIlF#9**6oVrbQ4{e8ynHH zIk&W36g@NnTImq9<@`NF%Z{;JptutmF;xfZvzeB#s?E-3G+e%+7bKcG$Y?^L=2W3K zy~?Q@8mpLkTXi_M`RYN=blr>Ad{CPYeT1$Fz0)$*i|}m1qk)?5-MT4%)9e5oz6-s5 zE=xQFp34pfhwplg%4Di|*B$$L^c8rPFFsw`-EvFLD;gNqDm=afr(B9^IeRFoTPAoL zg11l}YNbtB*I%*TySP6J?7(Z78}LLo`!(NXKCE>Mk(^bEyi$;LL>M4+?f!w<-4sp$ zH&f5@?mV2nYd}^EQlpUb$fRFPRRn#Qso0OHog}nm&yM*bF;cdNycv&vXd`KrJ3*70 z>Qhw_)F?Z+dQ^_UW}B9gjXZ#dNKzH)e|OX8ks7>Oa3A7YyFxN*oo*bDg=TlQ7Ii;n z%{vLiL2FXSf2BsmrEuo z9c0N#WIf|D`E-gl0u|IG3ejxinu}0ToVC!`89(KI`AP^iKmK@N!+h8%Guz0@z$dqf zo3f{qYsRZ}nOYg^dQR1tEVjxn^pj}u=a79s|CxK+r0g*cV&UiAA3q~-!yW{72m@rN zwGIJwkh_i1U~wxPJvLWvU6a^$Yj(!mW5p13LrEAjJdJb=r#sRoOLXVFN;ERE_$#Znj(Igrdb^GrC{o} zYQGmq$g+0!NKM3)3s94l<;3LBT-&LvVGe87Eo*>iBYMEC&F)K8+Ib98=Y;WO))wP7 zpmOmk=+<=0&ep{ZV2&Xw64ja4s6c&twxDqBy%=_blMe4FLFKa0h%pVED!yp+$gO8? zV|CG#eM(@K)h~y&7ZeJgF8#PXfk=z4R z7T~Camc=_PhU0_^9?TuIxGn-Qb$V?>!7OrA=-_lxJ-z5iFFo(m@Wo}Xp=!i;Nxog%dz{P$=wW{^djD zfuelvQ0K>P&z%!uIf(AjEZrCwV~k^nILD0RgyUga7HPI=8>GF{HEq$cj190qD$^Qn zN0T#;Ew^hhJpJ;uEIwOQ)m_^G;7E|QMUb3*?`>#264Mms2I(8(lw^oTj_wqJbM)me zM`)2Fpee<9h%{6un11@m!Ih;oh!ACmKe8+6bJBoD2%#S`xc`K@D4E)B^i!yeZU?s3 zundmy4Aw=VejunutgXT^Vl@H|bf9hQ)_I3pbGYyp-ME(9j0idu)+(Lr8Y?-4AR_|-EixTl%m7%+;JapxIdf4+Wx zjz8i#=?_i#{x8SZIV+L2|S?3G6w4H}(`l=2EIhPTW9gOOm zvcf^bpwhOvG1gOK z6(!_w6=Ei6WJPJxt^qrUmlH}C2z_t~>gyFU;M^UlwU4zD)@_SROpid_VY@)@x%Z@R zJ`acaaO)s>4tXQHQ0oub790ejqFWK#wz0dCZFfN@EB}TgYZva#5X*PP*qG}H+tVpV zqtnfY*O2cDgCbni9^=HzqXvgx>)>LD7KXG0x|rXgtEg^UazYFmXk<9dR(49d$^a#1 zxKUD5P5nND25R4fX*3Rj@iay@JI@yD0Au&uNG zcTg3xPB$sh86*)ZY1Ed(gDAvJYXu505Lr+bTVX%0;-1;G?ivp9-mRELJB3cS@XoeS zd@FZLaW%nFFB)u>9Elbz9g_hv-b&xjS2H$tx7}lSI1)BP9k+-=WXi0nBlbdz!+8eN z%DBOupyvx;P5}fo{B8pZp>=Mu;PUF&`QozSl_3V=5p;npGtW8O^r6sO7$#pW=mBIp zqgX`QZ*U;NU{-))q|+sj^f8LYlKp2ZWdzeQR3}eN+JNbmif%V|0S2M33Yh__)xW-{ zK3+|udJ47Mo?`pN*?WG8viXl#s>8rB>r58?_V5M3duOJkv%?yV5G?vGCYi`P=Pu){ z3hu6Ebk=9yI@`Z32Hg|@+kIS6hxbL!3!2P_PR2vI-5CN6DZiC*c(L#=VosMnl*vnB zdabeAgrEoStveeu2}k#@qW~x*CA#Kz{1q+)ECxu1rL41R&FqzM%*Q?K!Voh)j`P3c ze4LYEIaQb((q+!9Uj4%0)6ZG-NYg`cOBw*IEQ=@@kH!inwpTM6Gd(v6F%(+@nk z%D1=qp%#z(gx_(92WvR%Y=LmqxJ{3$IuNYvB+`$U1YYiY)_N9Qex5C2cU#li<7 z&utdngndMV1deO{W0lZ$Po;xRZQc{c)nB?&M9X**12s4!Hx zE@cY(_#1V(U1bHPnne657%>wG- z(wkGnFS}iHsWMkgO~p6`z%I`M325t0(?IkELdw(JlD)8nqsDNE>wG6aB)a%FdGj5S zGne;hDlQ<&_;S`WX+$bj0P(`h*^i+OnC{=<5F)V+&Ztdg}=80g;j_N885MHeN* z?9K^DhDHd*7j6zWc;2VG-1;aW0vyGvkHUvEl&lE&xgAls8AdAd1y=s5Y)jWn1`G$@OFvs0_}Ov6C}niN z#1~ctI3s{Hg-$n@>fDW;_F6X6dr>^={9gt+6Jx#d*_BAIOG2(vTBd7)uQwA~=}mov zhh^FNf@lG=cAS*5i(TCru(1vR*T<#863KVK@N8NN|JH`?S5MB)EqIOTznio6`lE*- z>w8Z4yDqui8QIa<(9JS$;{joS9M=b3v4UCyQK!M+4&mwrr&Cv-l+u3-?~*i4p4pdi z299AjCi@{+vM4D{#Nkjm1Lthh}io3gQW2}hPsBA)N zWglmcEvCvOLJ<{MC}P$wwX>E6tYtH*-E@wpzSWMYP}Ob|gT)@gTi_sDYfjdd`idd8 zhmw8Ff24T~-YZ2+xq3GPoEsP_d!7vo+;PGf{MKDW#xTl;GEBGabfVA7`PK`q8$71v zRO>bj90b)}B^4tv-MYH`ONsf|3JlvM$}s{x<7R920c|ZTN0pU{ab@x_<1m5qE!$L3 zrb=N_6shIg?FAKIFcsE@#}e__m<)VT^t6baB5cF3g3vJ50CGKYBX$*+GdU5TCFg!= z`+^u$Lx_AdqHl+d^-cws%+kj$GLSQw+zhBEhhzlgNk^yiJ?~J_My(a(1qLmb-CSTp zi5EpFP`Nd+`pFHsE*rx5UprPrq+4b9Vc_o5!4COraa-_NJ%-13tcBz@Gh=zX zzaX_2u4__J_;S@ zb8pj+y{z3a|s>-+VbX2hz&FH9exBbp!h^Fw2!Ytu~U3kd)9pm?i z?(*eC84gLvcDB{qFhl#*W8;|PRye(?$(zFG~Go*6ao3X;$yUWHr}YY-($vp;%`uIbkm%H`S8V6SeA2@{gWi-t;bT^(D*TG zW=mJSmpXlXqz<~9c`&?-sXf?`N;(l7=mx)~D)kZ~q>&X|nD4ppbD0UB0(xZtDG#1k z#!QH@#1|FSc$;_A0-ga=eB>3sMKRNq4Jta`RdYjCGZ9Ic8+=z!2SpSS<~Dgpx*?8X zdYb0%_e7w1N9z!Djbswmkbg`0q*?j!zyBHKmYiFOxq3AKSb^Es&P`NmI#YY_aibV!0dU1s)CF?Km(JzVH`(Hq%K!I6r*1)MI^Gh@plf~qa=V8{VjA^~>Xk$I3o}M9L@znQtuo|ir z3t-=r0L5VDFt^}_-$l46a*#i0DtZP7PVXP=hM0x5NpPV@i0KfXhR&Arpo87*%6iWm z8UWTw+W6DV-HS|oSAG;xfa5LR+A9g6=o;IPuwKah`pjLx8Q<-K!3enZM#$$vZFI9Vr zSS;@U!HN}R-=`b;9t?T$un4zUSSvwzIf`z`Jll+29&W|A?Qwuja7OU7+)nK_)DF*1 z%U}=#^b1vHo2B+BQCA6ukGo4+m8XOIgrn<KG&ejBH31f;U`s39g24l6-RlJOkcwlHq*L@#E0XvByv;w+BOp|Eu+?*CHm6UqF6 z6%|CeADUGumbjL*6Gs)7E8^c{iDg_|F@KHmqk--}0Wz!&cKCr2zL_5gls8VYVvBdBT`eW7bpHTFFX#{GHUhyL8&*4>6E*ftUIm+dV%cHedpTt7j4So_mS zCd!`~JB8M49*o>-xD~b$Jq~r;(1!o33xBj*$RIUY;vF0H&x{F&OUT=Ip*I^0Y{rel z?G4uO-!sA&PK5znn?ImEYr5on$Of6sQ?|%{(Ul@n{ywFmRZU0*1rX)nk&lsXDKtI? z7G6V!NabYTKY9pjVQqcaTL3V&s*u7rc~=M%05IbsqzMC9*}HzZM&BO>d~z0}ni$^3 ziC;mtqS(8IO8Uj6P3}~fB0H~juzL6BuVIw_EXmeDhq>Ffz<6X~WsuXC>QFK?!8QgJ zi*Cc?Igri^L#Vp+92)1(%bT~P1?VR%?TaFgD&&m_Qd;ti>Em#YdOP<0A#PGJ5e3z4Z!4~G^_R3eLhdZphEx_0JsEsQntA(_LCxVziDas9X5 zZDRu+>29MZtPBukX9D6@n89)QKc|e~!&S_m-%?Ye9p>5Sn<$u%8)Gc53qAynEnCK$ z=!ANtJ&D7)x9N24ZF=?zsmdy`IdV5gMd~}o!p#N%n_>uW49~6OD@Q-vu1bwEB1}!rz+5;j3r{j($HsHK;cofepKC68yH^akwxpLsNHk-9R4anYk)UH&J~`dy-(Oq{ zg9oNiH0oili8#lU~7M_+R z1~HJU2EK&31(d^|k${buYGYMKL8z_k;EWV31u41M(T4K>i=n*t$MYz99CshLn4A{H+ zy6|VE_F>G~F@Z=1P60DJ(<%)NmLU3$2qTcp4af#;;~_!)I*;woIOq9#;`99w!;pN^ z_vOh}B_iRmAlkuu=R1dZ8U*nux+tgQGl!vyo8_4x56IuznuGjq=WNp%AIJIQFwZk0 zWFBLnlGV4&U-2izg(j|jBhZAjxuzFH{gD@jwvM}X&*ntfof5Eg;jboNROuRCN zM;p{94Gb6b20Go?MT5MFMLVR*->xHb+g3|0^z4l>_XJ0-{a*wNv$;U}l>YZA|1+lA zpzxOrmRR4;2`HH+?%Maba`N+`8%AQ6Kk?kaoYjss5j2j!sYZ5 z!d07V2Um>29eFA5K( z%I6etit<~lVsYOD8AUNSk-i+^2xqxdmk#vO>#=Q5CaYr}Cg=sN4(`2ez<_8}RQu;k zK0B09(j%cAG29e4T=#xFQA6`}s~Etr38j@hbZ})NK=th6R{u6(~yrXcz-V>@rsEb z24r7w=>O_qdv!&ur)7dld{zZ^cN&t8jJEG!0)};HN9wo{Wk7bSWDtGn?;<(~J7Mh4A3S_8 zuo5=6fPlV;-@p=?+<)TRA)w{qPY+Y<@}_`bD28SkLp~s4`PAc?pJfyS0A(e`ZZND; z1$VaiKz=vo9S$odICHilj`P{|_HilnHm)I3p#WLx%<$bIf#dU}DiQOd)oTubnr6g_QU zC51tm-|hw(Q=WFGeS|@!iQztYkg0p)gh_Uz5vhdiGw!E=O&muW906My^4v6a*igc1 z@49Ma8zT@Zl_yCr$r`3ThbOubuHQMbG#^LSAp3S(SB9-Q{z5ui|DphoS#n8V@gz@A^7@);sban;gWz2K*M7*tG&G9$N4a4diO&Ya zCtX^tbVt7uU?}m&s=K}{tn80v2)P9XlsbUO?P#*AiA~1yYth<&`FD-i5&?ogR2{cH zMm>#~>R@HO1qvWv4zB??mu%DH;o~@7wrO<;^VQdO4&|%Y8Q~zq@ETnc793VY-FJ?G z+^Z#;XlfZXZZ=UP5RN~2RB0ltO?oID*gUq29Afb z>+S2mGceA2>?LN;?zZLTZR6l%sKye)lgmWK+91UQ0=j+E(osOB`&=9l9%`18rT)9Y z-^`!0eZV2XS6b**oNY^Iv+C8NZcY}C7ZN6=*O9D>V>mggYmUwy8{y(U%ciaK!)y54 z@HAipW$EZ_ej7g^Mi2&WNe`f_+!)eO8G>I9P2LW~M9in5XEzKn1V<+NaVTh*&S&0C z<%wuG)dBzM@;oC&r)px&^*IkeIct~>z+nc-OQE-|S~}-6(uLO*ZNvh5$PR8jIh==G z>w6HAQ~jJy$V)Dydo3;KwMW3diI-_B3Vld|*hCsoncB zv3z1j8O-UaSWPigrDkc*wGdyXwIpjMt0^YCo4`R8@D3fG)TU#0?ISKG=M2+W`J;>* zH{%jQCqD-KK1WY>_D4(S7J+CtRpk)<#ENQEJv4;W-CY%nrH;aPRzcg=#Y-G45VDVa zvRW<2j%Zs0{Md$EsG}E}?=KjG&Hemt11ysMb=z%zx9O5@c5%I(8!Tvh4)m={eW^*u zA$U~5U1-mAc&t7#A(mcr^t%=OO?-^tW@JNg1TsYl2eWl;_fARHm!HcvP+*BXu{%>% z3$mire6YlzvUOxc`;sJ0Jvv@B7bGPCbyuBm7()aSTH(@^+Uc0;7TG6D#6?>YJ3+O%)g^NLLKWk#@^AJsgg+G}{!-k#hr_)9G|;0-;~_D2&Wjov_`L z`ySAo^l`Wa-6JO{bnz%O=@)Ls@=NJ3dPoy2?q78^H%@GG?TH6m`KpAjWu?0FUG66` z7KCBBHC5^uk8-yAiZ1Ecu{P$$C4{e`E-&6RC`ReKX2yQgW1A%ULIx8vRZ)=b7>V`k zrx~xEx4(f}zm8G+%whPJDW#~U#Zw_2hVlT1V#=P+5gX(74R;(0Y@RhqnnmJ$k@JiK z6!*EOFK~9f&C@yCl$UJN!Gxb&rYlg0Kq4BD5@DRf)r>gwWK>yPi#3mrGzAI0q_?R% z$oirV0-dFw^|@O@wAyQ|DPDssaml?4kZd$xO9R02bBgtTU>cZfe`(YXzTQu?6R(eL zF9rlNwn?G>j(tEy;8fLKVvN|R7s)N<32fr2i-lr4)OAWGWq)^@K0$`Y^>Qc%<4@Ey zV?f%L!wAy8ICPV_5^b$pD8^T+Y< zF-(Jnvj_0RP#++@uc=wdPr7%p9?sUJw`sQJ!rG6K3@xLQD_Tl4{7}bf5{A|pX!zi6 zLyJOpX-FCo2iaCmXR>C=RXEi(C1?Ln9h65WHd5!9FJIl7IWx2+3_=S#NfC9m^DxtD`-e!?2KXII<8FfM@sa~)+DHtEVZKj_mhK+zM1Zpgy-W>w!7 zK$biNekHNV7uluq(K0Kk^;@qR)Yi%uDpxx9 zzO2eEU45DQ<_&DIt6&<#rV2<0rMpbr4~YD13F{247T;j#Ek1teK$A~AofKXZhWiKE zZMAifmri8LBK)Z~)(n9@*-g9u`GhgDWP+Mq*bZ^@pq;^*JcD`M#@y391On+GNHkpx zcH-H59GItTp|{gA2|RBKg7fir*Fti^ilT%W4d$X*s%5K+@TOlL<H7mEk+%33>1H;y6m!EHi-sa<+a=k&tzn2aP3p;d4W&J%zorNLlxS|^z zxc!lR{S7?hn9{vHg@`_!@?Ie}mqzW3r2<>HkDKwW%5949pm}Pqf*m!4Ce6}n1-Zi! zl)pMR#teZMa#E8~`w7RQhV)>+of8=wqtxH&#^P4e!p!CHM6QEyyX{f=$S72mPU%Qs z!a8TM^jN;0J>_;}UGBuohyMb>JMRb zxM8huWgudvAk6zA*8q!e%?6Mf+5R)fA^gDn# zB_X#IdU}+Otd*&aIRH@25UJmyO-GT~^EEuU+bB>zDX^Z++C?>M*_~*eT`h%qIR-gJ zXVmyQZ+w^uK7@mHy`F4hjB(qv6FM+-Su->`E(IeYEmm2wHBBBN`#P*1XZ&}ZKSbH4 zzopyArBi5|n*Q`=Jt4_NJN-&!bU5=is}gAIvkbT%{Bis75*4n7vAFOTu7@B-G3&9G zA25(;I=Vrzk%OdXSMrTC}6+m`I0eO_jKUDtQ3ge;z7AV%{K6GPBziLg>9dOKhpw5d9} zr2ShV4(LY=)*RjhXD&~to9e_>-}aWl4B1j7hV*xZZUM9Z0)}+Lh5FsNX}=qL zAniIlHb-v7sE7INdi(DZf){OZ0@*tJ)YawkBJH4+Cz70m2J{N6kb*X{iUu%e%YPjYXKi&3XS)WqT`IPn zt78E%yfN-mRl^Go^I*-!*LnWlP?m;b z+w9tSE}}>gW97_~Lbjs@^EPuCuRxkT$8IxUWB@6a=z%C~Z^m}@f;lGt4g$Ggj&q>f z)zQYA{^$2sh~dTojX>HK2q$`c`|*7n8obU1Pp$BHCPj0)^X9;7;oZ-EcHJuyus}v? z9}Jm|05}U1?&woZ74%z*Q}~-Kd3*O!JFR{=fna z^Fz;BV?WR?F=)jcu^7vgOF|f;F$9cx>26Vb4yG#)|fuysxm2V3hROhlHImSs(Tl!$p*Mj!ik2^y~yu+n^Qgv`G$v>PWrz zdZn|WE5$d1^*~VIR4l>Xq$`0N)4xG5?7h)U@91F*)cX`&=InE-#_o36Pv+apb&aSs}u<3H~ zy>YX2LLcs5#r;o}1BA2Sr(rwR_74sYVoV)rNJjzY}&69{i;*;LD3VuQ> z)T2MQ8hj%|9QAw5kG9Z2i^;M^@>2}w zA5*8S88>UHwfAjEzZ=43(-*jrtFF=E`D+6?``)H&cZDHXtLBJ{ z)&Ot?EsXBJl0Iiw78K%3X#_r)8;^4*{d1^}_!~gYUhje~bwAC4UG!ZkuvY0o)smsb zx$>F#@FY!YmqGWv=;7Eysc2S&r2}U8Vvhf8*!mUv)*Gv^4&%GCcY0xm96nJanU;W2 z%g+xV?12z{oF_Ge?z*(GFbYHCd%u03MGp`hr(*|F@eaJya$2eol`qRkNA*ZM%i~2Rhk4BXJCi7VfATjahi{wXrOY>(4x6 z{yN26vrS0}@!-!n+atk?ev6GdL7gLu>7(+LJdg8<&&o#>Sqf^4rn%Pmulq`)PZ_S( z6r$SfBWjFm1R_?`=21JHsGz-Tn_gE=<99N)8fs%}K8?2ko?3L(-=#WrIUqhdxYtpk zDLyj}({L-4`uMwS&FU;tP9wn0w>)`Zqp6ESg5VX_M0bn`ZLE48*Oj`WBj%YN7o&3{ zD+Mtqo(}SooPJk@9(ZMlaD!fLXJ4M9%6h<>_ifsrrYgO^1sG?Myn&%}%0{TktDWN# zXUd!Fjct1)T7wYLSscDo>@YqnqAawp0eX)cqYT6^-YK@np=$xiLP(eDq7_iUP*?^l zbQo+Kec?ddtOh{AZ%^wPUAejBUw8FQ!c=pYZHoM5-RgF6Q-p?lI^8Nm+@7YVxR9nl z9qHLjwuv#bnX73dpb4j4D0=rOyT(V{?SZg)y1;Qh4zf=4Vis!g?&bZa4D#+ktpV?ETPC5bmuiwENd}- zEJlQq#l~t_01=#DkBzD@{03!#UAK0Z*aeR=WIl$SOVn-m(#{QyT$-X-JNpG{yl)#z zKYfey4mS=(o2EEj5X!Ncnmquzl{@Gw#cNglIF?k_UvGsSvq}}1*#J!M=ja0Oo%0R1 zOt`%7ZIjy%+f_Lo$%E0-NWE`-gT7SMO#j93PaP7J8Hbd7ap`S(33*#Q^12y`;z^OA zgNWHDdDM=+MWIU{;#Zf8DAs9x7fQOI9?#;)j})e83_Uv20Z*v#DO9*)?=7cCj~Uf+ zhG>MRmQbNu>wIpMxA|4*s`a1CZ;(2RB_TEy|oVC25HME9uM9)O64+GJK1RKWIMRV(ex` zSbe;hnCo~GVZ3?_*jz5|H~h*b-z4?4bB;I$#0Q;#<9d8f#H!(obWW#BXj@%Ykz$qS z-lm7MPFSvK-DcO@2EE>Dzu5F!!^9I2XvvBqr(4qX@bJQ8wW)?BFlt>q4@qZL-7th@ zpqT1+d7l*^i!2(bn9bv!*)@?jq!9{kc#31ajj(-wC3<`U2$9C<}cR8m9_<2xX5??z@XRia!H(|O>P^Rall&XmhR+CP8+VMGj(^bs14a)8EJ zTX7$xZi47ujrd)CF2kVb4Pi+r#!&*?-kaa#L7J`|V6)>n?T)6%388K**SxKZ6y0q!WUY0vC(sTEMUb2ZP<5HbLbNE{L>6sG7-+dwo@CL4^8yzz zVz$Q{Io-HJ6bhnuNEqAmGW~@@ouRq`ra<5M{9Eg6RWHmC&Stx#O|2Ai%YE^A&0S{-Z0DcO-$5=xJOub7w>;hGCvJGmWqY}2$iRXTR!TSgvo_dtg&ih_jEhE1r^2Q0%#s% z!!$$ZpSlC?yx7s|1&ZUqM=g-H8Lr#XniMUb%ePH`#Ubh{+Ut&ifFos1i?Gle-00P@ zl~^P5SsK1$XX?HVb?1G%9L?Fr`{rp}As$Vmv3x=SgSViju@$7~lWS<31g9yd*;&0Abj z{5l0)U3^cGij7&QTDMUj1?NC4iqQG;Zz#B3(9#30GEy0;7TQI@NVT>ZrjD~gtadhS znX(TJYvxGR_I%vbR&O#Ww0Mxm3&$ls--hd=7xc;?;b-}~NOBhg+g{HgY(I z$40jVw=-@a!DnafKvxBD^%~4LeEs?Q3tF^c*+ZF)sLLF*nxfS|s%arV?^+}@@1*5T$& z7S`9=#V?Q@qAF?cfAchT049cCOS~(5f4Kgtj$Uz;?TJvIe2ilvBR>_ zn`8bCt((GEv`IMLYPk(M9Gzt-uFf;A{N3QvD*$2zCvzi| z2=_gQwq2P`R5)tj%YiT49?tGJ@Eoi+ImW#C)V3Bx&Ebut?2ZR*MW&<$&eJJ}gf3JO zzN>sx?Xoq7$roIk`V}KdRuebi$jETxpxTlV5kfO?u`gS;DY{`L4x6Z*lYvD6MrdH_ zJSy8n&Dqr7>hGLaKS{4T;x7P-PPa)Av*vn|9NBSj3yft&1F?p79OLoMR#(5exXj1( z4Cr;~HepAb1T9d7p;SfcH*AhyJ>riY_{C#3Vd*!`{`!~O9u$#=jqrs|k}_?uzW%u^ zd~HXXWEyUUi7?~bVu4u5Wt@=aYrJ~a1r&i}1o2knSBVxl5Cf8DK)f8i>)2)VO){xZ{5fO$ zHPt7yvJG}#Cxf?~m@lJ!&tFuW+v#L{!1Mwm)QYE}W$}13>0S7Jjvclh@7appx9vDX zS{=3Y2W0jsJJRPlyP_78G7MlfhIxhNr$TQdK-k>x7NQ(1O|C`04)@TEn%1TD5{~D& z2RHz&l90U9$=juH6~2Z?;gq(+&FR;LiV} z+jk>F=Qe3R-!SxC(3h;kN3;cl3A;{se!IE5&5hQMMmaGyvr!l8MMWs|mL8aRX2Y>Y zca>v%I~gcroa6LfPsaPUVZU3y-TMb=xeL_Imfpq7R&t+4>VUPTTGT8u-#r$VX|eVx zaV~LpYgspG{cfzg07Ex|`3J@Ej>+>MH+8x_tfh8V>dMF~BqEJ|=Gg^O3m6rPu&uCO zO;b_YJ~#!hi76bI!yJSyX+B^2r|TU_sJ7J5&2vm9RWRtd2S*R|rX2UL zNuMySflZuxTPr`!ZOIQ+lMY&C%v^W?c{B{L4_5?2vpWs9cyg}Vl<6kD+w(gdLq)Zn z)>%FLe0$$+d*{sqSG%#jRrV>R9iyjm)3Z$~j)z*}gX?@kX9A={$F0TU^za@{)Tq$QHT1_*)}aP(L58zo+k zmu#9PJxixW`_%IdtV&MFXC9j8*yrolq_yua3 zYEr#8hmP4Dx2Qi1#)x>rVO$mHCBm)aQt(v>t_OZV&K&y70`cAgd3gxe(*yTBs^xp% z5P=d(+M&EZAgre-Nv@qrH-Gz+!ayf1W84L)`#>=F z05|wBo3W7BtYjEUSoTJ{EGKw2&PBkjh}Grx2kqHS9jG2kO+4MfJCey&a-Dv?^1qeb zZ5dPxJHiFap9{YJT>d@FM`bmZF@wHJ9@ZdR6$6GLPn)iuGK!SR_>+pG+w5Ma+OG97|%?x{<0`C0iZ>eb)Qv$|_O?hZ=#}XP`4Q(p~ zkfRx$#hIFKFp9g4DnNh!%TVsNG~WjN)`E_);5_uH|S8$nZS>U6w}) z`8rI6571BLE+G(y%uRzUm+PY)DT2;0!gIlL`2*exz45x~jW}5FyJe5SkSHI4vcp#v zjzM!oARmFiM+-rtst_(2$Kp8M-^^P$V4`yJ_#nPsI1F;8th^l{MJ5*G15o6w5*He) zqgKu8QLP8NiOGi6K{?6}%XMi1mWovisU9Eix1KcUE!U;(^s{P|4$Cl7 zBR(QM28FCs6E-Ag)eA9t+gjNbZ?AvJurXcr234eh;*xiL0N=x7RoR<-_C4*?V}h<` zZZO-wS6)dWbQQz9cWd*vw<+(vP1#n%Ty2uBfE5iDN2#bZMcy#oN)a|p!O=(RdL5xj z5)bn@V57ud!rTZ6$%1Wjz`Bpr`GU!heE0_A;Wt=Uu7n;`AyPFWs+cMq?zj3J<#O9} zw*@YZXtS8MCKdW9@@qalZZ%aO?y+8?`Gr&2uc3fBYGU@K6Z!5UJ3ue%li@8Q?-B#K zcH@AR2>NUU@GE_|e!gtR%Wr?i?{dT1oI&=a@6m40(_*LIw=A@sAP(s_jdV=Y&K6Y9 z9t*hMU2i#OVwga^@caiG@lwIUIyM*rpMNCPzq`gLkeesv10}_HCd-}TJ&U!GNR4^1 z8yO_Eo;$14yG)=IWLAit56tLm2coglO{j!3LL;E3QH{_F?RkvoVjFIuKWTm@Yegev zAuJ{1xZ76V1SzF+$|Rj}6_cV3$9-Y>g3_Rl7nmh3|Oc2@v5Pu2<4;_Ne3%U7T)Zu9+n*)D4>lG7shW zjNB+Anrz4+-pr|gQe9ph?r;G}|2XGZeH{AOc0pK=TFCw0WVe<~(rb3QQD=2n78_JIPAO zsgrQ(NlUL_I`~lp9xVAB^pYH@rQhmE+tW@w+dcX8=5Dy)&%OC60RN@Kb40Y#ROPwy z-i04Tg>oMFj83=jU6M;hDRsiPdTtqj$F~eW z-nYxc@dot0emBh~ItFhY5@Zuek(j@$t1}80Zw(gk?RrCnYob1I2f|BXfwYyB@2NCV zx8C`wuO?$er`lDe&+u$xOp|*ROCf{mhpxL4go?f^?D-490j(92{Pv)*;k*DG5oV+| zB3pjgmD3GE2Mq^e0P)~Xy_P|aZoRBQ zq4^Rv4tNIceu~y{gDtxd7f*Qj-=0o4KKra$Z%(mtIS#}@WsILR(oW~^C=DWMmfsB) zc7`JxLnvHogUJqJkeO{T6hUU0Q03ozf*ym7xSH4R zr2v_uy4%Kcd_($%b804K07A!fLf!$70;oB@$h&wF_#C%ec!a-QaCzpP8VMt6ln0#- z3QQdLYd?1nZ7cBdaH3fc|M+vk*PqLuOC|X8;@9ZG8X62HQZ=w(BYBHIxFu1O&wo@= zQ(46w%Bvjku!^ZZ8-lyXu$M-hMLJ4K&#qjRDF5Nkp$)^b+A28f3Mf#+FH>Jc!Bd%tmol+`}61S zc~%8CH{C6E_93NDn{0lSrCF~VXWg>O!axh4i$rtJYxirh_?Z%tW&6hR?QdF>8@<3? zPuG;7I4ynuYqFwH=yc1~RMOz5R~0JNiP?%3hoRz5R#&YF0i<(21{mdT(bxXgM04To z&HDiRnPg>TXeJjhoq;ILuI%gYHW@=d((2ogC(AWi6tR?8kGIE-dSWeH+g?7W;lule zF19def3==V*X!~(ze9o|rm0VTs%_EKhR@~hcl(pwVnMF(5REOF`inZ|Xm3rve$-Ba zT60nsDu~zrikvsu5c<@pP9RDL%}ZyB<2;kRYYH=>KM|EdexA{GGzWs>$Feb2c~GMn zQzW(^XgS@)g4GxSH1nz5tXM926ydYUt!Q+28x6Y^@Ic+t7JLyZxaq%I;L<7DfVx$mEC7w`+(j@@s~L_K7gNr!~MW_%&I zOXsA02*G!sk$aB?zPCQrB9EWn?c-1b$pv$#gd^&d;G-8R`JN42>~7m2d)b1Ie0eg) z9LoRZsD}exHAh}K0WBv<)^aI%$KBv=Tgn;_ms3fvLhy%vD|EUkD%<2rD`BOEMp~Ax zMdW#YDxlf{(HCP*{!oR^th(oepPdBT#9O6@Ukz+tj^T%cQX#a>18Q)@xY^BPSxnu{?{*7$lW@KH`nEDi z@tMZcta7zsO|7N(3#9`?YZ4lQv%-(GHw7nZ8V z3xXx&sSl3#=gRAUt8_?kiX*322f~VmL@=AvKS2Mv;OlY+_#)8xGLOi#;rMzsFm5W; zu*7TP-Ktgz6jnvT8@=T0IytAZiiJ7rW(+j5H$peUEXx2nZscj!hM}}_9u*nH0RR!7 zFlTF$P8864Ss&zY$A~3pLEWx`@L&Y7 z(t=@$iBW<-Fr~Ed2C-L{2sYbZa}*qxve3FRdrMPw#1D33kXoeGQ=2h!rl_Uo;4<4z zU1PSq$wNa%#C~XCIM4{7-tvJ=7Yp1t5g05(J}7lYR6W6ZmEvfL-R7B^O&qV@x5v=C z;b3$lO5u}ABONM_EXVJ5N+b2=cDp>Q8Qyy=Y{CqAhXIx8u|+myXU*lO<(Nv4^2+HZ zcX!AjShlKpaRbHOM}QaJpzS25dlG02ymZTHwqR7$L&hTS<_Xn>5KGyaC82W@i? zfDl#;u@W%*-DK!FxY#FknIA1~litGYIgbP5Q=*SHI~H!83S)9O6b*2bktNjsSeCwD zL3c28`uQAl$u0{UKfaq)={KsRMN>9s_DyE5Hz&)rNsvn_c<&#Z1~L0ImjPu|1GmQ1 zye^5lS-!$_O{=6LL-+?!U5<-y%;I9Mhf3L2f_ zOz7rrqrrR{=E5Rfm|269fq~GCx#bTU^uq>}02R7fyI*HUt)Jfw_Dc0P`E*$|UMO+E zc?F;-bVxW{>E3~B5QX1kUg}$h_GS6=NTYrM2zsh~4Na=vL(vwso`|U?N)jz1p`&hU z`=7M{3iXOVR4ZZe0jW?{KZAm*F<+)+WR_pLOpv$(clkyNEOk>J%!tmnsgmON>A!+I z;_6O?fi0#`PE5Y+Hk|omhPE(OkiYXQ2jo$vIYYzF*VWmH(Xl!eM4hn)Ij$E=@z=?@ zv@~9`qWbf26quFw?Ovzow{kxlh{lTQKKsv=yk4moSec7%ObbExnE#KLucRRKG zXu^!2`&$%+VK-P*81m?=^_{Yc&(8M6{b5;MK2MpvtQ!y5rUy355<`G4mKsPPkcb_a z-#B!Lz!$E|^5JybtNK>pbc1cA+hSFbF6`$s&H&i6BV&t_Po0Nwdb5OD^Dy0^olX(t zQEhg}@0`O3B+?`fegzG^fi_hXUV~|=9`CThO9poiZAtMYAvS#bOA-j(D&k76K=<=@ zCdaGytzpyWab$e|Y^o!ptzQhHL+VE@ zolWp@=5M`05Ux2!i7q?!7Ibz}Y!oTBbRMx`g32m<&i zihldiRnz-2dGJF^T7Nzq5ufdwvM{|AR=^r z+MTcd$!$lv0r-MHe{PKyfNVL3kz}%MsVwXto=>{maH^j~)t`)XN>wg*q%x~NQiOM4 zi=*yHGdnM^?LEj5hIae$VYqw4Zds#4a_o4jfq-?xvV?T9_8jUzFWmf))$}$#LeiQz z5ufR9lOzs=h7{5bTuBzU|B}%`!LXyjWVYyDbRj?I#@(I$0l4t4pAGXobyjJqWvuXd zM2;W-ZM?8*d%8Q}R?1zk#r+YsS(duK@ClJPlhIV3L88QypWdd|d$Zn`?BtZ3;|1c- zBO2>MG?yQ*rkD16uM5XjYh9~w-cXSPE%mNg&d#E>W6}8O0PI7`je;Jrs3v!@LUc4* zo~Xp@DN~U<5Z-@>&R1dcnNI*M^6Pe2ERrSe*_g!xSQ zUaUTA06pi2wq$tLAhGgbZJ)R3L ztK`$_!EU~`zt`xknOH~Tu`FA5w`#t4I{sYn=LT8GFIifcahS~1&}W_HJ1{H5NB%w1X?kwcq9Ldb>PaZ(2cbY=K_Zc_=qS zfz49zLgSRMcCJ71uKbw%34)3w)%Zah24uM#+yVd)2lw($H#9B_j@USrDX3seht}NF zET6{a$ntl*RQYTl51E%6u&!9fXW6~-s@q|8Wo~6>N|7G|e5K~X5ftASOHouU$J?D% z*TD(2{GD&_8}6}hyJ5iO-@n(tSN6;6V9J+)Cen#4-CS?C1`8-^JX{K~>d*?Iz)RaMW^%z&aI)D0RJ}gOcM)cBV?UiXap)Qn~*NvP7$a%OD?HXtgCPWgd9FTlL z85MvMsCSR`jc0y3Ru7E(pinUIQe~a1-U~Gy5;`SFvz|frG!SBW6!0k-*HXJ>o8F;% zD<-%LOGpzkQS%2%c&Q78#!lrZmKH!O*vs2V|2dhmJp--tu-rQM#IoEzc=Q?OAc_rD zwjeTXiNabic%yK2q(MP99wu(I8hE*1q1X+CkVeWm;lM!T1((iJYQhx!l-?iaqhTPz zcP9B_SLlT}N;zPc4K81Z#@fXn3_^M432);3Er;8|Q(D78zRuktoXl9avn9}0rgDb6 z^z4&Ri^GN45)5e~@oCmG{2BX(ySt5?SCV4#L#1mXIwarv*2ww?&p&@Imq&3zD{MAbU$RPTH8WXEIT7A3QTE=hOB|J5WiL&b6q>!D7 zPAp|kBoDo*rc*AN!N7$upk_$z8GKhaN2NzGsP+A+|GvzxNIakJG~CC;1d%>mv1JQ?(e#@G-m*laBwUb-6WI zxLgW*EXZt%&?0?>`xf{AtC$D57eJFzO(0F=|#tub3!~6o5 zL6)C&E$iWTWAIOgP*3N*lb=s96sYb|SH4rgi zS{nrU)cxvQTk^Y?i22TU?tQj2;o_06$3zg#4-fhLKXYxm&sz-37yIv`rNyR_5MwJn z=Np&gE!&iOz+BUqFB6a@J&XEwK7wefGnQFUP(W3@ekX!LIzWxQ4XNl#xQD#`xkbGh#JnYXGB%UGv{EceYodMx zhLT~$hKCzz`GPBeA&#ZHlN72nDt)^>RVCG?qn*aoq=q)Cr7JV+r4F+J2IbL#ZB1-6 zFY{SV)6$Y05SXea8Q7I=i_;7`jb_0K&_H{X_^v0ggM`u0N&dYYMvKr52D_h!NC8-AR7nNPs%c1O)-DfBPp2 zfk*Q7=kAEQ2W;vjyyeccoWm#}-Al`pw{v4r>1uL8_=Csi4A+sx^J#my$}-@`cE4IO&40lC$s$Rt--=A>h1Cmkscl; z3j88XIv`EV@}U|r~q^3zRDMvaCL2J+8DJ*5MSVl0>>r>l?u z$7gW_D8DeOLq>{6?z{&sY@P@&CgNl8Nk&r{ z&Vb6OeS_V$EUc2gzgPbEmDdI*RFQxBrS6+)>>JBx9xon2PU*5BSP70RKDdZSP0ZDI zp*QK?ruKNjf`e{RRiX60kvQuH6bMWDvN@nQarpgnRG3yv8|#DN{L4md6rZF!4wl8N zKM6|%&sP7e9ehdaxKtqw&{MhSM?n1Uw__Ho5?R&wNvdoi81<0fJ{o&=w=H|V829}a zHMD2_IBZJ{8D33E(S#NY&AME1;vfFM|Ns96K?QW!{TYLm97@;UEm#w#>{AEz-B>F{ zAE9l3ufmP#ThcGcRskR17J}E8JR@wXBUSRH?(Xk4I%YuVD|oF52=TeWucF(1;7fMV z!y?wPp=Ux)E%lP`msNFy=&t)iGbrF#B;Oo5!UlclYK7;dYAV&@qCN=lV8kk~A~Xb7 z4W+Ku1_mC@=P+V?l0J7sQ320GxXw5fsU4<5tIQV|kIOmlu&;~Ra+Ss8%yZV$Y{s+w zJ<1Nv+Ph#>j29)VRIjB$K_ge)z6y||P}!&U^Gs9GkNHb|bV%4drux|~PsM;A0v!79 zwp_D=+cK2tmjQ2OArxCDbnIK6bqDX4`|RX?A9ajGBgt(x5w1#|A7b9{suM4vcZEhP zT1IS|`zHzW+-aN9{du~|SGDyB5_5vo?4_jWv)nnF*$8pAv9ZI4pQ&MP z>NKxblU#_&q`eJ+r|Mr(!4M{2iPwcXt@V6CmTy>i2{KisKQO#YCJo0rOmt*yiGfKG zl5hQFusUx3TMj&DvNvdTeYgFUT(8_I+SYpOD)TKRl*Nb4ORRlLjL+7Mb_ulJ*8*DU&EN=J306oDs4xakSPX7A-#o6n&0{pd ziL!!&o&}y6k}qgv$>#fn6Ry~g5a^NUvW)h~WuVhdR`dqltnY@J=*Zhq>+j#X9tp1A zh2PR|OQ^P|QN^V?lWNfFEmfGi6?(hes|^}$V+9t^s~-xQlcT{UWMfe$jSE1g*MQ#% zqhPf`;!U~T_n6gWq7(`WH*l)Ku^H_4m4fQhuc)3{#IkG{*D7fd4A_$$`U9`2=AIi0 z%Q}~}yW2ULs#V(>=iI-{!RVcnE3ztbGBSDC)#h0K4CQg-Cui8e-*LC6Prn<=4yDWA zd)2ni!C&Kj#nk7(%@w=0wS-ttsj^$qA z^hf=dYi`r1&oX1y}E{OZ`T^jXe{#ak<+ zZ1uLPAkdVs%#QLQ)mmB z-5EXGvGB+?Z7%eNwiobL2Nf})+P%OX&iu)REBL;LAq7m7O`swXFRS5zxN{a+tAzyF zE9<+#5-MeNoK41{)3oOwrR$3A%9fLa)ebZ?{~M&rPAe_NiTJt1r|;Pt58W8^L7`J!7IJFCkZ2A zoAA?Ub|vHLADC6Z>SSrqv_`y_R0#!bKpe;|-=zht2% z0r_3%4KQFhm;ES5ae>=n7~8c#-CRgZ2@+9!hD{-hfkz1+$D1tF!=KwTlocejQ>~Me zL{^BEjXF-A>q-uMuqB)<8(sZwv>VFJY~o_s`|0~nbtvhX{SU?RMPo{Z@?v0g(8N6v zP@mAbwR@6xx1<{xGL}U{fWly?uWjck zf>F`{RY0o0rG5Li$RiH0VE~mj>r{}oq}hXJ!b?_FK#Pjd*W0L&lF~_0qYNXeepf>O zx(r`tO^2x@R2VawBAUqQOiw$X0uMV*i;4jZDaxvuV)zK2Onz}?il=GH+)||#d=JhF zQ#GL8{(@n2qKr#` z%}1>!-d?ZV%_6l>@G;xIVpVhgxAdFad>a=FryV;G$Ky=n7t3gBDry{RV8C0^2LyW4(dF9FRCBB*aJ{DR5||e^jm0QfbOq*(UsM(3k~|N7x-2 zx}ek+18-YH&GpA!^1%|-tvuu#N{xBRjx4F7{v15;v*5=nM?J~7SICo+gr*=H(exn{ zj@y#D;N%}sx#aJ<;K@?Z;f|TNZ;;_B^I6CB&a0q{q*!c~RSH?Ld3~2M0;Pp$3KPy| zi6sB~?_rMw#YR>EwRMsTKgp*+cGuG&hR)k;;XNO%8FaTP-mAPVf4(!CrPkT#9NWgq z)8)(>rqGOo+}JOyfW~~3LulPxY8t__l>tt5-g9%by3{0^X*0s#Han0oQJ#>_%%Z84 z=D)G2rJWvrRXs_&HOr{1O7s5DW^QTG+E}+4BG1L!ce@ z^Q6qyf$vVbw~)7Ves@l8Oor!<`(41&f5$p!9+%v7gDPeoZ+(@`juDoU2B7(|pxT{IQvq!fv^#$|M#P=GA72BX!RXcEpirM5$K;fEyCQT(h?T53V-_uZsBPg>=aQI{RneYw;LSz zJDv>|{yd`IN~ZRF9T`i=s*|1wovQ7$L2J%YX9{BSjL$E^h^|zl&fx?lrb~r>a*U&9 zR5}}Gtr5*)K&h$i^%_q)Ea<~9+iyiODCA<_SlS0AfJC#cVQ7kSm62{8x!W4T{PfWe zVZ$_#yq5Ot+OYMYA4X??fII#Qf=^pgCJ{r@Ez~-TIFvo~SNoix%4YqWGIMFStix8M zLwSwiY73xXROpOV7!vum=VC}&AgZkgU-_$W#;vYcp1lv4-D6>$mReOYiCGoyk0bnG zEpv&9ZVe};`-J2fzg5%9!@!y@nqe)4vdkBUk~Te%&(+Q&RoGUr?Nd0mzPmYya-`3$ zkykjb#5q2aK_QfWCAlittvCoHrLoVta*e_?h5YWVfFv6D2YNjn+X$4lpC0BV8-2(-8O=c1D@0Sr}?T&I0!6Eg;fnu?{;sU|SS0d)eU$6W--ED>^&S$+h zJbWPtwguyWt8&(zj}E7~Q0LHQQ$NU`C2~+_ez_UO!_1&S!IbBO zf5u-hp}bM0PBVM$(vXW7uNnzz6uWL^Bgi5(kxVvG2j-TU4kM{Bv=4xz&Ap2Ej=H z(di_IBcj=q9vuzZd$&mc0Q2XveD^km(7E;%pv^?*ky6s6cyX*VYwD686&_+?l?arK zPdTZfA`_whx zL}$&ns6tu}E+Oy;ErR)fC+KdYhT^0*DVg^>gq3Rkl(K;r5Rpkqc5njx(xa4C*#PS+ zyWX~|JcsbDWLvCkmj3!Fy&+NdHUrDA!-PS#N6jv`A-+&U2^?_9vT|p28tFTf?X{cz z4{g64ZkKy^Qz7BZ^*kL%lE6n199Mg)+tIfE_NZ)LQJC9vfV`a#>LMJ@PO#cKs2vvu z-6=Sj%+RJMj_|aHvcsTUL5{!7!^_y|BgT9C#8n=m#{GZbyYnBfKftp6x9&ASXdv`a z&2b8CQ<_#rdcYn`cnE?|VqC+)76W+x8eXfr*QoR!u zh3_O_HMn!uMfUJN{T3ee@;{SLUAF9va>$G%5+RI8*iHELRQC@f|G@I+f=fCyoLemA zCKn+m-aE#%!naxRcL)xuMW6xT+kd0xOtnrrl*OPTyrp5owqIrypV!<^1nM1U^$Lk{ z{GpcykZ2p!d9&R9SgXJ*^hF%5>=0`9N(hp{$fyveq=Kk-kTmUguw!D6Zqs;v_Rwpq z_~a$vMsbFXo#w z4Jv;wmz3*GqdH-|m2H4bE~9p5Zc)HA7f2R~Y0LZoJw*W{*i&{~WPVC>Yi#{Cx4wnA z=OEKbi1z1ZamBndJ(yFGoDoxZe$hxKq*rRY_ezRmb#A9wR-bO1;Ji{;oiF2Ve8*uO zjJZQyCd!824}Uu(&|Gf)P|l3zgCQqgTO~5-ee(l0?Fgo#@hKA6jz$y|2YdiYj6DDM z5%P9(y?uJ#xGye{;}m5#XIB4$admAR~>Ma3f|BrjIYw?a+fXkeQA zECp)Q9JUlfLQ8Ddc0`7!L#4=0iUUnqo~g2B?g)OFAgdkMP5p1F+mr|;vt*vH7ix5Y zqturbS)ysWdIYNGtQcWT@_1b6dD!^`;tb_IJ6-u{hDak7VKez|OGKuM@2A--vh8+n z6-vjrX#28aAyl|G<|Bhi-3|#!$U%Ku-XBg^1JkE&BpM}e<+xyU@0&<#l2$$eE)w0y z?W_jgTVq`AEfyByo_5ZnsB&lu=O$-2$JOujXmR9W1NhK3Dnlv(?sT+hldGQZ{CM09 z*J|%{7&j|SolE$pV3caqyEy1RF9^*iQ#K6R??Mje{S|mPiMwrib{K&mAOZ*p`$moS z&V$!(&Oxl>5d`b9%qJW>(LOu%4hegd;og5^5V|94=0Ua;!CW5vv|a$l70A}5@lbZc z)k#NgU(58Tuv{*;Y}3o5D@PmbptfA>Mb&X{LQqvHTH+4MdRWQ2;ZAZPQ2pdw%o z-0OD>tH=$2AJs}R9Gb&sS*7!_f+z33ws;mm{f1AL4I!Y-0z0FihMVq|h)!u;Fl1&n z{-?VQ63Zf)Sx-fkQj)o)<}776Pu}XgbSiCq ze53}7R)SDqtm&@BB?7L#LW}i$3ZGju(x!?02Qd`p~A za_i^BM3UrqwQqjz&^OoH9S0kYSkTVwlCL56 zi}^La2tP5e+CBJa4%SY2F&ziHcS|Y8>=xCE(GlV;KY|!0aGDhpl_3_z9$hF9ta1Eq z)fydnT%$q5MYff3j2kuAlXV*A8kd#Va-z~u&R*9JdQs|`e(h;nRG)d*A)!!dynqcUr$U@Z z?w7H|!{*UMA2s>RZ1xMj(V(ewm~n+AiV|9_AUW|zkpKR=P*8=;4)(vwQCG#W#o1ob zc>&_GD&Qim(JEDTn#2HY?==CJA-ZIIw(s+F>U;QSHgc0(vbmS$80w%=bG-y4L_40K z3B<-enHFx>U5Cu>qQEMt4IXaUhKIgO178t5kcMk3N|RgW$1-_U<9c=lINz%zF% zEarujm+iiyah&DpH&6nLA?#r#Q-W}Zx#ktA>We1oWqd<00EBfK0gXE4gU*h7>f4na z4J2frEc7G=4Q4?9M1dPbvE-XRVJ z*l{vCg8}txV}7up!+aW_D{~HswZW<|5NH?-r&2c+O?UGdKkP+*YJ%U8gNIfE>XVb@ zy$M7k{GaYNkK*85@3{AQ@Vn4k>%N!~nQ|Def&CBkyf?uuS|tOdr88sel~50yd@nnR z28GwBP3BrJKD^1 z6eu}8DzDu~o%Qh1AZA-=3*~c)a9dFe)^RgJ8JEuYg?(w#va?h=sObRVMo#yXuI#y~ z#|Ufp`=kYmVErAa%*L;R%&+&2%YJ)Vfw}u@7Fa>-*xlw&oL|@5Kh@M?Y;!x=xR#a? zZ@}RCx}IbeYaWE4xP9!aRpXa3p0d3r;BIO$yjUHn7OU`FAUCI_CifUR^hpgA{44>T zwQr4r=5e@-17@g%M0o_Q4Hec>Dl|RzwRU4$39tV`=av#wWf85Y+B);S|HPw@QEWPt zIb{`Bm!T@1zckNl!RoM;+e47+Um)kf&$sX^JvtirxS)t7M& zE>w;DA}G*Y29MZYc7U##72LNZDic|Ca}RNzcC1;(y%>0Twc~*bHcYS3-NvdxSVbU) zON)alZ(P3TZFz1B9v|VIp9VfV(||$5zJo>V6o-`)NDRrHOZ__aim7;`DxTN*_FT!o zshVw&UsqAt5mW($ZIEJ2AW`ghOy(41F~SJRM8X9i?@fDcXi6y5eQ?|9FbnaojMAFdRrpVd8-EEYe4U1rk zD&@BRktv8;m&^)X(8+epY^f?-6SP?*L}qFe8UN$|{O2;CU6{D%mL@~n%x6-8RsvivY+X4Rccxt3CKi>{ImZX_jX8=Rp2uTX+a8 zX8&&fl}|ir!>AOVM;xMFY9WvTQy#|uN1oDplvshuQPZk z8nvCTru^xUfUamR`&pi8y@Q*_c(Wk!8iKHO?}Fkr^%JyK@WZULx`JsZ(n{y0$3pSP zlT~97gw?6De4hO&U%W84KVl_ZfXqdr9gzYIKB~F^Q_+6%Mq^xj9Aqlz+LHKlT;l@B ze7%ZBJklD*Mc9=c2Ii+3^fKv@jx%_rxKU*Lk5z_enMPLVlDIZ6&E^5aDVUP9_)3ux zsX#wgn_UZ)IB@M-AimV_jh+w-tAub1BbEa@^4`z%AiaD1w->OJQsAT-Ls*^1^X06O`#h z5fSN94<1mS&|Vot)6bL-zuT`vfCH@6y2g-UFGkxBS%-JGg|UxT=-}S%WYxs^`VX5q zu#fD~p-9dUGwfZ45H*{xYpytt|FIP*M=Y6k% zKV?VUs9S$^zwj9H1t*yl^jIJ_y%gkAnX?>8+$-e4L+KR#}idsTH^iTpoYg ziC3*CNkPH;j0&Q-VhMw6%-EJ3z8;r>XVnF3L02u>tv0AD1P8Q_wRN&5*Bfg~l=b6I zh&+WvWa{t#vJ7>C(oA!b7g$Gaa78aD3n-Ey&w(6Tfz(g?TTJx3Fk=K>=RIGi!5M=V z@c7&dC>yr{Wx1^VDdgE_zszLv4UdZlr4x4$Q8r|CUQWT@mpc03d*9QI?xSx`x6byv z?@7N+`||ytv@0#a_FMRFYx_L7v7rPCC0i?^2Cx-0Kv#C!3yDcnryJG!5<+1Hs$I&7!F)93$=_&|pEh%V*6x)ki&wF_a9Ge6q80zEJU%*~dxYiQXZBp5fF8aN-P1$A~fmO#)yQ{>H67 zN$8#LKLy62&SW@X11YwCN4;?`>TOkzA6W!0h>_dWFE9o{Msyr^?oMTUY;Q7*iqJx2 zG$UVp3p>8Rc#gJPmTR;UH>`-nc^&~UHj*ja+`wKKI4z%pDWg+e5=M8lZE%Y%bqv@6 zyV)mi3dBq_3!#ym`PLn!ggNmo!~;Vrex5_r9NbvXn{%Oboh$?IrTBZDQ$3(+yK-4~ zNMOX-SA;yP78nf@y<|}*eY6Ly41DixkKgdOBeRU&%Px5V3rsTiX#QOuRo;J8dHZv@ zT(+H#-1cU~tIAY44BP&GU${i7#KDC`M`d6lDc70Pvri6tU7S_viGc{#9fX)k!IJ84 zIHg!Jl9}sgZ#1XRGbjb+PM2IW)7)}J%(y%<4q!^>*QAW^3NmjL%YoS#g}v{vJH zja6<|ZG*8aW-Sb@uc%PaDBg|bJ-@w?)q}xRyXgJ*@LIc`G32LMO>MZ_ns5RyUZ+c? zF^ik{gYOF6IkliE5zyaD=?bKLP^f|eA}Fa{R2-E9&E62~BH`ANDsZN5{V+O(^vpno zgMDJCeNvRF6}7ZbxlS|T)7gqZTzzB(lo%%>v$>07-v2DpNO;w#)NuVr~g6Qz)jy1`&ZAodx9 z3h2~NWqtHzC-ResZiCtU?036EzuP0E?XAbc_Mwz{S(!1Hn*GbdOoelu$6_sk1m z1)U#8X!jvVeX+31f~(uLXg~7mWR7x@u#S?2uYE1^*>|WYQ~`K&NH`T~$1s%8;g@Ci zRP4uVy#DtlPo<1w;$=2iiS(hb8=Lyngio@`q^b_-oZ@MR!N@3~%(~E9OCBU$L=Wmx zD$8yKBrc`(&AK}(ANOobH@6n)Ut1T0u6Uu8Gl2d%Z!5|!yXUU=F_f-C04rgwEDJHf zwbC^FMY0|OR>o?WML=QSDHekCZBbwvJ5iR|+p@RYb<mC9`z(=0&(mp5KmhrTA)Y-BpV6iN|a!TAdmOhDy`c zqlzGEzj+eooswWN%WZ#jj^7PYHuaekS$Tki+?fv%us?tY;zS;pVHkm6)@a9cw2H@6 zig+FA+<1daC^7vT+ivX@F5gFHlvfHX{=w5CZZcww5D2haqwrOZD;n`O9I^&#bgm16i_OUQT zASCu?BhU7S-|ccaA}3uznH?dRAAidBiE}SDkZ&wyE9PmXCgPt$ZKDWLPS4t^ObkYp z9c;X0r+2{q=~z*x0~8%?jTq+&y(jjvab&^32utcTql=c}4ROFYz=r*cS<%v}nKnBo zq-p$F{i)QCd#~CyGNDig#OgBiR1~OAj!^oLhu?gYi zafW+`vvL|=Y>~Ar-s7?8rwyG7Z=r3csM*GSnw{LP>eBlUGm85XhZR@EKh@r_QpGLQ zs;J$j$c?t%tmUqkA`Y;WApdxDSso#8sP6k2QWp8$a#zmEOjk3d9GxhkFxZT1#a+d` zMqTI)Cd%XvqLn%g6l$F|jYvQ3wIcJ1%xJcI#o5rRTDIvN#nSV-L~m%rEZbVxux%}TpP*^!XF;#r;s@E3O4ah7c(Zg}4c z-$zgw7y_Nz@%Q(8s`qSDL?`so!3?r{42ef-9ov$V!?d9C=dM}X6&bh7fOlnOX@QTD zGz`TZ8}zWN^-&oznsdd6LybeZ*qo$%QZ1Morf40y;x=8HwR`Bzr24WUI-iHYX2O$* z@XRVL0U-{Q`weZ^*^Km^W;>hXe%!U)J^|O)2Rv{#Mbt~a2s63^FWUzX3DeRlAz+>k zn+iUu=f>fi=C}T{x5s&l2bwBG7UnGm`VAkkD9RGI+aFF?_ z2QDhtMisrvbd@%Og5WZ*z$y-3;qloAzXuY1f>R4wxlT8yeVEA_R;`hw^^T@cy)~`7n8f;3@Q4kS%KSu^`3QQxKF^>^?N`0>G%Ls{ifgu^>hse-^ z+UiP$l5JZvm$FzszQa$gIPIc$Vo~FO8b`gTFg>WdO+?MtN#p&}=kmyHj9Ixr`dkEi zyd!r?>$}jmhVHa@kz&#ZG;AiN@B@$2~tXs`_8A${iA@=n`Ii8*v;Z4rljr(SfEPL5nzA zt)wZw6emhjNbf`du<%R1N$cuv{a z3`_BMFj(#AHYGwoMq}aC$*gVvOT+*9AOAV2P;u{gs~5iQ}jjy4q3 zy=FJX!g(Eda&Gm5Ld3oyND^Sqs3>6bBZ3Wi*T;_^Qz0L!GAWq`)NGt&+x?AnlLw7d zwzo^=>=1D{BB$X)O-d84ib3)ds;Lqscn+g>%k8bymqW*|F%iwzVe zuPj=%Usw&4Xe$36*p}0n0PDxWQikFd9t85tdm9gCTpl5BVo`6h?l!gwn6@~xG#bp% z2B*Z@j6=j3PnzmyOIgCu_!H3QFipYbLH_7F4NRGzJrggWQtoxiOYnSR3ENLJALW@F zs(c&Dr;J=V-7Tf&ZmTo=_-9m2;w$WGkojgS>hZ%Pg+BR~<3s%+6+UD7Jq)Sa4 z#CsQ(ne(8>aekB7kH`|CH+6Z6uU$_y!`~|U`MthoQxB4+8_g$kw9!MD-B3d%8vymCU?9UjluiQj!ssqKNHQ0VH6>g zdfEjHs@XewscWn5!eD-!@}t6Ws-V+M{X^rESUKP%0wd_PDwh6ww@zD0n0swuw7Uy+ z`EIa)!$2y_W(2H`Runcw4{{3XIOwo9(s(9@vS($~P;?SU&TlW(eg|Lqtz z0^zzmU_uo_Ar7516B-i}kulK($r*aydC`0udl@=jVN~W`rm~2Iqw3_ZYEDp-ivdo( zxAk57?2xc`fy&;h8Lt=uNY4a;*E^2cCg&Q^-~me20*by3EH?GE@tf~q*$b|XjYnBj z>Xo9|q*fmMeSCt)gsMls!W+Twz zmZLmQ1Y60Hn_u3aiI-e3bw@MEXPud{;q#@a=3wN{*-IZUa7O4`A^(lq+ZjE%udo4HAR(a3?v`l{rsKEb0omUZfGaK?sH4 zp?@AeZJGZuG#rs=JR)BWsvmnOX*=P$F=F*cDSXxd{6KBTp+9OwJrW?+*s!KC-1)Vh0dr2- z&DD^FHH(R!`iOIDiKYE+b-~s?7vwtyild^Kw>N4C28&u*1kh@1jE>!tAHwJp0N1})D#CKsr~@KgTySRE4f;@o)$s_p6m|? zImcMqkOJ$m?A$fJ`_bGA-P zH$JOX)c8|O+o#asO$D-Z)72sDAA<&2&U4Vl|NM{tJhw&lbE+Y)jZq1aTpiOoo=>r| z#E#NL?5T^&MXUV0<`zy-l;dYx4}#+=YiK{vrcDqoENSH=qQz-dtP)mdPQ-&RXyjdY zuZ}0!Ed}mu`%v1Qnq6~)**JPy6Q${bWK4sdv4Kln7^ux+9I()uJ1WDlkza-Ud{H!- z%Z5TJwZnpUkPyGfPMpizbQxL-mBzcpwOy~|FvUKm2{sTwg|U9@7T5+6eSs>Xn@kR@ zALW`DxoGg)A2VrSbPMkBrC{t*(0$n{+?V|3txlu(sG6Yo$U1^!Lhz@`; z4nty}fGj_M!|P$daY}uOc?IuiCVo!Or4B}l@`|J3q*I97J0wi)b*86#cjmr;!)&Q2 zAA`|_4auvaa8pmkaFu!A&_F07om*lAvuK1GS-JV$F1I!dNQ^IAwjOra*|pB(>Ssgh z1<0VR;G#~#M5Z#e#HvIqpi-@5QvSwx7;@_uS4%5tr4B)y-gtmEZR}dJH+Pa|k z{r&{o?4f`w=9V_L8*Az;U3}q#3<5y1ggPQ{|BV7rm=>Z*0S{(L(Z%2VZugM4?chk| z*4?tGQBC2p%PFVJ>_jDZ2%`-iIO2zi)EzC^4+`iAoWV0wsQ!62mUCj$OetgtIi2$c160w*M| z!{^QGi*K-2huhHUSXeRHri9R{vd>3O+n?AG&rOodA}}&$g-Vk@vE1c)yZ3>3N3#ZCI~e^M9nQzwG)<34&6$Z;C$p$STtnG; z@?_IrH;yR=3-(IitTzC=32gRHcwv~R3xF0bh2-;fs(tC-VdhE(*wxOWRZTlgR_*LL zB`9%5?seQ^@Sp^~x4W%~xX@$jridbUqeAL{`r&}%{qK_zjqB`Im|$Xe!B46jPG--8 z$Z8q+t^dtVf2wxY1;0lu6ui@`&Wle!APnaJMZUwf_ocMj>|Si}9iC@@}F&?}a5i)^O#{ zWehPsMn9M9l~=Or?fKNpALZEEo??Z`dtWGUzo}uL$_GuTc2<(vBAT0__{fxe{3)6P zmN>Z<8F6M*o!LKPT(EAi09m#lfq@(SHR5PI}DVVUJeM{=N8QVU_g~EW=cVZ z%`icQpvf5N4AQH?=Bs)8P(wJJAY-df1{gN4bV+p*GD1_6W_VOxmP#4ahtsX+GvekQ#S}} zN;5)30_S7KZfi#y_e6o+Qu4(J+FS=h%@bDmTAu^%cFoGmmQ~pguZ4hjp>3Wd^}Z{%q{Frk8G1rCz2ipY-E8m2Z3Odf-t!d5O+Cj z5uFsp4);yF4(eyf{Z5bAwXGBJZ`xIr3VcyKSkvo&K8lemJ;&aiZr{C4+0092oeaaV zbW93q;6ZoEP}w2Iuv>NuNp@*bF`-kD32u?`dN7?jpPurP*oXabgEACCA)BdzXT133Yi zmv%t_Mpp(^YCoWO)}r-5S^kP__6b5(oK3phtQrtgsenm?oS>Acc#SY@6^)FA`f<|yYN zLXnR0`hWrDcEF(oW=XO7_-C)rPRawr-P<7oW~jIPN=`G7#|?6vBX90D<_YiN0dv}V zBb~LFuGFKwa1?DP{MHN=TM*==P{_@~hAFZmcbmBl|4AGud{?P3#Y-;fH`rsTET)^W z)_zwcJpQ}T+m7oEW<+ZIUKHtmCb}TPL}Yd~MK;1*Fx%vqUg);l1!!L{zG2{b4{fJj zeej{PzN#f48k1_vHn999^uuV;&^A~uf)OPB^*zy3p!7`XOl|7UGD*SF>bTu~ZP!(Y z1c-2PI^BZUNm8S;Ds;UmPMO)kcB~2iLS^xWPjqUIu?ruFqZV&9jsO655=T_|18qXQ-YhD%bMdV! z-?SgbGDb3Agof0W9NxDoG3i!k8k4Pk&x2V(hSZ)z^TLZlm}LuOK#A`6Lp%?9XS^_n zs;K-f^u}aohf<`V0u(T-;8vYAlBhNK9fO=%IuPD+FGCQq0wzej0=>D%O&pR-^F<4U z-1beratHBv$Y)6PAVru%pHdJF!P-n^4Eoi7&wg`*4XvpNac-9-6sKku`zT;hBleMu zCee7py!&7O@n2o+Cc6bTm)!;l)4gCeHEwGjiUy}S$ctg{2s(48Y1=NL$kE!FROd*X zd?DUp0g957eud5BB0c{sOi)I3p@2+W-t%FrJ+m;A&x6(*T0jKfFGI=#2B|1?a1pcZ zk3~=@G86FMX4y>>Dw=m-VMrfch}q2h|G3tTJd@8>*QlnHK0`|m9ns2~z7Z_i94nT^ zz0Xu;NI;A2(zPgXagY+3eHlL|i))^{tt++V5;xeQQF0CIn0*fdN?C z>TWB48`XX6xE9AiazP2F6Ur$g&`l!!9b|sMs`$iXPLE8<%PL>8gCmNEyKPzRSZMzs zB!Pz*G8<_+qntpU;=1UN0F*JI*O?){RM^ah1y$I1GLgifN`npaX3vz6_qGJGbvL@Utu$L ze~wT+(SyF?oZj0JS$Pbt*YJV4omRUW=!6-{A!Za54SxsiPe%&UU+D9HuBc5OnI0zn zJ(0bhSx8=7uk{lrFAh9s! zyh)8^Q(h!^5HwB$`Z8&dQ~Q2JbKlso=xBx3?5Ec{Zo{RK7MxE586F2mXB)p8bk}88 zdZl}75emCIWzv}!=e+{&0Tuj?xAhV+69h& zRKx)%Aw14t+qQ-dlyg!{6TTyq7OVvAr@KtFU}Pk0g_uDPchACH3Z+oWEF4+?R{m*q zJ2vFj)0E&NPy2IfxkY)QsPN1@_xk#}AfAu45eitkWr2wl(mzb9YFMYrKgGZ;gb%c* zE@x@mox?%{(L|->9JpbNoYc@%nVu)65E`+quxVPSVp}pwalhfzv9UbV59D7Qm8(+) zWh6tE{hfK-J|f!~sPqb-y2b?Jzb*NsF{#MThC7L`n~bBMi3iS@=f zk@q^EPWD6CsnorKibg>kMR~WTg`oc3ptcI%eSCu=l{&YD?2%c&u9=>i#7@_Id=ti*9w zlwWanZ=H0jJwsXj=ourQ?zZ`Kf>sOF$zA;GMz^u?`1%TMeD^lJ12%2uu1oJ&2NVG>+ZLgjU6zhI6dLXrNCih?fCCyO-`}JW z%@hU(fgfClKKio`Xedj(*tWp!2$o;A;u2H`#a^vjkmr^QM3NsG1%^E~RP>dl%2u zeJVy*$*q0X*$eUdR0zR{e1QG*yVG3ApaU~*0h>5(WRg&cm`B~5RkF4N zh*@j>N0$9$zkRM&j9cUV!7YuHRpDYTI;mqtIB$7!s9|5Pj+Qq1f!FQUlm$=(#=R2H{kbR$1Z}kntImTeiV*Fhrr8Y#}SvNH-+^GdKX;fkR>C^wsJi9>s_ZCb?DSe zW;U}a<$&X(^aK}!g+sdk_y&3=dkQtwTSJyD9{OGO8a4s;F^>-;E14bq^>!plq8 zvber7j!`nS|L~=5T~Uy8@5?~`5JfLWDg{1oOoj4OxgB+=i zxx|AE^5%K7`qTHQ(0jHivY!E4mYBL8dEatewH8q7-Ig#q73o z3zAX=df=IgOoa*K~>F5>e~$Xe`nBFOlYi`c|+PVG5z_rR%k&YFmFY z(6~o8^VeKW6f+s;|&-i`Fk52|Ex zcuB~C+_k-l{d_W{d;san$=mNay3Lq_+;24=NR3jhlAKclf z)`rN)wS+?C(i)PZ&W(QAC6BwwvbY9yNq}N$i@E7?jJe zT4JpyR08}mC`sc!EYh4^)o%MNneWV`><{o=9U>d~i?l>SJir;!)B zGFdOI^DQQ;7*7Qr5RU&506L=0{JK%_s5*LqyDef#Ja`c|N(t%2Jh;-C)~%$E0gyW> zZ7n48{q3(j^GrX>yfqApvcJbxIlqo{d>eQ)jF9L?m7b2A#dMv)6U?x}Rsy#RMUJ|L z2RPQxCo-~?TMl%iOgE*MddmvrYx_7ZX-y9WKjB7gGRR9pah={}eT!Q%L6Z!nPwd)IEo=acA;|NS31vt-QW3ye_vOK9qlf$OvhrN^v!vrw| zSjw84vhXr$>y-?DD|}0r!&KPzmfB6Q8HIk(NCEz{|-d;Wg@JnN6Aj{rQ>d9Js`(AKZ>6CXnpzf%`dY}yS}D|9p45r8!-5I zgSj?B3~19uy9+6hR9PByO%y-Op%YwxwRgk#crQVQwHy);X&3mSq{rUrDK zPxv-*vI5-EyqZiK%uQ6VRC?W-MueKk&b|K3R^w>#Zru8ZwfF7&e|OG1NNL$+%cHk^ z+~sT*$L2)pR52TGlF4K>dQC3~#fsf+V*0FTp26jJZJ0+ti5_QdUJ@2+#Q?{H{pfWX z#kk$Okxsj78in%-2M=HDz~$BF2gJ|1apVv&!InSM&p5KOo;(cAfTuZ^7wT3~WwnzH zhC*!G_gf*VtT@&egA&^HnCm%~yHxtS%=yvmC z2(oH>fNs%YYQ(sGTGdg!ZGzDNr?+sd*kEBvalk5X+3;{Yd!dzU4c|~nCq*O~TdYC_ z+i0-mRD(RfJ67DsS7_>@tMv6BA*?hJkX22B-&F{p#4Ai^WYV<4}?D3(*Xxs5nYLoTn4#kwh$b+maDK7oA3uGXajE{Cfj}pzaV4RVl3E`tj67-ECdOf``R0%KkQ0 zT?4WTjwAHO^ES7PtkX?h^bRHkC+SCA2G3_6 zH=bjTK%KVA1qlmFS86b0O=3s! zQdS6zet%H3$TJ*xHHs=!AML!|JN@d)<(k!$O`F z)pmQU>!WR)=G7EUhy3qmgcPU=4KiMVoD8n(6c!pQZNrgTHoANrxBbwzan#JzeUo$D z)6Fi_1|jV4@p_XQSe}ZoWU87ObFwM7>%5iq#LOoj3#pVJitR$RxKy#GliPOc-17^X{L0Jv}vzte0qw71DofQqy}U0 zq*^@m)`7QwL~9(Z9d%(h71<~au^6{>hlEPM3f~G02a5)n+siaa=2LA}+mqidyp4{D z)>6|`UIW(u#G5DC3#++BbZMs%}nTym*bnOCKE0yr$E=3bTG?fFa}GFXG-&32 z8XN-{oO)+=x4j(UJeJ^ycI?lmc7o3eDZx$u{5mK18)&W~w`)*L1E_4>P<$|O|!E{i}8s!X6 z@!3gX8n?Bx=W9LOI9s4!O}f@e-+L(t93{W1#U;c6crnTJUVucCX6QaSPHi+x`4;%S6{ zK{kLcaDvb{Js=@t)48@fwsN0!G_A-DYRLHKE)BQ{atUELQr47&`~jUu`G1T6g99rn zI`~nsEO9%#gp(8p+3Qr&!Du_d#AqW+z(C0EV>7vK^;Y?O)#wB+*Q!JtvupGD^kA`v zjM(SK1#eKhzId64#32J7tGU|>=>i$isYujB|6Gn@7gU`JJca+&z%bq6S_j zk(f2}{|Q=s&alFa39YGS-K8yftc>~^0)3TEDI>Iv5zDJggbiVuqP4bC*`zu^?Cr2A zpFfDwJEC}$YH_zg9=U@Z5luBL3MHbqO~oSyoPpY#K;jeMRuHI<1NaM@K*T6wDTm@~ zB)7JSCpHuraAz#E*%I7yC}4PwGiw#R=m_Gt_7t+Yz;J<1)mVYuh=Y-nOwz)?Qc+7?~24=60cdd_!*3}D89%(AFY&w3F zlhsOerMF(#0!5Wzx)tH884=d9p4;r4b{G$GBD0uC79xbw8P0o0%aY@@AATHWgSq*d zAvdFHg`oCf=?n+Lfx=RFZg@xCb=@eCCyw$!WY_XV;bZ0{+rhyrMaxpprz?Tnct06S zz=;9-qtm@*@UW{ZdjXYLNB%oFfs9LY3~#z)N&I`an+v3vkOCqU`OYeuLXLgHe8x== zAYkGK`{+y9=F~ly}GlM;uJD`c<|~6=#u9y_r_ml_S43e!F1q$|eLB z(GMf%p-7sXZ+}j8v}XVyG8D9-q9rPflS|G=uUF2Hq9G--{S#ZJH5_I#pKoamDynRG zvtSc}03HXifDnV%)G!RTzt|D<6}t{uCU11mVkMgEty9(Fw5vm@^!~Iu)}&}7aPsTQ#J~ym?k1AS%g{O|o{w(jlHZHQ) z=JOd(`%z3dW#2g`0wem}(D-t8{1EB^kp%JpU3Ods{HSJ+G|4%rw371j5KXcviyXBY zoe$TWvrq*quET)y;*07~FWDMP@6bhr1!qWO3UQt^H0l296q{7mlrKoKYjoiDmt$gv z0Yv1NN^@G4zPB$M)t!q=oNl)|Zkq}9_d`1+>6mN~!-LWtvueY9=3}x!gp{+~!Yx=9 zOhpmZ*u?QhhQ^KgW5YthekALnhERW6?^6LA-tEA0r1FdqOV#!_reY%_Z62`TR#2v7 zzwnBNDwsA6gA9;7+we`tTAzGfi*3J>M+IQdQyHUcN0wiK)saRKCpmlaW%*2~=c6HM zRsoCxhdNXB43A>{;lcg+OhR@iU=UkdAuGT>|vgQbmDPQxfFO{yjgP9?(|aPRrq!NCH)+V1 zO`-OQGK&X0U%=b~`#(AvFz~ZV+E3jEzXEE5`qbfxC9ictku)%s*^kk_-uG8-Mz$em zB;-#D;knO|rPylAFbg^2hTnEX-tE>?#;7RDBFjNE1Wql7QT6NAR0TCJ^RU2@s_>a3 z-@o{+|IQGl4JC}4Y{bA5dNh_m#N>y&mf3RE-?jV`4t5fSfO^&I4jN;u@Us~WUnBYs zSqo!vs9_RvCdY{Dn2)iHBaE(@{$3543la8MvMi3vysz~cp|L+grQZ!(br}$beoI}= z;JHu15YW@X(&I*ukD@&}d~>&9%3+Zxz(R694>C}IXD)DG&hUKR@QY8V_YCLnefc1{ ztdw*+RU_nMbWk>}pY96W>vn;PaSP_c*AO@ip=A<<98qgJApDAjA)EXgONb134gFTd zaKEVGdK=@eZGtwgbG1Fy71`T)iO4^K4{kmUVIbBg49eR)t@Fk%lQZB!0a|sUAcE-G37LL`r+TCp#nRN76sRE8gKgq2mEC%i1`S80T*<8a#V>Gw$4CSk#PLg?WUEVrF zL|DO6n#*$1UG?)cDJ`@p`wMhUSk>6QTES#-1&^^5TAdDKfiTTSMA61i-b0C4M2?>L zoY`dp)L`44D!_*plll>fLy@<>-EAFpuj^=+=0+TP+vj1kio=CG|1*ui65C$M{+x7k zO{Q(9z*K!SWh*vmr|oe#HA**Ejej~F<^0UnpAK!MvH5`e)?Ah6flHd0RHg=N1sc15 zrn;zF*F46_;u>QuC-{QiwAA#t=_>0P#br+m%8ba#jzZ>##8GJWk>(mxHu0Giq(W*;N1{48CF{OP{=npGHZ#{L4ITC-$Gk_2g%23lv*WNyVYR!=~PQ` z@yWpi%h(Zwh7#Ea5x&$>aa#8fWJx2~k+F~^??0!WTQQFU z6k!SamfX>WW6!lAJZh}QA$wJjlgBYy6k|-TK18rOmLtOqsY|n?8aNRfk1EnUMG;6G zqI^{vi6iogKS9NW5{aG&AcJl4sehfF=wvVpiTK-o7=Z^-s4li5Zrf5?Q{_SNBkQ*V$bH zizUZg!2p6rVzGC+dzt_gcZZ42Jz3FZ+08Rn!NaR_C_K{Gb{2#l1`!I>uo_n`-NP7o zhzIT5dMiAvtaqXZ1HrB z{HW?Df_+0p)f6laMZLZ#B&-QB{0R<{y)4F%gv)RzgD%zojHInM1{s=md7VU#o@ zBDQ-cx?nAfY(Qj=Qk5DWOXprmy!{z7Z?!kP1Yd&yL71zc0d8h8+5&$wh(3} zb6GNQyDamW+e3GQD!Te0J97*TCv`*>CzHDRUlE$F?DlT3p2_VF!uNu{P=Z-%h65@P z02JuuoBVSyDW*3-f${y`J%>IH(zIfXU%pY1C&R0zrl)^Ax-gH*Gm! zcjL=8#+h_dn1xKkShBg#R{?K>D`NiEOqmvwyCI31BjQnyy`ty23lxpW^&0+ zk9#jq>&{cd$0xl_o@ru~3C>U&s)Gm{+1e{zs+`^E3HW)H8tWLC&Ra8u z`*9d585u>Bo~B^FO~NXWv=xOPLmP3oVA?>IRNCEEF$Rb7D?KjS8e6=cfmk1Q8r#sA z%xHKNm4YQLX@Rt=S6K8Zgj#{X>)7Bwia~LI>l?Bx1eLe=9(bYiyG(HRooQN~Zu@K1 zm=r1gw{3)Rd#8XLGkB)!vGYD>s!Z8t%wZ`yQFbA3XI0khB(IbTE51ZrsKdxip8IpZ z3}HxGp187b#zhaZ$l^+915Y2n@p{55P=zmCNBQ^AzN~1wAS&emqFq)5WWh?9$(G@G zqZQGgPT!H}l~nrsf8&+B6| z352Wk$IFAz)K0YTdLmkFL$4@sisX>u#~>+Sk`BnG=8geEVnrjb*~_`kDA^jeyqs2S z2UMLLpU)|P-pN{nh5GZ{pou!YK4gsw%evUhKo4}D6$#Dx4r{JJ{e}2Mk7z(U*NUg} z;0!MZp~ur;A3xoCTo)7Pl;jxbsAz5mV-^*`1p?a8xVLWU4;TeIW)!6ja~O}R!n~gC zGUwB?A>?V-LYs;-;t%T-4n?RK4m9P{WBV9m$ePu%E=@y@tfT6?GqEFx)B}s2T#>NW zJcvy_Y34lN%Wm!TOp(j?yHO8%-62OSCJ2f2W~fN=JgNxmRv0dcQWCzJ3tC)nFs428 zRBP4rI0D>Z#++zZ#{!Lsj~s2&*qP6K0&LPS`f8;rKp#}ppB_ddL9O{>wX84UjDXMy zSw^2lHw$J>q37qX(8O^eoFH4tqHS2A#vvZH!{cbkoSaEZ1roj{`Oz$rRs6eBWQ{z8 z$gb&PdFXz{KZaiR`b+yN{y5#(tz+Q~9+49MLtim4p7=mbyl2o`55Pac znrDW)p6=jjMPN`=2`x+fbT8E@eht`*ygc}EjRM~|oA#<`t5=V+A7m z)9P>DRF|l=Nv)q|2d#8wwOGHhf|2O5Ic>!m!elA&@u+6F;Qj<7<8GK6AF0_C8HT|b zjNDJaP=unFN?GgQxuNx`2hZqYf)Xt_CKPEjFFKJPQ^G0;+1GUon?}4WV?AxchVva` zyILoXIWD>bdeo~s1LAl<7fkHusR0N_n=2O&FfwoG5!lPpwmw=E$;VjDi>gAN1{ z;uxDXb0^N?z_>M^0r!qGpDW_4ukGV!>o@{OdeIwx0@INJDo$kG)jl>?w= z#Ps-(Lfq7O(X0i2l;7r?b#wqrU@(fztv3W2>O!k=xI$!`gIJ-PWj&vu6IqnEJubOR z?o<@LVz%6fin!yPdP8>s;w)!29}ThpG12)BvHTsgO;OAMt}euzKYboi_>xp#gSY20 zMYvEtp#W~P>pJSMs#m>&@>eqGn|*(V0Z3T&8B~tA+oZ@Y$W>Sl4Ha3B;RG8##wzV}k9d1Ty`ei84F+oXkx{o+V_Srp78a&T3nr2-}m{bsgp&6%IC-n2Q1X8e&2w@zY^9fj)*N zIy4hSy-P=V$H~Ml)QB+uUPe&JS3C=0>PjCn&3vo*Sjr*Z>|?}2 z045OQ;q3nNpFr`tQX2dStaS5}_psXy;q`hib*(Y?STvmBjSjz@=3pZ-nR8QR3xprF#!>ac>1;MX3;r8MYmifFP7`nQ zk;BfHkg)Y?-*;*hP96P$m`@?aI5eyaR_Na^8HIhCInNYLG~EIlD#-LO7KV*2+X zg{bJ3FK=h`{d16v1xJLV;L?3s`d!LdMc_xFyA5slfF@Bx@Qc<7P?Q+7^DV{Gkz2#H zyE?uzj^)eDW*rYY-5?YLVK>ZCvgdq4fdR+j&6`Xy{h8&1JT>wbY`5%eAINurPqaj9#G1|+=jj&n$P53glCofhvLHb)4@J*d7?9h3fyt< z-|0`sAI}5E!S(?!ndfwCItew9?lcb;not6v9W#G}g9{O1&tUfmBxqH3qRy z?AqrHRcbxoV-hdE5$U17&#KQ5gq4tdgW_Y3r8Zz$o>C;k_j|@k7vYEfg}6*@ESm;N zAQx$%_~zGn4MYrk2JL|F{q$M49eIl5g9`Z}k2s$QV;3!qECTV2gUsjBJ)O8&Pg~Yo z5KX1B<+Rh;_QF`gIG-8;8kBzX0?moY8~`315%&2vN<5WV38oNwt>C5hm^I_XOFA)# zAY}$b!IW?{pPVLAzY45nz9H|e%*nxMIKjyv+(?BpyW6JnwtOywtb*P4-4Ri)YQA0)am|R!>Qe>IC-HoZW!}!*zxhlPAllTCmeu(z z{S#+l@Jjcj<9a-0+47#lO4E2=MaSLx2U|KY1)z;B6YWEVI&jUu> z|8H~YO%xcNg62J-+DfW5WaFnBY062oYrVJuV1?ND#V3{#MO!d@Yrp0%D^v{7r5aqB zB&nsw-3F(!{xzVE_|E6$*3#spDo>kX`L`VK*Xk0XCsi`$aw7hZHnI;*QO@kG)T(A1Vv{(Bia_lwmr5$U z;Rs%l`N##1rTk;*(7ukcdC`nzGyrd-~A*A=HHbh<$uA)lmWq_~Xy z-KU6J>(I%0V4lwzE%R(OGX=zjIAbRw#qzlIVN}E@7Ze)5+w@Y5xJFUk+-Rsoc zZL|)_5GymT!4TFR{gFLo)K0#IaHJgqysGy-Jcc;@6;YA@21^ojU=X4^_0TjK?q-8T zP_Fp?ABB*&C`nl8>a!<5GNQl{U3$xc5-q~Qgo(^6u}|OP^d`(F@KG@qF($EHh~q*Q z6f>%}pyEze=@h_eEX_@3KDju@tRgeCrqay6BqyC^wvCB&U1IE(s6?n$7P+upN*lI05M9?$(i_+4tG84M)s=ar)Gze9b3EcqrDo^c05CR4gseS36hOVXOP7lRc4k zkFiwG1k9l{I3P@(*a?`YJyK|tK#J~e>utMPCVHzlx2MA#Gl45uUQu%0u$1BYUrKu{ zAmLfSX(bTSBgosM`JEWA+|-Oxr5h*D2dHMJZb6|@A0b)^f{ zO3yi3U$!dM)H&ffEZn(rZLNNIrhMYc7a}m~`|dtydSX$FC0Z1MKdxt$?vcY?`IIJS zn?=BtrWW@eG)E<@n3@>1@U?Je8wlN!i>EX&PPc6q%|3_>TcQb<#PZ_8>TX^{goKS3Kg8MRjBb?jRl(1?`$o+kwkv1G4T1?V4+o1QsLElc+xBwX ztM?_IZgWd}j%$Yu(k{RRk5}}BOJdPK{pal+J(d8~gC3PNu-s{KNGc4!8G0Rk4TS9rctaJ$<3d&^fGQp6_Q$YDL3YtB^^wo_MBBk-!ju} zySeRX$7nc3wic+pk6QoMAy|d&7^K?m`U-QcKZ_x5`56w4M*@*Sgy^V^$JEB8xt7)N zxps@kkiw}D%#0bBtBe*V9TKVVe69P=QWiq-45<2xuR8Fw7qbN&-KY^%G^*2W&Jks? zJ+^nN>)94u3dWHlV=NvjZW4n)i#g8f9B(J z_@HWfdATB(S4VAEQeKwFbZICFV+oS?_p7NFU1Z$q=GiUAR@wVmU0*f!v^Nw?tO?1c za2ml`^g;&td^TN%5*TX57 zY(ziZpdV_#$$)@c*0-tLFyY8S$(X18j$5hB(r@v zpH-SGR~lwV4&ZQsOKd(M_7*v#YbvCEiPkKun+nd+8+@%hDW)2pKP*jXm#^PiMxz$* zI>YC-`dAd7PgOlsLt-xHtfw;|z{{p>XD{=NZStrU5_Rz=Yxc1^zt-8`fObqKvsz>?iA)^ zBGt73J?Tf1$~9a=(@^I*NQMa-283O%_^Lz63({+Unk!eXK06Xx3_C9=aXQskSocP1 zpzSYgOQ@bjM!KD!7x#S-h$8V)ebzhXU_&wVJn`%IYnT93xL|WeN&T?onMK7YKH9#8 zi6Y(6!PCt^&mH3D=hxdaMV``na0t_b8EeCiOoP3n)^r>civZfAf&~L}ls@5c>oits zp*^BduR^(d)xp;1KduGkhJ2?HtO8fy)=A5~5rd6?>8M8-^e;0miJ~VV%|UuQiRSZQ z3ZJjDuDg3#;T}S+Nvd}YKujC%#hx+sn=*9s_K6b6ZfW%K`=aFoDf!h zFRc7Nc6unUnMh3Af8Wkx&)y~PrUR=g>S1ta=#Pw+^GD&Tx%v=aS$>^6^hgvtw1p0I&a!Dnz0(jyIzolv zPt`dtN*FjP(Y2D&F51JNqD6{mBq5Kbjt@C!eLLU$Qb1@p_ViZoBs_)qQcgV$^oZeo y_w{Y+lJq+ZZ(VbxTz`xTU4r1ebZjX83NQe1bwtV&2lmzg0000An z(eHD9kL#@Gyx;fF_nh;v7HiMmv-iHQ`-;!!x~}_%5M{+TxDUu5prD}O%1BG7qM)FE zMnOT-#zI40p*er7i9Fo16jcyKL8*#*cw>x#JjZ?~t>cV>@?h)NFDeHYD zi<*Oli@ULtIf}9|$lirrQBwV>8#g--JLiiRr%u@@D2Q+w2~qX;275~wMu3^N`#HN52tX~STb-s3+2ZR>AOdGk|U)+K`}ex`h9%d@%8u7=pG09uMd6%EjYg~Nd5eO z;f7-bWIxxEQ0wh>>tVVV$6K%6u*~LV1h36+8Gp8SeRYrSjJFggX&K#Ho`LUqSsu(q zxd|h>*ABhqvaiCZeOr%q55eU5!_CVkHmJXzy5TrF7S3932&3Z8l+!tL3QBiS5W4E9 zvlL#r_>$DLq16UI(u#?Vkk@~Q`CC`44>+_kv3rHHvb?uD4~6f}lMKO#92x?O{9WN& zNF97*;+e`(uX*&7gS+9PJ7q9{oIglUX&HG09%@24)D4;Y&=mwrNslmZA0(zXn zj5}H-XBcXDPFO-KCWNUXC*(yLz(5-%{KC`cv>UxA>c(HA5z@{TW6*mdKtdBtc0fBPBDw zgwM1EeBet%uqh5XZ|vSojoaastsFS39_+|f1(tflYG()E#&@1>l|8d_kTibKtp4_5 zl1Jrc^T!IKQkX~RA&2ixX9{2Y)%t>A#`_Dsm`$d8VR>dZNBD)CGb@Pq$E}CUH#a{r z!MC?JUMsgpyVYk%eZx8qDMjF_8=d5A{u_Kk2;frW)Fy~NVHa{B#m#*(M&tFjP*?9NT=f#rmwzG=x&(F>3w#E$;^2^sdJ#v{hjGKlC z`An+>TRY*ax%&33=$oK5_1lWa7Ns5miQ{i7GqfG!^j^O&ySKG@(JIxAX?QoVIj#TG z&tnAq?zTsX4_u#qD!;x;3fk`LBvt}p4tr&2=7N^zq9h__g*LW%7nXBV&IOnW+jql% zOx92R3enH2NG}gTMhAvOS!rzrZCQg@Be&1lS6hO()^p+wV;nZwS!$n?u=nyNGaDSf zSNr%!KRauveGwb@?V<3v3UHb4xHx4NQ@Y{xL4Go67n@~h9T0rEQ85|y5$oFEGM4<_ z&FS~_r(x;c7elIg#`#Nro=5FVms3&gLLR%IJsAd}ta_o_o|*VD$)PWAg!A{+ z7=nZ^h04SbD38vNLZTRLBcr?J29z)&qV`ik|BOp=A=GI~ZH77H1HBwI!k;|&x?|<$ zW+7yzJPaPfaQf<{_gI*FlrMzqpwr>*!X0-C6*7ME+ySvM^GIOdGfZ<(4^gZQM~+pP z6uVs@oO|Cl@bE($62A88ABS8Hv@DH)MNcR=e5D9<%r3MlZed$vQQZnQM`@>ehBw{^ zOAa7Rj%w=MH5&YL#2uVRv$}HjQyG?Ni?=n`J)n^fVwQzw^;ia3~aAuE( zG$Pl7jc?ueFd%cLSOeD6M48#}Pg-rzD~pSrHLmU>slWkbfbs#o33yt=-lH@j?m{C~ zriHJX?o!J@5a;|=vfp4gY#^O-r!t}el`ghTuYwW(@H9pxABAh6L_rsowlPlct z?UD9&4`ejQDede|fnP>rSdqjulcTvDG=&O`R_I^Lw8oddLKIbZIDqf=k2QS5=Dp6% ztt;XEN}vo?E}zd=QQnirKlCO?bWPcn>27mY@4gxW9L9yWzjM|=JH-~OlQO&y1&aBc zCMbUy_3q*qWKW<%_B`&8$kM!lPpkXO0*|s5>}J3L7zz_gh>P7H z()|{{W<% zF2W5D(nB?YFqh!k`sNj<==}{zL5FimKgjRY-)pQa{-QS&k`G9bq{JFkRLi`*xlToT zhyKNkK*xDp=Iv>7sxSiXHNTTIb%r$3_8fjY=?Ne|Y6adZp1n3)Z$*40=bzg-T#w}1 znNZ)GD~7y|3hWevW3J&%G?@Se)Is4w>>EG;q=WF-4nxGcGI!~eoU)@Ft|3|ul z!=dMgjHN(!@9h0LJ}tqkqT_^jzh3prV!uFX4QlV`d|@hC1kvtPnHccr9J7o&796-+byV3gJ8U(UJQ7giO{O zwaJEC9A)f(DvmuO(0D+vl<>e0Y%-z-h1#i_8J~SN6S{c5&j_)Wt z>s&?Q07uC6BFXDBQJH)1zeTCkZm}g4Sjc-Of~dG5^2*>1)*I%!-+ELdG(8m!whb@*9-htnaSgRMsDTaSrK z5QzSDrJ!ocJS9!ghpyHikZaR2rfag#kN=RmY~J*VB*|LGhT)onuj<|vc(UEpVG^v4 zFeJ`E>H?Xey8|fRp?)?ws+QTaO$vPMzHqn6(4B#Jb1QHzwZI}gR`8kftsjE(k9Xp7 z9@O0qDTN9mqTud(xJP=~mnmpBQGZGt%Q^gg@#53+rH(f@`A|)swFL(0nZ;1kkS0Rq zmV)ds#5;3pz1eJ|DlmKce_% zsFfD+iTmSpo|5s+OQ6gWQ4KHM$)|FXfvAM!#gR?lCvbpEc*r@>8B+-F=HZ``Xmr0U z&OHjWuxe3hdm)!n7#JZw*@0+ATU#ZHCqy%V8x)ikn;yxrK zl4d$K`PxxBZ=oQn2ZEaX`44vqF^<(BEY39!3H0V%W<+O*C{?eZ4!CO~i8xhP>I#;u zfHD^95pq5*;ghp6=!%qGLz|j#+gbtz-d7tXvG+lY8$aW*5}%dBX_hse_fUb z%Y|yETTB{9E{nNHrF%_wF>?uIGAHyv4d#Mi4~Qkv&wfBN7zIgr!nFhF;bb8ZkIWsE z!SRU_cLm~dIuRlXaK7fHRu`|JOadCNVlSE^&k-&aov9J&XTI8}#goBy%#s1KD!?Le zssFOv&15|JcV3}LZCat2!fZam1(kUeY2$yQ&A0@;QG!2nx3t=JpTt`9k>n0R44K)b z^=z?}iDQn6Aj(5^8T4mob;ZQkwT-PpD+WpOzN%{nKQl-(tBQZVkvJ`ap-z3>yD4@9 zWCNwb{a?%ru@%EkV1>mOc{jB1o}G}RR#9gCBZq^$h3gJ72F=`}aZ0@e@G2{jN4y5t ze2%WIn!dP%XFjg%UFSDmAv~UGO|(V&KYV>iIy@&tie9tg(Hc<+ZO^B$U}Y&@xkA+D zGDc<)n_Vk;Ey0j>D|B3F#VsiutQvQ)j+AXonjIh^Kh{GzMxByw2uh@rJphhlS}SXa zaY2jKEsqOw_V-s-(_`|eEI$b?QakfR%!Y+v+)X*4d=H;Z@M@3T267iMXMQOs-Vy*HrXUN(;DW>(=s4B@` zu{j4)9>x21nonK;K_`qSBkAU_$-WV3+qfn5eDZ1IqvzbdFN>Czd$VcwIrFf7)>r-k zQBo#pM?mp4vf8o84PanZjC=$MS$mvB7s+g5Q6Rl*1PM<=gpk_MzH9#_1JAbNfk)he zdhkLC>!vEM!VWo`wbAPeWz28~f1#Lu_p#y~HHtY3G`1-!Y_K!O(lWjxRe<6=nnL`y z2rvx1#mb#wUZgcID6&7-tjrB9=Zvg-M?qaM4qiw3&$iREJPjkjqNuv}yhD-|`m_o zs$hDWMLVbjsi0;2;kT>yHX05L@N@ih^VPWx*%hrt!I9T(TNA#; zPy#4CF>wzGL3=_xqu(l1qCP`ub##C@X*DAWO@N8Ik?MFq>fh!S63au3KT;}CCvThM zmAqKLI9a)RDbc8>6t=zs@cUCkR&TS%0+4_Qd=;-b-y{8w(|u39dH?Q3>Cnn{Y>X1~ zTI9Zmv+#4Cwurap)wp(Xc8LVHW(g8qUAygfqGJlzWtJK~JHiqrIRw5I6- zKuNCVM_okoev}&1QB9|U)eHPTatpU0bQcW=%W=Dlps~8f{lEOGUy!5inP`4Vg0Gi- zoSU2E~KVnS7EBv{- z!+InD*T08i6sv%e6`=^8WVUnm(x>T};X3$bl6_E741ngNWL_0gyj&?g-i#Cpe4V_$ z-h@qVM`Dfkc`z%#`QJ#q+^qF(fLb*~an$dtw5rD3PGXSsu2R^FwP=QXb}c>7Y;)O>9~Y4m*%D)XOe3?cZ15jO%L5nD}p zU`th=zhHZnM|D%ey|JD`g{~qBV@ORpocoF6<`TW#x5WIPM z&jYgd2n8hz4Oyc0JdW-?rCrkUQXIK_@&H-#QkaTREyO*I2oQ_z#TSb% zc!h^-S1|vqmaz>-^{`d?2qbwRN+paXnz8xv9tuj~XJmHEd4Ii15PMvk@t()O2l=+YP`1soQ(Z|c&6eSOA|5NJ5vt6KrdlUs}3f2Pp*edabt~U^@wHbC>mZLjh|7J9wYF5A5ELN zC_JtQ@s-?Q{^82<#3{+gK#g&Lpb@mmAlI(9?sh6c_pj%5;cL_sc(BlII7j=C&u!CM z)kVU6tWoO%j#l-m0^kJYP=26X*K#jMvTNkZwqo@^10)<3*A_@pYy+=Y^uBGThM_MD zP#ZR%=5IHf{Pk)G*63+5fJywY72Z1j4e`qNQL%C|ed%#zV}$ctFTpFzKST^i&9gj* z-NzkNm(kG?6boAz-+5N~Zh*#GJONlH;3n0`T}Js9q;LIH>{aXVl8?5I1cf@U7&lD8 zJZ2VeO4+N5{fxo$vxL|Zcj-~m1+mp9z$u8JOf?<7?ytz3()OcYEBBV+*-0HmWFCD| z#z39F_XlurZoP1*^<>R`Eh|}^J6jyZRlf>CB%~G6lSDWJI|YCCa>v8 z(9BK*cod~DGOra%PtgD;npWB3f5>#XS=Fd>gia=leL8M~2J+I(6F~|xg6m1-=7!D| z{(%{_#b~gVB7BLX(^~3~>UiZ(?rrTdcIAj;ilLI-qS)ArCinI{NuUyoRA#Ge*j7U5 zZ6@L^s8rqU{U(HwBm=RIG_|@ok)5c_Pg(qgi##oB)Bbt!?Zx|6vFiD&&}sPCc&=4ak;9 z%RE0(0{6ft&?~Cy>jHEX*Ib7Q%T#c(+=x~gz`Q8778mOXGE&wZ5mJ?f#K?xk0TqAus&cdVVkFA4*Gtj6dy5>IM;M41qc8IGJ|5U8a_5j zohnnt6lJRCv=HdYPNL3V_9(1kX3OTGLR9meVy$hqe70Fhwsn* zih!t(tU2!M?l;YoyZVucH@K+=?zc<+V-7SK%yC$?JIN(@qh1xhBhX@P{nyIROiDcU zUB`*5G(1AZ&1_;OCF@Ae9u%is!(AZ+4jsn_dAoe^8KbJt|3m)J^HTx_CV`p4h~p7; zU0F+5b#U-tF`y`K+;3+tZ=*#PP8e=;vgSo7J@N4e*dhUonNYq~CfT)I3 zd+{<`_H0&0LS@jV=#9Yh=b5~ySd#`guzA;7o}CQ!+K!#CTFIhbWNzK^ z|Ae&xBqlCgUj{S=9yTj8GjUEdOo2Bnc)x|r>TZK=>2^M-4QhF6h45-m=`)@+nHV&y zKN2K3NwBY+xB8uyD3^`DmmSl5{Z@2RMMJQ{oJ+)AS@2MlOuvyre>?O+94dsj+cOV$ zbb!Vg32Fi9u@YWxFJS5+1rIjJ@cDfMQzw+L~uia$|n5*78hZ z789JTVk_2lSm*ph*>OklLoD^twet6^jwCQQERJphk1K<;tBO~4xJ!aQ@``BHI>ud! z7OHCam7h|VAeSRDY6jXbhCN}a^C9pj7WT_RcRMETkI3g=x$$_(l<|y{S$wf zfd}J{B;$@(-eGXEk4t3h=qs7BX)qT-5}Er`Bix|U``z5rs~;X!1^1|sw{%A<<>X6$ zmPH$lgv^tlRVwIZsig200<#pzuu(V1e&=@Cx_L+HS7$=d$bwZ5lWA>ZE)k-6R>^ms z8i+EIBOkXN3v1-Q4Ley6a)~VHIMEsfUkJ57^E`^e*0&i==2OA3dsav5fiKS{f4oc| zhViT+5@aZ+yGgoUu?NffJ8_M-93S)`j!KsrUJmJa5&e;MZdO)iO3R=j1#_Oy;Bq&p_$Qpk4ORWvp5i{O|MoRyJLI#Q;!CfpPME-`h=`_Q^u3 zKm^HO$zyTV`yKZk^dlUYlb1H724z&M(os?(^ZvsTF!Y8uL*9?%B7^B$=!$^4a&Ero zMmF9NV_h;ISynf%Bwt|=jA`fDRB&Eb-+$nY?mabr42hq4&MAtIHoUVX2V{z+7&y{D z?IpfQ4{@PV$!mBzdLOj5!;KsRVL?AB-{pQZJ*k=&sL?7=n|putG*?67PIFlEOwWdo zF5r&<7WqdOJSFj3gWmB_M`lEVQX_;vdt4$co{^O(3%g8ik_U~4$UPDSO)f7*`a02r zUK4%KJmOlxBI`AJq=V;;qbM+bQauG6N>*uOPxxCS_tDa{6n9o--MMo}YM&qK4NR`xT$S1BUD_Ib7A0l|>!_kj~-c#JPxg*-5>an{k2$7H!3r6-Z za$SDYAEKNU>dQjYdeJ?@1b;Qu$&mZ|27A8pkWm-`c9(AxrwRjvZoU&4-n?F`-Q8z5 zaDII*q{m#;@%_R|z@Jwv73x07f5BZjkrWnbZ zd}Z*>**HI;*gczlh4#D41vJG(@tb>(qyhBs)rdp;YI3Qrl@1PXQ5lEA$_ID(x{a`zlPkA zR43iI{%ex06ZkohLMKPGp?IFn!N@DGhM23KmVQ#(Qm(MKJUQc(be}t*Xw|O zg7iTKXlW{rZ|>89ip?71#@X`BIF;n42vQ2;%B2ejXdJ)l463!px?>Q8Nv09IXHCgH zG`w`6Z;L=tI4n8Uc?L;&AN?r;d_O*UTVv6co|P^PiZuYutClXZOsJ1KBgw6DzW3K6 zQ4ewzv*yT2q38Z%j167QoGf`(ss-hbXBj0c6pbBkiFG%64#A^MfCm96&3Q9<4n|^bcqH3Zfvw7=xmqn2(l>yE1`ALg#(P2fYYEC?4+Z4~1;>n;t)Gf= zp*c@a)#v^9dp>LUzR2yFhcr}xqEk`pFGv;f+7NC$i;zt!2B8=7ix8Qst$u%$XV46V z9HFpLVHmfnIw&vfTjPw*v?Gj1OqJB|^2t2upuVF!Wq{y>6>>m)uzUm)UzirsSfBre zv4W*xGY)sIh0i7_Qs?;)E!=|}Tt z3~iTJNV7cl;Ma+muSq;c)m`+rNO*u0j~O=x;E_-%cf$I$xX85RGaBr@llUUHCc+;2 zUOBhe@EbQ(R(IUo4#()D_UCE>b9W=Ka7UfoKtKudTY6=rEpT!>LB9{Q%%f6OG0ns z0!#8Vx0LA%`;?lZ#{2}$8Y<*1Qf&i8d-q=i7DK^%?-9{9?Gek zI!ab+101XmV4}#9AF`}y=1J5pD*X$|KJO9WL0{jNwlOFSb@45lN2ah@2RC4s0TAr} z3Yk+pU9!96yKazf&C=+hZw_9~MWS(+1F9yxlsnJT5kK{~O5|Ddc&R)^b|TDY{;8e> z5-f;1)KeHuR5dlnYebmh&`PYHW`Hygo~T9iqp(4X2_uOCV5YSlKI`{}5G{L^RGd+o ztuWW_P+-mhcDG|p@f0V7JXltxq9vWv_J;f379UR3JFMNv^z5>bmFED7%KTB*0~w;D zT6cAh1MQOAr58h<56Axn2oHWEZF}S~v))Mo@GerwE1G$mjSpsQBZ#W&C#l*(u`pW8 zM7>t%K+yfGnIk!g6*mme=sxeA4*7q_8}1w-C7I^Ntr%o2Z*!_jXp?iQ;^IEFqAQ#S z_7A_%;jvz~a-b&sc6hi?OiVHj&GG;_%81rj*y+{}e;ul-vtKu|-2JgnNko>IQOeTS zkA-`Re|&;_e!=~-fH+|EvtMPF-%le?T%3o;d|4@X%^qCM; zLq)S>HI2lhNYr;Z8+CF}sok^E@O?1&ou87}TeGjLH`QO#KHh}a_JW+RA3yPeUTG`iDo&^LuuoVy#R1l$84v0|k-hhOZGP_t zgS+wr`Zv7Mx3{bhciH;BZIDOk({((FNGIQ2t1+&wcYlQ1q>>}0h=&Jwhwjs|28D(` z4AV`jwH<5mxUSoO2;p&ccEmeAadZd8Lc`cVkcIpPKNEHgtC(br&~6%*AWTdZG~Slj z&%x18#ao5s!)Vy@PM?sGOH{}-6>4_Rm4!L$t>WDcptuKn~a|YFRV0*jeR_kPg}1bV_KIjPdnj<-`g|!EmNdeKLlvfxBQ>n3sFM9G`vYo-qhpy~ z?cQjckB$szMo~XBl@I^tQ9e+9`xUJSVqGdLv!Y>_)t-BTMqCpw!~6Tm2o5hWKmM?6-CTquhPSUzn<V$Og^L;!& z0TJi38dfAPeM+(cx+UCmZg?r6=Aub2p6FItS7*Pv_>!MB9?h{@ai&}$Rgsdv&VH`g zvYx6tAOO`@D85WGke7DzX-tNsOMU}umxO<>lvJ0b51-4}?HTQ+HBJ*&Z3k@1nj2fxWNyC{MR_sVhLj z!Plbz$fuiD7nNsJzqd;QzGdQ5hi zo$Xr)FeEHI6boBHPH|h?V7W{#$<1#5e0!<@)<8QtBfte@&(aMw#>>$s;=JJY~%a1f2^bGTZ!QIkuv|Xb`^!Vi0|&W6udBK*Av36 zxJQ(m-^V4?RdN5$`GahTl6n*)aEWfCo{0+ZtBSa|yM{$YhTvd-RUFw{uGOoc5)?>u ze+)EI-c2m~czX-|a*A*9*q(dWxw4_xJFG2Gl}jc#A{#}a0=sIAMt;}C%D^y}%!jiH zx~xo=nwdUbr63wKjRDBoovEOvB23OqnFex499PX6c?0cUiIL`j6@v4K&$y2`V*u5z zV=+y$eGSP!t#%R z(CI`L$lKyO2a02AYc;f^E7p|lj{86fYetnns5+^q|ZLQaM zXpQm7t|}p+D&c8GQHTr^b_f&p5lAM4 zmZu7u!HhfPq8=9$o0gHLJ_`&Rcg`F($C&G9k4T4#i}NQC*V-)ug#uTdY^#O96N$(_ z@?kRpeG0^)5^sDZLn?k&6cRsd%B4m)0I}rxD$}nhH!vjqRYu{#&-0%yu4B$1C^#|4 zvHSU0B;Gmi^t7+RLBC@RAS*}gQ{jiZz}MY2K7qWiG3_iZ&CShCml#=RIoP>ok=}st zhdt9f`CA-pY}#=URyJJ5q?O%iXxqU&v5fi}&z20{5YlJILg+nDCb4>EH9h?xD!;VIS|<3fO*c zT>^D}tY;d62HY?T?K2PPMu(qKzcw-&IcV((&k#Z7fBlk{lU<0(;Ej&XJ@bL;z?X6r z{j@A>`hER3u#J(wHa zl?Pc!|IrvmV+xy7&Bt4xo7TnK*GoPUfo`q@Q4$jcwY9Z*dB~WV^iS@K7IEMsMKZ~c zp2&fmaXhzkK-T`A?UzqJreDxN`fo6Ahd{D_pDP`}1a#iM#@)|d6H)$=svg}rQ$8zuD17d1k{iCD%KdNqm%19RJ+K&6!AbqkA4 zOG|>#ZiSj^h=nE3P=Ef_W@BUSQ-&aB5QvJWuOF0=`X)hAA(dqFM^ZgA`FOLp)gdw! zd#L7u9UmnlJR-Md(sWGgS>gnT)&pPH&QQ_ymHhIlnJ+v@F$NWs0 z)d<%iL6%D$UwO!zWk0^cLDdESm>U}EUK*-h#e+q-zO|J%(6sP$lJzL517ro^QZCmk zA^M35T-gr+L9FL=D)dV63dRI_lcfEs|fqkhC_#D8`%D`cU-+>e+{-74yB*(i0(Z ziLsd}An9d5hqLbbCQpt;Mp-*3Jt-z8Ge)K{B^0A^Eqn@10t$*YLYF|^<51|mif-5 z+K`&oR(<*R($-?OU!g5tKcZ}wUia2k#nq>#)~A*;L~>O@nbI~9-ZWJ8SUecZ=f zrt@j`=}olVyT>)op~nxArYGgG(OoRf@&^ECY?jlUxHn8gyZ7~CtMSV8>K*2)TQo}C z%*{8<*#v6smx^3dBg=Ft$YHixy0MATk0=PJ)^~|SQD;{8^hqc38+4jiKMvCBTid#* zOFy0JeG|BmPfNUMR?oy_tEIVEUFZQa=JsR*Day2hR3=&y1BMoo5?w$`#m*&(f&+T) z4u*?GuHP@8p=%DM^VoHb`fv3We)HRk79lJj8C^zQrb0EK0t8S#*&{NcaUPE&G^J^( z=U^I?IdgL6T+BQF>E!O}>X?sXXFk%k36UcP6XA_3Y?4Qs;*PEe?lOpWt{0 z%l676kr!-+-iX#+5+$n4^!R9U(j;<R4>1suY^p z_rEs7Y-Vy=#4CpNJ>7ODYfKB4DbE@`l%q1lJu z=R(xm=2|;S0)RxwTu+ZwX6o)#jusM%LW_|K%_6g*uqlz!blsWGGqSrB@SBd$rQF|N zgaGk=o-_9i?#(x8j2xc>x`NKOd8|d+$I#(QPk7DX$trPy_^CYh>&YLM=y49)9Zq`o zOSIV1xw1VpJvE(FHC0WT*pdH`E)d!M(8lVv;^0f7A65uGb{c^EaIx2GG8^z0+SgTc ziNt#lj)#YnhqJ%Gv34MPcyPFu03e-W6)4kJsp3t+ zOVoZK-S2{L(UxkmX69Q)Yv4s*>1(6Y&!TJh@yUc;`er*m(c@5Zd2|KKJeLZ8^5UIQ zy}in_4!{%z&`KmG#ej)!f<*z4!z4Z-kuo(r9r@99f8N!qsBijImUDi2czUSOQPstz zU6HaEXOGWy5{t`pZ>uqmelU~E@u2HS&QdW0SbEU{T2tC<1WO?8E&yY*fh@dc$$82m z$ief9TQrG~G!8(N@Q5HEF>T3Le>@I#>%QuQyBPvqReP&r8!DqOgDd^Fg14fxDn*3B z9QYIz3(+F&0Dbvl_Bulk8(jd&7RCB*|KD=W#2GBM^UoDF|nGBFX7Q+sBHE zA9VSXDMw0)n%ix2wu-waBAwrQGS|2>@M2rdKo|0u5}AMK&AfB}rN6LX`E4CqQ0QSD zFYkFBoZ(erBJ)&SL~TS#OWzf^l-FUu^_0 zyt5%0@j70wlKFIfZaY(~Av#f=@8NIa;;U=z85;?GXKfo^QP7-J@Mh$=PJbamo>I_u zVPFHj*$rx&9+wuMo+izG^ga8TuaKKtT}*a|P7$_G6U>m$R5g&DT@&#rnEL;!`w$6F z9f`mN_bzmFboBR!pusYuD!$|6=0}nPP4)`0x$}p~`K(uuC0Q^psI_%Rp>hky%jwNH z2Xm=TCxxi!Z}1+!u(oHvL}P#n{z2TOd&;%oGKk3 z@bKI?39`F~su?041D#Jt)8cdEXA2ufjs7;2Z(A+jbmmY5%G|ShQ1RjJ4q23gWcp15 z!=DISuZA;mGel-ooAmD(e+sH&Wy(8V@iwIKq#^Nl>YkO_R&aiTXeoyYfP z&yi?T0RjXMOiE5zmL?0hcc;yrFes$GaM`MC^HE(}d;M0Q5p||qzu8_8IWD9t!V*H@ zo>L@EG!$IAXXNlkr%93WmWDaB2dGl%X0y7;%5AFsBXU9tXQdUiR@K@Zb-3W{y|TRK z<<6cic=xkS-=Y1zNB=3dcoPy(^EGCWR8-HttF1d*SE)0jX-*mXFg33LNy|a^Fcc_+ zT)spvr)sih@Ow*twP>`{1LoyXhU&%h46zOL%Z5L2cY-N}hiy+bnH_X-u3=sjz!xXi z*OM9lR)sOCASai}Agd#5WOjGBmYNZtmXgNnxHVh#oMAAH-)p=9RFYUuCg| zbDDgo-y{_fsLN-q9yVTMOsJAd80@-?pm5V=vPwT2HAsmp2 zTTc%^n^3u>M{`+;rl8yE>{XDWpd);}F)nC^Bju>C|GCzRx9if)w%YxR-Ib!wEcK70cM{H^kQOr}>p+h{~+9zG`yJ>2b4}uvbT*BH4jb;C)}bN{(eDg|OvnE{0kGTPjcQyBUwW zdC9Ot{U+OqPz^oJvbh%PvmK0wyzF|@d9F4kfk?vLajuLF1QqKy^Ac}UCNzHqq<7Om z++9Y&CxP%7d`MviIbZcO0?ouPN=s4fW^gQrL&)AhEjXz^JY z_xM_%@OYtp#{Yv)@7XSAu1)Xv)+Uu)s@cXCSJ4R+hmrLz|1y@Oa)TEqG0%fEM>2RT zmAdRLyM>PI32n6Zau~B>7^k_)KDV8D#V5@0v~O z5}WOc03~#9AMRK+ZnbfSh~$^Pr@4(an@TA!Oos-%7!;BxE95S)JEi0)cr4(_jx{|B z$pVh!UqBO@LxzPiioef9&FF6`zTeQ&FEYI<;<#FJN)Xp|%d zcb_stE1xm!X8MxUXC}y+2YQ+~IQ%ejnCUBiM$Dua_F3T!7K-F7J=21B!kLPaY>~@g z#MhCx@7DspRtSR~4-}r3=`1l@f^eobL+ybbWhe8W?r3 z)T;M27KJ0~W-)E8xqhYHacF~qYN^esKVH-)AAeui^zyT0T+bjGkIRkUvK?fzp$Q1m z(5|%KUJSxN(0k>)8vY$U(1rbQxj6oBtK%_IMA#=9zA!S+sg=32GdVfge)-{KE)!&X zLydG$4`{wR=vu;yyyEk2WO+g=GF{Mq{H9-xiJv1=&}-ucV>gL?;r}D-yW`nx+`YR! z9jIClik8|%)vA@WW^JXVMx?0SP@6=gliIawMC{mm&(_|16KWG9My$km@1Ea#KJWRQ z_nhD3kNoexulrixaaEcxWzY*$D(a%#J8?P(!m9#=i=G50C-)hh* znZeVUow{O|TW*bfvoiP9Ek^qkmsOOA$krrd6h-0bUOXy(4K*X$jHaY|{G+#e4>n^rkh|y${gpGelhDcw7Bch_{ zpt3Hz!@uMTneQ>{3FQ4uCcClR3z z&@vl(%T=?>tp{rdF+-lipARi`G+gQF>QDsfEv{P%NS0Gk1 zuFhwFIaV)&nlOcG9lySZ)i9Q}KL9?zF+tN}v$f)V^=Jo3nN=H!^3ZX*6fv~?S;m8H zynbPPMs8{>w^v)+!**kMdsNPfCQ|7y2b&+wLDH}E9%mTDoxcZ}F~mljRQRn3^~EQbr8u{2+>v4U9aRRAKkoTf+Kzz-AiOX2@d& zlVuJqA+G`}gwQC$iD8blmf<6_;Uk~AOSN*q&3&2s#|b1dP6^Hs z!T*$;@!)~g18fhI9I|!MFmOc}4RY%B9Wl1jjFp1_{>^03CXAM{AE`ZNiC)FHakIn} z)Sk}(?bT!{YAS@nV^g}^adwB|09Ol?B3MXKyD?&TGDnPe?H@qLmlwQtoozJy$0tXpAyB-RM||g4aE@Pk3O;`ekykb+O^b+PH4>g(gtsnw zv5dmdlE~i7vr|S*Ouf%excD9;%(%{d%s2ROw!&%5m&`U^z4`Ni)z5uXy4s^}ppG0e z-GJ!K3{#S-a?niRdm8Ai(ecAd@kxePXOEaRZI4bE|5~1HikA!mVZFi)GOeHcb$=^= zt2Y>}G|T&RNLOu;{xCT&*Jj}vsD8@e;p`K)Ms-HaS)aXu5O{ywW7g;Nv;vBj@!8%; zMlLpQ!Tc&5r7JB@wLjq)3DuKbc#K2 z0K3>6RxMDlO*X7ACgzFy5~(R~j92^ggw7)OaWNQ(&-|QA+X1+8bB019f6jp8dn+J% zwOA4U@saZG1&Op5}%a61uwZifAe11*+}1LblOQ2 ztCdjn_G~a#Zy8tNL;kep!b8bY<=s0iev=^&QtLW3>4uPifeK-5myIjfC*8nH%e2%R z;4;+t+GmQ2hft9;gM#o3P4wNnfLU*6%{*JlW8q6;Jv!CE=bPypE#dC$?&3tbz2i@L zvrkP+U9U?L4kdJa& zXw^2D^*LU`vLm+!^!h-~^Ajo|V6WZ&Do*NdR=Ed4{JX>f3e@@L7<)aL6x_8U4EDhf zG$FLXg`fYaXeh`wzM#QSMxfEG>4W#qlhP`i7r#VGSz!Bq$RZ|E=u0wsXG_i624nZE zgV<`!XS~(=4uzmq9($vgr}(4^gR`D9B`f1INFM6D-mCq;77&NPU{Y`G7cyWrN6FHE z{$daS>eL1!kwDx9^d3&TXy+M~DRTAbSf(of*=$+JHyN$0csvx^7^r?lI6#wK*GB3T zyi`l3GbWfoiKfFrlX{a3Rnr{>wStiPK)grS}% zgQ-}dL@A%MVIYdA6XZ${kMi^UDrPGXr=4Fde7&k_9A@g5`esqb$1CipDzCi~>RF$| z&Xx+OC#lT37vhDdK?Z>k-e;9Gu~~e=a);d*)R}7X$!AGZ&(jH)J~{aAe5}QZ+JhBh zOoJ&vHPg3!p>gn?xqs$Ol$9Bv+x}mP1TWL9Mjik``t-f`%LE}KNz`;Mliu>fxQch9 zrB_+>9U;=l8KTGnubkuS>UUjuaUedWDlNtaaCGRy1lgp99q;A#xi;;@CzVKYe`1|Q zV-wEA#Ku0-vqi6qLwZP2si}m8q&gju5Ab@*f`cPIBfk|m&g!SzbBE$8;K#j70%JW; zIH6daC0fX^?)^+kY{ZQ{(Yv=$v+)v27vstmK2G&@*)N=UQAp$>mL{z0YtD{q3hIb^ z+Thde(~1JH$5{)Ot94cZ+KyUcUA@uXVs{9#+}+-2Of2^Aaid+~2!F7`HD@Wd~!=lXzbZhTdwXCcUl=rHN*dAPMeK46SEvvs@O#xWT9!UWH95Zh=8q5?<}%N5|W%AGxJs44Og z7~pFUJJUV#O%@F}F4H8fz2ejLok4774E~1x#{AU}XqB@lV_vE{HuDo6Tvcv|ys#d3TzRQ>6e}>k5Rp@mRi0 zC33}|f+|7Ub9?dZ)S8OjXZ2STUDnvc*J5cZ1q>5jOxIP}gdBod_faef<$MmZLqX`g zx|%?W9zA&dN+M#bnw&_YWF{fJqSLvkg(CDdCuIddJ%?+dflZtDTUONC`cZjOVBjLg!g`Q}h4@;ERQe6-PY2s+-bi*GnP zTcDn;JIC)in$&OOTX;;pcQ?{iv=YH?M?0w$THwOHR2NJSIjOaqo z-A`GxR)UEY=B)>z)HduKbRv!jzad@R!5^FN;t=CuA*?uqKLWv_aV|f1s4?zgheB~EU2pX{*rrrnjJg(w_)^4}!Y0Eipt^PXQG-jY6kwyz< zKU^wBqtR}h_E7CUs-9geHQUKdpfK`$KV}xSHzEl|`s|GXZ3t?6GSJbq{s=FsUGF?s z#E6k~+37Cg(YotRY_Bh&*kT{5i-?bDa}jQM{wLlY8%~HhIRu~Mfts-y)WSjqGCw}=lhtbRy9k%7}TAXMn<1n;-=&Or+*(+ zFnWOtU$*7z5up6*6+b~q6rcAh;H&(n3xfXt8|6x2+9xB61Elw%d}#I~)$Yxp>!Dgf znkJ9k<|3U+qJ@m4>?h~wb5|=ZWSZ&Hape(YImoPSA?P7XWbn` zc}8N?+j|ufCokv+U@@(|I5l-1;_B&S`bt6Fq0rJYkk|3RWfJ;}q&&t%ul27t3n0>{ zrM0$O4Vs>-I~EkZY~yli3M|@SjDTaE)_qm>B-8)}kh+fh_CsgSH$GQ@k%wi}&<5xI zvaSeUN|OpFxiX+&XMmnU&o|cXfp2Fn9)sHEZn(o_b6M=GK_^-*tVPdA&0nt+Yp}n^ z#_?3oBFvxflb*Vzo|uA?h;TQOa4k>h)8qdxjNuh?`J19Edm&pog(q#-$^9PmLnK@%KxPxqld>)YMp>88Qu+>`wR zWmqQ5n_>zqbUA=$Ky`6IwI*>Qr?V!|Pb89R=oZYRduXt650eXA8%a;kG?1jlsLS!? z06x&rGA0@7$D9%yrS?NH362{cK;=fFEG35V{gY6*gJp|)rU0PRnSPXI}tF2D!1&y^g z!0+wOf0g)!%EC8}I8{Q~a3$7LKqh4r;ujXO4T~VGZ7r?#kC*w!=LP4z;yi6)*}pY= z4PYKGOg784g73}riT-TL`z%xo)i=iu|JePKnf&#tK%3@Fg%vr~kIm~~U?pFcJn6Dh zVB&*M@6wh-Egc(pypwuuGW@GgxP5eVw0CBvV<5}FBrvtk!pm$Yqsj$}Ty9)n!H{M9_iOywx*h%#cKLH>imp9_X?fw z0xq0j&yPtF)S&a`2} zkUBno!*VzTh^fx$PD?Nh??mcyi|dCA$m2AFXNB34B`qb?Xfomp)+fG?Yq(EbsqO)0zW#H8&nID&*o%724f17z2J;b z$ZVef@$DY<7|6DI4W`cHy4qi%bWHrHCn+>h@7D8?tk3rP31WR9bp=zmJ>SBUh&URi z)yv3RleFeLH0g%gPpJfX5a8$X-K9_|nMx2Y+;4)K0sTe|SGr5Ki3 z@0}gDQ`S{#&=AF{BpAN8M4fMfpDH`~xzj|FAYC{eI)tLmddjFNFOpb+j`#M?%UH$C zS8XP4vN)jUl5W3KR9zV_aH;e^Of6zx|2I>ML?5Lc2E3=H5Rx1}NIRgNMq5eKHHiNzU%rVvgCaG;Gb{f*+UE8rIaV%o+GA~EHui_Y?1G3DIL zfR^t=iwVCz;L_rOwp~!{w!bXq_E}mv3j(pdD}x$Q9DOPj`;r&7iF5Fs>5}S??N)~Fu408VCDP2-WY8$YAb;Eb~u}K#> zI))yw2JFU5_I21#rHF}0OZ=R|7NyeGg#`ywDp~I=_3X0@SJ=K~U0B~-zz>2zc0MZy z6ft>;V7ti=?x|alv1fuyme=**n@CI_8UfH{8fp*Lf?2@8Ozw80e|G&cQJr8~b+Ww2Xxczr z?9v9GZE#`}pEw;&`+o+2&KYU2%me;iFvfd#g}veu(b}Nlv23H$=BXsuX9chlHSsz; z#sYx7+etSca~BXhK$fdM*vGQ#z|ZiU7C_!n3cDi+5nJoboc^NUrWdAI7U5i#<~5+3 z{E(}(`i?1IhJd!mH~O*&Pw1$^$NlU2%kq>#ChLa04iH%yxuGtu<~O1q_3BD`#246? z>g;;(pBBdsu%yWJ#kY8O#dbv^T04VHcM7kJ2+Y0GjQ1TGW-+iaFk>~|`E9%3<9Kx> z#~{~rA<9Sjm#4x0e{e-I^AVAWEvN?%=Bd6_X5D8Pcz%DGsrq|>eb!?=^ncy_px`=J zY!M3jt_+(Mb>-eO*MxJ z((hO26oBhNi~d7cj4Tppvg{U=22ux|@j*en8EGEst4z*dt+@2G}FH_{3mcBVp#!RAg@4fJD2e93o}HfJoSKjpNyywPzPu6m{XMl zCL8$y{VIoSTI*M9dP(-_Ac2#zaJxwROiyq5>6DD1{ORC)SEZH);ioqv6|-L3KA?9p zH0G4Z#1+dMHWwFUzLiDi*WPTWiug2E*XvEkv(+zt( z9Y@i>?Em`GL_I1N-~9g+DSo@wf7vQ)h&jSwtju&7fuok(mU*!a?!Trj?V}xk$XKi) zfp;D&VZE8cH8lnsukq>%lS63B7M^#{aZ&&avlS@A`5Zp zSVRrLYa&&PJMB2T93B!Gpl!(w6u6{kd6Q*0W*LSSUrO6a@59Bfp^jkZLIX_RvA#Gf7L zc=gA6skE-_sIv(|!@2uH4AkdrO+~F0=zx2z&%MaN%yasCgmD(N|CNy)m6_%>)Yb=9 zwK(Vjrr4vVidhr#b|Cf4*&^BieV^$MC0IoHU_nWnne1~( z-}4B`_t)$ya&;zcFfV;p$njC!7MAqM*%vfLx!Gm1=zwa%4=fQl&3U;DI%7C40u62u z-ZruJDBowr2Ge|dIP2v^q`a|05QPmqHJh#U=?7m_GO%NtIo|QufpQ)KLUx1aOCB}0 z$|gT;vnqe;23LOJuQH5H&eSx37`?F7s4}UF)dc*ShV|6t)m4}Jh5=>%f9e_L78e(P zzupd6oELwUsDy&tPZq0yE!*ZqiTm9A)d;M{UD;AYEB!*$Q^oh0p?+8l zRfKnv z*H8j@Z+u6TCo~ax*60$$R#*6@GbsAl3qXLo`nfXJb&cuiV%{!wvx#oi2!qeJdkhL> z-Ohjr@;TgR{L`$&7l2TQ>0OR>+ub>RAh)H)sy~DCT(^G-HE4VL7gD8s+Bh=$F8asz zfHmgqU_F>ck{6899ykVa#`z*nl86f02DU&@pRM_y>7O@S%KpR+p|Iu4Nn>`Rq;HnH1-KQhfUC2Mt?Z%>bZ`Ru zwX2`+^jz=Jqtr~{-TG9L_X4$wFUU80&HDhKl$D~TwhRM1I#BuPOQ1_SBPMq=bAFinr$JE&&EG;O0 zI)mr@y7}pW+0MVx>i+?jyqV$Dl~ddwsQM(_ak|y>`=YhRUI~=Jv25vfl3xvye4ZxQcQm zJ6#jq`8JG?_udMDPwssC6tG$Yd+$Y2n>OtBs>jTD&o#2d^nsl`p+4@*dW?yrg)tUV zL+LI6WYnGRk_$|I4wtNFt9EYzb9|wv+L$=t92Nd-WG6D;$u&JST@6wFBir%8YRM-v z!18Y1oc|l5&y~s2gK;%Qb$=eNwyKu>;N7UgGn~Mv0-nt+y!@#WoSs$RBZyfqc=bXV zRx&+37kumHS4CE_k*HF>^yjKE@{dOP6+gcAzUr6B`0TRHFhi4ysDm$d&FP7z zQ8^^&e-UcUZQ;37N+Kd6cI_-IXz8^sN^?tcqD}`XZ!!u&%e+sg4ZBA3C!`U(L2l~N zFvF6qk9kv}GYu=lA3yU@&b~eDzkiCkFN+|yH&L^tTsYmHYocbCed5snlcDPPa?Sob zW1SyS-^XP+MIl(1LXYP`BVLtNdPdm4Ay|hxfC}QH^p!JUGM7n$u3E&nG1cN{NR%FI93+*7-p;v;PLaIl=_ zA)Wrs&ZJ$D$j3r%WrLZpx$)y1?#6WYL7$kf&%1|BvuLOl0#^i@jqs||6X{I*{86K~ z!+IyuPIgmL24@0p3+ue7aVm=bA^D%{oTK%0?dkBOn;Fh4Kk^ee6!gL@%|9{+=1A5J zjzmoc-prMF8mN=&FeaT}XPY1msre8v*a zTVZ)Bp#*6kc%Onc#uM)k04vT%$tocrpPfIPRiI_=UYMAO1;#Ruce&>+ZLLDR4n7Wv z_Z_yLY1TGD1}EEG|AtCCwHwN|wxg1WIO*2IhN9&v z561y$+T-0sot5DWxU;wJJZc6(CJPrJ5vRb_%%d*=^SmHm7PS*)o#?q{i(Cd3pVsFR zOZQgT$XkKhsFMTINCgzRS+yq&&O1-_6$YLD^)Je1&ee45PVHB6J>5DB9u7zYNKa!g zRD4qo@==^KC0+V;AoGoWMZ*{OjRtRt>*;5e`WCV@eL84;pWZWUY@zgwsMpQ1X1Da8 zwD?FkpMrn~6Yp)kVCRUh0i7r*y5Wm)I&>K$N{Dn3dRtD8uL7IbFA>3?{;wYS%F!`E zg)nVVyeK_*JIUqo?x>(~!<)0gCH1l1#3I+VaLNVcV2Ctgrmo9itJ-nlY*Qv=3+Lf{ ze)b38E;|3&aZ2G2(j|bmAAMcB3@l-#ymvn$Bd$>g+@vN95L$<88ivu6%Y9>9gOOrl{m9Vdv3H#uB}TsCPpjc|t&EtG`O9H{}pE=l*63 z%HqXkc7PUTd3_o9wIM7r;n%Qx*PkEk|F!>@&|lwpsc+a4*hjhUReR{H$5ydu_cjnW zVq-AU=fFICs{!P>xHto$sF)`I%-FBd7v$@B9<2S`lRRQjf410E06JaW%ONzluVon| zN}YEC0Kw5>Y7Dy!01)_}9_IIIsCyB+6)>oyt+6qn)lFBA!8{>vq+%11#AeY@Gyt;z zi~=(^e-L6HFc~gVGsD%)GR%;;8manBQmxdAtjzuk;-az^{i#4Vft+~u>sC7NmO$Wn z0tefQ-ld|)sj}BJgIiG4bRK0b43Sn>E|S>)Q`NeV9upy(>Qb(;51adjrP7G#Q(N3(jZRu2)@>sOAo^~69l$V#&p*P>b*79(;X&eON zO7rnlrsD2&);h^cAOv^*yLW^|nUNYgjXcFaGbXSUAvRZ81;$`h!ryG~tS!+K2=Ppd zOq(m|dwZID)OSVa$w}?Om6c{5pVG&R&n&&l|(Ycq~`BSo3i}(|#fk znk`hQH;u}~M8)@q2b$Pxk9W(?Yxf$DReHg$8`D`$+6g|}{(98^5?~e@t*V4&H|SS2 z%n2v|t;}@_y^))|7F+ngsr8$cRK%SeI4+f*Ig)WV`jeB*=FDge*Cx6c3*sI*k*1~8 zTZ1v8_6NIL5JWJu?7NxBwH!v_sWRKHkLW}8iJF!1`Z_-kf>gEjY%*&N+jN$P>!E}5 z?neMFd*XSpJ;%s~FegL@U*{{@&%;klOh`!q5@|Pg+rii8IQOkYnveAzX7w)0?$9S* z#LADHThQwL-z1nZV}YrR<BGI#f9$!j-wOLF$^U@o|s&r7Yme^Tcs3KkNGU%Nwac5uB_b_m2kG zjO9(%!`E8BmIOW?a=Y%|heqv!3+j#rWE|^}OS{K@Az95zvSfL-4({u4c2#oVMeJ}~}>fvSnph9?et zf%WcP`N5%LhCZkWd?Rvgh%xA{I6&G{+MJ9$TpzIRE~F^|_H`AWE#+=3R2$RBzfTYK zK7c9#p1JA=^Y9x&5?8Tkid~KWE#DaNM1QRl?%tB7SqS8Q0R%G}1HM z)YFHXXql)giU`Hp>wHI)xOXU>?ll@K5?RMlQ?>ipa(2&?@rgDn@b2P{OW+oSd0dp+ z?zw#9I34Z>9d10blBQ#aGu$;bg0?N=f#!3~#DqjyC1G20qunoJ$PbBOiPEB+ZfedG zoy(yK6X*SjI28{VxbV0hZLjZORBbboyCzsH8XgfLBui>%>4SO`$^?WVvZ#Y!r=hG; z#6NT%fYFIq{W(Cca7+NpND9IZM+IXr-oz;DK8(+sLjbXtt3>Yo%44C1HH7{XHHTBm zbY*SM%<(92CNg<#WM(E2PysYFU@%Z;-OiILb>*N$S+n_#WX=rDA7TZWg=v~O61W;y zb`hKQQYTt{L8EHqsjpmo#oh6ug`WUCD*>A*o2w@iaFveh<)>5b2{VZ8)zia6%y5rr zQ;+(}8a;lAerth#Zb4O)W@cufK9Q#0)c@?GpnRjWvfp87;NQ&b;=CZ&H}AH-4cfY_ zY>gK?HibYe3^W2+VwuHFVjkTWH#34amIgJiG5HgvMMZ7(tpTXo2c` zwB85a+gPv;AxwCYKFU#V&LX_~@+}StNu_mG%Zs%^L9q2P3~IIcYYCQZ+?g;FY)^nT z*sV;`%Z%myE9w6m8rgGgHR8@eM!&`Y0IAE!$QTHbQ4NZa!Hw9P*Qi)z;2XsI zoY;`NGNLjv1-3pV^C{sDXlZKvEUNKGQQ%7VL@9g|Kx7YLv+n1unlht~m8hoH%kl1a zr;9B*0;Dwk7LTGy*A}l{S-MJUCV93U!Q04iB?O|41aCVgSzV>@_!h$aJ&6UA9UuK{ z?zJSJpo59pc>B@s@f+7$zJYO^V7L$QilGK@z(yw-HTTZ`P7T` z7%aJjyVW2+q8S}oTJM5B@K z&zmUN$7{`ZGAwUyL1aBn8z)Ku7~|hd{z5i}hcAG^ojsk3YZIfbt+=5kTo)$@)#I5%Jpfr58AOhpR>GEu+^7 zxFVm^dHNxlAi>}*TABe#{@jORwQim|&vc$TBrz^6dp7)>{`s5XzS(9f2Yj7_;z4te znm;=xBmRf-nv|JcwS&G@W3C1+DCkI)ui8KBWopB$ESH>_e71vz+Vu8Y-1cIcNJm!0 z;GmV&+wR`>g!tI{CjxG&`uO?W#bd(`mlEAoH!J1AG-8@lmXpG~k36$-gT^gl%iC&=wL9=iaUVYbd}^OR+D>vfsy$VS$X3e_59rd&xM z4tL(gU)>Acn(%tR-nvwhk((%kJnrrYffjE%VcM!EuFQTa!?BNT z&q`yq#o=ve~Z66I2v$i3r-Yq9*<1^{JQv}*T%gofY~the_W*kMEbj)CLd83 z0S6Q@Ynk@0=c@hzf4KmD7f$4f$;rvvNpdDQ$o#?rRueOX`29W1B7^d8Dck<`j_NPX z<43O{=JN8o=69A>BNrnh=}5C*nwt}H6Ot9kK9hj)aWusTY?Qu|GR$#)%xIM%)UIY4 ziGD02YN>3syGTqc0%U0O9L7hzbso(bUtPE;iD(`yUoC(@_0N2so z_?=dntXjkwvCx{%t)UtkAC?>+6I;wHRW#F<Hlf%CQdaqmP-cjb^ z3DWXs5C3~gn{f=vIKjtXt?k{N)I6T7N<#G2&8Hxm2kpB*d}+V0x@Hj}lh>#913iKA z2oxc!4;n1_3U>+4jFc`!Z!{}Sa0TkdYYs;BXsP{(OMEOW0JDN`rEHv}OBw3vy0mdi zvP8d@)*UN`YRDh{$SmG24Ep)=FJX3+B(>xUH`PH1DP(fAz@R}34bVzPa)1T;siTte z+4@VGBu7mToSVV>r%#7Ax;qpogfZfDv3yqsDre`IR2f88r2R7p>lVZ(2T2eCCF9U z6MPha7!-Z$+NFiO%OY9iQt0$P_o%!E02##rxwuxIba^mrUPc)YZ(fDBtt)f$Xgt?U z_!0dCB<}E@gFaPSMd>ggm?i2Cm_y3_jr`k{)o%x3132?}UoI{URqePa!|Ern&R-`q zGR_&8hZDq?DGx%Vo76 zG*{-JucqbzpB(>q%BU4vY1iAW6v5-lE5O&;BM6Iw4vNJda}caXq-DsS)=hPF9Z7>0P#iXSl<2bJrN&&Y>O7 z2n$5Z*RoqTVe|}g_nt{|K_q!ZVLqco1z>Dl5uA-Ggrg>lhk7Iu35EM>DaSWcYL<_w z*H}O(r;QB1U3WP{-AVDzmJ)wAWRCJa%V^%Kf&(1jY?yC=)jFLd86b7?4GQW%{x~N* zm@js}<6kwnwpBxLm>$ncv-N=kQx-_hI+#_&v2ncUGO4`1w^K`1u)UkNz4OgKq_V_s zy<$wCT>r4BZYROwD=XdB#S2~fK$rbO)}`Lq*w#2uh4i`~$UYsbuPJv7XS}-lH`LFeJxC6RKgEB z#`uWvX~2H$pJD+|)QE7Vh~s!1Wnv;*eaJjqrBZNFq3vK*GA;8J4?;%8F5esK7Zr83 zE`T4-rv0&mZMck+1lf9LJG?h<%v@Z|THCqo8X3PpYU2sg2(e>fGiAOb`Q(|TOioK9 zvB+3g`JJW!G%Q+1)NLg3%ccq~qoDsLPV9S-5Oo~*p7+|fZ)bv+jMu%%3>0qmw1c_# zlETU~n|$@@&`J*a+%)K@EDrIUjDSMmYOHRt=ZIFB-AIa?^UzSd<=c$rw!^Ldd?k0~ z7thYZucsD=-=VcI4_}xMH@6u7dS|id^X=}Rf5axhDNKOIzqnI*>HHF~Keg?3|8D)@ z=}_)p{pjk$yN}+!tnj&P99d4=V8;s_UjhuIhIxji-9GPKxMAUd7RvNTkEm?`Y zda%TwQL|NF<6q+9G~`VaV=V5-utC`T{bprzfr*7J?lwIW>z%u+n=L|-_d;2}yt(%0 z^xL1)o#=*pp>6a>jDWwVrc3!}SvPows@Ip#m&EF-tKqKeQb`tLTc;FMguxnA$-IQb zC`m$1y|ZBd7WG{}x+{2iE1uNawjVM?sTxEmP`?{Z2fhdL`5;v8^78Fe;g_(E>#4ef zzT9tv)Ph)I2i>Y2_I)eK5^dCuOygodKRdfr&^2iJ*Dt-8$@?saP=agQ&^|5{;}z|d z0aWDoezlePD}44P5xXzBDw)=cztE0kvdn(ogF^E+I$I9mJZ4$h?u4V`?f|YiOw5BB zgJ+~E6jlN4|yk!3UZ4gCOYQ+p<; zc6o0IFd4m_2a@{$fD6m@=ziE5Ooe)_I3UT?o6{wZLkyO_Q$r-x(s%ty_W@aa?wr!E zYKMUxx+FwP3-O!(I(^Q1LEySd%Xp?la&@x*jzWYkxsBTJ$D?;{yGi?55G;O@?n{!%d%Ur79XX$BSS(s{TpjX0S9o&10V>GF`iW_^Ae+_4;Vm zRFc`}?fKh~g7L~kC(;F+Aa}LBt477@kNkl!Sd&GyA`g{zz-IEf3qP#2g%ZsOesASH z2BJw9yfoA(>(VG&ZB6#@KX8csJ(jKT*0e1%t@if=G!!1>Xdd1mB+4r#@Z?E*Q??oB z(lI5Mg2AF?lj1YOSTjA~#E~YwSyi#>`Jvv%2A>=m4hK9wn^lzD9U#imkx|R(*sDjL z*GQg9epgz)`u$VM*G{t*@z($fSZ9`y5F5JsK!`A$?t%G;uHNtNu%9VRj`}t!=3nqT zQ{m!d_OsV7-oLB(@x^DyNzQ^#K*b~Eh9eV0G9&XC4=5ku>RIz`wReE2&O(|<LNJ3b#KZKU6!NOy)@OMmRP4OX=$Nm_YsPl zq%5O;8rYnh$j?z?mXh?)bmC=LWX=z}k?c+U^poa3Vd)1C_{E;^36)#qglpxgP0=%2 zK3L^_MfURY1&n{bUi;5~_Zj-HUl0GssHQMzUTFX|-oI3ulY_tt$xX(1Y>d>Punuy? z&bxrc2(LasQ=J^n)2lE`^`&XK?}W3MN3?JehhF%G07HrH-*>0Q9;Il$*1%?NkDB$6 zRVa;_pQB&iEsH8aZ^d*rC|Dlc5PBV++y*-q6#U?xXC4L@1 znyeS&zO;lx5V!iXjO~n+Pf4T;f?H1wuKeMjNM;f+>+GZVWn16VO4n=G<~w?8tYm(- z`k3r>aZd0PN0+?3n*1H5$et7_5@gN^Hi90g?=>W=xVCKOF4u zextk@a)0iA^2K`9B9YC2_!#k?!0R`2{_5Xy@G)gOVhCyY{O{@#(!Jh|V5mVCxE5u{ z8&zFh>9*Pr_!qeYJHv;BaVb8os84)lW8iJ4b?U5Dlvjc2>Ga9Blo>ugB<(!BP37=g z@EQ%vh{)s0dynpQ@H`N{m-~b_|JC#JP(~R z7khCFuqcDgCSSg~nh!yg68ib>i}u?fgkstWrIe@l&z8LKx?Gi)d& zIt&bKHhsEr37Jy&+2xY_jnBG96xxtGU<tq8Z2V>?kFQ?%x%NMj6Dt>RTwR%=_N2!pmN@N*~ZO7D5(yI6Qj-SM2aXd7T_>XP_ zv6VG4J|HqY3vTWM%RYMa_{qKYF&QaPKEx?-zBU&yQlm+_-lOAd^$(4-sgisB{`NIZ zI^C9ATw0QV57F-KXr8gh+H_g10@f29awq3bl5Okvl64?aV3i!2e>%h}AXb7|jK(WG z;8?Beur4=Eb&g9GwVxiJUXH&iu}NQUUgI#a)qE@y!L1p>G$8R-&FoE0;(&kFMLrr3 zgu9hsOuzpVp9$+qWq(aC?&9#kHYC(fgz9V4lwLUK1Ey&eHSu?nuK!cJ5lMUqw5UqH z8O>{3TQm6(@@Srs+tzdy#EU#W{`y94Wc=pl{>AEnJn#~-Pp)k5ag*PE59>Imee(ya zR6lx3_SgM6>hQMFqH0CVhqurh{ds`Ft>JgxwFajYqAuG zt%uz9&X`7>2~NOgvf7*PB9zi8ycUlTiq%zOj~2bc5^e7!8_M}l=Z5~w&dt>zk+QLq zzW!D~DN@3K#`}2M+hoRPWyGGI-&6Te;bA22?N`jebv#jVsT8CT$df$r4^6WHO#)@a zx86?0ok1Kf6;k7#$NSU+KU|KiSzQHssd*|_3!b*5r?bBLz84@l?%BPl=Y;97&+(L- z7#X|wdtsk%z}++*U)>2ySY4pqqKRX_4WXkm{hRLPZMuHhCm>vff$!{a_;037c~K)@ zU$08vmg8HifX4dWm9>76ad1Cpb6d+;Us(|v9ra47jsK3Tr8amh&o~snKANLqc$6sZ zX3z9kM3Y+-uniZ(0pebvU>3ilW5D_I8kyvKME-j92xv1bxp~}eA-a=dF&t&oOUuR= znLU!uss3G8spya|_EayIxV>#NRSxUY7nE^d?azQswYRsE_KAR7!Q^CLTZyezP{e20 zrnDR7Md>7Ka`W8fT@`AWg!}8cZ_lnG;cs2vv0%b~uhVZZBrLHfEU*J5xs*3^;X}KY z*BPr@J@WAh8B4Cv-l5byq&{@Mx#J>iM>AgJRtWuofPX*)W<0w*)l9At&)2?>>lcQ> zC8pu|208Am20S&r{`dKO92~R`(rM0uVazza9Q{8lmIw2X0HNTKg5;v<7?JO*H*4IOHgl&apk(G_J8*A}ZMB5Z^ z!0c}#+bw5sF#KS1GCDfy>Xj=xq|zapgzRMY=Iwe zCZUx3#~J4I0ytY2{X$?gA{@fbW^L2;SN+}a0rf96=zke%?Bj8IHNB9?5B6i_<#yxu z8w=Dcp+Q(~slY$wL`7GI2x&RJ+_VpCOG_i!uX4h} z=gVQnF_F=vWAY5}QBwJ?B~F$BUF&PC7OTuDflj(vh3x7WKi+^KeK2KGAgzS5z3eZu zaJF^Q_&>D0WmuH$+BQ5hzyLF}0@5Xjq%?xSAPPteh)8!xHmLPTyJjk!sZ*fmjyTjU!IOKX!%-Ax*m8P5Wt9zTl*I@iPJ`Q1_^ zt!_L&kFW!PB_jp1eDOtsIpE^12!JL*XXnt3jL;+Np$`*ZQ4->mBE*D;@ZJ60W7p#$ z8RV7jxHPa5@%Q8a3H-r-O+wuG4D6*8e0Ez=26?o1WdBS{OHXgOn()#gzp04Qj7Hq>G13uKfC}k%NN+CBxE>&u*<{lJ`GC1?eyX4gNbt;0EWqO4aT@A|m4BUHtvs zsJ;ED{eApn=K~LqT}OPl>qhFL)NKJ<_qX=y#)n^9^YDYe2Yzam6^^3+Dd?Hs}(#TU{uYxbV-If+Sz#UGPNPfS>dNU>G=|Y-ZOkWIDJk@s=rdrX~8V!^2un%V5 zG&T_pqfyiVPzc$guj`1-Kj`Vz*f-FJ&%JS8pev%D)X|M{h!d=~wsnskLsxe8JE2B_ zPPvTh{b2VRG67{l0D-saw?B**`BT@$B_<}<>uRH=a8@1LM1ZZM7$!eIecj}KdZ9eI zjrm>BwVH3%(eJ76$AeOxE&na3W9OO^zWf+@bnuItlbeUrO==4Ji`ee0mbvWQp^uR(7SBD?mVPO*H=V2CP z6J!^=eUC%*UhA$UUK<4Bm0a^e)bCM-J2y)Jg!#f(=Hkn* zO4Sa@0md#iWf;D&e(Ln}K+C-SaXa^y1_@w)j%DqJ?b4!VlgYZvEkJ@ClG*ZA%ZG#f(#ca4m6b`x^Gx2enY zm|p61oUr10b)0)=LA`Tuyk))Xp>_9vOpFin`$P)3^-fPmPd{F?h|A1xbWg7XvU6A( zZK2sAKXim#%WF1nFihR&U=+s`+GrXLfp*vpOE*cH2IZSB9C_1ZwFryWy zrHp%Jrayqmsd;Fm+NA@P*VB-r_ObCx!aq`{|BIOeLMbF`FO(9r+BsPlK>kgG@f7XB zeY!~$5dvxED;O&JXV_%PPI37!hI3dX=IYlw7E2D(CI(tt&2$i;H_l zPVSDJ_Z?hsU0s?z3{YYFi_pJCS4q%cbxW*-$bCrh&9|j*!bOGyh2=8GwX+o@8NH09 z26dQ*e}d&($bMiI&~tKKIPgQ zey){y8u>M4JZPLenzjkO5RTo#(}(S7)2cK$kTx7-pZS zpEE80Aw^1mP9z?u)~>WQS%rm##g3q?VrS>%w*CI0xved#f{Q!*Xc%2`Wxe>j<`$pG zk+TzS_u|%wAg>_rojjQrN}*ciOr-YGne;80^i0O|EneHc`~eo#VJQ>L&e|4vZ;Gpw zSuSU84L3b+3kfk)vNf|YRsKBDp>wSmA^5!>Q@G01pznL>VKICo|2IhQc{rtN#XtBQ z+}~CGG%LC1y4-_e*NN7n&3!T?%ETh+?c1kGT)Gqje|*|dDR1AhDpuc8MzN_7%&}cA zSN8qlU&$&IoY8&!e(T^ze{X+(iAeqRs|W9Y3W##}={}r&$6h*WyeA$X@Zp^;?x|?h zK*aRKfVXwbqq4o98_C$w8M8v8QdZgSQBiSgyH}Uf?nzz7s95&aBv5r_$VbS^TllQ11|3yGs9*$;r6;JlJQIllB@1qa3D< z8+(&^7fb&Mwmo`<62+kXoFok-dYv1xZ7ny8E1;>Q&V+)#=ip%e!iITn-z5*YEy00- zbilq07)8;Q_fEs{sIEWxjrrjusgiuh!z1OrdfsPJ~mr*Och|tIJ(K`lCX!i%HyIhwy_8%B2wrW~L@x zM=4%h0hT}#+zn&D{)PveV=*wpMn+HIU8=rYza7GTaf?coUV?glJtd9gSz>Y~2dD4R zYMcO267s-j^`yo3+tJqjFS>fMX&XQhAgcd}l`5PFDu&C-iqzhrypid%u7ZrA>_c7g zBJCf36+>o`pcqyam>sbsn(!`~>jK8Hn@^>LTvgAZ-}usZH`_VE+%|c%K+pV&)JEvG zbZ}ElRp#`VsGv5oCn7weQ{FJS&grr2Sw~f2GEhO82O*?T3*}FnFLhv$Y|eGG$(S$p zpopzzIDM>63m=Z_juA;q`{oroJls6`}(rbU!?7XR3XF zglg;Vs8JN4{Xr^t@5tr(1U7Fj8YRsd{m^pb9?&3l*LF`IF-3yZa`XJ$TVRnw=9pTe)=8tSD_ zrSbW?9}!9WNeTupG=b{yNjE_853&8woZ-!chZn$I+<=+f0IggPVXb%> z;2>vUPC(w|bp#b$lmtW$Tl9FZ;-H)_a<3wTVj*$5FV_X3ZC-=!_LE-tXyQy3K)phw zpD;VtJlNs zWI~5f4{IiNWhJdey*I@6Gt!r~h0I=LP}k+s-LNXlu|So@PoC$IUu|BXAj+qt2^o|H zGXAx~hvz_YSV19TCZUeQ?J~Y{ctkO||5L?1EYqAx0qru<)sMKZ+{mNZXD0nOBC@7_ zNmLBZ-%;I*+*zJ#h|!2=#!Mm5DA~v-j^SZ0un4E^xY>A;Wg}ywSu?XmW6L>nt^2mw z_~%F`$~!b~utf?l?4_xkP@M4>W9|o>^yE$6>hqKI(#iEJ2{DR}e0;RxU$=~$x}K`K zH|3?&l1Ophc(WO`LG?6KnY$QoHZ>H-?Zw;poBu9 zy_qNqEbr-ZQ(TfIUvCe^pS8{lXRKw7u6tZ?fg9tnZj^}W&H?Jz%+*nJ3SL@ol|)^H zoO!AIR73U3^0RU{C3gF56hfopJ#bUqHDuAOM~>l&&f!m-@$p>B4k}=~F7o@15n)b| zyUzRNpPcyx%J~KO`8Kgzd%M3jcXjvnbhoy3wDz@h_qTU%n0yI+^!awMsJ%gKr9dq(t)+WT z!kcI(E`em;(%3|^(M6n>FXv;XS6Q}};S1^Q$!BjPDNEAt1ND0OpX7+Ko-Ui2e^-w$ zDE(0H}W&NjVYY-AA*yb@6yphyh-RR9Ok5 zFxc@*mGI}#l?_B$+{PrM9G>4md@}&2lf6@21VyGc!_Hzy7-qQG7GZPSem8Rars~9c@IZh7C1d5^A+=+%^N#mYjj{bAs%P!roS)6 zLc{U2-%2{S!{syi*k6OMjJN9LmK?Sl+#ahR6)g5`n0$*qh*WS?*v`%qI1*+mvqXmB z-6 zqZ0ss^bRhz#67_5kOrGEq9PyMlbK1RASVs5V7BneXe0E8hkddrwLlj(n;0b;4D2&Rn&Z4EbYL>-_q$(&;abSL+i)D=`cbb;-<_9Iwf( z5%YtO4F=T|$*S~Cp7Z13Sm)6@Gxdggnhzi0!4mK;{xhDW8=(1`vMf-BChuXxH$JJV zWpJc35^@ASEW_{QbktI#!sBEI>z0#Ej;=l>dW{tY8p=*W<{HxZy@6tH7oF_8IY|7+TYG7L&qZQREyLUu=UH$#-EnR(G?d?Rb<6f<(bTPPSq(z+DDj#E^DKZ~-*%gu%GLlT`~K)_?_ajE9{r)+ASyX)ZR&TqxyG5R z`d-p!cQ_L(;RP+(2Q~C>HTsW+wGu*jLB@Xfc0vk+#wRuW@#N8%a#BO1ykWB582=w? zEu_}OC~H2p7T{z!0uQp1kfu*BZ+$YJEFRC;7U2LjpAHc{y@vXH(fcwr<@3PD=fRie zEGjCh8UJfvqY;ybtq`9J=L!*&L{GO~li${0y7*uy(YC3%#p22`1P_R|92`XA!x$#~ z<(IoZbFKuH=#wSYSDzn|hu1&%=+Xb_yHWAAkJH3b6%N20euN_)I7sv)=>Ey^9v;IA zE*{<7$BW*GTe)TIxnZKoDWb`%_V%$P(|yWRfW;*vHKQb1n7@JT?c{v$Ndr4IEivlN zTe_mp+7r#GX;Fr;`g#d6h*1Xem(!P%R=KRM{4zZ91E{%`aDm|o65k0e{d6fW zcd&nHZ0N1{HqsJkc5s;+|L!6eLp#Cw$|wf=COhc~>OWFO@tl-l9G4eL24IAbG*ix) zCemJ=oXR%a&9ErQT?eNaeNCM*rv3@-G&tbEHwYJWtpz>2Uj?zkHp@@D*Q`~*&{Pd^ z$LlOGj08zBkeGx0{EVfUW-&#*Ts!`|U|x}d%!czL1%OC$g1oh!#lLBM_3ZJJ?UT5` zceX_jeZI6__%xRthbhvEyJ~OeRF;RF_4Rm{=#wNg-gaLpxV?<2;Z^K3Nc!R>ebRgP z{YSZ1UvJmG3K9(_I*52rMk3jbyZPdTacBCc-ubcf&ddO}SI~14hL}}913qJwOo<1O z^H;&aqd{pI>1oMPp~+E+Z-Ud)q7;#Vx?yh=!y=!1+A#|e(-1QhmVsz^i8)0m&COEd z!cpOGQQ^U)&Ar{&NAna55AvPwF}7&053j+2>3R=piSYy}?}X;2D=R+U+wc9V{rpV> zDycl;LrK89?&7ovkvw_6er#LFKlCVW>Ihki`+jhR(LZ{+` zUScnEqZvr&S7x}`yo?6kf5atDmvn!4G)bs$PVW1RF!6^-HgCie!<$L<9N`n6dRfcU zD1!x;Vrq3cL+W%neLQJoXbSbZwl)#fuVck(ED7k_JkbZUU5GELWr zf%uw968yy857@!S$73(8B5beaZm*?cAk1SR%*5nwASrAi%qA(Rauk;2rusVFDZ2moR#az3*7h z?fJCuv#+G1jw1DCM}dNlhj}7^WcqcU&exKp;H#{#*tNK;WMD3hsD%B;LZ^+v&onFj zdg3l;!!1~MVi-{Woox9RORC?ozL5Kz^;|t~QK}j#Ex{}t8l<@{2;NVd=txobkgU83TgOrP_*L)N&o6`UPCoFgt~5B;xWDtxTt+?^BmJKJUC zWZo;ki`(y>mFxz6ngvU?1o{NX;RLDR;nA;9O7#0*6g7Vzy1-T4XL!)>e|T$Vj>X5D zXxBO1C316_G-)wu>RW?{)bW+C&F0cP`P|L8*`%qyKJL!;yUkrKa#T?kSdO2-4i-x$ z#yI_b-}+g)38R_a4OT1W@UaT($5b_@JVZ_kA-t-Z$N_oF9!u5d9d&6x`ws0~1$##l ze~~W=I`m9^c0V0PUB-WCeS!bIN8`Bh)z!qui6`Dz+@~AKX1p26jgH%!-(`SdO(B0v z^pJ(GSvP(toQE)AH^e_=75ycMREvIffZ1f;x=pAf1;sE#S3@qfiek0U{&p~2%@duk zNG^c8;yelpI(c=f{q_Yg<_C+~^^1Ol3QXE%909Ql9LEKwa zT-SO+@#5SwD0&yhut52M06Ud(yqH9ucKL?nSMNE3>!nW@xvIPGrMf&0@9nfN_{b3* zI6v8!r(Klr4%j)=Ia+*b@cri$2+$A}X%!VP5eSz-99Ie#S4y0+_+wel}IOjHQg7E}fgev&S-z z?`Nlt4bV&0`qw=2Q)q3Q=*m^p_{7pslH;{rV?Wj4cY2fvECHcya9e==cSt(7!J(|> z8ZnVhKV zqZtrhWmp+HFD!O974fOO%;^0G0I3&1Kw_W!wl&37=8IZ{Xh0;FR!{(=dIm2=}nrP!l1B z`*N%Bxp!YIv-mLQVh6=&2(md(%GTGuh+2SI_hpbmYwbBq|B2oyK0k_N&B#sQ_uL=wg zj*H4@V^va>J|iQs0%$M()46w9;}d(RWJ4dZsqd>crt8KzJGOb|+4&|P3eaAP>lfeK zLXWq&dsPe@rKZ&N1t-SVjjglp@D_5oC_i2A9uc@RlDL?O2nW398_c8N+ra< zSv`F`i#tmGl?~K?ouBDF$<%_w9NCA;X_O1Q&#tD_D=vNCQR}6z*T-N5N0^|I)mEU# z@YoGLtIJymf6+mbmz3UWt7{zIn+LRt_W5;j&~kJ}BFAZHqamUpVPhxc^6W9)XWaxVY}H&G6}xV82WMFs@i*d7&O$-@9bRdO35f;ot6CNAO(QwF`e){r@_ zG#z}Rk5pF2Nr2sZ%9yT=WBVmZEDWx2#g6Lu9q_4k)Q1p)aDfHHM=W<4rNsk?`f1-- zs6}5IXM)#eW@jMWz{-QZzSiE0xl!WB*f=U!C`oS>Lq&Z>Wzx=Jt2HIt!*6Kk|4ymoWf~Rz_FEzvp<2juISJu40GS73W%7+AbQI%v+dK zt(3(`K0rT3CE+3@gsZRz$Ih53XM$L9--H%}fTU41bGYf51!|*q2d^w_%y3 z8pbC-55Tn&Y|lgi(=3LmJO!ZUvbZFxRco}2J>0GBoL$_WvCmJ~ceZJmN23*TkLXF3 zR_?o>Ancfo*IG_Qc%^pi3SjP)Fz2$v%6pYV&uWG>g*4;qefGyEZhTu;>>7~Nxtu#M z&EV?8o=nrV=AdHlrskMVs&{Jd86o=bt{)wW;VEhNO(IfKX(nlD`Vt}3RV*{+PBbmh zoL$sd9nzRnkdLZPou|X4`LvPDYar=z)RC=NF;>bc-viP#bHGArHF1_^=Vf83sSiHW z8hx%IG&(FeGRi+TQZ6LuD#+(lRk`cpsuurdezHg9mB%h#6G_>MX+rsmfBpe!ck2EI z2S^PJ+I+=ee)85|M8v!yIZZVnSv7zk(npx}A}CTfg@3V^kn5G5EN-!k#t@n)Z=fj@ z2o;xNo*1Ov6Pi%8#Hke>c=uph{l%H%(N*C}MxQbPN&LqEx?Ji-M|AX$_9yPlma-zM0yq2U4u|_j$cu&-N9<>m!U+C`^#mg8Jo4_Yyc!E+(mk-rYatSn* zj9v+Pnu6}NR*>_6x&q8vzHD=766l=?N_GjO%{^Lbl(lytO~pKy*nTU~Q}-|$VI z3{mIH`P0UvGY13TyN;|ia&8laIn%fl1xibGV*y0|E7n(@?XTE}ghxX%#+HFWxjE7rBy-DPw#(8}8k=dQ zy|t=LD{wcX;I+MdT-z6Bxs#xQ-F5LUh21^9CLinChSRS3!$L~Ki-TN$g6a`m@fWz` zx?U+%qN_G3gp~2*0}DtD8}h5M|CR=j5h|99Te(dcgcISdu7A!Wd{+wq_sl#TraPb zPNJM=M<1BzU3h!4LwsMa6t!mhdIy=67}D%m`_~B`Y&{?FKfC(zzN7n|Wm-|!Ovb;GO73)`LihQ_%+ zKK^!07X3IF`&d!IK-5?3xs0-|vb3S%W97Z*#J4S{0>HuyN$=Cm&j8{1S?5eHS~*3S zUk`wMc6OruPxU(1LS}E>t!P^0zDj0%#ruf;NqBH-bqKT?dS-Htb9&^gF%8FF|B~_H zdjRg~U915_PPz1|KjGs8bp>`ePi$%P-rT!KUxbOc``a3hmI%9*teCc|N+&P9rv>EI zM&6nXrpQD(qJE;#aQazE)+WQ{{&K*?q5PLA&+0!tHat4s5@!JEE@pW@z=`kkHt7GLoe^(iMm>mm~7I zmEC>_#@KI82#B|#$htQIo8OQ5703GXs#$KbJ~Z%q0!y&Q%BY` zsnt`(rswWy>oyYa(l7STOD%T@Wk3Ho)1jKMDjeS^sH3ui)=u_H-THfi($v;|WMy{+ zy>kaH0B2!Yz(t^gv57Y)eAhN#(7Bltn%}o0$02{EgpPSH?l<3>KlHpmy~%Gb{&0!2 z;&}-q6gu_UZ=qyZxj9v)CvU2;*eQ7)Bc>1$vR<%UC)Sbr67u1T-iLRPbI-eVK4x1Y zczjVg1P?T1)9-%#KK_Mhw$J;@d;n7aYP7MbrH8X0KKz#QhE?|kArF0=KaBvnQE$Qy zqEtC#dDhZ$#oQEYx2)Wm|C7r-7pHp%`(>Wuy8ThaeFVR<+JhgYX4rDyk*%8uQEN2I zbtuWqa?@QNpZ@7i!KO-?3+(3E=(-}3G8)iPc*#FTUSinjz4WCxyno75?2@TY(Y9p+~!-j{!}@$KQ?S?d^tEdqVz5D5Mwp|DL=PPDo$ zgKhB7%%aE3-yBhnQJ7Kfx~2sQSz?gC{APH0ka4Z&d3xEnEQiS%<3HT|@wI(LZB7=t z{+@ILxh?r<7U%!wBph=?fCTTKex3ULpI6wcyxKz)kUM@yKcyUDd{9S|S&SN!1jMay z*3pPC(ZLM;H{OMn+zSQeP%0h1ChdKlh$|V=&4dV+8ru~B{jJwPVT&1^QPC6oamx%e zvPyX2liNf7-eYx8(CG^uZ>TPcDp1+ziJnitb`=3k`UxPJ?^-VJP6#xctJPa36W09{ zbmh(GmW~ni){4#BXgW2bBQM|=BU0TruCepHWs9)t&Ph;||4fFbQ~yrnl7c zLnEF9b6`@|sH6POCSl;A0puhEiNIR&-3lV?^~$6`T4E9BaA)Gs?B=M1L$?Au%8#Te zXsI=ug)fOwf9Gmw-jjlk!Ud13KD|Lv3`GlKeQOViYB69`=1yMsgDI@aB}mUXD0xkf zUEpb=BSiD?bK7NoST5sada~u zuJfqjlbClDn8%{~>zlh|zF&cpD4g%N}JXl<#6Ip;x6yFQ6A;JLUUwVD*Ha^ixY5Tg9H%7r0n z$FH?RbknnKg%-;+xoe6# zZOMEe{!D=xhTBr$3eyA`pv%`7hPjX&m{Pi}X*|TAv_76l-P;h9Nvk*nuXHw=!f=`U z-PA@X&-PU=$?{RbRoMsbYAb#YRX>KXqVHlhKT(z<&q;=CUg9V< z@{#6BjQ*>jk8y-wXEUMPmF|GBIuB{0f=lucx7DBqZ0dMBXmDg1in&w|@e2D#vU?dJIWdMp99%ZpE3jM8*b9iW%dQR;y>x6#GdH~tJvr;4vl_^l*Tw9rg% zJ3tWx!&JAeELBY9f%Dr4#NK*MRD@0A2F35q%EyRQNh5#PFwCkDmg6H2@V-K^*RO?d zsiCJ%K)JZ?Ds){}43Iyovth;VqUpLVtho-Gni>^?OK<%r7g~f=-uV(&Yuc+@m2G+fylZ>owr*Q;hmC{?meV3ryT0(J?=VgN9 zkHmwq&dqSOsH{Y`*g&bR<^Rv`1;&788eWym{S_w%e`^Z=bE{#h^Vd*eBLW|4%^W&6 zXni18PWyO0ayZjG*1&V0FA0YnAylAiiZwE9><|kIMU(GEVf|4l^RM;{C{OsaGG0)j zn-FIHb}h@+AOs}u9y>S{j!k{O429+lqiJn%B9z3n2o(?%;@EkhNAKL_%J7GA)0!b) zz1psVnEu`ajA}F$Lr$WUMZp$IbWv9akUy%R{uTIv5ZbT=%P9D#Z&)+jmDL}Lf9Q|x zks^H^iD*n$7wH2A6Jsv3f5Lx5;cIO&$_!j3sX&+r;h&H8 zk4Im7tJFw=-*1WTQE6WJ9*rgM=Bv|4k(RTyWkdTs}IE zDM7@~1OEYU{>f5hhlc=H!uxjW0Ip=s4Q+&cKvE$pxr3w+B^F*9aIM_DyONXqWg`Z)cMsNwTJpwil{5QjciJ*O*AHe4iEqE zns)Ux`{4$f1brcLapD0NT&`$RMTfrkJ-2~e>g4^3L$LUP|Dc3To7eIT{uw8$;(2N> zU(bVIZzUWyUOk5!UgC{blKT;2Zu;76Kwt@#NYC`z6Q8-IrjTR#7=uBmApuEaIgC*- zu)9(p;%kO`NoZwOTyDsvoeNQ;Ke-j;t@d!s8l*jGb*K^ByeD(TS#}5y#vxMvrP=wS zJjb9OS?r&HkTFnMs!7wq#U%c59r7`9931F()M2X=RvMzCaI#eq>OVNK-^8_XpqU!e zNFWNBF?)?`G0;qqCu8HO?cfXWb9L|$fzM6k6M=iCv)?cv+2%t5wSat%Jka7TvwR}L z$L?x`X8G|^QQtrt*;>u(rmScpQNg^|TR0W7+UR;nTA2!5Q3LFadbp{C=wXBhrB%0H z69Wj36~?a#F^UygfA=oa0wrkr@%u1#2MrIVsm}(78Yn#|svH?zDw0QwNMH^Wmqci9 zCJdeNZIOWFk&H6Mpbu2+nWJAp!Hi+X)+G1K@$lVSy30w;LGBO?1j1NNUWlNh1L048 z2d^$lr+T#_qOC>1=vjPxK~dJSL}50Kehsz# zMWEtgcsY#S8Zr+$T6=c#!Ibjcc4ZwP^1KGTn-kW@?I0BbSh=9D4Y~=;impL#$%9JC ztyZz+l0b;2h&V+ur-tFL5nW|uHpv6oeAtQ!Rc!rv97xDU53 zH~@MC^%QHKxlP%4MtU)Z4c~gVJO(?glSPBI&xft)jXc(+ei?050pZV-8GCAvW^UdLC?021gLLPo(GKAQyvJYJ z7oc~CZL>f%luhxKO=c1&ZU^dT`9sIwWUPo*9Oee@Q&+VB>ZpF*ZehSRlLp3J!wXfF zX8|ek;BPZ_IHGjR@k9o{N(s$3gBZL@15oxQ2Y;R8eK=jNg zGet}cNtayk8PgRg_$z3P6*GPd+szQ~&lo0E_lgpco^Luh%tQ5+e?1@(*_d%wadVsw zcyiVAj_&d$k@X=g90d+Wv!VXVw&nmKioK z-n@Av@9w!uUK;}b0D)0#WnkZ}0fWX5IjEHW2VSss&||30GI0EpgOg~ z97Tb^0a;Puk!TTtSeu8-JEZUDL5XmK=sCgP0*FGJ__Y|sd9>bE)7s!B1Om6`tcklB z@Gfu7%HoI=BaRih;F}av(<#hNTIlkJ7cKyFTw~EfY#1Jp8p)|Kcmth{hXLn5M&ZK7 z1JvY>U$2W1Lj~N+`IXimQwX@=WQ>t(cR`zB;rv>JpVR>_OO5bWAyyU@CoVEDfT9fh zR~RFqCXEeD5VB3k*sX1&4c8U|jR$QJf$l&nX>KEw9UnPDwC^LUoe0+R=YO9PMwSCl zO~p;&j0t2bR|N45W0CNBLdS@nIj+JY0dXZf?J2(p4;EL-Uu)w+x*J|VqUdl0BNK

    mEW?jjf>(oy;h&7B)SZlX_Q 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 + + + +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 0000000000000000000000000000000000000000..c8d842af182064f1dff931f8b7aef38733643d16 GIT binary patch literal 2698 zcmaJ@X;c&E8lI4gS2h7d6eVa#1ld9smOvyVkOV|Yh$JX=K|(S>z)3NV!K%IIp6mU7%)IY+p67YjIcKu?ydX2< z#l`>tm<6w7hXDYBueTlMBK6N=n)?^}*CLn`0Sn|YaEeF?0{#+tG>8p`M6qBPD3WLr zuY9XE2R9*31m=(u&B zToPBo0^_7BQ=3N5{p%u!2S;rlh2}rMSDvm9a#!kB`r+28BYK*gG8O)V z0vl9`l~M&PmCLZRilS(F5=_VGGyS^+Nbyltrh4BdeZz=qk%CBeBh8ld0m$Y4e<%cf zM62L1@SlADr?5(>QGmoSP$f@NiuHqw!Ow_8EpX|D@h~ zuH@6Xczlve)F(rn9qfM$`oooeduGj#YpZuYEh^P6=i_HXblQsI;*)oJ?3!x;|bpW*8%J+@}dD?PEj!1U#RXGTHl>I;B4`|kwwwdKMSY$J98kXYhy9>W>ibZ z=);B2#PE!+(KVT!H8EoS!*pg#_~aM7m02dpKkcj9!vCXedGnLs9X;Vwh zBagiJ?Vy)1%5kXZ4wo6*A3#KXvl_GJ!o9I0nMQ;KT8F8qUv_%tE3Gi**wYCyoyzX5 zy4J?s32m?JH=f!fA5BiEoY%OMw_rxn$LUm7YxZ_CEaVp)a*qxt7OI4}`}a-|#;i5L zDOC+-QIXzFae%NXA8Oa0Z4%`ixQ97LMr?^e>b#i6mObHnw)pC1-r7H*yvg?VrDwmn zRxrV`Vxg&b!PshEeado5!_5cgLTd9O9Yg*5bk~>+uVmFnUUP)iZsoACtq+emsn1pn!oKvK8zmigub`|l*1|>|c^3!Qs_Pm}Fpm~-Z7wOB z&Q&>%ala0&!EY5d;B}&OYZw1v4R9{ge?`}6b7};@$=RDs-!SjNMZrk|m?XAi9_Ty& zBC0qNprN{(_BdZlyS^r<*fW0tOuaErZJBB4^y^PsNpEz^wx0dYlHX}?>-g{;qZsA| zpAo3s+{Q%in-=LB+nB~QsZK*Y4S8eD-V7{X=xu=Rf3?}zrqO7-6EdghX`o#IA9moGJlU!XZvHdt1A;@MhB;l7R%48o-yaWrd0 z@D0*sq4(e>DAUnT`n7FMrtX;nLZHCz{k5Y2+X~-n*?jcnq_&?!xOaZeQi6+lUt4}# z$^#8*YJNxF%Z+|JdLA3*bZIy?wk57*t`SZ6eEvbMfivD_&8>*uc!3-k#B2a+Q(foT z?L{nf9s(G-Bz!ax>IJg>u425*-pw;lSn z>+)+%_+7=Sy)gOE-X21O(HRLs>AU#E3;$Pb=lokBVtXNiR=;F%l-1C!YPX^*^+)Ms!4l=IK5zYTmgX|zbS(JA5z;Sm< zsu}7J%cj~L?U4`1-4p2#3w)N>HhhUO`*Ge5eB{wvYvO-$D)|}RD6sAZ2D^x%%?Pk` zg*MgkA&U8W{Am3d53#tWoOplCH-T@JUE27$dUM@D&D0Tm^~#K!QTu$#<13FXlV8rD z?o!$68fg)6MwllAW>brEmG)<=8{1pew^yf1X1qMCRyG8S@Elz<6@tTdrWM-K>PZYwVbX!J40>3Qx==mO{Mmv^mvcJZ$$$)?{uM&%DbPFV~rr})5IO44~7%X@CO z@0#uk<)entPoRab-fT#}H{D_ + + +<% +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 0000000000000000000000000000000000000000..3fe9d7832fa631f02f02f962122d0c004184b64b GIT binary patch literal 10165 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 BJl+5R literal 0 HcmV?d00001 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/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/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 From fc580eb0e99c451ea6e3f09303d2e3668e52b27a Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 14 Feb 2017 11:39:37 -0800 Subject: [PATCH 034/107] Updated copy license for test --- .../Framework/EnvironmentVariableTestConditionAttribute.cs | 2 +- test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs | 2 +- test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs | 2 +- test/AspNetCoreModule.Test/Framework/TestUtility.cs | 2 +- test/AspNetCoreModule.Test/Framework/TestWebApplication.cs | 2 +- test/AspNetCoreModule.Test/Framework/TestWebSite.cs | 2 +- test/AspNetCoreModule.Test/FunctionalTest.cs | 2 +- test/AspNetCoreModule.Test/FunctionalTestHelper.cs | 2 +- test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs | 2 +- test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs | 2 +- test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs | 2 +- test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs | 2 +- .../WebSocketClientHelper/WebSocketClientHelper.cs | 2 +- .../WebSocketClientHelper/WebSocketClientUtility.cs | 2 +- .../WebSocketClientHelper/WebSocketConnect.cs | 2 +- .../WebSocketClientHelper/WebSocketConstants.cs | 2 +- .../WebSocketClientHelper/WebSocketState.cs | 2 +- .../ImpersonateMiddleware.cs | 2 +- test/AspNetCoreModule.TestSites.Standard/Program.cs | 2 +- test/AspNetCoreModule.TestSites.Standard/Startup.cs | 2 +- .../StartupCompressionCaching.cs | 2 +- test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs | 2 +- .../StartupNtlmAuthentication.cs | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs b/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs index c8691bc91a..8e17664271 100644 --- a/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs +++ b/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Testing.xunit; diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index aba16ea281..c7ec62efb9 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCoreModule.Test.HttpClientHelper; using Microsoft.Web.Administration; diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index f72d57c4b3..07aafb1264 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.IO; diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index f0422d2aeb..6b05d5b74f 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Text; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs index d3d041effa..49d6c2fa16 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.IO; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index 504962b5ec..b6863185de 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.IO; diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 9a8b2ee960..ac152d3784 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCoreModule.Test.Framework; using Microsoft.AspNetCore.Testing.xunit; diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index d1cc5e0f24..34fcf05e55 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCoreModule.Test.Framework; using System; diff --git a/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs b/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs index b776fd130a..2266931c5b 100644 --- a/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs +++ b/test/AspNetCoreModule.Test/HttpClientHelper/HttpClientHelper.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCoreModule.Test.Framework; using System; diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs index f4bde38b26..d6c987fa4b 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace AspNetCoreModule.Test.WebSocketClient { diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs index c3033ade4b..50fd11aca2 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/FrameType.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace AspNetCoreModule.Test.WebSocketClient { diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs index 4f9f78f75f..f42edd644c 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frames.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Text; diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs index 1410bda6e8..401818ce99 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCoreModule.Test.Framework; using System; diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs index 7c484af7de..4c2e728437 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientUtility.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Linq; diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs index df0e99789b..69cd3fd5a6 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections.Generic; diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs index 413b0a3c69..ba5a43daf0 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConstants.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace AspNetCoreModule.Test.WebSocketClient { diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs index 79a711fa56..b9a4d8c5db 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace AspNetCoreModule.Test.WebSocketClient { diff --git a/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs b/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs index dd0c68166a..d3b9aa8b01 100644 --- a/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs +++ b/test/AspNetCoreModule.TestSites.Standard/ImpersonateMiddleware.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using System.Security.Principal; diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index 2bcffdbb43..8444f54020 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index ab01b39ab8..149b4b5429 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs b/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs index d7db198bab..fb22ff3c24 100644 --- a/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs +++ b/test/AspNetCoreModule.TestSites.Standard/StartupCompressionCaching.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs b/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs index 6e64632365..7399bac55d 100644 --- a/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs +++ b/test/AspNetCoreModule.TestSites.Standard/StartupHelloWorld.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs index 034b27e1f8..c29fa05332 100644 --- a/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs +++ b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Builder; From cc29517ef39622cf397b7fcb92bfc548afb33657 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 24 Feb 2017 17:52:16 -0800 Subject: [PATCH 035/107] Add https client ceritificate mapping test (#74) --- .gitignore | 2 +- .../Framework/IISConfigUtility.cs | 239 ++++++++++- .../Framework/TestUtility.cs | 47 ++- .../Framework/TestWebApplication.cs | 23 +- .../Framework/TestWebSite.cs | 17 +- test/AspNetCoreModule.Test/FunctionalTest.cs | 23 + .../FunctionalTestHelper.cs | 297 ++++++++++--- test/AspNetCoreModule.Test/project.json | 5 +- .../Program.cs | 34 +- .../Properties/launchSettings.json | 5 +- .../TestResources/testCert.pfx | Bin 0 -> 2483 bytes tools/certificate.ps1 | 397 ++++++++++++++++++ tools/httpsys.ps1 | 394 +++++++++++++++++ 13 files changed, 1392 insertions(+), 91 deletions(-) create mode 100644 test/AspNetCoreModule.TestSites.Standard/TestResources/testCert.pfx create mode 100644 tools/certificate.ps1 create mode 100644 tools/httpsys.ps1 diff --git a/.gitignore b/.gitignore index e3bd9b7abe..9d24973eeb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,4 @@ src/AspNetCore/aspnetcore_msg.rc src/AspNetCore/version.h .build -AspNetCoreModule.VC.db \ No newline at end of file +*.VC.*db \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index c7ec62efb9..f727b958f4 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -4,8 +4,8 @@ using AspNetCoreModule.Test.HttpClientHelper; using Microsoft.Web.Administration; using System; +using System.Collections.ObjectModel; using System.IO; -using System.Management; using System.ServiceProcess; using System.Threading; @@ -260,6 +260,40 @@ namespace AspNetCoreModule.Test.Framework } } + 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; + 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); @@ -924,14 +958,205 @@ namespace AspNetCoreModule.Test.Framework } } - public void AddBindingToSite(string siteName, string Ip, int Port, string host) + public string CreateSelfSignedCertificateWithMakeCert(string subjectName, string issuerName = null, string extendedKeyUsage = null) + { + string makecertExeFilePath = "makecert.exe"; + var makecertExeFilePaths = new string[] + { + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.1", "bin", "x64", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.1", "bin", "x86", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.0", "bin", "x64", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.0", "bin", "x86", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows SKDs", "Windows", "v7.1A", "bin", "x64", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows SKDs", "Windows", "v7.1A", "bin", "makecert.exe") + }; + + foreach (string item in makecertExeFilePaths) + { + if (File.Exists(item)) + { + makecertExeFilePath = item; + break; + } + } + + 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 System.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 System.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 System.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 System.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 System.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 System.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 System.ApplicationException("Failed to delete certificate, output: " + output); + } + } + } + + public void AddBindingToSite(string siteName, string ipAddress, int port, string host, string protocol = "http") { string bindingInfo = ""; - if (Ip == null) - Ip = "*"; - bindingInfo += Ip; + if (ipAddress == null) + ipAddress = "*"; + bindingInfo += ipAddress; bindingInfo += ":"; - bindingInfo += Port; + bindingInfo += port; bindingInfo += ":"; if (host != null) bindingInfo += host; @@ -944,7 +1169,7 @@ namespace AspNetCoreModule.Test.Framework { SiteCollection sites = serverManager.Sites; Binding b = sites[siteName].Bindings.CreateElement(); - b.SetAttributeValue("protocol", "http"); + b.SetAttributeValue("protocol", protocol); b.SetAttributeValue("bindingInformation", bindingInfo); sites[siteName].Bindings.Add(b); diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index 6b05d5b74f..b688582dc8 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -16,6 +16,9 @@ using System.Security.AccessControl; using System.Net.Http; using System.Threading.Tasks; using System.Net; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Runspaces; namespace AspNetCoreModule.Test.Framework { @@ -216,7 +219,7 @@ namespace AspNetCoreModule.Test.Framework } return false; } - + public static void GiveWritePermissionTo(string folder, SecurityIdentifier sid) { DirectorySecurity fsecurity = Directory.GetAccessControl(folder); @@ -696,6 +699,48 @@ namespace AspNetCoreModule.Test.Framework return result; } + public static string RunPowershellScript(string scriptText) + { + IPEndPoint a = new IPEndPoint(0, 443); + + // create Powershell runspace + Runspace runspace = RunspaceFactory.CreateRunspace(); + + // 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 int RunCommand(string fileName, string arguments = null, bool checkStandardError = true, bool waitForExit=true) { int pid = -1; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs index 49d6c2fa16..3e6498e6dc 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs @@ -101,21 +101,30 @@ namespace AspNetCoreModule.Test.Framework } } - public Uri GetHttpUri() + public Uri GetUri() { return new Uri("http://" + _testSite.HostName + ":" + _testSite.TcpPort.ToString() + URL); } - public Uri GetHttpUri(string subPath) + public Uri GetUri(string subPath, int port = -1, string protocol = "http") { - string tempSubPath = subPath; - if (!tempSubPath.StartsWith("/")) + if (port == -1) { - tempSubPath = "/" + tempSubPath; + port = _testSite.TcpPort; } - return new Uri("http://" + _testSite.HostName + ":" + _testSite.TcpPort.ToString() + URL + tempSubPath); - } + 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 { diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index b6863185de..a946fc49d3 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -66,6 +66,19 @@ namespace AspNetCoreModule.Test.Framework } } + public string _postFix = null; + public string PostFix + { + get + { + return _postFix; + } + set + { + _postFix = value; + } + } + public int _tcpPort = 8080; public int TcpPort { @@ -103,11 +116,12 @@ namespace AspNetCoreModule.Test.Framework // 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++) { - string postfix = Path.GetRandomFileName(); + postfix = Path.GetRandomFileName(); siteName = loggerPrefix.Replace(" ", "") + "_" + postfix; siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", siteName); if (!Directory.Exists(siteRootPath)) @@ -174,6 +188,7 @@ namespace AspNetCoreModule.Test.Framework // Initialize member variables _hostName = "localhost"; _siteName = siteName; + _postFix = postfix; _tcpPort = tcpPort; RootAppContext = new TestWebApplication("/", Path.Combine(siteRootPath, "WebSite1"), this); diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index ac152d3784..52f5fa72ad 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -289,5 +289,28 @@ namespace AspNetCoreModule.Test { return DoCachingTest(appPoolBitness); } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + public Task SendHTTPSRequestTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + return DoSendHTTPSRequestTest(appPoolBitness); + } + + [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [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); + } } } diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 34fcf05e55..09b3cb0ce7 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -41,7 +41,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); 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", "")); @@ -50,7 +50,7 @@ namespace AspNetCoreModule.Test var httpClientHandler = new HttpClientHandler(); var httpClient = new HttpClient(httpClientHandler) { - BaseAddress = testSite.AspNetCoreApp.GetHttpUri(), + BaseAddress = testSite.AspNetCoreApp.GetUri(), Timeout = TimeSpan.FromSeconds(5), }; @@ -73,7 +73,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(1000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -99,7 +99,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(1000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -131,7 +131,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(1000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; @@ -163,7 +163,7 @@ namespace AspNetCoreModule.Test Thread.Sleep(500); string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; - string backendProcessId = await GetResponse(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; @@ -196,7 +196,7 @@ namespace AspNetCoreModule.Test Thread.Sleep(1000); string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; - string backendProcessId = await GetResponse(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; @@ -221,8 +221,8 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(500); - string totalNumber = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetEnvironmentVariables"), HttpStatusCode.OK); - Assert.True(totalNumber == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetEnvironmentVariables"), HttpStatusCode.OK))); + string totalNumber = await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK); + Assert.True(totalNumber == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK))); iisConfig.SetANCMConfig( testSite.SiteName, @@ -237,7 +237,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); int expectedValue = Convert.ToInt32(totalNumber) + 1; - Assert.True(expectedValue.ToString() == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetEnvironmentVariables"), HttpStatusCode.OK))); + Assert.True(expectedValue.ToString() == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK))); iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestBar", "bar" }); Thread.Sleep(500); @@ -245,8 +245,8 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); expectedValue++; - Assert.True("foo" == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ExpandEnvironmentVariablesANCMTestFoo"), HttpStatusCode.OK))); - Assert.True("bar" == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ExpandEnvironmentVariablesANCMTestBar"), HttpStatusCode.OK))); + Assert.True("foo" == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariablesANCMTestFoo"), HttpStatusCode.OK))); + Assert.True("bar" == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariablesANCMTestBar"), HttpStatusCode.OK))); } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -270,11 +270,11 @@ namespace AspNetCoreModule.Test Thread.Sleep(1100); // verify 503 - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); // rename app_offline.htm to _app_offline.htm and verify 200 testSite.AspNetCoreApp.MoveFile("App_Offline.Htm", "_App_Offline.Htm"); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); 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); @@ -305,11 +305,11 @@ namespace AspNetCoreModule.Test // verify 503 string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; - await VerifyResponseBody(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); + await VerifyResponseBody(testSite.RootAppContext.GetUri(urlForUrlRewrite), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); // delete app_offline.htm and verify 200 testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); - string backendProcessId = await GetResponse(testSite.RootAppContext.GetHttpUri(urlForUrlRewrite), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); 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); @@ -333,7 +333,7 @@ namespace AspNetCoreModule.Test new KeyValuePair("TestData", testData), }; var expectedResponseBody = "FirstName=Mickey&LastName=Mouse&TestData=" + testData; - await VerifyPostResponseBody(testSite.AspNetCoreApp.GetHttpUri("EchoPostData"), postFormData, expectedResponseBody, HttpStatusCode.OK); + await VerifyPostResponseBody(testSite.AspNetCoreApp.GetUri("EchoPostData"), postFormData, expectedResponseBody, HttpStatusCode.OK); } } @@ -359,7 +359,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "disableStartUpErrorPage", true); iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processPath", errorMessageContainThis); - var responseBody = await GetResponse(testSite.AspNetCoreApp.GetHttpUri(), HttpStatusCode.BadGateway); + var responseBody = await GetResponse(testSite.AspNetCoreApp.GetUri(), HttpStatusCode.BadGateway); responseBody = responseBody.Replace("\r", "").Replace("\n", "").Trim(); Assert.True(responseBody == curstomErrorMessage); @@ -376,7 +376,7 @@ namespace AspNetCoreModule.Test // check JitDebugger before continuing TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); - responseBody = await GetResponse(testSite.AspNetCoreApp.GetHttpUri(), HttpStatusCode.BadGateway); + responseBody = await GetResponse(testSite.AspNetCoreApp.GetUri(), HttpStatusCode.BadGateway); Assert.True(responseBody.Contains("808681")); // verify event error log @@ -409,7 +409,7 @@ namespace AspNetCoreModule.Test DateTime startTimeInsideLooping = DateTime.Now; Thread.Sleep(50); - var statusCode = await GetResponseStatusCode(testSite.AspNetCoreApp.GetHttpUri("GetProcessId")); + var statusCode = await GetResponseStatusCode(testSite.AspNetCoreApp.GetUri("GetProcessId")); if (statusCode != HttpStatusCode.OK.ToString()) { Assert.True(i >= valueOfRapidFailsPerMinute, i.ToString() + "is greater than or equals to " + valueOfRapidFailsPerMinute.ToString()); @@ -418,7 +418,7 @@ namespace AspNetCoreModule.Test break; } - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -455,7 +455,7 @@ namespace AspNetCoreModule.Test for (int i = 0; i < 20; i++) { - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); int id = Convert.ToInt32(backendProcessId); if (!processIDs.Contains(id)) { @@ -487,7 +487,7 @@ namespace AspNetCoreModule.Test for (int i = 0; i < 20; i++) { - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); int id = Convert.ToInt32(backendProcessId); if (!processIDs.Contains(id)) { @@ -521,11 +521,11 @@ namespace AspNetCoreModule.Test Thread.Sleep(500); if (startupTimeLimit < startupDelay) { - await VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("DoSleep3000"), HttpStatusCode.BadGateway); + await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep3000"), HttpStatusCode.BadGateway); } else { - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri("DoSleep3000"), "Running", HttpStatusCode.OK); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep3000"), "Running", HttpStatusCode.OK); } } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -543,11 +543,11 @@ namespace AspNetCoreModule.Test if (requestTimeout.ToString() == "00:02:00") { - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri("DoSleep65000"), "Running", HttpStatusCode.OK, timeout:70); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep65000"), "Running", HttpStatusCode.OK, timeout:70); } else if (requestTimeout.ToString() == "00:01:00") { - await VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("DoSleep65000"), HttpStatusCode.BadGateway, 70); + await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, 70); } else { @@ -573,8 +573,8 @@ namespace AspNetCoreModule.Test new string[] { "ANCMTestShutdownDelay", "20000" } ); - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); // Set a new value such as 100 to make the backend process being recycled @@ -585,8 +585,8 @@ namespace AspNetCoreModule.Test var difference = endTime - startTime; Assert.True(difference.Seconds >= expectedClosingTime); Assert.True(difference.Seconds < expectedClosingTime + 3); - Assert.True(backendProcessId != await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK)); - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + Assert.True(backendProcessId != await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK)); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -605,7 +605,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogFile", @".\logs\stdout"); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); 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")); @@ -615,7 +615,7 @@ namespace AspNetCoreModule.Test // verify the log file is not created because backend process is not recycled Assert.True(Directory.GetFiles(logPath).Length == 0); - Assert.True(backendProcessId == (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK))); + Assert.True(backendProcessId == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK))); // 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); @@ -630,7 +630,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); - Assert.True(backendProcessId != (await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK))); + Assert.True(backendProcessId != (await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK))); // 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)); @@ -647,7 +647,7 @@ namespace AspNetCoreModule.Test using (var iisConfig = new IISConfigUtility(ServerType.IIS)) { string arguments = argumentsPrefix + testSite.AspNetCoreApp.GetArgumentFileName(); - string tempProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string tempProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); var tempBackendProcess = Process.GetProcessById(Convert.ToInt32(tempProcessId)); // replace $env with the actual test value @@ -669,7 +669,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); Thread.Sleep(500); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); } @@ -685,7 +685,7 @@ namespace AspNetCoreModule.Test { string result = string.Empty; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); - string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("DumpRequestHeaders"), HttpStatusCode.OK); + string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); iisConfig.EnableWindowsAuthentication(testSite.SiteName); @@ -696,13 +696,13 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); Thread.Sleep(500); - requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("DumpRequestHeaders"), HttpStatusCode.OK); + requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); if (enabledForwardWindowsAuthToken) { string expectedHeaderName = "MS-ASPNETCORE-WINAUTHTOKEN"; Assert.True(requestHeaders.ToUpper().Contains(expectedHeaderName)); - result = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ImpersonateMiddleware"), HttpStatusCode.OK); + result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); bool compare = false; string expectedValue1 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERDOMAIN%") + "\\" + Environment.ExpandEnvironmentVariables("%USERNAME%"); @@ -723,7 +723,7 @@ namespace AspNetCoreModule.Test { Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); - result = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("ImpersonateMiddleware"), HttpStatusCode.OK); + result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); Assert.True(result.Contains("ImpersonateMiddleware-UserName = NoAuthentication")); } } @@ -740,10 +740,10 @@ namespace AspNetCoreModule.Test { // allocating 1024,000 KB - await VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("MemoryLeak1024000"), HttpStatusCode.OK); + await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("MemoryLeak1024000"), HttpStatusCode.OK); // get backend process id - string pocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string pocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); // get process id of IIS worker process (w3wp.exe) string userName = testSite.SiteName; @@ -777,7 +777,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "rapidFailsPerMinute", 100); Thread.Sleep(3000); - await VerifyResponseStatus(testSite.RootAppContext.GetHttpUri("small.htm"), HttpStatusCode.OK); + await VerifyResponseStatus(testSite.RootAppContext.GetUri("small.htm"), HttpStatusCode.OK); Thread.Sleep(1000); int x = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); @@ -788,14 +788,14 @@ namespace AspNetCoreModule.Test // check JitDebugger before continuing foundVSJit = TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); - await VerifyResponseStatus(testSite.RootAppContext.GetHttpUri("small.htm"), HttpStatusCode.OK); + await VerifyResponseStatus(testSite.RootAppContext.GetUri("small.htm"), HttpStatusCode.OK); Thread.Sleep(3000); } int y = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); Assert.True(x == y && foundVSJit == false, "worker process is not recycled after 30 seconds"); - string backupPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backupPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); string newPocessIdBackendProcess = backupPocessIdBackendProcess; // Verify IIS recycling happens while there is memory leak @@ -805,9 +805,9 @@ namespace AspNetCoreModule.Test foundVSJit = TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); // allocating 2048,000 KB - await VerifyResponseStatus(testSite.AspNetCoreApp.GetHttpUri("MemoryLeak2048000"), HttpStatusCode.OK); + await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("MemoryLeak2048000"), HttpStatusCode.OK); - newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); if (foundVSJit || backupPocessIdBackendProcess != newPocessIdBackendProcess) { // worker process is recycled expectedly and backend process is recycled together @@ -834,7 +834,7 @@ namespace AspNetCoreModule.Test z = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); Assert.True(x != z, "worker process is recycled"); - newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); Assert.True(backupPocessIdBackendProcess != newPocessIdBackendProcess, "backend process is recycled"); } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -876,29 +876,29 @@ namespace AspNetCoreModule.Test string result = string.Empty; if (!useCompressionMiddleWare && !enableIISCompression) { - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("foohtm"), "verify response body"); Assert.False(result.Contains("Content-Encoding"), "verify response header"); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("barhtm"), "verify response body"); Assert.False(result.Contains("Content-Encoding"), "verify response header"); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("defaulthtm"), "verify response body"); Assert.False(result.Contains("Content-Encoding"), "verify response header"); } else { - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("foohtm"), "verify response body"); Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("barhtm"), "verify response body"); Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("defaulthtm"), "verify response body"); Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); } @@ -937,20 +937,20 @@ namespace AspNetCoreModule.Test string result = string.Empty; - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); string headerValue = GetHeaderValue(result, "MyCustomHeader"); Assert.True(result.Contains("foohtm"), "verify response body"); Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); Thread.Sleep(2000); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); string headerValue2 = GetHeaderValue(result, "MyCustomHeader"); Assert.True(result.Contains("foohtm"), "verify response body"); Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); Assert.Equal(headerValue, headerValue2); Thread.Sleep(12000); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetHttpUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); Assert.True(result.Contains("foohtm"), "verify response body"); Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); string headerValue3 = GetHeaderValue(result, "MyCustomHeader"); @@ -959,23 +959,184 @@ namespace AspNetCoreModule.Test testSite.AspNetCoreApp.RestoreFile("web.config"); } } - + + public static async Task DoSendHTTPSRequestTest(IISConfigUtility.AppPoolBitness appPoolBitness) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest")) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + string hostName = ""; + string subjectName = "localhost"; + string ipAddress = "*"; + string hexIPAddress = "0x00"; + int sslPort = 46300; + + // 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); + + // Verify http request + string result = string.Empty; + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.Contains("Running"), "verify response body"); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + result = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.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 DoClientCertificateMappingTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useHTTPSMiddleWare) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoClientCertificateMappingTest")) + { + using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + { + 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 = 46300; + + // 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 = TestUtility.RunPowershellScript("( Get-LocalUser -Name " + userName + " 2> $null ).Name"); + if (temp == userName) + { + temp = TestUtility.RunPowershellScript("net localgroup administrators /Delete " + userName); + temp = TestUtility.RunPowershellScript("net user " + userName + " /Delete"); + } + temp = TestUtility.RunPowershellScript("net user " + userName + " " + password + " /ADD"); + temp = TestUtility.RunPowershellScript("net localgroup administrators /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"); + iisConfig.EnableOneToOneClientCertificateMapping(testSite.SiteName, ".\\" + userName, password, publicKey); + + // 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)); + } + + // 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()"); + string expectedHeaderName = "MS-ASPNETCORE-CLIENTCERT"; + Assert.True(outputRawContent.Contains(expectedHeaderName)); + + // 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.True(outputRawContent.Contains(publicKey)); + + // Verify non-https request returns 403.4 error + string result = string.Empty; + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.Forbidden); + Assert.True(result.Contains("403.4")); + + // Verify https request without using client certificate returns 403.7 + result = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.Forbidden); + Assert.True(result.Contains("403.7")); + + // Clean up user + temp = TestUtility.RunPowershellScript("net localgroup administrators /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")) { DateTime startTime = DateTime.Now; - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); // Get Process ID - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetHttpUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); // Verify WebSocket without setting subprotocol - await VerifyResponseBodyContain(testSite.WebSocketApp.GetHttpUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server + await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server // Verify WebSocket subprotocol - await VerifyResponseBodyContain(testSite.WebSocketApp.GetHttpUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // echoSubProtocol.aspx has hard coded path for the websocket server + await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // echoSubProtocol.aspx has hard coded path for the websocket server // Verify process creation ANCM event log Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); @@ -983,7 +1144,7 @@ namespace AspNetCoreModule.Test // Verify websocket using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) { - var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetHttpUri("websocket"), true, true); + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); Assert.True(frameReturned.Content.Contains("Connection: Upgrade")); Assert.True(frameReturned.Content.Contains("HTTP/1.1 101 Switching Protocols")); Thread.Sleep(500); @@ -996,7 +1157,7 @@ namespace AspNetCoreModule.Test } // send a simple request again and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetHttpUri(), "Running", HttpStatusCode.OK); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); } } @@ -1129,7 +1290,7 @@ namespace AspNetCoreModule.Test private static async Task CheckChunkedAsync(HttpClient client, TestWebApplication webApp) { - var response = await client.GetAsync(webApp.GetHttpUri("chunked")); + var response = await client.GetAsync(webApp.GetUri("chunked")); var responseText = await response.Content.ReadAsStringAsync(); try { @@ -1256,14 +1417,14 @@ namespace AspNetCoreModule.Test string responseStatus = "NotInitialized"; var httpClientHandler = new HttpClientHandler(); - httpClientHandler.UseDefaultCredentials = true; + httpClientHandler.UseDefaultCredentials = true; var httpClient = new HttpClient(httpClientHandler) { BaseAddress = uri, Timeout = TimeSpan.FromSeconds(timeout), }; - + if (requestHeaders != null) { for (int i = 0; i < requestHeaders.Length; i=i+2) @@ -1293,7 +1454,7 @@ namespace AspNetCoreModule.Test else { response = await TestUtility.RetryRequest(() => - { + { return httpClient.PostAsync(string.Empty, postHttpContent); }, TestUtility.Logger, retryCount: numberOfRetryCount); } diff --git a/test/AspNetCoreModule.Test/project.json b/test/AspNetCoreModule.Test/project.json index 4fb9bb1370..bc9d8099d2 100644 --- a/test/AspNetCoreModule.Test/project.json +++ b/test/AspNetCoreModule.Test/project.json @@ -16,7 +16,8 @@ "Microsoft.Extensions.Logging.Console": "1.2.0-*", "Microsoft.Extensions.PlatformAbstractions": "1.2.0-*", "Microsoft.Net.Http.Headers": "1.1.0-*", - "xunit": "2.2.0-*" + "xunit": "2.2.0-*", + "System.Management.Automation.dll": "10.0.10586" }, "frameworks": { "net451": { @@ -28,7 +29,7 @@ "System.Net.Http.WebRequest": "", "System.Runtime": "", "System.ServiceProcess": "", - "System.Xml": "" + "System.Xml": "" } } } diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index 8444f54020..e02eeabd3b 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -2,16 +2,20 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration; using Microsoft.Net.Http.Server; using System; using System.IO; +using System.Security.Cryptography.X509Certificates; using System.Threading; namespace AspnetCoreModule.TestSites.Standard { public static class Program { + private static X509Certificate2 _x509Certificate2; + public static void Main(string[] args) { var config = new ConfigurationBuilder() @@ -22,7 +26,33 @@ namespace AspnetCoreModule.TestSites.Standard IWebHostBuilder builder = null; if (!string.IsNullOrEmpty(startUpClassString)) { - if (startUpClassString == "StartupCompressionCaching") + 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(options => + { + HttpsConnectionFilterOptions httpsoptions = new HttpsConnectionFilterOptions(); + httpsoptions.ServerCertificate = _x509Certificate2; + httpsoptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + httpsoptions.CheckCertificateRevocation = false; + options.UseHttps(httpsoptions); + }) + .UseStartup(); + } + else if (startUpClassString == "StartupCompressionCaching") { builder = new WebHostBuilder() .UseConfiguration(config) @@ -111,8 +141,8 @@ namespace AspnetCoreModule.TestSites.Standard } var host = builder.Build(); - host.Run(); + 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 index b4fe3e5a15..76176b9680 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json +++ b/test/AspNetCoreModule.TestSites.Standard/Properties/launchSettings.json @@ -4,15 +4,16 @@ "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:39982/", - "sslPort": 0 + "sslPort": 44375 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "launchUrl": "https://localhost:44375/", "environmentVariables": { - "ANCMTestStartupClassName": "StartupCompression", + "ANCMTestStartupClassName": "StartupHTTPS", "ASPNET_ENVIRONMENT": "HelloWorld" } }, diff --git a/test/AspNetCoreModule.TestSites.Standard/TestResources/testCert.pfx b/test/AspNetCoreModule.TestSites.Standard/TestResources/testCert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..7118908c2d730670c16e9f8b2c532a262c951989 GIT binary patch literal 2483 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 literal 0 HcmV?d00001 diff --git a/tools/certificate.ps1 b/tools/certificate.ps1 new file mode 100644 index 0000000000..1a19029ad9 --- /dev/null +++ b/tools/certificate.ps1 @@ -0,0 +1,397 @@ +# 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 F97AB75DCF1C62547E4B5E7025D60001892A6A60 -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 + } + + # if _exportToSSLStore points to a .pfx file + if ($exportToSSLStore.ToLower().EndsWith(".pfx")) + { + if (-not $_password) + { + return ("Error!!! _password is required") + } + + $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") + } + } + + Export-Certificate -Cert $cert -FilePath $tempExportFile | Out-Null + if (-not (Test-Path $tempExportFile)) + { + return ("Error!!! Can't export $TargetSSLStore\$_targetThumbPrint to $tempExportFile") + } + + # 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 + 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 From 5fadbcb329141a2eddd72e433032595a88a67950 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 27 Feb 2017 16:37:59 -0800 Subject: [PATCH 036/107] Upgrade to VS2017 (#76) --- AspNetCoreModule.sln | 20 +-- Build/repo.props | 6 + build.ps1 | 2 +- global.json | 3 - .../AspNetCoreModule.Test.ForVS.csproj | 155 ------------------ .../AspNetCoreModule.Test.xproj | 20 --- .../Framework/IISConfigUtility.cs | 1 - .../Framework/InitializeTestMachine.cs | 5 + .../Framework/TestUtility.cs | 2 +- .../FunctionalTestHelper.cs | 15 +- .../aspnetcoremodule.test.csproj | 51 ++++++ test/AspNetCoreModule.Test/project.json | 36 ---- .../project.json.compileerror.txt | 38 ----- .../AspnetCoreModule.TestSites.Standard.xproj | 18 -- ...aspnetcoremodule.testsites.standard.csproj | 21 +++ .../project.json | 46 ------ 16 files changed, 101 insertions(+), 338 deletions(-) create mode 100644 Build/repo.props delete mode 100644 global.json delete mode 100644 test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj delete mode 100644 test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj create mode 100644 test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj delete mode 100644 test/AspNetCoreModule.Test/project.json delete mode 100644 test/AspNetCoreModule.Test/project.json.compileerror.txt delete mode 100644 test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj create mode 100644 test/AspNetCoreModule.TestSites.Standard/aspnetcoremodule.testsites.standard.csproj delete mode 100644 test/AspNetCoreModule.TestSites.Standard/project.json diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index e2ab3a60d5..d18cf23d4b 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26224.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" ProjectSection(ProjectDependencies) = postProject @@ -12,16 +12,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{02F461DC-5166-4E88-AAD5-CF110016A647}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspNetCoreModule.Test", "test\AspNetCoreModule.Test\AspNetCoreModule.Test.xproj", "{4DDA7560-AA29-4161-A5EA-A7E8F3997321}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2097C03C-E2F7-4396-B3BC-4335F1B87B5E}" - ProjectSection(SolutionItems) = preProject - global.json = global.json - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FDD2EDF8-1B62-4978-9815-9D95260B8B91}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspnetCoreModule.TestSites.Standard", "test\AspNetCoreModule.TestSites.Standard\AspnetCoreModule.TestSites.Standard.xproj", "{030225D8-4EE8-47E5-B692-2A96B3B51A38}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreModule.Test", "test\AspNetCoreModule.Test\AspNetCoreModule.Test.csproj", "{4DDA7560-AA29-4161-A5EA-A7E8F3997321}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreModule.TestSites.Standard", "test\AspNetCoreModule.TestSites.Standard\AspNetCoreModule.TestSites.Standard.csproj", "{030225D8-4EE8-47E5-B692-2A96B3B51A38}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0EF45656-B25D-40D8-959C-726EAF185E60}" + ProjectSection(SolutionItems) = preProject + NuGet.Config = NuGet.Config + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Build/repo.props b/Build/repo.props new file mode 100644 index 0000000000..a56e75519b --- /dev/null +++ b/Build/repo.props @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index 8f2f99691a..0605b59c01 100644 --- a/build.ps1 +++ b/build.ps1 @@ -33,7 +33,7 @@ cd $PSScriptRoot $repoFolder = $PSScriptRoot $env:REPO_FOLDER = $repoFolder -$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" +$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip" if ($env:KOREBUILD_ZIP) { $koreBuildZip=$env:KOREBUILD_ZIP diff --git a/global.json b/global.json deleted file mode 100644 index 0c827e1b26..0000000000 --- a/global.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "projects": [ "test" ] -} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj deleted file mode 100644 index dd35d6f469..0000000000 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.ForVS.csproj +++ /dev/null @@ -1,155 +0,0 @@ - - - - - Debug - AnyCPU - {FB383E4C-A762-4FEA-BEFF-B3E6F4FA40C5} - Library - Properties - AspNetCoreModule.FunctionalTest - AspNetCoreModule.FunctionalTest - v4.5.1 - 512 - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\packages\dotnet-test-xunit.2.2.0-preview2-build1029\lib\net451\dotnet-test-xunit.exe - True - - - ..\..\..\..\Users\jhkim\.nuget\packages\Microsoft.AspNetCore.Server.IntegrationTesting\0.3.0-preview1-22821\lib\net451\Microsoft.AspNetCore.Server.IntegrationTesting.dll - - - ..\..\packages\Microsoft.AspNetCore.Server.Testing.0.2.0-alpha1-21873\lib\net451\Microsoft.AspNetCore.Server.Testing.dll - True - - - ..\..\packages\Microsoft.AspNetCore.Testing.1.2.0-preview1-22815\lib\net451\Microsoft.AspNetCore.Testing.dll - True - - - ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.1.2.0-preview1-22821\lib\netstandard1.0\Microsoft.Extensions.Configuration.Abstractions.dll - True - - - ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.2.0-preview1-22821\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - True - - - ..\..\packages\Microsoft.Extensions.FileProviders.Abstractions.1.1.0-preview1-final\lib\netstandard1.0\Microsoft.Extensions.FileProviders.Abstractions.dll - True - - - ..\..\packages\Microsoft.Extensions.FileProviders.Embedded.1.1.0-preview1-final\lib\net451\Microsoft.Extensions.FileProviders.Embedded.dll - True - - - ..\..\packages\Microsoft.Extensions.Logging.1.2.0-preview1-22821\lib\netstandard1.1\Microsoft.Extensions.Logging.dll - True - - - ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.2.0-preview1-22821\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll - True - - - ..\..\packages\Microsoft.Extensions.Logging.Console.1.2.0-preview1-22821\lib\net451\Microsoft.Extensions.Logging.Console.dll - True - - - ..\..\packages\Microsoft.Extensions.PlatformAbstractions.1.2.0-preview1-22821\lib\net451\Microsoft.Extensions.PlatformAbstractions.dll - True - - - ..\..\packages\Microsoft.Extensions.Primitives.1.2.0-preview1-22821\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll - True - - - ..\..\packages\Microsoft.Net.Http.Headers.1.2.0-preview1-22821\lib\netstandard1.1\Microsoft.Net.Http.Headers.dll - True - - - ..\..\packages\Microsoft.Web.Administration.7.0.0.0\lib\net20\Microsoft.Web.Administration.dll - True - - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True - - - - ..\..\packages\System.Buffers.4.3.0\lib\netstandard1.1\System.Buffers.dll - True - - - - - - - - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll - True - - - ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - True - - - - - - - - - - ..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll - True - - - ..\..\packages\xunit.assert.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.assert.dll - True - - - ..\..\packages\xunit.extensibility.core.2.2.0-beta4-build3444\lib\net45\xunit.core.dll - True - - - ..\..\packages\xunit.extensibility.execution.2.2.0-beta4-build3444\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj deleted file mode 100644 index b1ed5dc42f..0000000000 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.xproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 4dda7560-aa29-4161-a5ea-a7e8f3997321 - AspNetCoreModule.Test - .\obj - - - 2.0 - - - - - - \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index f727b958f4..0a77d73e57 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -4,7 +4,6 @@ using AspNetCoreModule.Test.HttpClientHelper; using Microsoft.Web.Administration; using System; -using System.Collections.ObjectModel; using System.IO; using System.ServiceProcess; using System.Threading; diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 07aafb1264..22a07e43fd 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -27,6 +27,11 @@ namespace AspNetCoreModule.Test.Framework public InitializeTestMachine() { + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) + { + TestUtility.LogInformation("Error!!! Skipping to run InitializeTestMachine::InitializeTestMachine() because the test process is started on syswow mode"); + throw new NotSupportedException("Running this test progrom in syswow64 mode is not supported"); + } _referenceCount++; if (_referenceCount == 1) diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index b688582dc8..9e7d6d67fd 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -705,7 +705,7 @@ namespace AspNetCoreModule.Test.Framework // create Powershell runspace Runspace runspace = RunspaceFactory.CreateRunspace(); - + // open it runspace.Open(); diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 09b3cb0ce7..97bf2d92d8 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -1041,15 +1041,12 @@ namespace AspNetCoreModule.Test // Create a new local administrator user string userName = "tempuser" + TestUtility.RandomString(5); - string password = "AncmTest123!"; - string temp = TestUtility.RunPowershellScript("( Get-LocalUser -Name " + userName + " 2> $null ).Name"); - if (temp == userName) - { - temp = TestUtility.RunPowershellScript("net localgroup administrators /Delete " + userName); - temp = TestUtility.RunPowershellScript("net user " + userName + " /Delete"); - } + 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 administrators /Add " + userName); + 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"); @@ -1102,7 +1099,7 @@ namespace AspNetCoreModule.Test Assert.True(result.Contains("403.7")); // Clean up user - temp = TestUtility.RunPowershellScript("net localgroup administrators /Delete " + userName); + temp = TestUtility.RunPowershellScript("net localgroup IIS_IUSRS /Delete " + userName); temp = TestUtility.RunPowershellScript("net user " + userName + " /Delete"); // Remove the SSL Certificate mapping diff --git a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj new file mode 100644 index 0000000000..9a9b87c30c --- /dev/null +++ b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj @@ -0,0 +1,51 @@ + + + + net452 + true + true + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.Test/project.json b/test/AspNetCoreModule.Test/project.json deleted file mode 100644 index bc9d8099d2..0000000000 --- a/test/AspNetCoreModule.Test/project.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "buildOptions": { - "warningsAsErrors": true, - "copyToOutput": { - "include": [ - "Http.config" - ] - } - }, - "testRunner": "xunit", - "dependencies": { - "Microsoft.Web.Administration": "7.0.0", - "dotnet-test-xunit": "2.2.0-*", - "Microsoft.AspNetCore.Testing": "1.2.0-*", - "Microsoft.Extensions.Logging": "1.2.0-*", - "Microsoft.Extensions.Logging.Console": "1.2.0-*", - "Microsoft.Extensions.PlatformAbstractions": "1.2.0-*", - "Microsoft.Net.Http.Headers": "1.1.0-*", - "xunit": "2.2.0-*", - "System.Management.Automation.dll": "10.0.10586" - }, - "frameworks": { - "net451": { - "frameworkAssemblies": { - "System.Data": "", - "System.IO.Compression.FileSystem": "", - "System.Management": "", - "System.Net.Http": "", - "System.Net.Http.WebRequest": "", - "System.Runtime": "", - "System.ServiceProcess": "", - "System.Xml": "" - } - } - } -} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/project.json.compileerror.txt b/test/AspNetCoreModule.Test/project.json.compileerror.txt deleted file mode 100644 index e3833d3d0b..0000000000 --- a/test/AspNetCoreModule.Test/project.json.compileerror.txt +++ /dev/null @@ -1,38 +0,0 @@ -{ - "version": "1.1.0-*", - "dependencies": { - "Microsoft.AspNetCore.WebSockets.Server": "*", - "Microsoft.AspNetCore.Server.IISIntegration": "1.2.0-*", - "Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*", - "Microsoft.AspNetCore.Server.WebListener": "1.2.0-*", - "Microsoft.AspNetCore.WebUtilities": "1.2.0-*", - "Microsoft.Extensions.Configuration.CommandLine": "1.2.0-*", - "Microsoft.Extensions.Configuration.Json": "1.2.0-*", - "Microsoft.Extensions.Logging.Console": "1.2.0-*", - "Microsoft.Net.Http.Headers": "1.2.0-*" - }, - "buildOptions": { - "emitEntryPoint": true - }, - "publishOptions": { - "include": [ - "web.config" - ] - }, - "frameworks": { - "netcoreapp1.1": { - "dependencies": { - "Microsoft.NETCore.App": { - "version": "1.1.0-*", - "type": "platform" - } - } - } - }, - "tools": { - "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" - }, - "scripts": { - "postpublish": "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" - } -} \ No newline at end of file diff --git a/test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj b/test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj deleted file mode 100644 index 9ad0d6bf8b..0000000000 --- a/test/AspNetCoreModule.TestSites.Standard/AspnetCoreModule.TestSites.Standard.xproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 030225d8-4ee8-47e5-b692-2a96b3b51a38 - .\obj - .\bin\ - - - 2.0 - 49212 - - - \ No newline at end of file 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..08a7cfdd27 --- /dev/null +++ b/test/AspNetCoreModule.TestSites.Standard/aspnetcoremodule.testsites.standard.csproj @@ -0,0 +1,21 @@ + + + netcoreapp1.1 + + + + + + + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.TestSites.Standard/project.json b/test/AspNetCoreModule.TestSites.Standard/project.json deleted file mode 100644 index cf5023ba58..0000000000 --- a/test/AspNetCoreModule.TestSites.Standard/project.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "dependencies": { - "Microsoft.AspNetCore.WebSockets.Server": "0.1.0-rc2-final", - "Microsoft.AspNetCore.Diagnostics": "1.0.1-*", - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.1-*", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.1-*", - "Microsoft.AspNetCore.Server.WebListener": "1.0.1-*", - "Microsoft.AspNetCore.Server.Kestrel.Https": "1.0.1-*", - "Microsoft.AspNetCore.ResponseCompression": "*", - "Microsoft.AspNetCore.ResponseCaching": "*", - "Microsoft.AspNetCore.StaticFiles": "*", - "Microsoft.Extensions.Configuration.CommandLine": "1.0.1-*", - "Microsoft.Extensions.Logging.Console": "1.0.1-*" - }, - - "buildOptions": { - "emitEntryPoint": true, - "copyToOutput": [ - ] - }, - - "publishOptions": { - "include": [ - "web.config" - ] - }, - - "frameworks": { - "netcoreapp1.1": { - "dependencies": { - "Microsoft.NETCore.App": { - "version": "1.0.1-*", - "type": "platform" - } - } - } - }, - - "tools": { - "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" - }, - - "scripts": { - "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] - } -} From c983c0e642dd13522576ccc3274d297cb8276ca0 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 27 Feb 2017 17:09:56 -0800 Subject: [PATCH 037/107] Updated to remove throwing exception when test starts with 32 bit mode --- test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 22a07e43fd..0bf79b4e25 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -30,7 +30,7 @@ namespace AspNetCoreModule.Test.Framework if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) { TestUtility.LogInformation("Error!!! Skipping to run InitializeTestMachine::InitializeTestMachine() because the test process is started on syswow mode"); - throw new NotSupportedException("Running this test progrom in syswow64 mode is not supported"); + return; } _referenceCount++; From 5ae8155c2a93c93f526ab64c1db1045fa8deef2a Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 27 Feb 2017 17:25:33 -0800 Subject: [PATCH 038/107] Updated version for Microsoft.Net.Http.Headers to fix build issue --- test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj index 9a9b87c30c..58bcdc9a21 100644 --- a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj +++ b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj @@ -22,7 +22,7 @@ - + From 5854e03ddc0941b9b749757a70720eb3cc799bbd Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Mon, 3 Apr 2017 16:06:25 -0700 Subject: [PATCH 039/107] Update Korebuild and dependency versions (#85) Merging --- .gitignore | 3 +- Build/Key.snk | Bin 0 -> 596 bytes Build/common.props | 23 ++++++++++++++ Build/dependencies.props | 11 +++++++ NuGet.config | 4 +-- build.ps1 | 16 +++++----- .../aspnetcoremodule.test.csproj | 28 ++++++++---------- version.props | 7 +++++ 8 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 Build/Key.snk create mode 100644 Build/common.props create mode 100644 Build/dependencies.props create mode 100644 version.props diff --git a/.gitignore b/.gitignore index 9d24973eeb..03944b51ac 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,5 @@ src/AspNetCore/aspnetcore_msg.rc src/AspNetCore/version.h .build -*.VC.*db \ No newline at end of file +*.VC.*db +global.json \ No newline at end of file diff --git a/Build/Key.snk b/Build/Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..e10e4889c125d3120cd9e81582243d70f7cbb806 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098=Iw=HCsnz~#iVhm& zj%TU(_THUee?3yHBjk$37ysB?i5#7WD$={H zV4B!OxRPrb|8)HPg~A}8P>^=#y<)56#=E&NzcjOtPK~<4n6GHt=K$ro*T(lhby_@U zEk(hLzk1H)0yXj{A_5>fk-TgNoP|q6(tP2xo8zt8i%212CWM#AeCd?`hS|4~L({h~Moo(~vy&3Z z1uI}`fd^*>o=rwbAGymj6RM^pZm(*Kfhs+Y1#`-2JPWZMK8@;ZWCk2+9bX4YP);~fj-BU*R zQPvWv$89!{Rl9wM+zR>_TSkn^voYxA?2G iKnV#iZ6Ah`K>b=@=IjYJXrxL124zR(38)nxe+&q_$QXwJ literal 0 HcmV?d00001 diff --git a/Build/common.props b/Build/common.props new file mode 100644 index 0000000000..c87cc995fa --- /dev/null +++ b/Build/common.props @@ -0,0 +1,23 @@ + + + + + + Microsoft ASP.NET Core + https://github.com/aspnet/AspNetCoreModule + git + $(MSBuildThisFileDirectory)Key.snk + true + true + $(VersionSuffix)-$(BuildNumber) + + + + + + + + + + + \ No newline at end of file diff --git a/Build/dependencies.props b/Build/dependencies.props new file mode 100644 index 0000000000..8c81df7f34 --- /dev/null +++ b/Build/dependencies.props @@ -0,0 +1,11 @@ + + + 1.2.0-* + 4.3.0 + 2.0.0-* + 1.6.1 + 2.0.0-* + 15.0.0 + 2.2.0 + + \ No newline at end of file diff --git a/NuGet.config b/NuGet.config index 4e531e985d..6f9f028ca1 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,7 +2,7 @@ - + - + \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index 0605b59c01..dc220d733f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,6 +1,6 @@ $ErrorActionPreference = "Stop" -function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) +function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) { while($true) { @@ -19,7 +19,7 @@ function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $ret Start-Sleep -Seconds 10 } - else + else { $exception = $_.Exception throw $exception @@ -33,7 +33,7 @@ cd $PSScriptRoot $repoFolder = $PSScriptRoot $env:REPO_FOLDER = $repoFolder -$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip" +$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" if ($env:KOREBUILD_ZIP) { $koreBuildZip=$env:KOREBUILD_ZIP @@ -43,18 +43,18 @@ $buildFolder = ".build" $buildFile="$buildFolder\KoreBuild.ps1" if (!(Test-Path $buildFolder)) { - Write-Host "Downloading KoreBuild from $koreBuildZip" - + Write-Host "Downloading KoreBuild from $koreBuildZip" + $tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid() New-Item -Path "$tempFolder" -Type directory | Out-Null $localZipFile="$tempFolder\korebuild.zip" - + DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6 Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder) - + New-Item -Path "$buildFolder" -Type directory | Out-Null copy-item "$tempFolder\**\build\*" $buildFolder -Recurse @@ -64,4 +64,4 @@ if (!(Test-Path $buildFolder)) { } } -&"$buildFile" $args \ No newline at end of file +&"$buildFile" @args \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj index 58bcdc9a21..9da583a52b 100644 --- a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj +++ b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj @@ -1,7 +1,8 @@  + - net452 + net46 true true @@ -14,22 +15,17 @@ - - - + + + + - - - - - - - - - - - - + + + + + + diff --git a/version.props b/version.props new file mode 100644 index 0000000000..38c93687ab --- /dev/null +++ b/version.props @@ -0,0 +1,7 @@ + + + + 1.2.0 + preview1 + + \ No newline at end of file From 2c5132251b02dc706f421e5bd83e4b21c2ed392a Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 24 Apr 2017 18:17:33 -0700 Subject: [PATCH 040/107] Updated to support IISExpress and add new test cases for the additional environment variables (#93) --- Build/common.props | 8 +- Build/repo.props | 4 + Build/repo.targets | 59 ++++ makefile.shade | 20 -- src/AspNetCore/AspNetCore.vcxproj | 3 + ...vironmentVariableTestConditionAttribute.cs | 41 --- .../Framework/IISConfigUtility.cs | 275 +++++++++++----- .../Framework/InitializeTestMachine.cs | 202 ++++++++---- .../Framework/TestUtility.cs | 45 ++- .../Framework/TestWebApplication.cs | 4 +- .../Framework/TestWebSite.cs | 196 ++++++++---- test/AspNetCoreModule.Test/FunctionalTest.cs | 161 +++++----- .../FunctionalTestHelper.cs | 293 +++++++++++++++--- test/AspNetCoreModule.Test/app.config | 10 +- .../aspnetcoremodule.test.csproj | 10 +- test/stresstestwebroot/app_offline.htm | 1 + test/stresstestwebroot/web.config | 9 + tools/certificate.ps1 | 139 +++++++-- tools/stresstest.ps1 | 96 ++++++ 19 files changed, 1151 insertions(+), 425 deletions(-) create mode 100644 Build/repo.targets delete mode 100644 makefile.shade delete mode 100644 test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs create mode 100644 test/stresstestwebroot/app_offline.htm create mode 100644 test/stresstestwebroot/web.config create mode 100644 tools/stresstest.ps1 diff --git a/Build/common.props b/Build/common.props index c87cc995fa..7eb2e446b7 100644 --- a/Build/common.props +++ b/Build/common.props @@ -12,12 +12,18 @@ $(VersionSuffix)-$(BuildNumber) + + RunConfiguration.TargetPlatform=x64 + + + \ No newline at end of file diff --git a/Build/repo.props b/Build/repo.props index a56e75519b..1d5d44d0dd 100644 --- a/Build/repo.props +++ b/Build/repo.props @@ -3,4 +3,8 @@ + + + RunConfiguration.TargetPlatform=x64 + \ No newline at end of file diff --git a/Build/repo.targets b/Build/repo.targets new file mode 100644 index 0000000000..2138b46789 --- /dev/null +++ b/Build/repo.targets @@ -0,0 +1,59 @@ + + + true + $(VerifyDependsOn);PublishPackage + https://dotnet.myget.org/F/aspnetcoremodule/api/v2/package + + + + + + + + + + + + + + + %(MSBuild15ExePaths.FullPath) + + + + + + + + + + + + + + $(RepositoryRoot).build\nuget.exe + $(RepositoryRoot)nuget\AspNetCore.nuspec + + + + + + + + + + + + <_PackagePublisherPath>@(PackagePublisherPath)\PackagePublisher.exe + + + + + + + + \ No newline at end of file diff --git a/makefile.shade b/makefile.shade deleted file mode 100644 index 6be672789f..0000000000 --- a/makefile.shade +++ /dev/null @@ -1,20 +0,0 @@ -default BASE_DIR_LOCAL='${Directory.GetCurrentDirectory()}' -default BUILD_DIR_LOCAL='${Path.Combine(BASE_DIR_LOCAL, "artifacts", "build")}' -var VERSION='0.1' -var FULL_VERSION='0.1' - -use-standard-lifecycle -k-standard-goals - -#make-nupkg target='package' - log info='Make nuget package containing ASP.NET Core Module' - @{ - var nugetExePath = Environment.GetEnvironmentVariable("KOREBUILD_NUGET_EXE"); - if (string.IsNullOrEmpty(nugetExePath)) - { - nugetExePath = Path.Combine(BASE_DIR_LOCAL, ".build", "nuget.exe"); - } - - var nuspecPath = Path.Combine(BASE_DIR_LOCAL, "nuget", "AspNetCore.nuspec"); - ExecClr(nugetExePath, "pack " + nuspecPath + " -OutputDirectory " + BUILD_DIR_LOCAL + " -prop VERSION=1.0.0-" + BuildNumber); - } diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 423cf6e188..c795aed3da 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -71,6 +71,9 @@ + + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + NotUsing diff --git a/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs b/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs deleted file mode 100644 index 8e17664271..0000000000 --- a/test/AspNetCoreModule.Test/Framework/EnvironmentVariableTestConditionAttribute.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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.Testing.xunit; - -namespace AspNetCoreModule.Test.Framework -{ - /// - /// Skip test if a given environment variable is not enabled. To enable the test, set environment variable - /// to "true" for the test process. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class EnvironmentVariableTestConditionAttribute : Attribute, ITestCondition - { - private readonly string _environmentVariableName; - - public EnvironmentVariableTestConditionAttribute(string environmentVariableName) - { - _environmentVariableName = environmentVariableName; - } - - public bool IsMet - { - get - { - return string.Compare(Environment.GetEnvironmentVariable(_environmentVariableName), "true", ignoreCase: true) == 0; - } - } - - public string SkipReason - { - get - { - return $"To run this test, set the environment variable {_environmentVariableName}=\"true\". {AdditionalInfo}"; - } - } - - public string AdditionalInfo { get; set; } - } -} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index 0a77d73e57..5e122c900b 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -22,9 +22,10 @@ namespace AspNetCoreModule.Test.Framework public static string DefaultAppPool = "DefaultAppPool"; } + public static string ApppHostTemporaryBackupFileExtention = null; private ServerType _serverType = ServerType.IIS; private string _iisExpressConfigPath = null; - + public enum AppPoolBitness { enable32Bit, @@ -53,63 +54,167 @@ namespace AspNetCoreModule.Test.Framework } } - public IISConfigUtility(ServerType type, string iisExpressConfigPath = null) + public IISConfigUtility(ServerType type, string iisExpressConfigPath) { _serverType = type; _iisExpressConfigPath = iisExpressConfigPath; } - public static void BackupAppHostConfig() + public static bool BackupAppHostConfig(string fileExtenstion, bool overWriteMode) { + bool result = true; string fromfile = Strings.AppHostConfigPath; - string tofile = Strings.AppHostConfigPath + ".ancmtest.bak"; + string tofile = Strings.AppHostConfigPath + fileExtenstion; if (File.Exists(fromfile)) { - TestUtility.FileCopy(fromfile, tofile, overWrite: false); + try + { + TestUtility.FileCopy(fromfile, tofile, overWrite: overWriteMode); + } + catch + { + result = false; + } + } + return result; + } + + public static void RestoreAppHostConfig(bool restoreFromMasterBackupFile = true) + { + string masterBackupFileExtension = ".ancmtest.mastebackup"; + 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); + } } } - public static void RestoreAppHostConfig() + private static void CopyAppHostConfig(string fromfile, string tofile) { - string fromfile = Strings.AppHostConfigPath + ".ancmtest.bak"; - string tofile = Strings.AppHostConfigPath; - if (!File.Exists(fromfile) && !File.Exists(tofile)) { // IIS is not installed, don't do anything here return; } - // backup first if the backup file is not available if (!File.Exists(fromfile)) { - BackupAppHostConfig(); + throw new System.ApplicationException("Failed to backup " + tofile); } - // try again after the ininial clean up - if (File.Exists(fromfile)) + // try restoring applicationhost.config again after the ininial clean up for better reliability + try { - try - { - TestUtility.FileCopy(fromfile, tofile, true, true); - } - catch - { - // ignore - } + 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); - } + // 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); + } - if (File.ReadAllBytes(fromfile).Length != File.ReadAllBytes(tofile).Length) - { - throw new System.ApplicationException("Failed to restore applicationhost.config"); - } + // verify restoration is done successfully + if (File.ReadAllBytes(fromfile).Length != File.ReadAllBytes(tofile).Length) + { + throw new System.ApplicationException("Failed to restore applicationhost.config from " + fromfile + " to " + tofile); } } @@ -243,7 +348,7 @@ namespace AspNetCoreModule.Test.Framework } } - public void EnableWindowsAuthentication(string siteName) + public void EnableIISAuthentication(string siteName, bool windows, bool basic, bool anonymous) { TestUtility.LogInformation("Enable Windows authentication : " + siteName); using (ServerManager serverManager = GetServerManager()) @@ -251,9 +356,11 @@ namespace AspNetCoreModule.Test.Framework Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection anonymousAuthenticationSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication", siteName); - anonymousAuthenticationSection["enabled"] = false; + 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"] = true; + windowsAuthenticationSection["enabled"] = windows; serverManager.CommitChanges(); } @@ -265,7 +372,7 @@ namespace AspNetCoreModule.Test.Framework using (ServerManager serverManager = GetServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); - + ConfigurationSection iisClientCertificateMappingAuthenticationSection = config.GetSection("system.webServer/security/authentication/iisClientCertificateMappingAuthentication", siteName); // enable iisClientCertificateMappingAuthentication @@ -275,7 +382,10 @@ namespace AspNetCoreModule.Test.Framework // add a new oneToOne mapping collection item ConfigurationElement addElement = oneToOneMappingsCollection.CreateElement("add"); addElement["userName"] = userName; - addElement["password"] = password; + if (password != null) + { + addElement["password"] = password; + } addElement["certificate"] = publicKey; oneToOneMappingsCollection.Add(addElement); @@ -326,32 +436,39 @@ namespace AspNetCoreModule.Test.Framework public void SetANCMConfig(string siteName, string appName, string attributeName, object attributeValue) { - using (ServerManager serverManager = GetServerManager()) + try { - Configuration config = serverManager.GetWebConfiguration(siteName, appName); - ConfigurationSection aspNetCoreSection = config.GetSection("system.webServer/aspNetCore"); - if (attributeName == "environmentVariable") + using (ServerManager serverManager = GetServerManager()) { - 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) + Configuration config = serverManager.GetWebConfiguration(siteName, appName); + ConfigurationSection aspNetCoreSection = config.GetSection("system.webServer/aspNetCore"); + if (attributeName == "environmentVariable") { - throw new System.ApplicationException("duplicated collection item"); + 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 System.ApplicationException("duplicated collection item"); + } + environmentVariablesCollection.Add(environmentVariableElement); + } + else + { + aspNetCoreSection[attributeName] = attributeValue; } - environmentVariablesCollection.Add(environmentVariableElement); - } - else - { - aspNetCoreSection[attributeName] = attributeValue; - } - serverManager.CommitChanges(); - } + serverManager.CommitChanges(); + } + } + catch (Exception ex) + { + throw ex; + } } public void ConfigureCustomLogging(string siteName, string appName, int statusCode, int subStatusCode, string path) @@ -387,21 +504,29 @@ namespace AspNetCoreModule.Test.Framework { if (_isIISInstalled == null) { - bool result = true; - if (!File.Exists(Path.Combine(Strings.IIS64BitPath, "iiscore.dll"))) + _isIISInstalled = true; + if (_isIISInstalled == true && !File.Exists(Path.Combine(Strings.IIS64BitPath, "iiscore.dll"))) { - result = false; + _isIISInstalled = false; } - if (!File.Exists(Path.Combine(Strings.IIS64BitPath, "config", "applicationhost.config"))) + if (_isIISInstalled == true && !File.Exists(Path.Combine(Strings.IIS64BitPath, "config", "applicationhost.config"))) { - result = false; + _isIISInstalled = false; } - _isIISInstalled = result; } return _isIISInstalled; } + set + { + _isIISInstalled = value; + } } + public static bool IsIISReady { + get; + set; + } + public bool IsAncmInstalled(ServerType servertype) { bool result = true; @@ -422,7 +547,7 @@ namespace AspNetCoreModule.Test.Framework return result; } - public string GetServiceStatus(string serviceName) + public static string GetServiceStatus(string serviceName) { ServiceController sc = new ServiceController(serviceName); @@ -959,26 +1084,8 @@ namespace AspNetCoreModule.Test.Framework public string CreateSelfSignedCertificateWithMakeCert(string subjectName, string issuerName = null, string extendedKeyUsage = null) { - string makecertExeFilePath = "makecert.exe"; - var makecertExeFilePaths = new string[] - { - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.1", "bin", "x64", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.1", "bin", "x86", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.0", "bin", "x64", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.0", "bin", "x86", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows SKDs", "Windows", "v7.1A", "bin", "x64", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows SKDs", "Windows", "v7.1A", "bin", "makecert.exe") - }; + string makecertExeFilePath = TestUtility.GetMakeCertPath(); - foreach (string item in makecertExeFilePaths) - { - if (File.Exists(item)) - { - makecertExeFilePath = item; - break; - } - } - string parameter; string targetSSLStore = string.Empty; if (issuerName == null) diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 0bf79b4e25..12df0ca755 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -11,14 +11,18 @@ namespace AspNetCoreModule.Test.Framework public class InitializeTestMachine : IDisposable { // - // By default, we don't use the private AspNetCoreFile + // By default, we use the private AspNetCoreFile which were created from this solution // - public static bool UsePrivateAspNetCoreFile = false; + public static bool UsePrivateAspNetCoreFile = true; public static int SiteId = 40000; - public static string Aspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore_private.dll"); + public const string PrivateFileName = "aspnetcore_private.dll"; + public static string Aspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", PrivateFileName); public static string Aspnetcore_path_original = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore.dll"); - public static string Aspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv", "aspnetcore_private.dll"); + public static string Aspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv", PrivateFileName); + public static string IISExpressAspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", PrivateFileName); + public static string IISExpressAspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", PrivateFileName); + public static string IISExpressAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); public static string IISAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "config", "schema", "aspnetcore_schema.xml"); public static int _referenceCount = 0; @@ -27,11 +31,6 @@ namespace AspNetCoreModule.Test.Framework public InitializeTestMachine() { - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) - { - TestUtility.LogInformation("Error!!! Skipping to run InitializeTestMachine::InitializeTestMachine() because the test process is started on syswow mode"); - return; - } _referenceCount++; if (_referenceCount == 1) @@ -41,27 +40,87 @@ namespace AspNetCoreModule.Test.Framework _InitializeTestMachineCompleted = false; TestUtility.LogInformation("InitializeTestMachine::Start"); - if (Environment.ExpandEnvironmentVariables("%ANCMDebug%").ToLower() == "true") + if (Environment.ExpandEnvironmentVariables("%ANCMTEST_DEBUG%").ToLower() == "true") { System.Diagnostics.Debugger.Launch(); } - TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); - TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); - // cleanup before starting - string siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest"); + // check Makecert.exe exists try { - if (IISConfigUtility.IsIISInstalled == true) - { - IISConfigUtility.RestoreAppHostConfig(); - } + string makecertExeFilePath = TestUtility.GetMakeCertPath(); + TestUtility.RunCommand(makecertExeFilePath, null, true, true); + TestUtility.LogInformation("Verified makecert.exe is available : " + makecertExeFilePath); } - catch + catch (Exception ex) { - TestUtility.LogInformation("Failed to restore applicationhost.config"); + throw new System.ApplicationException("makecert.exe is not available : " + ex.Message); } + TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); + + // check if we can use IIS server instead of IISExpress + try + { + IISConfigUtility.IsIISReady = false; + if (IISConfigUtility.IsIISInstalled == true) + { + if (Environment.GetEnvironmentVariable("ANCMTEST_USE_IISEXPRESS") != null && Environment.GetEnvironmentVariable("ANCMTEST_USE_IISEXPRESS").Equals("true", StringComparison.InvariantCultureIgnoreCase)) + { + throw new System.ApplicationException("'ANCMTestServerType' environment variable is set to 'true'"); + } + + // check websocket is installed + if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) + { + TestUtility.LogInformation("Websocket is installed"); + } + else + { + throw new System.ApplicationException("websocket module is not installed"); + } + + 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 System.ApplicationException("WWW service can't start"); + } + + // check URLRewrite module exists + if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "rewrite.dll"))) + { + TestUtility.LogInformation("Verified URL Rewrite module installed for IIS server"); + } + else + { + throw new System.ApplicationException("URL Rewrite module is not installed"); + } + + if (IISConfigUtility.ApppHostTemporaryBackupFileExtention == null) + { + throw new System.ApplicationException("Failed to backup applicationhost.config"); + } + IISConfigUtility.IsIISReady = true; + } + } + catch (Exception ex) + { + RollbackIISApplicationhostConfigFile(); + TestUtility.LogInformation("We will use IISExpress instead of IIS: " + ex.Message); + } + + string siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest"); if (!Directory.Exists(siteRootPath)) { Directory.CreateDirectory(siteRootPath); @@ -97,18 +156,16 @@ namespace AspNetCoreModule.Test.Framework PreparePrivateANCMFiles(); // update applicationhost.config for IIS server - if (IISConfigUtility.IsIISInstalled == true) + if (IISConfigUtility.IsIISReady) { - - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(ServerType.IIS, null)) { iisConfig.AddModule("AspNetCoreModule", Aspnetcore_path, null); } } } - - _InitializeTestMachineCompleted = true; + _InitializeTestMachineCompleted = true; TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() End"); } @@ -122,7 +179,7 @@ namespace AspNetCoreModule.Test.Framework { TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Waiting..."); Thread.Sleep(500); - } + } } if (!_InitializeTestMachineCompleted) { @@ -138,60 +195,68 @@ namespace AspNetCoreModule.Test.Framework { TestUtility.LogInformation("InitializeTestMachine::Dispose() Start"); TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); - - if (InitializeTestMachine.UsePrivateAspNetCoreFile) - { - if (IISConfigUtility.IsIISInstalled == true) - { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) - { - try - { - iisConfig.AddModule("AspNetCoreModule", Aspnetcore_path_original, null); - } - catch - { - TestUtility.LogInformation("Failed to restore aspnetcore.dll path!!!"); - } - } - } - } + 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 debug build - outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Debug"); + // First try with release build + outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Release"); - // If debug build does is not available, try with release build + // 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", "Release"); - } - - 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, "src", "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; build aspnetcore.dll for both x86 and x64 and then try again!!!"); + outputPath = Path.Combine(solutionRoot, "artifacts", "build", "AspNetCore", "bin", "Debug"); } - // create an extra private copy of the private file on IISExpress directory + 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!!!"); + } + + // create an extra private copy of the private file on IIS directory if (InitializeTestMachine.UsePrivateAspNetCoreFile) { bool updateSuccess = false; @@ -204,10 +269,15 @@ namespace AspNetCoreModule.Test.Framework TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); TestUtility.ResetHelper(ResetHelperMode.StopW3svcStartW3svc); Thread.Sleep(1000); - TestUtility.FileCopy(Path.Combine(outputPath, "x64", "aspnetcore.dll"), Aspnetcore_path); + string from = Path.Combine(outputPath, "x64", "aspnetcore.dll"); + TestUtility.FileCopy(from, Aspnetcore_path, overWrite:true, ignoreExceptionWhileDeletingExistingFile:false); + TestUtility.FileCopy(from, IISExpressAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + if (TestUtility.IsOSAmd64) { - TestUtility.FileCopy(Path.Combine(outputPath, "Win32", "aspnetcore.dll"), Aspnetcore_X86_path); + from = Path.Combine(outputPath, "Win32", "aspnetcore.dll"); + TestUtility.FileCopy(from, Aspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + TestUtility.FileCopy(from, IISExpressAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); } updateSuccess = true; } diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index 9e7d6d67fd..e7373e2299 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -215,6 +215,7 @@ namespace AspNetCoreModule.Test.Framework { exceptionBlock?.Invoke(exception); } + LogInformation("ANCMTEST::RetryHelper Retrying " + retry); Thread.Sleep(retryDelayMilliseconds); } return false; @@ -248,21 +249,21 @@ namespace AspNetCoreModule.Test.Framework { if (format != null) { - Logger.LogTrace(format); + Logger.LogTrace(format, parameters); } } public static void LogError(string format, params object[] parameters) { if (format != null) { - Logger.LogError(format); + Logger.LogError(format, parameters); } } public static void LogInformation(string format, params object[] parameters) { if (format != null) { - Logger.LogInformation(format); + Logger.LogInformation(format, parameters); } } @@ -433,6 +434,31 @@ namespace AspNetCoreModule.Test.Framework } } + public static string GetMakeCertPath() + { + string makecertExeFilePath = "makecert.exe"; + var makecertExeFilePaths = new string[] + { + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.1", "bin", "x64", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.1", "bin", "x86", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.0", "bin", "x64", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.0", "bin", "x86", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows SKDs", "Windows", "v7.1A", "bin", "x64", "makecert.exe"), + Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows SKDs", "Windows", "v7.1A", "bin", "makecert.exe") + }; + + foreach (string item in makecertExeFilePaths) + { + if (File.Exists(item)) + { + makecertExeFilePath = item; + break; + } + } + + return makecertExeFilePath; + } + public static int GetNumberOfProcess(string processFileName, int expectedNumber = 1, int retry = 0) { int result = 0; @@ -704,7 +730,16 @@ namespace AspNetCoreModule.Test.Framework IPEndPoint a = new IPEndPoint(0, 443); // create Powershell runspace - Runspace runspace = RunspaceFactory.CreateRunspace(); + 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(); @@ -759,7 +794,7 @@ namespace AspNetCoreModule.Test.Framework p.StartInfo.UseShellExecute = false; p.StartInfo.CreateNoWindow = true; - p.Start(); + p.Start(); pid = p.Id; string standardOutput = string.Empty; string standardError = string.Empty; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs index 3e6498e6dc..83f0b03e32 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebApplication.cs @@ -175,7 +175,7 @@ namespace AspNetCoreModule.Test.Framework // read web.config string fileContent = TestUtility.FileReadAllText(filePath); - // get the value of processPath attribute of aspNetCore element + // get the value of arguments attribute of aspNetCore element if (fileContent != null) { result = TestUtility.XmlParser(fileContent, "aspNetCore", "arguments", null); @@ -197,7 +197,7 @@ namespace AspNetCoreModule.Test.Framework { string fromfile = Path.Combine(_physicalPath, from + ".bak"); string tofile = Path.Combine(_physicalPath, from); - if (!File.Exists(tofile)) + if (!File.Exists(fromfile)) { BackupFile(from); } diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index a946fc49d3..cf062ef1e9 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -19,7 +19,7 @@ namespace AspNetCoreModule.Test.Framework public TestUtility testHelper; private ILogger _logger; private int _iisExpressPidBackup = -1; - + private string postfix = string.Empty; public void Dispose() @@ -29,10 +29,17 @@ namespace AspNetCoreModule.Test.Framework if (_iisExpressPidBackup != -1) { var iisExpressProcess = Process.GetProcessById(Convert.ToInt32(_iisExpressPidBackup)); - iisExpressProcess.Kill(); - iisExpressProcess.WaitForExit(); + try + { + iisExpressProcess.Kill(); + iisExpressProcess.WaitForExit(); + iisExpressProcess.Close(); + } + catch + { + TestUtility.RunPowershellScript("stop-process -id " + _iisExpressPidBackup); + } } - TestUtility.LogInformation("TestWebSite::Dispose() End"); } @@ -91,14 +98,30 @@ namespace AspNetCoreModule.Test.Framework _tcpPort = value; } } + + 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", ServerType serverType = ServerType.IIS) + public TestWebSite(IISConfigUtility.AppPoolBitness appPoolBitness, string loggerPrefix = "ANCMTest", bool startIISExpress = true, bool copyAllPublishedFiles = false) { + _appPoolBitness = appPoolBitness; + + // + // Default server type is IISExpress. we, however, should use IIS server instead if IIS server is ready to use. + // + IisServerType = ServerType.IISExpress; + if (IISConfigUtility.IsIISReady) + { + IisServerType = ServerType.IIS; + } + TestUtility.LogInformation("TestWebSite::TestWebSite() Start"); string solutionPath = InitializeTestMachine.GetSolutionDirectory(); - if (serverType == ServerType.IIS) + if (IisServerType == ServerType.IIS) { // check JitDebugger before continuing TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); @@ -134,11 +157,19 @@ namespace AspNetCoreModule.Test.Framework 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 only DotnetCore v1.1 // string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp1.1", "publish"); - + string publishPathOutput = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", "publishPathOutput"); + // // Publish aspnetcore app // @@ -147,44 +178,51 @@ namespace AspNetCoreModule.Test.Framework string argumentForDotNet = "publish " + srcPath; TestUtility.LogInformation("TestWebSite::TestWebSite() StandardTestApp is not published, trying to publish on the fly: dotnet.exe " + argumentForDotNet); TestUtility.RunCommand("dotnet", argumentForDotNet); + 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(); + iisConfig.CreateSite(tempSiteName, publishPathOutput, tempId, tempId); + iisConfig.SetANCMConfig(tempSiteName, "/", "arguments", Path.Combine(publishPathOutput, argumentFileName)); + iisConfig.DeleteSite(tempSiteName); + } _publishedAspnetCoreApp = true; } - - // check published files - bool checkPublishedFiles = false; - string[] publishedFiles = Directory.GetFiles(publishPath); - foreach (var item in publishedFiles) + + if (copyAllPublishedFiles) { - if (Path.GetFileName(item) == "web.config") - { - checkPublishedFiles = true; - } + // 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")); } - - if (!checkPublishedFiles) + else { - throw new System.ApplicationException("web.config is not available in " + publishPath); + // 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")); } - // Copy the pubishpath to standardAppRootPath - TestUtility.DirectoryCopy(publishPath, aspnetCoreAppRootPath); - int tcpPort = InitializeTestMachine.SiteId++; - int siteId = tcpPort; + _siteId = tcpPort; // // initialize class member variables // string appPoolName = null; - if (serverType == ServerType.IIS) + if (IisServerType == ServerType.IIS) { appPoolName = siteName; } - else if (serverType == ServerType.IISExpress) + else if (IisServerType == ServerType.IISExpress) { appPoolName = "Clr4IntegratedAppPool"; } - + // Initialize member variables _hostName = "localhost"; _siteName = siteName; @@ -211,49 +249,99 @@ namespace AspNetCoreModule.Test.Framework URLRewriteApp.RestoreFile("web.config"); URLRewriteApp.DeleteFile("app_offline.htm"); - // copy http.config to the test site root directory and initialize iisExpressConfigPath with the path - string iisExpressConfigPath = null; - if (serverType == ServerType.IISExpress) - { - iisExpressConfigPath = Path.Combine(siteRootPath, "http.config"); - TestUtility.FileCopy(Path.Combine(solutionPath, "test", "AspNetCoreModule.Test", "http.config"), iisExpressConfigPath); - } - // // Create site and apps // - using (var iisConfig = new IISConfigUtility(serverType, iisExpressConfigPath)) + using (var iisConfig = new IISConfigUtility(IisServerType, IisExpressConfigPath)) { - if (serverType == ServerType.IIS) + // Create apppool + if (IisServerType == ServerType.IIS) { iisConfig.CreateAppPool(appPoolName); - bool is32bit = (appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit); - iisConfig.SetAppPoolSetting(appPoolName, "enable32BitAppOnWin64", is32bit); + + // Switch bitness + if (TestUtility.IsOSAmd64 && appPoolBitness == IISConfigUtility.AppPoolBitness.enable32Bit) + { + iisConfig.SetAppPoolSetting(appPoolName, "enable32BitAppOnWin64", true); + } } - iisConfig.CreateSite(siteName, RootAppContext.PhysicalPath, siteId, this.TcpPort, appPoolName); + + if (InitializeTestMachine.UsePrivateAspNetCoreFile && IisServerType == ServerType.IISExpress) + { + iisConfig.AddModule("AspNetCoreModule", ("%IIS_BIN%\\" + InitializeTestMachine.PrivateFileName), null); + } + + iisConfig.CreateSite(siteName, 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 (serverType == ServerType.IISExpress) - { - 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); + if (startIISExpress) + { + StartIISExpress(); } TestUtility.LogInformation("TestWebSite::TestWebSite() End"); } + + public void StartIISExpress(string verificationCommand = null) + { + if (IisServerType == ServerType.IIS) + { + return; + } + + 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); + + bool isIISExpressReady = false; + int timeout = 3; + for (int i = 0; i < timeout * 5; i++) + { + string statusCode = string.Empty; + try + { + if (verificationCommand == null) + { + verificationCommand = "( invoke-webrequest http://localhost:" + TcpPort + " ).StatusCode"; + } + statusCode = TestUtility.RunPowershellScript(verificationCommand); + } + catch + { + statusCode = "ExceptionError"; + + } + if ("200" == statusCode) + { + isIISExpressReady = true; + break; + } + else + { + System.Threading.Thread.Sleep(200); + } + } + if (isIISExpressReady) + { + TestUtility.LogInformation("IISExpress is ready to use"); + } + else + { + throw new ApplicationException("IISExpress is not responding within " + timeout + " seconds"); + } + } } } diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 52f5fa72ad..1ef8474b08 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -3,6 +3,7 @@ using AspNetCoreModule.Test.Framework; using Microsoft.AspNetCore.Testing.xunit; +using System; using System.Threading.Tasks; using Xunit; @@ -10,30 +11,19 @@ namespace AspNetCoreModule.Test { public class FunctionalTest : FunctionalTestHelper, IClassFixture { - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(ServerType.IISExpress, IISConfigUtility.AppPoolBitness.noChange)] - [InlineData(ServerType.IISExpress, IISConfigUtility.AppPoolBitness.enable32Bit)] - public Task BasicTestOnIISExpress(ServerType serverType, IISConfigUtility.AppPoolBitness appPoolBitness) + [InlineData(IISConfigUtility.AppPoolBitness.noChange)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] + public async void BasicTest(IISConfigUtility.AppPoolBitness appPoolBitness) { - return DoBasicTest(serverType, appPoolBitness); + await DoBasicTest(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] - [ConditionalTheory] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(ServerType.IIS, IISConfigUtility.AppPoolBitness.noChange)] - [InlineData(ServerType.IIS, IISConfigUtility.AppPoolBitness.enable32Bit)] - public Task BasicTestOnIIS(ServerType serverType, IISConfigUtility.AppPoolBitness appPoolBitness) - { - return DoBasicTest(serverType, appPoolBitness); - } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5)] @@ -44,9 +34,9 @@ namespace AspNetCoreModule.Test { return DoRapidFailsPerMinuteTest(appPoolBitness, valueOfRapidFailsPerMinute); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19)] @@ -59,9 +49,9 @@ namespace AspNetCoreModule.Test { return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -72,20 +62,9 @@ namespace AspNetCoreModule.Test { return DoStartupTimeLimitTest(appPoolBitness, starupTimeLimit); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] - [ConditionalTheory] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, "a")] - public Task WebSocketTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) - { - return DoWebSocketTest(appPoolBitness, testData); - } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -94,9 +73,9 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationAfterBackendProcessBeingKilled(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -105,9 +84,9 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationAfterW3WPProcessBeingKilled(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -117,8 +96,8 @@ namespace AspNetCoreModule.Test return DoRecycleApplicationAfterWebConfigUpdated(appPoolBitness); } - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -127,9 +106,9 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationWithURLRewrite(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -138,20 +117,26 @@ namespace AspNetCoreModule.Test { return DoRecycleParentApplicationWithURLRewrite(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange)] - public Task EnvironmentVariablesTest(IISConfigUtility.AppPoolBitness appPoolBitness) + [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(appPoolBitness); + return DoEnvironmentVariablesTest(environmentVariableName, environmentVariableValue, expectedEnvironmentVariableValue, appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -160,9 +145,9 @@ namespace AspNetCoreModule.Test { return DoAppOfflineTestWithRenaming(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -171,9 +156,9 @@ namespace AspNetCoreModule.Test { return DoAppOfflineTestWithUrlRewriteAndDeleting(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] @@ -182,9 +167,9 @@ namespace AspNetCoreModule.Test { return DoPostMethodTest(appPoolBitness, testData); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -194,8 +179,8 @@ namespace AspNetCoreModule.Test return DoDisableStartUpErrorPageTest(appPoolBitness); } - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -205,8 +190,8 @@ namespace AspNetCoreModule.Test return DoProcessesPerApplicationTest(appPoolBitness, valueOfProcessesPerApplication); } - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "00:02:00")] @@ -218,8 +203,8 @@ namespace AspNetCoreModule.Test return DoRequestTimeoutTest(appPoolBitness, requestTimeout); } - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -229,8 +214,8 @@ namespace AspNetCoreModule.Test return DoStdoutLogEnabledTest(appPoolBitness); } - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "dotnet.exe", "./")] @@ -241,9 +226,9 @@ namespace AspNetCoreModule.Test { return DoProcessPathAndArgumentsTest(appPoolBitness, processPath, argumentsPrefix); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -255,19 +240,8 @@ namespace AspNetCoreModule.Test return DoForwardWindowsAuthTokenTest(appPoolBitness, enabledForwardWindowsAuthToken); } - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] - [ConditionalTheory] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange)] - public Task RecylingAppPoolTest(IISConfigUtility.AppPoolBitness appPoolBitness) - { - return DoRecylingAppPoolTest(appPoolBitness); - } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true, true)] @@ -278,9 +252,9 @@ namespace AspNetCoreModule.Test { return DoCompressionTest(appPoolBitness, useCompressionMiddleWare, enableIISCompression); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -289,9 +263,9 @@ namespace AspNetCoreModule.Test { return DoCachingTest(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -300,9 +274,9 @@ namespace AspNetCoreModule.Test { return DoSendHTTPSRequestTest(appPoolBitness); } - - [EnvironmentVariableTestCondition("IIS_VARIATIONS_ENABLED")] + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -312,5 +286,32 @@ namespace AspNetCoreModule.Test { return DoClientCertificateMappingTest(appPoolBitness, useHTTPSMiddleWare); } + + ////////////////////////////////////////////////////////// + // NOTE: below test scenarios are not valid for Win7 OS + ////////////////////////////////////////////////////////// + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "IIS does not support Websocket on Win7")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "a")] + public Task WebSocketTest(IISConfigUtility.AppPoolBitness appPoolBitness, string testData) + { + return DoWebSocketTest(appPoolBitness, testData); + } + + [ConditionalTheory] + [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [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); + } } } diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 97bf2d92d8..8c206a7dfd 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -18,11 +18,71 @@ 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 ANCMTestSkipCondition : Attribute, ITestCondition + { + private readonly string _environmentVariableName; + + public ANCMTestSkipCondition(string environmentVariableName) + { + _environmentVariableName = environmentVariableName; + } + + public bool IsMet + { + get + { + bool result = true; + if (_environmentVariableName == "RunAsAdministratorAndX64Bitness") + { + try + { + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) + { + throw new System.InvalidOperationException("this should be started with x64 process mode on 64 bit machine"); + } + + bool isElevated; + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new WindowsPrincipal(identity); + isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); + if (!isElevated) + { + throw new System.ApplicationException("this should be started as an administrator"); + } + } + catch (Exception ex) + { + AdditionalInfo = ex.Message; + + result = false; + } + } + return result; + } + } + + public string SkipReason + { + get + { + return $"Skip condition: {_environmentVariableName}: this test case is skipped becauset {AdditionalInfo}."; + } + } + + public string AdditionalInfo { get; set; } + } + public class FunctionalTestHelper { + public FunctionalTestHelper() + { + } + private const int _repeatCount = 3; public enum ReturnValueType @@ -33,18 +93,20 @@ namespace AspNetCoreModule.Test None } - public static async Task DoBasicTest(ServerType serverType, IISConfigUtility.AppPoolBitness appPoolBitness) + public static async Task DoBasicTest(IISConfigUtility.AppPoolBitness appPoolBitness) { - using (var testSite = new TestWebSite(appPoolBitness, "DoBasicTest", serverType)) + using (var testSite = new TestWebSite(appPoolBitness, "DoBasicTest")) { string backendProcessId_old = null; DateTime startTime = DateTime.Now; + Thread.Sleep(3000); string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); 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(); @@ -78,6 +140,7 @@ namespace AspNetCoreModule.Test 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); @@ -89,6 +152,12 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoRecycleApplicationAfterW3WPProcessBeingKilled")) { + 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++) @@ -212,11 +281,15 @@ namespace AspNetCoreModule.Test } } - public static async Task DoEnvironmentVariablesTest(IISConfigUtility.AppPoolBitness appPoolBitness) + 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(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { DateTime startTime = DateTime.Now; Thread.Sleep(500); @@ -237,22 +310,78 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); int expectedValue = Convert.ToInt32(totalNumber) + 1; + string totalResult = (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK)); Assert.True(expectedValue.ToString() == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK))); - iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestBar", "bar" }); - Thread.Sleep(500); + 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); - - expectedValue++; + totalResult = (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK)); + Assert.True(expectedValue.ToString() == totalResult); Assert.True("foo" == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariablesANCMTestFoo"), HttpStatusCode.OK))); - Assert.True("bar" == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariablesANCMTestBar"), HttpStatusCode.OK))); + Assert.True(expectedEnvironmentVariableValue == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariables" + environmentVariableName), HttpStatusCode.OK))); + + // Verify other common environment variables + string temp = (await GetResponse(testSite.AspNetCoreApp.GetUri("DumpEnvironmentVariables"), HttpStatusCode.OK)); + Assert.True(temp.Contains("ASPNETCORE_PORT")); + Assert.True(temp.Contains("ASPNETCORE_APPL_PATH")); + Assert.True(temp.Contains("ASPNETCORE_IIS_HTTPAUTH")); + Assert.True(temp.Contains("ASPNETCORE_TOKEN")); + Assert.True(temp.Contains("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES")); + + // Verify other inherited environment variables + Assert.True(temp.Contains("PROCESSOR_ARCHITECTURE")); + Assert.True(temp.Contains("USERNAME")); + Assert.True(temp.Contains("USERDOMAIN")); + Assert.True(temp.Contains("USERPROFILE")); } testSite.AspNetCoreApp.RestoreFile("web.config"); } } - + public static async Task DoAppOfflineTestWithRenaming(IISConfigUtility.AppPoolBitness appPoolBitness) { using (var testSite = new TestWebSite(appPoolBitness, "DoAppOfflineTestWithRenaming")) @@ -350,7 +479,7 @@ namespace AspNetCoreModule.Test Thread.Sleep(500); - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { DateTime startTime = DateTime.Now; Thread.Sleep(500); @@ -390,7 +519,7 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoRapidFailsPerMinuteTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { bool rapidFailsTriggered = false; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "rapidFailsPerMinute", valueOfRapidFailsPerMinute); @@ -446,9 +575,10 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoProcessesPerApplicationTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + 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(); @@ -469,6 +599,7 @@ namespace AspNetCoreModule.Test } Assert.Equal(valueOfProcessesPerApplication, processIDs.Count); + foreach (var id in processIDs) { var backendProcess = Process.GetProcessById(id); @@ -505,7 +636,7 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoStartupTimeLimitTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { int startupDelay = 3; //3 seconds iisConfig.SetANCMConfig( @@ -536,7 +667,7 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoRequestTimeoutTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse(requestTimeout)); Thread.Sleep(500); @@ -562,7 +693,7 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoShutdownTimeLimitTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { // Set new value (10 second) to make the backend process get the Ctrl-C signal and measure when the recycle happens iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", valueOfshutdownTimeLimit); @@ -598,16 +729,18 @@ namespace AspNetCoreModule.Test { testSite.AspNetCoreApp.DeleteDirectory("logs"); - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { DateTime startTime = DateTime.Now; - Thread.Sleep(500); + 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 GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); 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)); @@ -642,9 +775,9 @@ namespace AspNetCoreModule.Test public static async Task DoProcessPathAndArgumentsTest(IISConfigUtility.AppPoolBitness appPoolBitness, string processPath, string argumentsPrefix) { - using (var testSite = new TestWebSite(appPoolBitness, "DoProcessPathAndArgumentsTest")) + using (var testSite = new TestWebSite(appPoolBitness, "DoProcessPathAndArgumentsTest", copyAllPublishedFiles:true)) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string arguments = argumentsPrefix + testSite.AspNetCoreApp.GetArgumentFileName(); string tempProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); @@ -681,15 +814,14 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoForwardWindowsAuthTokenTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string result = string.Empty; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); - iisConfig.EnableWindowsAuthentication(testSite.SiteName); - + iisConfig.EnableIISAuthentication(testSite.SiteName, windows:true, basic:false, anonymous:false); Thread.Sleep(500); // check JitDebugger before continuing @@ -736,7 +868,13 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoRecylingAppPoolTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + 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 @@ -845,7 +983,7 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoCompressionTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string startupClass = "StartupCompressionCaching"; if (!useCompressionMiddleWare) @@ -911,7 +1049,7 @@ namespace AspNetCoreModule.Test { using (var testSite = new TestWebSite(appPoolBitness, "DoCachingTest")) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string startupClass = "StartupCompressionCaching"; @@ -937,16 +1075,26 @@ namespace AspNetCoreModule.Test string result = string.Empty; - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - string headerValue = GetHeaderValue(result, "MyCustomHeader"); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); - Thread.Sleep(2000); + const int retryCount = 3; + string headerValue = string.Empty; + string headerValue2 = string.Empty; + for (int i = 0; i < retryCount; i++) + { + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + headerValue = GetHeaderValue(result, "MyCustomHeader"); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + Thread.Sleep(1500); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - string headerValue2 = GetHeaderValue(result, "MyCustomHeader"); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + headerValue2 = GetHeaderValue(result, "MyCustomHeader"); + Assert.True(result.Contains("foohtm"), "verify response body"); + Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + if (headerValue == headerValue2) + { + break; + } + } Assert.Equal(headerValue, headerValue2); Thread.Sleep(12000); @@ -962,15 +1110,15 @@ namespace AspNetCoreModule.Test public static async Task DoSendHTTPSRequestTest(IISConfigUtility.AppPoolBitness appPoolBitness) { - using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest")) + using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest", startIISExpress:false)) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string hostName = ""; string subjectName = "localhost"; string ipAddress = "*"; string hexIPAddress = "0x00"; - int sslPort = 46300; + int sslPort = InitializeTestMachine.SiteId + 6300; // Add https binding and get https uri information iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); @@ -984,6 +1132,9 @@ namespace AspNetCoreModule.Test // 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 string result = string.Empty; result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); @@ -1009,9 +1160,9 @@ namespace AspNetCoreModule.Test public static async Task DoClientCertificateMappingTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useHTTPSMiddleWare) { - using (var testSite = new TestWebSite(appPoolBitness, "DoClientCertificateMappingTest")) + using (var testSite = new TestWebSite(appPoolBitness, "DoClientCertificateMappingTest", startIISExpress: false)) { - using (var iisConfig = new IISConfigUtility(ServerType.IIS)) + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string hostName = ""; string rootCN = "ANCMTest" + testSite.PostFix; @@ -1021,7 +1172,7 @@ namespace AspNetCoreModule.Test string ipAddress = "*"; string hexIPAddress = "0x00"; - int sslPort = 46300; + int sslPort = InitializeTestMachine.SiteId + 6300; // Add https binding and get https uri information iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); @@ -1050,7 +1201,27 @@ namespace AspNetCoreModule.Test // 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"); - iisConfig.EnableOneToOneClientCertificateMapping(testSite.SiteName, ".\\" + userName, password, publicKey); + + bool setPasswordSeperately = false; + if (testSite.IisServerType == ServerType.IISExpress && IISConfigUtility.IsIISInstalled == true) + { + 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) @@ -1073,9 +1244,13 @@ namespace AspNetCoreModule.Test 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("( invoke-webrequest " + rootHttpsUri.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").StatusCode"); + // 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"); + 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" @@ -1134,10 +1309,7 @@ namespace AspNetCoreModule.Test // Verify WebSocket subprotocol await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // echoSubProtocol.aspx has hard coded path for the websocket server - - // Verify process creation ANCM event log - Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); - + // Verify websocket using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) { @@ -1386,10 +1558,30 @@ namespace AspNetCoreModule.Test if (unZipContent) { var inputStream = await response.Content.ReadAsStreamAsync(); - var outputStream = new MemoryStream(); + + // 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)) { - await gzip.CopyToAsync(outputStream); + 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; @@ -1414,7 +1606,8 @@ namespace AspNetCoreModule.Test string responseStatus = "NotInitialized"; var httpClientHandler = new HttpClientHandler(); - httpClientHandler.UseDefaultCredentials = true; + httpClientHandler.UseDefaultCredentials = true; + httpClientHandler.AutomaticDecompression = DecompressionMethods.None; var httpClient = new HttpClient(httpClientHandler) { diff --git a/test/AspNetCoreModule.Test/app.config b/test/AspNetCoreModule.Test/app.config index d36d43e498..49e0f8825c 100644 --- a/test/AspNetCoreModule.Test/app.config +++ b/test/AspNetCoreModule.Test/app.config @@ -26,6 +26,14 @@ + + + + + + + + - + diff --git a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj index 9da583a52b..4545c18afb 100644 --- a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj +++ b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj @@ -6,12 +6,15 @@ true true + + + + PreserveNewest - @@ -32,7 +35,6 @@ - @@ -41,6 +43,10 @@ + + + + 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 index 1a19029ad9..c5a6fce639 100644 --- a/tools/certificate.ps1 +++ b/tools/certificate.ps1 @@ -26,7 +26,7 @@ ("Result: $thumbPrint2") .\certificate.ps1 -Command Export-CertificateTo -TargetThumbPrint $thumbPrint2 -TargetSSLStore "Cert:\LocalMachine\My" -ExportToSSLStore "Cert:\CurrentUser\My" - .\certificate.ps1 -Command Export-CertificateTo -TargetThumbPrint F97AB75DCF1C62547E4B5E7025D60001892A6A60 -ExportToSSLStore C:\gitroot\AspNetCoreModule\tools\test.pfx -PfxPassword test + .\certificate.ps1 -Command Export-CertificateTo -TargetThumbPrint $thumbPrint2 -TargetSSLStore "Cert:\LocalMachine\My" -ExportToSSLStore C:\gitroot\AspNetCoreModule\tools\test.pfx -PfxPassword test # Clean up @@ -238,6 +238,17 @@ function Export-CertificateTo($_targetThumbPrint, $_exportToSSLStore, $_password 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")) { @@ -246,33 +257,123 @@ function Export-CertificateTo($_targetThumbPrint, $_exportToSSLStore, $_password return ("Error!!! _password is required") } - $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 + 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") } } - - Export-Certificate -Cert $cert -FilePath $tempExportFile | Out-Null - if (-not (Test-Path $tempExportFile)) + + if ($isThisWin7) { - return ("Error!!! Can't export $TargetSSLStore\$_targetThumbPrint to $tempExportFile") + [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 } - # 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 if (-not (Test-Path "$_exportToSSLStore\$_targetThumbPrint")) { return ("Error!!! Can't copy $TargetSSLStore\$_targetThumbPrint to $_exportToSSLStore") 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 From 1d9dcdf7ae305cac6e314b6836c7778c44b442a9 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 15 May 2017 13:47:17 -0700 Subject: [PATCH 041/107] Install NuGet.CommandLine 3.5.0 to acquire nuget.exe --- Build/repo.targets | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Build/repo.targets b/Build/repo.targets index 2138b46789..5e22efef79 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -3,10 +3,12 @@ true $(VerifyDependsOn);PublishPackage https://dotnet.myget.org/F/aspnetcoremodule/api/v2/package + 3.5.0 + @@ -33,7 +35,7 @@ - $(RepositoryRoot).build\nuget.exe + $([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))nuget.commandline\$(NuGetCommandLineVersion)\tools\nuget.exe $(RepositoryRoot)nuget\AspNetCore.nuspec From 289ecd475dcd6585e1e613c2fdea3d12111b64fb Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 16 May 2017 10:38:17 -0700 Subject: [PATCH 042/107] Added ANCMtestFlags environment variable (#98) --- test/AspNetCoreModule.Test/FunctionalTest.cs | 50 +++++++++---------- .../FunctionalTestHelper.cs | 49 +++++++++++------- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 1ef8474b08..daed463ce2 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -12,7 +12,7 @@ namespace AspNetCoreModule.Test public class FunctionalTest : FunctionalTestHelper, IClassFixture { [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange)] @@ -23,7 +23,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5)] @@ -36,7 +36,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19)] @@ -51,7 +51,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -64,7 +64,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -75,7 +75,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -86,7 +86,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -97,7 +97,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -108,7 +108,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -119,7 +119,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("ANCMTestBar", "bar", "bar", IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -136,7 +136,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -147,7 +147,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -158,7 +158,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] @@ -169,7 +169,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -180,7 +180,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -191,7 +191,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "00:02:00")] @@ -204,7 +204,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -215,7 +215,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "dotnet.exe", "./")] @@ -228,7 +228,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -241,7 +241,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true, true)] @@ -254,7 +254,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -265,7 +265,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -276,7 +276,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -291,7 +291,7 @@ namespace AspNetCoreModule.Test // NOTE: below test scenarios are not valid for Win7 OS ////////////////////////////////////////////////////////// [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "IIS does not support Websocket on Win7")] @@ -303,7 +303,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("RunAsAdministratorAndX64Bitness")] + [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "WAS does not handle private memory limitation with Job object on Win7")] diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 8c206a7dfd..ad2da5c40c 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -37,29 +37,42 @@ namespace AspNetCoreModule.Test get { bool result = true; - if (_environmentVariableName == "RunAsAdministratorAndX64Bitness") + if (_environmentVariableName == "%ANCMTestFlags%") { - try + var envValue = Environment.ExpandEnvironmentVariables(_environmentVariableName); + if (envValue == "") { - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) - { - throw new System.InvalidOperationException("this should be started with x64 process mode on 64 bit machine"); - } - - bool isElevated; - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); - if (!isElevated) - { - throw new System.ApplicationException("this should be started as an administrator"); - } + envValue = "AdminAnd64Bit"; } - catch (Exception ex) + switch (envValue) { - AdditionalInfo = ex.Message; + case "AdminAnd64Bit": + try + { + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) + { + throw new System.InvalidOperationException("this should be started with x64 process mode on 64 bit machine"); + } - result = false; + bool isElevated; + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new WindowsPrincipal(identity); + isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); + if (!isElevated) + { + throw new System.ApplicationException("this should be started as an administrator"); + } + } + catch (Exception ex) + { + AdditionalInfo = ex.Message; + + result = false; + } + break; + case "SkipTest": + result = false; + break; } } return result; From c7e17d13f371c050bf861cbc0d1308650f815c42 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 16 May 2017 17:08:14 -0700 Subject: [PATCH 043/107] ANCM test update to fix test issues (#99) --- .../Framework/IISConfigUtility.cs | 6 +-- .../Framework/InitializeTestMachine.cs | 46 ++++++++++++++----- .../Framework/TestUtility.cs | 40 +++++++++++----- .../Framework/TestWebSite.cs | 5 +- .../FunctionalTestHelper.cs | 2 +- 5 files changed, 70 insertions(+), 29 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index 5e122c900b..284ef9148a 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -81,7 +81,7 @@ namespace AspNetCoreModule.Test.Framework public static void RestoreAppHostConfig(bool restoreFromMasterBackupFile = true) { - string masterBackupFileExtension = ".ancmtest.mastebackup"; + string masterBackupFileExtension = ".ancmtest.masterbackup"; string masterBackupFilePath = Strings.AppHostConfigPath + masterBackupFileExtension; string temporaryBackupFileExtenstion = null; string temporaryBackupFilePath = null; @@ -532,14 +532,14 @@ namespace AspNetCoreModule.Test.Framework bool result = true; if (servertype == ServerType.IIS) { - if (!File.Exists(InitializeTestMachine.IISAspnetcoreSchema_path)) + if (!File.Exists(InitializeTestMachine.FullIisAspnetcoreSchema_path)) { result = false; } } else { - if (!File.Exists(InitializeTestMachine.IISExpressAspnetcoreSchema_path)) + if (!File.Exists(InitializeTestMachine.IisExpressAspnetcoreSchema_path)) { result = false; } diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 12df0ca755..0b9aee8cbf 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -17,24 +17,34 @@ namespace AspNetCoreModule.Test.Framework public static int SiteId = 40000; public const string PrivateFileName = "aspnetcore_private.dll"; - public static string Aspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", PrivateFileName); - public static string Aspnetcore_path_original = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "aspnetcore.dll"); - public static string Aspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "inetsrv", PrivateFileName); - public static string IISExpressAspnetcore_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", PrivateFileName); - public static string IISExpressAspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", PrivateFileName); + 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 = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", PrivateFileName); + public static string IisExpressAspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", PrivateFileName); - public static string IISExpressAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", "config", "schema", "aspnetcore_schema.xml"); - public static string IISAspnetcoreSchema_path = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "inetsrv", "config", "schema", "aspnetcore_schema.xml"); + 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 void CheckPerquisiteForANCMTEst() + { + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) + { + throw new System.InvalidOperationException(@"ANCM test should be started with x64 process mode on 64 bit machine; if you run this test on Visual Studio, you should set X64 first after selecting 'Test -> Test Settings -> Default Process Architecture' menu"); + } + } public InitializeTestMachine() { _referenceCount++; if (_referenceCount == 1) { + CheckPerquisiteForANCMTEst(); + TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Start"); _InitializeTestMachineCompleted = false; @@ -160,7 +170,7 @@ namespace AspNetCoreModule.Test.Framework { using (var iisConfig = new IISConfigUtility(ServerType.IIS, null)) { - iisConfig.AddModule("AspNetCoreModule", Aspnetcore_path, null); + iisConfig.AddModule("AspNetCoreModule", FullIisAspnetcore_path, null); } } } @@ -269,16 +279,28 @@ namespace AspNetCoreModule.Test.Framework TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); TestUtility.ResetHelper(ResetHelperMode.StopW3svcStartW3svc); Thread.Sleep(1000); + string from = Path.Combine(outputPath, "x64", "aspnetcore.dll"); - TestUtility.FileCopy(from, Aspnetcore_path, overWrite:true, ignoreExceptionWhileDeletingExistingFile:false); - TestUtility.FileCopy(from, IISExpressAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + TestUtility.FileCopy(from, FullIisAspnetcore_path, overWrite:true, ignoreExceptionWhileDeletingExistingFile:false); + TestUtility.FileCopy(from, IisExpressAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + + // NOTE: schema file can't be overwritten, if there is any schema change, that should be updated manually + from = Path.Combine(outputPath, "x64", "aspnetcore_schema.xml"); + TestUtility.FileCopy(from, FullIisAspnetcoreSchema_path, overWrite: false, ignoreExceptionWhileDeletingExistingFile: false); + TestUtility.FileCopy(from, IisExpressAspnetcoreSchema_path, overWrite: false, ignoreExceptionWhileDeletingExistingFile: false); if (TestUtility.IsOSAmd64) { from = Path.Combine(outputPath, "Win32", "aspnetcore.dll"); - TestUtility.FileCopy(from, Aspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); - TestUtility.FileCopy(from, IISExpressAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + TestUtility.FileCopy(from, FullIisAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + TestUtility.FileCopy(from, IisExpressAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + + // NOTE: schema file can't be overwritten, if there is any schema change, that should be updated manually + from = Path.Combine(outputPath, "Win32", "aspnetcore_schema.xml"); + TestUtility.FileCopy(from, IisExpressAspnetcoreSchema_X86_path, overWrite: false, ignoreExceptionWhileDeletingExistingFile: false); } + + updateSuccess = true; } catch diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index e7373e2299..67c6947e0f 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -437,25 +437,41 @@ namespace AspNetCoreModule.Test.Framework public static string GetMakeCertPath() { string makecertExeFilePath = "makecert.exe"; - var makecertExeFilePaths = new string[] + var makecertExeFilePaths = new Dictionary(); + makecertExeFilePaths.Add("default", Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits")); + if (IsOSAmd64) { - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.1", "bin", "x64", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.1", "bin", "x86", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits", "8.0", "bin", "x64", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows Kits", "8.0", "bin", "x86", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows SKDs", "Windows", "v7.1A", "bin", "x64", "makecert.exe"), - Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Windows SKDs", "Windows", "v7.1A", "bin", "makecert.exe") - }; + makecertExeFilePaths.Add("wow64mode", Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "Windows Kits")); + } - foreach (string item in makecertExeFilePaths) + foreach (var item in makecertExeFilePaths) { - if (File.Exists(item)) + string[] files = null; + if (!Directory.Exists(item.Value)) { - makecertExeFilePath = item; + 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; } diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index cf062ef1e9..6c887dff34 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -187,6 +187,10 @@ namespace AspNetCoreModule.Test.Framework 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, publishPathOutput, tempId, tempId); iisConfig.SetANCMConfig(tempSiteName, "/", "arguments", Path.Combine(publishPathOutput, argumentFileName)); iisConfig.DeleteSite(tempSiteName); @@ -322,7 +326,6 @@ namespace AspNetCoreModule.Test.Framework catch { statusCode = "ExceptionError"; - } if ("200" == statusCode) { diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index ad2da5c40c..51e12a52c8 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -40,7 +40,7 @@ namespace AspNetCoreModule.Test if (_environmentVariableName == "%ANCMTestFlags%") { var envValue = Environment.ExpandEnvironmentVariables(_environmentVariableName); - if (envValue == "") + if (string.IsNullOrEmpty(envValue)) { envValue = "AdminAnd64Bit"; } From ec9085314737c6b64f442fc827d3b1771ec12732 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Wed, 17 May 2017 12:57:39 -0700 Subject: [PATCH 044/107] Disable using privateAspNetCoreFile to test release candiate build (#100) --- test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 0b9aee8cbf..7ee8217519 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -13,7 +13,7 @@ namespace AspNetCoreModule.Test.Framework // // By default, we use the private AspNetCoreFile which were created from this solution // - public static bool UsePrivateAspNetCoreFile = true; + public static bool UsePrivateAspNetCoreFile = false; public static int SiteId = 40000; public const string PrivateFileName = "aspnetcore_private.dll"; From 88cc1c14d0f80e0cecee595df8408170353fe54c Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Wed, 17 May 2017 16:40:29 -0700 Subject: [PATCH 045/107] Added a new environment variable value UsePrivateAspNetCoreFile for ANCMTestFlags and fixing test issues (#101) --- .../Framework/InitializeTestMachine.cs | 58 ++++++++++++++++--- .../Framework/TestWebSite.cs | 2 +- test/AspNetCoreModule.Test/FunctionalTest.cs | 4 +- .../FunctionalTestHelper.cs | 33 +++++++---- tools/certificate.ps1 | 1 + 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 7ee8217519..2f960f45e8 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -10,10 +10,44 @@ namespace AspNetCoreModule.Test.Framework { public class InitializeTestMachine : IDisposable { - // - // By default, we use the private AspNetCoreFile which were created from this solution - // - public static bool UsePrivateAspNetCoreFile = false; + public const string ANCMTestFlagsEnvironmentVariable = "%ANCMTestFlags%"; + public const string ANCMTestFlagsDefaultContext = "AdminAnd64Bit"; + public const string ANCMTestFlagsTestSkipContext = "SkipTest"; + public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivateAspNetCoreFile"; + + private static bool? _usePrivateAspNetCoreFile = null; + public static bool? UsePrivateAspNetCoreFile + { + get { + // + // By default, we don't use the private AspNetCore.dll that is compiled with this solution. + // In order to use the private file, you should add 'UsePrivateAspNetCoreFile' flag to the Environmnet variable %ANCMTestFlag%. + // + // Set ANCMTestFlag=%ANCMTestFlag%;UsePrivateAspNetCoreFile + // Or + // $Env:ANCMTestFlag=$Env:ANCMTestFlag + ";UsePrivateAspNetCoreFile" + // + if (_usePrivateAspNetCoreFile == null) + { + _usePrivateAspNetCoreFile = false; + var envValue = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); + if (envValue.ToLower().Contains(ANCMTestFlagsUsePrivateAspNetCoreFileContext.ToLower())) + { + TestUtility.LogInformation("PrivateAspNetCoreFile is set"); + _usePrivateAspNetCoreFile = true; + } + else + { + TestUtility.LogInformation("PrivateAspNetCoreFile is not set"); + } + } + return _usePrivateAspNetCoreFile; + } + set + { + _usePrivateAspNetCoreFile = value; + } + } public static int SiteId = 40000; public const string PrivateFileName = "aspnetcore_private.dll"; @@ -30,12 +64,18 @@ namespace AspNetCoreModule.Test.Framework private static bool _InitializeTestMachineCompleted = false; private string _setupScriptPath = null; - private void CheckPerquisiteForANCMTEst() + private bool CheckPerquisiteForANCMTest() { + bool result = true; + TestUtility.LogInformation("CheckPerquisiteForANCMTest(): Environment.Is64BitOperatingSystem: {0}, Environment.Is64BitProcess {1}", Environment.Is64BitOperatingSystem, Environment.Is64BitProcess); + TestUtility.LogInformation("%ANCMTestFlags%: {0}", Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable)); + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) { - throw new System.InvalidOperationException(@"ANCM test should be started with x64 process mode on 64 bit machine; if you run this test on Visual Studio, you should set X64 first after selecting 'Test -> Test Settings -> Default Process Architecture' menu"); + TestUtility.LogInformation("CheckPerquisiteForANCMTest() Failed: ANCM test should be started with x64 process mode on 64 bit machine; if you run this test on Visual Studio, you should set X64 first after selecting 'Test -> Test Settings -> Default Process Architecture' menu"); + result = false; } + return result; } public InitializeTestMachine() { @@ -43,7 +83,7 @@ namespace AspNetCoreModule.Test.Framework if (_referenceCount == 1) { - CheckPerquisiteForANCMTEst(); + CheckPerquisiteForANCMTest(); TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Start"); @@ -161,7 +201,7 @@ namespace AspNetCoreModule.Test.Framework } } - if (InitializeTestMachine.UsePrivateAspNetCoreFile) + if (InitializeTestMachine.UsePrivateAspNetCoreFile == true) { PreparePrivateANCMFiles(); @@ -267,7 +307,7 @@ namespace AspNetCoreModule.Test.Framework } // create an extra private copy of the private file on IIS directory - if (InitializeTestMachine.UsePrivateAspNetCoreFile) + if (InitializeTestMachine.UsePrivateAspNetCoreFile == true) { bool updateSuccess = false; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index 6c887dff34..851ee2491a 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -270,7 +270,7 @@ namespace AspNetCoreModule.Test.Framework } } - if (InitializeTestMachine.UsePrivateAspNetCoreFile && IisServerType == ServerType.IISExpress) + if (InitializeTestMachine.UsePrivateAspNetCoreFile == true && IisServerType == ServerType.IISExpress) { iisConfig.AddModule("AspNetCoreModule", ("%IIS_BIN%\\" + InitializeTestMachine.PrivateFileName), null); } diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index daed463ce2..7488278f03 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -295,8 +295,10 @@ namespace AspNetCoreModule.Test [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "IIS does not support Websocket on Win7")] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] + [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); diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 51e12a52c8..0e9c0fcd0a 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -26,7 +26,6 @@ namespace AspNetCoreModule.Test public class ANCMTestSkipCondition : Attribute, ITestCondition { private readonly string _environmentVariableName; - public ANCMTestSkipCondition(string environmentVariableName) { _environmentVariableName = environmentVariableName; @@ -37,16 +36,24 @@ namespace AspNetCoreModule.Test get { bool result = true; - if (_environmentVariableName == "%ANCMTestFlags%") + if (_environmentVariableName == InitializeTestMachine.ANCMTestFlagsEnvironmentVariable) { var envValue = Environment.ExpandEnvironmentVariables(_environmentVariableName); if (string.IsNullOrEmpty(envValue)) { - envValue = "AdminAnd64Bit"; + envValue = InitializeTestMachine.ANCMTestFlagsDefaultContext; } - switch (envValue) + else { - case "AdminAnd64Bit": + envValue += ";" + InitializeTestMachine.ANCMTestFlagsDefaultContext; + } + + // split tokens with ';' + var tokens = envValue.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string token in tokens) + { + if (token.Equals(InitializeTestMachine.ANCMTestFlagsDefaultContext, StringComparison.InvariantCultureIgnoreCase)) + { try { if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) @@ -69,10 +76,12 @@ namespace AspNetCoreModule.Test result = false; } - break; - case "SkipTest": + } + if (token.Equals(InitializeTestMachine.ANCMTestFlagsTestSkipContext, StringComparison.InvariantCultureIgnoreCase)) + { + AdditionalInfo = InitializeTestMachine.ANCMTestFlagsTestSkipContext + " is set"; result = false; - break; + } } } return result; @@ -242,7 +251,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); DateTime startTime = DateTime.Now; - Thread.Sleep(500); + Thread.Sleep(1100); string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); @@ -443,7 +452,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); DateTime startTime = DateTime.Now; - Thread.Sleep(500); + Thread.Sleep(1100); // verify 503 string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; @@ -1332,9 +1341,11 @@ namespace AspNetCoreModule.Test Thread.Sleep(500); VerifySendingWebSocketData(websocketClient, testData); - Thread.Sleep(500); + frameReturned = websocketClient.Close(); + Thread.Sleep(500); + Assert.True(frameReturned.FrameType == FrameType.Close, "Closing Handshake"); } diff --git a/tools/certificate.ps1 b/tools/certificate.ps1 index c5a6fce639..52c8817796 100644 --- a/tools/certificate.ps1 +++ b/tools/certificate.ps1 @@ -374,6 +374,7 @@ $([Convert]::ToBase64String($certificate.Export('Cert'), [System.Base64Formattin 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") From bce531f61a051c09c9fc2b38a7e72cee6123c95f Mon Sep 17 00:00:00 2001 From: pan-wang Date: Tue, 23 May 2017 17:25:45 -0700 Subject: [PATCH 046/107] add features: flowing authentication info, hosting environment variable support; fix client disconnect and app_offline issues (#102) resubmit --- src/AspNetCore/AspNetCore.vcxproj | 3 +- src/AspNetCore/Inc/application.h | 21 +- src/AspNetCore/Inc/aspnetcoreconfig.h | 65 +- src/AspNetCore/Inc/aspnetcoreutils.h | 25 - src/AspNetCore/Inc/environmentvariablehash.h | 146 ++ src/AspNetCore/Inc/forwardinghandler.h | 29 +- src/AspNetCore/Inc/protocolconfig.h | 7 - src/AspNetCore/Inc/serverprocess.h | 147 +- src/AspNetCore/Src/application.cxx | 54 +- src/AspNetCore/Src/aspnetcoreconfig.cxx | 441 ++--- src/AspNetCore/Src/aspnetcoreutils.cxx | 55 - src/AspNetCore/Src/filewatcher.cxx | 2 +- src/AspNetCore/Src/forwardinghandler.cxx | 1153 ++++++----- src/AspNetCore/Src/precomp.hxx | 2 +- src/AspNetCore/Src/processmanager.cxx | 3 + src/AspNetCore/Src/protocolconfig.cxx | 1 - src/AspNetCore/Src/serverprocess.cxx | 1831 ++++++++++-------- src/AspNetCore/Src/websockethandler.cxx | 94 +- 18 files changed, 2254 insertions(+), 1825 deletions(-) delete mode 100644 src/AspNetCore/Inc/aspnetcoreutils.h create mode 100644 src/AspNetCore/Inc/environmentvariablehash.h delete mode 100644 src/AspNetCore/Src/aspnetcoreutils.cxx diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index c795aed3da..192d0f3fdb 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -157,7 +157,7 @@ - + @@ -177,7 +177,6 @@ - diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h index b8b6d548bc..ba393d66e7 100644 --- a/src/AspNetCore/Inc/application.h +++ b/src/AspNetCore/Inc/application.h @@ -70,10 +70,12 @@ public: VOID ) { - BOOL fResult = FALSE; + BOOL fResult = TRUE; LARGE_INTEGER li = {0}; CHAR *pszBuff = NULL; - HANDLE handle = CreateFile( m_Path.QueryStr(), + HANDLE handle = INVALID_HANDLE_VALUE; + + handle = CreateFile( m_Path.QueryStr(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, @@ -81,8 +83,15 @@ public: FILE_ATTRIBUTE_NORMAL, NULL ); - if( handle == 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; } @@ -91,8 +100,6 @@ public: goto Finished; } - fResult = TRUE; - if( li.HighPart != 0 ) { // > 4gb file size not supported @@ -113,10 +120,10 @@ public: } Finished: - if( handle ) + if( handle != INVALID_HANDLE_VALUE ) { CloseHandle(handle); - handle = NULL; + handle = INVALID_HANDLE_VALUE; } if( pszBuff != NULL ) diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h index b05491b4ec..e83e4301ff 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -5,6 +5,10 @@ #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" @@ -34,7 +38,6 @@ extern HTTP_MODULE_ID g_pModuleId; extern IHttpServer * g_pHttpServer; - class ASPNETCORE_CONFIG : IHttpStoredContext { public: @@ -54,13 +57,13 @@ public: _In_ IHttpContext *pHttpContext, _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig ); - - MULTISZ* + + ENVIRONMENT_VAR_HASH* QueryEnvironmentVariables( VOID ) { - return &m_mszEnvironment; + return m_pEnvironmentVariables; } DWORD @@ -139,6 +142,24 @@ public: return m_fForwardWindowsAuthToken; } + BOOL + QueryWindowsAuthEnabled() + { + return m_fWindowsAuthEnabled; + } + + BOOL + QueryBasicAuthEnabled() + { + return m_fBasicAuthEnabled; + } + + BOOL + QueryAnonymousAuthEnabled() + { + return m_fAnonymousAuthEnabled; + } + BOOL QueryDisableStartUpErrorPage() { @@ -155,10 +176,10 @@ private: // // private constructor - // - + // ASPNETCORE_CONFIG(): - m_fStdoutLogEnabled( FALSE ) + m_fStdoutLogEnabled( FALSE ), + m_pEnvironmentVariables( NULL ) { } @@ -167,18 +188,20 @@ private: IHttpContext *pHttpContext ); - DWORD m_dwRequestTimeoutInMS; - DWORD m_dwStartupTimeLimitInMS; - DWORD m_dwShutdownTimeLimitInMS; - MULTISZ m_mszEnvironment; - DWORD m_dwRapidFailsPerMinute; - STRU m_struApplication; - STRU m_struArguments; - STRU m_struProcessPath; - BOOL m_fStdoutLogEnabled; - STRU m_struStdoutLogFile; - DWORD m_dwProcessesPerApplication; - BOOL m_fForwardWindowsAuthToken; - BOOL m_fDisableStartUpErrorPage; - MULTISZ m_mszRecycleOnFileChangeFiles; + DWORD m_dwRequestTimeoutInMS; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + DWORD m_dwRapidFailsPerMinute; + DWORD m_dwProcessesPerApplication; + STRU m_struApplication; + STRU m_struArguments; + STRU m_struProcessPath; + STRU m_struStdoutLogFile; + BOOL m_fStdoutLogEnabled; + BOOL m_fForwardWindowsAuthToken; + BOOL m_fDisableStartUpErrorPage; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; }; diff --git a/src/AspNetCore/Inc/aspnetcoreutils.h b/src/AspNetCore/Inc/aspnetcoreutils.h deleted file mode 100644 index 2f54e180a3..0000000000 --- a/src/AspNetCore/Inc/aspnetcoreutils.h +++ /dev/null @@ -1,25 +0,0 @@ -// 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 ASPNETCORE_UTILS -{ -public: - - static - HRESULT - ReplacePlaceHolderWithValue( - _Inout_ LPWSTR pszStr, - _In_ LPWSTR pszPlaceholder, - _In_ DWORD cchPlaceholder, - _In_ DWORD dwValue, - _In_ DWORD dwNumDigitsInValue, - _Out_ BOOL *pfReplaced - ); - -private: - ASPNETCORE_UTILS() - { - } -}; \ No newline at end of file diff --git a/src/AspNetCore/Inc/environmentvariablehash.h b/src/AspNetCore/Inc/environmentvariablehash.h new file mode 100644 index 0000000000..8914999935 --- /dev/null +++ b/src/AspNetCore/Inc/environmentvariablehash.h @@ -0,0 +1,146 @@ +// 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); + strTemp.Copy(pEntry->QueryName()); + strTemp.Append(pEntry->QueryValue()); + pMultiSz->Append(strTemp.QueryStr()); + } + + static + VOID + CopyToTable( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); + pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); + ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); + pHash->InsertRecord(pNewEntry); + } + +private: + ENVIRONMENT_VAR_HASH(const ENVIRONMENT_VAR_HASH &); + void operator=(const ENVIRONMENT_VAR_HASH &); +}; diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h index bc655526be..26c8723f57 100644 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -1,19 +1,8 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + #pragma once -/*++ - -Copyright (c) 2013 Microsoft Corporation - -Module Name: - - forwardinghandler.h - -Abstract: - - Handler for handling URLs from out-of-box. - ---*/ - #include "forwarderconnection.h" #include "protocolconfig.h" #include "serverprocess.h" @@ -109,7 +98,13 @@ public: 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, @@ -186,7 +181,6 @@ private: __in const PROTOCOL_CONFIG * pProtocol, __in HINTERNET hConnect, __inout STRU * pstrUrl, - const STRU& strDestination, ASPNETCORE_CONFIG* pAspNetCoreConfig, SERVER_PROCESS* pServerProcess ); @@ -201,7 +195,6 @@ private: HRESULT GetHeaders( const PROTOCOL_CONFIG * pProtocol, - PCWSTR pszDestination, PCWSTR * ppszHeaders, DWORD * pcchHeaders, ASPNETCORE_CONFIG* pAspNetCoreConfig, @@ -325,6 +318,10 @@ private: bool m_fHandleClosedDueToClient; bool m_fResponseHeadersReceivedAndSet; BOOL m_fDoReverseRewriteHeaders; + BOOL m_fErrorHandled; + BOOL m_fWebSocketUpgrade; + BOOL m_fFinishRequest; + BOOL m_fClientDisconnected; DWORD m_msStartTime; DWORD m_BytesToReceive; diff --git a/src/AspNetCore/Inc/protocolconfig.h b/src/AspNetCore/Inc/protocolconfig.h index f7d915d6a9..7cc0dc03d7 100644 --- a/src/AspNetCore/Inc/protocolconfig.h +++ b/src/AspNetCore/Inc/protocolconfig.h @@ -33,12 +33,6 @@ class PROTOCOL_CONFIG return m_msTimeout; } - BOOL - QueryPreserveHostHeader() const - { - return m_fPreserveHostHeader; - } - BOOL QueryReverseRewriteHeaders() const { @@ -90,7 +84,6 @@ class PROTOCOL_CONFIG private: BOOL m_fKeepAlive; - BOOL m_fPreserveHostHeader; BOOL m_fReverseRewriteHeaders; BOOL m_fIncludePortInXForwardedFor; diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index 458b13575c..8d3f27cb33 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -6,14 +6,21 @@ #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_PLACEHOLDER L"%ASPNETCORE_PORT%" -#define ASPNETCORE_PORT_PLACEHOLDER_CCH 17 -#define ASPNETCORE_DEBUG_PORT_STR L"ASPNETCORE_DEBUG_PORT" -#define ASPNETCORE_DEBUG_PORT_PLACEHOLDER L"%ASPNETCORE_DEBUG_PORT%" -#define ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH 23 -#define MAX_ACTIVE_CHILD_PROCESSES 16 +#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 FORWARDER_CONNECTION; @@ -25,14 +32,17 @@ public: HRESULT Initialize( - _In_ PROCESS_MANAGER *pProcessManager, - _In_ STRU *pszProcessExePath, - _In_ STRU *pszArguments, - _In_ DWORD dwStartupTimeLimitInMS, - _In_ DWORD dwShtudownTimeLimitInMS, - _In_ MULTISZ *pszEnvironment, - _In_ BOOL fStdoutLogEnabled, - _In_ STRU *pstruStdoutLogFile + _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 ); @@ -66,12 +76,6 @@ public: return m_dwPort; } - DWORD - GetDebugPort() - { - return m_dwDebugPort; - } - VOID ReferenceServerProcess( VOID @@ -118,6 +122,12 @@ public: _In_ PTP_TIMER Timer ); + LPCWSTR + QueryPortStr() + { + return m_struPort.QueryStr(); + } + LPCWSTR QueryFullLogPath() { @@ -159,12 +169,6 @@ private: _In_ LPSTARTUPINFOW pStartupInfo ); - HRESULT - CheckIfServerIsUp( - _In_ DWORD dwPort, - _Out_ BOOL *pfReady - ); - HRESULT CheckIfServerIsUp( _In_ DWORD dwPort, @@ -182,13 +186,49 @@ private: GetChildProcessHandles( ); - DWORD - GenerateRandomPort( - VOID - ) - { - return (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1; - } + HRESULT + SetupListenPort( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable + ); + + HRESULT + SetupAppPath( + IHttpContext* pContext, + 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( + const STRU* const pStruCommandline, + STRU* pStruErrorMessage + ); + + HRESULT + GetRandomPort( + DWORD* pdwPickedPort, + DWORD dwExcludedPort + ); DWORD GetNumberOfDigits( @@ -213,44 +253,51 @@ private: } FORWARDER_CONNECTION *m_pForwarderConnection; - HANDLE m_hJobObject; BOOL m_fStdoutLogEnabled; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + + STTIMER m_Timer; + SOCKET m_socket; + STRU m_struLogFile; STRU m_struFullLogFile; - STTIMER m_Timer; - HANDLE m_hStdoutHandle; - volatile LONG m_lStopping; - volatile BOOL m_fReady; - CRITICAL_SECTION m_csLock; - SOCKET m_socket; - DWORD m_dwPort; - DWORD m_dwDebugPort; STRU m_ProcessPath; STRU m_Arguments; + STRU m_struAppPath; + STRU m_struAppFullPath; + STRU m_struPort; + STRU m_pszRootApplicationPath; + volatile LONG m_lStopping; + volatile BOOL m_fReady; + mutable LONG m_cRefs; + + DWORD m_dwPort; DWORD m_dwStartupTimeLimitInMS; DWORD m_dwShutdownTimeLimitInMS; - MULTISZ m_Environment; - mutable LONG m_cRefs; - HANDLE m_hProcessWaitHandle; DWORD m_cChildProcess; - HANDLE m_hChildProcessWaitHandles[MAX_ACTIVE_CHILD_PROCESSES]; + 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; // // m_hChildProcessHandle is the handle to process created by // m_hProcessHandle process if it does. // - HANDLE m_hChildProcessHandles[MAX_ACTIVE_CHILD_PROCESSES]; - DWORD m_dwChildProcessIds[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/AspNetCore/Src/application.cxx b/src/AspNetCore/Src/application.cxx index 0312c3f32f..b723162814 100644 --- a/src/AspNetCore/Src/application.cxx +++ b/src/AspNetCore/Src/application.cxx @@ -5,6 +5,12 @@ APPLICATION::~APPLICATION() { + if (m_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->DereferenceAppOfflineHtm(); + m_pAppOfflineHtm = NULL; + } + if (m_pFileWatcherEntry != NULL) { // Mark the entry as invalid, @@ -109,6 +115,7 @@ APPLICATION::UpdateAppOfflineFileHandle() STRU strFilePath; PATH::ConvertPathToFullPath(L".\\app_offline.htm", m_strAppPhysicalPath.QueryStr(), &strFilePath); APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; + APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL; if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr()) && GetLastError() == ERROR_FILE_NOT_FOUND) { @@ -117,30 +124,41 @@ APPLICATION::UpdateAppOfflineFileHandle() else { m_fAppOfflineFound = TRUE; - APP_OFFLINE_HTM *pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); - DBG_ASSERT(pNewAppOfflineHtm != NULL); + // + // send shutdown signal + // - if (pNewAppOfflineHtm->Load()) + // The reason why we send the shutdown signal before loading the new app_offline file is because we want to make some delay + // before reading the appoffline.htm so that the file change can be done on time. + if (m_pProcessManager != NULL) { - // - // loaded new app offline htm - // - pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); + m_pProcessManager->SendShutdownSignal(); + } - // - // send shutdown signal to the app - // - if (m_pProcessManager != NULL) + pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); + + if ( pNewAppOfflineHtm != NULL ) + { + if (pNewAppOfflineHtm->Load()) { - m_pProcessManager->SendShutdownSignal(); + // + // 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; } } } - - if (pOldAppOfflineHtm != NULL) - { - pOldAppOfflineHtm->DereferenceAppOfflineHtm(); - pOldAppOfflineHtm = NULL; - } } \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index 6e65fb555a..e9a7c54877 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -12,6 +12,12 @@ ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() { APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); } + if(m_pEnvironmentVariables != NULL) + { + m_pEnvironmentVariables->Clear(); + delete m_pEnvironmentVariables; + m_pEnvironmentVariables = NULL; + } } HRESULT @@ -24,7 +30,7 @@ ASPNETCORE_CONFIG::GetConfig( IHttpApplication *pHttpApplication = pHttpContext->GetApplication(); ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; - if( ppAspNetCoreConfig == NULL) + if (ppAspNetCoreConfig == NULL) { hr = E_INVALIDARG; goto Finished; @@ -33,10 +39,10 @@ ASPNETCORE_CONFIG::GetConfig( *ppAspNetCoreConfig = NULL; // potential bug if user sepcific config at virtual dir level - pAspNetCoreConfig = (ASPNETCORE_CONFIG*) + pAspNetCoreConfig = (ASPNETCORE_CONFIG*) pHttpApplication->GetModuleContextContainer()->GetModuleContext(g_pModuleId); - if( pAspNetCoreConfig != NULL ) + if (pAspNetCoreConfig != NULL) { *ppAspNetCoreConfig = pAspNetCoreConfig; pAspNetCoreConfig = NULL; @@ -44,31 +50,31 @@ ASPNETCORE_CONFIG::GetConfig( } pAspNetCoreConfig = new ASPNETCORE_CONFIG; - if( pAspNetCoreConfig == NULL ) + if (pAspNetCoreConfig == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - hr = pAspNetCoreConfig->Populate( pHttpContext ); - if( FAILED( hr ) ) + hr = pAspNetCoreConfig->Populate(pHttpContext); + if (FAILED(hr)) { goto Finished; } hr = pHttpApplication->GetModuleContextContainer()-> - SetModuleContext( pAspNetCoreConfig, g_pModuleId ); - if( FAILED( hr ) ) + SetModuleContext(pAspNetCoreConfig, g_pModuleId); + if (FAILED(hr)) { - if( hr == HRESULT_FROM_WIN32( ERROR_ALREADY_ASSIGNED ) ) + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) { delete pAspNetCoreConfig; - pAspNetCoreConfig = (ASPNETCORE_CONFIG*) pHttpApplication-> - GetModuleContextContainer()-> - GetModuleContext( g_pModuleId ); + pAspNetCoreConfig = (ASPNETCORE_CONFIG*)pHttpApplication-> + GetModuleContextContainer()-> + GetModuleContext(g_pModuleId); - _ASSERT( pAspNetCoreConfig != NULL ); + _ASSERT(pAspNetCoreConfig != NULL); hr = S_OK; } @@ -79,8 +85,8 @@ ASPNETCORE_CONFIG::GetConfig( } else { - // set appliction info here instead of inside Populate() - // as the destructor will delete the backend process + // 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)) { @@ -93,7 +99,7 @@ ASPNETCORE_CONFIG::GetConfig( Finished: - if( pAspNetCoreConfig != NULL ) + if (pAspNetCoreConfig != NULL) { delete pAspNetCoreConfig; pAspNetCoreConfig = NULL; @@ -102,82 +108,134 @@ Finished: return hr; } -VOID ReverseMultisz( MULTISZ * pmszInput, - LPCWSTR pszStr, - MULTISZ * pmszOutput ) -{ - if(pszStr == NULL) return; - - ReverseMultisz( pmszInput, pmszInput->Next( pszStr ), pmszOutput ); - - pmszOutput->Append( pszStr ); -} - HRESULT ASPNETCORE_CONFIG::Populate( IHttpContext *pHttpContext ) { HRESULT hr = S_OK; - STACK_STRU ( strSiteConfigPath, 256); + STACK_STRU(strSiteConfigPath, 256); STRU strEnvName; STRU strEnvValue; - STRU strFullEnvVar; + STRU strExpandedEnvValue; IAppHostAdminManager *pAdminManager = NULL; IAppHostElement *pAspNetCoreElement = NULL; + IAppHostElement *pWindowsAuthenticationElement = NULL; + IAppHostElement *pBasicAuthenticationElement = NULL; + IAppHostElement *pAnonymousAuthenticationElement = NULL; IAppHostElement *pEnvVarList = NULL; - IAppHostElementCollection *pEnvVarCollection = NULL; IAppHostElement *pEnvVar = NULL; - //IAppHostElement *pRecycleOnFileChangeFileList = NULL; - //IAppHostElementCollection *pRecycleOnFileChangeFileCollection = NULL; - //IAppHostElement *pRecycleOnFileChangeFile = NULL; + IAppHostElementCollection *pEnvVarCollection = NULL; ULONGLONG ullRawTimeSpan = 0; ENUM_INDEX index; - STRU strExpandedEnvValue; - MULTISZ mszEnvironment; - MULTISZ mszEnvironmentListReverse; - MULTISZ mszEnvNames; - LPWSTR pszEnvName; - LPCWSTR pcszEnvName; - LPCWSTR pszEnvString; - STRU strFilePath; + ENVIRONMENT_VAR_ENTRY* pEntry = 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 = g_pHttpServer->GetAdminManager(); - hr = strSiteConfigPath.Copy( pHttpContext->GetApplication()->GetAppConfigPath() ); - if( FAILED( hr ) ) + hr = strSiteConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); + if (FAILED(hr)) { goto Finished; } - hr = pAdminManager->GetAdminSection( CS_ASPNETCORE_SECTION, - strSiteConfigPath.QueryStr(), - &pAspNetCoreElement ); - if( FAILED( hr ) ) + hr = pAdminManager->GetAdminSection(CS_WINDOWS_AUTHENTICATION_SECTION, + strSiteConfigPath.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; + } + } + + hr = pAdminManager->GetAdminSection(CS_BASIC_AUTHENTICATION_SECTION, + strSiteConfigPath.QueryStr(), + &pBasicAuthenticationElement); + if (FAILED(hr)) + { + m_fBasicAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pBasicAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fBasicAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = pAdminManager->GetAdminSection(CS_ANONYMOUS_AUTHENTICATION_SECTION, + strSiteConfigPath.QueryStr(), + &pAnonymousAuthenticationElement); + if (FAILED(hr)) + { + m_fAnonymousAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pAnonymousAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fAnonymousAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = pAdminManager->GetAdminSection(CS_ASPNETCORE_SECTION, + strSiteConfigPath.QueryStr(), + &pAspNetCoreElement); + if (FAILED(hr)) { goto Finished; } - hr = GetElementStringProperty( pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_EXE_PATH, - &m_struProcessPath ); - if( FAILED( hr ) ) + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_EXE_PATH, + &m_struProcessPath); + if (FAILED(hr)) { goto Finished; } - hr = GetElementStringProperty( pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_ARGUMENTS, - &m_struArguments ); - if( FAILED( hr ) ) + 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 ) ) + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE, + &m_dwRapidFailsPerMinute); + if (FAILED(hr)) { goto Finished; } @@ -185,298 +243,173 @@ ASPNETCORE_CONFIG::Populate( // // rapidFailsPerMinute cannot be greater than 100. // - - if(m_dwRapidFailsPerMinute > MAX_RAPID_FAILS_PER_MINUTE) + 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 ) ) + 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 ) ) + 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 ) ) + 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); + 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 ) ) + + 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 ) ) + 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 ) ) + 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 ) ) + hr = GetElementChildByName(pAspNetCoreElement, + CS_ASPNETCORE_ENVIRONMENT_VARIABLES, + &pEnvVarList); + if (FAILED(hr)) { goto Finished; } - hr = pEnvVarList->get_Collection( &pEnvVarCollection ); - if( FAILED( hr ) ) + hr = pEnvVarList->get_Collection(&pEnvVarCollection); + if (FAILED(hr)) { goto Finished; } - for( hr = FindFirstElement( pEnvVarCollection, &index, &pEnvVar ) ; - SUCCEEDED( hr ) ; - hr = FindNextElement( pEnvVarCollection, &index, &pEnvVar ) ) + for (hr = FindFirstElement(pEnvVarCollection, &index, &pEnvVar); + SUCCEEDED(hr); + hr = FindNextElement(pEnvVarCollection, &index, &pEnvVar)) { - if( hr == S_FALSE ) + if (hr == S_FALSE) { hr = S_OK; break; } - hr = GetElementStringProperty( pEnvVar, - CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME, - &strEnvName); - if( FAILED( hr ) ) + 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; } - hr = GetElementStringProperty( pEnvVar, - CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE, - &strEnvValue); - if( FAILED( hr ) ) - { - goto Finished; - } - - hr = strFullEnvVar.Append(strEnvName); - if( FAILED( hr ) ) - { - goto Finished; - } - - hr = strFullEnvVar.Append(L"="); - if( FAILED( hr ) ) - { - goto Finished; - } - - pszEnvName = strFullEnvVar.QueryStr(); - while( pszEnvName != NULL && *pszEnvName != '\0') - { - *pszEnvName = towupper( *pszEnvName ); - pszEnvName++; - } - - if( !mszEnvNames.FindString( strFullEnvVar ) ) - { - if( !mszEnvNames.Append( strFullEnvVar ) ) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - } - - hr = STRU::ExpandEnvironmentVariables( strEnvValue.QueryStr(), &strExpandedEnvValue ); - if( FAILED( hr ) ) - { - goto Finished; - } - - hr = strFullEnvVar.Append(strExpandedEnvValue); - if( FAILED( hr ) ) - { - goto Finished; - } - - if( !mszEnvironment.Append(strFullEnvVar) ) + 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(); - strFullEnvVar.Reset(); - pEnvVar->Release(); pEnvVar = NULL; + pEntry = NULL; } - // basically the following logic is to select - - ReverseMultisz( &mszEnvironment, - mszEnvironment.First(), - &mszEnvironmentListReverse ); - - pcszEnvName = mszEnvNames.First(); - while(pcszEnvName != NULL) - { - pszEnvString = mszEnvironmentListReverse.First(); - while( pszEnvString != NULL ) - { - if(wcsstr(pszEnvString, pcszEnvName) != NULL) - { - if(!m_mszEnvironment.Append(pszEnvString)) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - break; - } - pszEnvString = mszEnvironmentListReverse.Next(pszEnvString); - } - pcszEnvName = mszEnvNames.Next(pcszEnvName); - } - - // - // let's disable this feature for now - // - // get all files listed in recycleOnFileChange - /* - hr = GetElementChildByName( pAspNetCoreElement, - CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE, - &pRecycleOnFileChangeFileList ); - if( FAILED( hr ) ) - { - goto Finished; - } - - hr = pRecycleOnFileChangeFileList->get_Collection( &pRecycleOnFileChangeFileCollection ); - if( FAILED( hr ) ) - { - goto Finished; - } - - for( hr = FindFirstElement( pRecycleOnFileChangeFileCollection, &index, &pRecycleOnFileChangeFile ) ; - SUCCEEDED( hr ) ; - hr = FindNextElement( pRecycleOnFileChangeFileCollection, &index, &pRecycleOnFileChangeFile ) ) - { - if( hr == S_FALSE ) - { - hr = S_OK; - break; - } - - hr = GetElementStringProperty( pRecycleOnFileChangeFile, - CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE_PATH, - &strFilePath); - if( FAILED( hr ) ) - { - goto Finished; - } - - if(!m_mszRecycleOnFileChangeFiles.Append( strFilePath )) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - strFilePath.Reset(); - pRecycleOnFileChangeFile->Release(); - pRecycleOnFileChangeFile = NULL; - } - */ - Finished: - if( pAspNetCoreElement != NULL ) + if (pAspNetCoreElement != NULL) { pAspNetCoreElement->Release(); pAspNetCoreElement = NULL; } - if( pEnvVarList != NULL ) + if (pEnvVarList != NULL) { pEnvVarList->Release(); pEnvVarList = NULL; } - if( pEnvVar != NULL ) + if (pEnvVar != NULL) { pEnvVar->Release(); pEnvVar = NULL; } - if( pEnvVarCollection != NULL ) + if (pEnvVarCollection != NULL) { pEnvVarCollection->Release(); pEnvVarCollection = NULL; } - /* if( pRecycleOnFileChangeFileCollection != NULL ) + if (pEntry != NULL) { - pRecycleOnFileChangeFileCollection->Release(); - pRecycleOnFileChangeFileCollection = NULL; + pEntry->Dereference(); + pEntry = NULL; } - if( pRecycleOnFileChangeFileList != NULL ) - { - pRecycleOnFileChangeFileList->Release(); - pRecycleOnFileChangeFileList = NULL; - } - - if( pRecycleOnFileChangeFile != NULL ) - { - pRecycleOnFileChangeFile->Release(); - pRecycleOnFileChangeFile = NULL; - }*/ - return hr; } \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreutils.cxx b/src/AspNetCore/Src/aspnetcoreutils.cxx deleted file mode 100644 index 3d2f9612aa..0000000000 --- a/src/AspNetCore/Src/aspnetcoreutils.cxx +++ /dev/null @@ -1,55 +0,0 @@ -// 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" - -// -// ReplacePlaceHolderWithValue replaces a placeholder found in -// pszStr with dwValue. -// If replace is successful, pfReplaced is TRUE else FALSE. -// - -HRESULT -ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - _Inout_ LPWSTR pszStr, - _In_ LPWSTR pszPlaceholder, - _In_ DWORD cchPlaceholder, - _In_ DWORD dwValue, - _In_ DWORD dwNumDigitsInValue, - _Out_ BOOL *pfReplaced -) -{ - HRESULT hr = S_OK; - LPWSTR pszPortPlaceHolder = NULL; - - DBG_ASSERT( pszStr != NULL ); - DBG_ASSERT( pszPlaceholder != NULL ); - DBG_ASSERT( pfReplaced != NULL ); - - *pfReplaced = FALSE; - - if((pszPortPlaceHolder = wcsstr(pszStr, pszPlaceholder)) != NULL) - { - if( swprintf_s( pszPortPlaceHolder, - cchPlaceholder, - L"%u", - dwValue ) == -1 ) - { - hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ); - goto Finished; - } - - if( wmemcpy_s( pszPortPlaceHolder + dwNumDigitsInValue, - cchPlaceholder, - L" ", - cchPlaceholder - dwNumDigitsInValue ) != 0 ) - { - hr = HRESULT_FROM_WIN32( EINVAL ); - goto Finished; - } - - *pfReplaced = TRUE; - } -Finished: - return hr; -} \ No newline at end of file diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx index 96e321ef60..9bd5d6c2da 100644 --- a/src/AspNetCore/Src/filewatcher.cxx +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -318,7 +318,7 @@ FILE_WATCHER_ENTRY::Monitor(VOID) _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 & ~FILE_NOTIFY_CHANGE_ATTRIBUTES, + FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, &cbRead, &_overlapped, NULL)) diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 0fe0ea9e2c..5f4e316181 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -22,32 +22,36 @@ PROTOCOL_CONFIG FORWARDING_HANDLER::sm_ProtocolConfig; FORWARDING_HANDLER::FORWARDING_HANDLER( __in IHttpContext * pW3Context -) : m_Signature ( FORWARDING_HANDLER_SIGNATURE ), - m_cRefs ( 1 ), - m_pW3Context ( pW3Context ), - m_pChildRequestContext ( NULL ), - m_hRequest ( NULL ), - m_fHandleClosedDueToClient( FALSE ), - m_fResponseHeadersReceivedAndSet( FALSE ), - m_fDoReverseRewriteHeaders ( FALSE ), - m_msStartTime ( 0 ), - m_BytesToReceive ( 0 ), - m_BytesToSend ( 0 ), - m_pEntityBuffer ( NULL ), - m_cchLastSend ( 0 ), - m_cEntityBuffers ( 0 ), - m_cBytesBuffered ( 0 ), - m_cMinBufferLimit ( 0 ), - m_pszOriginalHostHeader ( NULL ), - m_RequestStatus ( FORWARDER_START ), - m_pDisconnect ( NULL ), - m_pszHeaders ( NULL ), - m_cchHeaders ( 0 ), - m_fWebSocketEnabled ( FALSE ), - m_cContentLength ( 0 ), - m_pWebSocket ( NULL ), - m_pApplication( NULL ), - m_pAppOfflineHtm( NULL ) +) : m_Signature(FORWARDING_HANDLER_SIGNATURE), +m_cRefs(1), +m_pW3Context(pW3Context), +m_pChildRequestContext(NULL), +m_hRequest(NULL), +m_fHandleClosedDueToClient(FALSE), +m_fResponseHeadersReceivedAndSet(FALSE), +m_fDoReverseRewriteHeaders(FALSE), +m_msStartTime(0), +m_BytesToReceive(0), +m_BytesToSend(0), +m_pEntityBuffer(NULL), +m_cchLastSend(0), +m_cEntityBuffers(0), +m_cBytesBuffered(0), +m_cMinBufferLimit(0), +m_pszOriginalHostHeader(NULL), +m_RequestStatus(FORWARDER_START), +m_pDisconnect(NULL), +m_pszHeaders(NULL), +m_cchHeaders(0), +m_fWebSocketEnabled(FALSE), +m_cContentLength(0), +m_pWebSocket(NULL), +m_pApplication(NULL), +m_pAppOfflineHtm(NULL), +m_fErrorHandled(FALSE), +m_fWebSocketUpgrade(FALSE), +m_fFinishRequest(FALSE), +m_fClientDisconnected(FALSE) { InitializeSRWLock(&m_RequestLock); } @@ -95,6 +99,10 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( if (m_hRequest != NULL) { + // m_hRequest should have already been closed and set to NULL + // if not, we cannot close it as it may callback and cause AV + // let's do our best job here + WinHttpSetStatusCallback(m_hRequest, NULL, NULL, NULL); WinHttpCloseHandle(m_hRequest); m_hRequest = NULL; } @@ -105,7 +113,7 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( m_pApplication = NULL; } - if( m_pAppOfflineHtm != NULL ) + if(m_pAppOfflineHtm != NULL) { m_pAppOfflineHtm->DereferenceAppOfflineHtm(); m_pAppOfflineHtm = NULL; @@ -144,8 +152,8 @@ FORWARDING_HANDLER::ReferenceForwardingHandler( if (sm_pTraceLog != NULL) { WriteRefTraceLog(sm_pTraceLog, - cRefs, - this); + cRefs, + this); } } @@ -154,10 +162,10 @@ FORWARDING_HANDLER::DereferenceForwardingHandler( VOID ) const { - DBG_ASSERT(m_cRefs != 0 ); - + DBG_ASSERT(m_cRefs != 0); + LONG cRefs = 0; - if ( (cRefs = InterlockedDecrement(&m_cRefs) ) == 0) + if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) { delete this; } @@ -165,8 +173,8 @@ FORWARDING_HANDLER::DereferenceForwardingHandler( if (sm_pTraceLog != NULL) { WriteRefTraceLog(sm_pTraceLog, - cRefs, - this); + cRefs, + this); } } @@ -179,8 +187,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( HRESULT hr; IHttpResponse * pResponse = m_pW3Context->GetResponse(); IHttpRequest * pRequest = m_pW3Context->GetRequest(); - STACK_STRA ( strHeaderName, 128); - STACK_STRA ( strHeaderValue, 2048); + STACK_STRA(strHeaderName, 128); + STACK_STRA(strHeaderValue, 2048); DWORD index = 0; PSTR pchNewline; PCSTR pchEndofHeaderValue; @@ -237,32 +245,32 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // Skip over any spaces before the '\n' // for (pchEndofHeaderValue = pchNewline - 1; - (pchEndofHeaderValue > pchStatus) && - ((*pchEndofHeaderValue == ' ') || - (*pchEndofHeaderValue == '\r')); - pchEndofHeaderValue--) + (pchEndofHeaderValue > pchStatus) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) {} // // Copy the status description // if (FAILED(hr = strHeaderValue.Copy( - pchStatus, - (DWORD)(pchEndofHeaderValue - pchStatus) + 1)) || + pchStatus, + (DWORD)(pchEndofHeaderValue - pchStatus) + 1)) || FAILED(hr = pResponse->SetStatus(uStatus, - strHeaderValue.QueryStr(), - 0, - S_OK, - NULL, - TRUE))) + 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) + pszHeaders[index] != '\r' && pszHeaders[index] != '\n' && pszHeaders[index] != '\0'; + index = static_cast(pchNewline - pszHeaders) + 1) { // // Find the ':' in Header : Value\r\n @@ -278,12 +286,12 @@ FORWARDING_HANDLER::SetStatusAndHeaders( { return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); } - + // // Take care of header continuation // while (pchNewline[1] == ' ' || - pchNewline[1] == '\t') + pchNewline[1] == '\t') { pchNewline = strchr(pchNewline + 1, '\n'); } @@ -300,9 +308,9 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // PCSTR pchEndofHeaderName; for (pchEndofHeaderName = pchColon - 1; - (pchEndofHeaderName >= pszHeaders + index) && - (*pchEndofHeaderName == ' '); - pchEndofHeaderName--) + (pchEndofHeaderName >= pszHeaders + index) && + (*pchEndofHeaderName == ' '); + pchEndofHeaderName--) {} pchEndofHeaderName++; @@ -311,8 +319,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // Copy the header name // if (FAILED(hr = strHeaderName.Copy( - pszHeaders + index, - (DWORD)(pchEndofHeaderName - pszHeaders) - index))) + pszHeaders + index, + (DWORD)(pchEndofHeaderName - pszHeaders) - index))) { return hr; } @@ -321,8 +329,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // Skip over the ':' and any trailing spaces // for (index = static_cast(pchColon - pszHeaders) + 1; - pszHeaders[index] == ' '; - index++) + pszHeaders[index] == ' '; + index++) {} @@ -330,10 +338,10 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // Skip over any spaces before the '\n' // for (pchEndofHeaderValue = pchNewline - 1; - (pchEndofHeaderValue >= pszHeaders + index) && - ((*pchEndofHeaderValue == ' ') || - (*pchEndofHeaderValue == '\r')); - pchEndofHeaderValue--) + (pchEndofHeaderValue >= pszHeaders + index) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) {} pchEndofHeaderValue++; @@ -341,13 +349,13 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // // Copy the header value // - if (pchEndofHeaderValue == pszHeaders+index) + if (pchEndofHeaderValue == pszHeaders + index) { strHeaderValue.Reset(); } else if (FAILED(hr = strHeaderValue.Copy( - pszHeaders + index, - (DWORD)(pchEndofHeaderValue - pszHeaders) - index))) + pszHeaders + index, + (DWORD)(pchEndofHeaderValue - pszHeaders) - index))) { return hr; } @@ -360,9 +368,9 @@ FORWARDING_HANDLER::SetStatusAndHeaders( if (headerIndex == UNKNOWN_INDEX) { hr = pResponse->SetHeader(strHeaderName.QueryStr(), - strHeaderValue.QueryStr(), - static_cast(strHeaderValue.QueryCCH()), - FALSE); // fReplace + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + FALSE); // fReplace } else { @@ -391,9 +399,9 @@ FORWARDING_HANDLER::SetStatusAndHeaders( } hr = pResponse->SetHeader(static_cast(headerIndex), - strHeaderValue.QueryStr(), - static_cast(strHeaderValue.QueryCCH()), - TRUE); // fReplace + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + TRUE); // fReplace } if (FAILED(hr)) { @@ -405,7 +413,7 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // Explicitly remove the Server header if the back-end didn't set one. // - if ( !fServerHeaderPresent ) + if (!fServerHeaderPresent) { pResponse->DeleteHeader("Server"); } @@ -471,9 +479,9 @@ FORWARDING_HANDLER::DoReverseRewrite( return hr; } if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation, - strTemp.QueryStr(), - static_cast(strTemp.QueryCCH()), - TRUE))) + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) { return hr; } @@ -510,9 +518,9 @@ Location: return hr; } if (FAILED(hr = pResponse->SetHeader(HttpHeaderLocation, - strTemp.QueryStr(), - static_cast(strTemp.QueryCCH()), - TRUE))) + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) { return hr; } @@ -525,7 +533,7 @@ SetCookie: // syntax name=value ; ... ; Domain=.host ; ... // pHeaders = &pResponse->GetRawHttpResponse()->Headers; - for (DWORD i=0; iUnknownHeaderCount; i++) + for (DWORD i = 0; iUnknownHeaderCount; i++) { if (_stricmp(pHeaders->pUnknownHeaders[i].pName, "Set-Cookie") != 0) { @@ -567,9 +575,9 @@ SetCookie: pszStartHost++; } pszEndHost = pszStartHost; - while(!IsSpace(*pszEndHost) && - *pszEndHost != ';' && - *pszEndHost != '\0') + while (!IsSpace(*pszEndHost) && + *pszEndHost != ';' && + *pszEndHost != '\0') { pszEndHost++; } @@ -600,38 +608,44 @@ SetCookie: HRESULT FORWARDING_HANDLER::GetHeaders( const PROTOCOL_CONFIG * pProtocol, - PCWSTR pszDestination, PCWSTR * ppszHeaders, DWORD * pcchHeaders, - ASPNETCORE_CONFIG* pAspNetCoreConfig, + ASPNETCORE_CONFIG* pAspNetCoreConfig, SERVER_PROCESS* pServerProcess ) { - IHttpRequest *pRequest = m_pW3Context->GetRequest(); + HRESULT hr = S_OK; PCSTR pszCurrentHeader; - USHORT cchCurrentHeader; - PCSTR pszFinalHeader; - DWORD cchFinalHeader; - STACK_STRA( strTemp, 64); - HTTP_REQUEST_HEADERS *pHeaders; 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; // - // Update host header if so configured + // 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 = PATH::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 { - STACK_STRA( straTemp, 256 ); - if (FAILED(hr = straTemp.CopyW(pszDestination)) || - FAILED(hr = pRequest->SetHeader(HttpHeaderHost, - straTemp.QueryStr(), - static_cast(straTemp.QueryCCH()), - TRUE))) // fReplace - { - return hr; - } + return hr; } // @@ -641,11 +655,11 @@ FORWARDING_HANDLER::GetHeaders( // pHeaders = &m_pW3Context->GetRequest()->GetRawHttpRequest()->Headers; - for (DWORD i=0; iUnknownHeaderCount; i++) + 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 ); + mszMsAspNetCoreHeaders.Append(pHeaders->pUnknownHeaders[i].pName, (DWORD)pHeaders->pUnknownHeaders[i].NameLength); } } @@ -655,35 +669,35 @@ FORWARDING_HANDLER::GetHeaders( // iterate the list of headers to be removed and delete them from the request. // - while(ppHeadersToBeRemoved != NULL) + while (ppHeadersToBeRemoved != NULL) { - m_pW3Context->GetRequest()->DeleteHeader( ppHeadersToBeRemoved ); - ppHeadersToBeRemoved = mszMsAspNetCoreHeaders.Next( ppHeadersToBeRemoved ); + m_pW3Context->GetRequest()->DeleteHeader(ppHeadersToBeRemoved); + ppHeadersToBeRemoved = mszMsAspNetCoreHeaders.Next(ppHeadersToBeRemoved); } - if( pServerProcess->QueryGuid() != NULL ) + if (pServerProcess->QueryGuid() != NULL) { - hr = m_pW3Context->GetRequest()->SetHeader( "MS-ASPNETCORE-TOKEN", - pServerProcess->QueryGuid(), - (USHORT)strlen(pServerProcess->QueryGuid()), - TRUE ); - if(FAILED(hr)) + hr = m_pW3Context->GetRequest()->SetHeader("MS-ASPNETCORE-TOKEN", + pServerProcess->QueryGuid(), + (USHORT)strlen(pServerProcess->QueryGuid()), + TRUE); + if (FAILED(hr)) { return hr; } } - if( pAspNetCoreConfig->QueryForwardWindowsAuthToken() && + if (pAspNetCoreConfig->QueryForwardWindowsAuthToken() && (_wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"negotiate") == 0 || - _wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"ntlm") == 0) ) + _wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"ntlm") == 0)) { - if( m_pW3Context->GetUser()->GetPrimaryToken() != NULL && - m_pW3Context->GetUser()->GetPrimaryToken() != INVALID_HANDLE_VALUE ) + 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)) + hr = pServerProcess->SetWindowsAuthToken(m_pW3Context->GetUser()->GetPrimaryToken(), + &hTargetTokenHandle); + if (FAILED(hr)) { return hr; } @@ -691,18 +705,18 @@ FORWARDING_HANDLER::GetHeaders( // // set request header with target token value // - CHAR pszHandleStr[16] = {0}; - if(_ui64toa_s( (UINT64) hTargetTokenHandle, pszHandleStr, 16, 16 ) != 0) + 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)) + hr = m_pW3Context->GetRequest()->SetHeader("MS-ASPNETCORE-WINAUTHTOKEN", + pszHandleStr, + (USHORT)strlen(pszHandleStr), + TRUE); + if (FAILED(hr)) { return hr; } @@ -793,14 +807,14 @@ FORWARDING_HANDLER::GetHeaders( } } - if(FAILED(hr = strTemp.Append(pszScheme))) + if (FAILED(hr = strTemp.Append(pszScheme))) { return hr; } - if(FAILED(pRequest->SetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), + if (FAILED(pRequest->SetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), strTemp.QueryStr(), - (USHORT) strTemp.QueryCCH(), + (USHORT)strTemp.QueryCCH(), TRUE))) { return hr; @@ -816,25 +830,26 @@ FORWARDING_HANDLER::GetHeaders( } 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))) + 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); + 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 + pProtocol->QueryClientCertName()->QueryStr(), + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace { return hr; } @@ -853,8 +868,8 @@ FORWARDING_HANDLER::GetHeaders( // Get all the headers to send to the client // hr = m_pW3Context->GetServerVariable("ALL_RAW", - ppszHeaders, - pcchHeaders); + ppszHeaders, + pcchHeaders); if (FAILED(hr)) { return hr; @@ -869,7 +884,6 @@ FORWARDING_HANDLER::CreateWinHttpRequest( __in const PROTOCOL_CONFIG * pProtocol, __in HINTERNET hConnect, __inout STRU * pstrUrl, - const STRU& strDestination, ASPNETCORE_CONFIG* pAspNetCoreConfig, SERVER_PROCESS* pServerProcess ) @@ -877,7 +891,7 @@ FORWARDING_HANDLER::CreateWinHttpRequest( HRESULT hr = S_OK; PCWSTR pszVersion = NULL; PCSTR pszVerb; - STACK_STRU( strVerb, 32 ); + STACK_STRU(strVerb, 32); // // Create the request handle for this request (leave some fields blank, @@ -894,9 +908,9 @@ FORWARDING_HANDLER::CreateWinHttpRequest( { DWORD cchUnused; hr = m_pW3Context->GetServerVariable( - "HTTP_VERSION", - &pszVersion, - &cchUnused); + "HTTP_VERSION", + &pszVersion, + &cchUnused); if (FAILED(hr)) { goto Finished; @@ -904,13 +918,13 @@ FORWARDING_HANDLER::CreateWinHttpRequest( } m_hRequest = WinHttpOpenRequest(hConnect, - strVerb.QueryStr(), - pstrUrl->QueryStr(), - pszVersion, - WINHTTP_NO_REFERER, - WINHTTP_DEFAULT_ACCEPT_TYPES, - WINHTTP_FLAG_ESCAPE_DISABLE_QUERY - | g_OptionalWinHttpFlags ); + 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()); @@ -918,10 +932,10 @@ FORWARDING_HANDLER::CreateWinHttpRequest( } if (!WinHttpSetTimeouts(m_hRequest, - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout())) + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout())) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; @@ -929,9 +943,9 @@ FORWARDING_HANDLER::CreateWinHttpRequest( DWORD dwResponseBufferLimit = pProtocol->QueryResponseBufferLimit(); if (!WinHttpSetOption(m_hRequest, - WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE, - &dwResponseBufferLimit, - sizeof(dwResponseBufferLimit))) + WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE, + &dwResponseBufferLimit, + sizeof(dwResponseBufferLimit))) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; @@ -939,16 +953,16 @@ FORWARDING_HANDLER::CreateWinHttpRequest( DWORD dwMaxHeaderSize = pProtocol->QueryMaxResponseHeaderSize(); if (!WinHttpSetOption(m_hRequest, - WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE, - &dwMaxHeaderSize, - sizeof(dwMaxHeaderSize))) + 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()) @@ -956,20 +970,30 @@ FORWARDING_HANDLER::CreateWinHttpRequest( dwOption |= WINHTTP_DISABLE_KEEP_ALIVE; } if (!WinHttpSetOption(m_hRequest, - WINHTTP_OPTION_DISABLE_FEATURE, - &dwOption, - sizeof(dwOption))) + 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, - strDestination.QueryStr(), - &m_pszHeaders, - &m_cchHeaders, - pAspNetCoreConfig, - pServerProcess); + &m_pszHeaders, + &m_cchHeaders, + pAspNetCoreConfig, + pServerProcess); if (FAILED(hr)) { goto Finished; @@ -982,7 +1006,7 @@ Finished: REQUEST_NOTIFICATION_STATUS FORWARDING_HANDLER::OnExecuteRequestHandler( -VOID + VOID ) { REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; @@ -990,10 +1014,10 @@ VOID bool fRequestLocked = FALSE; ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; FORWARDER_CONNECTION *pConnection = NULL; - STACK_STRU( strDestination, 32); - STACK_STRU( strUrl, 2048); - STACK_STRU( struEscapedUrl, 2048); - STACK_STRU( strDescription, 128); + STACK_STRU(strDestination, 32); + STACK_STRU(strUrl, 2048); + STACK_STRU(struEscapedUrl, 2048); + STACK_STRU(strDescription, 128); HINTERNET hConnect = NULL; IHttpRequest *pRequest = m_pW3Context->GetRequest(); IHttpResponse *pResponse = m_pW3Context->GetResponse(); @@ -1030,9 +1054,9 @@ VOID // parse original url // if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, - &fSecure, - &strDestination, - &strUrl))) + &fSecure, + &strDestination, + &strUrl))) { goto Failure; } @@ -1065,8 +1089,8 @@ VOID goto Failure; } - hr = pApplicationManager->GetApplication( m_pW3Context, - &m_pApplication ); + hr = pApplicationManager->GetApplication(m_pW3Context, + &m_pApplication); if (FAILED(hr)) { goto Failure; @@ -1080,47 +1104,47 @@ VOID if (m_pApplication->AppOfflineFound() && m_pAppOfflineHtm != NULL) { - HTTP_DATA_CHUNK DataChunk; - PCSTR pszANCMHeader; - DWORD cchANCMHeader; - BOOL fCompletionExpected = FALSE; + HTTP_DATA_CHUNK DataChunk; + PCSTR pszANCMHeader; + DWORD cchANCMHeader; + BOOL fCompletionExpected = FALSE; - if (FAILED(m_pW3Context->GetServerVariable(STR_ANCM_CHILDREQUEST, - &pszANCMHeader, - &cchANCMHeader))) // first time failure - { - if (SUCCEEDED(hr = m_pW3Context->CloneContext( - CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, - &m_pChildRequestContext - )) && - SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( - STR_ANCM_CHILDREQUEST, - L"1")) && - SUCCEEDED(hr = m_pW3Context->ExecuteRequest( - TRUE, // fAsync - m_pChildRequestContext, - EXECUTE_FLAG_DISABLE_CUSTOM_ERROR, // by pass Custom Error module - NULL, // pHttpUser - &fCompletionExpected))) - { - if (!fCompletionExpected) - { - retVal = RQ_NOTIFICATION_CONTINUE; - } - else - { - retVal = RQ_NOTIFICATION_PENDING; - } - goto Finished; - } - // - // fail to create child request, fall back to default 502 error - // - } + if (FAILED(m_pW3Context->GetServerVariable(STR_ANCM_CHILDREQUEST, + &pszANCMHeader, + &cchANCMHeader))) // first time failure + { + if (SUCCEEDED(hr = m_pW3Context->CloneContext( + CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, + &m_pChildRequestContext + )) && + SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( + STR_ANCM_CHILDREQUEST, + L"1")) && + SUCCEEDED(hr = m_pW3Context->ExecuteRequest( + TRUE, // fAsync + m_pChildRequestContext, + EXECUTE_FLAG_DISABLE_CUSTOM_ERROR, // by pass Custom Error module + NULL, // pHttpUser + &fCompletionExpected))) + { + if (!fCompletionExpected) + { + retVal = RQ_NOTIFICATION_CONTINUE; + } + else + { + retVal = RQ_NOTIFICATION_PENDING; + } + goto Finished; + } + // + // fail to create child request, fall back to default 502 error + // + } - DataChunk.DataChunkType = HttpDataChunkFromMemory; - DataChunk.FromMemory.pBuffer = (PVOID)m_pAppOfflineHtm->m_Contents.QueryStr(); - DataChunk.FromMemory.BufferLength = m_pAppOfflineHtm->m_Contents.QueryCB(); + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = (PVOID)m_pAppOfflineHtm->m_Contents.QueryStr(); + DataChunk.FromMemory.BufferLength = m_pAppOfflineHtm->m_Contents.QueryCB(); if (FAILED(hr = pResponse->WriteEntityChunkByReference(&DataChunk))) { @@ -1128,17 +1152,17 @@ VOID } pResponse->SetStatus(503, "Service Unavailable", 0, hr); pResponse->SetHeader("Content-Type", - "text/html", - (USHORT)strlen("text/html"), - FALSE - ); // no need to check return hresult + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); // no need to check return hresult goto Finished; } - hr = m_pApplication->GetProcess( m_pW3Context, - pAspNetCoreConfig, - &pServerProcess); + hr = m_pApplication->GetProcess(m_pW3Context, + pAspNetCoreConfig, + &pServerProcess); if (FAILED(hr)) { fProcessStartFailure = TRUE; @@ -1175,12 +1199,11 @@ VOID } hr = CreateWinHttpRequest(pRequest, - pProtocol, - hConnect, - &struEscapedUrl, - strDestination, - pAspNetCoreConfig, - pServerProcess); + pProtocol, + hConnect, + &struEscapedUrl, + pAspNetCoreConfig, + pServerProcess); if (FAILED(hr)) { @@ -1275,9 +1298,9 @@ VOID // if (!WinHttpSetOption(m_hRequest, - WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, - NULL, - 0)) + WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, + NULL, + 0)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; @@ -1304,12 +1327,12 @@ VOID // ReferenceForwardingHandler(); if (!WinHttpSendRequest(m_hRequest, - m_pszHeaders, - m_cchHeaders, - NULL, - 0, - cbContentLength, - reinterpret_cast(static_cast(this)))) + m_pszHeaders, + m_cchHeaders, + NULL, + 0, + cbContentLength, + reinterpret_cast(static_cast(this)))) { hr = HRESULT_FROM_WIN32(GetLastError()); DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, @@ -1355,9 +1378,9 @@ Failure: &cchANCMHeader))) // first time failure { if (SUCCEEDED(hr = m_pW3Context->CloneContext( - CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, - &m_pChildRequestContext - )) && + CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, + &m_pChildRequestContext + )) && SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( STR_ANCM_CHILDREQUEST, L"1")) && @@ -1395,7 +1418,7 @@ Failure: "text/html", (USHORT)strlen("text/html"), FALSE - ); + ); goto Finished; } } @@ -1433,7 +1456,7 @@ Failure: strDescription.QueryStr(), strDescription.QueryCCH(), FALSE); - } + } Finished: @@ -1443,7 +1466,7 @@ Finished: pConnection = NULL; } - if( pServerProcess != NULL ) + if (pServerProcess != NULL) { pServerProcess->DereferenceServerProcess(); pServerProcess = NULL; @@ -1456,7 +1479,7 @@ Finished: ReleaseSRWLockShared(&m_RequestLock); DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); } - + if (retVal != RQ_NOTIFICATION_PENDING) { // @@ -1494,17 +1517,17 @@ FORWARDING_HANDLER::OnAsyncCompletion( Routine Description: - Handle the completion from IIS and continue the execution - of this request based on the current state. +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 +cbCompletion - Number of bytes associated with this completion +dwCompletionStatus - the win32 status associated with this completion Return Value: - REQUEST_NOTIFICATION_STATUS +REQUEST_NOTIFICATION_STATUS --*/ { @@ -1512,17 +1535,16 @@ Return Value: REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; BOOL fLocked = FALSE; bool fClientError = FALSE; - DBG_ASSERT(m_pW3Context != NULL); - __analysis_assume(m_pW3Context != NULL); + BOOL fClosed = FALSE; - if ( sm_pTraceLog != NULL ) + if (sm_pTraceLog != NULL) { - WriteRefTraceLogEx( sm_pTraceLog, - m_cRefs, - this, - "FORWARDING_HANDLER::OnAsyncCompletion Enter", - reinterpret_cast(static_cast(cbCompletion)), - reinterpret_cast(static_cast(hrCompletionStatus)) ); + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnAsyncCompletion Enter", + reinterpret_cast(static_cast(cbCompletion)), + reinterpret_cast(static_cast(hrCompletionStatus))); } // @@ -1534,6 +1556,9 @@ Return Value: // ReferenceForwardingHandler(); + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + // // OnAsyncCompletion can be called on a Winhttp io completion thread. // Hence we need to check the TLS before we acquire the shared lock. @@ -1551,7 +1576,7 @@ Return Value: if (m_hRequest == NULL) { - if (m_RequestStatus == FORWARDER_DONE) + if (m_RequestStatus == FORWARDER_DONE && m_fFinishRequest) { retVal = RQ_NOTIFICATION_FINISH_REQUEST; goto Finished; @@ -1584,10 +1609,22 @@ Return Value: // // WebSocket upgrade is successful. Close the WinHttpRequest Handle // - - WinHttpCloseHandle(m_hRequest); - m_hRequest = NULL; - + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + fClosed = WinHttpCloseHandle(m_hRequest); + DBG_ASSERT(fClosed); + if (fClosed) + { + m_fWebSocketUpgrade = TRUE; + m_hRequest = NULL; + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } retVal = RQ_NOTIFICATION_PENDING; goto Finished; } @@ -1628,8 +1665,8 @@ Return Value: case FORWARDER_SENDING_REQUEST: hr = OnSendingRequest(cbCompletion, - hrCompletionStatus, - &fClientError); + hrCompletionStatus, + &fClientError); if (FAILED(hr)) { goto Failure; @@ -1662,6 +1699,13 @@ Failure: IHttpResponse *pResponse = m_pW3Context->GetResponse(); pResponse->DisableKernelCache(); pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + // double check to set right status code + if (!m_pW3Context->GetConnection()->IsConnected()) + { + fClientError = TRUE; + } + if (fClientError) { if (!m_fResponseHeadersReceivedAndSet) @@ -1679,40 +1723,40 @@ Failure: } else { - STACK_STRU( strDescription, 128); + 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.") +#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); + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL); } else { LoadString(g_hModule, - IDS_SERVER_ERROR, - strDescription.QueryStr(), - strDescription.QuerySizeCCH()); + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); } - (VOID) strDescription.SyncWithBuffer(); + (VOID)strDescription.SyncWithBuffer(); if (strDescription.QueryCCH() != 0) { pResponse->SetErrorDescription( - strDescription.QueryStr(), - strDescription.QueryCCH(), - FALSE); + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); } - if( hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) { pResponse->ResetConnection(); goto Finished; @@ -1750,7 +1794,7 @@ Finished: // // Do not use this object after dereferencing it, it may be gone. // - + return retVal; } @@ -1776,17 +1820,17 @@ FORWARDING_HANDLER::OnSendingRequest( m_BytesToReceive = 0; m_cchLastSend = 5; // "0\r\n\r\n" - // - // WinHttpWriteData can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // + // + // 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)) + "0\r\n\r\n", + 5, + NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); DereferenceForwardingHandler(); @@ -1832,8 +1876,8 @@ FORWARDING_HANDLER::OnSendingRequest( m_pEntityBuffer[4] = '\r'; m_pEntityBuffer[5] = '\n'; - m_pEntityBuffer[cbCompletion+6] = '\r'; - m_pEntityBuffer[cbCompletion+7] = '\n'; + m_pEntityBuffer[cbCompletion + 6] = '\r'; + m_pEntityBuffer[cbCompletion + 7] = '\n'; if (cbCompletion < 0x10) { @@ -1878,9 +1922,9 @@ FORWARDING_HANDLER::OnSendingRequest( // ReferenceForwardingHandler(); if (!WinHttpWriteData(m_hRequest, - m_pEntityBuffer + cbOffset, - cbCompletion, - NULL)) + m_pEntityBuffer + cbOffset, + cbCompletion, + NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); DereferenceForwardingHandler(); @@ -1903,7 +1947,7 @@ HRESULT FORWARDING_HANDLER::OnReceivingResponse( ) { - HRESULT hr = S_OK; + HRESULT hr = S_OK; if (m_cBytesBuffered >= m_cMinBufferLimit) { @@ -1918,7 +1962,7 @@ FORWARDING_HANDLER::OnReceivingResponse( // buffering // m_BytesToSend = min(m_cMinBufferLimit, BUFFER_SIZE); - if (m_BytesToSend < BUFFER_SIZE/2) + if (m_BytesToSend < BUFFER_SIZE / 2) { // // Disable buffering. @@ -1969,9 +2013,9 @@ FORWARDING_HANDLER::OnReceivingResponse( // ReferenceForwardingHandler(); if (!WinHttpReadData(m_hRequest, - m_pEntityBuffer, - min(m_BytesToSend, BUFFER_SIZE), - NULL)) + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); DereferenceForwardingHandler(); @@ -1986,10 +2030,10 @@ Failure: VOID FORWARDING_HANDLER::OnWinHttpCompletionInternal( -HINTERNET hRequest, -DWORD dwInternetStatus, -LPVOID lpvStatusInformation, -DWORD dwStatusInformationLength + HINTERNET hRequest, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength ) /*++ @@ -2018,6 +2062,7 @@ None __analysis_assume(m_pW3Context != NULL); IHttpResponse * pResponse = m_pW3Context->GetResponse(); BOOL fDerefForwardingHandler = TRUE; + BOOL fEndRequest = FALSE; UNREFERENCED_PARAMETER(dwStatusInformationLength); @@ -2031,46 +2076,6 @@ None NULL); } - // - // If the request is upgraded to websocket, route the completion - // to the websocket handler. We don't need to take the request lock, - // since for websocket request, the parent request handle is already - // closed. - // - - if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) - { - 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; - } - - fDerefForwardingHandler = FALSE; - fAnotherCompletionExpected = TRUE; - - goto Finished; - } - // // ReadLock on the winhttp handle to protect from a client disconnect/ // server stop closing the handle while we are using it. @@ -2083,6 +2088,7 @@ None // async completion - release one reference when async operation is // initiated, two references if async operation is not initiated // + if (TlsGetValue(g_dwTlsIndex) != this) { DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); @@ -2091,20 +2097,78 @@ None TlsSetValue(g_dwTlsIndex, this); fIsCompletionThread = TRUE; DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); - - } - - if (m_hRequest == NULL) - { - fClientError = m_fHandleClosedDueToClient; - goto Failure; } - if (!m_pWebSocket) + fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); + if (!fEndRequest) { - DBG_ASSERT(hRequest == m_hRequest); + if (!m_pW3Context->GetConnection()->IsConnected()) + { + m_fClientDisconnected = TRUE; + if (m_hRequest != NULL) + { + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } + else + { + // if WinHttpCloseHandle failed, we are already in failure path + // nothing can can be done here + } + } + hr = ERROR_CONNECTION_ABORTED; + fClientError = m_fHandleClosedDueToClient = TRUE; + fDerefForwardingHandler = FALSE; + // wait for HANDLE_CLOSING callback to clean up + fAnotherCompletionExpected = !fEndRequest; + goto Failure; + } } + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + fDerefForwardingHandler = FALSE; + 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: @@ -2154,6 +2218,21 @@ None fDerefForwardingHandler = FALSE; fAnotherCompletionExpected = TRUE; break; + + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + if (m_RequestStatus != FORWARDER_DONE) + { + hr = ERROR_CONNECTION_ABORTED; + fClientError = m_fHandleClosedDueToClient; + } + else + { + fDerefForwardingHandler = m_fClientDisconnected && !m_fWebSocketUpgrade; + } + m_hRequest = NULL; + m_pWebSocket = NULL; + fAnotherCompletionExpected = FALSE; + break; case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: hr = ERROR_CONNECTION_ABORTED; break; @@ -2190,71 +2269,76 @@ None goto Finished; Failure: - - if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) - { - m_RequestStatus = FORWARDER_RESET_CONNECTION; - goto Finished; - } - m_RequestStatus = FORWARDER_DONE; - pResponse->DisableKernelCache(); - pResponse->GetRawHttpResponse()->EntityChunkCount = 0; - if (fClientError) + if (!m_fErrorHandled) { - if (!m_fResponseHeadersReceivedAndSet) + m_fErrorHandled = TRUE; + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) { - pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); + m_RequestStatus = FORWARDER_RESET_CONNECTION; + goto Finished; + } + + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + fClientError = !m_pW3Context->GetConnection()->IsConnected(); + 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 { - // - // Response headers from origin server were - // already received and set for the current response. - // Honor the response status. - // - } - } - else - { - STACK_STRU(strDescription, 128); + STACK_STRU(strDescription, 128); - pResponse->SetStatus(502, "Bad Gateway", 3, hr); + pResponse->SetStatus(502, "Bad Gateway", 3, hr); - if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && - hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) - { + 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); - } - else - { - LoadString(g_hModule, - IDS_SERVER_ERROR, - strDescription.QueryStr(), - strDescription.QuerySizeCCH()); - } - strDescription.SyncWithBuffer(); - if (strDescription.QueryCCH() != 0) - { - pResponse->SetErrorDescription( - strDescription.QueryStr(), - strDescription.QueryCCH(), - FALSE); + FormatMessage( + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } } } Finished: + if (fIsCompletionThread) - { + { DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); TlsSetValue(g_dwTlsIndex, NULL); ReleaseSRWLockShared(&m_RequestLock); @@ -2275,14 +2359,45 @@ Finished: // If fAnotherCompletionExpected is false, this method must post the completion. // + if (m_RequestStatus == FORWARDER_DONE) + { + if (m_hRequest != NULL) + { + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + if(WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } + } + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + m_pWebSocket = NULL; + } - if (!fAnotherCompletionExpected ) + 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; + m_pW3Context->PostCompletion(0); + + } + } + 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. // - m_pW3Context->PostCompletion(0); + + m_pW3Context->PostCompletion(0); + // // No code executed after posting the completion. // @@ -2292,7 +2407,7 @@ Finished: HRESULT FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( HINTERNET hRequest, - DWORD , + DWORD, __out bool * pfClientError, __out bool * pfAnotherCompletionExpected ) @@ -2310,7 +2425,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( if (m_pEntityBuffer == NULL) { m_pEntityBuffer = GetNewResponseBuffer( - ENTITY_BUFFER_SIZE); + ENTITY_BUFFER_SIZE); if (m_pEntityBuffer == NULL) { hr = E_OUTOFMEMORY; @@ -2321,23 +2436,23 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( if (sm_pTraceLog != NULL) { WriteRefTraceLogEx(sm_pTraceLog, - m_cRefs, - this, - "Calling ReadEntityBody", - NULL, - NULL); + m_cRefs, + this, + "Calling ReadEntityBody", + NULL, + NULL); } hr = pRequest->ReadEntityBody( - m_pEntityBuffer + 6, - min(m_BytesToReceive, BUFFER_SIZE), - TRUE, // fAsync - NULL, // pcbBytesReceived - NULL); // pfCompletionPending + 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); - + m_BytesToReceive == INFINITE); + // // ERROR_HANDLE_EOF is not an error. // @@ -2346,7 +2461,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( if (m_BytesToReceive == INFINITE) { m_BytesToReceive = 0; - m_cchLastSend = 5; + m_cchLastSend = 5; // // WinHttpWriteData can operate asynchronously. @@ -2356,9 +2471,9 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( // ReferenceForwardingHandler(); if (!WinHttpWriteData(m_hRequest, - "0\r\n\r\n", - 5, - NULL)) + "0\r\n\r\n", + 5, + NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); DereferenceForwardingHandler(); @@ -2414,8 +2529,8 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( ) { HRESULT hr = S_OK; - STACK_BUFFER( bufHeaderBuffer, 2048); - STACK_STRA( strHeaders, 2048); + STACK_BUFFER(bufHeaderBuffer, 2048); + STACK_STRA(strHeaders, 2048); DWORD dwHeaderSize = bufHeaderBuffer.QuerySize(); UNREFERENCED_PARAMETER(pfAnotherCompletionExpected); @@ -2429,11 +2544,11 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( // dwHeaderSize = bufHeaderBuffer.QuerySize(); if (!WinHttpQueryHeaders(hRequest, - WINHTTP_QUERY_RAW_HEADERS_CRLF, - WINHTTP_HEADER_NAME_BY_INDEX, - bufHeaderBuffer.QueryPtr(), - &dwHeaderSize, - WINHTTP_NO_HEADER_INDEX)) + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) { if (!bufHeaderBuffer.Resize(dwHeaderSize)) { @@ -2446,11 +2561,11 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( // 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)) + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; @@ -2458,7 +2573,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( } if (FAILED(hr = strHeaders.CopyW( - reinterpret_cast(bufHeaderBuffer.QueryPtr())))) + reinterpret_cast(bufHeaderBuffer.QueryPtr())))) { goto Finished; } @@ -2470,19 +2585,19 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( // 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' ) + + if (!strHeaders.IsEmpty() && strHeaders.QueryStr()[strHeaders.QueryCCH() - 1] != '\n') { - hr = strHeaders.Append( "\r\n" ); - if ( FAILED( hr ) ) + hr = strHeaders.Append("\r\n"); + if (FAILED(hr)) { goto Finished; } } if (FAILED(hr = SetStatusAndHeaders( - strHeaders.QueryStr(), - strHeaders.QueryCCH()))) + strHeaders.QueryStr(), + strHeaders.QueryCCH()))) { goto Finished; } @@ -2500,10 +2615,10 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( m_RequestStatus = FORWARDER_RECEIVED_WEBSOCKET_RESPONSE; hr = m_pW3Context->GetResponse()->Flush( - TRUE, - TRUE, - NULL, - NULL); + TRUE, + TRUE, + NULL, + NULL); if (FAILED(hr)) { @@ -2552,7 +2667,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusDataAvailable( } m_pEntityBuffer = GetNewResponseBuffer( - min(m_BytesToSend, BUFFER_SIZE)); + min(m_BytesToSend, BUFFER_SIZE)); if (m_pEntityBuffer == NULL) { hr = E_OUTOFMEMORY; @@ -2567,9 +2682,9 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusDataAvailable( // ReferenceForwardingHandler(); if (!WinHttpReadData(hRequest, - m_pEntityBuffer, - min(m_BytesToSend, BUFFER_SIZE), - NULL)) + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); DereferenceForwardingHandler(); @@ -2589,14 +2704,14 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusReadComplete( __out bool * pfAnotherCompletionExpected ) { - HRESULT hr = S_OK; + 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_cMinBufferLimit >= BUFFER_SIZE / 2) { if (m_cContentLength != 0) { @@ -2649,8 +2764,8 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusReadComplete( // Always post a completion to resume the WinHTTP data pump. // hr = pResponse->Flush(TRUE, // fAsync - TRUE, // fMoreData - NULL); // pcbSent + TRUE, // fMoreData + NULL); // pcbSent if (FAILED(hr)) { goto Finished; @@ -2670,21 +2785,21 @@ Finished: // static HRESULT FORWARDING_HANDLER::StaticInitialize( - BOOL fEnableReferenceCountTracing + BOOL fEnableReferenceCountTracing ) /*++ Routine Description: - Global initialization routine for FORWARDING_HANDLERs +Global initialization routine for FORWARDING_HANDLERs Arguments: - fEnableReferenceCountTracing - True if ref count tracing should be use. - +fEnableReferenceCountTracing - True if ref count tracing should be use. + Return Value: - HRESULT +HRESULT --*/ { @@ -2698,7 +2813,7 @@ Return Value: } hr = sm_pAlloc->Initialize(sizeof(FORWARDING_HANDLER), - 64); // nThreshold + 64); // nThreshold if (FAILED(hr)) { goto Failure; @@ -2709,10 +2824,10 @@ Return Value: // overwritten by the client // sm_hSession = WinHttpOpen(L"", - WINHTTP_ACCESS_TYPE_NO_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - WINHTTP_FLAG_ASYNC); + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + WINHTTP_FLAG_ASYNC); if (sm_hSession == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); @@ -2729,10 +2844,10 @@ Return Value: // Setup the callback function // if (WinHttpSetStatusCallback(sm_hSession, - FORWARDING_HANDLER::OnWinHttpCompletion, - ( WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | - WINHTTP_CALLBACK_STATUS_SENDING_REQUEST ), - NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + FORWARDING_HANDLER::OnWinHttpCompletion, + (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Failure; @@ -2744,9 +2859,9 @@ Return Value: // DWORD dwRedirectOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; if (!WinHttpSetOption(sm_hSession, - WINHTTP_OPTION_REDIRECT_POLICY, - &dwRedirectOption, - sizeof(dwRedirectOption))) + WINHTTP_OPTION_REDIRECT_POLICY, + &dwRedirectOption, + sizeof(dwRedirectOption))) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Failure; @@ -2754,14 +2869,14 @@ Return Value: // Initialize Application Manager APPLICATION_MANAGER *pApplicationManager = APPLICATION_MANAGER::GetInstance(); - if(pApplicationManager == NULL) + if (pApplicationManager == NULL) { hr = E_OUTOFMEMORY; goto Failure; } hr = pApplicationManager->Initialize(); - if(FAILED(hr)) + if (FAILED(hr)) { goto Failure; } @@ -2775,9 +2890,9 @@ Return Value: } if (LoadString(g_hModule, - IDS_INVALID_PROPERTY, - sm_strErrorFormat.QueryStr(), - sm_strErrorFormat.QuerySizeCCH()) == 0) + IDS_INVALID_PROPERTY, + sm_strErrorFormat.QueryStr(), + sm_strErrorFormat.QuerySizeCCH()) == 0) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Failure; @@ -2803,9 +2918,9 @@ Return Value: goto Failure; } - if ( fEnableReferenceCountTracing ) + if (fEnableReferenceCountTracing) { - sm_pTraceLog = CreateRefTraceLog( 10000, 0 ); + sm_pTraceLog = CreateRefTraceLog(10000, 0); } return S_OK; @@ -2826,15 +2941,15 @@ FORWARDING_HANDLER::StaticTerminate( Routine Description: - Global termination routine for FORWARDING_HANDLERs +Global termination routine for FORWARDING_HANDLERs Arguments: - None - +None + Return Value: - None +None --*/ { @@ -2851,9 +2966,9 @@ Return Value: DWORD tickCount = GetTickCount(); - while( g_dwActiveServerProcesses > 0 ) + while (g_dwActiveServerProcesses > 0) { - if( (GetTickCount() - tickCount) > 10000 ) + if ((GetTickCount() - tickCount) > 10000) { break; } @@ -2905,18 +3020,18 @@ CopyMultiSzToOutput( PCWSTR pszListCopy = pszList; while (*pszList != L'\0') { - cbData += (static_cast(wcslen(pszList)) + 1)*sizeof(WCHAR); + cbData += (static_cast(wcslen(pszList)) + 1) * sizeof(WCHAR); pszList += wcslen(pszList) + 1; } cbData += sizeof(WCHAR); if (FAILED(pProvider->GetOutputBuffer(cbData, - &pvData))) + &pvData))) { return; } memcpy(pvData, - pszListCopy, - cbData); + pszListCopy, + cbData); *pcbData = cbData; } @@ -2933,7 +3048,7 @@ struct CACHE_CONTEXT PCSTR pszHostName; IGlobalRSCAQueryProvider *pProvider; __field_bcount_part(cbBuffer, cbData) - PBYTE pvData; + PBYTE pvData; DWORD cbData; DWORD cbBuffer; }; @@ -2943,13 +3058,27 @@ FORWARDING_HANDLER::TerminateRequest( bool fClientInitiated ) { - AcquireSRWLockExclusive(&m_RequestLock); + bool fAcquiredLock = FALSE; + + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + fAcquiredLock = TRUE; + } if (m_hRequest != NULL) { - WinHttpCloseHandle(m_hRequest); - m_hRequest = NULL; - + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } m_fHandleClosedDueToClient = fClientInitiated; } @@ -2962,7 +3091,13 @@ FORWARDING_HANDLER::TerminateRequest( m_pWebSocket->TerminateRequest(); } - ReleaseSRWLockExclusive(&m_RequestLock); + if (fAcquiredLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } } BYTE * @@ -2970,17 +3105,17 @@ FORWARDING_HANDLER::GetNewResponseBuffer( DWORD dwBufferSize ) { - DWORD dwNeededSize = (m_cEntityBuffers+1)*sizeof(BYTE *); + DWORD dwNeededSize = (m_cEntityBuffers + 1) * sizeof(BYTE *); if (dwNeededSize > m_buffEntityBuffers.QuerySize() && !m_buffEntityBuffers.Resize( - max(dwNeededSize, m_buffEntityBuffers.QuerySize()*2))) + max(dwNeededSize, m_buffEntityBuffers.QuerySize() * 2))) { return NULL; } BYTE *pBuffer = (BYTE *)HeapAlloc(GetProcessHeap(), - 0, // dwFlags - dwBufferSize); + 0, // dwFlags + dwBufferSize); if (pBuffer == NULL) { return NULL; @@ -2996,11 +3131,11 @@ VOID FORWARDING_HANDLER::FreeResponseBuffers() { BYTE **pBuffers = m_buffEntityBuffers.QueryPtr(); - for (DWORD i=0; i #include -#include "aspnetcoreutils.h" +#include "environmentvariablehash.h" #include "..\aspnetcore_msg.h" #include "aspnetcoreconfig.h" #include "serverprocess.h" diff --git a/src/AspNetCore/Src/processmanager.cxx b/src/AspNetCore/Src/processmanager.cxx index 26f96becc0..39a9a1811d 100644 --- a/src/AspNetCore/Src/processmanager.cxx +++ b/src/AspNetCore/Src/processmanager.cxx @@ -238,6 +238,9 @@ PROCESS_MANAGER::GetProcess( pConfig->QueryArguments(), pConfig->QueryStartupTimeLimitInMS(), pConfig->QueryShutdownTimeLimitInMS(), + pConfig->QueryWindowsAuthEnabled(), + pConfig->QueryBasicAuthEnabled(), + pConfig->QueryAnonymousAuthEnabled(), pConfig->QueryEnvironmentVariables(), pConfig->QueryStdoutLogEnabled(), pConfig->QueryStdoutLogFile() diff --git a/src/AspNetCore/Src/protocolconfig.cxx b/src/AspNetCore/Src/protocolconfig.cxx index 85fd86aa61..3f17e64a9f 100644 --- a/src/AspNetCore/Src/protocolconfig.cxx +++ b/src/AspNetCore/Src/protocolconfig.cxx @@ -11,7 +11,6 @@ PROTOCOL_CONFIG::Initialize() m_fKeepAlive = TRUE; m_msTimeout = 120000; - m_fPreserveHostHeader = TRUE; m_fReverseRewriteHeaders = FALSE; if (FAILED(hr = m_strXForwardedForName.CopyW(L"X-Forwarded-For"))) diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index c63362548e..5a2289b4c6 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -11,14 +11,17 @@ extern BOOL g_fNsiApiNotSupported; HRESULT SERVER_PROCESS::Initialize( - PROCESS_MANAGER *pProcessManager, - STRU *pszProcessExePath, - STRU *pszArguments, - DWORD dwStartupTimeLimitInMS, - DWORD dwShtudownTimeLimitInMS, - MULTISZ *pszEnvironment, - BOOL fStdoutLogEnabled, - STRU *pstruStdoutLogFile + 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 ) { HRESULT hr = S_OK; @@ -28,29 +31,14 @@ SERVER_PROCESS::Initialize( m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS; m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS; m_fStdoutLogEnabled = fStdoutLogEnabled; - + m_fWindowsAuthEnabled = fWindowsAuthEnabled; + m_fBasicAuthEnabled = fBasicAuthEnabled; + m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; m_pProcessManager->ReferenceProcessManager(); - hr = m_ProcessPath.Copy(*pszProcessExePath); - if (FAILED(hr)) - { - goto Finished; - } - - hr = m_struLogFile.Copy(*pstruStdoutLogFile); - if (FAILED(hr)) - { - goto Finished; - } - - hr = m_Arguments.Copy(*pszArguments); - if (FAILED(hr)) - { - goto Finished; - } - - hr = m_Environment.Copy(*pszEnvironment); - if (FAILED(hr)) + if (FAILED (hr = m_ProcessPath.Copy(*pszProcessExePath)) || + FAILED (hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| + FAILED (hr = m_Arguments.Copy(*pszArguments))) { goto Finished; } @@ -60,7 +48,7 @@ SERVER_PROCESS::Initialize( m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES NULL); // LPCTSTR lpName #pragma warning( disable : 4312) - // 0xdeadbeef is used by Antares + // 0xdeadbeef is used by Antares if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) { m_hJobObject = NULL; @@ -81,104 +69,32 @@ SERVER_PROCESS::Initialize( goto Finished; } } + + m_pEnvironmentVarTable = pEnvironmentVariables; } Finished: return hr; } - HRESULT -SERVER_PROCESS::StartProcess( - IHttpContext *context +SERVER_PROCESS::GetRandomPort +( + DWORD* pdwPickedPort, + DWORD dwExcludedPort = 0 ) { - HRESULT hr = S_OK; - PROCESS_INFORMATION processInformation = { 0 }; - STARTUPINFOW startupInfo = { 0 }; - WCHAR *pszCommandLine = NULL; - DWORD dwCommandLineLen = 0; - DWORD dwNumDigitsInPort = 0; - DWORD dwNumDigitsInDebugPort = 0; - LPWSTR pszCurrentEnvironment = NULL; - DWORD dwCurrentEnvSize = 0; - DWORD dwCreationFlags = 0; - BOOL fReady = FALSE; - DWORD dwTickCount = 0; - STACK_STRU(strAspNetCorePortEnvVar, 32); - STACK_STRU(strAspNetCoreDebugPortEnvVar, 32); - MULTISZ mszNewEnvironment; - MULTISZ mszEnvCopy; - DWORD cChildProcess = 0; - DWORD dwTimeDifference = 0; - STACK_STRU(strEventMsg, 256); - BOOL fDebugPortEnvSet = FALSE; - BOOL fReplacedEnv = FALSE; - BOOL fPortInUse = FALSE; - LPCWSTR pszRootApplicationPath = NULL; - BOOL fDebuggerAttachedToChildProcess = FALSE; - STRU strFullProcessPath; - STRU struRelativePath; - STRU struApplicationId; - RPC_STATUS rpcStatus; - UUID logUuid; - PSTR pszLogUuid = NULL; - BOOL fRpcStringAllocd = FALSE; - STRU struGuidEnv; - STRU finalCommandLine; - BOOL fDonePrepareCommandLine = FALSE; - // - // process id of the process listening on port we randomly generated. - // - DWORD dwActualProcessId = 0; - WCHAR* pszPath = NULL; - WCHAR* pszFullPath = NULL; - LPCWSTR apsz[1]; - PCWSTR pszAppPath = NULL; - DWORD dwBufferSize = 0; - - GetStartupInfoW(&startupInfo); - - // - // setup stdout and stderr handles to our stdout handle only if - // the handle is valid. - // - - SetupStdHandles(context, &startupInfo); - - // - // generate new guid for each process - // - - 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; - - hr = m_straGuid.Copy(pszLogUuid); - if (FAILED(hr)) - { - goto Finished; - } - - // - // Generate random port that the new process will listen on. - // + HRESULT hr = S_OK; + BOOL fPortInUse = FALSE; + DWORD dwActualProcessId = 0; if (g_fNsiApiNotSupported) { - m_dwPort = GenerateRandomPort(); + // + // 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 { @@ -190,75 +106,123 @@ SERVER_PROCESS::StartProcess( // determing whether the randomly generated port is // in use by any other process. // - - m_dwPort = GenerateRandomPort(); - hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fPortInUse); + 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) + if (cRetry >= MAX_RETRY) { hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); - goto Finished; } } - dwNumDigitsInPort = GetNumberOfDigits(m_dwPort); - hr = strAspNetCorePortEnvVar.SafeSnwprintf(L"%s=%u", ASPNETCORE_PORT_STR, m_dwPort); - if (FAILED(hr)) + 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; } - // - // Generate random debug port that the new process will listen on. - // this will only be used if ASPNETCORE_DEBUG_PORT placeholder is found - // in the aspNetCore config. - // - - if (g_fNsiApiNotSupported) + if (swprintf_s(buffer, 15, L"%d", m_dwPort) <= 0) { - while ((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); - } - else - { - DWORD cRetry = 0; - do - { - while ((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); - hr = CheckIfServerIsUp(m_dwDebugPort, &dwActualProcessId, &fPortInUse); - } while (fPortInUse && ++cRetry < MAX_RETRY); - - if (cRetry > MAX_RETRY) - { - hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); - goto Finished; - } + hr = E_INVALIDARG; + goto Finished; } - dwNumDigitsInDebugPort = GetNumberOfDigits(m_dwDebugPort); - hr = strAspNetCoreDebugPortEnvVar.SafeSnwprintf(L"%s=%u", - ASPNETCORE_DEBUG_PORT_STR, - m_dwDebugPort); - if (FAILED(hr)) + 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; } - // - // Create environment for new process - // +Finished: + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} - struApplicationId.Copy(L"ASPNETCORE_APPL_PATH="); +HRESULT +SERVER_PROCESS::SetupAppPath( + IHttpContext* pContext, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + DWORD dwCounter = 0; + DWORD dwPosition = 0; + WCHAR* pszPath = NULL; + 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; + } + + if (m_struAppPath.IsEmpty()) + { + if (FAILED(hr = m_pszRootApplicationPath.Copy(pContext->GetApplication()->GetApplicationPhysicalPath())) || + FAILED(hr = m_struAppFullPath.Copy(pContext->GetApplication()->GetAppConfigPath()))) + { + goto Finished; + } + } // let's find the app path. IIS does not support nested sites // we can seek for the fourth '/' if it exits // MACHINE/WEBROOT/APPHOST//. - pszAppPath = context->GetApplication()->GetAppConfigPath(); - DWORD dwCounter = 0; - DWORD dwPosition = 0; - while (pszAppPath[dwPosition] != NULL) + pszPath = m_struAppFullPath.QueryStr(); + while (pszPath[dwPosition] != NULL) { - if (pszAppPath[dwPosition] == '/') + if (pszPath[dwPosition] == '/') { dwCounter++; if (dwCounter == 4) @@ -266,49 +230,395 @@ SERVER_PROCESS::StartProcess( } dwPosition++; } + if (dwCounter == 4) { - struApplicationId.Append(pszAppPath + dwPosition); + hr = m_struAppPath.Copy(pszPath + dwPosition); } else { - struApplicationId.Append(L"/"); + hr = m_struAppPath.Copy(L"/"); } - mszNewEnvironment.Append(struApplicationId); + if (FAILED(hr)) + { + goto Finished; + } - struGuidEnv.Copy(L"ASPNETCORE_TOKEN="); - struGuidEnv.AppendA(m_straGuid.QueryStr(), m_straGuid.QueryCCH()); - - mszNewEnvironment.Append(struGuidEnv); - - pszRootApplicationPath = context->GetApplication()->GetApplicationPhysicalPath(); - - // - // generate process command line. - // - dwCommandLineLen = (DWORD)wcslen(pszRootApplicationPath) + m_ProcessPath.QueryCCH() + m_Arguments.QueryCCH() + 4; - - pszCommandLine = new WCHAR[dwCommandLineLen]; - if (pszCommandLine == 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_struAppPath.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(); + if (dwError != ERROR_ENVVAR_NOT_FOUND) + { + 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 = struRelativePath.Copy(pszRootApplicationPath)) || - FAILED(hr = struRelativePath.Append(L"\\")) || - FAILED(hr = struRelativePath.Append(pszPath))) + if (FAILED(hr = strRelativePath.Copy(m_pszRootApplicationPath.QueryStr())) || + FAILED(hr = strRelativePath.Append(L"\\")) || + FAILED(hr = strRelativePath.Append(pszPath))) { goto Finished; } - dwBufferSize = struRelativePath.QueryCCH() + 1; + dwBufferSize = strRelativePath.QueryCCH() + 1; pszFullPath = new WCHAR[dwBufferSize]; if (pszFullPath == NULL) { @@ -317,259 +627,81 @@ SERVER_PROCESS::StartProcess( } if (_wfullpath(pszFullPath, - struRelativePath.QueryStr(), + strRelativePath.QueryStr(), dwBufferSize) == NULL) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto Finished; } - FILE *file = NULL; if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL) { fclose(file); pszPath = pszFullPath; } } - - if (swprintf_s(pszCommandLine, - dwCommandLineLen, - L"\"%s\" %s", - pszPath, - m_Arguments.QueryStr()) == -1) - { - hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); - goto Finished; - } - - // - // replace %ASPNETCORE_PORT% with port number - // - - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - pszCommandLine, - ASPNETCORE_PORT_PLACEHOLDER, - ASPNETCORE_PORT_PLACEHOLDER_CCH, - m_dwPort, - dwNumDigitsInPort, - &fReplacedEnv); - if (FAILED(hr)) + if (FAILED(hr = pstrCommandLine->Copy(pszPath)) || + FAILED(hr = pstrCommandLine->Append(L" ")) || + FAILED(hr = pstrCommandLine->Append(m_Arguments.QueryStr()))) { goto Finished; } - // - // append AspNetCorePort to env variables. - // - mszNewEnvironment.Append(strAspNetCorePortEnvVar); - - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - pszCommandLine, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, - m_dwDebugPort, - dwNumDigitsInDebugPort, - &fReplacedEnv); - if (FAILED(hr)) +Finished: + if (pszFullPath != NULL) { - goto Finished; + delete pszFullPath; } - - if (fReplacedEnv) - { - // - // append debug port to environment only if - // ASPNETCORE_DEBUG_PORT placeholder is present. - // - - mszNewEnvironment.Append(strAspNetCoreDebugPortEnvVar); - fDebugPortEnvSet = TRUE; - } - - // - // append the environment variables from web.config/aspNetCore section. - // append takes in length of string without the last null char - // this allows user to override current environment variables - // - - if (!mszEnvCopy.Copy(m_Environment)) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - LPWSTR multisz = mszEnvCopy.QueryStr(); - - while (*multisz != '\0') - { - // - // replace %ASPNETCORE_PORT% placeholder if present. - // - - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - multisz, - ASPNETCORE_PORT_PLACEHOLDER, - ASPNETCORE_PORT_PLACEHOLDER_CCH, - m_dwPort, - dwNumDigitsInPort, - &fReplacedEnv); - if (FAILED(hr)) - { - goto Finished; - } - - // - // replace %ASPNETCORE_DEBUG_PORT% placeholder if present. - // if this placeholder is present, add this placeholder=value as - // an environment variable as well. - // - - hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( - multisz, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER, - ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, - m_dwDebugPort, - dwNumDigitsInDebugPort, - &fReplacedEnv); - if (FAILED(hr)) - { - goto Finished; - } - - if (fReplacedEnv && !fDebugPortEnvSet) - { - mszNewEnvironment.Append(strAspNetCoreDebugPortEnvVar); - } - - mszNewEnvironment.Append(multisz); - multisz += wcslen(multisz) + 1; - } - - // - // Append the current env. - // copy takes in number of bytes including the double null terminator - // - pszCurrentEnvironment = GetEnvironmentStringsW(); - if (pszCurrentEnvironment == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); - goto Finished; - } - - // - // determine length of current environment block - // - - do - { - while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); - } while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); - - DBG_ASSERT(dwCurrentEnvSize > 0); - // - // environment block ends with \0\0, we don't want include the last \0 for appending - // - mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize - 1); - - dwCreationFlags = CREATE_NO_WINDOW | - CREATE_UNICODE_ENVIRONMENT | - CREATE_SUSPENDED | - CREATE_NEW_PROCESS_GROUP; + return hr; +} - finalCommandLine.Copy(pszCommandLine); - fDonePrepareCommandLine = TRUE; +HRESULT +SERVER_PROCESS::PostStartCheck( + const STRU* const pStruCommandline, + STRU* pStruErrorMessage) +{ + HRESULT hr = S_OK; - if (!CreateProcessW( - NULL, // applicationName - finalCommandLine.QueryStr(), - NULL, // processAttr - NULL, // threadAttr - TRUE, // inheritHandles - dwCreationFlags, - mszNewEnvironment.QueryStr(), - pszRootApplicationPath, // currentDir - &startupInfo, - &processInformation)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - // don't check return code as we already in error report - strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), - hr, - 0); - goto Finished; - } + BOOL fReady = FALSE; + BOOL fProcessMatch = FALSE; + BOOL fDebuggerAttached = FALSE; + DWORD dwTickCount = 0; + DWORD dwTimeDifference = 0; + DWORD dwActualProcessId = 0; + INT iChildProcessIndex = -1; - m_hProcessHandle = processInformation.hProcess; - m_dwProcessId = processInformation.dwProcessId; - - if (m_hJobObject != NULL) - { - if (!AssignProcessToJobObject(m_hJobObject, - processInformation.hProcess)) - { - 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; - } - - if (CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0) + if (CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) { // some error occurred - assume debugger is not attached; - fDebuggerAttachedToChildProcess = FALSE; + fDebuggerAttached = FALSE; } - // - // since servers like tomcat would startup even if there was a port - // collision, need to make sure the server is up and listening on - // the port specified. - // - dwTickCount = GetTickCount(); do { DWORD processStatus; - if (GetExitCodeProcess(m_hProcessHandle, &processStatus)) { + // make sure the process is still running if (processStatus != STILL_ACTIVE) { hr = E_FAIL; - strEventMsg.SafeSnwprintf( + pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), hr, processStatus); goto Finished; } } - if (g_fNsiApiNotSupported) - { - hr = CheckIfServerIsUp(m_dwPort, &fReady); - } - else - { - hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); - } + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); - fDebuggerAttachedToChildProcess = IsDebuggerIsAttached(); + fDebuggerAttached = IsDebuggerIsAttached(); if (!fReady) { @@ -578,12 +710,10 @@ SERVER_PROCESS::StartProcess( dwTimeDifference = (GetTickCount() - dwTickCount); } while (fReady == FALSE && - ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttachedToChildProcess)); + ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttached)); - hr = RegisterProcessWait(&m_hProcessWaitHandle, - m_hProcessHandle); - - if (FAILED(hr)) + // register call back with the created process + if (FAILED(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle))) { goto Finished; } @@ -591,50 +721,53 @@ SERVER_PROCESS::StartProcess( // // check if debugger is attached after startupTimeout. // - if (!fDebuggerAttachedToChildProcess && - CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0) + if (!fDebuggerAttached && + CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) { // some error occurred - assume debugger is not attached; - fDebuggerAttachedToChildProcess = FALSE; + fDebuggerAttached = FALSE; } - hr = GetChildProcessHandles(); - if (FAILED(hr)) - { - goto Finished; - } - - BOOL processMatch = FALSE; if (dwActualProcessId == m_dwProcessId) { m_dwListeningProcessId = m_dwProcessId; - processMatch = TRUE; + fProcessMatch = TRUE; } - for (DWORD i = 0; i < m_cChildProcess; ++i) + if (!fProcessMatch) { - if (!processMatch && dwActualProcessId == m_dwChildProcessIds[i]) + // could be the scenario that backend creates child process + if (FAILED(hr = GetChildProcessHandles())) { - m_dwListeningProcessId = m_dwChildProcessIds[i]; - processMatch = TRUE; + goto Finished; } - if (m_hChildProcessHandles[i] != NULL) + for (DWORD i = 0; i < m_cChildProcess; ++i) { - if (fDebuggerAttachedToChildProcess == FALSE && CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttachedToChildProcess) == 0) + // a child process listen on the assigned port + if (dwActualProcessId == m_dwChildProcessIds[i]) { - // some error occurred - assume debugger is not attached; - fDebuggerAttachedToChildProcess = FALSE; - } + m_dwListeningProcessId = m_dwChildProcessIds[i]; + fProcessMatch = TRUE; - hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], - m_hChildProcessHandles[i]); - if (FAILED(hr)) - { - goto Finished; - } + if (m_hChildProcessHandles[i] != NULL) + { + if (fDebuggerAttached == FALSE && + CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } - cChildProcess++; + if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], + m_hChildProcessHandles[i]))) + { + goto Finished; + } + iChildProcessIndex = i; + } + break; + } } } @@ -643,15 +776,14 @@ SERVER_PROCESS::StartProcess( // // hr is already set by CheckIfServerIsUp // - if (dwTimeDifference >= m_dwStartupTimeLimitInMS) { hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); - strEventMsg.SafeSnwprintf( + pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), m_dwPort, hr); } @@ -659,25 +791,25 @@ SERVER_PROCESS::StartProcess( goto Finished; } - if (!g_fNsiApiNotSupported && !processMatch) + if (!g_fNsiApiNotSupported && !fProcessMatch) { // // process that we created is not listening // on the port we specified. // fReady = FALSE; - strEventMsg.SafeSnwprintf( + pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), m_dwPort, hr); hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } - if (cChildProcess > 0) + if (iChildProcessIndex >= 0) { // // final check to make sure child process listening on HTTP is still UP @@ -686,22 +818,15 @@ SERVER_PROCESS::StartProcess( // and we would not know about it. // - if (g_fNsiApiNotSupported) - { - hr = CheckIfServerIsUp(m_dwPort, &fReady); - } - else - { - hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); - } + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); if ((FAILED(hr) || fReady == FALSE)) { - strEventMsg.SafeSnwprintf( + pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath, + pStruCommandline->QueryStr(), m_dwPort, hr); goto Finished; @@ -736,18 +861,159 @@ SERVER_PROCESS::StartProcess( if (!g_fNsiApiNotSupported) { - m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, FALSE, m_dwListeningProcessId); + m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + m_dwListeningProcessId); } // // mark server process as Ready // - m_fReady = TRUE; +Finished: + return hr; +} + +HRESULT +SERVER_PROCESS::StartProcess( + IHttpContext *context +) +{ + HRESULT hr = S_OK; + PROCESS_INFORMATION processInformation = {0}; + STARTUPINFOW startupInfo = {0}; + BOOL fDonePrepareCommandLine = FALSE; + DWORD dwCreationFlags = 0; + + STACK_STRU( strEventMsg, 256); + STRU strFullProcessPath; + STRU struRelativePath; + STRU struApplicationId; + STRU struCommandLine; + + 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(context, &startupInfo); + + 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(context, 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; + } + + // + // generate process command line. + // + if (FAILED(hr = SetupCommandLine(&struCommandLine))) + { + goto Finished; + } + + fDonePrepareCommandLine = TRUE; + + dwCreationFlags = CREATE_NO_WINDOW | + CREATE_UNICODE_ENVIRONMENT | + CREATE_SUSPENDED | + CREATE_NEW_PROCESS_GROUP; + + if (!CreateProcessW( + NULL, // applicationName + struCommandLine.QueryStr(), + NULL, // processAttr + NULL, // threadAttr + TRUE, // inheritHandles + dwCreationFlags, + mszNewEnvironment.QueryStr(), + m_pszRootApplicationPath.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(&struCommandLine, &strEventMsg))) + { + goto Finished; + } + + if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, - pszAppPath, + m_struAppFullPath.QueryStr(), m_dwProcessId, m_dwPort))) { @@ -772,22 +1038,35 @@ SERVER_PROCESS::StartProcess( } Finished: - if (FAILED(hr)) + 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( - pszAppPath, - ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, - hr); + m_struAppFullPath.QueryStr(), + ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, + hr); else strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, - pszAppPath, - pszRootApplicationPath, - finalCommandLine.QueryStr(), - hr); + ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath, + struCommandLine.QueryStr(), + hr); } apsz[0] = strEventMsg.QueryStr(); @@ -809,81 +1088,41 @@ Finished: } } - if (fRpcStringAllocd) - { - RpcStringFreeA((BYTE **)&pszLogUuid); - pszLogUuid = NULL; - } - - if (processInformation.hThread != NULL) - { - CloseHandle(processInformation.hThread); - processInformation.hThread = NULL; - } - - if (pszCurrentEnvironment != NULL) - { - FreeEnvironmentStringsW(pszCurrentEnvironment); - pszCurrentEnvironment = NULL; - } - - if (pszCommandLine != NULL) - { - delete[] pszCommandLine; - pszCommandLine = NULL; - } - - if (pszFullPath != NULL) - { - delete[] pszFullPath; - pszFullPath = NULL; - } - - if (FAILED(hr) || m_fReady == FALSE) + if ( FAILED( hr ) || m_fReady == FALSE) { if (m_hStdoutHandle != NULL) { - if (m_hStdoutHandle != INVALID_HANDLE_VALUE) + if ( m_hStdoutHandle != INVALID_HANDLE_VALUE ) { - CloseHandle(m_hStdoutHandle); + CloseHandle( m_hStdoutHandle ); } m_hStdoutHandle = NULL; } - if (m_fStdoutLogEnabled) + if ( m_fStdoutLogEnabled ) { m_Timer.CancelTimer(); } if (m_hListeningProcessHandle != NULL) { - if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE) + if( m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) { - CloseHandle(m_hListeningProcessHandle); + CloseHandle( m_hListeningProcessHandle ); } m_hListeningProcessHandle = NULL; } - if (m_hProcessWaitHandle != NULL) + if ( m_hProcessWaitHandle != NULL ) { - UnregisterWait(m_hProcessWaitHandle); + UnregisterWait( m_hProcessWaitHandle ); m_hProcessWaitHandle = NULL; } - for (DWORD i = 0; iGetApplication()->GetApplicationPhysicalPath(), - &struAbsLogFilePath); + hr = PATH::ConvertPathToFullPath( m_struLogFile.QueryStr(), + context->GetApplication()->GetApplicationPhysicalPath(), + &struAbsLogFilePath ); if (FAILED(hr)) { goto Finished; } GetSystemTime(&systemTime); - hr = struLogFileName.SafeSnwprintf(L"%s_%d_%d%d%d%d%d%d.log", - struAbsLogFilePath.QueryStr(), - GetCurrentProcessId(), - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond); + hr = struLogFileName.SafeSnwprintf( L"%s_%d_%d%d%d%d%d%d.log", + struAbsLogFilePath.QueryStr(), + GetCurrentProcessId(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond ); if (FAILED(hr)) { goto Finished; } - m_hStdoutHandle = CreateFileW(struLogFileName.QueryStr(), - FILE_WRITE_DATA, - FILE_SHARE_READ, - &saAttr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (m_hStdoutHandle == INVALID_HANDLE_VALUE) + m_hStdoutHandle = CreateFileW( struLogFileName.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if ( m_hStdoutHandle == INVALID_HANDLE_VALUE ) { fStdoutLoggingFailed = TRUE; m_hStdoutHandle = NULL; - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, - struLogFileName.QueryStr(), - HRESULT_FROM_GETLASTERROR()))) + if( SUCCEEDED( strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + struLogFileName.QueryStr(), + HRESULT_FROM_GETLASTERROR() ) ) ) { apsz[0] = strEventMsg.QueryStr(); @@ -1010,23 +1249,23 @@ SERVER_PROCESS::SetupStdHandles( } } - if (!fStdoutLoggingFailed) + if( !fStdoutLoggingFailed ) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; pStartupInfo->hStdError = m_hStdoutHandle; pStartupInfo->hStdOutput = m_hStdoutHandle; - m_struFullLogFile.Copy(struLogFileName); + m_struFullLogFile.Copy( struLogFileName ); // start timer to open and close handles regularly. m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000); } } - if ((!m_fStdoutLogEnabled || fStdoutLoggingFailed) && + if( (!m_fStdoutLogEnabled || fStdoutLoggingFailed) && m_pProcessManager->QueryNULHandle() != NULL && - m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE) + m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE ) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; @@ -1049,103 +1288,28 @@ SERVER_PROCESS::TimerCallback( { Instance; Timer; - SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*)Context; + SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*) Context; HANDLE hStdoutHandle = NULL; - SECURITY_ATTRIBUTES saAttr = { 0 }; + SECURITY_ATTRIBUTES saAttr = {0}; HRESULT hr = S_OK; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; + saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; - hStdoutHandle = CreateFileW(pServerProcess->QueryFullLogPath(), - FILE_READ_DATA, - FILE_SHARE_WRITE, - &saAttr, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (hStdoutHandle == INVALID_HANDLE_VALUE) + hStdoutHandle = CreateFileW( pServerProcess->QueryFullLogPath(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if( hStdoutHandle == INVALID_HANDLE_VALUE ) { hr = HRESULT_FROM_GETLASTERROR(); } - CloseHandle(hStdoutHandle); -} - -HRESULT -SERVER_PROCESS::CheckIfServerIsUp( - _In_ DWORD dwPort, - _Out_ BOOL *fReady -) -{ - HRESULT hr = S_OK; - int iResult = 0; - SOCKADDR_IN sockAddr; - BOOL fLocked = FALSE; - - _ASSERT(fReady != NULL); - - *fReady = FALSE; - - EnterCriticalSection(&m_csLock); - fLocked = TRUE; - - if (m_socket == INVALID_SOCKET || m_socket == NULL) - { - m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (m_socket == 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(m_socket, (SOCKADDR *)&sockAddr, sizeof(sockAddr)); - if (iResult == SOCKET_ERROR) - { - hr = HRESULT_FROM_WIN32(WSAGetLastError()); - goto Finished; - } - - // - // Connected successfully, close socket. - // - - iResult = closesocket(m_socket); - if (iResult == SOCKET_ERROR) - { - hr = HRESULT_FROM_WIN32(WSAGetLastError()); - goto Finished; - } - - m_socket = NULL; - *fReady = TRUE; - -Finished: - - if (fLocked) - { - LeaveCriticalSection(&m_csLock); - fLocked = FALSE; - } - - return hr; + CloseHandle( hStdoutHandle ); } HRESULT @@ -1161,40 +1325,40 @@ SERVER_PROCESS::CheckIfServerIsUp( MIB_TCPROW_OWNER_PID *pOwner = NULL; DWORD dwSize = 0; - DBG_ASSERT(pfReady); - DBG_ASSERT(pdwProcessId); + DBG_ASSERT( pfReady ); + DBG_ASSERT( pdwProcessId ); *pfReady = FALSE; *pdwProcessId = 0; - - dwResult = GetExtendedTcpTable(NULL, - &dwSize, - FALSE, - AF_INET, - TCP_TABLE_OWNER_PID_LISTENER, - 0); + + 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); + hr = HRESULT_FROM_WIN32( dwResult ); goto Finished; } pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); - if (pTCPInfo == NULL) + if(pTCPInfo == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - - dwResult = GetExtendedTcpTable(pTCPInfo, - &dwSize, - FALSE, - AF_INET, - TCP_TABLE_OWNER_PID_LISTENER, - 0); + + dwResult = GetExtendedTcpTable(pTCPInfo, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); if (dwResult != NO_ERROR) { - hr = HRESULT_FROM_WIN32(dwResult); + hr = HRESULT_FROM_WIN32( dwResult ); goto Finished; } @@ -1202,7 +1366,7 @@ SERVER_PROCESS::CheckIfServerIsUp( for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++) { pOwner = &pTCPInfo->table[dwLoop]; - if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort) + if( ntohs((USHORT)pOwner->dwLocalPort) == dwPort ) { *pdwProcessId = pOwner->dwOwningPid; *pfReady = TRUE; @@ -1212,9 +1376,9 @@ SERVER_PROCESS::CheckIfServerIsUp( Finished: - if (pTCPInfo != NULL) + if( pTCPInfo != NULL ) { - HeapFree(GetProcessHeap(), 0, pTCPInfo); + HeapFree( GetProcessHeap(), 0, pTCPInfo ); pTCPInfo = NULL; } @@ -1251,20 +1415,20 @@ SERVER_PROCESS::SendSignal( FreeConsole(); } - if (fFreeConsole) + if(fFreeConsole) { // IISExpress and hostedwebcore w3wp run as background process // have to attach console back to ensure post app_offline scenario still work AttachConsole(ATTACH_PARENT_PROCESS); } } - + if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) { - if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { //Backend process will be terminated, remove the waitcallback - if (m_hProcessWaitHandle != NULL) + if (m_hProcessWaitHandle != NULL) { UnregisterWait(m_hProcessWaitHandle); m_hProcessWaitHandle = NULL; @@ -1327,32 +1491,32 @@ SERVER_PROCESS::StopProcess( m_pProcessManager->IncrementRapidFailCount(); - for (INT i = 0; iNumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0)); + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0 ) ); - if (dwError == ERROR_MORE_DATA) + if( dwError == ERROR_MORE_DATA ) { hr = E_OUTOFMEMORY; // some error goto Finished; } - if (processList == NULL || - (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0)) + 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) + if( processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES ) { - hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); goto Finished; } - - for (DWORD i = 0; iNumberOfProcessIdsInList; i++) + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) { dwPid = (DWORD)processList->ProcessIdList[i]; - if (dwPid != dwWorkerProcessPid) + if( dwPid != dwWorkerProcessPid ) { - HANDLE hProcess = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid - ); - BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent); - if (!returnValue) + HANDLE hProcess = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); + if( ! returnValue ) { goto Finished; } - if (fDebuggerPresent) + if( fDebuggerPresent ) { break; } @@ -1461,7 +1625,7 @@ SERVER_PROCESS::IsDebuggerIsAttached( Finished: - if (processList != NULL) + if( processList != NULL ) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1469,7 +1633,7 @@ Finished: return fDebuggerPresent; } -HRESULT +HRESULT SERVER_PROCESS::GetChildProcessHandles( VOID ) @@ -1488,7 +1652,7 @@ SERVER_PROCESS::GetChildProcessHandles( { dwError = NO_ERROR; - if (processList != NULL) + if( processList != NULL ) { HeapFree(GetProcessHeap(), 0, processList); processList = NULL; @@ -1497,77 +1661,77 @@ SERVER_PROCESS::GetChildProcessHandles( cbNumBytes = cbNumBytes * 2; } - processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)HeapAlloc( - GetProcessHeap(), - 0, - cbNumBytes - ); - if (processList == NULL) + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if( processList == NULL ) { hr = E_OUTOFMEMORY; goto Finished; } - RtlZeroMemory(processList, cbNumBytes); + RtlZeroMemory( processList, cbNumBytes ); - if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, - NULL)) + if( !QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL) ) { dwError = GetLastError(); - if (dwError != ERROR_MORE_DATA) + if( dwError != ERROR_MORE_DATA ) { hr = HRESULT_FROM_WIN32(dwError); goto Finished; } } - } while (dwRetries++ < 5 && - processList != NULL && - (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); - if (dwError == ERROR_MORE_DATA) + if( dwError == ERROR_MORE_DATA ) { hr = E_OUTOFMEMORY; // some error goto Finished; } - if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)) + 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) + if( processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES ) { - hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); goto Finished; } - - for (DWORD i = 0; iNumberOfProcessIdsInList; i++) + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) { dwPid = (DWORD)processList->ProcessIdList[i]; - if (dwPid != m_dwProcessId && - dwPid != dwWorkerProcessPid) + if( dwPid != m_dwProcessId && + dwPid != dwWorkerProcessPid ) { - m_hChildProcessHandles[m_cChildProcess] = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid - ); + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); m_dwChildProcessIds[m_cChildProcess] = dwPid; - m_cChildProcess++; + m_cChildProcess ++; } } Finished: - if (processList != NULL) + if( processList != NULL ) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1577,7 +1741,7 @@ Finished: HRESULT SERVER_PROCESS::StopAllProcessesInJobObject( - VOID + VOID ) { HRESULT hr = S_OK; @@ -1591,7 +1755,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( do { - if (processList != NULL) + if( processList != NULL ) { HeapFree(GetProcessHeap(), 0, processList); processList = NULL; @@ -1600,66 +1764,66 @@ SERVER_PROCESS::StopAllProcessesInJobObject( cbNumBytes = cbNumBytes * 2; } - processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)HeapAlloc( - GetProcessHeap(), - 0, - cbNumBytes - ); - if (processList == NULL) + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if( processList == NULL ) { hr = E_OUTOFMEMORY; goto Finished; } - RtlZeroMemory(processList, cbNumBytes); + RtlZeroMemory( processList, cbNumBytes ); - if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, - NULL)) + if( !QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL) ) { DWORD dwError = GetLastError(); - if (dwError != ERROR_MORE_DATA) + if( dwError != ERROR_MORE_DATA ) { hr = HRESULT_FROM_WIN32(dwError); goto Finished; } } - } while (dwRetries++ < 5 && - processList != NULL && - (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); - if (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++) + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) { - if (dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i]) + if( dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i] ) { - hProcess = OpenProcess(PROCESS_TERMINATE, - FALSE, - (DWORD)processList->ProcessIdList[i]); - if (hProcess != NULL) + hProcess = OpenProcess( PROCESS_TERMINATE, + FALSE, + (DWORD)processList->ProcessIdList[i] ); + if( hProcess != NULL ) { - if (!TerminateProcess(hProcess, 1)) + if( !TerminateProcess(hProcess, 1) ) { hr = HRESULT_FROM_GETLASTERROR(); } else { - WaitForSingleObject(hProcess, INFINITE); + WaitForSingleObject( hProcess, INFINITE ); } - if (hProcess != NULL) + if( hProcess != NULL ) { - CloseHandle(hProcess); + CloseHandle( hProcess ); hProcess = NULL; } } @@ -1668,7 +1832,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( Finished: - if (processList != NULL) + if( processList != NULL ) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1676,27 +1840,26 @@ Finished: return hr; } -SERVER_PROCESS::SERVER_PROCESS() : - m_cRefs(1), - m_hProcessHandle(NULL), - m_hProcessWaitHandle(NULL), - m_dwProcessId(0), - m_cChildProcess(0), - m_socket(INVALID_SOCKET), - m_fReady(FALSE), - m_lStopping(0L), - m_hStdoutHandle(NULL), - m_fStdoutLogEnabled(FALSE), - m_hJobObject(NULL), - m_pForwarderConnection(NULL), - m_dwListeningProcessId(0), - m_hListeningProcessHandle(NULL) +SERVER_PROCESS::SERVER_PROCESS() : + m_cRefs( 1 ), + m_hProcessHandle( NULL ), + m_hProcessWaitHandle( NULL ), + m_dwProcessId( 0 ), + m_cChildProcess( 0 ), + m_socket( INVALID_SOCKET ), + m_fReady( FALSE ), + m_lStopping( 0L ), + m_hStdoutHandle( NULL ), + m_fStdoutLogEnabled( FALSE ), + m_hJobObject( NULL ), + m_pForwarderConnection( NULL ), + m_dwListeningProcessId( 0 ), + m_hListeningProcessHandle( NULL ) { InterlockedIncrement(&g_dwActiveServerProcesses); - srand((unsigned int)time(NULL)); - InitializeCriticalSection(&m_csLock); + srand(GetTickCount()); - for (INT i = 0; iDereferenceProcessManager(); m_pProcessManager = NULL; } - if (m_pForwarderConnection != NULL) + if(m_pForwarderConnection != NULL) { m_pForwarderConnection->DereferenceForwarderConnection(); m_pForwarderConnection = NULL; } - DeleteCriticalSection(&m_csLock); + 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); } -VOID +VOID ProcessHandleCallback( _In_ PVOID pContext, _In_ BOOL ) { - SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*)pContext; + SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*) pContext; pServerProcess->HandleProcessExit(); } @@ -1817,31 +1983,31 @@ SERVER_PROCESS::RegisterProcessWait( HRESULT hr = S_OK; NTSTATUS status = 0; - _ASSERT(phWaitHandle != NULL && *phWaitHandle == NULL); + _ASSERT( phWaitHandle != NULL && *phWaitHandle == NULL ); *phWaitHandle = NULL; // wait thread will dereference. ReferenceServerProcess(); - status = RegisterWaitForSingleObject( - phWaitHandle, - hProcessToWaitOn, - (WAITORTIMERCALLBACK)&ProcessHandleCallback, - this, - INFINITE, - WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD - ); + status = RegisterWaitForSingleObject( + phWaitHandle, + hProcessToWaitOn, + (WAITORTIMERCALLBACK)&ProcessHandleCallback, + this, + INFINITE, + WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD + ); - if (status < 0) + if( status < 0 ) { - hr = HRESULT_FROM_NT(status); + hr = HRESULT_FROM_NT( status ); goto Finished; } Finished: - if (FAILED(hr)) + if( FAILED( hr ) ) { *phWaitHandle = NULL; DereferenceServerProcess(); @@ -1855,9 +2021,10 @@ SERVER_PROCESS::HandleProcessExit() { HRESULT hr = S_OK; BOOL fReady = FALSE; + DWORD dwProcessId = 0; if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { - CheckIfServerIsUp(m_dwPort, &fReady); + CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady); if (!fReady) { diff --git a/src/AspNetCore/Src/websockethandler.cxx b/src/AspNetCore/Src/websockethandler.cxx index f95a98c0e2..05147f64ba 100644 --- a/src/AspNetCore/Src/websockethandler.cxx +++ b/src/AspNetCore/Src/websockethandler.cxx @@ -212,7 +212,10 @@ WEBSOCKET_HANDLER::IndicateCompletionToIIS( _pHandler->SetStatus(FORWARDER_DONE); - _pHttpContext->IndicateCompletion(RQ_NOTIFICATION_PENDING); + // 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); } HRESULT @@ -744,18 +747,25 @@ Routine Description: ++*/ { HRESULT hr = S_OK; + BOOL fLocked = FALSE; CleanupReason cleanupReason = CleanupReasonUnknown; DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnWinHttpSendComplete"); - EnterCriticalSection (&_RequestLock); - if (_fCleanupInProgress) { goto Finished; } + EnterCriticalSection (&_RequestLock); + + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } // // Data was successfully sent to backend. // Initiate next receive from IIS. @@ -768,8 +778,10 @@ Routine Description: } Finished: - - LeaveCriticalSection(&_RequestLock); + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } if (FAILED (hr)) { @@ -840,18 +852,24 @@ Routine Description: --*/ { HRESULT hr = S_OK; + BOOL fLocked = FALSE; CleanupReason cleanupReason = CleanupReasonUnknown; DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnWinHttpReceiveComplete"); - EnterCriticalSection(&_RequestLock); - if (_fCleanupInProgress) { goto Finished; } + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } hr = DoIisWebSocketSend( pCompletionStatus->dwBytesTransferred, pCompletionStatus->eBufferType @@ -864,9 +882,10 @@ Routine Description: } Finished: - - LeaveCriticalSection(&_RequestLock); - + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } if (FAILED (hr)) { Cleanup (cleanupReason); @@ -902,21 +921,26 @@ Routine Description: --*/ { HRESULT hr = S_OK; + BOOL fLocked = FALSE; CleanupReason cleanupReason = CleanupReasonUnknown; UNREFERENCED_PARAMETER(cbIo); DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisSendComplete"); - EnterCriticalSection(&_RequestLock); - - if (FAILED (hrCompletion)) + if (FAILED(hrCompletion)) { hr = hrCompletion; cleanupReason = ClientDisconnect; goto Finished; } + if (_fCleanupInProgress) + { + goto Finished; + } + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; if (_fCleanupInProgress) { goto Finished; @@ -934,9 +958,10 @@ Routine Description: } Finished: - - LeaveCriticalSection(&_RequestLock); - + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } if (FAILED (hr)) { Cleanup (cleanupReason); @@ -977,15 +1002,14 @@ Routine Description: --*/ { HRESULT hr = S_OK; + BOOL fLocked = FALSE; CleanupReason cleanupReason = CleanupReasonUnknown; WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType; DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisReceiveComplete"); - EnterCriticalSection(&_RequestLock); - - if (FAILED (hrCompletion)) + if (FAILED(hrCompletion)) { cleanupReason = ClientDisconnect; hr = hrCompletion; @@ -997,6 +1021,13 @@ Routine Description: goto Finished; } + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } // // Get Buffer Type from flags. // @@ -1018,9 +1049,10 @@ Routine Description: } Finished: - - LeaveCriticalSection(&_RequestLock); - + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } if (FAILED (hr)) { Cleanup (cleanupReason); @@ -1042,7 +1074,7 @@ Finished: VOID WEBSOCKET_HANDLER::Cleanup( CleanupReason reason - ) +) /*++ Routine Description: @@ -1056,11 +1088,18 @@ Arguments: CleanupReason --*/ { - DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); + 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; @@ -1080,5 +1119,8 @@ Arguments: _pHttpContext->CancelIo(); Finished: - LeaveCriticalSection(&_RequestLock); + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } } From fc54fef0bf27f98f780334e28126e5e0bbb5118c Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Thu, 8 Jun 2017 10:42:32 -0700 Subject: [PATCH 047/107] Fix nuget output path (#107) Fix nuget output path * Update theoutput path of nuget package --- Build/repo.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Build/repo.targets b/Build/repo.targets index 5e22efef79..77658df344 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -39,7 +39,7 @@ $(RepositoryRoot)nuget\AspNetCore.nuspec - + @@ -56,6 +56,6 @@ DependsOnTargets="_ResolvePackagePublisherPath" Condition="'$(PublishPackage)'=='true'"> - + \ No newline at end of file From e714153782a9fde9a529804efb0bcbf015af20b1 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 9 Jun 2017 10:29:14 -0700 Subject: [PATCH 048/107] Update to PackagePublisher 1.0.2-* --- Build/repo.targets | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Build/repo.targets b/Build/repo.targets index 77658df344..91af778ab3 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -1,4 +1,4 @@ - + true $(VerifyDependsOn);PublishPackage @@ -7,7 +7,7 @@ - + @@ -42,20 +42,16 @@ - - - - - - - <_PackagePublisherPath>@(PackagePublisherPath)\PackagePublisher.exe - - - - + + + + + + + \ No newline at end of file From f5253459ecf64b723b2a0da003118a0144004699 Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 12 Jun 2017 15:16:01 -0700 Subject: [PATCH 049/107] Change nuget pacakge version to include -pre- --- Build/repo.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/repo.targets b/Build/repo.targets index 91af778ab3..43f840f38e 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -39,7 +39,7 @@ $(RepositoryRoot)nuget\AspNetCore.nuspec - + Date: Mon, 12 Jun 2017 19:41:12 -0700 Subject: [PATCH 050/107] =?UTF-8?q?Antares=20blocks=20some=20windows=20API?= =?UTF-8?q?s.=20We=20have=20use=20socket=20instead=20of=20calli=E2=80=A6?= =?UTF-8?q?=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Antares blocks some windows APIs. We have use socket instead of calling GetExtendedTcpTable to check whether the backend is listening on given port. * Use socket instead of calling GetExtendedTcpTable to check if the backend process listens on given port since Antares blocks couple APIs * Antares blocks some windows APIs. We have use socket instead of calling GetExtendedTcpTable * update format * format change --- src/AspNetCore/Src/serverprocess.cxx | 612 +++++++++++++++------------ 1 file changed, 334 insertions(+), 278 deletions(-) diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 5a2289b4c6..7a20383f01 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -36,9 +36,9 @@ SERVER_PROCESS::Initialize( m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; m_pProcessManager->ReferenceProcessManager(); - if (FAILED (hr = m_ProcessPath.Copy(*pszProcessExePath)) || - FAILED (hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| - FAILED (hr = m_Arguments.Copy(*pszArguments))) + if (FAILED(hr = m_ProcessPath.Copy(*pszProcessExePath)) || + FAILED(hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| + FAILED(hr = m_Arguments.Copy(*pszArguments))) { goto Finished; } @@ -48,7 +48,7 @@ SERVER_PROCESS::Initialize( m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES NULL); // LPCTSTR lpName #pragma warning( disable : 4312) - // 0xdeadbeef is used by Antares + // 0xdeadbeef is used by Antares if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) { m_hJobObject = NULL; @@ -133,7 +133,7 @@ SERVER_PROCESS::SetupListenPort( if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') { m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); - if(m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) + if (m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) { hr = E_INVALIDARG; goto Finished; @@ -170,7 +170,7 @@ SERVER_PROCESS::SetupListenPort( goto Finished; } - if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)) || FAILED(hr = m_struPort.Copy(buffer))) { @@ -251,14 +251,14 @@ SERVER_PROCESS::SetupAppPath( hr = E_OUTOFMEMORY; goto Finished; } - if (FAILED (hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppPath.QueryStr())) || - FAILED (hr = pEnvironmentVarTable->InsertRecord(pEntry))) + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppPath.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) { goto Finished; } Finished: - if (pEntry!= NULL) + if (pEntry != NULL) { pEntry->Dereference(); pEntry = NULL; @@ -310,7 +310,7 @@ SERVER_PROCESS::SetupAppToken( fRpcStringAllocd = TRUE; - if (FAILED (hr = m_straGuid.Copy(pszLogUuid))) + if (FAILED(hr = m_straGuid.Copy(pszLogUuid))) { goto Finished; } @@ -427,7 +427,7 @@ SERVER_PROCESS::InitEnvironmentVariablesTable( pEnvironmentVarTable->FindKey(HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); - if (pHostingEntry !=NULL ) + if (pHostingEntry != NULL) { // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration // the value will be used in OutputEnvironmentVariables. Do nothing here @@ -453,7 +453,7 @@ SERVER_PROCESS::InitEnvironmentVariablesTable( { // have to increase the buffer and try get environment var again strStartupAssemblyEnv.Reset(); - strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) +1); + strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) + 1); dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, strStartupAssemblyEnv.QueryStr(), strStartupAssemblyEnv.QuerySizeCCH()); @@ -470,7 +470,7 @@ SERVER_PROCESS::InitEnvironmentVariablesTable( } strStartupAssemblyEnv.SyncWithBuffer(); - if (fFound) + if (fFound) { strStartupAssemblyEnv.Append(L";"); } @@ -583,7 +583,7 @@ SERVER_PROCESS::OutputEnvironmentVariables // append the remaining env variable in hash table pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToMultiSz, pmszOutput); -Finished: +Finished: if (pszEnvironmentVariables != NULL) { FreeEnvironmentStringsW(pszEnvironmentVariables); @@ -698,9 +698,10 @@ SERVER_PROCESS::PostStartCheck( goto Finished; } } - + // + // dwActualProcessId will be set only when NsiAPI(GetExtendedTcpTable) is supported + // hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); - fDebuggerAttached = IsDebuggerIsAttached(); if (!fReady) @@ -727,51 +728,74 @@ SERVER_PROCESS::PostStartCheck( // some error occurred - assume debugger is not attached; fDebuggerAttached = FALSE; } - - if (dwActualProcessId == m_dwProcessId) + if (!g_fNsiApiNotSupported) { - m_dwListeningProcessId = m_dwProcessId; - fProcessMatch = TRUE; - } - - if (!fProcessMatch) - { - // could be the scenario that backend creates child process - if (FAILED(hr = GetChildProcessHandles())) + // + // NsiAPI(GetExtendedTcpTable) is supported. we should check whether processIds matche + // + if (dwActualProcessId == m_dwProcessId) { - goto Finished; + m_dwListeningProcessId = m_dwProcessId; + fProcessMatch = TRUE; } - for (DWORD i = 0; i < m_cChildProcess; ++i) + if (!fProcessMatch) { - // a child process listen on the assigned port - if (dwActualProcessId == m_dwChildProcessIds[i]) + // could be the scenario that backend creates child process + if (FAILED(hr = GetChildProcessHandles())) { - m_dwListeningProcessId = m_dwChildProcessIds[i]; - fProcessMatch = TRUE; + goto Finished; + } - if (m_hChildProcessHandles[i] != NULL) + for (DWORD i = 0; i < m_cChildProcess; ++i) + { + // a child process listen on the assigned port + if (dwActualProcessId == m_dwChildProcessIds[i]) { - if (fDebuggerAttached == FALSE && - CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0) - { - // some error occurred - assume debugger is not attached; - fDebuggerAttached = FALSE; - } + m_dwListeningProcessId = m_dwChildProcessIds[i]; + fProcessMatch = TRUE; - if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], - m_hChildProcessHandles[i]))) + if (m_hChildProcessHandles[i] != NULL) { - goto Finished; + 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; } - iChildProcessIndex = i; + break; } - 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(), + pStruCommandline->QueryStr(), + m_dwPort, + hr); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } } - if (fReady == FALSE) + if (!fReady) { // // hr is already set by CheckIfServerIsUp @@ -787,25 +811,6 @@ SERVER_PROCESS::PostStartCheck( m_dwPort, hr); } - - goto Finished; - } - - if (!g_fNsiApiNotSupported && !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(), - pStruCommandline->QueryStr(), - m_dwPort, - hr); - hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } @@ -862,7 +867,7 @@ SERVER_PROCESS::PostStartCheck( if (!g_fNsiApiNotSupported) { m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, + FALSE, m_dwListeningProcessId); } @@ -913,7 +918,7 @@ SERVER_PROCESS::StartProcess( // // setup the the port that the backend process will listen on // - if (FAILED (hr= SetupListenPort(pHashTable))) + if (FAILED(hr = SetupListenPort(pHashTable))) { goto Finished; } @@ -967,7 +972,7 @@ SERVER_PROCESS::StartProcess( mszNewEnvironment.QueryStr(), m_pszRootApplicationPath.QueryStr(), // currentDir &startupInfo, - &processInformation) ) + &processInformation)) { hr = HRESULT_FROM_WIN32(GetLastError()); // don't the check return code as we already in error report @@ -996,9 +1001,9 @@ SERVER_PROCESS::StartProcess( } } - if (ResumeThread( processInformation.hThread ) == -1) + if (ResumeThread(processInformation.hThread) == -1) { - hr = HRESULT_FROM_WIN32( GetLastError() ); + hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; } @@ -1012,10 +1017,10 @@ SERVER_PROCESS::StartProcess( if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, - m_struAppFullPath.QueryStr(), - m_dwProcessId, - m_dwPort))) + ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, + m_struAppFullPath.QueryStr(), + m_dwProcessId, + m_dwPort))) { apsz[0] = strEventMsg.QueryStr(); @@ -1088,34 +1093,34 @@ Finished: } } - if ( FAILED( hr ) || m_fReady == FALSE) + if (FAILED( hr ) || m_fReady == FALSE) { if (m_hStdoutHandle != NULL) { - if ( m_hStdoutHandle != INVALID_HANDLE_VALUE ) + if (m_hStdoutHandle != INVALID_HANDLE_VALUE) { - CloseHandle( m_hStdoutHandle ); + CloseHandle(m_hStdoutHandle); } m_hStdoutHandle = NULL; } - if ( m_fStdoutLogEnabled ) + if (m_fStdoutLogEnabled) { m_Timer.CancelTimer(); } if (m_hListeningProcessHandle != NULL) { - if( m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE) { - CloseHandle( m_hListeningProcessHandle ); + CloseHandle(m_hListeningProcessHandle); } m_hListeningProcessHandle = NULL; } - if ( m_hProcessWaitHandle != NULL ) + if (m_hProcessWaitHandle != NULL) { - UnregisterWait( m_hProcessWaitHandle ); + UnregisterWait(m_hProcessWaitHandle); m_hProcessWaitHandle = NULL; } @@ -1134,7 +1139,7 @@ SERVER_PROCESS::SetWindowsAuthToken( { HRESULT hr = S_OK; - if ( m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + if (m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE) { if (!DuplicateHandle( GetCurrentProcess(), hToken, @@ -1171,7 +1176,7 @@ SERVER_PROCESS::SetupStdHandles( DBG_ASSERT(pStartupInfo); - if ( m_fStdoutLogEnabled ) + if (m_fStdoutLogEnabled) { saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; @@ -1179,7 +1184,7 @@ SERVER_PROCESS::SetupStdHandles( if (m_hStdoutHandle != NULL) { - if(!CloseHandle( m_hStdoutHandle )) + if (!CloseHandle(m_hStdoutHandle)) { hr = HRESULT_FROM_GETLASTERROR(); goto Finished; @@ -1188,45 +1193,45 @@ SERVER_PROCESS::SetupStdHandles( m_hStdoutHandle = NULL; } - hr = PATH::ConvertPathToFullPath( m_struLogFile.QueryStr(), - context->GetApplication()->GetApplicationPhysicalPath(), - &struAbsLogFilePath ); + hr = PATH::ConvertPathToFullPath(m_struLogFile.QueryStr(), + context->GetApplication()->GetApplicationPhysicalPath(), + &struAbsLogFilePath ); if (FAILED(hr)) { goto Finished; } GetSystemTime(&systemTime); - hr = struLogFileName.SafeSnwprintf( L"%s_%d_%d%d%d%d%d%d.log", - struAbsLogFilePath.QueryStr(), - GetCurrentProcessId(), - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond ); + hr = struLogFileName.SafeSnwprintf(L"%s_%d_%d%d%d%d%d%d.log", + struAbsLogFilePath.QueryStr(), + GetCurrentProcessId(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond ); if (FAILED(hr)) { goto Finished; } - m_hStdoutHandle = CreateFileW( struLogFileName.QueryStr(), - FILE_WRITE_DATA, - FILE_SHARE_READ, - &saAttr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL ); - if ( m_hStdoutHandle == INVALID_HANDLE_VALUE ) + m_hStdoutHandle = CreateFileW(struLogFileName.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (m_hStdoutHandle == INVALID_HANDLE_VALUE) { fStdoutLoggingFailed = TRUE; m_hStdoutHandle = NULL; - if( SUCCEEDED( strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, - struLogFileName.QueryStr(), - HRESULT_FROM_GETLASTERROR() ) ) ) + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + struLogFileName.QueryStr(), + HRESULT_FROM_GETLASTERROR()))) { apsz[0] = strEventMsg.QueryStr(); @@ -1249,23 +1254,23 @@ SERVER_PROCESS::SetupStdHandles( } } - if( !fStdoutLoggingFailed ) + if (!fStdoutLoggingFailed) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; pStartupInfo->hStdError = m_hStdoutHandle; pStartupInfo->hStdOutput = m_hStdoutHandle; - m_struFullLogFile.Copy( struLogFileName ); + m_struFullLogFile.Copy(struLogFileName); // start timer to open and close handles regularly. m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000); } } - if( (!m_fStdoutLogEnabled || fStdoutLoggingFailed) && + if ((!m_fStdoutLogEnabled || fStdoutLoggingFailed) && m_pProcessManager->QueryNULHandle() != NULL && - m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE ) + m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; @@ -1297,19 +1302,19 @@ SERVER_PROCESS::TimerCallback( saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; - hStdoutHandle = CreateFileW( pServerProcess->QueryFullLogPath(), - FILE_READ_DATA, - FILE_SHARE_WRITE, - &saAttr, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL ); - if( hStdoutHandle == INVALID_HANDLE_VALUE ) + hStdoutHandle = CreateFileW(pServerProcess->QueryFullLogPath(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hStdoutHandle == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_GETLASTERROR(); } - CloseHandle( hStdoutHandle ); + CloseHandle(hStdoutHandle); } HRESULT @@ -1324,61 +1329,119 @@ SERVER_PROCESS::CheckIfServerIsUp( 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 ); + 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; - - 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) + if (!g_fNsiApiNotSupported) { - 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; - } + dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); - // 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 ) + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) { - *pdwProcessId = pOwner->dwOwningPid; - *pfReady = TRUE; - break; + 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; + } + + // + // Connected successfully, close socket. + // + iResult = closesocket(socketCheck); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + socketCheck = INVALID_SOCKET; + *pfReady = TRUE; } Finished: - if( pTCPInfo != NULL ) + if (pTCPInfo != NULL) { - HeapFree( GetProcessHeap(), 0, pTCPInfo ); + HeapFree(GetProcessHeap(), 0, pTCPInfo); pTCPInfo = NULL; } @@ -1428,7 +1491,7 @@ SERVER_PROCESS::SendSignal( if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { //Backend process will be terminated, remove the waitcallback - if (m_hProcessWaitHandle != NULL) + if (m_hProcessWaitHandle != NULL) { UnregisterWait(m_hProcessWaitHandle); m_hProcessWaitHandle = NULL; @@ -1491,26 +1554,26 @@ SERVER_PROCESS::StopProcess( m_pProcessManager->IncrementRapidFailCount(); - for(INT i=0;iNumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0 ) ); + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0 ) ); - if( dwError == ERROR_MORE_DATA ) + if (dwError == ERROR_MORE_DATA) { hr = E_OUTOFMEMORY; // some error goto Finished; } - if( processList == NULL || - ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0 ) ) + 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 ) + if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES) { - hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } - for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + for (DWORD i=0; iNumberOfProcessIdsInList; i++) { dwPid = (DWORD)processList->ProcessIdList[i]; - if( dwPid != dwWorkerProcessPid ) + if (dwPid != dwWorkerProcessPid) { - HANDLE hProcess = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid - ); + HANDLE hProcess = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid); + BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); - if( ! returnValue ) + if (!returnValue) { goto Finished; } - if( fDebuggerPresent ) + if (fDebuggerPresent) { break; } @@ -1625,7 +1688,7 @@ SERVER_PROCESS::IsDebuggerIsAttached( Finished: - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1652,7 +1715,7 @@ SERVER_PROCESS::GetChildProcessHandles( { dwError = NO_ERROR; - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); processList = NULL; @@ -1666,7 +1729,7 @@ SERVER_PROCESS::GetChildProcessHandles( 0, cbNumBytes ); - if( processList == NULL ) + if (processList == NULL) { hr = E_OUTOFMEMORY; goto Finished; @@ -1674,50 +1737,50 @@ SERVER_PROCESS::GetChildProcessHandles( RtlZeroMemory( processList, cbNumBytes ); - if( !QueryInformationJobObject( + if (!QueryInformationJobObject( m_hJobObject, JobObjectBasicProcessIdList, processList, cbNumBytes, - NULL) ) + NULL)) { dwError = GetLastError(); - if( dwError != ERROR_MORE_DATA ) + if (dwError != ERROR_MORE_DATA) { hr = HRESULT_FROM_WIN32(dwError); goto Finished; } } - } while( dwRetries++ < 5 && - processList != NULL && - ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + } while(dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); - if( dwError == ERROR_MORE_DATA ) + if (dwError == ERROR_MORE_DATA) { hr = E_OUTOFMEMORY; // some error goto Finished; } - if( processList == NULL || ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ) + 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 ) + if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES) { - hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } - for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + for (DWORD i=0; iNumberOfProcessIdsInList; i++) { dwPid = (DWORD)processList->ProcessIdList[i]; - if( dwPid != m_dwProcessId && - dwPid != dwWorkerProcessPid ) + if (dwPid != m_dwProcessId && + dwPid != dwWorkerProcessPid) { m_hChildProcessHandles[m_cChildProcess] = OpenProcess( PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, @@ -1731,7 +1794,7 @@ SERVER_PROCESS::GetChildProcessHandles( Finished: - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1755,7 +1818,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( do { - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); processList = NULL; @@ -1777,53 +1840,53 @@ SERVER_PROCESS::StopAllProcessesInJobObject( RtlZeroMemory( processList, cbNumBytes ); - if( !QueryInformationJobObject( + if (!QueryInformationJobObject( m_hJobObject, JobObjectBasicProcessIdList, processList, cbNumBytes, - NULL) ) + NULL)) { DWORD dwError = GetLastError(); - if( dwError != ERROR_MORE_DATA ) + if (dwError != ERROR_MORE_DATA) { hr = HRESULT_FROM_WIN32(dwError); goto Finished; } } - } while( dwRetries++ < 5 && + } while (dwRetries++ < 5 && processList != NULL && - ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); - if( 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++ ) + for (DWORD i=0; iNumberOfProcessIdsInList; i++) { - if( dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i] ) + if (dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i]) { - hProcess = OpenProcess( PROCESS_TERMINATE, - FALSE, - (DWORD)processList->ProcessIdList[i] ); - if( hProcess != NULL ) + hProcess = OpenProcess(PROCESS_TERMINATE, + FALSE, + (DWORD)processList->ProcessIdList[i]); + if (hProcess != NULL) { - if( !TerminateProcess(hProcess, 1) ) + if (!TerminateProcess(hProcess, 1)) { hr = HRESULT_FROM_GETLASTERROR(); } else { - WaitForSingleObject( hProcess, INFINITE ); + WaitForSingleObject(hProcess, INFINITE); } - if( hProcess != NULL ) + if (hProcess != NULL) { - CloseHandle( hProcess ); + CloseHandle(hProcess); hProcess = NULL; } } @@ -1832,7 +1895,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( Finished: - if( processList != NULL ) + if (processList != NULL) { HeapFree(GetProcessHeap(), 0, processList); } @@ -1840,21 +1903,20 @@ Finished: return hr; } -SERVER_PROCESS::SERVER_PROCESS() : - m_cRefs( 1 ), - m_hProcessHandle( NULL ), - m_hProcessWaitHandle( NULL ), - m_dwProcessId( 0 ), - m_cChildProcess( 0 ), - m_socket( INVALID_SOCKET ), - m_fReady( FALSE ), - m_lStopping( 0L ), - m_hStdoutHandle( NULL ), - m_fStdoutLogEnabled( FALSE ), - m_hJobObject( NULL ), - m_pForwarderConnection( NULL ), - m_dwListeningProcessId( 0 ), - m_hListeningProcessHandle( NULL ) +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) { InterlockedIncrement(&g_dwActiveServerProcesses); srand(GetTickCount()); @@ -1869,88 +1931,82 @@ SERVER_PROCESS::SERVER_PROCESS() : SERVER_PROCESS::~SERVER_PROCESS() { - if(m_socket != NULL) - { - closesocket( m_socket ); - m_socket = NULL; - } - - if(m_hProcessWaitHandle != NULL) + if (m_hProcessWaitHandle != NULL) { UnregisterWait( m_hProcessWaitHandle ); m_hProcessWaitHandle = NULL; } - for(INT i=0;iDereferenceProcessManager(); m_pProcessManager = NULL; } - if(m_pForwarderConnection != NULL) + if (m_pForwarderConnection != NULL) { m_pForwarderConnection->DereferenceForwarderConnection(); m_pForwarderConnection = NULL; @@ -1983,14 +2039,14 @@ SERVER_PROCESS::RegisterProcessWait( HRESULT hr = S_OK; NTSTATUS status = 0; - _ASSERT( phWaitHandle != NULL && *phWaitHandle == NULL ); + _ASSERT(phWaitHandle != NULL && *phWaitHandle == NULL); *phWaitHandle = NULL; // wait thread will dereference. ReferenceServerProcess(); - status = RegisterWaitForSingleObject( + status = RegisterWaitForSingleObject( phWaitHandle, hProcessToWaitOn, (WAITORTIMERCALLBACK)&ProcessHandleCallback, @@ -1999,15 +2055,15 @@ SERVER_PROCESS::RegisterProcessWait( WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD ); - if( status < 0 ) + if (status < 0) { - hr = HRESULT_FROM_NT( status ); + hr = HRESULT_FROM_NT(status); goto Finished; } Finished: - if( FAILED( hr ) ) + if (FAILED(hr)) { *phWaitHandle = NULL; DereferenceServerProcess(); From 6b411adbd0d430dee952e5baede2545cb9e87f25 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Wed, 21 Jun 2017 16:48:38 -0700 Subject: [PATCH 051/107] revert the preserve host header change as it failed on Antares (#114) --- src/AspNetCore/Inc/protocolconfig.h | 7 +++++++ src/AspNetCore/Src/forwardinghandler.cxx | 24 +++++++++++++----------- src/AspNetCore/Src/protocolconfig.cxx | 1 + 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/AspNetCore/Inc/protocolconfig.h b/src/AspNetCore/Inc/protocolconfig.h index 7cc0dc03d7..f7d915d6a9 100644 --- a/src/AspNetCore/Inc/protocolconfig.h +++ b/src/AspNetCore/Inc/protocolconfig.h @@ -33,6 +33,12 @@ class PROTOCOL_CONFIG return m_msTimeout; } + BOOL + QueryPreserveHostHeader() const + { + return m_fPreserveHostHeader; + } + BOOL QueryReverseRewriteHeaders() const { @@ -84,6 +90,7 @@ class PROTOCOL_CONFIG private: BOOL m_fKeepAlive; + BOOL m_fPreserveHostHeader; BOOL m_fReverseRewriteHeaders; BOOL m_fIncludePortInXForwardedFor; diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 5f4e316181..39820d3837 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -635,19 +635,21 @@ FORWARDING_HANDLER::GetHeaders( // this is wrong but Kestrel has dependency on it. // should change it in the future // - if (FAILED(hr = PATH::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 + if (!pProtocol->QueryPreserveHostHeader()) { - return hr; + if (FAILED(hr = PATH::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 diff --git a/src/AspNetCore/Src/protocolconfig.cxx b/src/AspNetCore/Src/protocolconfig.cxx index 3f17e64a9f..85fd86aa61 100644 --- a/src/AspNetCore/Src/protocolconfig.cxx +++ b/src/AspNetCore/Src/protocolconfig.cxx @@ -11,6 +11,7 @@ PROTOCOL_CONFIG::Initialize() m_fKeepAlive = TRUE; m_msTimeout = 120000; + m_fPreserveHostHeader = TRUE; m_fReverseRewriteHeaders = FALSE; if (FAILED(hr = m_strXForwardedForName.CopyW(L"X-Forwarded-For"))) From 622da80b431d2c34573aa91656665a0c427d065e Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Fri, 23 Jun 2017 13:46:21 -0700 Subject: [PATCH 052/107] Update PackagePublisher version (#113) --- Build/repo.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/repo.targets b/Build/repo.targets index 43f840f38e..3abc33ed4a 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -7,7 +7,7 @@ - + From 002c8b9bc9a86cd3b4308e62c1f70ea625cbaae2 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 10 Jul 2017 14:55:15 -0700 Subject: [PATCH 053/107] Adding shutdown http message to support the scenario that ctrl signal is not allowed (#118) --- src/AspNetCore/Inc/resource.h | 2 + src/AspNetCore/Inc/serverprocess.h | 26 ++ src/AspNetCore/Src/aspnetcore_msg.mc | 6 + src/AspNetCore/Src/serverprocess.cxx | 471 ++++++++++++++++++++------- 4 files changed, 391 insertions(+), 114 deletions(-) diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index 8b2b9f9844..a7f1b30543 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -16,3 +16,5 @@ #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'." + diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index 8d3f27cb33..e83c5ecef0 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -252,6 +252,27 @@ private: return digits; } + static + VOID + SendShutDownSignal( + LPVOID lpParam + ); + + VOID + SendShutDownSignalInternal( + VOID + ); + + HRESULT + SendShutdownHttpMessage( + VOID + ); + + VOID + TerminateBackendProcess( + VOID + ); + FORWARDER_CONNECTION *m_pForwarderConnection; BOOL m_fStdoutLogEnabled; BOOL m_fWindowsAuthEnabled; @@ -291,6 +312,11 @@ private: 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. + // // m_hChildProcessHandle is the handle to process created by // m_hProcessHandle process if it does. diff --git a/src/AspNetCore/Src/aspnetcore_msg.mc b/src/AspNetCore/Src/aspnetcore_msg.mc index 6e009c8b50..09cc4615e0 100644 --- a/src/AspNetCore/Src/aspnetcore_msg.mc +++ b/src/AspNetCore/Src/aspnetcore_msg.mc @@ -61,6 +61,12 @@ Language=English %1 . +Messageid=1006 +SymbolicName=ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +Language=English +%1 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 7a20383f01..0d2346bbae 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -647,7 +647,6 @@ SERVER_PROCESS::SetupCommandLine( goto Finished; } - Finished: if (pszFullPath != NULL) { @@ -1056,7 +1055,7 @@ Finished: pHashTable = NULL; } - if ( FAILED(hr) ) + if (FAILED(hr)) { if (strEventMsg.IsEmpty()) { @@ -1093,7 +1092,7 @@ Finished: } } - if (FAILED( hr ) || m_fReady == FALSE) + if (FAILED(hr) || m_fReady == FALSE) { if (m_hStdoutHandle != NULL) { @@ -1448,93 +1447,70 @@ Finished: return hr; } -// send ctrl-c signnal to the process to let it gracefully shutdown +// send signal to the process to let it gracefully shutdown // if the process cannot shutdown within given time, terminate it -// todo: allow user to config this shutdown timeout - VOID SERVER_PROCESS::SendSignal( VOID ) { - HANDLE hProc = INVALID_HANDLE_VALUE; - BOOL fIsSuccess = FALSE; - BOOL fFreeConsole = FALSE; - LPCWSTR apsz[1]; - STACK_STRU(strEventMsg, 256); + HRESULT hr = S_OK; + HANDLE hThread = NULL; ReferenceServerProcess(); - hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); - if (hProc != INVALID_HANDLE_VALUE) + m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); + + if (m_hShutdownHandle == NULL) { - // free current console first, as we migh have one, e.g., hostedwebcore case - fFreeConsole = FreeConsole(); - - if (AttachConsole(m_dwProcessId)) - { - // call ctrl-break instead of ctrl-c as child process may ignore ctrl-c - fIsSuccess = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId); - FreeConsole(); - } - - if(fFreeConsole) - { - // IISExpress and hostedwebcore w3wp run as background process - // have to attach console back to ensure post app_offline scenario still work - AttachConsole(ATTACH_PARENT_PROCESS); - } - } - - if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) - { - if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) - { - //Backend process will be terminated, remove the waitcallback - if (m_hProcessWaitHandle != NULL) - { - UnregisterWait(m_hProcessWaitHandle); - m_hProcessWaitHandle = NULL; - } - - // cannot gracefully shutdown or timeout, terminate the process - TerminateProcess(m_hProcessHandle, 0); - - // as we skipped process exit callback (ProcessHandleCallback), - // need to dereference the object otherwise memory leak - DereferenceServerProcess(); - - // 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); - } - } - } + // since we cannot open the process. let's terminate the process + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; } - if (hProc != INVALID_HANDLE_VALUE) + 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) { - CloseHandle(hProc); - hProc = INVALID_HANDLE_VALUE; + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; } - if (m_hProcessHandle != INVALID_HANDLE_VALUE) + + if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) { - CloseHandle(m_hProcessHandle); - m_hProcessHandle = INVALID_HANDLE_VALUE; + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + goto Finished; + } + + +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(); @@ -1609,8 +1585,8 @@ SERVER_PROCESS::IsDebuggerIsAttached( } processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, + GetProcessHeap(), + 0, cbNumBytes ); if (processList == NULL) @@ -1619,14 +1595,14 @@ SERVER_PROCESS::IsDebuggerIsAttached( goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, - NULL) ) + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL)) { dwError = GetLastError(); if (dwError != ERROR_MORE_DATA) @@ -1639,7 +1615,7 @@ SERVER_PROCESS::IsDebuggerIsAttached( } while (dwRetries++ < 5 && processList != NULL && (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0 ) ); + processList->NumberOfProcessIdsInList == 0)); if (dwError == ERROR_MORE_DATA) { @@ -1669,11 +1645,11 @@ SERVER_PROCESS::IsDebuggerIsAttached( if (dwPid != dwWorkerProcessPid) { HANDLE hProcess = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid); + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid); - BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); + BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent); if (!returnValue) { goto Finished; @@ -1725,8 +1701,8 @@ SERVER_PROCESS::GetChildProcessHandles( } processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, + GetProcessHeap(), + 0, cbNumBytes ); if (processList == NULL) @@ -1735,13 +1711,13 @@ SERVER_PROCESS::GetChildProcessHandles( goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, NULL)) { dwError = GetLastError(); @@ -1752,9 +1728,9 @@ SERVER_PROCESS::GetChildProcessHandles( } } - } while(dwRetries++ < 5 && - processList != NULL && - (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); if (dwError == ERROR_MORE_DATA) { @@ -1780,9 +1756,9 @@ SERVER_PROCESS::GetChildProcessHandles( { dwPid = (DWORD)processList->ProcessIdList[i]; if (dwPid != m_dwProcessId && - dwPid != dwWorkerProcessPid) + dwPid != dwWorkerProcessPid ) { - m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, FALSE, dwPid @@ -1828,23 +1804,23 @@ SERVER_PROCESS::StopAllProcessesInJobObject( } processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, + GetProcessHeap(), + 0, cbNumBytes ); - if( processList == NULL ) + if (processList == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, NULL)) { DWORD dwError = GetLastError(); @@ -1855,8 +1831,8 @@ SERVER_PROCESS::StopAllProcessesInJobObject( } } - } while (dwRetries++ < 5 && - processList != NULL && + } while (dwRetries++ < 5 && + processList != NULL && (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)) @@ -1916,12 +1892,13 @@ SERVER_PROCESS::SERVER_PROCESS() : m_hJobObject(NULL), m_pForwarderConnection(NULL), m_dwListeningProcessId(0), - m_hListeningProcessHandle(NULL) + m_hListeningProcessHandle(NULL), + m_hShutdownHandle(NULL) { InterlockedIncrement(&g_dwActiveServerProcesses); srand(GetTickCount()); - for(INT i=0;i 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_struAppPath); + } + 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)) + { + // call ctrl-break instead of ctrl-c as child process may ignore 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); + } + } + + 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); + 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; + } + + // as we skipped process exit callback (ProcessHandleCallback), + // need to dereference the object otherwise memory leak + DereferenceServerProcess(); + + // 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 From 078a03ac707fa5fa9979dc11def5a031e24890c1 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 10 Jul 2017 15:02:53 -0700 Subject: [PATCH 054/107] Fixed build issue (#119) --- test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj index 4545c18afb..54f1e9c3ff 100644 --- a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj +++ b/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj @@ -20,6 +20,7 @@ + From afba2d05aec0f56db287eecd3711e264db501ecc Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Wed, 12 Jul 2017 16:48:47 -0700 Subject: [PATCH 055/107] Jhkim/add test gracefulshutdown (#120) Add new test for Graceful shutdown and Filtering MS request headers --- .../Framework/InitializeTestMachine.cs | 17 ++- test/AspNetCoreModule.Test/FunctionalTest.cs | 39 +++++-- .../FunctionalTestHelper.cs | 109 +++++++++++++++--- .../Program.cs | 32 ++++- .../Startup.cs | 19 ++- 5 files changed, 184 insertions(+), 32 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 2f960f45e8..f64a1ed317 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -13,8 +13,9 @@ namespace AspNetCoreModule.Test.Framework public const string ANCMTestFlagsEnvironmentVariable = "%ANCMTestFlags%"; public const string ANCMTestFlagsDefaultContext = "AdminAnd64Bit"; public const string ANCMTestFlagsTestSkipContext = "SkipTest"; - public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivateAspNetCoreFile"; - + public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivate"; + public const string ANCMTestFlagsUseIISExpressContext = "UseIISExpress"; + private static bool? _usePrivateAspNetCoreFile = null; public static bool? UsePrivateAspNetCoreFile { @@ -115,11 +116,17 @@ namespace AspNetCoreModule.Test.Framework IISConfigUtility.IsIISReady = false; if (IISConfigUtility.IsIISInstalled == true) { - if (Environment.GetEnvironmentVariable("ANCMTEST_USE_IISEXPRESS") != null && Environment.GetEnvironmentVariable("ANCMTEST_USE_IISEXPRESS").Equals("true", StringComparison.InvariantCultureIgnoreCase)) - { + var envValue = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); + if (envValue.ToLower().Contains(ANCMTestFlagsUseIISExpressContext.ToLower())) + { + TestUtility.LogInformation("UseIISExpress is set"); throw new System.ApplicationException("'ANCMTestServerType' environment variable is set to 'true'"); } - + else + { + TestUtility.LogInformation("UseIISExpress is not set"); + } + // check websocket is installed if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) { diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 7488278f03..93d0df1f1e 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -39,15 +39,21 @@ namespace AspNetCoreModule.Test [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0)] - public Task ShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime) + [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); + return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime, isGraceFullShutdownEnabled); } [ConditionalTheory] @@ -274,7 +280,22 @@ namespace AspNetCoreModule.Test { return DoSendHTTPSRequestTest(appPoolBitness); } - + + [ConditionalTheory] + [ANCMTestSkipCondition("%ANCMTestFlags%")] + [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] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 0e9c0fcd0a..f20551586e 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -711,37 +711,54 @@ namespace AspNetCoreModule.Test } } - public static async Task DoShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime) + 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)) { - // Set new value (10 second) to make the backend process get the Ctrl-C signal and measure when the recycle happens - iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", valueOfshutdownTimeLimit); - iisConfig.SetANCMConfig( - testSite.SiteName, - testSite.AspNetCoreApp.Name, - "environmentVariable", - new string[] { "ANCMTestShutdownDelay", "20000" } - ); + DateTime startTime = DateTime.Now; - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + // 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" }); + expectedGracefulShutdownResponseStatusCode = "200"; + } + + string response = await GetResponse(testSite.AspNetCoreApp.GetUri(""), HttpStatusCode.OK); + Assert.True(response == "Running"); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); - // Set a new value such as 100 to make the backend process being recycled - DateTime startTime = DateTime.Now; + // 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 - startTime; + var difference = endTime - startTime2; Assert.True(difference.Seconds >= expectedClosingTime); Assert.True(difference.Seconds < expectedClosingTime + 3); - Assert.True(backendProcessId != await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK)); + string newBackendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + Assert.True(backendProcessId != newBackendProcessId); await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); - } + // 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"); } } @@ -1180,6 +1197,58 @@ namespace AspNetCoreModule.Test } } + 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 + string requestHeaders = string.Empty; + requestHeaders = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); + requestHeaders = requestHeaders.Replace(" ", ""); + Assert.False(requestHeaders.ToUpper().Contains(requestHeader.ToLower()+":")); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + requestHeader = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); + requestHeaders = requestHeaders.Replace(" ", ""); + Assert.False(requestHeaders.ToUpper().Contains(requestHeader.ToLower() + ":")); + + // 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)) @@ -1512,6 +1581,16 @@ namespace AspNetCoreModule.Test 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); diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index e02eeabd3b..35e934da14 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -14,6 +14,9 @@ namespace AspnetCoreModule.TestSites.Standard { public static class Program { + public static IApplicationLifetime AappLifetime; + public static int GracefulShutdownDelayTime = 0; + private static X509Certificate2 _x509Certificate2; public static void Main(string[] args) @@ -141,7 +144,34 @@ namespace AspnetCoreModule.TestSites.Standard } var host = builder.Build(); - host.Run(); + 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( + () => 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) { diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index 149b4b5429..4a69fc9fe9 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -2,6 +2,7 @@ // 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; @@ -199,7 +200,7 @@ namespace AspnetCoreModule.TestSites.Standard app.Map("/ImpersonateMiddleware", subApp => { - subApp.UseMiddleware(); + subApp.UseMiddleware(); subApp.Run(context => { return context.Response.WriteAsync(""); @@ -302,7 +303,21 @@ namespace AspnetCoreModule.TestSites.Standard { response += de.Key + ":" + de.Value + "
    "; } - } + } + } + + // Handle shutdown event from ANCM + if (HttpMethods.IsPost(context.Request.Method) && + //context.Request.Path.Equals(ANCMRequestPath) && + string.Equals("shutdown", context.Request.Headers["MS-ASPNETCORE-EVENT"], StringComparison.OrdinalIgnoreCase)) + { + response = "shutdown"; + string shutdownMode = Environment.GetEnvironmentVariable("GracefulShutdown"); + if (String.IsNullOrEmpty(shutdownMode) || !shutdownMode.ToLower().StartsWith("disabled")) + { + context.Response.StatusCode = StatusCodes.Status202Accepted; + Program.AappLifetime.StopApplication(); + } } return context.Response.WriteAsync(response); }); From 4d35489e851aba2b19dd2374d38e7f39fe56a954 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 17 Jul 2017 11:19:09 -0700 Subject: [PATCH 056/107] Use dotnet pack to pack AspNetCore file --- Build/repo.targets | 10 ++++++---- NuGet.config | 1 + nuget/AspNetCore.csproj | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 nuget/AspNetCore.csproj diff --git a/Build/repo.targets b/Build/repo.targets index 3abc33ed4a..1d39f35999 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -8,7 +8,6 @@ - @@ -35,11 +34,14 @@ - $([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))nuget.commandline\$(NuGetCommandLineVersion)\tools\nuget.exe - $(RepositoryRoot)nuget\AspNetCore.nuspec + 1.0.0-pre-$(BuildNumber) + $(ArtifactsDir)build\ - + + 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) + + + From 4b9a10363c92d1cc239679c92de17091627a4191 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 18 Jul 2017 14:53:34 -0700 Subject: [PATCH 057/107] Explicitly call Restore on AspNetCore.csproj before calling pack on it. --- Build/repo.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/repo.targets b/Build/repo.targets index 1d39f35999..ca7bf29eb8 100644 --- a/Build/repo.targets +++ b/Build/repo.targets @@ -40,7 +40,7 @@ From 42853bcca19e789c67c4df3511b88bb4918ed377 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 27 Jul 2017 14:38:04 -0700 Subject: [PATCH 058/107] Update bootstrapper to use the compiled version of KoreBuild --- .gitignore | 4 +- build.cmd | 2 +- build.ps1 | 218 +++++++++++++----- {Build => build}/Build.Settings | 0 {Build => build}/Config.Definitions.Props | 0 {Build => build}/Key.snk | Bin {Build => build}/Version.props | 0 {Build => build}/build.msbuild | 0 {Build => build}/common.props | 8 +- {Build => build}/dependencies.props | 0 {Build => build}/repo.props | 0 {Build => build}/repo.targets | 0 ...st.csproj => AspNetCoreModule.Test.csproj} | 0 ...spNetCoreModule.TestSites.Standard.csproj} | 0 version.props | 7 - version.xml | 6 + 16 files changed, 178 insertions(+), 67 deletions(-) rename {Build => build}/Build.Settings (100%) rename {Build => build}/Config.Definitions.Props (100%) rename {Build => build}/Key.snk (100%) rename {Build => build}/Version.props (100%) rename {Build => build}/build.msbuild (100%) rename {Build => build}/common.props (93%) rename {Build => build}/dependencies.props (100%) rename {Build => build}/repo.props (100%) rename {Build => build}/repo.targets (100%) rename test/AspNetCoreModule.Test/{aspnetcoremodule.test.csproj => AspNetCoreModule.Test.csproj} (100%) rename test/AspNetCoreModule.TestSites.Standard/{aspnetcoremodule.testsites.standard.csproj => AspNetCoreModule.TestSites.Standard.csproj} (100%) delete mode 100644 version.props create mode 100644 version.xml diff --git a/.gitignore b/.gitignore index 03944b51ac..fa7a3504ea 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,6 @@ src/AspNetCore/version.h .build *.VC.*db -global.json \ No newline at end of file +global.json +korebuild-lock.txt + diff --git a/build.cmd b/build.cmd index 7d4894cb4a..b6c8d24864 100644 --- a/build.cmd +++ b/build.cmd @@ -1,2 +1,2 @@ @ECHO OFF -PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" \ No newline at end of file +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" diff --git a/build.ps1 b/build.ps1 index dc220d733f..d5eb4d5cf2 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,67 +1,177 @@ -$ErrorActionPreference = "Stop" +#!/usr/bin/env powershell +#requires -version 4 -function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) -{ - while($true) - { - try - { - Invoke-WebRequest $url -OutFile $downloadLocation - break - } - catch - { - $exceptionMessage = $_.Exception.Message - Write-Host "Failed to download '$url': $exceptionMessage" - if ($retries -gt 0) { - $retries-- - Write-Host "Waiting 10 seconds before retrying. Retries left: $retries" - Start-Sleep -Seconds 10 +<# +.SYNOPSIS +Build this repository +.DESCRIPTION +Downloads korebuild if required. Then builds the repository. + +.PARAMETER Path +The folder to build. Defaults to the folder containing this script. + +.PARAMETER Channel +The channel of KoreBuild to download. Overrides the value from the config file. + +.PARAMETER DotNetHome +The directory where .NET Core tools will be stored. + +.PARAMETER ToolsSource +The base url where build tools can be downloaded. Overrides the value from the config file. + +.PARAMETER Update +Updates KoreBuild to the latest version even if a lock file is present. + +.PARAMETER ConfigFile +The path to the configuration file that stores values. Defaults to version.xml. + +.PARAMETER MSBuildArgs +Arguments to be passed to MSBuild + +.NOTES +This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. +When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. + +The $ConfigFile is expected to be an XML file. It is optional, and the configuration values in it are optional as well. + +.EXAMPLE +Example config file: +```xml + + + + dev + https://aspnetcore.blob.core.windows.net/buildtools + + +``` +#> +[CmdletBinding(PositionalBinding = $false)] +param( + [string]$Path = $PSScriptRoot, + [Alias('c')] + [string]$Channel, + [Alias('d')] + [string]$DotNetHome, + [Alias('s')] + [string]$ToolsSource, + [Alias('u')] + [switch]$Update, + [string]$ConfigFile = (Join-Path $PSScriptRoot 'version.xml'), + [Parameter(ValueFromRemainingArguments = $true)] + [string[]]$MSBuildArgs +) + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +# +# Functions +# + +function Get-KoreBuild { + + $lockFile = Join-Path $Path 'korebuild-lock.txt' + + if (!(Test-Path $lockFile) -or $Update) { + Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile + } + + $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 + if (!$version) { + Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" + } + $version = $version.TrimStart('version:').Trim() + $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) + + if (!(Test-Path $korebuildPath)) { + Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" + New-Item -ItemType Directory -Path $korebuildPath | Out-Null + $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" + + try { + $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" + Get-RemoteFile $remotePath $tmpfile + if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { + # Use built-in commands where possible as they are cross-plat compatible + Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } - else - { - $exception = $_.Exception - throw $exception + else { + # Fallback to old approach for old installations of PowerShell + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) } } + catch { + Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore + throw + } + finally { + Remove-Item $tmpfile -ErrorAction Ignore + } } + + return $korebuildPath } -cd $PSScriptRoot - -$repoFolder = $PSScriptRoot -$env:REPO_FOLDER = $repoFolder - -$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" -if ($env:KOREBUILD_ZIP) -{ - $koreBuildZip=$env:KOREBUILD_ZIP +function Join-Paths([string]$path, [string[]]$childPaths) { + $childPaths | ForEach-Object { $path = Join-Path $path $_ } + return $path } -$buildFolder = ".build" -$buildFile="$buildFolder\KoreBuild.ps1" - -if (!(Test-Path $buildFolder)) { - Write-Host "Downloading KoreBuild from $koreBuildZip" - - $tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid() - New-Item -Path "$tempFolder" -Type directory | Out-Null - - $localZipFile="$tempFolder\korebuild.zip" - - DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6 - - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder) - - New-Item -Path "$buildFolder" -Type directory | Out-Null - copy-item "$tempFolder\**\build\*" $buildFolder -Recurse - - # Cleanup - if (Test-Path $tempFolder) { - Remove-Item -Recurse -Force $tempFolder +function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) { + if ($RemotePath -notlike 'http*') { + Copy-Item $RemotePath $LocalPath + return } + + $retries = 10 + while ($retries -gt 0) { + $retries -= 1 + try { + Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath + return + } + catch { + Write-Verbose "Request failed. $retries retries remaining" + } + } + + Write-Error "Download failed: '$RemotePath'." } -&"$buildFile" @args \ No newline at end of file +# +# Main +# + +# Load configuration or set defaults + +if (Test-Path $ConfigFile) { + [xml] $config = Get-Content $ConfigFile + if (!($Channel)) { [string] $Channel = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildChannel' } + if (!($ToolsSource)) { [string] $ToolsSource = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildToolsSource' } +} + +if (!$DotNetHome) { + $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` + elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` + elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` + else { Join-Path $PSScriptRoot '.dotnet'} +} + +if (!$Channel) { $Channel = 'dev' } +if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } + +# Execute + +$korebuildPath = Get-KoreBuild +Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') + +try { + Install-Tools $ToolsSource $DotNetHome + Invoke-RepositoryBuild $Path @MSBuildArgs +} +finally { + Remove-Module 'KoreBuild' -ErrorAction Ignore +} diff --git a/Build/Build.Settings b/build/Build.Settings similarity index 100% rename from Build/Build.Settings rename to build/Build.Settings diff --git a/Build/Config.Definitions.Props b/build/Config.Definitions.Props similarity index 100% rename from Build/Config.Definitions.Props rename to build/Config.Definitions.Props diff --git a/Build/Key.snk b/build/Key.snk similarity index 100% rename from Build/Key.snk rename to build/Key.snk diff --git a/Build/Version.props b/build/Version.props similarity index 100% rename from Build/Version.props rename to build/Version.props diff --git a/Build/build.msbuild b/build/build.msbuild similarity index 100% rename from Build/build.msbuild rename to build/build.msbuild diff --git a/Build/common.props b/build/common.props similarity index 93% rename from Build/common.props rename to build/common.props index 7eb2e446b7..bb6d90a0c1 100644 --- a/Build/common.props +++ b/build/common.props @@ -1,6 +1,6 @@ - + Microsoft ASP.NET Core @@ -20,10 +20,10 @@
    - - \ No newline at end of file + diff --git a/Build/dependencies.props b/build/dependencies.props similarity index 100% rename from Build/dependencies.props rename to build/dependencies.props diff --git a/Build/repo.props b/build/repo.props similarity index 100% rename from Build/repo.props rename to build/repo.props diff --git a/Build/repo.targets b/build/repo.targets similarity index 100% rename from Build/repo.targets rename to build/repo.targets diff --git a/test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj similarity index 100% rename from test/AspNetCoreModule.Test/aspnetcoremodule.test.csproj rename to test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj diff --git a/test/AspNetCoreModule.TestSites.Standard/aspnetcoremodule.testsites.standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj similarity index 100% rename from test/AspNetCoreModule.TestSites.Standard/aspnetcoremodule.testsites.standard.csproj rename to test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj diff --git a/version.props b/version.props deleted file mode 100644 index 38c93687ab..0000000000 --- a/version.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - 1.2.0 - preview1 - - \ No newline at end of file diff --git a/version.xml b/version.xml new file mode 100644 index 0000000000..ebc4d03121 --- /dev/null +++ b/version.xml @@ -0,0 +1,6 @@ + + + + dev + + From a8802b2d2c6d651c9896c7886c4f63005c00ff2d Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 28 Jul 2017 09:52:21 -0700 Subject: [PATCH 059/107] Fix versions in the test project so restore works --- ...AspNetCoreModule.TestSites.Standard.csproj | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj index 08a7cfdd27..17ff1b6b8d 100644 --- a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -1,21 +1,20 @@ - netcoreapp1.1 + netcoreapp1.0 - + - - - - - - - - - - - + + + + + + + + + + From 24f09551d9b4c2b4fceb4f4d69bcd3050c555d27 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 28 Jul 2017 17:17:26 -0700 Subject: [PATCH 060/107] Remove usage of PackagePublisher --- build/common.props | 6 --- build/dependencies.props | 11 ++---- build/repo.targets | 38 ++++++++----------- .../AspNetCoreModule.Test.csproj | 7 +--- 4 files changed, 21 insertions(+), 41 deletions(-) diff --git a/build/common.props b/build/common.props index bb6d90a0c1..940c1db088 100644 --- a/build/common.props +++ b/build/common.props @@ -20,10 +20,4 @@ - - diff --git a/build/dependencies.props b/build/dependencies.props index 8c81df7f34..72812e4125 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,11 +1,8 @@ 1.2.0-* - 4.3.0 - 2.0.0-* - 1.6.1 - 2.0.0-* - 15.0.0 - 2.2.0 + 2.1.1-* + 15.3.0-* + 2.3.0-beta2-* - \ No newline at end of file + diff --git a/build/repo.targets b/build/repo.targets index ca7bf29eb8..0faa63a2b7 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -1,15 +1,9 @@ - true - $(VerifyDependsOn);PublishPackage https://dotnet.myget.org/F/aspnetcoremodule/api/v2/package - 3.5.0 + $(VerifyDependsOn);PublishPackage - - - - @@ -35,25 +29,23 @@ 1.0.0-pre-$(BuildNumber) - $(ArtifactsDir)build\ - + - - - + + + + + + - - - - - - \ No newline at end of file + diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj index 54f1e9c3ff..8ed0ec821d 100644 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj @@ -6,7 +6,7 @@ true true - + @@ -29,7 +29,7 @@ - + @@ -48,7 +48,4 @@ - - - From 93b37e14db9d568dc4b13b9956b74fa4d3b48c7d Mon Sep 17 00:00:00 2001 From: pan-wang Date: Fri, 4 Aug 2017 14:51:15 -0700 Subject: [PATCH 061/107] fix websocket connection issue and some memory leak, and add debug print (#129) --- src/AspNetCore/Inc/environmentvariablehash.h | 13 ++++++-- src/AspNetCore/Src/dllmain.cpp | 12 ++++++- src/AspNetCore/Src/forwarderconnection.cxx | 13 ++++++++ src/AspNetCore/Src/forwardinghandler.cxx | 18 +++++++++++ src/AspNetCore/Src/serverprocess.cxx | 33 ++++++++++++-------- 5 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/AspNetCore/Inc/environmentvariablehash.h b/src/AspNetCore/Inc/environmentvariablehash.h index 8914999935..1c92cc161e 100644 --- a/src/AspNetCore/Inc/environmentvariablehash.h +++ b/src/AspNetCore/Inc/environmentvariablehash.h @@ -122,6 +122,8 @@ public: { 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()); @@ -135,9 +137,14 @@ public: ) { ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); - pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); - ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); - pHash->InsertRecord(pNewEntry); + if (pNewEntry != NULL) + { + pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); + ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); + DBG_ASSERT(pHash); + pHash->InsertRecord(pNewEntry); + pNewEntry->Dereference(); + } } private: diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index 0797c3efe7..2aaf5d7044 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -4,7 +4,12 @@ #include "precomp.hxx" #include -//DECLARE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); +#ifdef DEBUG + DECLARE_DEBUG_PRINTS_OBJECT(); + DECLARE_DEBUG_VARIABLE(); + DECLARE_PLATFORM_TYPE(); +#endif // DEBUG + HTTP_MODULE_ID g_pModuleId = NULL; IHttpServer * g_pHttpServer = NULL; @@ -142,6 +147,11 @@ HRESULT HRESULT hr = S_OK; CProxyModuleFactory * pFactory = NULL; +#ifdef DEBUG + CREATE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); + g_dwDebugFlags = DEBUG_FLAGS_ANY; +#endif // DEBUG + CREATE_DEBUG_PRINT_OBJECT; LoadGlobalConfiguration(); diff --git a/src/AspNetCore/Src/forwarderconnection.cxx b/src/AspNetCore/Src/forwarderconnection.cxx index f3b04abbc8..9dd853992d 100644 --- a/src/AspNetCore/Src/forwarderconnection.cxx +++ b/src/AspNetCore/Src/forwarderconnection.cxx @@ -33,6 +33,19 @@ FORWARDER_CONNECTION::Initialize( 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; diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 39820d3837..afc59f72a0 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -2,6 +2,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include "precomp.hxx" +#include + // Just to be aware of the FORWARDING_HANDLER object size. C_ASSERT(sizeof(FORWARDING_HANDLER) <= 632); @@ -53,6 +55,11 @@ m_fWebSocketUpgrade(FALSE), m_fFinishRequest(FALSE), m_fClientDisconnected(FALSE) { +#ifdef DEBUG + DBGPRINTF((DBG_CONTEXT, + "FORWARDING_HANDLER::FORWARDING_HANDLER \n")); +#endif // DEBUG + InitializeSRWLock(&m_RequestLock); } @@ -63,6 +70,11 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( // // Destructor has started. // +#ifdef DEBUG + DBGPRINTF((DBG_CONTEXT, + "FORWARDING_HANDLER::~FORWARDING_HANDLER \n")); +#endif // DEBUG + m_Signature = FORWARDING_HANDLER_SIGNATURE_FREE; // @@ -2101,6 +2113,11 @@ None DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); } +#ifdef DEBUG + DBGPRINTF((DBG_CONTEXT, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x -- %d --%p\n", dwInternetStatus, m_fWebSocketUpgrade, m_pW3Context)); +#endif // DEBUG + fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); if (!fEndRequest) { @@ -2377,6 +2394,7 @@ Finished: if (m_pWebSocket != NULL) { m_pWebSocket->TerminateRequest(); + m_pWebSocket->Terminate(); m_pWebSocket = NULL; } diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 0d2346bbae..fe83e65a0d 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -443,7 +443,10 @@ SERVER_PROCESS::InitEnvironmentVariablesTable( if (dwResult == 0) { dwError = GetLastError(); - if (dwError != ERROR_ENVVAR_NOT_FOUND) + // 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; @@ -1421,23 +1424,21 @@ SERVER_PROCESS::CheckIfServerIsUp( hr = HRESULT_FROM_WIN32(WSAGetLastError()); goto Finished; } - - // - // Connected successfully, close socket. - // - iResult = closesocket(socketCheck); - if (iResult == SOCKET_ERROR) - { - hr = HRESULT_FROM_WIN32(WSAGetLastError()); - goto Finished; - } - - socketCheck = INVALID_SOCKET; *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); @@ -1650,6 +1651,12 @@ SERVER_PROCESS::IsDebuggerIsAttached( dwPid); BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent); + if (hProcess != NULL) + { + CloseHandle(hProcess); + hProcess = NULL; + } + if (!returnValue) { goto Finished; From d15129110ef4cfccaf2169f4381ab6fb7aec6517 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 24 Aug 2017 13:07:05 -0700 Subject: [PATCH 062/107] Ensure .NET Core 1.0.5 is installed --- build/repo.props | 3 ++- .../AspNetCoreModule.TestSites.Standard.csproj | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/repo.props b/build/repo.props index 1d5d44d0dd..f38e6b5141 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,5 +1,6 @@ + @@ -7,4 +8,4 @@ RunConfiguration.TargetPlatform=x64 - \ No newline at end of file + diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj index 17ff1b6b8d..784d5e618c 100644 --- a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -1,6 +1,7 @@ netcoreapp1.0 + 1.0.5 From 6b8449f4913c7b495c274f05e2450951a0f22896 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Wed, 30 Aug 2017 16:33:30 -0500 Subject: [PATCH 063/107] Sortable log files with stdoutLogFile (#131) Fixes #130 --- src/AspNetCore/Src/serverprocess.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index fe83e65a0d..3382000a55 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -1204,15 +1204,15 @@ SERVER_PROCESS::SetupStdHandles( } GetSystemTime(&systemTime); - hr = struLogFileName.SafeSnwprintf(L"%s_%d_%d%d%d%d%d%d.log", + hr = struLogFileName.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", struAbsLogFilePath.QueryStr(), - GetCurrentProcessId(), systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, - systemTime.wSecond ); + systemTime.wSecond, + GetCurrentProcessId() ); if (FAILED(hr)) { goto Finished; From 509ddc6ceda29f3318f3df89e5c6872e8e231959 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 11 Sep 2017 15:30:54 -0700 Subject: [PATCH 064/107] Adds ANCM in-process flag. (#144) --- src/AspNetCore/aspnetcore_schema.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AspNetCore/aspnetcore_schema.xml b/src/AspNetCore/aspnetcore_schema.xml index 30750a64c7..ca41d48691 100644 --- a/src/AspNetCore/aspnetcore_schema.xml +++ b/src/AspNetCore/aspnetcore_schema.xml @@ -25,6 +25,7 @@ + From a732b106f5ee4d17f74282ac28c50b0ef6fbd968 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 11 Sep 2017 18:44:15 -0700 Subject: [PATCH 065/107] Fix websocket close handshake issue and race condition when websocket client disconnect without close handshake (#151) --- src/AspNetCore/Inc/environmentvariablehash.h | 2 + src/AspNetCore/Inc/forwardinghandler.h | 1 + src/AspNetCore/Inc/serverprocess.h | 4 -- src/AspNetCore/Inc/websockethandler.h | 3 ++ src/AspNetCore/Src/aspnetcoreconfig.cxx | 1 + src/AspNetCore/Src/forwardinghandler.cxx | 30 ++++++++--- src/AspNetCore/Src/serverprocess.cxx | 15 ++++-- src/AspNetCore/Src/websockethandler.cxx | 52 ++++++++++++++------ 8 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/AspNetCore/Inc/environmentvariablehash.h b/src/AspNetCore/Inc/environmentvariablehash.h index 1c92cc161e..797c63c21c 100644 --- a/src/AspNetCore/Inc/environmentvariablehash.h +++ b/src/AspNetCore/Inc/environmentvariablehash.h @@ -136,6 +136,7 @@ public: PVOID pvData ) { + // best effort copy, ignore the failure ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); if (pNewEntry != NULL) { @@ -143,6 +144,7 @@ public: ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); DBG_ASSERT(pHash); pHash->InsertRecord(pNewEntry); + // Need to dereference as InsertRecord references it now pNewEntry->Dereference(); } } diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h index 26c8723f57..c66fa5c77f 100644 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -322,6 +322,7 @@ private: BOOL m_fWebSocketUpgrade; BOOL m_fFinishRequest; BOOL m_fClientDisconnected; + BOOL m_fHasError; DWORD m_msStartTime; DWORD m_BytesToReceive; diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index e83c5ecef0..16a7fd33e6 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -313,10 +313,6 @@ private: HANDLE m_hListeningProcessHandle; HANDLE m_hProcessWaitHandle; HANDLE m_hShutdownHandle; - // - // m_hChildProcessHandle is the handle to process created by - // m_hProcessHandle process if it does. - // // m_hChildProcessHandle is the handle to process created by // m_hProcessHandle process if it does. diff --git a/src/AspNetCore/Inc/websockethandler.h b/src/AspNetCore/Inc/websockethandler.h index c8abce55e3..845452760d 100644 --- a/src/AspNetCore/Inc/websockethandler.h +++ b/src/AspNetCore/Inc/websockethandler.h @@ -206,6 +206,9 @@ private: volatile BOOL _fIndicateCompletionToIis; + volatile + BOOL _fReceivedCloseMsg; + static LIST_ENTRY sm_RequestsListHead; diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index e9a7c54877..e919eb7d9b 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -376,6 +376,7 @@ ASPNETCORE_CONFIG::Populate( strExpandedEnvValue.Reset(); pEnvVar->Release(); pEnvVar = NULL; + pEntry->Dereference(); pEntry = NULL; } diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index afc59f72a0..665a82d8be 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -53,7 +53,8 @@ m_pAppOfflineHtm(NULL), m_fErrorHandled(FALSE), m_fWebSocketUpgrade(FALSE), m_fFinishRequest(FALSE), -m_fClientDisconnected(FALSE) +m_fClientDisconnected(FALSE), +m_fHasError(FALSE) { #ifdef DEBUG DBGPRINTF((DBG_CONTEXT, @@ -1171,6 +1172,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( FALSE ); // no need to check return hresult + m_fHasError = TRUE; goto Finished; } @@ -1368,6 +1370,7 @@ Failure: // Reset status for consistency. // m_RequestStatus = FORWARDER_DONE; + m_fHasError = TRUE; pResponse->DisableKernelCache(); pResponse->GetRawHttpResponse()->EntityChunkCount = 0; @@ -1592,7 +1595,10 @@ REQUEST_NOTIFICATION_STATUS { if (m_RequestStatus == FORWARDER_DONE && m_fFinishRequest) { - retVal = RQ_NOTIFICATION_FINISH_REQUEST; + if (m_fHasError) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + } goto Finished; } @@ -1706,7 +1712,7 @@ Failure: // Reset status for consistency. // m_RequestStatus = FORWARDER_DONE; - + m_fHasError = TRUE; // // Do the right thing based on where the error originated from. // @@ -1779,8 +1785,17 @@ Failure: // // Finish the request on failure. + // Let IIS pipeline continue only after receiving handle close callback + // from WinHttp. This ensures no more callback from WinHttp // - retVal = RQ_NOTIFICATION_FINISH_REQUEST; + if (m_hRequest != NULL) + { + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } + } + retVal = RQ_NOTIFICATION_PENDING; Finished: @@ -2249,7 +2264,6 @@ None fDerefForwardingHandler = m_fClientDisconnected && !m_fWebSocketUpgrade; } m_hRequest = NULL; - m_pWebSocket = NULL; fAnotherCompletionExpected = FALSE; break; case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: @@ -2391,11 +2405,13 @@ Finished: m_hRequest = NULL; } } + + // + // If the request is a websocket request, initiate cleanup. + // if (m_pWebSocket != NULL) { m_pWebSocket->TerminateRequest(); - m_pWebSocket->Terminate(); - m_pWebSocket = NULL; } if(fEndRequest) diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 3382000a55..9487d2a5ca 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -2280,13 +2280,18 @@ SERVER_PROCESS::SendShutDownSignalInternal( TerminateBackendProcess(); } FreeConsole(); - } - if (fFreeConsole) + 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 { - // 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); + // terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); } } diff --git a/src/AspNetCore/Src/websockethandler.cxx b/src/AspNetCore/Src/websockethandler.cxx index 05147f64ba..1958c6a9c4 100644 --- a/src/AspNetCore/Src/websockethandler.cxx +++ b/src/AspNetCore/Src/websockethandler.cxx @@ -35,13 +35,15 @@ LIST_ENTRY WEBSOCKET_HANDLER::sm_RequestsListHead; TRACE_LOG * WEBSOCKET_HANDLER::sm_pTraceLog; -WEBSOCKET_HANDLER::WEBSOCKET_HANDLER(): - _pHttpContext ( NULL ), - _pWebSocketContext ( NULL ), - _pHandler ( NULL ), - _dwOutstandingIo ( 0 ), - _fCleanupInProgress ( FALSE ), - _fIndicateCompletionToIis ( FALSE ) +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"); @@ -58,16 +60,20 @@ WEBSOCKET_HANDLER::Terminate( DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::Terminate"); RemoveRequest(); + _fCleanupInProgress = TRUE; - _pWebSocketContext = NULL; - _pHttpContext = NULL; - + if (_pHttpContext != NULL) + { + _pHttpContext->CancelIo(); + _pHttpContext = NULL; + } if (_hWebSocketRequest) { WinHttpCloseHandle(_hWebSocketRequest); _hWebSocketRequest = NULL; } + _pWebSocketContext = NULL; DeleteCriticalSection(&_RequestLock); delete this; @@ -216,6 +222,9 @@ WEBSOCKET_HANDLER::IndicateCompletionToIIS( // 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 @@ -498,6 +507,12 @@ Routine Description: } 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. @@ -947,14 +962,19 @@ Routine Description: } // - // Write Completed, initiate next read from backend server. + // Only call read if no close hand shake was received from backend // - - hr = DoWinHttpWebSocketReceive(); - if (FAILED (hr)) + if (!_fReceivedCloseMsg) { - cleanupReason = ServerDisconnect; - goto Finished; + // + // Write Completed, initiate next read from backend server. + // + hr = DoWinHttpWebSocketReceive(); + if (FAILED(hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } } Finished: From c5f59e06c361539b65791556691271688457f592 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 12 Sep 2017 11:39:55 -0700 Subject: [PATCH 066/107] Added new test scenarios for websocket (#153) --- AspNetCoreModule.sln | 17 +- .../AspNetCoreModule.Test.csproj | 14 +- .../Framework/IISConfigUtility.cs | 18 + .../Framework/InitializeTestMachine.cs | 2 +- .../Framework/TestUtility.cs | 44 +- .../Framework/TestWebSite.cs | 199 +++++- test/AspNetCoreModule.Test/FunctionalTest.cs | 27 + .../FunctionalTestHelper.cs | 649 ++++++++++++++++-- .../WebSocketClientHelper/Frame.cs | 16 +- .../WebSocketClientHelper.cs | 122 +++- .../WebSocketClientHelper/WebSocketConnect.cs | 4 +- .../WebSocketClientHelper/WebSocketState.cs | 1 + .../Program.cs | 12 +- .../Startup.cs | 23 +- test/WebRoot/WebSocket/chatplus.html | 366 ++++++++++ .../WebRoot/WebSocket/images/select_arrow.gif | Bin 0 -> 636 bytes .../WebSocket/images/select_arrow_down.gif | Bin 0 -> 650 bytes .../WebSocket/images/select_arrow_over.gif | Bin 0 -> 639 bytes test/WebSocketClientEXE/App.config | 6 + test/WebSocketClientEXE/Program.cs | 53 ++ .../Properties/AssemblyInfo.cs | 36 + test/WebSocketClientEXE/TestUtility.cs | 15 + .../WebSocketClientEXE.csproj | 77 +++ 23 files changed, 1552 insertions(+), 149 deletions(-) create mode 100644 test/WebRoot/WebSocket/chatplus.html create mode 100644 test/WebRoot/WebSocket/images/select_arrow.gif create mode 100644 test/WebRoot/WebSocket/images/select_arrow_down.gif create mode 100644 test/WebRoot/WebSocket/images/select_arrow_over.gif create mode 100644 test/WebSocketClientEXE/App.config create mode 100644 test/WebSocketClientEXE/Program.cs create mode 100644 test/WebSocketClientEXE/Properties/AssemblyInfo.cs create mode 100644 test/WebSocketClientEXE/TestUtility.cs create mode 100644 test/WebSocketClientEXE/WebSocketClientEXE.csproj diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index d18cf23d4b..54c32b50f6 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26224.1 +VisualStudioVersion = 15.0.26815.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" ProjectSection(ProjectDependencies) = postProject @@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution NuGet.Config = NuGet.Config EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketClientEXE", "test\WebSocketClientEXE\WebSocketClientEXE.csproj", "{4062EA94-75F5-4691-86DC-C8594BA896DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,6 +79,18 @@ Global {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Win32.Build.0 = Release|Any CPU {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.ActiveCfg = Release|Any CPU {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.Build.0 = Release|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Win32.ActiveCfg = Debug|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Win32.Build.0 = Debug|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|x64.Build.0 = Debug|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Any CPU.Build.0 = Release|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Win32.ActiveCfg = Release|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Win32.Build.0 = Release|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|x64.ActiveCfg = Release|Any CPU + {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -86,5 +100,6 @@ Global {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} {4DDA7560-AA29-4161-A5EA-A7E8F3997321} = {02F461DC-5166-4E88-AAD5-CF110016A647} {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {02F461DC-5166-4E88-AAD5-CF110016A647} + {4062EA94-75F5-4691-86DC-C8594BA896DE} = {02F461DC-5166-4E88-AAD5-CF110016A647} EndGlobalSection EndGlobal diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj index 8ed0ec821d..e366f51373 100644 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj @@ -5,6 +5,7 @@ net46 true true + AspNetCoreModule.Test @@ -21,8 +22,8 @@ - - + + @@ -44,8 +45,13 @@ - - + + + + + + + diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index 284ef9148a..fc4c48ab3b 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -79,6 +79,24 @@ namespace AspNetCoreModule.Test.Framework 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"; diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index f64a1ed317..09f32d0691 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -14,7 +14,7 @@ namespace AspNetCoreModule.Test.Framework public const string ANCMTestFlagsDefaultContext = "AdminAnd64Bit"; public const string ANCMTestFlagsTestSkipContext = "SkipTest"; public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivate"; - public const string ANCMTestFlagsUseIISExpressContext = "UseIISExpress"; + private const string ANCMTestFlagsUseIISExpressContext = "UseIISExpress"; private static bool? _usePrivateAspNetCoreFile = null; public static bool? UsePrivateAspNetCoreFile diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index 67c6947e0f..0241427759 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -791,7 +791,47 @@ namespace AspNetCoreModule.Test.Framework 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; @@ -809,7 +849,7 @@ namespace AspNetCoreModule.Test.Framework } p.StartInfo.UseShellExecute = false; - p.StartInfo.CreateNoWindow = true; + p.StartInfo.CreateNoWindow = true; p.Start(); pid = p.Id; string standardOutput = string.Empty; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index 851ee2491a..e03e438267 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -99,12 +99,43 @@ namespace AspNetCoreModule.Test.Framework } } + 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 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) + public TestWebSite(IISConfigUtility.AppPoolBitness appPoolBitness, string loggerPrefix = "ANCMTest", bool startIISExpress = true, bool copyAllPublishedFiles = false, bool attachAppVerifier = false) { _appPoolBitness = appPoolBitness; @@ -165,9 +196,9 @@ namespace AspNetCoreModule.Test.Framework } // - // Currently we use only DotnetCore v1.1 + // Currently we use DotnetCore v1.0 // - string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp1.1", "publish"); + string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp1.0", "publish"); string publishPathOutput = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", "publishPathOutput"); // @@ -177,7 +208,12 @@ namespace AspNetCoreModule.Test.Framework { string argumentForDotNet = "publish " + srcPath; 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")); @@ -283,19 +319,27 @@ namespace AspNetCoreModule.Test.Framework if (startIISExpress) { - 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(string verificationCommand = null) + public void StartIISExpress() { if (IisServerType == ServerType.IIS) { return; } + // reset workerProcessID + this.WorkerProcessID = 0; + string cmdline; string argument = "/siteid:" + _siteId + " /config:" + IisExpressConfigPath; @@ -309,41 +353,126 @@ namespace AspNetCoreModule.Test.Framework } TestUtility.LogInformation("TestWebSite::TestWebSite() Start IISExpress: " + cmdline + " " + argument); _iisExpressPidBackup = TestUtility.RunCommand(cmdline, argument, false, false); - - bool isIISExpressReady = false; - int timeout = 3; - for (int i = 0; i < timeout * 5; i++) + } + + public void AttachAppverifier() + { + string cmdline; + string processName = "iisexpress.exe"; + if (IisServerType == ServerType.IIS) { - string statusCode = string.Empty; - try - { - if (verificationCommand == null) - { - verificationCommand = "( invoke-webrequest http://localhost:" + TcpPort + " ).StatusCode"; - } - statusCode = TestUtility.RunPowershellScript(verificationCommand); - } - catch - { - statusCode = "ExceptionError"; - } - if ("200" == statusCode) - { - isIISExpressReady = true; - break; - } - else - { - System.Threading.Thread.Sleep(200); - } + processName = "w3wp.exe"; } - if (isIISExpressReady) + 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) { - TestUtility.LogInformation("IISExpress is ready to use"); + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new System.ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); + } } else { - throw new ApplicationException("IISExpress is not responding within " + timeout + " seconds"); + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new System.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 System.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 System.ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); + } + } + else + { + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "windbg.exe"); + if (!File.Exists(debuggerCmdline)) + { + throw new System.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 System.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 System.ApplicationException("Not found :" + cmdline); + } + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "wow64", "windbg.exe"); + if (!File.Exists(debuggerCmdline)) + { + throw new System.ApplicationException("Not found :" + debuggerCmdline); + } + } + else + { + cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "appverif.exe"); + if (!File.Exists(cmdline)) + { + throw new System.ApplicationException("Not found :" + cmdline); + } + debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "windbg.exe"); + if (!File.Exists(debuggerCmdline)) + { + throw new System.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 index 93d0df1f1e..adc46727bf 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -308,6 +308,33 @@ namespace AspNetCoreModule.Test return DoClientCertificateMappingTest(appPoolBitness, useHTTPSMiddleWare); } + [ConditionalTheory] + [ANCMTestSkipCondition("%ANCMTestFlags%")] + [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] + [ANCMTestSkipCondition("%ANCMTestFlags%")] + [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 ////////////////////////////////////////////////////////// diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index f20551586e..075774a83b 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -174,6 +174,8 @@ namespace AspNetCoreModule.Test { 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"); @@ -204,6 +206,11 @@ namespace AspNetCoreModule.Test 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); } } } @@ -213,6 +220,8 @@ namespace AspNetCoreModule.Test 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++) { @@ -231,6 +240,11 @@ namespace AspNetCoreModule.Test 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 @@ -320,9 +334,9 @@ namespace AspNetCoreModule.Test Assert.True(totalNumber == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK))); iisConfig.SetANCMConfig( - testSite.SiteName, - testSite.AspNetCoreApp.Name, - "environmentVariable", + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", new string[] { "ANCMTestFoo", "foo" } ); @@ -366,7 +380,7 @@ namespace AspNetCoreModule.Test // 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 @@ -377,7 +391,7 @@ namespace AspNetCoreModule.Test } } Thread.Sleep(500); - + // check JitDebugger before continuing TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); totalResult = (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK)); @@ -387,17 +401,17 @@ namespace AspNetCoreModule.Test // Verify other common environment variables string temp = (await GetResponse(testSite.AspNetCoreApp.GetUri("DumpEnvironmentVariables"), HttpStatusCode.OK)); - Assert.True(temp.Contains("ASPNETCORE_PORT")); - Assert.True(temp.Contains("ASPNETCORE_APPL_PATH")); - Assert.True(temp.Contains("ASPNETCORE_IIS_HTTPAUTH")); - Assert.True(temp.Contains("ASPNETCORE_TOKEN")); - Assert.True(temp.Contains("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES")); + 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.True(temp.Contains("PROCESSOR_ARCHITECTURE")); - Assert.True(temp.Contains("USERNAME")); - Assert.True(temp.Contains("USERDOMAIN")); - Assert.True(temp.Contains("USERPROFILE")); + Assert.Contains("PROCESSOR_ARCHITECTURE", temp); + Assert.Contains("USERNAME", temp); + Assert.Contains("USERDOMAIN", temp); + Assert.Contains("USERPROFILE", temp); } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -410,8 +424,10 @@ namespace AspNetCoreModule.Test { string backendProcessId_old = null; string fileContent = "BackEndAppOffline"; - testSite.AspNetCoreApp.CreateFile(new string[] { fileContent }, "App_Offline.Htm"); + 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 @@ -423,6 +439,11 @@ namespace AspNetCoreModule.Test // verify 503 await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), fileContent + "\r\n", 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 GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); @@ -444,6 +465,8 @@ namespace AspNetCoreModule.Test { 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++) @@ -458,6 +481,11 @@ namespace AspNetCoreModule.Test string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; await VerifyResponseBody(testSite.RootAppContext.GetUri(urlForUrlRewrite), fileContent + "\r\n", 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 GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); @@ -528,7 +556,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); responseBody = await GetResponse(testSite.AspNetCoreApp.GetUri(), HttpStatusCode.BadGateway); - Assert.True(responseBody.Contains("808681")); + Assert.Contains("808681", responseBody); // verify event error log Assert.True(TestUtility.RetryHelper((arg1, arg2, arg3) => VerifyApplicationEventLog(arg1, arg2, arg3), errorEventId, startTime, errorMessageContainThis)); @@ -574,7 +602,7 @@ namespace AspNetCoreModule.Test 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); @@ -582,7 +610,7 @@ namespace AspNetCoreModule.Test backendProcess.Kill(); Thread.Sleep(3000); } - Assert.True(rapidFailsTriggered, "Verify 503 error"); + Assert.True(rapidFailsTriggered, "Verify 502 Bad Gateway error"); // verify event error log int errorEventId = 1003; @@ -647,7 +675,7 @@ namespace AspNetCoreModule.Test processIDs.Add(id); } } - Assert.Equal(1, processIDs.Count); + Assert.Single(processIDs); } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -662,9 +690,9 @@ namespace AspNetCoreModule.Test { int startupDelay = 3; //3 seconds iisConfig.SetANCMConfig( - testSite.SiteName, - testSite.AspNetCoreApp.Name, - "environmentVariable", + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", new string[] { "ANCMTestStartUpDelay", (startupDelay * 1000).ToString() } ); @@ -676,7 +704,7 @@ namespace AspNetCoreModule.Test { await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep3000"), HttpStatusCode.BadGateway); } - else + else { await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep3000"), "Running", HttpStatusCode.OK); } @@ -691,12 +719,12 @@ namespace AspNetCoreModule.Test { using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { - iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse(requestTimeout)); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "requestTimeout", TimeSpan.Parse(requestTimeout)); Thread.Sleep(500); if (requestTimeout.ToString() == "00:02:00") { - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep65000"), "Running", HttpStatusCode.OK, timeout:70); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep65000"), "Running", HttpStatusCode.OK, timeout: 70); } else if (requestTimeout.ToString() == "00:01:00") { @@ -732,7 +760,7 @@ namespace AspNetCoreModule.Test string response = await GetResponse(testSite.AspNetCoreApp.GetUri(""), HttpStatusCode.OK); Assert.True(response == "Running"); - + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -749,7 +777,7 @@ namespace AspNetCoreModule.Test await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); // if expectedClosing time is less than the shutdownDelay time, gracefulshutdown is supposed to fail and failure event is expected - if (expectedClosingTime*1000 + 1000 == shutdownDelayTime) + 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)); @@ -814,7 +842,7 @@ namespace AspNetCoreModule.Test public static async Task DoProcessPathAndArgumentsTest(IISConfigUtility.AppPoolBitness appPoolBitness, string processPath, string argumentsPrefix) { - using (var testSite = new TestWebSite(appPoolBitness, "DoProcessPathAndArgumentsTest", copyAllPublishedFiles:true)) + using (var testSite = new TestWebSite(appPoolBitness, "DoProcessPathAndArgumentsTest", copyAllPublishedFiles: true)) { using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { @@ -848,7 +876,7 @@ namespace AspNetCoreModule.Test testSite.AspNetCoreApp.RestoreFile("web.config"); } } - + public static async Task DoForwardWindowsAuthTokenTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool enabledForwardWindowsAuthToken) { using (var testSite = new TestWebSite(appPoolBitness, "DoForwardWindowsAuthTokenTest")) @@ -858,9 +886,9 @@ namespace AspNetCoreModule.Test string result = string.Empty; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); - Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); + Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); - iisConfig.EnableIISAuthentication(testSite.SiteName, windows:true, basic:false, anonymous:false); + iisConfig.EnableIISAuthentication(testSite.SiteName, windows: true, basic: false, anonymous: false); Thread.Sleep(500); // check JitDebugger before continuing @@ -871,7 +899,7 @@ namespace AspNetCoreModule.Test if (enabledForwardWindowsAuthToken) { string expectedHeaderName = "MS-ASPNETCORE-WINAUTHTOKEN"; - Assert.True(requestHeaders.ToUpper().Contains(expectedHeaderName)); + Assert.Contains(expectedHeaderName, requestHeaders.ToUpper()); result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); bool compare = false; @@ -892,10 +920,10 @@ namespace AspNetCoreModule.Test } else { - Assert.False(requestHeaders.ToUpper().Contains("MS-ASPNETCORE-WINAUTHTOKEN")); + Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); - Assert.True(result.Contains("ImpersonateMiddleware-UserName = NoAuthentication")); + Assert.Contains("ImpersonateMiddleware-UserName = NoAuthentication", result); } } @@ -915,13 +943,13 @@ namespace AspNetCoreModule.Test using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { - + // allocating 1024,000 KB await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("MemoryLeak1024000"), HttpStatusCode.OK); - + // get backend process id string pocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); - + // get process id of IIS worker process (w3wp.exe) string userName = testSite.SiteName; int processIdOfWorkerProcess = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); @@ -948,7 +976,7 @@ namespace AspNetCoreModule.Test 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); @@ -1029,12 +1057,12 @@ namespace AspNetCoreModule.Test { startupClass = "StartupNoCompressionCaching"; } - + // set startup class iisConfig.SetANCMConfig( - testSite.SiteName, - testSite.AspNetCoreApp.Name, - "environmentVariable", + testSite.SiteName, + testSite.AspNetCoreApp.Name, + "environmentVariable", new string[] { "ANCMTestStartupClassName", startupClass } ); @@ -1091,7 +1119,7 @@ namespace AspNetCoreModule.Test using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string startupClass = "StartupCompressionCaching"; - + // set startup class iisConfig.SetANCMConfig( testSite.SiteName, @@ -1103,7 +1131,7 @@ namespace AspNetCoreModule.Test // 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"); @@ -1149,7 +1177,7 @@ namespace AspNetCoreModule.Test public static async Task DoSendHTTPSRequestTest(IISConfigUtility.AppPoolBitness appPoolBitness) { - using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest", startIISExpress:false)) + using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest", startIISExpress: false)) { using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { @@ -1161,12 +1189,12 @@ namespace AspNetCoreModule.Test // 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"); + 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); @@ -1228,13 +1256,13 @@ namespace AspNetCoreModule.Test string requestHeaders = string.Empty; requestHeaders = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); requestHeaders = requestHeaders.Replace(" ", ""); - Assert.False(requestHeaders.ToUpper().Contains(requestHeader.ToLower()+":")); + Assert.DoesNotContain(requestHeader.ToLower() + ":", requestHeaders.ToUpper()); // Verify https request Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); requestHeader = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); requestHeaders = requestHeaders.Replace(" ", ""); - Assert.False(requestHeaders.ToUpper().Contains(requestHeader.ToLower() + ":")); + Assert.DoesNotContain(requestHeader.ToLower() + ":", requestHeaders.ToUpper()); // Remove the SSL Certificate mapping iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); @@ -1273,11 +1301,11 @@ namespace AspNetCoreModule.Test // 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; + 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); @@ -1337,7 +1365,8 @@ namespace AspNetCoreModule.Test // 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("( invoke-webrequest " + rootHttpsUri.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").StatusCode"); + 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"); @@ -1348,21 +1377,21 @@ namespace AspNetCoreModule.Test Uri targetHttpsUriForDumpRequestHeaders = testSite.AspNetCoreApp.GetUri("DumpRequestHeaders", sslPort, protocol: "https"); string outputRawContent = TestUtility.RunPowershellScript("( invoke-webrequest " + targetHttpsUriForDumpRequestHeaders.OriginalString + " -CertificateThumbprint " + thumbPrintForClientAuthentication + ").RawContent.ToString()"); string expectedHeaderName = "MS-ASPNETCORE-CLIENTCERT"; - Assert.True(outputRawContent.Contains(expectedHeaderName)); + Assert.Contains(expectedHeaderName, 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.True(outputRawContent.Contains(publicKey)); + Assert.Contains(publicKey, outputRawContent); // Verify non-https request returns 403.4 error string result = string.Empty; result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.Forbidden); - Assert.True(result.Contains("403.4")); + Assert.Contains("403.4", result); // Verify https request without using client certificate returns 403.7 result = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.Forbidden); - Assert.True(result.Contains("403.7")); + Assert.Contains("403.7", result); // Clean up user temp = TestUtility.RunPowershellScript("net localgroup IIS_IUSRS /Delete " + userName); @@ -1376,7 +1405,7 @@ namespace AspNetCoreModule.Test iisConfig.DeleteCertificate(thumbPrintForWebServer, @"Cert:\LocalMachine\My"); if (useHTTPSMiddleWare) { - iisConfig.DeleteCertificate(thumbPrintForKestrel, @"Cert:\LocalMachine\My"); + iisConfig.DeleteCertificate(thumbPrintForKestrel, @"Cert:\LocalMachine\My"); } iisConfig.DeleteCertificate(thumbPrintForClientAuthentication, @"Cert:\CurrentUser\My"); } @@ -1388,25 +1417,32 @@ namespace AspNetCoreModule.Test { 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 VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); // Get Process ID - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); - + string backendProcessId_old = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + // Verify WebSocket without setting subprotocol await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server // Verify WebSocket subprotocol await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // 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.True(frameReturned.Content.Contains("Connection: Upgrade")); - Assert.True(frameReturned.Content.Contains("HTTP/1.1 101 Switching Protocols")); + Assert.Contains("Connection: Upgrade", frameReturned.Content); + Assert.Contains("HTTP/1.1 101 Switching Protocols", frameReturned.Content); Thread.Sleep(500); VerifySendingWebSocketData(websocketClient, testData); @@ -1418,8 +1454,489 @@ namespace AspNetCoreModule.Test Assert.True(frameReturned.FrameType == FrameType.Close, "Closing Handshake"); } - // send a simple request again and verify the response body + // send a simple request and verify the response body await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + Thread.Sleep(500); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + } + } + + // send a simple request and verify the response body + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + Thread.Sleep(500); + backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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.Equal("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.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + } + } + */ + + // send a simple request and verify the response body + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + } + } + + 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"); + + using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) + { + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + Assert.DoesNotContain("Connection: Upgrade", frameReturned.Content); + + //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 VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + // 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 it + testSite.StartIISExpress(); + + if (verifyTimeout) + { + Thread.Sleep(500); + + // initial request which requires more than startup timeout should fails + await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep5000"), HttpStatusCode.BadGateway, timeout: 10); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep5000"), "Running", HttpStatusCode.OK, timeout: 10); + + // request which requires more than request timeout should fails + await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, timeout: 70); + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep50000"), "Running", HttpStatusCode.OK, 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 VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + // Get Process ID + string backendProcessId_old = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + + // Verify WebSocket without setting subprotocol + await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server + + // Verify WebSocket subprotocol + await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // 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 VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + Thread.Sleep(500); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + } + + // send a simple request and verify the response body + await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + Thread.Sleep(500); + backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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 VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + var result = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); + Assert.True(result.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 VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "test" + "\r\n", 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 == false) + { + TestUtility.RunPowershellScript("stop-process -Name windbg -Force -Confirm:$false 2> $null"); } } @@ -1561,11 +2078,11 @@ namespace AspNetCoreModule.Test Assert.Null(response.Headers.ConnectionClose); Assert.Null(GetContentLength(response)); } - catch (XunitException) + catch (XunitException ex) { TestUtility.LogInformation(response.ToString()); TestUtility.LogInformation(responseText); - throw; + throw ex; } } @@ -1786,7 +2303,7 @@ namespace AspNetCoreModule.Test } foreach (string item in expectedStringsInResponseBody) { - Assert.True(responseText.Contains(item)); + Assert.Contains(item, responseText); } } Assert.Equal(expectedResponseStatus, response.StatusCode); diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs index d6c987fa4b..3f0fa9b755 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs @@ -1,12 +1,14 @@ // 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; // 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) { @@ -18,6 +20,18 @@ namespace AspNetCoreModule.Test.WebSocketClient 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; } diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs index 401818ce99..ebd32f5ff0 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Text; using System.Collections; using System.Threading; -using Xunit; namespace AspNetCoreModule.Test.WebSocketClient { @@ -33,6 +32,34 @@ namespace AspNetCoreModule.Test.WebSocketClient } } + 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) { Address = address; @@ -44,7 +71,11 @@ namespace AspNetCoreModule.Test.WebSocketClient InitiateWithAlwaysReading(); } SendWebSocketRequest(WebSocketClientUtility.WebSocketVersion); - Thread.Sleep(3000); + + if (!WaitForWebSocketState(WebSocketState.ConnectionOpen)) + { + throw new Exception("Failed to open a connection"); + } Frame openingFrame = null; @@ -53,23 +84,21 @@ namespace AspNetCoreModule.Test.WebSocketClient else openingFrame = Connection.DataReceived[0]; - Thread.Sleep(1000); - IsOpened = true; return openingFrame; } - + public Frame Close() { CloseConnection(); - Thread.Sleep(1000); - + Frame closeFrame = null; if (!IsAlwaysReading) closeFrame = ReadData(); else closeFrame = Connection.DataReceived[Connection.DataReceived.Count - 1]; + IsOpened = false; return closeFrame; } @@ -137,7 +166,7 @@ namespace AspNetCoreModule.Test.WebSocketClient Encoding.UTF8.GetString(outputData, 0, outputData.Length)); } } - + public void ReadDataCallback(IAsyncResult result) { WebSocketConnect client = (WebSocketConnect) result.AsyncState; @@ -190,28 +219,17 @@ namespace AspNetCoreModule.Test.WebSocketClient // Create frame with the tempBuffer Frame frame = new Frame(tempBuffer); - int nextFrameIndex = 0; + ProcessReceivedData(frame); + int nextFrameIndex = frame.IndexOfNextFrame; + while (nextFrameIndex != -1) { - TestUtility.LogInformation("ReadDataCallback: Client {0:D3}: Read Type {1} : {2} ", Connection.Id, frame.FrameType, bytesReadIntotal); + tempBuffer = tempBuffer.SubArray(frame.IndexOfNextFrame, tempBuffer.Length - frame.IndexOfNextFrame); + frame = new Frame(tempBuffer); ProcessReceivedData(frame); - - // Send Pong if the frame was Ping - if (frame.FrameType == FrameType.Ping) - SendPong(frame); - nextFrameIndex = frame.IndexOfNextFrame; - if (nextFrameIndex != -1) - { - tempBuffer = tempBuffer.SubArray(frame.IndexOfNextFrame, tempBuffer.Length - frame.IndexOfNextFrame); - frame = new Frame(tempBuffer); - } } - // Send Pong if the frame was Ping - if (frame.FrameType == FrameType.Ping) - SendPong(frame); - if (client.IsDisposed) return; @@ -271,6 +289,10 @@ namespace AspNetCoreModule.Test.WebSocketClient { Send(Frames.PONG); } + public void SendClose() + { + Send(Frames.CLOSE_FRAME); + } public void SendPong(Frame receivedPing) { @@ -291,8 +313,13 @@ namespace AspNetCoreModule.Test.WebSocketClient public void CloseConnection() { - Connection.Done = true; + 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) @@ -336,12 +363,45 @@ namespace AspNetCoreModule.Test.WebSocketClient private void ProcessReceivedData(Frame frame) { - if (frame.Content.Contains("Connection: Upgrade") - && frame.Content.Contains("Upgrade: Websocket") - && frame.Content.Contains("HTTP/1.1 101 Switching Protocols")) - WebSocketState = WebSocketState.ConnectionOpen; - if (frame.FrameType == FrameType.Close) - WebSocketState = WebSocketState.ConnectionClosed; + 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); } diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs index 69cd3fd5a6..284b65e32a 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketConnect.cs @@ -35,9 +35,7 @@ namespace AspNetCoreModule.Test.WebSocketClient public byte[] InputData { get; set; } public bool IsDisposed { get; set; } - public bool Done { get; set; } - - + public int Id { get; set; } public MyTcpClient TcpClient { get; set; } diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs index b9a4d8c5db..0c25d95f7b 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketState.cs @@ -7,6 +7,7 @@ namespace AspNetCoreModule.Test.WebSocketClient { NonWebSocket, ConnectionOpen, + ClosingFromClientStarted, ConnectionClosed } } diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index 35e934da14..ca854a8e2b 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -15,6 +15,7 @@ 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; @@ -153,16 +154,21 @@ namespace AspnetCoreModule.TestSites.Standard } AappLifetime.ApplicationStarted.Register( - () => Thread.Sleep(1000) + () => { + Thread.Sleep(1000); + } ); AappLifetime.ApplicationStopping.Register( - () => Thread.Sleep(Startup.SleeptimeWhileClosing / 2) + () => { + 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 { diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index 4a69fc9fe9..18981450c2 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -32,17 +32,36 @@ namespace AspnetCoreModule.TestSites.Standard // the below line is not required at present, however keeping in case the default value is changed later. options.ForwardWindowsAuthentication = true; }); - } + } 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) { - await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + 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); } 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/images/select_arrow.gif b/test/WebRoot/WebSocket/images/select_arrow.gif new file mode 100644 index 0000000000000000000000000000000000000000..781c8bc4333fa6ab61b68d7155d224d3bace2155 GIT binary patch literal 636 zcmd6kO>fd*0DxaUq_CBYFwJlqfyq?1sSDG?mK_{n>eP7k>`vBwd%8D^xk*i8s9Eh@Pq zH@+}!$MLD55r@OvhYdKV@6QjtQK@HU`m~sB^tDk%jB7O!4K}!i2ZG^f z%sO>%#6TM~CX-tM-sBm4?8dY4iN^ZRr{=S-9&WgkOLj2-7gZe*Q~&?~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0be8124c2004e7b8b4ae267af4feb9a6a9a3fe44 GIT binary patch literal 650 zcmZ?wbhEHb^?u*^w_6UqTe;^`Uf;*c=^xwXewelX zSsh!H3d0A9tSqT;Fx8b<)l1j$6AA-D#NiamtGKm+rmZe(YoEjF0QL z-7ast$v|JA_>%=}p$>=y#R&uZ^M+tmWkoqTu|Q*K>42c9NL4qr2oVuYUptmr9wB_n zYJOZ67Q9Xp%R|}ibQMjlxp;ZSZP>ONakC4oUB7YjR<`Xs<=nZr?Cr%pC3(*9a0`fu zSjsZ6UXzky;JAI^62txL_iu3s7;DO2dGN?VNl90l(bksr(R~9M8D@Vg#=n0baSHj& z_;7&DzMhedfkR`$0$w#yEtU=q2DS-2k`^MBiVxWs85LM%IE)rNY37i3sNm6@w8V3Y zLWqFHh6WZ+7A3z8363coTvA5j1`!({9cdO93|O?`QPRn&5`2rSIusXbaY*S+VPRsh F1^_uT4paaD literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0620571c8179977789c9b41fb9055fcdb34f4f3c GIT binary patch literal 639 zcmZ?wbhEHbV|w|}p^{&V5!pHq+i zo_qE8z>`16UjM)G_Rq^-|F69KGkg7m>1!TLT>bFT=f9U;{X6vN&(6EQ=WKd<|I@$6 zU;g*5e0cip|95}>KmGQ9>e{DEcRs)H;!oGI#}igPnX&HSkr)4NfBZjf&HcTP|K54? zw`cji8?XLsIsSUVmZy`}K6?1+-_=)t?|=9+echvDkN$r9|NqjPe}`WEU%l_;-Uq*X zS3WuM`1g#BPxhUD_UYgM$;%Z*_?j61R z=g|3AJ8%6yeD?YI+i&J>2Ksd{0>z&!U}x!oNKl+Gu-|F$(J@!h)s?U|l9zW;R`S$w zGLhxymy?oIQ{xk06EZik<(Hbl#Hg(;z{#efFhzuEqb4WoR#rB_b^IGPZP~WNS58in z{U|#t7Z>wMK?xCkCWeio%*+hNSI?ejxWjbi4#Q0&CPTvqq7PVZsK_6C@qmHl6N`#6 z3#e0>Fs0p1Mz$ud_qGTJ`ul?o4Mo~ H85pbqDA^8^ literal 0 HcmV?d00001 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..67a5fa5efe --- /dev/null +++ b/test/WebSocketClientEXE/WebSocketClientEXE.csproj @@ -0,0 +1,77 @@ + + + + + Debug + AnyCPU + {4062EA94-75F5-4691-86DC-C8594BA896DE} + Exe + WebSocketClientEXE + WebSocketClientEXE + v4.6 + 512 + true + + + 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 From 0120dae36bdc59bc3badaa87ca70a740a11bff02 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Wed, 13 Sep 2017 14:32:53 -0700 Subject: [PATCH 067/107] Jhkim/updatetest (#155) * Added new test scenarios for websocket * Fixed test issues * Fixed test issues --- .../FunctionalTestHelper.cs | 10 ++++---- .../WebSocketClientHelper/Frame.cs | 5 ++-- .../WebSocketClientHelper.cs | 25 +++++++++++++++---- ...AspNetCoreModule.TestSites.Standard.csproj | 2 +- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 075774a83b..dcbd06adc3 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -1484,7 +1484,7 @@ namespace AspNetCoreModule.Test int lastIndex = websocketClient.Connection.DataReceived.Count - 1; // Verify text data is matched to the string sent by server - Assert.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); } } @@ -1527,7 +1527,7 @@ namespace AspNetCoreModule.Test int lastIndex = websocketClient.Connection.DataReceived.Count - 1; // Verify text data is matched to the string sent by server - Assert.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); // Verify the application file can be removed under app_offline mode testSite.AspNetCoreApp.BackupFile(appDllFileName); @@ -1576,7 +1576,7 @@ namespace AspNetCoreModule.Test int lastIndex = websocketClient.Connection.DataReceived.Count - 1; // Verify text data is matched to the string sent by server - Assert.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); } } */ @@ -1601,7 +1601,7 @@ namespace AspNetCoreModule.Test using (WebSocketClientHelper websocketClient = new WebSocketClientHelper()) { - var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true); + var frameReturned = websocketClient.Connect(testSite.AspNetCoreApp.GetUri("websocket"), true, true, waitForConnectionOpen:false); Assert.DoesNotContain("Connection: Upgrade", frameReturned.Content); //BugBug: Currently we returns 101 here. @@ -1795,7 +1795,7 @@ namespace AspNetCoreModule.Test int lastIndex = websocketClient.Connection.DataReceived.Count - 1; // Verify text data is matched to the string sent by server - Assert.Equal("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); + Assert.Contains("ClosingFromServer", websocketClient.Connection.DataReceived[lastIndex].TextData); } // send a simple request and verify the response body diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs index 3f0fa9b755..5e7d66a9d5 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/Frame.cs @@ -20,8 +20,9 @@ namespace AspNetCoreModule.Test.WebSocketClient public FrameType FrameType { get; set; } public byte[] Data { get; private set; } - - public string TextData { + + public string TextData + { get { if (DataLength == 0) diff --git a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs index ebd32f5ff0..76b038c792 100644 --- a/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs +++ b/test/AspNetCoreModule.Test/WebSocketClientHelper/WebSocketClientHelper.cs @@ -60,7 +60,7 @@ namespace AspNetCoreModule.Test.WebSocketClient return result; } - public Frame Connect(Uri address, bool storeData, bool isAlwaysReading) + public Frame Connect(Uri address, bool storeData, bool isAlwaysReading, bool waitForConnectionOpen = true) { Address = address; StoreData = storeData; @@ -72,9 +72,25 @@ namespace AspNetCoreModule.Test.WebSocketClient } SendWebSocketRequest(WebSocketClientUtility.WebSocketVersion); - if (!WaitForWebSocketState(WebSocketState.ConnectionOpen)) + if (waitForConnectionOpen) { - throw new Exception("Failed to open a connection"); + 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; @@ -83,8 +99,7 @@ namespace AspNetCoreModule.Test.WebSocketClient openingFrame = ReadData(); else openingFrame = Connection.DataReceived[0]; - - IsOpened = true; + return openingFrame; } diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj index 784d5e618c..bc68c4518c 100644 --- a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -1,4 +1,4 @@ - + netcoreapp1.0 1.0.5 From 5c2906a7c6156e9011690679c617502c119b8eee Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 13 Sep 2017 14:40:00 -0700 Subject: [PATCH 068/107] [WIP] Fixing win7 compilation issues. (#141) --- src/AspNetCore/Src/precomp.hxx | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index e42cc92418..bdd540d23a 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -7,9 +7,12 @@ // // System related headers // -#define NTDDI_VERSION 0x06020000 -#define _WIN32_WINNT 0x0602 #define _WINSOCKAPI_ + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + #include #include #include @@ -17,7 +20,25 @@ //#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 From 2e540341dbd37e606942ccacb9330ccb2843f79c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 20 Sep 2017 16:09:56 -0700 Subject: [PATCH 069/107] Adds in-process mode to ANCM. (#152) --- build/dependencies.props | 2 +- src/AspNetCore/AspNetCore.vcxproj | 15 +- src/AspNetCore/Inc/application.h | 77 ++- src/AspNetCore/Inc/applicationmanager.h | 2 +- src/AspNetCore/Inc/aspnetcoreapplication.h | 127 ++++ src/AspNetCore/Inc/aspnetcoreconfig.h | 29 +- src/AspNetCore/Inc/fx_ver.h | 49 ++ src/AspNetCore/Src/aspnetcoreapplication.cxx | 601 +++++++++++++++++++ src/AspNetCore/Src/aspnetcoreconfig.cxx | 50 +- src/AspNetCore/Src/dllmain.cpp | 4 +- src/AspNetCore/Src/fx_ver.cxx | 195 ++++++ src/AspNetCore/Src/precomp.hxx | 5 + src/AspNetCore/Src/proxymodule.cxx | 81 ++- src/AspNetCore/Src/serverprocess.cxx | 4 +- src/AspNetCore/aspnetcore_schema.xml | 1 + src/IISLib/IISLib.vcxproj | 11 +- 16 files changed, 1202 insertions(+), 51 deletions(-) create mode 100644 src/AspNetCore/Inc/aspnetcoreapplication.h create mode 100644 src/AspNetCore/Inc/fx_ver.h create mode 100644 src/AspNetCore/Src/aspnetcoreapplication.cxx create mode 100644 src/AspNetCore/Src/fx_ver.cxx diff --git a/build/dependencies.props b/build/dependencies.props index 72812e4125..64524cd6c4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,4 +5,4 @@ 15.3.0-* 2.3.0-beta2-* - + \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 192d0f3fdb..b642d81af6 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -1,5 +1,5 @@  - + @@ -26,32 +26,33 @@ AspNetCore aspnetcore false + 10.0.15063.0 DynamicLibrary true - v140 + v141 Unicode DynamicLibrary true - v140 + v141 Unicode false DynamicLibrary false - v140 + v141 true Unicode DynamicLibrary false - v140 + v141 true Unicode @@ -153,6 +154,7 @@ + @@ -171,9 +173,11 @@ + + @@ -181,6 +185,7 @@ + diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h index ba393d66e7..cecd121b80 100644 --- a/src/AspNetCore/Inc/application.h +++ b/src/AspNetCore/Inc/application.h @@ -147,8 +147,10 @@ class APPLICATION public: APPLICATION() : m_pProcessManager(NULL), m_pApplicationManager(NULL), m_cRefs(1), - m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL) + m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL), + m_pAspNetCoreApplication(NULL) { + InitializeSRWLock(&m_srwLock); } APPLICATION_KEY * @@ -181,6 +183,61 @@ public: return m_pProcessManager->GetProcess( context, pConfig, ppServerProcess ); } + HRESULT + GetAspNetCoreApplication( + _In_ ASPNETCORE_CONFIG *pConfig, + _In_ IHttpContext *context, + _Out_ ASPNETCORE_APPLICATION **ppAspNetCoreApplication + ) + { + HRESULT hr = S_OK; + BOOL fLockTaken = FALSE; + ASPNETCORE_APPLICATION *application; + IHttpApplication *pHttpApplication = context->GetApplication(); + + if (m_pAspNetCoreApplication == NULL) + { + AcquireSRWLockExclusive(&m_srwLock); + fLockTaken = TRUE; + + if (m_pAspNetCoreApplication == NULL) + { + application = new ASPNETCORE_APPLICATION(); + if (application == NULL) { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = application->Initialize(pConfig); + if (FAILED(hr)) + { + goto Finished; + } + + // Assign after initialization + m_pAspNetCoreApplication = application; + } + } + else if (pHttpApplication->GetModuleContextContainer()->GetModuleContext(g_pModuleId) == NULL) + { + // This means that we are trying to load a second application + // TODO set a flag saying that the whole app pool is invalid ' + // (including the running application) and return 500 every request. + hr = E_FAIL; + goto Finished; + } + + *ppAspNetCoreApplication = m_pAspNetCoreApplication; + + Finished: + if (fLockTaken) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + + return hr; + } + HRESULT Recycle() { @@ -226,14 +283,16 @@ public: private: - STRU m_strAppPhysicalPath; - mutable LONG m_cRefs; - APPLICATION_KEY m_applicationKey; - PROCESS_MANAGER* m_pProcessManager; - APPLICATION_MANAGER *m_pApplicationManager; - BOOL m_fAppOfflineFound; - APP_OFFLINE_HTM *m_pAppOfflineHtm; - FILE_WATCHER_ENTRY *m_pFileWatcherEntry; + STRU m_strAppPhysicalPath; + mutable LONG m_cRefs; + APPLICATION_KEY m_applicationKey; + PROCESS_MANAGER* m_pProcessManager; + APPLICATION_MANAGER *m_pApplicationManager; + BOOL m_fAppOfflineFound; + APP_OFFLINE_HTM *m_pAppOfflineHtm; + FILE_WATCHER_ENTRY *m_pFileWatcherEntry; + ASPNETCORE_APPLICATION *m_pAspNetCoreApplication; + SRWLOCK m_srwLock; }; class APPLICATION_HASH : diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index f254c8db5f..1389d651e9 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -9,7 +9,7 @@ class APPLICATION_MANAGER { public: - static + static APPLICATION_MANAGER* GetInstance( VOID diff --git a/src/AspNetCore/Inc/aspnetcoreapplication.h b/src/AspNetCore/Inc/aspnetcoreapplication.h new file mode 100644 index 0000000000..725535ccc8 --- /dev/null +++ b/src/AspNetCore/Inc/aspnetcoreapplication.h @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +typedef void(*request_handler_cb) (int error, IHttpContext* pHttpContext, void* pvCompletionContext); +typedef REQUEST_NOTIFICATION_STATUS(*PFN_REQUEST_HANDLER) (IHttpContext* pHttpContext, void* pvRequstHandlerContext); +typedef BOOL(*PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); + +class ASPNETCORE_APPLICATION +{ +public: + + ASPNETCORE_APPLICATION(): + m_pConfiguration(NULL), + m_RequestHandler(NULL) + { + } + + ~ASPNETCORE_APPLICATION() + { + if (m_hThread != NULL) + { + CloseHandle(m_hThread); + m_hThread = NULL; + } + + if (m_pInitalizeEvent != NULL) + { + CloseHandle(m_pInitalizeEvent); + m_pInitalizeEvent = NULL; + } + } + + HRESULT + Initialize( + _In_ ASPNETCORE_CONFIG* pConfig + ); + + REQUEST_NOTIFICATION_STATUS + ExecuteRequest( + _In_ IHttpContext* pHttpContext + ); + + VOID + Shutdown( + VOID + ); + + VOID + SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_callback, + _In_ PFN_SHUTDOWN_HANDLER shutdown_callback, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext + ); + + // Executes the .NET Core process + HRESULT + ExecuteApplication( + VOID + ); + + ASPNETCORE_CONFIG* + GetConfig( + VOID + ) + { + return m_pConfiguration; + } + + static + ASPNETCORE_APPLICATION* + GetInstance( + VOID + ) + { + return s_Application; + } + +private: + // Thread executing the .NET Core process + HANDLE m_hThread; + + // Configuration for this application + ASPNETCORE_CONFIG* m_pConfiguration; + + // The request handler callback from managed code + PFN_REQUEST_HANDLER m_RequestHandler; + VOID* m_RequstHandlerContext; + + // The shutdown handler callback from managed code + PFN_SHUTDOWN_HANDLER m_ShutdownHandler; + VOID* m_ShutdownHandlerContext; + + // The event that gets triggered when managed initialization is complete + HANDLE m_pInitalizeEvent; + + // The exit code of the .NET Core process + INT m_ProcessExitCode; + + static ASPNETCORE_APPLICATION* s_Application; + + static VOID + FindDotNetFolders( + _In_ STRU *pstrPath, + _Out_ std::vector *pvFolders + ); + + static HRESULT + FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult + ); + + static BOOL + DirectoryExists( + _In_ STRU *pstrPath + ); + + static BOOL + GetEnv( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult + ); +}; + diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h index e83e4301ff..b2a62695dc 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -27,6 +27,7 @@ #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 @@ -57,7 +58,7 @@ public: _In_ IHttpContext *pHttpContext, _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig ); - + ENVIRONMENT_VAR_HASH* QueryEnvironmentVariables( VOID @@ -117,11 +118,19 @@ public: STRU* QueryApplicationPath( VOID - ) + ) { return &m_struApplication; } + STRU* + QueryApplicationFullPath( + VOID + ) + { + return &m_struApplicationFullPath; + } + STRU* QueryProcessPath( VOID @@ -166,6 +175,19 @@ public: return m_fDisableStartUpErrorPage; } + BOOL + QueryIsInProcess() + { + return m_fIsInProcess; + } + + BOOL + QueryIsOutOfProcess() + { + return m_fIsOutOfProcess; + } + + STRU* QueryStdoutLogFile() { @@ -197,11 +219,14 @@ private: STRU m_struArguments; STRU m_struProcessPath; STRU m_struStdoutLogFile; + STRU m_struApplicationFullPath; BOOL m_fStdoutLogEnabled; BOOL m_fForwardWindowsAuthToken; BOOL m_fDisableStartUpErrorPage; BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; + BOOL m_fIsInProcess; + BOOL m_fIsOutOfProcess; ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; }; 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/Src/aspnetcoreapplication.cxx b/src/AspNetCore/Src/aspnetcoreapplication.cxx new file mode 100644 index 0000000000..3e2f727f5a --- /dev/null +++ b/src/AspNetCore/Src/aspnetcoreapplication.cxx @@ -0,0 +1,601 @@ +#include "precomp.hxx" +#include "fx_ver.h" +#include + +typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); + +// Initialization export + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +register_callbacks( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + ASPNETCORE_APPLICATION::GetInstance()->SetCallbackHandles( + request_handler, + shutdown_handler, + pvRequstHandlerContext, + pvShutdownHandlerContext + ); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_REQUEST* +http_get_raw_request( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->GetRequest()->GetRawHttpRequest(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_RESPONSE* +http_get_raw_response( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->GetResponse()->GetRawHttpResponse(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( + _In_ IHttpContext* pHttpContext, + _In_ USHORT statusCode, + _In_ PCSTR pszReason +) +{ + pHttpContext->GetResponse()->SetStatus(statusCode, pszReason); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_post_completion( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->PostCompletion(0); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_indicate_completion( + _In_ IHttpContext* pHttpContext, + _In_ REQUEST_NOTIFICATION_STATUS notificationStatus +) +{ + pHttpContext->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(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +BSTR // TODO probably should make this a wide string +http_get_application_full_path() +{ + return SysAllocString(ASPNETCORE_APPLICATION::GetInstance()->GetConfig()->QueryApplicationFullPath()->QueryStr()); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_read_request_bytes( + _In_ IHttpContext* pHttpContext, + _In_ CHAR* pvBuffer, + _In_ DWORD cbBuffer, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ DWORD* pDwBytesReceived, + _In_ BOOL* pfCompletionPending +) +{ + IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pHttpContext->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_write_response_bytes( + _In_ IHttpContext* pHttpContext, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD nChunks, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent; + + HRESULT hr = pHttpResponse->WriteEntityChunks( + pDataChunks, + nChunks, + fAsync, + fMoreData, + pfnCompletionCallback, + pvCompletionContext, + &dwBytesSent, + pfCompletionExpected); + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_flush_response_bytes( + _In_ IHttpContext* pHttpContext, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent; + + HRESULT hr = pHttpResponse->Flush( + fAsync, + fMoreData, + pfnCompletionCallback, + pvCompletionContext, + &dwBytesSent, + pfCompletionExpected); + return hr; +} + +// Thread execution callback +static +VOID +ExecuteAspNetCoreProcess( + _In_ LPVOID pContext +) +{ + HRESULT hr; + ASPNETCORE_APPLICATION *pApplication = (ASPNETCORE_APPLICATION*)pContext; + + hr = pApplication->ExecuteApplication(); + if (hr != S_OK) + { + // TODO log error + } +} + +ASPNETCORE_APPLICATION* +ASPNETCORE_APPLICATION::s_Application = NULL; + +VOID +ASPNETCORE_APPLICATION::SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + m_RequestHandler = request_handler; + m_RequstHandlerContext = pvRequstHandlerContext; + m_ShutdownHandler = shutdown_handler; + m_ShutdownHandlerContext = pvShutdownHandlerContext; + + // Initialization complete + SetEvent(m_pInitalizeEvent); +} + +HRESULT +ASPNETCORE_APPLICATION::Initialize( + _In_ ASPNETCORE_CONFIG * pConfig +) +{ + HRESULT hr = S_OK; + + DWORD dwTimeout; + DWORD dwResult; + DBG_ASSERT(pConfig != NULL); + + m_pConfiguration = pConfig; + + m_pInitalizeEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not set + NULL); // name + + if (m_pInitalizeEvent == NULL) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + 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) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + // If the debugger is attached, never timeout + if (IsDebuggerPresent()) + { + dwTimeout = INFINITE; + } + else + { + dwTimeout = 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) + { + return HRESULT_FROM_WIN32(dwResult); + } + else if (dwResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + dwResult = WaitForSingleObject(m_hThread, 0); + + // The thread ended it means that something failed + if (dwResult == WAIT_OBJECT_0) + { + return HRESULT_FROM_WIN32(dwResult); + } + else if (dwResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +HRESULT +ASPNETCORE_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; + + // Get the System PATH value. + if (!GetEnv(L"PATH", &strFullPath)) + { + goto Failed; + } + + // Split on ';', checking to see if dotnet.exe exists in any folders. + pszDotnetExeLocation = wcstok_s(strFullPath.QueryStr(), L";", &strDelimeterContext); + + while (pszDotnetExeLocation != NULL) + { + dwCopyLength = 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 Failed; + } + + hr = strDotnetFolderLocation.Copy(pszDotnetExeLocation, dwCopyLength); + if (FAILED(hr)) + { + goto Failed; + } + + if (dwCopyLength > 0 && pszDotnetExeLocation[dwCopyLength - 1] != L'\\') + { + hr = strDotnetExeLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Failed; + } + } + + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Failed; + } + + if (PathFileExists(strDotnetExeLocation.QueryStr())) + { + // means we found the folder with a dotnet.exe inside of it. + break; + } + pszDotnetExeLocation = wcstok_s(NULL, L";", &strDelimeterContext); + } + + hr = strDotnetFolderLocation.Append(L"\\host\\fxr"); + if (FAILED(hr)) + { + goto Failed; + } + + if (!DirectoryExists(&strDotnetFolderLocation)) + { + goto Failed; + } + + // Find all folders under host\\fxr\\ for version numbers. + hr = strHostFxrSearchExpression.Copy(strDotnetFolderLocation); + if (FAILED(hr)) + { + goto Failed; + } + + hr = strHostFxrSearchExpression.Append(L"\\*"); + if (FAILED(hr)) + { + goto Failed; + } + + // As we use the logic from core-setup, we are opting to use std here. + // TODO remove all uses of std? + FindDotNetFolders(&strHostFxrSearchExpression, &vVersionFolders); + + if (vVersionFolders.size() == 0) + { + goto Failed; + } + + hr = FindHighestDotNetVersion(vVersionFolders, &strHighestDotnetVersion); + if (FAILED(hr)) + { + goto Failed; + } + hr = strDotnetFolderLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Failed; + } + + hr = strDotnetFolderLocation.Append(strHighestDotnetVersion.QueryStr()); + if (FAILED(hr)) + { + goto Failed; + + } + + hr = strDotnetFolderLocation.Append(L"\\hostfxr.dll"); + if (FAILED(hr)) + { + goto Failed; + } + + hModule = LoadLibraryW(strDotnetFolderLocation.QueryStr()); + + if (hModule == NULL) + { + // .NET Core not installed (we can log a more detailed error message here) + goto Failed; + } + + // Get the entry point for main + pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main"); + if (pProc == NULL) { + goto Failed; + } + + // The first argument is mostly ignored + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Failed; + } + + argv[0] = strDotnetExeLocation.QueryStr(); + PATH::ConvertPathToFullPath(m_pConfiguration->QueryArguments()->QueryStr(), m_pConfiguration->QueryApplicationFullPath()->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; + + m_ProcessExitCode = pProc(2, argv); + if (m_ProcessExitCode != 0) + { + // TODO error + } + + return hr; +Failed: + // TODO log any errors + return hr; +} + +BOOL +ASPNETCORE_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 +ASPNETCORE_APPLICATION::FindDotNetFolders( + _In_ STRU *pstrPath, + _Out_ std::vector *pvFolders +) +{ + HANDLE handle = NULL; + WIN32_FIND_DATAW data = { 0 }; + + handle = FindFirstFileExW(pstrPath->QueryStr(), 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); +} + +HRESULT +ASPNETCORE_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)) + { + max_ver = std::max(max_ver, fx_ver); + } + } + + hr = pstrResult->Copy(max_ver.as_str().c_str()); + + // we check FAILED(hr) outside of function + return hr; +} + +BOOL +ASPNETCORE_APPLICATION::DirectoryExists( + _In_ STRU *pstrPath +) +{ + WIN32_FILE_ATTRIBUTE_DATA data; + + if (pstrPath->IsEmpty()) + { + return false; + } + + return GetFileAttributesExW(pstrPath->QueryStr(), GetFileExInfoStandard, &data); +} + +REQUEST_NOTIFICATION_STATUS +ASPNETCORE_APPLICATION::ExecuteRequest( + _In_ IHttpContext* pHttpContext +) +{ + if (m_RequestHandler != NULL) + { + return m_RequestHandler(pHttpContext, m_RequstHandlerContext); + } + + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); + return RQ_NOTIFICATION_FINISH_REQUEST; +} + + +VOID +ASPNETCORE_APPLICATION::Shutdown( + VOID +) +{ + // First call into the managed server and shutdown + BOOL result = m_ShutdownHandler(m_ShutdownHandlerContext); + s_Application = NULL; + delete this; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index e919eb7d9b..944ff75e28 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -8,11 +8,16 @@ ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() // // the destructor will be called once IIS decides to recycle the module context (i.e., application) // + // shutting down core application first + if (ASPNETCORE_APPLICATION::GetInstance() != NULL) { + ASPNETCORE_APPLICATION::GetInstance()->Shutdown(); + } + m_struApplicationFullPath.Reset(); if (!m_struApplication.IsEmpty()) { APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); } - if(m_pEnvironmentVariables != NULL) + if (m_pEnvironmentVariables != NULL) { m_pEnvironmentVariables->Clear(); delete m_pEnvironmentVariables; @@ -86,7 +91,7 @@ ASPNETCORE_CONFIG::GetConfig( else { // set appliction info here instead of inside Populate() - // as the destructor will delete the backend process + // as the destructor will delete the backend process hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); if (FAILED(hr)) { @@ -118,6 +123,8 @@ ASPNETCORE_CONFIG::Populate( STRU strEnvName; STRU strEnvValue; STRU strExpandedEnvValue; + STRU strApplicationFullPath; + STRU strHostingModel; IAppHostAdminManager *pAdminManager = NULL; IAppHostElement *pAspNetCoreElement = NULL; IAppHostElement *pWindowsAuthenticationElement = NULL; @@ -144,13 +151,18 @@ ASPNETCORE_CONFIG::Populate( } pAdminManager = g_pHttpServer->GetAdminManager(); - hr = strSiteConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); if (FAILED(hr)) { goto Finished; } + hr = m_struApplicationFullPath.Copy(pHttpContext->GetApplication()->GetApplicationPhysicalPath()); + if (FAILED(hr)) + { + goto Finished; + } + hr = pAdminManager->GetAdminSection(CS_WINDOWS_AUTHENTICATION_SECTION, strSiteConfigPath.QueryStr(), &pWindowsAuthenticationElement); @@ -224,6 +236,23 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_HOSTING_MODEL, + &strHostingModel); + if (FAILED(hr)) + { + goto Finished; + } + + if (strHostingModel.IsEmpty() || strHostingModel.Equals(L"outofprocess", TRUE)) + { + m_fIsOutOfProcess = TRUE; + } + else if (strHostingModel.Equals(L"inprocess", TRUE)) + { + m_fIsInProcess = TRUE; + } + hr = GetElementStringProperty(pAspNetCoreElement, CS_ASPNETCORE_PROCESS_ARGUMENTS, &m_struArguments); @@ -314,14 +343,13 @@ ASPNETCORE_CONFIG::Populate( { goto Finished; } - - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_STDOUT_LOG_FILE, - &m_struStdoutLogFile); - 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, diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index 2aaf5d7044..e510dc535d 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -210,6 +210,7 @@ HRESULT // static object initialized. // pFactory = new CProxyModuleFactory; + if (pFactory == NULL) { hr = E_OUTOFMEMORY; @@ -225,7 +226,8 @@ HRESULT goto Finished; } - pFactory = NULL; + pFactory = NULL; + g_pResponseHeaderHash = new RESPONSE_HEADER_HASH; if (g_pResponseHeaderHash == NULL) { diff --git a/src/AspNetCore/Src/fx_ver.cxx b/src/AspNetCore/Src/fx_ver.cxx new file mode 100644 index 0000000000..1c844d3113 --- /dev/null +++ b/src/AspNetCore/Src/fx_ver.cxx @@ -0,0 +1,195 @@ +// 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 +#include +#include "fx_ver.h" +#include "precomp.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/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index bdd540d23a..178c66698f 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -42,6 +42,10 @@ #include #include +#include +#include +#include + // // Option available starting Windows 8. // 111 is the value in SDK on May 15, 2012. @@ -110,6 +114,7 @@ inline bool IsSpace(char ch) #include "environmentvariablehash.h" #include "..\aspnetcore_msg.h" #include "aspnetcoreconfig.h" +#include "aspnetcoreapplication.h" #include "serverprocess.h" #include "processmanager.h" #include "filewatcher.h" diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index d19f4488ff..502d91f035 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -3,7 +3,6 @@ #include "precomp.hxx" - __override HRESULT CProxyModuleFactory::GetHttpModule( @@ -79,32 +78,86 @@ CProxyModule::OnExecuteRequestHandler( IHttpEventProvider * ) { - m_pHandler = new FORWARDING_HANDLER(pHttpContext); - if (m_pHandler == NULL) - { - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY); - return RQ_NOTIFICATION_FINISH_REQUEST; - } + HRESULT hr; + APPLICATION_MANAGER* pApplicationManager; + APPLICATION* pApplication; + ASPNETCORE_CONFIG* config; + ASPNETCORE_APPLICATION* pAspNetCoreApplication; + ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - return m_pHandler->OnExecuteRequestHandler(); + // TODO store whether we are inproc or outofproc so we don't need to check the config everytime? + if (config->QueryIsOutOfProcess())// case insensitive + { + m_pHandler = new FORWARDING_HANDLER(pHttpContext); + if (m_pHandler == NULL) + { + hr = E_OUTOFMEMORY; + goto Failed; + } + + return m_pHandler->OnExecuteRequestHandler(); + } + else if (config->QueryIsInProcess()) + { + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Failed; + } + + hr = pApplicationManager->GetApplication(pHttpContext, + &pApplication); + if (FAILED(hr)) + { + goto Failed; + } + + hr = pApplication->GetAspNetCoreApplication(config, pHttpContext, &pAspNetCoreApplication); + if (FAILED(hr)) + { + goto Failed; + } + + // Allow reading and writing to simultaneously + ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); + + // Disable response buffering by default, we'll do a write behind buffering in managed code + ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); + + // TODO: Optimize sync completions + return pAspNetCoreApplication->ExecuteRequest(pHttpContext); + } +Failed: + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; } __override REQUEST_NOTIFICATION_STATUS CProxyModule::OnAsyncCompletion( - IHttpContext *, + IHttpContext * pHttpContext, DWORD dwNotification, BOOL fPostNotification, IHttpEventProvider *, IHttpCompletionInfo * pCompletionInfo ) { - UNREFERENCED_PARAMETER(dwNotification); - UNREFERENCED_PARAMETER(fPostNotification); - DBG_ASSERT(dwNotification == RQ_EXECUTE_REQUEST_HANDLER); - DBG_ASSERT(fPostNotification == FALSE); + // TODO store whether we are inproc or outofproc so we don't need to check the config everytime? + ASPNETCORE_CONFIG* config; + ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - return m_pHandler->OnAsyncCompletion( + if (config->QueryIsOutOfProcess()) + { + return m_pHandler->OnAsyncCompletion( pCompletionInfo->GetCompletionBytes(), pCompletionInfo->GetCompletionStatus()); + } + else if (config->QueryIsInProcess()) + { + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_CONTINUE; + } + + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; } \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 9487d2a5ca..e8922bd1f0 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -832,7 +832,7 @@ SERVER_PROCESS::PostStartCheck( pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, m_struAppFullPath.QueryStr(), - m_pszRootApplicationPath, + m_pszRootApplicationPath.QueryStr(), pStruCommandline->QueryStr(), m_dwPort, hr); @@ -1071,7 +1071,7 @@ Finished: strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, m_struAppFullPath.QueryStr(), - m_pszRootApplicationPath, + m_pszRootApplicationPath.QueryStr(), struCommandLine.QueryStr(), hr); } diff --git a/src/AspNetCore/aspnetcore_schema.xml b/src/AspNetCore/aspnetcore_schema.xml index ca41d48691..c00cc8a77c 100644 --- a/src/AspNetCore/aspnetcore_schema.xml +++ b/src/AspNetCore/aspnetcore_schema.xml @@ -27,6 +27,7 @@ + diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj index 8ff54a4c75..296e723b3f 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -23,31 +23,32 @@ Win32Proj IISLib IISLib + 10.0.15063.0 StaticLibrary true - v140 + v141 Unicode StaticLibrary true - v140 + v141 Unicode StaticLibrary false - v140 + v141 true Unicode StaticLibrary false - v140 + v141 true Unicode From f130330db8d29e679eef70ee75b8d7f147489359 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 25 Sep 2017 15:10:31 -0700 Subject: [PATCH 070/107] Updating Xunit and dotnet versions (#143) --- build/dependencies.props | 5 +- src/AspNetCore/Src/serverprocess.cxx | 11 +++-- .../Framework/TestWebSite.cs | 4 +- test/AspNetCoreModule.Test/FunctionalTest.cs | 38 +++++++-------- .../FunctionalTestHelper.cs | 11 ++--- ...AspNetCoreModule.TestSites.Standard.csproj | 17 +------ .../Program.cs | 47 +++---------------- .../Startup.cs | 9 +--- .../StartupNtlmAuthentication.cs | 12 +++-- 9 files changed, 55 insertions(+), 99 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 64524cd6c4..37991cfb7d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -2,7 +2,8 @@ 1.2.0-* 2.1.1-* - 15.3.0-* - 2.3.0-beta2-* + 15.3.0 + 0.6.1 + 2.3.0-beta4-build3742 \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index e8922bd1f0..8296d6df9c 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -1063,17 +1063,22 @@ Finished: if (strEventMsg.IsEmpty()) { if (!fDonePrepareCommandLine) + { + strEventMsg.SafeSnwprintf( - m_struAppFullPath.QueryStr(), - ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, - hr); + 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(); diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index e03e438267..5f52097e94 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -196,9 +196,9 @@ namespace AspNetCoreModule.Test.Framework } // - // Currently we use DotnetCore v1.0 + // Currently we use DotnetCore v2.0 // - string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp1.0", "publish"); + string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp2.0", "publish"); string publishPathOutput = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", "publishPathOutput"); // diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index adc46727bf..5f764cb801 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -21,7 +21,7 @@ namespace AspNetCoreModule.Test { await DoBasicTest(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -34,7 +34,7 @@ namespace AspNetCoreModule.Test { return DoRapidFailsPerMinuteTest(appPoolBitness, valueOfRapidFailsPerMinute); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -55,7 +55,7 @@ namespace AspNetCoreModule.Test { return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime, isGraceFullShutdownEnabled); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -68,7 +68,7 @@ namespace AspNetCoreModule.Test { return DoStartupTimeLimitTest(appPoolBitness, starupTimeLimit); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -79,7 +79,7 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationAfterBackendProcessBeingKilled(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -90,7 +90,7 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationAfterW3WPProcessBeingKilled(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -101,7 +101,7 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationAfterWebConfigUpdated(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -112,7 +112,7 @@ namespace AspNetCoreModule.Test { return DoRecycleApplicationWithURLRewrite(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -123,7 +123,7 @@ namespace AspNetCoreModule.Test { return DoRecycleParentApplicationWithURLRewrite(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -140,7 +140,7 @@ namespace AspNetCoreModule.Test { return DoEnvironmentVariablesTest(environmentVariableName, environmentVariableValue, expectedEnvironmentVariableValue, appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -151,7 +151,7 @@ namespace AspNetCoreModule.Test { return DoAppOfflineTestWithRenaming(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -162,7 +162,7 @@ namespace AspNetCoreModule.Test { return DoAppOfflineTestWithUrlRewriteAndDeleting(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -173,7 +173,7 @@ namespace AspNetCoreModule.Test { return DoPostMethodTest(appPoolBitness, testData); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -195,7 +195,7 @@ namespace AspNetCoreModule.Test { return DoProcessesPerApplicationTest(appPoolBitness, valueOfProcessesPerApplication); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -208,7 +208,7 @@ namespace AspNetCoreModule.Test { return DoRequestTimeoutTest(appPoolBitness, requestTimeout); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -219,7 +219,7 @@ namespace AspNetCoreModule.Test { return DoStdoutLogEnabledTest(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -232,7 +232,7 @@ namespace AspNetCoreModule.Test { return DoProcessPathAndArgumentsTest(appPoolBitness, processPath, argumentsPrefix); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -258,7 +258,7 @@ namespace AspNetCoreModule.Test { return DoCompressionTest(appPoolBitness, useCompressionMiddleWare, enableIISCompression); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] @@ -269,7 +269,7 @@ namespace AspNetCoreModule.Test { return DoCachingTest(appPoolBitness); } - + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index dcbd06adc3..b2e6f126d7 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -898,8 +898,8 @@ namespace AspNetCoreModule.Test requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); if (enabledForwardWindowsAuthToken) { - string expectedHeaderName = "MS-ASPNETCORE-WINAUTHTOKEN"; - Assert.Contains(expectedHeaderName, requestHeaders.ToUpper()); + + Assert.Contains("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); bool compare = false; @@ -1376,8 +1376,8 @@ namespace AspNetCoreModule.Test // 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()"); - string expectedHeaderName = "MS-ASPNETCORE-CLIENTCERT"; - Assert.Contains(expectedHeaderName, outputRawContent); + + 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"); @@ -2251,7 +2251,7 @@ namespace AspNetCoreModule.Test { postHttpContent = new FormUrlEncodedContent(postData); } - + if (numberOfRetryCount > 1 && expectedResponseStatus == HttpStatusCode.OK) { if (postData == null) @@ -2337,7 +2337,6 @@ namespace AspNetCoreModule.Test } TestUtility.LogInformation(responseText); TestUtility.LogInformation(responseStatus); - throw; } return result; } diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj index bc68c4518c..ad4431d629 100644 --- a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -1,21 +1,8 @@  - netcoreapp1.0 - 1.0.5 + netcoreapp2.0 - - - - - - - - - - - - - + diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index ca854a8e2b..89f464b0b2 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -4,11 +4,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration; -using Microsoft.Net.Http.Server; using System; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Threading; +using System.Net; namespace AspnetCoreModule.TestSites.Standard { @@ -25,7 +25,7 @@ namespace AspnetCoreModule.TestSites.Standard var config = new ConfigurationBuilder() .AddCommandLine(args) .Build(); - + string startUpClassString = Environment.GetEnvironmentVariable("ANCMTestStartupClassName"); IWebHostBuilder builder = null; if (!string.IsNullOrEmpty(startUpClassString)) @@ -36,7 +36,7 @@ namespace AspnetCoreModule.TestSites.Standard string pfxPassword = "testPassword"; if (File.Exists(@".\TestResources\testcert.pfx")) { - _x509Certificate2 = new X509Certificate2(@".\TestResources\testcert.pfx", pfxPassword); + _x509Certificate2 = new X509Certificate2(@".\TestResources\testcert.pfx", pfxPassword); } else { @@ -46,14 +46,7 @@ namespace AspnetCoreModule.TestSites.Standard builder = new WebHostBuilder() .UseConfiguration(config) .UseIISIntegration() - .UseKestrel(options => - { - HttpsConnectionFilterOptions httpsoptions = new HttpsConnectionFilterOptions(); - httpsoptions.ServerCertificate = _x509Certificate2; - httpsoptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate; - httpsoptions.CheckCertificateRevocation = false; - options.UseHttps(httpsoptions); - }) + .UseKestrel() .UseStartup(); } else if (startUpClassString == "StartupCompressionCaching") @@ -116,33 +109,8 @@ namespace AspnetCoreModule.TestSites.Standard { Startup.SleeptimeWhileClosing = Convert.ToInt32(shutdownDelay); } - - // Switch between Kestrel and WebListener for different tests. Default to Kestrel for normal app execution. - if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.WebListener", System.StringComparison.Ordinal)) - { - if (string.Equals(builder.GetSetting("environment") ?? - Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), - "NtlmAuthentication", System.StringComparison.Ordinal)) - { - // Set up NTLM authentication for WebListener as follows. - // For IIS and IISExpress use inetmgr to setup NTLM authentication on the application or - // modify the applicationHost.config to enable NTLM. - builder.UseWebListener(options => - { - options.ListenerSettings.Authentication.AllowAnonymous = true; - options.ListenerSettings.Authentication.Schemes = - AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM; - }); - } - else - { - builder.UseWebListener(); - } - } - else - { - builder.UseKestrel(); - } + + builder.UseKestrel(); var host = builder.Build(); AappLifetime = (IApplicationLifetime)host.Services.GetService(typeof(IApplicationLifetime)); @@ -152,7 +120,6 @@ namespace AspnetCoreModule.TestSites.Standard { GracefulShutdownDelayTime = Convert.ToInt32(gracefulShutdownDelay); } - AappLifetime.ApplicationStarted.Register( () => { Thread.Sleep(1000); @@ -178,7 +145,7 @@ namespace AspnetCoreModule.TestSites.Standard { // ignore } - + if (Startup.SleeptimeWhileClosing != 0) { Thread.Sleep(Startup.SleeptimeWhileClosing); diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index 18981450c2..516dae7b16 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -28,9 +28,6 @@ namespace AspnetCoreModule.TestSites.Standard public void ConfigureServices(IServiceCollection services) { services.Configure(options => { - // Considering the default value of ForwardWindowsAuthentication is true, - // the below line is not required at present, however keeping in case the default value is changed later. - options.ForwardWindowsAuthentication = true; }); } @@ -73,7 +70,6 @@ namespace AspnetCoreModule.TestSites.Standard { app.UseWebSockets(new WebSocketOptions { - ReplaceFeature = true }); subApp.Use(async (context, next) => @@ -81,7 +77,7 @@ namespace AspnetCoreModule.TestSites.Standard if (context.WebSockets.IsWebSocketRequest) { var webSocket = await context.WebSockets.AcceptWebSocketAsync("mywebsocketsubprotocol"); - await Echo(webSocket); + await Echo(webSocket); } else { @@ -96,7 +92,6 @@ namespace AspnetCoreModule.TestSites.Standard { app.UseWebSockets(new WebSocketOptions { - ReplaceFeature = true }); subApp.Use(async (context, next) => @@ -123,7 +118,7 @@ namespace AspnetCoreModule.TestSites.Standard return context.Response.WriteAsync(process.Id.ToString()); }); }); - + app.Map("/EchoPostData", subApp => { subApp.Run(context => diff --git a/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs index c29fa05332..bab2f1e32f 100644 --- a/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs +++ b/test/AspNetCoreModule.TestSites.Standard/StartupNtlmAuthentication.cs @@ -2,6 +2,7 @@ // 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; @@ -47,18 +48,19 @@ namespace AspnetCoreModule.TestSites.Standard } else { - return context.Authentication.ChallengeAsync(); + return context.ChallengeAsync(); } } if (context.Request.Path.Equals("/Forbidden")) { - return context.Authentication.ForbidAsync(Microsoft.AspNetCore.Http.Authentication.AuthenticationManager.AutomaticScheme); + return context.ForbidAsync(); + } if (context.Request.Path.Equals("/AutoForbid")) { - return context.Authentication.ChallengeAsync(); + return context.ChallengeAsync(); } if (context.Request.Path.Equals("/RestrictedNegotiate")) @@ -69,7 +71,7 @@ namespace AspnetCoreModule.TestSites.Standard } else { - return context.Authentication.ChallengeAsync("Negotiate"); + return context.ChallengeAsync("Negotiate"); } } @@ -81,7 +83,7 @@ namespace AspnetCoreModule.TestSites.Standard } else { - return context.Authentication.ChallengeAsync("NTLM"); + return context.ChallengeAsync("NTLM"); } } From 890e49e539ca37ea21c76e48ddcbbe1da2318719 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 29 Sep 2017 14:49:12 -0700 Subject: [PATCH 071/107] ANCM Test Refactoring: Initial check-in to fix the build issue on AspNetCI (#172) * Initial check-in to fix the build issue on AspNetCI * Updated with Jimmy's feedbacks --- .../Framework/IISConfigUtility.cs | 73 +-- .../Framework/InitializeTestMachine.cs | 458 +++++++++++------- .../Framework/TestUtility.cs | 4 +- .../Framework/TestWebSite.cs | 72 ++- test/AspNetCoreModule.Test/FunctionalTest.cs | 60 +-- .../FunctionalTestHelper.cs | 79 +-- 6 files changed, 420 insertions(+), 326 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index fc4c48ab3b..86077f22db 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -155,7 +155,7 @@ namespace AspNetCoreModule.Test.Framework { ApppHostTemporaryBackupFileExtention = temporaryBackupFileExtenstion; break; - } + } } } @@ -263,7 +263,7 @@ namespace AspNetCoreModule.Test.Framework addElement[attribute] = value; break; } - serverManager.CommitChanges(); + serverManager.CommitChanges(); } } @@ -291,7 +291,7 @@ namespace AspNetCoreModule.Test.Framework } } - public void CreateSite(string siteName, string physicalPath, int siteId, int tcpPort, string appPoolName = "DefaultAppPool") + public void CreateSite(string siteName, string hostname, string physicalPath, int siteId, int tcpPort, string appPoolName = "DefaultAppPool") { TestUtility.LogInformation("Creating web site : " + siteName); @@ -312,7 +312,7 @@ namespace AspNetCoreModule.Test.Framework ConfigurationElement bindingElement = bindingsCollection.CreateElement("binding"); bindingElement["protocol"] = @"http"; - bindingElement["bindingInformation"] = "*:" + tcpPort + ":"; + bindingElement["bindingInformation"] = "*:" + tcpPort + ":" + hostname; bindingsCollection.Add(bindingElement); ConfigurationElementCollection siteCollection = siteElement.GetCollection(); @@ -327,7 +327,6 @@ namespace AspNetCoreModule.Test.Framework applicationCollection.Add(virtualDirectoryElement); siteCollection.Add(applicationElement); sitesCollection.Add(siteElement); - serverManager.CommitChanges(); } } @@ -361,7 +360,6 @@ namespace AspNetCoreModule.Test.Framework virtualDirectoryElement["physicalPath"] = physicalPath; applicationCollection.Add(virtualDirectoryElement); siteCollection.Add(applicationElement); - serverManager.CommitChanges(); } } @@ -379,7 +377,6 @@ namespace AspNetCoreModule.Test.Framework basicAuthenticationSection["enabled"] = basic; ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); windowsAuthenticationSection["enabled"] = windows; - serverManager.CommitChanges(); } } @@ -416,7 +413,6 @@ namespace AspNetCoreModule.Test.Framework anonymousAuthenticationSection["enabled"] = false; ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); windowsAuthenticationSection["enabled"] = false; - serverManager.CommitChanges(); } } @@ -431,7 +427,6 @@ namespace AspNetCoreModule.Test.Framework ConfigurationSection urlCompressionSection = config.GetSection("system.webServer/urlCompression", siteName); urlCompressionSection["doStaticCompression"] = enabled; urlCompressionSection["doDynamicCompression"] = enabled; - serverManager.CommitChanges(); } } @@ -447,7 +442,6 @@ namespace AspNetCoreModule.Test.Framework anonymousAuthenticationSection["enabled"] = true; ConfigurationSection windowsAuthenticationSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication", siteName); windowsAuthenticationSection["enabled"] = false; - serverManager.CommitChanges(); } } @@ -509,41 +503,10 @@ namespace AspNetCoreModule.Test.Framework errorElement2["subStatusCode"] = subStatusCode; errorElement2["path"] = path; httpErrorsCollection.Add(errorElement2); - serverManager.CommitChanges(); } Thread.Sleep(500); } - - private static bool? _isIISInstalled = null; - public static bool? IsIISInstalled - { - get - { - if (_isIISInstalled == null) - { - _isIISInstalled = true; - if (_isIISInstalled == true && !File.Exists(Path.Combine(Strings.IIS64BitPath, "iiscore.dll"))) - { - _isIISInstalled = false; - } - if (_isIISInstalled == true && !File.Exists(Path.Combine(Strings.IIS64BitPath, "config", "applicationhost.config"))) - { - _isIISInstalled = false; - } - } - return _isIISInstalled; - } - set - { - _isIISInstalled = value; - } - } - - public static bool IsIISReady { - get; - set; - } public bool IsAncmInstalled(ServerType servertype) { @@ -646,7 +609,7 @@ namespace AspNetCoreModule.Test.Framework { modulesCollection.Remove(module); } - + serverManager.CommitChanges(); } return result; @@ -748,7 +711,6 @@ namespace AspNetCoreModule.Test.Framework { ApplicationPoolCollection appPools = serverManager.ApplicationPools; appPools[appPoolName].ProcessModel.IdleTimeout = TimeSpan.FromMinutes(idleTimeoutMinutes); - serverManager.CommitChanges(); } } @@ -767,7 +729,6 @@ namespace AspNetCoreModule.Test.Framework { ApplicationPoolCollection appPools = serverManager.ApplicationPools; appPools[appPoolName].ProcessModel.MaxProcesses = maxProcesses; - serverManager.CommitChanges(); } } @@ -788,7 +749,6 @@ namespace AspNetCoreModule.Test.Framework appPools[appPoolName].ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser; appPools[appPoolName].ProcessModel.UserName = userName; appPools[appPoolName].ProcessModel.Password = password; - serverManager.CommitChanges(); } } @@ -810,7 +770,6 @@ namespace AspNetCoreModule.Test.Framework { ApplicationPoolCollection appPools = serverManager.ApplicationPools; appPools[appPoolName]["startMode"] = startMode; - serverManager.CommitChanges(); } } @@ -841,11 +800,13 @@ namespace AspNetCoreModule.Test.Framework { ApplicationPoolCollection appPools = serverManager.ApplicationPools; if (start) + { appPools[appPoolName].Start(); + } else + { appPools[appPoolName].Stop(); - - serverManager.CommitChanges(); + } } } catch (Exception ex) @@ -902,8 +863,9 @@ namespace AspNetCoreModule.Test.Framework { ApplicationPoolCollection appPools = serverManager.ApplicationPools; while (appPools.Count > 0) + { appPools.RemoveAt(0); - + } serverManager.CommitChanges(); } } @@ -948,7 +910,6 @@ namespace AspNetCoreModule.Test.Framework b.SetAttributeValue("bindingInformation", bindingInfo); site.Bindings.Add(b); - serverManager.CommitChanges(); } } @@ -988,7 +949,6 @@ namespace AspNetCoreModule.Test.Framework sites[siteName].Stop(); sites[siteName].SetAttributeValue("serverAutoStart", false); } - serverManager.CommitChanges(); } } @@ -1033,7 +993,6 @@ namespace AspNetCoreModule.Test.Framework vdir.SetAttributeValue("physicalPath", physicalPath); app.VirtualDirectories.Add(vdir); - serverManager.CommitChanges(); } } @@ -1176,7 +1135,7 @@ namespace AspNetCoreModule.Test.Framework } 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"); @@ -1391,8 +1350,9 @@ namespace AspNetCoreModule.Test.Framework SiteCollection sites = serverManager.Sites; while (sites.Count > 0) + { sites.RemoveAt(0); - + } serverManager.CommitChanges(); } } @@ -1406,11 +1366,8 @@ namespace AspNetCoreModule.Test.Framework using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); - ConfigurationSection webLimitsSection = config.GetSection("system.applicationHost/webLimits"); webLimitsSection["dynamicRegistrationThreshold"] = threshold; - - serverManager.CommitChanges(); } } @@ -1418,6 +1375,6 @@ namespace AspNetCoreModule.Test.Framework { 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 index 09f32d0691..53b7fcd389 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -5,58 +5,41 @@ 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 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.Contains(flagValue.ToLower()); + } + } + public class InitializeTestMachine : IDisposable { public const string ANCMTestFlagsEnvironmentVariable = "%ANCMTestFlags%"; - public const string ANCMTestFlagsDefaultContext = "AdminAnd64Bit"; - public const string ANCMTestFlagsTestSkipContext = "SkipTest"; - public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivate"; - private const string ANCMTestFlagsUseIISExpressContext = "UseIISExpress"; - - private static bool? _usePrivateAspNetCoreFile = null; - public static bool? UsePrivateAspNetCoreFile - { - get { - // - // By default, we don't use the private AspNetCore.dll that is compiled with this solution. - // In order to use the private file, you should add 'UsePrivateAspNetCoreFile' flag to the Environmnet variable %ANCMTestFlag%. - // - // Set ANCMTestFlag=%ANCMTestFlag%;UsePrivateAspNetCoreFile - // Or - // $Env:ANCMTestFlag=$Env:ANCMTestFlag + ";UsePrivateAspNetCoreFile" - // - if (_usePrivateAspNetCoreFile == null) - { - _usePrivateAspNetCoreFile = false; - var envValue = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); - if (envValue.ToLower().Contains(ANCMTestFlagsUsePrivateAspNetCoreFileContext.ToLower())) - { - TestUtility.LogInformation("PrivateAspNetCoreFile is set"); - _usePrivateAspNetCoreFile = true; - } - else - { - TestUtility.LogInformation("PrivateAspNetCoreFile is not set"); - } - } - return _usePrivateAspNetCoreFile; - } - set - { - _usePrivateAspNetCoreFile = value; - } - } - + 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 = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "IIS Express", PrivateFileName); - public static string IisExpressAspnetcore_X86_path = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"), "IIS Express", 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"); @@ -65,27 +48,225 @@ namespace AspNetCoreModule.Test.Framework private static bool _InitializeTestMachineCompleted = false; private string _setupScriptPath = null; - private bool CheckPerquisiteForANCMTest() + private static bool? _makeCertExeAvailable = null; + public static bool MakeCertExeAvailable { - bool result = true; - TestUtility.LogInformation("CheckPerquisiteForANCMTest(): Environment.Is64BitOperatingSystem: {0}, Environment.Is64BitProcess {1}", Environment.Is64BitOperatingSystem, Environment.Is64BitProcess); - TestUtility.LogInformation("%ANCMTestFlags%: {0}", Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable)); - - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) + get { - TestUtility.LogInformation("CheckPerquisiteForANCMTest() Failed: ANCM test should be started with x64 process mode on 64 bit machine; if you run this test on Visual Studio, you should set X64 first after selecting 'Test -> Test Settings -> Default Process Architecture' menu"); - result = false; + 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 + { + _makeCertExeAvailable = false; + } + } + return (_makeCertExeAvailable == true); } - return result; } + + 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) + { + bool isElevated; + WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); + 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(), ""); + } + } + + _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 System.ApplicationException("IIS server is not installed"); + } + + // Check websocket is installed + if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) + { + TestUtility.LogInformation("Websocket is installed"); + } + else + { + throw new System.ApplicationException("websocket module 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 System.ApplicationException("WWW service can't start"); + } + + // check URLRewrite module exists + if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "rewrite.dll"))) + { + TestUtility.LogInformation("Verified URL Rewrite module installed for IIS server"); + } + else + { + throw new System.ApplicationException("URL Rewrite module is not installed"); + } + + if (IISConfigUtility.ApppHostTemporaryBackupFileExtention == null) + { + throw new System.ApplicationException("Failed to backup applicationhost.config"); + } + } + public InitializeTestMachine() { _referenceCount++; + // This method should be called only one time if (_referenceCount == 1) { - CheckPerquisiteForANCMTest(); - TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Start"); _InitializeTestMachineCompleted = false; @@ -93,94 +274,34 @@ namespace AspNetCoreModule.Test.Framework TestUtility.LogInformation("InitializeTestMachine::Start"); if (Environment.ExpandEnvironmentVariables("%ANCMTEST_DEBUG%").ToLower() == "true") { - System.Diagnostics.Debugger.Launch(); + System.Diagnostics.Debugger.Launch(); } - - // check Makecert.exe exists - try - { - string makecertExeFilePath = TestUtility.GetMakeCertPath(); - TestUtility.RunCommand(makecertExeFilePath, null, true, true); - TestUtility.LogInformation("Verified makecert.exe is available : " + makecertExeFilePath); - } - catch (Exception ex) - { - throw new System.ApplicationException("makecert.exe is not available : " + ex.Message); - } - + + // + // Clean up IISExpress processes + // TestUtility.ResetHelper(ResetHelperMode.KillIISExpress); - // check if we can use IIS server instead of IISExpress - try + // + // Initalize IIS server + // + + if (TestFlags.Enabled(TestFlags.UseFullIIS)) { - IISConfigUtility.IsIISReady = false; - if (IISConfigUtility.IsIISInstalled == true) - { - var envValue = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); - if (envValue.ToLower().Contains(ANCMTestFlagsUseIISExpressContext.ToLower())) - { - TestUtility.LogInformation("UseIISExpress is set"); - throw new System.ApplicationException("'ANCMTestServerType' environment variable is set to 'true'"); - } - else - { - TestUtility.LogInformation("UseIISExpress is not set"); - } - - // check websocket is installed - if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) - { - TestUtility.LogInformation("Websocket is installed"); - } - else - { - throw new System.ApplicationException("websocket module is not installed"); - } - - 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 System.ApplicationException("WWW service can't start"); - } - - // check URLRewrite module exists - if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "rewrite.dll"))) - { - TestUtility.LogInformation("Verified URL Rewrite module installed for IIS server"); - } - else - { - throw new System.ApplicationException("URL Rewrite module is not installed"); - } - - if (IISConfigUtility.ApppHostTemporaryBackupFileExtention == null) - { - throw new System.ApplicationException("Failed to backup applicationhost.config"); - } - IISConfigUtility.IsIISReady = true; - } + InitializeIISServer(); } - catch (Exception ex) - { - RollbackIISApplicationhostConfigFile(); - TestUtility.LogInformation("We will use IISExpress instead of IIS: " + ex.Message); - } - - string siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest"); + + 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)) @@ -208,18 +329,12 @@ namespace AspNetCoreModule.Test.Framework } } - if (InitializeTestMachine.UsePrivateAspNetCoreFile == true) + // + // Intialize Private ANCM files for Full IIS server or IISExpress + // + if (TestFlags.Enabled(TestFlags.UsePrivateANCM)) { PreparePrivateANCMFiles(); - - // update applicationhost.config for IIS server - if (IISConfigUtility.IsIISReady) - { - using (var iisConfig = new IISConfigUtility(ServerType.IIS, null)) - { - iisConfig.AddModule("AspNetCoreModule", FullIisAspnetcore_path, null); - } - } } _InitializeTestMachineCompleted = true; @@ -231,7 +346,7 @@ namespace AspNetCoreModule.Test.Framework if (_InitializeTestMachineCompleted) { break; - } + } else { TestUtility.LogInformation("InitializeTestMachine::InitializeTestMachine() Waiting..."); @@ -312,12 +427,24 @@ namespace AspNetCoreModule.Test.Framework { throw new ApplicationException("aspnetcore.dll is not available; check if there is any build issue!!!"); } - - // create an extra private copy of the private file on IIS directory - if (InitializeTestMachine.UsePrivateAspNetCoreFile == true) - { - bool updateSuccess = false; + // + // 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; @@ -326,28 +453,16 @@ namespace AspNetCoreModule.Test.Framework TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); TestUtility.ResetHelper(ResetHelperMode.StopW3svcStartW3svc); Thread.Sleep(1000); - - string from = Path.Combine(outputPath, "x64", "aspnetcore.dll"); - TestUtility.FileCopy(from, FullIisAspnetcore_path, overWrite:true, ignoreExceptionWhileDeletingExistingFile:false); - TestUtility.FileCopy(from, IisExpressAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); - - // NOTE: schema file can't be overwritten, if there is any schema change, that should be updated manually - from = Path.Combine(outputPath, "x64", "aspnetcore_schema.xml"); - TestUtility.FileCopy(from, FullIisAspnetcoreSchema_path, overWrite: false, ignoreExceptionWhileDeletingExistingFile: false); - TestUtility.FileCopy(from, IisExpressAspnetcoreSchema_path, overWrite: false, ignoreExceptionWhileDeletingExistingFile: false); - + + // Copy private file on Inetsrv directory + TestUtility.FileCopy(Path.Combine(outputPath, "x64", "aspnetcore.dll"), FullIisAspnetcore_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); + if (TestUtility.IsOSAmd64) { - from = Path.Combine(outputPath, "Win32", "aspnetcore.dll"); - TestUtility.FileCopy(from, FullIisAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); - TestUtility.FileCopy(from, IisExpressAspnetcore_X86_path, overWrite: true, ignoreExceptionWhileDeletingExistingFile: false); - - // NOTE: schema file can't be overwritten, if there is any schema change, that should be updated manually - from = Path.Combine(outputPath, "Win32", "aspnetcore_schema.xml"); - TestUtility.FileCopy(from, IisExpressAspnetcoreSchema_X86_path, overWrite: false, ignoreExceptionWhileDeletingExistingFile: false); + + // 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 @@ -363,6 +478,15 @@ namespace AspNetCoreModule.Test.Framework { throw new System.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); + } + } } } diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index 0241427759..8238933fa7 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -287,7 +287,7 @@ namespace AspNetCoreModule.Test.Framework } if (File.Exists(from)) { - if (File.Exists(to) && overWrite == false) + if (File.Exists(to) && !overWrite) { return; } @@ -326,7 +326,7 @@ namespace AspNetCoreModule.Test.Framework if (File.Exists(from)) { - if (File.Exists(to) && overWrite == false) + if (File.Exists(to) && !overWrite) { return; } diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index 5f52097e94..79b4ab65a3 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -130,6 +130,21 @@ namespace AspNetCoreModule.Test.Framework } } + 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; } @@ -138,15 +153,36 @@ namespace AspNetCoreModule.Test.Framework public TestWebSite(IISConfigUtility.AppPoolBitness appPoolBitness, string loggerPrefix = "ANCMTest", bool startIISExpress = true, bool copyAllPublishedFiles = false, bool attachAppVerifier = false) { _appPoolBitness = appPoolBitness; - + // - // Default server type is IISExpress. we, however, should use IIS server instead if IIS server is ready to use. + // Initialize IisServerType // - IisServerType = ServerType.IISExpress; - if (IISConfigUtility.IsIISReady) + 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"); @@ -177,7 +213,7 @@ namespace AspNetCoreModule.Test.Framework { postfix = Path.GetRandomFileName(); siteName = loggerPrefix.Replace(" ", "") + "_" + postfix; - siteRootPath = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", siteName); + siteRootPath = Path.Combine(InitializeTestMachine.TestRootDirectory, siteName); if (!Directory.Exists(siteRootPath)) { break; @@ -199,14 +235,14 @@ namespace AspNetCoreModule.Test.Framework // Currently we use DotnetCore v2.0 // string publishPath = Path.Combine(srcPath, "bin", "Debug", "netcoreapp2.0", "publish"); - string publishPathOutput = Path.Combine(Environment.ExpandEnvironmentVariables("%SystemDrive%") + @"\", "inetpub", "ANCMTest", "publishPathOutput"); + string publishPathOutput = Path.Combine(InitializeTestMachine.TestRootDirectory, "publishPathOutput"); // // Publish aspnetcore app // if (_publishedAspnetCoreApp != true) { - string argumentForDotNet = "publish " + srcPath; + 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); @@ -227,7 +263,7 @@ namespace AspNetCoreModule.Test.Framework { argumentFileName = "AspNetCoreModule.TestSites.Standard.dll"; } - iisConfig.CreateSite(tempSiteName, publishPathOutput, tempId, tempId); + iisConfig.CreateSite(tempSiteName, HostNameBinding, publishPathOutput, tempId, tempId); iisConfig.SetANCMConfig(tempSiteName, "/", "arguments", Path.Combine(publishPathOutput, argumentFileName)); iisConfig.DeleteSite(tempSiteName); } @@ -306,12 +342,26 @@ namespace AspNetCoreModule.Test.Framework } } - if (InitializeTestMachine.UsePrivateAspNetCoreFile == true && IisServerType == ServerType.IISExpress) + if (TestFlags.Enabled(TestFlags.UsePrivateANCM) && IisServerType == ServerType.IISExpress) { - iisConfig.AddModule("AspNetCoreModule", ("%IIS_BIN%\\" + InitializeTestMachine.PrivateFileName), null); + 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, RootAppContext.PhysicalPath, _siteId, TcpPort, appPoolName); + 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); diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 5f764cb801..01c41eb85e 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -12,7 +12,7 @@ namespace AspNetCoreModule.Test public class FunctionalTest : FunctionalTestHelper, IClassFixture { [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.Default)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange)] @@ -23,7 +23,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5)] @@ -36,7 +36,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19, false)] @@ -57,7 +57,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -70,7 +70,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -81,7 +81,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -92,7 +92,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -103,7 +103,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -114,7 +114,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -125,7 +125,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("ANCMTestBar", "bar", "bar", IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -142,7 +142,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -153,7 +153,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -164,7 +164,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.Default)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] @@ -175,7 +175,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.Default)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -186,7 +186,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -197,7 +197,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "00:02:00")] @@ -210,7 +210,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -221,7 +221,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "dotnet.exe", "./")] @@ -234,7 +234,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -247,7 +247,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true, true)] @@ -260,7 +260,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -271,7 +271,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -282,7 +282,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE", "f")] @@ -297,7 +297,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -309,7 +309,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange, false, DoAppVerifierTest_StartUpMode.UseGracefulShutdown, DoAppVerifierTest_ShutDownMode.RecycleAppPool, 1)] @@ -325,7 +325,7 @@ namespace AspNetCoreModule.Test ////////////////////////////////////////////////////////// [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -339,7 +339,7 @@ namespace AspNetCoreModule.Test // NOTE: below test scenarios are not valid for Win7 OS ////////////////////////////////////////////////////////// [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "IIS does not support Websocket on Win7")] @@ -353,7 +353,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestSkipCondition("%ANCMTestFlags%")] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "WAS does not handle private memory limitation with Job object on Win7")] @@ -363,5 +363,5 @@ namespace AspNetCoreModule.Test { return DoRecylingAppPoolTest(appPoolBitness); } - } -} + } +} \ No newline at end of file diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index b2e6f126d7..d66c850f57 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -23,68 +23,31 @@ using Microsoft.AspNetCore.Testing.xunit; namespace AspNetCoreModule.Test { [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class ANCMTestSkipCondition : Attribute, ITestCondition + public class ANCMTestFlags : Attribute, ITestCondition { - private readonly string _environmentVariableName; - public ANCMTestSkipCondition(string environmentVariableName) + private readonly string _attributeValue; + public ANCMTestFlags(string attributeValue) { - _environmentVariableName = environmentVariableName; + _attributeValue = attributeValue.ToString(); } public bool IsMet { get { - bool result = true; - if (_environmentVariableName == InitializeTestMachine.ANCMTestFlagsEnvironmentVariable) + if (InitializeTestMachine.GlobalTestFlags.Contains(TestFlags.SkipTest)) { - var envValue = Environment.ExpandEnvironmentVariables(_environmentVariableName); - if (string.IsNullOrEmpty(envValue)) - { - envValue = InitializeTestMachine.ANCMTestFlagsDefaultContext; - } - else - { - envValue += ";" + InitializeTestMachine.ANCMTestFlagsDefaultContext; - } - - // split tokens with ';' - var tokens = envValue.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string token in tokens) - { - if (token.Equals(InitializeTestMachine.ANCMTestFlagsDefaultContext, StringComparison.InvariantCultureIgnoreCase)) - { - try - { - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) - { - throw new System.InvalidOperationException("this should be started with x64 process mode on 64 bit machine"); - } - - bool isElevated; - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); - if (!isElevated) - { - throw new System.ApplicationException("this should be started as an administrator"); - } - } - catch (Exception ex) - { - AdditionalInfo = ex.Message; - - result = false; - } - } - if (token.Equals(InitializeTestMachine.ANCMTestFlagsTestSkipContext, StringComparison.InvariantCultureIgnoreCase)) - { - AdditionalInfo = InitializeTestMachine.ANCMTestFlagsTestSkipContext + " is set"; - result = false; - } - } + AdditionalInfo = TestFlags.SkipTest + " is set"; + return false; } - return result; + + if (_attributeValue == TestFlags.RequireRunAsAdministrator + && !InitializeTestMachine.GlobalTestFlags.Contains(TestFlags.RunAsAdministrator)) + { + AdditionalInfo = _attributeValue + " is not belong to the given global test context(" + InitializeTestMachine.GlobalTestFlags + ")"; + return false; + } + return true; } } @@ -92,7 +55,7 @@ namespace AspNetCoreModule.Test { get { - return $"Skip condition: {_environmentVariableName}: this test case is skipped becauset {AdditionalInfo}."; + return $"Skip condition: ANCMTestFlags: this test case is skipped becauset {AdditionalInfo}."; } } @@ -998,7 +961,7 @@ namespace AspNetCoreModule.Test } int y = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); - Assert.True(x == y && foundVSJit == false, "worker process is not recycled after 30 seconds"); + Assert.True(x == y && !foundVSJit, "worker process is not recycled after 30 seconds"); string backupPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); string newPocessIdBackendProcess = backupPocessIdBackendProcess; @@ -1322,7 +1285,7 @@ namespace AspNetCoreModule.Test string publicKey = iisConfig.GetCertificatePublicKey(thumbPrintForClientAuthentication, @"Cert:\CurrentUser\My"); bool setPasswordSeperately = false; - if (testSite.IisServerType == ServerType.IISExpress && IISConfigUtility.IsIISInstalled == true) + if (testSite.IisServerType == ServerType.IISExpress) { setPasswordSeperately = true; iisConfig.EnableOneToOneClientCertificateMapping(testSite.SiteName, ".\\" + userName, null, publicKey); @@ -1934,7 +1897,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillWorkerProcess); // cleanup windbg process incase it is still running - if (testResult == false) + if (!testResult) { TestUtility.RunPowershellScript("stop-process -Name windbg -Force -Confirm:$false 2> $null"); } @@ -1984,7 +1947,7 @@ namespace AspNetCoreModule.Test // Verify test result for (int i = 0; i < 3; i++) { - if (DoVerifyDataSentAndReceived(websocketClient) == false) + if (!DoVerifyDataSentAndReceived(websocketClient)) { // retrying after 1 second sleeping Thread.Sleep(1000); @@ -2210,7 +2173,7 @@ namespace AspNetCoreModule.Test result = reader.ReadToEnd(); outputStream.Close(); } - } + } } else { From df673f631c11207af9025a134cc282eba2d1fc6a Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Fri, 29 Sep 2017 16:35:16 -0700 Subject: [PATCH 072/107] Disable tests (#175) --- test/AspNetCoreModule.Test/FunctionalTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 01c41eb85e..7f5c754a5b 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -12,7 +12,7 @@ namespace AspNetCoreModule.Test public class FunctionalTest : FunctionalTestHelper, IClassFixture { [ConditionalTheory] - [ANCMTestFlags(TestFlags.Default)] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange)] @@ -164,7 +164,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.Default)] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] @@ -175,7 +175,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.Default)] + [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] From ea7bc30dd3b1d1216030a190c11e2a06b27d2333 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 3 Oct 2017 14:21:39 -0700 Subject: [PATCH 073/107] Avoid AV if schema change doesn't exist. (#177) --- src/AspNetCore/Src/aspnetcoreconfig.cxx | 2 +- src/AspNetCore/Src/proxymodule.cxx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index 944ff75e28..26ed858cdc 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -241,7 +241,7 @@ ASPNETCORE_CONFIG::Populate( &strHostingModel); if (FAILED(hr)) { - goto Finished; + hr = S_OK; } if (strHostingModel.IsEmpty() || strHostingModel.Equals(L"outofprocess", TRUE)) diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index 502d91f035..9616d8485a 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -85,7 +85,6 @@ CProxyModule::OnExecuteRequestHandler( ASPNETCORE_APPLICATION* pAspNetCoreApplication; ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - // TODO store whether we are inproc or outofproc so we don't need to check the config everytime? if (config->QueryIsOutOfProcess())// case insensitive { m_pHandler = new FORWARDING_HANDLER(pHttpContext); From 746f578c3c01f0141479d5d9f31095ab6aba1935 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Wed, 4 Oct 2017 17:35:11 -0700 Subject: [PATCH 074/107] Panwang/inproc (#174) refactoring the code to support inprocess (step 1) with app_offline and Graceful shutdown support --- src/AspNetCore/AspNetCore.vcxproj | 6 +- src/AspNetCore/Inc/application.h | 130 +-- src/AspNetCore/Inc/applicationmanager.h | 6 +- src/AspNetCore/Inc/aspnetcoreconfig.h | 56 +- src/AspNetCore/Inc/forwardinghandler.h | 3 +- ...reapplication.h => inprocessapplication.h} | 115 ++- src/AspNetCore/Inc/outprocessapplication.h | 40 + src/AspNetCore/Inc/resource.h | 5 +- src/AspNetCore/Src/application.cxx | 100 +- src/AspNetCore/Src/applicationmanager.cxx | 124 ++- src/AspNetCore/Src/aspnetcore_msg.mc | 27 +- src/AspNetCore/Src/aspnetcoreconfig.cxx | 52 +- src/AspNetCore/Src/dllmain.cpp | 1 + src/AspNetCore/Src/forwardinghandler.cxx | 823 ++++++++--------- ...plication.cxx => inprocessapplication.cxx} | 867 +++++++++++------- src/AspNetCore/Src/outprocessapplication.cxx | 121 +++ src/AspNetCore/Src/precomp.hxx | 4 +- src/AspNetCore/Src/proxymodule.cxx | 97 +- src/AspNetCore/Src/serverprocess.cxx | 11 +- 19 files changed, 1454 insertions(+), 1134 deletions(-) rename src/AspNetCore/Inc/{aspnetcoreapplication.h => inprocessapplication.h} (54%) create mode 100644 src/AspNetCore/Inc/outprocessapplication.h rename src/AspNetCore/Src/{aspnetcoreapplication.cxx => inprocessapplication.cxx} (59%) create mode 100644 src/AspNetCore/Src/outprocessapplication.cxx diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index b642d81af6..cce29d5c78 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -154,7 +154,6 @@ - @@ -174,10 +173,13 @@ + + - + + diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h index cecd121b80..3bed6712b8 100644 --- a/src/AspNetCore/Inc/application.h +++ b/src/AspNetCore/Inc/application.h @@ -146,9 +146,9 @@ class APPLICATION { public: - APPLICATION() : m_pProcessManager(NULL), m_pApplicationManager(NULL), m_cRefs(1), - m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL), - m_pAspNetCoreApplication(NULL) + APPLICATION() : m_pApplicationManager(NULL), m_cRefs(1), + m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), + m_pFileWatcherEntry(NULL), m_pConfiguration(NULL) { InitializeSRWLock(&m_srwLock); } @@ -159,92 +159,14 @@ public: return &m_applicationKey; } - VOID - SetAppOfflineFound( - BOOL found - ) - { - m_fAppOfflineFound = found; - } - - BOOL - AppOfflineFound() - { - return m_fAppOfflineFound; - } + virtual + ~APPLICATION(); + virtual HRESULT - GetProcess( - _In_ IHttpContext *context, - _In_ ASPNETCORE_CONFIG *pConfig, - _Out_ SERVER_PROCESS **ppServerProcess - ) - { - return m_pProcessManager->GetProcess( context, pConfig, ppServerProcess ); - } - - HRESULT - GetAspNetCoreApplication( - _In_ ASPNETCORE_CONFIG *pConfig, - _In_ IHttpContext *context, - _Out_ ASPNETCORE_APPLICATION **ppAspNetCoreApplication - ) - { - HRESULT hr = S_OK; - BOOL fLockTaken = FALSE; - ASPNETCORE_APPLICATION *application; - IHttpApplication *pHttpApplication = context->GetApplication(); - - if (m_pAspNetCoreApplication == NULL) - { - AcquireSRWLockExclusive(&m_srwLock); - fLockTaken = TRUE; - - if (m_pAspNetCoreApplication == NULL) - { - application = new ASPNETCORE_APPLICATION(); - if (application == NULL) { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = application->Initialize(pConfig); - if (FAILED(hr)) - { - goto Finished; - } - - // Assign after initialization - m_pAspNetCoreApplication = application; - } - } - else if (pHttpApplication->GetModuleContextContainer()->GetModuleContext(g_pModuleId) == NULL) - { - // This means that we are trying to load a second application - // TODO set a flag saying that the whole app pool is invalid ' - // (including the running application) and return 500 every request. - hr = E_FAIL; - goto Finished; - } - - *ppAspNetCoreApplication = m_pAspNetCoreApplication; - - Finished: - if (fLockTaken) - { - ReleaseSRWLockExclusive(&m_srwLock); - } - - return hr; - } - - HRESULT - Recycle() - { - HRESULT hr = S_OK; - m_pProcessManager->ShutdownAllProcesses(); - return hr; - } + Initialize( + _In_ APPLICATION_MANAGER *pApplicationManager, + _In_ ASPNETCORE_CONFIG *pConfiguration) = 0; VOID ReferenceApplication() const @@ -266,14 +188,15 @@ public: return m_pAppOfflineHtm; } - ~APPLICATION(); + BOOL + AppOfflineFound() + { + return m_fAppOfflineFound; + } - HRESULT - Initialize( - _In_ APPLICATION_MANAGER *pApplicationManager, - _In_ LPCWSTR pszApplication, - _In_ LPCWSTR pszPhysicalPath - ); + virtual + VOID + OnAppOfflineHandleChange() = 0; VOID UpdateAppOfflineFileHandle(); @@ -281,18 +204,29 @@ public: HRESULT StartMonitoringAppOffline(); -private: + ASPNETCORE_CONFIG* + QueryConfig() + { + return m_pConfiguration; + } + + virtual + REQUEST_NOTIFICATION_STATUS + ExecuteRequest( + _In_ IHttpContext* pHttpContext + ) = 0; + +protected: - STRU m_strAppPhysicalPath; mutable LONG m_cRefs; APPLICATION_KEY m_applicationKey; - PROCESS_MANAGER* m_pProcessManager; APPLICATION_MANAGER *m_pApplicationManager; BOOL m_fAppOfflineFound; APP_OFFLINE_HTM *m_pAppOfflineHtm; FILE_WATCHER_ENTRY *m_pFileWatcherEntry; - ASPNETCORE_APPLICATION *m_pAspNetCoreApplication; + ASPNETCORE_CONFIG *m_pConfiguration; SRWLOCK m_srwLock; + }; class APPLICATION_HASH : diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index 1389d651e9..9b413341bd 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -39,6 +39,7 @@ public: HRESULT GetApplication( _In_ IHttpContext* pContext, + _In_ ASPNETCORE_CONFIG* pConfig, _Out_ APPLICATION ** ppApplication ); @@ -121,7 +122,9 @@ private: // // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload // - APPLICATION_MANAGER() : m_pApplicationHash(NULL), m_pFileWatcher(NULL), m_pHttp502ErrorPage(NULL), m_pstrErrorInfo( + APPLICATION_MANAGER() : m_pApplicationHash(NULL), m_pFileWatcher(NULL), + m_pHttp502ErrorPage(NULL), m_hostingModel(HOSTING_UNKNOWN), + m_pstrErrorInfo( " \ \ \ @@ -155,4 +158,5 @@ private: SRWLOCK m_srwLock; HTTP_DATA_CHUNK *m_pHttp502ErrorPage; LPSTR m_pstrErrorInfo; + APP_HOSTING_MODEL m_hostingModel; }; \ No newline at end of file diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h index b2a62695dc..5a4fbf398a 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -38,6 +38,14 @@ extern HTTP_MODULE_ID g_pModuleId; extern IHttpServer * g_pHttpServer; +extern BOOL g_fRecycleProcessCalled; + +enum APP_HOSTING_MODEL +{ + HOSTING_UNKNOWN = 0, + HOSTING_IN_PROCESS, + HOSTING_OUT_PROCESS +}; class ASPNETCORE_CONFIG : IHttpStoredContext { @@ -123,13 +131,13 @@ public: return &m_struApplication; } - STRU* - QueryApplicationFullPath( + STRU* + QueryApplicationFullPath( VOID - ) - { - return &m_struApplicationFullPath; - } + ) + { + return &m_struApplicationFullPath; + } STRU* QueryProcessPath( @@ -139,6 +147,22 @@ public: return &m_struProcessPath; } + APP_HOSTING_MODEL + QueryHostingModel( + VOID + ) + { + return m_hostingModel; + } + + STRU* + QueryHostingModelStr( + VOID + ) + { + return &m_strHostingModel; + } + BOOL QueryStdoutLogEnabled() { @@ -175,19 +199,6 @@ public: return m_fDisableStartUpErrorPage; } - BOOL - QueryIsInProcess() - { - return m_fIsInProcess; - } - - BOOL - QueryIsOutOfProcess() - { - return m_fIsOutOfProcess; - } - - STRU* QueryStdoutLogFile() { @@ -201,7 +212,8 @@ private: // ASPNETCORE_CONFIG(): m_fStdoutLogEnabled( FALSE ), - m_pEnvironmentVariables( NULL ) + m_pEnvironmentVariables( NULL ), + m_hostingModel( HOSTING_UNKNOWN ) { } @@ -220,13 +232,13 @@ private: STRU m_struProcessPath; STRU m_struStdoutLogFile; STRU m_struApplicationFullPath; + STRU m_strHostingModel; BOOL m_fStdoutLogEnabled; BOOL m_fForwardWindowsAuthToken; BOOL m_fDisableStartUpErrorPage; BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; - BOOL m_fIsInProcess; - BOOL m_fIsOutOfProcess; + APP_HOSTING_MODEL m_hostingModel; ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; }; diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h index c66fa5c77f..0213114b18 100644 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -47,7 +47,8 @@ class FORWARDING_HANDLER public: FORWARDING_HANDLER( - __in IHttpContext * pW3Context + __in IHttpContext * pW3Context, + __in APPLICATION * pApplication ); static void * operator new(size_t size); diff --git a/src/AspNetCore/Inc/aspnetcoreapplication.h b/src/AspNetCore/Inc/inprocessapplication.h similarity index 54% rename from src/AspNetCore/Inc/aspnetcoreapplication.h rename to src/AspNetCore/Inc/inprocessapplication.h index 725535ccc8..ca36067d11 100644 --- a/src/AspNetCore/Inc/aspnetcoreapplication.h +++ b/src/AspNetCore/Inc/inprocessapplication.h @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the MIT License. See License.txt in the project root for license information. #pragma once @@ -7,46 +7,34 @@ typedef void(*request_handler_cb) (int error, IHttpContext* pHttpContext, void* typedef REQUEST_NOTIFICATION_STATUS(*PFN_REQUEST_HANDLER) (IHttpContext* pHttpContext, void* pvRequstHandlerContext); typedef BOOL(*PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); -class ASPNETCORE_APPLICATION +#include "application.h" + +class IN_PROCESS_APPLICATION : public APPLICATION { public: + IN_PROCESS_APPLICATION(); - ASPNETCORE_APPLICATION(): - m_pConfiguration(NULL), - m_RequestHandler(NULL) - { - } - - ~ASPNETCORE_APPLICATION() - { - if (m_hThread != NULL) - { - CloseHandle(m_hThread); - m_hThread = NULL; - } - - if (m_pInitalizeEvent != NULL) - { - CloseHandle(m_pInitalizeEvent); - m_pInitalizeEvent = NULL; - } - } + ~IN_PROCESS_APPLICATION(); + __override HRESULT - Initialize( - _In_ ASPNETCORE_CONFIG* pConfig + Initialize(_In_ APPLICATION_MANAGER* pApplicationManager, + _In_ ASPNETCORE_CONFIG* pConfiguration); + + VOID + Recycle( + VOID ); + __override + VOID OnAppOfflineHandleChange(); + + __override REQUEST_NOTIFICATION_STATUS ExecuteRequest( _In_ IHttpContext* pHttpContext ); - VOID - Shutdown( - VOID - ); - VOID SetCallbackHandles( _In_ PFN_REQUEST_HANDLER request_callback, @@ -61,16 +49,13 @@ public: VOID ); - ASPNETCORE_CONFIG* - GetConfig( - VOID - ) - { - return m_pConfiguration; - } + HRESULT + LoadManagedApplication( + VOID + ); static - ASPNETCORE_APPLICATION* + IN_PROCESS_APPLICATION* GetInstance( VOID ) @@ -78,13 +63,11 @@ public: return s_Application; } + private: // Thread executing the .NET Core process HANDLE m_hThread; - // Configuration for this application - ASPNETCORE_CONFIG* m_pConfiguration; - // The request handler callback from managed code PFN_REQUEST_HANDLER m_RequestHandler; VOID* m_RequstHandlerContext; @@ -99,29 +82,41 @@ private: // The exit code of the .NET Core process INT m_ProcessExitCode; - static ASPNETCORE_APPLICATION* s_Application; + BOOL m_fManagedAppLoaded; + BOOL m_fLoadManagedAppError; - static VOID - FindDotNetFolders( - _In_ STRU *pstrPath, - _Out_ std::vector *pvFolders - ); + static IN_PROCESS_APPLICATION* s_Application; - static HRESULT - FindHighestDotNetVersion( - _In_ std::vector vFolders, - _Out_ STRU *pstrResult - ); + 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 - DirectoryExists( - _In_ STRU *pstrPath - ); + GetEnv( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult + ); - 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/AspNetCore/Inc/outprocessapplication.h b/src/AspNetCore/Inc/outprocessapplication.h new file mode 100644 index 0000000000..57e6022c0c --- /dev/null +++ b/src/AspNetCore/Inc/outprocessapplication.h @@ -0,0 +1,40 @@ +// 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 "application.h" + +class OUT_OF_PROCESS_APPLICATION : public APPLICATION +{ +public: + OUT_OF_PROCESS_APPLICATION(); + + ~OUT_OF_PROCESS_APPLICATION(); + + __override + HRESULT Initialize(_In_ APPLICATION_MANAGER* pApplicationManager, + _In_ ASPNETCORE_CONFIG* pConfiguration); + + __override + VOID OnAppOfflineHandleChange(); + + __override + REQUEST_NOTIFICATION_STATUS + ExecuteRequest( + _In_ IHttpContext* pHttpContext + ); + + HRESULT + GetProcess( + _In_ IHttpContext *context, + _Out_ SERVER_PROCESS **ppServerProcess + ) + { + return m_pProcessManager->GetProcess(context, m_pConfiguration, ppServerProcess); + } + +private: + + PROCESS_MANAGER* m_pProcessManager; +}; diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index a7f1b30543..f2982b2a27 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -17,4 +17,7 @@ #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'." \ No newline at end of file diff --git a/src/AspNetCore/Src/application.cxx b/src/AspNetCore/Src/application.cxx index b723162814..2bd5b688b8 100644 --- a/src/AspNetCore/Src/application.cxx +++ b/src/AspNetCore/Src/application.cxx @@ -20,92 +20,16 @@ APPLICATION::~APPLICATION() m_pFileWatcherEntry->StopMonitor(); m_pFileWatcherEntry = NULL; } - - if (m_pProcessManager != NULL) - { - m_pProcessManager->ShutdownAllProcesses(); - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } -} - -HRESULT -APPLICATION::Initialize( - _In_ APPLICATION_MANAGER* pApplicationManager, - _In_ LPCWSTR pszApplication, - _In_ LPCWSTR pszPhysicalPath -) -{ - HRESULT hr = S_OK; - - DBG_ASSERT(pszPhysicalPath != NULL); - DBG_ASSERT(pApplicationManager != NULL); - DBG_ASSERT(pszPhysicalPath != NULL); - m_strAppPhysicalPath.Copy(pszPhysicalPath); - - m_pApplicationManager = pApplicationManager; - - hr = m_applicationKey.Initialize(pszApplication); - if (FAILED(hr)) - { - goto Finished; - } - - 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; - } - } - - if (m_pFileWatcherEntry == NULL) - { - m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pApplicationManager->GetFileWatcher()); - if (m_pFileWatcherEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - } - - UpdateAppOfflineFileHandle(); - -Finished: - - if (FAILED(hr)) - { - if (m_pFileWatcherEntry != NULL) - { - m_pFileWatcherEntry->DereferenceFileWatcherEntry(); - m_pFileWatcherEntry = NULL; - } - - if (m_pProcessManager != NULL) - { - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } - } - - return hr; } HRESULT APPLICATION::StartMonitoringAppOffline() { HRESULT hr = S_OK; - - hr = m_pFileWatcherEntry->Create(m_strAppPhysicalPath.QueryStr(), L"app_offline.htm", this, NULL); - + if (m_pFileWatcherEntry != NULL) + { + hr = m_pFileWatcherEntry->Create(m_pConfiguration->QueryApplicationFullPath()->QueryStr(), L"app_offline.htm", this, NULL); + } return hr; } @@ -113,7 +37,7 @@ VOID APPLICATION::UpdateAppOfflineFileHandle() { STRU strFilePath; - PATH::ConvertPathToFullPath(L".\\app_offline.htm", m_strAppPhysicalPath.QueryStr(), &strFilePath); + PATH::ConvertPathToFullPath(L".\\app_offline.htm", m_pConfiguration->QueryApplicationFullPath()->QueryStr(), &strFilePath); APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL; @@ -124,18 +48,6 @@ APPLICATION::UpdateAppOfflineFileHandle() else { m_fAppOfflineFound = TRUE; - - // - // send shutdown signal - // - - // The reason why we send the shutdown signal before loading the new app_offline file is because we want to make some delay - // before reading the appoffline.htm so that the file change can be done on time. - if (m_pProcessManager != NULL) - { - m_pProcessManager->SendShutdownSignal(); - } - pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); if ( pNewAppOfflineHtm != NULL ) @@ -160,5 +72,7 @@ APPLICATION::UpdateAppOfflineFileHandle() pNewAppOfflineHtm = NULL; } } + + OnAppOfflineHandleChange(); } } \ No newline at end of file diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx index 6d288af7b3..6c2ce6d235 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -8,6 +8,7 @@ APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; HRESULT APPLICATION_MANAGER::GetApplication( _In_ IHttpContext* pContext, + _In_ ASPNETCORE_CONFIG* pConfig, _Out_ APPLICATION ** ppApplication ) { @@ -15,12 +16,17 @@ APPLICATION_MANAGER::GetApplication( APPLICATION *pApplication = NULL; APPLICATION_KEY key; BOOL fExclusiveLock = FALSE; + BOOL fMixedHostingModelError = FALSE; + BOOL fDuplicatedInProcessApp = FALSE; PCWSTR pszApplicationId = NULL; + LPCWSTR apsz[1]; + STACK_STRU ( strEventMsg, 256 ); *ppApplication = NULL; DBG_ASSERT(pContext != NULL); DBG_ASSERT(pContext->GetApplication() != NULL); + pszApplicationId = pContext->GetApplication()->GetApplicationId(); hr = key.Initialize(pszApplicationId); @@ -33,8 +39,28 @@ APPLICATION_MANAGER::GetApplication( if (*ppApplication == NULL) { + switch (pConfig->QueryHostingModel()) + { + case HOSTING_IN_PROCESS: + if (m_pApplicationHash->Count() > 0) + { + // Only one inprocess app is allowed per IIS worker process + fDuplicatedInProcessApp = TRUE; + hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); + goto Finished; + } + pApplication = new IN_PROCESS_APPLICATION(); + break; + + case HOSTING_OUT_PROCESS: + pApplication = new OUT_OF_PROCESS_APPLICATION(); + break; + + default: + hr = E_UNEXPECTED; + goto Finished; + } - pApplication = new APPLICATION(); if (pApplication == NULL) { hr = E_OUTOFMEMORY; @@ -53,18 +79,39 @@ APPLICATION_MANAGER::GetApplication( goto Finished; } - hr = pApplication->Initialize(this, pszApplicationId, pContext->GetApplication()->GetApplicationPhysicalPath()); + // 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 = pApplication->Initialize(this, pConfig); if (FAILED(hr)) { goto Finished; } hr = m_pApplicationHash->InsertRecord( pApplication ); - 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(); + } + ReleaseSRWLockExclusive(&m_srwLock); fExclusiveLock = FALSE; @@ -76,8 +123,10 @@ APPLICATION_MANAGER::GetApplication( Finished: - if (fExclusiveLock == TRUE) + if (fExclusiveLock) + { ReleaseSRWLockExclusive(&m_srwLock); + } if (FAILED(hr)) { @@ -86,12 +135,77 @@ Finished: pApplication->DereferenceApplication(); pApplication = NULL; } + + 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 pszApplication diff --git a/src/AspNetCore/Src/aspnetcore_msg.mc b/src/AspNetCore/Src/aspnetcore_msg.mc index 09cc4615e0..cc3d5d1c9c 100644 --- a/src/AspNetCore/Src/aspnetcore_msg.mc +++ b/src/AspNetCore/Src/aspnetcore_msg.mc @@ -1,6 +1,7 @@ ;/*++ ; -;Copyright (c) 2014 Microsoft Corporation +; Copyright (c) .NET Foundation. All rights reserved. +; Licensed under the MIT License. See License.txt in the project root for license information. ; ;Module Name: ; @@ -67,6 +68,30 @@ 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 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index 26ed858cdc..a63c3bf1ae 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -5,24 +5,37 @@ ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() { - // - // the destructor will be called once IIS decides to recycle the module context (i.e., application) - // - // shutting down core application first - if (ASPNETCORE_APPLICATION::GetInstance() != NULL) { - ASPNETCORE_APPLICATION::GetInstance()->Shutdown(); - } - m_struApplicationFullPath.Reset(); - if (!m_struApplication.IsEmpty()) + if (QueryHostingModel() == HOSTING_IN_PROCESS && + !g_fRecycleProcessCalled && + !g_pHttpServer->IsCommandLineLaunch()) { - APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); + // RecycleProcess can olny be called once + // In case of configuration change for in-process app + // We want notify IIS first to let new request routed to new worker process + g_fRecycleProcessCalled = TRUE; + g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Configuration Change"); } + + m_struApplicationFullPath.Reset(); if (m_pEnvironmentVariables != NULL) { m_pEnvironmentVariables->Clear(); delete m_pEnvironmentVariables; m_pEnvironmentVariables = NULL; } + + if (!m_struApplication.IsEmpty()) + { + APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); + } + + if (QueryHostingModel() == HOSTING_IN_PROCESS && + g_pHttpServer->IsCommandLineLaunch()) + { + // IISExpress scenario, only option is to call exit in case configuration change + // as CLR or application may change + exit(0); + } } HRESULT @@ -124,7 +137,6 @@ ASPNETCORE_CONFIG::Populate( STRU strEnvValue; STRU strExpandedEnvValue; STRU strApplicationFullPath; - STRU strHostingModel; IAppHostAdminManager *pAdminManager = NULL; IAppHostElement *pAspNetCoreElement = NULL; IAppHostElement *pWindowsAuthenticationElement = NULL; @@ -238,19 +250,27 @@ ASPNETCORE_CONFIG::Populate( hr = GetElementStringProperty(pAspNetCoreElement, CS_ASPNETCORE_HOSTING_MODEL, - &strHostingModel); + &m_strHostingModel); if (FAILED(hr)) { + // Swallow this error for backward compatability + // Use default behavior for empty string hr = S_OK; } - if (strHostingModel.IsEmpty() || strHostingModel.Equals(L"outofprocess", TRUE)) + if (m_strHostingModel.IsEmpty() || m_strHostingModel.Equals(L"outofprocess", TRUE)) { - m_fIsOutOfProcess = TRUE; + m_hostingModel = HOSTING_OUT_PROCESS; } - else if (strHostingModel.Equals(L"inprocess", TRUE)) + else if (m_strHostingModel.Equals(L"inprocess", TRUE)) { - m_fIsInProcess = TRUE; + m_hostingModel = HOSTING_IN_PROCESS; + } + else + { + // block unknown hosting value + hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + goto Finished; } hr = GetElementStringProperty(pAspNetCoreElement, diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index e510dc535d..8de9227579 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -15,6 +15,7 @@ HTTP_MODULE_ID g_pModuleId = NULL; IHttpServer * g_pHttpServer = NULL; BOOL g_fAsyncDisconnectAvailable = FALSE; BOOL g_fWinHttpNonBlockingCallbackAvailable = FALSE; +BOOL g_fRecycleProcessCalled = FALSE; PCWSTR g_pszModuleName = NULL; HINSTANCE g_hModule; HINSTANCE g_hWinHttpModule; diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 665a82d8be..6023449b9f 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -23,10 +23,12 @@ TRACE_LOG * FORWARDING_HANDLER::sm_pTraceLog = NULL; PROTOCOL_CONFIG FORWARDING_HANDLER::sm_ProtocolConfig; FORWARDING_HANDLER::FORWARDING_HANDLER( - __in IHttpContext * pW3Context + __in IHttpContext * pW3Context, + __in APPLICATION * pApplication ) : m_Signature(FORWARDING_HANDLER_SIGNATURE), m_cRefs(1), m_pW3Context(pW3Context), +m_pApplication(pApplication), m_pChildRequestContext(NULL), m_hRequest(NULL), m_fHandleClosedDueToClient(FALSE), @@ -48,7 +50,6 @@ m_cchHeaders(0), m_fWebSocketEnabled(FALSE), m_cContentLength(0), m_pWebSocket(NULL), -m_pApplication(NULL), m_pAppOfflineHtm(NULL), m_fErrorHandled(FALSE), m_fWebSocketUpgrade(FALSE), @@ -120,13 +121,13 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( m_hRequest = NULL; } - if(m_pApplication != NULL) + if (m_pApplication != NULL) { m_pApplication->DereferenceApplication(); m_pApplication = NULL; } - if(m_pAppOfflineHtm != NULL) + if (m_pAppOfflineHtm != NULL) { m_pAppOfflineHtm->DereferenceAppOfflineHtm(); m_pAppOfflineHtm = NULL; @@ -262,7 +263,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( ((*pchEndofHeaderValue == ' ') || (*pchEndofHeaderValue == '\r')); pchEndofHeaderValue--) - {} + { + } // // Copy the status description @@ -324,7 +326,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( (pchEndofHeaderName >= pszHeaders + index) && (*pchEndofHeaderName == ' '); pchEndofHeaderName--) - {} + { + } pchEndofHeaderName++; @@ -344,7 +347,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( for (index = static_cast(pchColon - pszHeaders) + 1; pszHeaders[index] == ' '; index++) - {} + { + } // @@ -355,7 +359,8 @@ FORWARDING_HANDLER::SetStatusAndHeaders( ((*pchEndofHeaderValue == ' ') || (*pchEndofHeaderValue == '\r')); pchEndofHeaderValue--) - {} + { + } pchEndofHeaderValue++; @@ -1042,9 +1047,11 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( USHORT cchHostName = 0; BOOL fSecure = FALSE; BOOL fProcessStartFailure = FALSE; + BOOL fInternalError = FALSE; HTTP_DATA_CHUNK *pDataChunk = NULL; DBG_ASSERT(m_RequestStatus == FORWARDER_START); + DBG_ASSERT(m_pApplication); // // Take a reference so that object does not go away as a result of @@ -1052,37 +1059,11 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( // ReferenceForwardingHandler(); - m_pszOriginalHostHeader = pRequest->GetHeader(HttpHeaderHost, &cchHostName); - - // read per site aspNetCore configuration. - hr = ASPNETCORE_CONFIG::GetConfig(m_pW3Context, &pAspNetCoreConfig); - if (FAILED(hr)) - { - // configuration error. - goto Failure; - } - - // override Protocol related config from aspNetCore config - pProtocol->OverrideConfig(pAspNetCoreConfig); - - // - // parse original url - // - if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, - &fSecure, - &strDestination, - &strUrl))) - { - goto Failure; - } - - if (FAILED(hr = PATH::EscapeAbsPath(pRequest, &struEscapedUrl))) - { - goto Failure; - } - - m_fDoReverseRewriteHeaders = pProtocol->QueryReverseRewriteHeaders(); + // get application configuation + pAspNetCoreConfig = m_pApplication->QueryConfig(); + + // check connection IHttpConnection * pClientConnection = m_pW3Context->GetConnection(); if (pClientConnection == NULL || !pClientConnection->IsConnected()) @@ -1091,26 +1072,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( goto Failure; } - m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); - - // - // Find the application that is supposed to service this request. - // - - pApplicationManager = APPLICATION_MANAGER::GetInstance(); - if (pApplicationManager == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } - - hr = pApplicationManager->GetApplication(m_pW3Context, - &m_pApplication); - if (FAILED(hr)) - { - goto Failure; - } - + // check offline m_pAppOfflineHtm = m_pApplication->QueryAppOfflineHtm(); if (m_pAppOfflineHtm != NULL) { @@ -1176,196 +1138,200 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( goto Finished; } - hr = m_pApplication->GetProcess(m_pW3Context, - pAspNetCoreConfig, - &pServerProcess); - if (FAILED(hr)) + switch (pAspNetCoreConfig->QueryHostingModel()) { - fProcessStartFailure = TRUE; - goto Failure; - } - - if (pServerProcess == NULL) + case HOSTING_IN_PROCESS: { - hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); - goto Failure; - } + // Allow reading and writing to simultaneously + ((IHttpContext3*)m_pW3Context)->EnableFullDuplex(); - if (pServerProcess->QueryWinHttpConnection() == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE); - goto Failure; - } + // Disable response buffering by default, we'll do a write behind buffering in managed code + ((IHttpResponse2*)m_pW3Context->GetResponse())->DisableBuffering(); - hConnect = pServerProcess->QueryWinHttpConnection()->QueryHandle(); - - // - // 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) + hr = ((IN_PROCESS_APPLICATION*)m_pApplication)->LoadManagedApplication(); + if (FAILED(hr)) { - m_fWebSocketEnabled = TRUE; + fInternalError = TRUE; + goto Failure; } + return m_pApplication->ExecuteRequest(m_pW3Context); } - - hr = CreateWinHttpRequest(pRequest, - pProtocol, - hConnect, - &struEscapedUrl, - pAspNetCoreConfig, - pServerProcess); - - if (FAILED(hr)) + case HOSTING_OUT_PROCESS: { - goto Failure; - } + m_pszOriginalHostHeader = pRequest->GetHeader(HttpHeaderHost, &cchHostName); - // - // Register for connection disconnect notification with http.sys. - // N.B. This feature is currently disabled due to synchronization conditions. - // + // override Protocol related config from aspNetCore config + pProtocol->OverrideConfig(pAspNetCoreConfig); - // disabling this disconnect notification as it causes synchronization/AV issue - // will re-enable it in the future after investigation + // + // parse original url + // + if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + &fSecure, + &strDestination, + &strUrl))) + { + goto Failure; + } - //if (g_fAsyncDisconnectAvailable) - //{ - // m_pDisconnect = static_cast( - // pClientConnection->GetModuleContextContainer()-> - // GetConnectionModuleContext(g_pModuleId)); - // if (m_pDisconnect == NULL) - // { - // m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT; - // if (m_pDisconnect == NULL) - // { - // hr = E_OUTOFMEMORY; - // goto Failure; - // } + if (FAILED(hr = PATH::EscapeAbsPath(pRequest, &struEscapedUrl))) + { + goto Failure; + } - // hr = pClientConnection->GetModuleContextContainer()-> - // SetConnectionModuleContext(m_pDisconnect, - // g_pModuleId); - // DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); - // if (FAILED(hr)) - // { - // goto Failure; - // } - // } + m_fDoReverseRewriteHeaders = pProtocol->QueryReverseRewriteHeaders(); + m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); + hr = ((OUT_OF_PROCESS_APPLICATION*)m_pApplication)->GetProcess( + m_pW3Context, + &pServerProcess); + if (FAILED(hr)) + { + fProcessStartFailure = TRUE; + goto Failure; + } - // // - // // Issue: There is a window of opportunity to miss on the disconnect - // // notification if it happens before the SetHandler() call is made. - // // It is suboptimal for performance, but should functionally be OK. - // // + if (pServerProcess == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Failure; + } - // m_pDisconnect->SetHandler(this); - //} + if (pServerProcess->QueryWinHttpConnection() == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE); + goto Failure; + } - // - // Read lock on the WinHTTP handle to protect from server closing - // the handle while it is in use. - // + hConnect = pServerProcess->QueryWinHttpConnection()->QueryHandle(); - AcquireSRWLockShared(&m_RequestLock); - fRequestLocked = TRUE; + // + // Mark request as websocket if upgrade header is present. + // - if (m_hRequest == NULL) - { - hr = HRESULT_FROM_WIN32(WSAECONNRESET); - goto Failure; - } + if (g_fWebSocketSupported) + { + USHORT cchHeader = 0; + PCSTR pszWebSocketHeader = pRequest->GetHeader("Upgrade", &cchHeader); - // - // Begins normal request handling. Send request to server. - // + if (cchHeader == 9 && _stricmp(pszWebSocketHeader, "websocket") == 0) + { + m_fWebSocketEnabled = TRUE; + } + } - m_RequestStatus = FORWARDER_SENDING_REQUEST; + hr = CreateWinHttpRequest(pRequest, + pProtocol, + hConnect, + &struEscapedUrl, + pAspNetCoreConfig, + pServerProcess); - // - // Calculate the bytes to receive from the content length. - // + if (FAILED(hr)) + { + goto Failure; + } - DWORD cbContentLength = 0; - PCSTR pszContentLength = pRequest->GetHeader(HttpHeaderContentLength); - if (pszContentLength != NULL) - { - cbContentLength = m_BytesToReceive = atol(pszContentLength); - if (m_BytesToReceive == INFINITE) + AcquireSRWLockShared(&m_RequestLock); + fRequestLocked = TRUE; + + if (m_hRequest == NULL) { 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. + // Begins normal request handling. Send request to server. // - if (!WinHttpSetOption(m_hRequest, - WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, + 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; + + // + // 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); + + // + // WinHttpSendRequest can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + if (!WinHttpSendRequest(m_hRequest, + m_pszHeaders, + m_cchHeaders, NULL, - 0)) + 0, + cbContentLength, + reinterpret_cast(static_cast(this)))) { hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); + DereferenceForwardingHandler(); + 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; + } - m_cchLastSend = m_cchHeaders; - - // - // 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); - - // - // WinHttpSendRequest can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - 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"); - DereferenceForwardingHandler(); + default: + hr = E_UNEXPECTED; + fInternalError = TRUE; 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: - // // Reset status for consistency. // @@ -1384,6 +1350,12 @@ Failure: pResponse->SetStatus(400, "Bad Request", 0, hr); goto Finished; } + else if (fInternalError) + { + // set a special sub error code indicating loading application error + pResponse->SetStatus(500, "Internal Server Error", 19, hr); + goto Finished; + } else if (fProcessStartFailure && !pAspNetCoreConfig->QueryDisableStartUpErrorPage()) { PCSTR pszANCMHeader; @@ -1476,7 +1448,6 @@ Failure: } Finished: - if (pConnection != NULL) { pConnection->DereferenceForwarderConnection(); @@ -1511,7 +1482,6 @@ Finished: // // Do not use this object after dereferencing it, it may be gone. // - return retVal; } @@ -1564,239 +1534,242 @@ REQUEST_NOTIFICATION_STATUS reinterpret_cast(static_cast(hrCompletionStatus))); } - // - // Take a reference so that object does not go away as a result of - // async completion. - // - // Read lock on the WinHTTP handle to protect from server closing - // the handle while it is in use. - // - ReferenceForwardingHandler(); - - DBG_ASSERT(m_pW3Context != NULL); - __analysis_assume(m_pW3Context != NULL); - - // - // OnAsyncCompletion can be called on a Winhttp io completion thread. - // Hence we need to check the TLS before we acquire the shared lock. - // - - if (TlsGetValue(g_dwTlsIndex) != this) + if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_OUT_PROCESS) { - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); - AcquireSRWLockShared(&m_RequestLock); - TlsSetValue(g_dwTlsIndex, this); - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + // + // Take a reference so that object does not go away as a result of + // async completion. + // + // Read lock on the WinHTTP handle to protect from server closing + // the handle while it is in use. + // + ReferenceForwardingHandler(); - fLocked = TRUE; - } + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); - if (m_hRequest == NULL) - { - if (m_RequestStatus == FORWARDER_DONE && m_fFinishRequest) + // + // OnAsyncCompletion can be called on a Winhttp io completion thread. + // Hence we need to check the TLS before we acquire the shared lock. + // + + if (TlsGetValue(g_dwTlsIndex) != this) { - if (m_fHasError) + 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) + { + if (m_RequestStatus == FORWARDER_DONE && m_fFinishRequest) { - retVal = RQ_NOTIFICATION_FINISH_REQUEST; + if (m_fHasError) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + } + goto Finished; } + + fClientError = m_fHandleClosedDueToClient; + goto Failure; + } + else if (m_RequestStatus == 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 + // + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + fClosed = WinHttpCloseHandle(m_hRequest); + DBG_ASSERT(fClosed); + if (fClosed) + { + m_fWebSocketUpgrade = TRUE; + m_hRequest = NULL; + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + } + else if (m_RequestStatus == FORWARDER_RESET_CONNECTION) + { + hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); + 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_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; } - fClientError = m_fHandleClosedDueToClient; - goto Failure; - } - else if (m_RequestStatus == 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. + // Either OnReceivingResponse or OnSendingRequest initiated an + // async WinHTTP operation, release this thread meanwhile, + // OnWinHttpCompletion method should resume the work by posting an IIS completion. // - - 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 - // - WinHttpSetStatusCallback(m_hRequest, - FORWARDING_HANDLER::OnWinHttpCompletion, - WINHTTP_CALLBACK_FLAG_HANDLES, - NULL); - fClosed = WinHttpCloseHandle(m_hRequest); - DBG_ASSERT(fClosed); - if (fClosed) - { - m_fWebSocketUpgrade = TRUE; - m_hRequest = NULL; - } - else - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; - } retVal = RQ_NOTIFICATION_PENDING; goto Finished; - } - else if (m_RequestStatus == FORWARDER_RESET_CONNECTION) - { - hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); - 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_RECEIVING_RESPONSE: + Failure: // - // 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. + // Reset status for consistency. // - if (FAILED(hrCompletionStatus)) + m_RequestStatus = FORWARDER_DONE; + m_fHasError = TRUE; + // + // Do the right thing based on where the error originated from. + // + IHttpResponse *pResponse = m_pW3Context->GetResponse(); + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + // double check to set right status code + if (!m_pW3Context->GetConnection()->IsConnected()) { - hr = hrCompletionStatus; fClientError = TRUE; - goto Failure; } - hr = OnReceivingResponse(); - if (FAILED(hr)) + if (fClientError) { - 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: - - // - // Reset status for consistency. - // - m_RequestStatus = FORWARDER_DONE; - m_fHasError = TRUE; - // - // Do the right thing based on where the error originated from. - // - IHttpResponse *pResponse = m_pW3Context->GetResponse(); - pResponse->DisableKernelCache(); - pResponse->GetRawHttpResponse()->EntityChunkCount = 0; - - // double check to set right status code - if (!m_pW3Context->GetConnection()->IsConnected()) - { - fClientError = TRUE; - } - - if (fClientError) - { - if (!m_fResponseHeadersReceivedAndSet) - { - pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); + 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 { - // - // Response headers from origin server were - // already received and set for the current response. - // Honor the response status. - // - } - } - else - { - STACK_STRU(strDescription, 128); + STACK_STRU(strDescription, 128); - pResponse->SetStatus(502, "Bad Gateway", 3, hr); + pResponse->SetStatus(502, "Bad Gateway", 3, hr); - if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && - hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) - { + 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); - } - else - { - LoadString(g_hModule, - IDS_SERVER_ERROR, - strDescription.QueryStr(), - strDescription.QuerySizeCCH()); - } - (VOID)strDescription.SyncWithBuffer(); - if (strDescription.QueryCCH() != 0) - { - pResponse->SetErrorDescription( - strDescription.QueryStr(), - strDescription.QueryCCH(), - FALSE); + FormatMessage( + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + (VOID)strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + pResponse->ResetConnection(); + goto Finished; + } } - if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + // + // 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) { - pResponse->ResetConnection(); - goto Finished; + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } } + retVal = RQ_NOTIFICATION_PENDING; } - // - // 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; - } - } - retVal = RQ_NOTIFICATION_PENDING; - Finished: if (fLocked) @@ -1849,12 +1822,12 @@ FORWARDING_HANDLER::OnSendingRequest( m_BytesToReceive = 0; m_cchLastSend = 5; // "0\r\n\r\n" - // - // WinHttpWriteData can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // + // + // 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", @@ -2134,7 +2107,7 @@ None #endif // DEBUG fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); - if (!fEndRequest) + if (!fEndRequest) { if (!m_pW3Context->GetConnection()->IsConnected()) { @@ -2169,7 +2142,7 @@ None fDerefForwardingHandler = FALSE; fAnotherCompletionExpected = TRUE; - if(m_pWebSocket == NULL) + if (m_pWebSocket == NULL) { goto Finished; } @@ -2194,8 +2167,8 @@ None case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: m_pWebSocket->OnWinHttpIoError( - (WINHTTP_WEB_SOCKET_ASYNC_RESULT*)lpvStatusInformation - ); + (WINHTTP_WEB_SOCKET_ASYNC_RESULT*)lpvStatusInformation + ); break; } goto Finished; @@ -2255,7 +2228,7 @@ None case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: if (m_RequestStatus != FORWARDER_DONE) - { + { hr = ERROR_CONNECTION_ABORTED; fClientError = m_fHandleClosedDueToClient; } @@ -2400,7 +2373,7 @@ Finished: FORWARDING_HANDLER::OnWinHttpCompletion, WINHTTP_CALLBACK_FLAG_HANDLES, NULL); - if(WinHttpCloseHandle(m_hRequest)) + if (WinHttpCloseHandle(m_hRequest)) { m_hRequest = NULL; } @@ -2414,7 +2387,7 @@ Finished: m_pWebSocket->TerminateRequest(); } - if(fEndRequest) + if (fEndRequest) { // only postCompletion after WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING // so that no further WinHttp callback will be called @@ -2432,7 +2405,7 @@ Finished: // IndicateCompletion to allow cleaning up the TLS before thread reuse. // - m_pW3Context->PostCompletion(0); + m_pW3Context->PostCompletion(0); // // No code executed after posting the completion. @@ -3111,7 +3084,7 @@ FORWARDING_HANDLER::TerminateRequest( FORWARDING_HANDLER::OnWinHttpCompletion, WINHTTP_CALLBACK_FLAG_HANDLES, NULL); - if (WinHttpCloseHandle(m_hRequest)) + if (WinHttpCloseHandle(m_hRequest)) { m_hRequest = NULL; } diff --git a/src/AspNetCore/Src/aspnetcoreapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx similarity index 59% rename from src/AspNetCore/Src/aspnetcoreapplication.cxx rename to src/AspNetCore/Src/inprocessapplication.cxx index 3e2f727f5a..f9a2f65ff6 100644 --- a/src/AspNetCore/Src/aspnetcoreapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -1,11 +1,14 @@ +// 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" -#include "fx_ver.h" #include typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); +// // Initialization export - +// EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID register_callbacks( @@ -15,7 +18,7 @@ register_callbacks( _In_ VOID* pvShutdownHandlerContext ) { - ASPNETCORE_APPLICATION::GetInstance()->SetCallbackHandles( + IN_PROCESS_APPLICATION::GetInstance()->SetCallbackHandles( request_handler, shutdown_handler, pvRequstHandlerContext, @@ -81,11 +84,21 @@ http_get_completion_info( *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 +// EXTERN_C __MIDL_DECLSPEC_DLLEXPORT BSTR // TODO probably should make this a wide string http_get_application_full_path() { - return SysAllocString(ASPNETCORE_APPLICATION::GetInstance()->GetConfig()->QueryApplicationFullPath()->QueryStr()); + LPWSTR pwzPath = NULL; + IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); + if(pApplication != NULL) + { + pwzPath = pApplication->QueryConfig()->QueryApplicationFullPath()->QueryStr(); + } + return SysAllocString(pwzPath); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT @@ -176,311 +189,45 @@ http_flush_response_bytes( pfCompletionExpected); return hr; } +// End of export -// Thread execution callback -static -VOID -ExecuteAspNetCoreProcess( - _In_ LPVOID pContext -) + +IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; + +IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(): + m_fManagedAppLoaded ( FALSE ), m_fLoadManagedAppError ( FALSE ) { - HRESULT hr; - ASPNETCORE_APPLICATION *pApplication = (ASPNETCORE_APPLICATION*)pContext; - - hr = pApplication->ExecuteApplication(); - if (hr != S_OK) - { - // TODO log error - } } -ASPNETCORE_APPLICATION* -ASPNETCORE_APPLICATION::s_Application = NULL; -VOID -ASPNETCORE_APPLICATION::SetCallbackHandles( - _In_ PFN_REQUEST_HANDLER request_handler, - _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, - _In_ VOID* pvRequstHandlerContext, - _In_ VOID* pvShutdownHandlerContext -) +IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() { - m_RequestHandler = request_handler; - m_RequstHandlerContext = pvRequstHandlerContext; - m_ShutdownHandler = shutdown_handler; - m_ShutdownHandlerContext = pvShutdownHandlerContext; - - // Initialization complete - SetEvent(m_pInitalizeEvent); -} - -HRESULT -ASPNETCORE_APPLICATION::Initialize( - _In_ ASPNETCORE_CONFIG * pConfig -) -{ - HRESULT hr = S_OK; - - DWORD dwTimeout; - DWORD dwResult; - DBG_ASSERT(pConfig != NULL); - - m_pConfiguration = pConfig; - - m_pInitalizeEvent = CreateEvent( - NULL, // default security attributes - TRUE, // manual reset event - FALSE, // not set - NULL); // name - - if (m_pInitalizeEvent == NULL) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - - 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) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - - // If the debugger is attached, never timeout - if (IsDebuggerPresent()) - { - dwTimeout = INFINITE; - } - else - { - dwTimeout = 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) - { - return HRESULT_FROM_WIN32(dwResult); - } - else if (dwResult == WAIT_FAILED) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - - dwResult = WaitForSingleObject(m_hThread, 0); - - // The thread ended it means that something failed - if (dwResult == WAIT_OBJECT_0) - { - return HRESULT_FROM_WIN32(dwResult); - } - else if (dwResult == WAIT_FAILED) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - - return S_OK; -} - -HRESULT -ASPNETCORE_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; - - // Get the System PATH value. - if (!GetEnv(L"PATH", &strFullPath)) - { - goto Failed; - } - - // Split on ';', checking to see if dotnet.exe exists in any folders. - pszDotnetExeLocation = wcstok_s(strFullPath.QueryStr(), L";", &strDelimeterContext); - - while (pszDotnetExeLocation != NULL) - { - dwCopyLength = 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 Failed; - } - - hr = strDotnetFolderLocation.Copy(pszDotnetExeLocation, dwCopyLength); - if (FAILED(hr)) - { - goto Failed; - } - - if (dwCopyLength > 0 && pszDotnetExeLocation[dwCopyLength - 1] != L'\\') - { - hr = strDotnetExeLocation.Append(L"\\"); - if (FAILED(hr)) - { - goto Failed; - } - } - - hr = strDotnetExeLocation.Append(pszDotnetExeString); - if (FAILED(hr)) - { - goto Failed; - } - - if (PathFileExists(strDotnetExeLocation.QueryStr())) - { - // means we found the folder with a dotnet.exe inside of it. - break; - } - pszDotnetExeLocation = wcstok_s(NULL, L";", &strDelimeterContext); - } - - hr = strDotnetFolderLocation.Append(L"\\host\\fxr"); - if (FAILED(hr)) - { - goto Failed; - } - - if (!DirectoryExists(&strDotnetFolderLocation)) - { - goto Failed; - } - - // Find all folders under host\\fxr\\ for version numbers. - hr = strHostFxrSearchExpression.Copy(strDotnetFolderLocation); - if (FAILED(hr)) - { - goto Failed; - } - - hr = strHostFxrSearchExpression.Append(L"\\*"); - if (FAILED(hr)) - { - goto Failed; - } - - // As we use the logic from core-setup, we are opting to use std here. - // TODO remove all uses of std? - FindDotNetFolders(&strHostFxrSearchExpression, &vVersionFolders); - - if (vVersionFolders.size() == 0) - { - goto Failed; - } - - hr = FindHighestDotNetVersion(vVersionFolders, &strHighestDotnetVersion); - if (FAILED(hr)) - { - goto Failed; - } - hr = strDotnetFolderLocation.Append(L"\\"); - if (FAILED(hr)) - { - goto Failed; - } - - hr = strDotnetFolderLocation.Append(strHighestDotnetVersion.QueryStr()); - if (FAILED(hr)) - { - goto Failed; - - } - - hr = strDotnetFolderLocation.Append(L"\\hostfxr.dll"); - if (FAILED(hr)) - { - goto Failed; - } - - hModule = LoadLibraryW(strDotnetFolderLocation.QueryStr()); - - if (hModule == NULL) - { - // .NET Core not installed (we can log a more detailed error message here) - goto Failed; - } - - // Get the entry point for main - pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main"); - if (pProc == NULL) { - goto Failed; - } - - // The first argument is mostly ignored - hr = strDotnetExeLocation.Append(pszDotnetExeString); - if (FAILED(hr)) - { - goto Failed; - } - - argv[0] = strDotnetExeLocation.QueryStr(); - PATH::ConvertPathToFullPath(m_pConfiguration->QueryArguments()->QueryStr(), m_pConfiguration->QueryApplicationFullPath()->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; - - m_ProcessExitCode = pProc(2, argv); - if (m_ProcessExitCode != 0) - { - // TODO error - } - - return hr; -Failed: - // TODO log any errors - return hr; + Recycle(); } BOOL -ASPNETCORE_APPLICATION::GetEnv( +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; + PWSTR pszBuffer = NULL; BOOL fSucceeded = FALSE; if (pszEnvironmentVariable == NULL) @@ -513,15 +260,15 @@ Finished: } VOID -ASPNETCORE_APPLICATION::FindDotNetFolders( - _In_ STRU *pstrPath, +IN_PROCESS_APPLICATION::FindDotNetFolders( + _In_ PCWSTR pszPath, _Out_ std::vector *pvFolders ) { HANDLE handle = NULL; WIN32_FIND_DATAW data = { 0 }; - handle = FindFirstFileExW(pstrPath->QueryStr(), FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); + handle = FindFirstFileExW(pszPath, FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); if (handle == INVALID_HANDLE_VALUE) { return; @@ -536,8 +283,489 @@ ASPNETCORE_APPLICATION::FindDotNetFolders( FindClose(handle); } +VOID +IN_PROCESS_APPLICATION::SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + m_RequestHandler = request_handler; + m_RequstHandlerContext = pvRequstHandlerContext; + m_ShutdownHandler = shutdown_handler; + m_ShutdownHandlerContext = pvShutdownHandlerContext; + + // Initialization complete + SetEvent(m_pInitalizeEvent); +} + +// +// Initialize is guarded by a lock inside APPLICATION_MANAGER::GetApplication +// It ensures only one application will be initialized and singleton +// Error wuill happen if you call Initialized outside APPLICATION_MANAGER::GetApplication +// +__override HRESULT -ASPNETCORE_APPLICATION::FindHighestDotNetVersion( +IN_PROCESS_APPLICATION::Initialize( + _In_ APPLICATION_MANAGER* pApplicationManager, + _In_ ASPNETCORE_CONFIG* pConfiguration +) +{ + HRESULT hr = S_OK; + DBG_ASSERT(pApplicationManager != NULL); + DBG_ASSERT(pConfiguration != NULL); + + m_pConfiguration = pConfiguration; + m_pApplicationManager = pApplicationManager; + hr = m_applicationKey.Initialize(pConfiguration->QueryApplicationPath()->QueryStr()); + if (FAILED(hr)) + { + goto Finished; + } + + // check app_offline + UpdateAppOfflineFileHandle(); + + if (m_pFileWatcherEntry == NULL) + { + m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(m_pApplicationManager->GetFileWatcher()); + if (m_pFileWatcherEntry == NULL) + { + hr = E_OUTOFMEMORY; + 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()); + goto Finished; + } + +Finished: + return hr; +} + +HRESULT +IN_PROCESS_APPLICATION::LoadManagedApplication() +{ + 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; + } + + 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; + } + + // If the debugger is attached, never timeout + if (IsDebuggerPresent()) + { + dwTimeout = INFINITE; + } + else + { + dwTimeout = m_pConfiguration->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) + { + // do we need kill the backend thread + 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 ? + + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, + m_pConfiguration->QueryApplicationPath()->QueryStr(), + m_pConfiguration->QueryApplicationFullPath()->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; +} + +VOID +IN_PROCESS_APPLICATION::Recycle( + VOID +) +{ + DWORD dwThreadStatus = 0; + DWORD dwTimeout = m_pConfiguration->QueryShutdownTimeLimitInMS(); + + AcquireSRWLockExclusive(&m_srwLock); + + if (!g_pHttpServer->IsCommandLineLaunch() && !g_fRecycleProcessCalled) + { + // IIS scenario. + // notify IIS first so that new request will be routed to new worker process + g_fRecycleProcessCalled = TRUE; + g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand"); + } + // 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 (g_pHttpServer->IsCommandLineLaunch()) + { + // IISExpress scenario + // Can only call exit to terminate current process + exit(0); + } +} + +VOID +IN_PROCESS_APPLICATION::OnAppOfflineHandleChange() +{ + // only recycle the worker process after managed app was loaded + // app_offline scenario managed application has not been loaded yet + if (m_fManagedAppLoaded || m_fLoadManagedAppError) + { + Recycle(); + + } +} + +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_APPLICATION::ExecuteRequest( + _In_ IHttpContext* pHttpContext +) +{ + if (m_RequestHandler != NULL) + { + return m_RequestHandler(pHttpContext, m_RequstHandlerContext); + } + + // + // return error as the application did not register callback + // + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); + return RQ_NOTIFICATION_FINISH_REQUEST; +} + +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 = 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 + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Finished; + } + + argv[0] = strDotnetExeLocation.QueryStr(); + PATH::ConvertPathToFullPath(m_pConfiguration->QueryArguments()->QueryStr(), + m_pConfiguration->QueryApplicationFullPath()->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; + + m_ProcessExitCode = pProc(2, argv); + if (m_ProcessExitCode != 0) + { + // TODO error + } + +Finished: + // TODO log any errors + 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::FindHighestDotNetVersion( _In_ std::vector vFolders, _Out_ STRU *pstrResult ) @@ -558,44 +786,3 @@ ASPNETCORE_APPLICATION::FindHighestDotNetVersion( // we check FAILED(hr) outside of function return hr; } - -BOOL -ASPNETCORE_APPLICATION::DirectoryExists( - _In_ STRU *pstrPath -) -{ - WIN32_FILE_ATTRIBUTE_DATA data; - - if (pstrPath->IsEmpty()) - { - return false; - } - - return GetFileAttributesExW(pstrPath->QueryStr(), GetFileExInfoStandard, &data); -} - -REQUEST_NOTIFICATION_STATUS -ASPNETCORE_APPLICATION::ExecuteRequest( - _In_ IHttpContext* pHttpContext -) -{ - if (m_RequestHandler != NULL) - { - return m_RequestHandler(pHttpContext, m_RequstHandlerContext); - } - - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); - return RQ_NOTIFICATION_FINISH_REQUEST; -} - - -VOID -ASPNETCORE_APPLICATION::Shutdown( - VOID -) -{ - // First call into the managed server and shutdown - BOOL result = m_ShutdownHandler(m_ShutdownHandlerContext); - s_Application = NULL; - delete this; -} \ No newline at end of file diff --git a/src/AspNetCore/Src/outprocessapplication.cxx b/src/AspNetCore/Src/outprocessapplication.cxx new file mode 100644 index 0000000000..ce76d3be14 --- /dev/null +++ b/src/AspNetCore/Src/outprocessapplication.cxx @@ -0,0 +1,121 @@ +// 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" + +OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION() + : m_pProcessManager(NULL) +{ +} + +OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() +{ + if (m_pProcessManager != NULL) + { + m_pProcessManager->ShutdownAllProcesses(); + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } +} + + +// +// Initialize is guarded by a lock inside APPLICATION_MANAGER::GetApplication +// It ensures only one application will be initialized and singleton +// Error will happen if you call Initialized outside APPLICATION_MANAGER::GetApplication +// +__override +HRESULT +OUT_OF_PROCESS_APPLICATION::Initialize( + _In_ APPLICATION_MANAGER* pApplicationManager, + _In_ ASPNETCORE_CONFIG* pConfiguration +) +{ + HRESULT hr = S_OK; + + DBG_ASSERT(pApplicationManager != NULL); + DBG_ASSERT(pConfiguration != NULL); + + m_pApplicationManager = pApplicationManager; + m_pConfiguration = pConfiguration; + + hr = m_applicationKey.Initialize(pConfiguration->QueryApplicationPath()->QueryStr()); + if (FAILED(hr)) + { + goto Finished; + } + + 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; + } + } + + if (m_pFileWatcherEntry == NULL) + { + m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pApplicationManager->GetFileWatcher()); + if (m_pFileWatcherEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + UpdateAppOfflineFileHandle(); + +Finished: + + if (FAILED(hr)) + { + if (m_pFileWatcherEntry != NULL) + { + m_pFileWatcherEntry->DereferenceFileWatcherEntry(); + m_pFileWatcherEntry = NULL; + } + + if (m_pProcessManager != NULL) + { + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } + } + + return hr; +} + +__override +VOID +OUT_OF_PROCESS_APPLICATION::OnAppOfflineHandleChange() +{ + // + // Sending signal to backend process for shutdown + // + if (m_pProcessManager != NULL) + { + m_pProcessManager->SendShutdownSignal(); + } +} + +__override +REQUEST_NOTIFICATION_STATUS +OUT_OF_PROCESS_APPLICATION::ExecuteRequest( + _In_ IHttpContext* pHttpContext +) +{ + // + // TODO: + // Ideally we should wrap the fowaring logic inside FORWARDING_HANDLER inside this function + // To achieve better abstraction. It is too risky to do it now + // + return RQ_NOTIFICATION_FINISH_REQUEST; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index 178c66698f..0e267a301d 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -114,12 +114,13 @@ inline bool IsSpace(char ch) #include "environmentvariablehash.h" #include "..\aspnetcore_msg.h" #include "aspnetcoreconfig.h" -#include "aspnetcoreapplication.h" #include "serverprocess.h" #include "processmanager.h" #include "filewatcher.h" #include "application.h" #include "applicationmanager.h" +#include "inprocessapplication.h" +#include "outprocessapplication.h" #include "resource.h" #include "path.h" #include "debugutil.h" @@ -130,6 +131,7 @@ inline bool IsSpace(char ch) #include "websockethandler.h" #include "forwardinghandler.h" #include "proxymodule.h" +#include "fx_ver.h" FORCEINLINE DWORD diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index 9616d8485a..3ee33002d7 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -78,55 +78,42 @@ CProxyModule::OnExecuteRequestHandler( IHttpEventProvider * ) { - HRESULT hr; - APPLICATION_MANAGER* pApplicationManager; - APPLICATION* pApplication; - ASPNETCORE_CONFIG* config; - ASPNETCORE_APPLICATION* pAspNetCoreApplication; - ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - - if (config->QueryIsOutOfProcess())// case insensitive + HRESULT hr = S_OK; + ASPNETCORE_CONFIG *pConfig = NULL; + APPLICATION_MANAGER *pApplicationManager = NULL; + APPLICATION *pApplication = NULL; + hr = ASPNETCORE_CONFIG::GetConfig(pHttpContext, &pConfig); + if (FAILED(hr)) { - m_pHandler = new FORWARDING_HANDLER(pHttpContext); - if (m_pHandler == NULL) - { - hr = E_OUTOFMEMORY; - goto Failed; - } - - return m_pHandler->OnExecuteRequestHandler(); + goto Failed; } - else if (config->QueryIsInProcess()) + + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) { - pApplicationManager = APPLICATION_MANAGER::GetInstance(); - if (pApplicationManager == NULL) - { - hr = E_OUTOFMEMORY; - goto Failed; - } - - hr = pApplicationManager->GetApplication(pHttpContext, - &pApplication); - if (FAILED(hr)) - { - goto Failed; - } - - hr = pApplication->GetAspNetCoreApplication(config, pHttpContext, &pAspNetCoreApplication); - if (FAILED(hr)) - { - goto Failed; - } - - // Allow reading and writing to simultaneously - ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); - - // Disable response buffering by default, we'll do a write behind buffering in managed code - ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); - - // TODO: Optimize sync completions - return pAspNetCoreApplication->ExecuteRequest(pHttpContext); + hr = E_OUTOFMEMORY; + goto Failed; } + + hr = pApplicationManager->GetApplication( + pHttpContext, + pConfig, + &pApplication); + if (FAILED(hr)) + { + goto Failed; + } + + m_pHandler = new FORWARDING_HANDLER(pHttpContext, pApplication); + + if (m_pHandler == NULL) + { + hr = E_OUTOFMEMORY; + goto Failed; + } + + return m_pHandler->OnExecuteRequestHandler(); + Failed: pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; @@ -142,21 +129,7 @@ CProxyModule::OnAsyncCompletion( IHttpCompletionInfo * pCompletionInfo ) { - // TODO store whether we are inproc or outofproc so we don't need to check the config everytime? - ASPNETCORE_CONFIG* config; - ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - - if (config->QueryIsOutOfProcess()) - { - return m_pHandler->OnAsyncCompletion( - pCompletionInfo->GetCompletionBytes(), - pCompletionInfo->GetCompletionStatus()); - } - else if (config->QueryIsInProcess()) - { - return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_CONTINUE; - } - - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); - return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; + return m_pHandler->OnAsyncCompletion( + pCompletionInfo->GetCompletionBytes(), + pCompletionInfo->GetCompletionStatus()); } \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 8296d6df9c..aac67ff8c6 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -1064,7 +1064,6 @@ Finished: { if (!fDonePrepareCommandLine) { - strEventMsg.SafeSnwprintf( m_struAppFullPath.QueryStr(), ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, @@ -1073,11 +1072,11 @@ Finished: else { strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, - m_struAppFullPath.QueryStr(), - m_pszRootApplicationPath.QueryStr(), - struCommandLine.QueryStr(), - hr); + ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + struCommandLine.QueryStr(), + hr); } } From 051f13f17ddf2f49ec86bb57100ca121697d1049 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Sun, 15 Oct 2017 14:38:23 -0700 Subject: [PATCH 075/107] Add AppVeyor. (#188) --- .appveyor.yml | 18 ++++++++++++++ build.cmd | 2 +- run.cmd | 2 ++ build.ps1 => run.ps1 | 58 +++++++++++++++++++++++++++----------------- 4 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 .appveyor.yml create mode 100644 run.cmd rename build.ps1 => run.ps1 (73%) diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000000..5a38c39fa9 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,18 @@ +init: + - git config --global core.autocrlf true +branches: + only: + - master + - release + - dev + - /^(.*\/)?ci-.*$/ +build_script: + - ps: .\run.ps1 default-build +clone_depth: 1 +environment: + global: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: 1 +test: off +deploy: off +os: Visual Studio 2017 \ No newline at end of file diff --git a/build.cmd b/build.cmd index b6c8d24864..052dabaa0f 100644 --- a/build.cmd +++ b/build.cmd @@ -1,2 +1,2 @@ @ECHO OFF -PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" \ No newline at end of file diff --git a/run.cmd b/run.cmd new file mode 100644 index 0000000000..b2eeb7419f --- /dev/null +++ b/run.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" \ No newline at end of file diff --git a/build.ps1 b/run.ps1 similarity index 73% rename from build.ps1 rename to run.ps1 index d5eb4d5cf2..a0400b97db 100644 --- a/build.ps1 +++ b/run.ps1 @@ -3,10 +3,13 @@ <# .SYNOPSIS -Build this repository +Executes KoreBuild commands. .DESCRIPTION -Downloads korebuild if required. Then builds the repository. +Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. + +.PARAMETER Command +The KoreBuild command to run. .PARAMETER Path The folder to build. Defaults to the folder containing this script. @@ -24,31 +27,32 @@ The base url where build tools can be downloaded. Overrides the value from the c Updates KoreBuild to the latest version even if a lock file is present. .PARAMETER ConfigFile -The path to the configuration file that stores values. Defaults to version.xml. +The path to the configuration file that stores values. Defaults to korebuild.json. -.PARAMETER MSBuildArgs -Arguments to be passed to MSBuild +.PARAMETER Arguments +Arguments to be passed to the command .NOTES This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. -The $ConfigFile is expected to be an XML file. It is optional, and the configuration values in it are optional as well. +The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set +in the file are overridden by command line parameters. .EXAMPLE Example config file: -```xml - - - - dev - https://aspnetcore.blob.core.windows.net/buildtools - - +```json +{ + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", + "channel": "dev", + "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" +} ``` #> [CmdletBinding(PositionalBinding = $false)] param( + [Parameter(Mandatory=$true, Position = 0)] + [string]$Command, [string]$Path = $PSScriptRoot, [Alias('c')] [string]$Channel, @@ -58,9 +62,9 @@ param( [string]$ToolsSource, [Alias('u')] [switch]$Update, - [string]$ConfigFile = (Join-Path $PSScriptRoot 'version.xml'), + [string]$ConfigFile, [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$MSBuildArgs + [string[]]$Arguments ) Set-StrictMode -Version 2 @@ -147,10 +151,20 @@ function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) { # Load configuration or set defaults +$Path = Resolve-Path $Path +if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } + if (Test-Path $ConfigFile) { - [xml] $config = Get-Content $ConfigFile - if (!($Channel)) { [string] $Channel = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildChannel' } - if (!($ToolsSource)) { [string] $ToolsSource = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildToolsSource' } + try { + $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json + if ($config) { + if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } + if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} + } + } catch { + Write-Warning "$ConfigFile could not be read. Its settings will be ignored." + Write-Warning $Error[0] + } } if (!$DotNetHome) { @@ -169,9 +183,9 @@ $korebuildPath = Get-KoreBuild Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') try { - Install-Tools $ToolsSource $DotNetHome - Invoke-RepositoryBuild $Path @MSBuildArgs + Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile + Invoke-KoreBuildCommand $Command @Arguments } finally { Remove-Module 'KoreBuild' -ErrorAction Ignore -} +} \ No newline at end of file From 7117147a0942be24096746d0adbbf141921adb0e Mon Sep 17 00:00:00 2001 From: pan-wang Date: Tue, 17 Oct 2017 14:43:31 -0700 Subject: [PATCH 076/107] adding FREB and more ETW log (#185) * adding FREB log and more ETW logs * add missing aspnetcore_event.h file from previous commit * Update aspnetcore_event.h change provide id as 0x8000 is used by cors --- src/AspNetCore/AspNetCore.vcxproj | 1 + src/AspNetCore/Inc/aspnetcore_event.h | 550 ++++++++++++++++++++ src/AspNetCore/Inc/resource.h | 3 +- src/AspNetCore/Src/aspnetcore_msg.mc | 6 + src/AspNetCore/Src/forwardinghandler.cxx | 53 ++ src/AspNetCore/Src/inprocessapplication.cxx | 56 +- src/AspNetCore/Src/precomp.hxx | 1 + src/AspNetCore/Src/serverprocess.cxx | 18 + 8 files changed, 683 insertions(+), 5 deletions(-) create mode 100644 src/AspNetCore/Inc/aspnetcore_event.h diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index cce29d5c78..63c5065e2b 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -154,6 +154,7 @@ + diff --git a/src/AspNetCore/Inc/aspnetcore_event.h b/src/AspNetCore/Inc/aspnetcore_event.h new file mode 100644 index 0000000000..11f9e248a2 --- /dev/null +++ b/src/AspNetCore/Inc/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/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index f2982b2a27..5f98458d85 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -20,4 +20,5 @@ #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'." \ No newline at end of file +#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/Src/aspnetcore_msg.mc b/src/AspNetCore/Src/aspnetcore_msg.mc index cc3d5d1c9c..ae8eec4e88 100644 --- a/src/AspNetCore/Src/aspnetcore_msg.mc +++ b/src/AspNetCore/Src/aspnetcore_msg.mc @@ -92,6 +92,12 @@ 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/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 6023449b9f..e49f8b1d0a 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -3,6 +3,7 @@ #include "precomp.hxx" #include +#include // Just to be aware of the FORWARDING_HANDLER object size. @@ -1151,9 +1152,27 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( hr = ((IN_PROCESS_APPLICATION*)m_pApplication)->LoadManagedApplication(); if (FAILED(hr)) { + _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; goto Failure; } + + // 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"); + } return m_pApplication->ExecuteRequest(m_pW3Context); } case HOSTING_OUT_PROCESS: @@ -1301,6 +1320,15 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( // async completion. // ReferenceForwardingHandler(); + + //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, @@ -1312,6 +1340,14 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( hr = HRESULT_FROM_WIN32(GetLastError()); DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); + if (ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::RaiseEvent( + m_pW3Context->GetTraceContext(), + NULL, + hr); + } + DereferenceForwardingHandler(); goto Failure; } @@ -2078,6 +2114,14 @@ None 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. @@ -2341,6 +2385,15 @@ Failure: } } + // 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 (fIsCompletionThread) diff --git a/src/AspNetCore/Src/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx index f9a2f65ff6..56aa94c7c1 100644 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -194,8 +194,10 @@ http_flush_response_bytes( IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; -IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(): - m_fManagedAppLoaded ( FALSE ), m_fLoadManagedAppError ( FALSE ) +IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(): + m_ProcessExitCode ( 0 ), + m_fManagedAppLoaded ( FALSE ), + m_fLoadManagedAppError ( FALSE ) { } @@ -545,6 +547,12 @@ IN_PROCESS_APPLICATION::ExecuteRequest( // // 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, + E_APPLICATION_ACTIVATION_EXEC_FAILURE); + } pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); return RQ_NOTIFICATION_FINISH_REQUEST; } @@ -739,11 +747,51 @@ IN_PROCESS_APPLICATION::ExecuteApplication( m_ProcessExitCode = pProc(2, argv); if (m_ProcessExitCode != 0) { - // TODO error + } Finished: - // TODO log any errors + // + // this method is called by the background thread and should never exit unless shutdown + // + if (!g_fRecycleProcessCalled) + { + STRU strEventMsg; + LPCWSTR apsz[1]; + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, + m_pConfiguration->QueryApplicationPath()->QueryStr(), + m_pConfiguration->QueryApplicationFullPath()->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; } diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index 0e267a301d..8a8121dbec 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -113,6 +113,7 @@ inline bool IsSpace(char ch) #include "environmentvariablehash.h" #include "..\aspnetcore_msg.h" +#include "aspnetcore_event.h" #include "aspnetcoreconfig.h" #include "serverprocess.h" #include "processmanager.h" diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index aac67ff8c6..b9f8010902 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -1042,6 +1042,15 @@ SERVER_PROCESS::StartProcess( 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: @@ -1097,6 +1106,15 @@ Finished: 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) From 68014a7acd6e08f7b1dae7caf4279b050e5a7cbc Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 17 Oct 2017 16:06:59 -0700 Subject: [PATCH 077/107] Changes async calls to use OnAsyncComplete event pattern (#184) --- AspNetCoreModule.sln | 5 +- src/AspNetCore/AspNetCore.vcxproj | 2 + src/AspNetCore/Inc/inprocessapplication.h | 12 ++ src/AspNetCore/Inc/inprocessstoredcontext.h | 67 +++++++ src/AspNetCore/Src/forwardinghandler.cxx | 28 ++- src/AspNetCore/Src/inprocessapplication.cxx | 179 +++++++++++++++++- src/AspNetCore/Src/inprocessstoredcontext.cxx | 79 ++++++++ src/AspNetCore/Src/precomp.hxx | 1 + src/AspNetCore/Src/proxymodule.cxx | 6 +- 9 files changed, 360 insertions(+), 19 deletions(-) create mode 100644 src/AspNetCore/Inc/inprocessstoredcontext.h create mode 100644 src/AspNetCore/Src/inprocessstoredcontext.cxx diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index 54c32b50f6..ff2f980680 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26815.1 +VisualStudioVersion = 15.0.26815.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" ProjectSection(ProjectDependencies) = postProject @@ -102,4 +102,7 @@ Global {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {02F461DC-5166-4E88-AAD5-CF110016A647} {4062EA94-75F5-4691-86DC-C8594BA896DE} = {02F461DC-5166-4E88-AAD5-CF110016A647} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0967E9B4-FEE7-40D7-860A-23E340E65840} + EndGlobalSection EndGlobal diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 63c5065e2b..261f9f0947 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -176,9 +176,11 @@ + + diff --git a/src/AspNetCore/Inc/inprocessapplication.h b/src/AspNetCore/Inc/inprocessapplication.h index ca36067d11..c80ac757fb 100644 --- a/src/AspNetCore/Inc/inprocessapplication.h +++ b/src/AspNetCore/Inc/inprocessapplication.h @@ -6,6 +6,7 @@ typedef void(*request_handler_cb) (int error, IHttpContext* pHttpContext, void* pvCompletionContext); typedef REQUEST_NOTIFICATION_STATUS(*PFN_REQUEST_HANDLER) (IHttpContext* pHttpContext, void* pvRequstHandlerContext); typedef BOOL(*PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); +typedef REQUEST_NOTIFICATION_STATUS(*PFN_MANAGED_CONTEXT_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion); #include "application.h" @@ -39,6 +40,7 @@ public: 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 ); @@ -54,6 +56,13 @@ public: VOID ); + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + IHttpContext* pHttpContext, + DWORD cbCompletion, + HRESULT hrCompletionStatus + ); + static IN_PROCESS_APPLICATION* GetInstance( @@ -76,6 +85,8 @@ private: 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; @@ -84,6 +95,7 @@ private: BOOL m_fManagedAppLoaded; BOOL m_fLoadManagedAppError; + BOOL m_fIsWebSocketsConnection; static IN_PROCESS_APPLICATION* s_Application; diff --git a/src/AspNetCore/Inc/inprocessstoredcontext.h b/src/AspNetCore/Inc/inprocessstoredcontext.h new file mode 100644 index 0000000000..01a6045609 --- /dev/null +++ b/src/AspNetCore/Inc/inprocessstoredcontext.h @@ -0,0 +1,67 @@ +// 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 IN_PROCESS_STORED_CONTEXT : public IHttpStoredContext +{ +public: + IN_PROCESS_STORED_CONTEXT( + IHttpContext* pHttpContext, + PVOID pvManagedContext + ); + ~IN_PROCESS_STORED_CONTEXT(); + + virtual + VOID + CleanupStoredContext( + VOID + ) + { + delete this; + } + + virtual + VOID + OnClientDisconnected( + VOID + ) + { + } + + virtual + VOID + OnListenerEvicted( + VOID + ) + { + } + + PVOID + QueryManagedHttpContext( + VOID + ); + + IHttpContext* + QueryHttpContext( + VOID + ); + + static + HRESULT + GetInProcessStoredContext( + IHttpContext* pHttpContext, + IN_PROCESS_STORED_CONTEXT** ppInProcessStoredContext + ); + + static + HRESULT + SetInProcessStoredContext( + IHttpContext* pHttpContext, + IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext + ); + +private: + PVOID m_pManagedHttpContext; + IHttpContext* m_pHttpContext; +}; + diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index e49f8b1d0a..13ebc415b3 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1143,11 +1143,6 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( { case HOSTING_IN_PROCESS: { - // Allow reading and writing to simultaneously - ((IHttpContext3*)m_pW3Context)->EnableFullDuplex(); - - // Disable response buffering by default, we'll do a write behind buffering in managed code - ((IHttpResponse2*)m_pW3Context->GetResponse())->DisableBuffering(); hr = ((IN_PROCESS_APPLICATION*)m_pApplication)->LoadManagedApplication(); if (FAILED(hr)) @@ -1570,7 +1565,28 @@ REQUEST_NOTIFICATION_STATUS reinterpret_cast(static_cast(hrCompletionStatus))); } - if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_OUT_PROCESS) + if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_IN_PROCESS) + { + 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(m_pW3Context, cbCompletion, hrCompletionStatus); + } + } + else if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_OUT_PROCESS) { // // Take a reference so that object does not go away as a result of diff --git a/src/AspNetCore/Src/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx index 56aa94c7c1..d65c2a23f7 100644 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -14,6 +14,7 @@ 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 ) @@ -21,6 +22,7 @@ register_callbacks( IN_PROCESS_APPLICATION::GetInstance()->SetCallbackHandles( request_handler, shutdown_handler, + async_completion_handler, pvRequstHandlerContext, pvShutdownHandlerContext ); @@ -56,10 +58,34 @@ EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_post_completion( - _In_ IHttpContext* pHttpContext + _In_ IHttpContext* pHttpContext, + DWORD cbBytes ) { - return pHttpContext->PostCompletion(0); + return pHttpContext->PostCompletion(cbBytes); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_set_managed_context( + _In_ IHttpContext* pHttpContext, + _In_ PVOID pvManagedContext +) +{ + HRESULT hr; + IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = new IN_PROCESS_STORED_CONTEXT(pHttpContext, pvManagedContext); + if (pInProcessStoredContext == NULL) + { + return E_OUTOFMEMORY; + } + + hr = IN_PROCESS_STORED_CONTEXT::SetInProcessStoredContext(pHttpContext, pInProcessStoredContext); + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) + { + hr = S_OK; + } + + return hr; } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT @@ -104,6 +130,92 @@ http_get_application_full_path() EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_read_request_bytes( + _In_ IHttpContext* pHttpContext, + _Out_ CHAR* pvBuffer, + _In_ DWORD dwCbBuffer, + _Out_ DWORD* pdwBytesReceived, + _Out_ BOOL* pfCompletionPending +) +{ + HRESULT hr; + + if (pHttpContext == NULL) + { + return E_FAIL; + } + if (dwCbBuffer == 0) + { + return E_FAIL; + } + IHttpRequest *pHttpRequest = (IHttpRequest*)pHttpContext->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_ IHttpContext* pHttpContext, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD dwChunks, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->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_ IHttpContext* pHttpContext, + _Out_ BOOL* pfCompletionExpected +) +{ + IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->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_ IHttpContext* pHttpContext, _In_ CHAR* pvBuffer, _In_ DWORD cbBuffer, @@ -137,10 +249,10 @@ http_read_request_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT -http_write_response_bytes( +http_websockets_write_bytes( _In_ IHttpContext* pHttpContext, _In_ HTTP_DATA_CHUNK* pDataChunks, - _In_ DWORD nChunks, + _In_ DWORD dwChunks, _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, _In_ VOID* pvCompletionContext, _In_ BOOL* pfCompletionExpected @@ -154,7 +266,7 @@ http_write_response_bytes( HRESULT hr = pHttpResponse->WriteEntityChunks( pDataChunks, - nChunks, + dwChunks, fAsync, fMoreData, pfnCompletionCallback, @@ -167,7 +279,7 @@ http_write_response_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT -http_flush_response_bytes( +http_websockets_flush_bytes( _In_ IHttpContext* pHttpContext, _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, _In_ VOID* pvCompletionContext, @@ -189,24 +301,71 @@ http_flush_response_bytes( pfCompletionExpected); return hr; } -// End of export +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_enable_websockets( + _In_ IHttpContext* pHttpContext +) +{ + if (!g_fWebSocketSupported) + { + return E_FAIL; + } + + ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); + ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); + + return S_OK; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_cancel_io( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->CancelIo(); +} + +// End of export IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; -IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(): +IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION() : m_ProcessExitCode ( 0 ), m_fManagedAppLoaded ( FALSE ), m_fLoadManagedAppError ( FALSE ) { } - IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() { Recycle(); } +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_APPLICATION::OnAsyncCompletion( + IHttpContext* pHttpContext, + DWORD cbCompletion, + HRESULT hrCompletionStatus +) +{ + HRESULT hr; + IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = NULL; + + hr = IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext(pHttpContext, &pInProcessStoredContext); + if (FAILED(hr)) + { + // Finish the request as we couldn't get the callback + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 19, hr); + return RQ_NOTIFICATION_FINISH_REQUEST; + } + + // Call the managed handler for async completion. + return m_AsyncCompletionHandler(pInProcessStoredContext->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); +} + BOOL IN_PROCESS_APPLICATION::DirectoryExists( _In_ STRU *pstrPath @@ -289,6 +448,7 @@ 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 ) @@ -297,6 +457,7 @@ IN_PROCESS_APPLICATION::SetCallbackHandles( m_RequstHandlerContext = pvRequstHandlerContext; m_ShutdownHandler = shutdown_handler; m_ShutdownHandlerContext = pvShutdownHandlerContext; + m_AsyncCompletionHandler = async_completion_handler; // Initialization complete SetEvent(m_pInitalizeEvent); diff --git a/src/AspNetCore/Src/inprocessstoredcontext.cxx b/src/AspNetCore/Src/inprocessstoredcontext.cxx new file mode 100644 index 0000000000..27704b6c96 --- /dev/null +++ b/src/AspNetCore/Src/inprocessstoredcontext.cxx @@ -0,0 +1,79 @@ +// 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" + +IN_PROCESS_STORED_CONTEXT::IN_PROCESS_STORED_CONTEXT( + IHttpContext* pHttpContext, + PVOID pMangedHttpContext +) +{ + m_pManagedHttpContext = pMangedHttpContext; + m_pHttpContext = pHttpContext; +} + +IN_PROCESS_STORED_CONTEXT::~IN_PROCESS_STORED_CONTEXT() +{ +} + +PVOID +IN_PROCESS_STORED_CONTEXT::QueryManagedHttpContext( + VOID +) +{ + return m_pManagedHttpContext; +} + +IHttpContext* +IN_PROCESS_STORED_CONTEXT::QueryHttpContext( + VOID +) +{ + return m_pHttpContext; +} + +HRESULT +IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext( + IHttpContext* pHttpContext, + IN_PROCESS_STORED_CONTEXT** ppInProcessStoredContext +) +{ + if (pHttpContext == NULL) + { + return E_FAIL; + } + + if (ppInProcessStoredContext == NULL) + { + return E_FAIL; + } + + *ppInProcessStoredContext = (IN_PROCESS_STORED_CONTEXT*)pHttpContext->GetModuleContextContainer()->GetModuleContext(g_pModuleId); + if (*ppInProcessStoredContext == NULL) + { + return E_FAIL; + } + + return S_OK; +} + +HRESULT +IN_PROCESS_STORED_CONTEXT::SetInProcessStoredContext( + IHttpContext* pHttpContext, + IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext +) +{ + if (pHttpContext == NULL) + { + return E_FAIL; + } + if (pInProcessStoredContext == NULL) + { + return E_FAIL; + } + + return pHttpContext->GetModuleContextContainer()->SetModuleContext( + pInProcessStoredContext, + g_pModuleId + ); +} \ No newline at end of file diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index 8a8121dbec..403d949256 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -120,6 +120,7 @@ inline bool IsSpace(char ch) #include "filewatcher.h" #include "application.h" #include "applicationmanager.h" +#include "inprocessstoredcontext.h" #include "inprocessapplication.h" #include "outprocessapplication.h" #include "resource.h" diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index 3ee33002d7..45f16a68e5 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -122,9 +122,9 @@ Failed: __override REQUEST_NOTIFICATION_STATUS CProxyModule::OnAsyncCompletion( - IHttpContext * pHttpContext, - DWORD dwNotification, - BOOL fPostNotification, + IHttpContext *, + DWORD, + BOOL, IHttpEventProvider *, IHttpCompletionInfo * pCompletionInfo ) From 02cffd16ecaea0ace748788e65cd6ec51e237a96 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Thu, 19 Oct 2017 12:15:24 -0700 Subject: [PATCH 078/107] Jhkim/refactoring test (#176) Cleans up IIS initialization testing framework --- .../Framework/IISConfigUtility.cs | 20 +++---- .../Framework/InitializeTestMachine.cs | 57 ++++++++++--------- .../Framework/TestWebSite.cs | 20 +++---- .../FunctionalTestHelper.cs | 2 +- 4 files changed, 50 insertions(+), 49 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index 86077f22db..79557281ed 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -208,7 +208,7 @@ namespace AspNetCoreModule.Test.Framework if (!File.Exists(fromfile)) { - throw new System.ApplicationException("Failed to backup " + tofile); + throw new ApplicationException("Failed to backup " + tofile); } // try restoring applicationhost.config again after the ininial clean up for better reliability @@ -232,7 +232,7 @@ namespace AspNetCoreModule.Test.Framework // verify restoration is done successfully if (File.ReadAllBytes(fromfile).Length != File.ReadAllBytes(tofile).Length) { - throw new System.ApplicationException("Failed to restore applicationhost.config from " + fromfile + " to " + tofile); + throw new ApplicationException("Failed to restore applicationhost.config from " + fromfile + " to " + tofile); } } @@ -465,7 +465,7 @@ namespace AspNetCoreModule.Test.Framework var element = FindElement(environmentVariablesCollection, "add", "name", value); if (element != null) { - throw new System.ApplicationException("duplicated collection item"); + throw new ApplicationException("duplicated collection item"); } environmentVariablesCollection.Add(environmentVariableElement); } @@ -1116,7 +1116,7 @@ namespace AspNetCoreModule.Test.Framework string output = TestUtility.RunPowershellScript(powershellScript); if (output.Length != 40) { - throw new System.ApplicationException("Failed to create a certificate, output: " + output); + throw new ApplicationException("Failed to create a certificate, output: " + output); } return output; } @@ -1131,7 +1131,7 @@ namespace AspNetCoreModule.Test.Framework string output = TestUtility.RunPowershellScript(powershellScript); if (output.Length != 40) { - throw new System.ApplicationException("Failed to create a certificate, output: " + output); + throw new ApplicationException("Failed to create a certificate, output: " + output); } return output; } @@ -1153,7 +1153,7 @@ namespace AspNetCoreModule.Test.Framework string output = TestUtility.RunPowershellScript(powershellScript); if (output != string.Empty) { - throw new System.ApplicationException("Failed to export a certificate to RootCA, output: " + output); + throw new ApplicationException("Failed to export a certificate to RootCA, output: " + output); } return output; } @@ -1169,7 +1169,7 @@ namespace AspNetCoreModule.Test.Framework string output = TestUtility.RunPowershellScript(powershellScript); if (output.Length < 500) { - throw new System.ApplicationException("Failed to get certificate public key, output: " + output); + throw new ApplicationException("Failed to get certificate public key, output: " + output); } return output; } @@ -1185,7 +1185,7 @@ namespace AspNetCoreModule.Test.Framework string output = TestUtility.RunPowershellScript(powershellScript); if (output != string.Empty) { - throw new System.ApplicationException("Failed to delete a certificate (thumbprint: " + thumbPrint + ", output: " + output); + throw new ApplicationException("Failed to delete a certificate (thumbprint: " + thumbPrint + ", output: " + output); } return output; } @@ -1207,7 +1207,7 @@ namespace AspNetCoreModule.Test.Framework string output = TestUtility.RunPowershellScript(powershellScript); if (output != string.Empty) { - throw new System.ApplicationException("Failed to configure certificate, output: " + output); + throw new ApplicationException("Failed to configure certificate, output: " + output); } } @@ -1227,7 +1227,7 @@ namespace AspNetCoreModule.Test.Framework output = TestUtility.RunPowershellScript(powershellScript); if (output != string.Empty) { - throw new System.ApplicationException("Failed to delete certificate, output: " + output); + throw new ApplicationException("Failed to delete certificate, output: " + output); } } } diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 53b7fcd389..9fc4d086c2 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -18,6 +18,8 @@ namespace AspNetCoreModule.Test.Framework 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"; @@ -65,7 +67,7 @@ namespace AspNetCoreModule.Test.Framework } catch { - _makeCertExeAvailable = false; + // ignore exception } } return (_makeCertExeAvailable == true); @@ -87,9 +89,8 @@ namespace AspNetCoreModule.Test.Framework { if (_globalTestFlags == null) { - bool isElevated; WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); - isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); + bool isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator); // check if this test process is started with the Run As Administrator start option _globalTestFlags = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); @@ -190,6 +191,26 @@ namespace AspNetCoreModule.Test.Framework } } + 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(); } @@ -213,17 +234,7 @@ namespace AspNetCoreModule.Test.Framework if (!isIISInstalled) { - throw new System.ApplicationException("IIS server is not installed"); - } - - // Check websocket is installed - if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) - { - TestUtility.LogInformation("Websocket is installed"); - } - else - { - throw new System.ApplicationException("websocket module is not installed"); + throw new ApplicationException("IIS server is not installed"); } // Clean up IIS worker process @@ -241,22 +252,12 @@ namespace AspNetCoreModule.Test.Framework } else { - throw new System.ApplicationException("WWW service can't start"); - } - - // check URLRewrite module exists - if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "rewrite.dll"))) - { - TestUtility.LogInformation("Verified URL Rewrite module installed for IIS server"); - } - else - { - throw new System.ApplicationException("URL Rewrite module is not installed"); + throw new ApplicationException("WWW service can't start"); } if (IISConfigUtility.ApppHostTemporaryBackupFileExtention == null) { - throw new System.ApplicationException("Failed to backup applicationhost.config"); + throw new ApplicationException("Failed to backup applicationhost.config"); } } @@ -355,7 +356,7 @@ namespace AspNetCoreModule.Test.Framework } if (!_InitializeTestMachineCompleted) { - throw new System.ApplicationException("InitializeTestMachine failed"); + throw new ApplicationException("InitializeTestMachine failed"); } } @@ -476,7 +477,7 @@ namespace AspNetCoreModule.Test.Framework } if (!updateSuccess) { - throw new System.ApplicationException("Failed to update aspnetcore.dll"); + throw new ApplicationException("Failed to update aspnetcore.dll"); } // update applicationhost.config for IIS server with the new private ASPNET Core file name diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index 79b4ab65a3..af7098f697 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -419,7 +419,7 @@ namespace AspNetCoreModule.Test.Framework cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "appverif.exe"); if (!File.Exists(cmdline)) { - throw new System.ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); + throw new ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); } } else @@ -427,7 +427,7 @@ namespace AspNetCoreModule.Test.Framework cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "appverif.exe"); if (!File.Exists(cmdline)) { - throw new System.ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); + throw new ApplicationException("Not found :" + cmdline + "; this test requires appverif.exe."); } } @@ -438,7 +438,7 @@ namespace AspNetCoreModule.Test.Framework } catch { - throw new System.ApplicationException("Failed to configure Appverifier"); + throw new ApplicationException("Failed to configure Appverifier"); } } @@ -456,7 +456,7 @@ namespace AspNetCoreModule.Test.Framework debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "wow64", "windbg.exe"); if (!File.Exists(debuggerCmdline)) { - throw new System.ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); + throw new ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); } } else @@ -464,7 +464,7 @@ namespace AspNetCoreModule.Test.Framework debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "windbg.exe"); if (!File.Exists(debuggerCmdline)) { - throw new System.ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); + throw new ApplicationException("Not found :" + debuggerCmdline + "; this test requires windbg.exe."); } } @@ -475,7 +475,7 @@ namespace AspNetCoreModule.Test.Framework } catch { - throw new System.ApplicationException("Failed to attach debuger"); + throw new ApplicationException("Failed to attach debuger"); } } @@ -497,12 +497,12 @@ namespace AspNetCoreModule.Test.Framework cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "syswow64", "appverif.exe"); if (!File.Exists(cmdline)) { - throw new System.ApplicationException("Not found :" + 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 System.ApplicationException("Not found :" + debuggerCmdline); + throw new ApplicationException("Not found :" + debuggerCmdline); } } else @@ -510,12 +510,12 @@ namespace AspNetCoreModule.Test.Framework cmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32", "appverif.exe"); if (!File.Exists(cmdline)) { - throw new System.ApplicationException("Not found :" + 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 System.ApplicationException("Not found :" + debuggerCmdline); + throw new ApplicationException("Not found :" + debuggerCmdline); } } TestUtility.RunCommand(cmdline, argument, true, false); diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index d66c850f57..7e15e1acf5 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -695,7 +695,7 @@ namespace AspNetCoreModule.Test } else { - throw new System.ApplicationException("wrong data"); + throw new ApplicationException("wrong data"); } } testSite.AspNetCoreApp.RestoreFile("web.config"); From 448a2afed86609e98588bd056092b58a94a5c373 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Sat, 21 Oct 2017 13:40:45 -0700 Subject: [PATCH 079/107] fix the AV in recycle process, issue #192. (#201) * fix the AV in recycle process. this is due to we call Recycle again when the background thread exists * more fixes * reset hosting mode when all applications got removed --- build/Version.props | 2 +- src/AspNetCore/Inc/inprocessapplication.h | 1 + src/AspNetCore/Inc/resource.h | 2 +- src/AspNetCore/Src/applicationmanager.cxx | 4 + src/AspNetCore/Src/aspnetcoreconfig.cxx | 17 ++++- src/AspNetCore/Src/forwardinghandler.cxx | 3 +- src/AspNetCore/Src/inprocessapplication.cxx | 83 ++++++++++++--------- 7 files changed, 67 insertions(+), 45 deletions(-) diff --git a/build/Version.props b/build/Version.props index 27a5f2db48..f5ae3f4b01 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ 7 1 - 1968 + 1987 -RTM diff --git a/src/AspNetCore/Inc/inprocessapplication.h b/src/AspNetCore/Inc/inprocessapplication.h index c80ac757fb..88aeb1c2fd 100644 --- a/src/AspNetCore/Inc/inprocessapplication.h +++ b/src/AspNetCore/Inc/inprocessapplication.h @@ -95,6 +95,7 @@ private: BOOL m_fManagedAppLoaded; BOOL m_fLoadManagedAppError; + BOOL m_fInitialized; BOOL m_fIsWebSocketsConnection; static IN_PROCESS_APPLICATION* s_Application; diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index 5f98458d85..be91685c10 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -11,7 +11,7 @@ #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 : %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." diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCore/Src/applicationmanager.cxx index 6c2ce6d235..f2640a55a5 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -221,6 +221,10 @@ APPLICATION_MANAGER::RecycleApplication( } AcquireSRWLockExclusive(&m_srwLock); m_pApplicationHash->DeleteKey(&key); + if (m_pApplicationHash->Count() == 0) + { + m_hostingModel = HOSTING_UNKNOWN; + } ReleaseSRWLockExclusive(&m_srwLock); Finished: diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index a63c3bf1ae..090c4e3f23 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -5,17 +5,26 @@ ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() { - if (QueryHostingModel() == HOSTING_IN_PROCESS && - !g_fRecycleProcessCalled && - !g_pHttpServer->IsCommandLineLaunch()) + if (QueryHostingModel() == HOSTING_IN_PROCESS && + !g_fRecycleProcessCalled && + (g_pHttpServer->GetAdminManager() != NULL)) { + // There is a bug in IHttpServer::RecycleProcess. It will hit AV when worker process + // has already been in recycling state. + // To workaround, do null check on GetAdminManager(). If it is NULL, worker process is in recycling + // Do not call RecycleProcess again + // RecycleProcess can olny be called once // In case of configuration change for in-process app // We want notify IIS first to let new request routed to new worker process - g_fRecycleProcessCalled = TRUE; + g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Configuration Change"); } + // It's safe for us to set this g_fRecycleProcessCalled + // as in_process scenario will always recycle the worker process for configuration change + g_fRecycleProcessCalled = TRUE; + m_struApplicationFullPath.Reset(); if (m_pEnvironmentVariables != NULL) { diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 13ebc415b3..2ddc077f88 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1043,7 +1043,6 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( IHttpRequest *pRequest = m_pW3Context->GetRequest(); IHttpResponse *pResponse = m_pW3Context->GetResponse(); PROTOCOL_CONFIG *pProtocol = &sm_ProtocolConfig; - APPLICATION_MANAGER *pApplicationManager = NULL; SERVER_PROCESS *pServerProcess = NULL; USHORT cchHostName = 0; BOOL fSecure = FALSE; @@ -1427,7 +1426,7 @@ Failure: } else { - if (SUCCEEDED(pApplicationManager->Get502ErrorPage(&pDataChunk))) + if (SUCCEEDED(APPLICATION_MANAGER::GetInstance()->Get502ErrorPage(&pDataChunk))) { if (FAILED(hr = pResponse->WriteEntityChunkByReference(pDataChunk))) { diff --git a/src/AspNetCore/Src/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx index d65c2a23f7..f52118a95a 100644 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -335,7 +335,8 @@ IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION() : m_ProcessExitCode ( 0 ), m_fManagedAppLoaded ( FALSE ), - m_fLoadManagedAppError ( FALSE ) + m_fLoadManagedAppError ( FALSE ), + m_fInitialized ( FALSE ) { } @@ -510,6 +511,7 @@ IN_PROCESS_APPLICATION::Initialize( hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; } + m_fInitialized = TRUE; Finished: return hr; @@ -636,50 +638,57 @@ IN_PROCESS_APPLICATION::Recycle( VOID ) { - DWORD dwThreadStatus = 0; - DWORD dwTimeout = m_pConfiguration->QueryShutdownTimeLimitInMS(); - - AcquireSRWLockExclusive(&m_srwLock); - - if (!g_pHttpServer->IsCommandLineLaunch() && !g_fRecycleProcessCalled) + if (m_fInitialized) { - // IIS scenario. - // notify IIS first so that new request will be routed to new worker process - g_fRecycleProcessCalled = TRUE; - g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand"); - } - // First call into the managed server and shutdown - if (m_ShutdownHandler != NULL) - { - m_ShutdownHandler(m_ShutdownHandlerContext); - m_ShutdownHandler = NULL; - } + DWORD dwThreadStatus = 0; + DWORD dwTimeout = m_pConfiguration->QueryShutdownTimeLimitInMS(); - 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) + AcquireSRWLockExclusive(&m_srwLock); + + if (!g_pHttpServer->IsCommandLineLaunch() && + !g_fRecycleProcessCalled && + (g_pHttpServer->GetAdminManager() != NULL)) { - // 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) + // IIS scenario. + // notify IIS first so that new request will be routed to new worker process + g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand"); + } + + g_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) { - TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT); + // 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); + CloseHandle(m_hThread); + m_hThread = NULL; + s_Application = NULL; - if (g_pHttpServer->IsCommandLineLaunch()) - { - // IISExpress scenario - // Can only call exit to terminate current process - exit(0); + ReleaseSRWLockExclusive(&m_srwLock); + if (g_pHttpServer && g_pHttpServer->IsCommandLineLaunch()) + { + // IISExpress scenario + // Can only call exit to terminate current process + exit(0); + } } } From c845a331bbda688eaafa8f50c33ac04481ef8dd3 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 23 Oct 2017 15:28:48 -0700 Subject: [PATCH 080/107] adding forwarding end freb event (#209) --- src/AspNetCore/Inc/forwardinghandler.h | 14 +++++--------- src/AspNetCore/Src/forwardinghandler.cxx | 6 ++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h index 0213114b18..eb04a07b30 100644 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -316,6 +316,7 @@ private: APP_OFFLINE_HTM *m_pAppOfflineHtm; APPLICATION *m_pApplication; + bool m_fWebSocketEnabled; bool m_fHandleClosedDueToClient; bool m_fResponseHeadersReceivedAndSet; BOOL m_fDoReverseRewriteHeaders; @@ -325,20 +326,17 @@ private: BOOL m_fClientDisconnected; BOOL m_fHasError; DWORD m_msStartTime; - DWORD m_BytesToReceive; DWORD m_BytesToSend; - - BYTE * m_pEntityBuffer; DWORD m_cchLastSend; - - static const SIZE_T INLINE_ENTITY_BUFFERS = 8; DWORD m_cEntityBuffers; - BUFFER_T m_buffEntityBuffers; - DWORD m_cBytesBuffered; DWORD m_cMinBufferLimit; + BYTE * m_pEntityBuffer; + static const SIZE_T INLINE_ENTITY_BUFFERS = 8; + BUFFER_T m_buffEntityBuffers; + PCSTR m_pszOriginalHostHeader; FORWARDING_REQUEST_STATUS m_RequestStatus; @@ -348,8 +346,6 @@ private: PCWSTR m_pszHeaders; DWORD m_cchHeaders; - bool m_fWebSocketEnabled; - STRU m_strFullUri; ULONGLONG m_cContentLength; diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 2ddc077f88..763828fa60 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -2286,6 +2286,12 @@ None 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) { hr = ERROR_CONNECTION_ABORTED; From bfb2c86cda84f192ae53d0d5491b91d78f8df3db Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 23 Oct 2017 16:26:15 -0700 Subject: [PATCH 081/107] moving export methods to a standalone file (#211) --- src/AspNetCore/AspNetCore.vcxproj | 1 + src/AspNetCore/Src/inprocessapplication.cxx | 324 ------------------- src/AspNetCore/Src/managedexports.cxx | 328 ++++++++++++++++++++ 3 files changed, 329 insertions(+), 324 deletions(-) create mode 100644 src/AspNetCore/Src/managedexports.cxx diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 261f9f0947..ba93deb7e2 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -182,6 +182,7 @@ + diff --git a/src/AspNetCore/Src/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx index f52118a95a..7224125635 100644 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -6,330 +6,6 @@ typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); -// -// 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_ IHttpContext* pHttpContext -) -{ - return pHttpContext->GetRequest()->GetRawHttpRequest(); -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -HTTP_RESPONSE* -http_get_raw_response( - _In_ IHttpContext* pHttpContext -) -{ - return pHttpContext->GetResponse()->GetRawHttpResponse(); -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( - _In_ IHttpContext* pHttpContext, - _In_ USHORT statusCode, - _In_ PCSTR pszReason -) -{ - pHttpContext->GetResponse()->SetStatus(statusCode, pszReason); -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -HRESULT -http_post_completion( - _In_ IHttpContext* pHttpContext, - DWORD cbBytes -) -{ - return pHttpContext->PostCompletion(cbBytes); -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -HRESULT -http_set_managed_context( - _In_ IHttpContext* pHttpContext, - _In_ PVOID pvManagedContext -) -{ - HRESULT hr; - IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = new IN_PROCESS_STORED_CONTEXT(pHttpContext, pvManagedContext); - if (pInProcessStoredContext == NULL) - { - return E_OUTOFMEMORY; - } - - hr = IN_PROCESS_STORED_CONTEXT::SetInProcessStoredContext(pHttpContext, pInProcessStoredContext); - if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) - { - hr = S_OK; - } - - return hr; -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -VOID -http_indicate_completion( - _In_ IHttpContext* pHttpContext, - _In_ REQUEST_NOTIFICATION_STATUS notificationStatus -) -{ - pHttpContext->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 -// -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -BSTR // TODO probably should make this a wide string -http_get_application_full_path() -{ - LPWSTR pwzPath = NULL; - IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); - if(pApplication != NULL) - { - pwzPath = pApplication->QueryConfig()->QueryApplicationFullPath()->QueryStr(); - } - return SysAllocString(pwzPath); -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -HRESULT -http_read_request_bytes( - _In_ IHttpContext* pHttpContext, - _Out_ CHAR* pvBuffer, - _In_ DWORD dwCbBuffer, - _Out_ DWORD* pdwBytesReceived, - _Out_ BOOL* pfCompletionPending -) -{ - HRESULT hr; - - if (pHttpContext == NULL) - { - return E_FAIL; - } - if (dwCbBuffer == 0) - { - return E_FAIL; - } - IHttpRequest *pHttpRequest = (IHttpRequest*)pHttpContext->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_ IHttpContext* pHttpContext, - _In_ HTTP_DATA_CHUNK* pDataChunks, - _In_ DWORD dwChunks, - _In_ BOOL* pfCompletionExpected -) -{ - IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->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_ IHttpContext* pHttpContext, - _Out_ BOOL* pfCompletionExpected -) -{ - IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->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_ IHttpContext* pHttpContext, - _In_ CHAR* pvBuffer, - _In_ DWORD cbBuffer, - _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, - _In_ VOID* pvCompletionContext, - _In_ DWORD* pDwBytesReceived, - _In_ BOOL* pfCompletionPending -) -{ - IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pHttpContext->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_ IHttpContext* pHttpContext, - _In_ HTTP_DATA_CHUNK* pDataChunks, - _In_ DWORD dwChunks, - _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, - _In_ VOID* pvCompletionContext, - _In_ BOOL* pfCompletionExpected -) -{ - IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->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_ IHttpContext* pHttpContext, - _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, - _In_ VOID* pvCompletionContext, - _In_ BOOL* pfCompletionExpected -) -{ - IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->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_ IHttpContext* pHttpContext -) -{ - if (!g_fWebSocketSupported) - { - return E_FAIL; - } - - ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); - ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); - - return S_OK; -} - -EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -HRESULT -http_cancel_io( - _In_ IHttpContext* pHttpContext -) -{ - return pHttpContext->CancelIo(); -} - -// End of export - IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION() : diff --git a/src/AspNetCore/Src/managedexports.cxx b/src/AspNetCore/Src/managedexports.cxx new file mode 100644 index 0000000000..4af8feadab --- /dev/null +++ b/src/AspNetCore/Src/managedexports.cxx @@ -0,0 +1,328 @@ +// 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_ IHttpContext* pHttpContext +) +{ + return pHttpContext->GetRequest()->GetRawHttpRequest(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_RESPONSE* +http_get_raw_response( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->GetResponse()->GetRawHttpResponse(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( + _In_ IHttpContext* pHttpContext, + _In_ USHORT statusCode, + _In_ PCSTR pszReason +) +{ + pHttpContext->GetResponse()->SetStatus(statusCode, pszReason); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_post_completion( + _In_ IHttpContext* pHttpContext, + DWORD cbBytes +) +{ + return pHttpContext->PostCompletion(cbBytes); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_set_managed_context( + _In_ IHttpContext* pHttpContext, + _In_ PVOID pvManagedContext +) +{ + HRESULT hr; + IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = new IN_PROCESS_STORED_CONTEXT(pHttpContext, pvManagedContext); + if (pInProcessStoredContext == NULL) + { + return E_OUTOFMEMORY; + } + + hr = IN_PROCESS_STORED_CONTEXT::SetInProcessStoredContext(pHttpContext, pInProcessStoredContext); + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) + { + hr = S_OK; + } + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_indicate_completion( + _In_ IHttpContext* pHttpContext, + _In_ REQUEST_NOTIFICATION_STATUS notificationStatus +) +{ + pHttpContext->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 +// +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +BSTR // TODO probably should make this a wide string +http_get_application_full_path() +{ + LPWSTR pwzPath = NULL; + IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); + if (pApplication != NULL) + { + pwzPath = pApplication->QueryConfig()->QueryApplicationFullPath()->QueryStr(); + } + return SysAllocString(pwzPath); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_read_request_bytes( + _In_ IHttpContext* pHttpContext, + _Out_ CHAR* pvBuffer, + _In_ DWORD dwCbBuffer, + _Out_ DWORD* pdwBytesReceived, + _Out_ BOOL* pfCompletionPending +) +{ + HRESULT hr; + + if (pHttpContext == NULL) + { + return E_FAIL; + } + if (dwCbBuffer == 0) + { + return E_FAIL; + } + IHttpRequest *pHttpRequest = (IHttpRequest*)pHttpContext->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_ IHttpContext* pHttpContext, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD dwChunks, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->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_ IHttpContext* pHttpContext, + _Out_ BOOL* pfCompletionExpected +) +{ + IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->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_ IHttpContext* pHttpContext, + _In_ CHAR* pvBuffer, + _In_ DWORD cbBuffer, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ DWORD* pDwBytesReceived, + _In_ BOOL* pfCompletionPending +) +{ + IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pHttpContext->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_ IHttpContext* pHttpContext, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD dwChunks, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->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_ IHttpContext* pHttpContext, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->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_ IHttpContext* pHttpContext +) +{ + if (!g_fWebSocketSupported) + { + return E_FAIL; + } + + ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); + ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); + + return S_OK; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_cancel_io( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->CancelIo(); +} + +// End of export \ No newline at end of file From 332b108f41bdc096773b880741c0675442fa6e6d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 25 Oct 2017 11:46:34 -0700 Subject: [PATCH 082/107] Changes PostCompletion to handle OnAsyncCompletion after managed request has completed. (#212) --- src/AspNetCore/Inc/inprocessstoredcontext.h | 23 +++++++++++++ src/AspNetCore/Src/inprocessapplication.cxx | 16 +++++++-- src/AspNetCore/Src/inprocessstoredcontext.cxx | 34 +++++++++++++++++++ src/AspNetCore/Src/managedexports.cxx | 24 +++++++++++++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/AspNetCore/Inc/inprocessstoredcontext.h b/src/AspNetCore/Inc/inprocessstoredcontext.h index 01a6045609..64244692d4 100644 --- a/src/AspNetCore/Inc/inprocessstoredcontext.h +++ b/src/AspNetCore/Inc/inprocessstoredcontext.h @@ -9,6 +9,7 @@ public: IHttpContext* pHttpContext, PVOID pvManagedContext ); + ~IN_PROCESS_STORED_CONTEXT(); virtual @@ -46,6 +47,26 @@ public: VOID ); + BOOL + QueryIsManagedRequestComplete( + VOID + ); + + VOID + IndicateManagedRequestComplete( + VOID + ); + + REQUEST_NOTIFICATION_STATUS + QueryAsyncCompletionStatus( + VOID + ); + + VOID + SetAsyncCompletionStatus( + REQUEST_NOTIFICATION_STATUS requestNotificationStatus + ); + static HRESULT GetInProcessStoredContext( @@ -63,5 +84,7 @@ public: private: PVOID m_pManagedHttpContext; IHttpContext* m_pHttpContext; + BOOL m_fManagedRequestComplete; + REQUEST_NOTIFICATION_STATUS m_requestNotificationStatus; }; diff --git a/src/AspNetCore/Src/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx index 7224125635..f57518b7e4 100644 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -30,6 +30,7 @@ IN_PROCESS_APPLICATION::OnAsyncCompletion( { HRESULT hr; IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = NULL; + REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; hr = IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext(pHttpContext, &pInProcessStoredContext); if (FAILED(hr)) @@ -38,9 +39,18 @@ IN_PROCESS_APPLICATION::OnAsyncCompletion( pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 19, hr); return RQ_NOTIFICATION_FINISH_REQUEST; } - - // Call the managed handler for async completion. - return m_AsyncCompletionHandler(pInProcessStoredContext->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); + else if (pInProcessStoredContext->QueryIsManagedRequestComplete()) + { + // means PostCompletion has been called and this is the associated callback. + dwRequestNotificationStatus = pInProcessStoredContext->QueryAsyncCompletionStatus(); + // TODO cleanup whatever disconnect listener there is + return dwRequestNotificationStatus; + } + else + { + // Call the managed handler for async completion. + return m_AsyncCompletionHandler(pInProcessStoredContext->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); + } } BOOL diff --git a/src/AspNetCore/Src/inprocessstoredcontext.cxx b/src/AspNetCore/Src/inprocessstoredcontext.cxx index 27704b6c96..c0b2d8ff16 100644 --- a/src/AspNetCore/Src/inprocessstoredcontext.cxx +++ b/src/AspNetCore/Src/inprocessstoredcontext.cxx @@ -8,8 +8,10 @@ IN_PROCESS_STORED_CONTEXT::IN_PROCESS_STORED_CONTEXT( PVOID pMangedHttpContext ) { + // TODO if we want to go by IIS patterns, we should have these in a separate initialize function m_pManagedHttpContext = pMangedHttpContext; m_pHttpContext = pHttpContext; + m_fManagedRequestComplete = FALSE; } IN_PROCESS_STORED_CONTEXT::~IN_PROCESS_STORED_CONTEXT() @@ -32,6 +34,38 @@ IN_PROCESS_STORED_CONTEXT::QueryHttpContext( return m_pHttpContext; } +BOOL +IN_PROCESS_STORED_CONTEXT::QueryIsManagedRequestComplete( + VOID +) +{ + return m_fManagedRequestComplete; +} + +VOID +IN_PROCESS_STORED_CONTEXT::IndicateManagedRequestComplete( + VOID +) +{ + m_fManagedRequestComplete = TRUE; +} + +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_STORED_CONTEXT::QueryAsyncCompletionStatus( + VOID +) +{ + return m_requestNotificationStatus; +} + +VOID +IN_PROCESS_STORED_CONTEXT::SetAsyncCompletionStatus( + REQUEST_NOTIFICATION_STATUS requestNotificationStatus +) +{ + m_requestNotificationStatus = requestNotificationStatus; +} + HRESULT IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext( IHttpContext* pHttpContext, diff --git a/src/AspNetCore/Src/managedexports.cxx b/src/AspNetCore/Src/managedexports.cxx index 4af8feadab..f9dd492fcc 100644 --- a/src/AspNetCore/Src/managedexports.cxx +++ b/src/AspNetCore/Src/managedexports.cxx @@ -62,6 +62,30 @@ http_post_completion( return pHttpContext->PostCompletion(cbBytes); } +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_set_completion_status( + _In_ IHttpContext* pHttpContext, + REQUEST_NOTIFICATION_STATUS requestNotificationStatus +) +{ + HRESULT hr = S_OK; + IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = NULL; + + hr = IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext( + pHttpContext, + &pInProcessStoredContext + ); + + if (FAILED(hr)) + { + return hr; + } + pInProcessStoredContext->IndicateManagedRequestComplete(); + pInProcessStoredContext->SetAsyncCompletionStatus(requestNotificationStatus); + return hr; +} + EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_set_managed_context( From 9c5d38a78635085eed2b5ed57696deb4d5a0cc99 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 25 Oct 2017 12:35:47 -0700 Subject: [PATCH 083/107] Expose VirtualDirectory to In Process mode (#210) --- src/AspNetCore/Inc/aspnetcoreconfig.h | 9 ++++++++ src/AspNetCore/Src/aspnetcoreconfig.cxx | 30 +++++++++++++++++++++++++ src/AspNetCore/Src/managedexports.cxx | 18 ++++++++++----- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h index 5a4fbf398a..b3fa4d7d8e 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -139,6 +139,14 @@ public: return &m_struApplicationFullPath; } + STRU* + QueryApplicationVirtualPath( + VOID + ) + { + return &m_struApplicationVirtualPath; + } + STRU* QueryProcessPath( VOID @@ -233,6 +241,7 @@ private: STRU m_struStdoutLogFile; STRU m_struApplicationFullPath; STRU m_strHostingModel; + STRU m_struApplicationVirtualPath; BOOL m_fStdoutLogEnabled; BOOL m_fForwardWindowsAuthToken; BOOL m_fDisableStartUpErrorPage; diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index 090c4e3f23..99ccac1c3e 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -157,6 +157,9 @@ ASPNETCORE_CONFIG::Populate( ULONGLONG ullRawTimeSpan = 0; ENUM_INDEX index; ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + DWORD dwCounter = 0; + DWORD dwPosition = 0; + WCHAR* pszPath = NULL; m_pEnvironmentVariables = new ENVIRONMENT_VAR_HASH(); if (m_pEnvironmentVariables == NULL) @@ -184,6 +187,33 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } + pszPath = strSiteConfigPath.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; + } + hr = pAdminManager->GetAdminSection(CS_WINDOWS_AUTHENTICATION_SECTION, strSiteConfigPath.QueryStr(), &pWindowsAuthenticationElement); diff --git a/src/AspNetCore/Src/managedexports.cxx b/src/AspNetCore/Src/managedexports.cxx index f9dd492fcc..b0a31daa5f 100644 --- a/src/AspNetCore/Src/managedexports.cxx +++ b/src/AspNetCore/Src/managedexports.cxx @@ -136,16 +136,22 @@ http_get_completion_info( // the signature should be changed. application's based address should be passed in // EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -BSTR // TODO probably should make this a wide string -http_get_application_full_path() +HRESULT // TODO probably should make this a wide string +http_get_application_paths( + _Out_ BSTR* pwzFullPath, + _Out_ BSTR* pwzVirtualPath +) { - LPWSTR pwzPath = NULL; IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); - if (pApplication != NULL) + + if (pApplication == NULL) { - pwzPath = pApplication->QueryConfig()->QueryApplicationFullPath()->QueryStr(); + return E_FAIL; } - return SysAllocString(pwzPath); + // These should be provided to the in process application as arguments? + *pwzFullPath = SysAllocString(pApplication->QueryConfig()->QueryApplicationFullPath()->QueryStr()); + *pwzVirtualPath = SysAllocString(pApplication->QueryConfig()->QueryApplicationVirtualPath()->QueryStr()); + return S_OK; } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT From 2cd59f86c4161b69f0e4541025f7e19e2dd8225a Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 30 Oct 2017 10:35:56 -0700 Subject: [PATCH 084/107] client disconnect change (#223) --- src/AspNetCore/Inc/forwardinghandler.h | 5 + src/AspNetCore/Src/forwardinghandler.cxx | 123 ++++++++++++++++------- 2 files changed, 94 insertions(+), 34 deletions(-) diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h index eb04a07b30..4a6ecbe451 100644 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ b/src/AspNetCore/Inc/forwardinghandler.h @@ -301,6 +301,11 @@ private: VOID ); + HRESULT + SetHttpSysDisconnectCallback( + VOID + ); + DWORD m_Signature; mutable LONG m_cRefs; diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 763828fa60..48caf98320 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1167,6 +1167,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler( NULL, L"InProcess Application"); } + SetHttpSysDisconnectCallback(); return m_pApplication->ExecuteRequest(m_pW3Context); } case HOSTING_OUT_PROCESS: @@ -1397,9 +1398,11 @@ Failure: &cchANCMHeader))) // first time failure { if (SUCCEEDED(hr = m_pW3Context->CloneContext( - CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, - &m_pChildRequestContext - )) && + CLONE_FLAG_BASICS | + CLONE_FLAG_HEADERS | + CLONE_FLAG_ENTITY | + CLONE_FLAG_SERVER_VARIABLE, + &m_pChildRequestContext)) && SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( STR_ANCM_CHILDREQUEST, L"1")) && @@ -1412,6 +1415,7 @@ Failure: { if (!fCompletionExpected) { + m_pW3Context->SetRequestHandled(); retVal = RQ_NOTIFICATION_CONTINUE; } else @@ -3141,45 +3145,54 @@ FORWARDING_HANDLER::TerminateRequest( bool fClientInitiated ) { - bool fAcquiredLock = FALSE; - - if (TlsGetValue(g_dwTlsIndex) != this) + if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_IN_PROCESS) { - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); - AcquireSRWLockShared(&m_RequestLock); - TlsSetValue(g_dwTlsIndex, this); - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); - fAcquiredLock = TRUE; + // + // Todo: need inform managed layer to abort the request + // } - - if (m_hRequest != NULL) + else { - WinHttpSetStatusCallback(m_hRequest, - FORWARDING_HANDLER::OnWinHttpCompletion, - WINHTTP_CALLBACK_FLAG_HANDLES, - NULL); - if (WinHttpCloseHandle(m_hRequest)) + bool fAcquiredLock = FALSE; + + if (TlsGetValue(g_dwTlsIndex) != this) { - m_hRequest = NULL; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + fAcquiredLock = TRUE; } - m_fHandleClosedDueToClient = fClientInitiated; - } - // - // If the request is a websocket request, initiate cleanup. - // + if (m_hRequest != NULL) + { + WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL); + if (WinHttpCloseHandle(m_hRequest)) + { + m_hRequest = NULL; + } + m_fHandleClosedDueToClient = fClientInitiated; + } - if (m_pWebSocket != NULL) - { - m_pWebSocket->TerminateRequest(); - } + // + // If the request is a websocket request, initiate cleanup. + // - if (fAcquiredLock) - { - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); - TlsSetValue(g_dwTlsIndex, NULL); - ReleaseSRWLockShared(&m_RequestLock); - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + } + + if (fAcquiredLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } } } @@ -3224,3 +3237,45 @@ FORWARDING_HANDLER::FreeResponseBuffers() m_pEntityBuffer = NULL; m_cBytesBuffered = 0; } + +HRESULT +FORWARDING_HANDLER::SetHttpSysDisconnectCallback() +{ + HRESULT hr = S_OK; + IHttpConnection * pClientConnection = m_pW3Context->GetConnection(); + + if (g_fAsyncDisconnectAvailable) + { + m_pDisconnect = static_cast( + pClientConnection->GetModuleContextContainer()-> + GetConnectionModuleContext(g_pModuleId)); + if (m_pDisconnect == NULL) + { + m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT; + if (m_pDisconnect == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pClientConnection->GetModuleContextContainer()-> + SetConnectionModuleContext(m_pDisconnect, + g_pModuleId); + DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); + if (FAILED(hr)) + { + goto Finished; + } + } + + // + // Issue: There is a window of opportunity to miss on the disconnect + // notification if it happens before the SetHandler() call is made. + // It is suboptimal for performance, but should functionally be OK. + // + m_pDisconnect->SetHandler(this); + + Finished: + return hr; + } +} From 192a403b9a782b7b692c9c35a48c0262e8a3ad8b Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Wed, 1 Nov 2017 17:31:38 -0700 Subject: [PATCH 085/107] Jhkim/fix testcodeissues (#229) Fix test issue of incorrect usage of string comparison for testFlags --- .../FunctionalTestHelper.cs | 16 ++++++------ .../Startup.cs | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 7e15e1acf5..45200ac380 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -35,14 +35,14 @@ namespace AspNetCoreModule.Test { get { - if (InitializeTestMachine.GlobalTestFlags.Contains(TestFlags.SkipTest)) + if (TestFlags.Enabled(TestFlags.SkipTest)) { AdditionalInfo = TestFlags.SkipTest + " is set"; return false; } if (_attributeValue == TestFlags.RequireRunAsAdministrator - && !InitializeTestMachine.GlobalTestFlags.Contains(TestFlags.RunAsAdministrator)) + && !TestFlags.Enabled(TestFlags.RunAsAdministrator)) { AdditionalInfo = _attributeValue + " is not belong to the given global test context(" + InitializeTestMachine.GlobalTestFlags + ")"; return false; @@ -849,7 +849,7 @@ namespace AspNetCoreModule.Test string result = string.Empty; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); - Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); + Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders, StringComparison.InvariantCultureIgnoreCase); iisConfig.EnableIISAuthentication(testSite.SiteName, windows: true, basic: false, anonymous: false); Thread.Sleep(500); @@ -883,7 +883,7 @@ namespace AspNetCoreModule.Test } else { - Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); + Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders, StringComparison.InvariantCultureIgnoreCase); result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); Assert.Contains("ImpersonateMiddleware-UserName = NoAuthentication", result); @@ -1219,13 +1219,13 @@ namespace AspNetCoreModule.Test string requestHeaders = string.Empty; requestHeaders = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); requestHeaders = requestHeaders.Replace(" ", ""); - Assert.DoesNotContain(requestHeader.ToLower() + ":", requestHeaders.ToUpper()); + Assert.DoesNotContain(requestHeader + ":", requestHeaders, StringComparison.InvariantCultureIgnoreCase); // Verify https request Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); requestHeader = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); requestHeaders = requestHeaders.Replace(" ", ""); - Assert.DoesNotContain(requestHeader.ToLower() + ":", requestHeaders.ToUpper()); + Assert.DoesNotContain(requestHeader + ":", requestHeaders, StringComparison.InvariantCultureIgnoreCase); // Remove the SSL Certificate mapping iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); @@ -1561,11 +1561,11 @@ namespace AspNetCoreModule.Test // 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); + Assert.DoesNotContain("Connection: Upgrade", frameReturned.Content, StringComparison.InvariantCultureIgnoreCase); //BugBug: Currently we returns 101 here. //Assert.DoesNotContain("HTTP/1.1 101 Switching Protocols", frameReturned.Content); diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index 516dae7b16..a86e1eb3f8 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -318,6 +318,31 @@ namespace AspnetCoreModule.TestSites.Standard 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); + } + } } // Handle shutdown event from ANCM From 49cf5236512e5bc7628fd5d0897f618fbb570530 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 2 Nov 2017 12:04:09 -0700 Subject: [PATCH 086/107] Add location information to ANCM package (#230) --- nuget/AspNetCore.nuspec | 1 + nuget/Microsoft.AspNetCore.AspNetCoreModule.props | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 nuget/Microsoft.AspNetCore.AspNetCoreModule.props diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index 3392bdd549..77232892b1 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -20,5 +20,6 @@ + \ 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..6e7a680584 --- /dev/null +++ b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -0,0 +1,8 @@ + + + + $(MSBuildThisFileDirectory)..\runtimes\win7\native\aspnetcore_x64.dll + $(MSBuildThisFileDirectory)..\runtimes\win7\native\aspnetcore_x86.dll + + + From 483e0e949163195aa2a9fa45eb0a5231119729e0 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Thu, 2 Nov 2017 17:30:18 -0700 Subject: [PATCH 087/107] Upgrade xunit (#231) --- test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj index e366f51373..eba88b8832 100644 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj @@ -22,8 +22,8 @@ - - + + From c315b27ad9f384749aab975f5f698f1929dec7fb Mon Sep 17 00:00:00 2001 From: George Chakhidze <0xfeeddeadbeef@gmail.com> Date: Mon, 6 Nov 2017 22:16:08 +0400 Subject: [PATCH 088/107] Fix the uninitialized g_hWinHttpModule global variable to avoid empty error messages (#225) --- src/AspNetCore/Src/dllmain.cpp | 1 + src/AspNetCore/Src/forwardinghandler.cxx | 29 +++++++++--------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index 8de9227579..1dc7317042 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -191,6 +191,7 @@ HRESULT g_pModuleId = pModuleInfo->GetId(); g_pszModuleName = pModuleInfo->GetName(); g_pHttpServer = pHttpServer; + g_hWinHttpModule = GetModuleHandle(TEXT("winhttp.dll")); // // WinHTTP does not create enough threads, ask it to create more. diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/AspNetCore/Src/forwardinghandler.cxx index 48caf98320..6eee4af412 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/AspNetCore/Src/forwardinghandler.cxx @@ -1451,9 +1451,8 @@ Failure: // pResponse->SetStatus(502, "Bad Gateway", 3, hr); - if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && - hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) - { + 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, @@ -1462,9 +1461,7 @@ Failure: 0, strDescription.QueryStr(), strDescription.QuerySizeCCH(), - NULL); - } - else + NULL) == 0) { LoadString(g_hModule, IDS_SERVER_ERROR, @@ -1774,9 +1771,8 @@ REQUEST_NOTIFICATION_STATUS pResponse->SetStatus(502, "Bad Gateway", 3, hr); - if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && - hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) - { + 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, @@ -1785,15 +1781,14 @@ REQUEST_NOTIFICATION_STATUS 0, strDescription.QueryStr(), strDescription.QuerySizeCCH(), - NULL); - } - else + NULL) == 0) { LoadString(g_hModule, IDS_SERVER_ERROR, strDescription.QueryStr(), strDescription.QuerySizeCCH()); } + (VOID)strDescription.SyncWithBuffer(); if (strDescription.QueryCCH() != 0) { @@ -2379,9 +2374,8 @@ Failure: pResponse->SetStatus(502, "Bad Gateway", 3, hr); - if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && - hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) - { + 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, @@ -2390,15 +2384,14 @@ Failure: 0, strDescription.QueryStr(), strDescription.QuerySizeCCH(), - NULL); - } - else + NULL) == 0) { LoadString(g_hModule, IDS_SERVER_ERROR, strDescription.QueryStr(), strDescription.QuerySizeCCH()); } + strDescription.SyncWithBuffer(); if (strDescription.QueryCCH() != 0) { From 13312109ffa576688cc300e727bfc2b242b1a033 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Mon, 6 Nov 2017 13:47:59 -0800 Subject: [PATCH 089/107] Test: Added a new MiddleWare to test the Gracefulshutdown message (#233) Added TestMiddleWare to handle the Gracefulshutdown message instead of IISMiddleware --- .../IISSetupFilter.cs | 31 +++++++++++++ .../Program.cs | 15 +++++-- .../Startup.cs | 38 ++++++---------- .../TestMiddleWareBeforeIISMiddleWare.cs | 43 +++++++++++++++++++ 4 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 test/AspNetCoreModule.TestSites.Standard/IISSetupFilter.cs create mode 100644 test/AspNetCoreModule.TestSites.Standard/TestMiddleWareBeforeIISMiddleWare.cs 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/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index 89f464b0b2..f7e3a9483a 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -2,13 +2,13 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration; using System; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Threading; -using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; namespace AspnetCoreModule.TestSites.Standard { @@ -88,8 +88,15 @@ namespace AspnetCoreModule.TestSites.Standard else { 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) - .UseIISIntegration() .UseStartup(); } @@ -139,7 +146,7 @@ namespace AspnetCoreModule.TestSites.Standard ); try { - host.Run(); + host.Run(); } catch { diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index a86e1eb3f8..af68d68aa4 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -252,16 +252,16 @@ namespace AspnetCoreModule.TestSites.Standard parameter = "1024"; if (item.Length > action.Length) { - parameter = item.Substring(action.Length); + parameter = item.Substring(action.Length); } - long size = Convert.ToInt32(parameter); - var rnd = new Random(); - byte[] b = new byte[size*1024]; + 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); + MemoryLeakList.Add(b); response = "MemoryLeak, size:" + size.ToString() + " KB, total: " + MemoryLeakList.Count.ToString(); } - + action = "ExpandEnvironmentVariables"; if (item.StartsWith(action)) { @@ -269,7 +269,7 @@ namespace AspnetCoreModule.TestSites.Standard { parameter = item.Substring(action.Length); response = Environment.ExpandEnvironmentVariables("%" + parameter + "%"); - } + } } action = "GetEnvironmentVariables"; @@ -285,9 +285,9 @@ namespace AspnetCoreModule.TestSites.Standard response = String.Empty; foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) - { + { response += de.Key + ":" + de.Value + "
    "; - } + } } action = "GetRequestHeaderValue"; @@ -312,7 +312,7 @@ namespace AspnetCoreModule.TestSites.Standard if (item.StartsWith(action)) { response = String.Empty; - + foreach (var de in context.Request.Headers) { response += de.Key + ":" + de.Value + "
    "; @@ -326,7 +326,7 @@ namespace AspnetCoreModule.TestSites.Standard 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"; + response = "10\r\nManually Chunked\r\n0\r\n\r\n"; parameter = item.Substring(action.Length); // valid encoding value: "chunked" or "chunked,gzip" @@ -334,7 +334,7 @@ namespace AspnetCoreModule.TestSites.Standard var tokens = parameter.Split("_"); // if respons body value was also given after "_" delimeter, use the value as response data. - if (tokens.Length == 2) + if (tokens.Length == 2) { encoding = tokens[0]; response = tokens[1]; @@ -344,20 +344,6 @@ namespace AspnetCoreModule.TestSites.Standard } } } - - // Handle shutdown event from ANCM - if (HttpMethods.IsPost(context.Request.Method) && - //context.Request.Path.Equals(ANCMRequestPath) && - string.Equals("shutdown", context.Request.Headers["MS-ASPNETCORE-EVENT"], StringComparison.OrdinalIgnoreCase)) - { - response = "shutdown"; - string shutdownMode = Environment.GetEnvironmentVariable("GracefulShutdown"); - if (String.IsNullOrEmpty(shutdownMode) || !shutdownMode.ToLower().StartsWith("disabled")) - { - context.Response.StatusCode = StatusCodes.Status202Accepted; - Program.AappLifetime.StopApplication(); - } - } return context.Response.WriteAsync(response); }); } 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); + } + } +} From 6bfcd4a2d46808a8ae2e810a7f898ca4b30b45b7 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 6 Nov 2017 20:28:08 -0800 Subject: [PATCH 090/107] Remove header serialization in favor of setting IIS response directly. (#228) --- src/AspNetCore/Src/managedexports.cxx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/AspNetCore/Src/managedexports.cxx b/src/AspNetCore/Src/managedexports.cxx index b0a31daa5f..a44980f476 100644 --- a/src/AspNetCore/Src/managedexports.cxx +++ b/src/AspNetCore/Src/managedexports.cxx @@ -355,4 +355,30 @@ http_cancel_io( return pHttpContext->CancelIo(); } +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_response_set_unknown_header( + _In_ IHttpContext* pHttpContext, + _In_ PCSTR pszHeaderName, + _In_ PCSTR pszHeaderValue, + _In_ USHORT usHeaderValueLength, + _In_ BOOL fReplace +) +{ + return pHttpContext->GetResponse()->SetHeader( pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace ); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_response_set_known_header( + _In_ IHttpContext* pHttpContext, + _In_ HTTP_HEADER_ID dwHeaderId, + _In_ PCSTR pszHeaderValue, + _In_ USHORT usHeaderValueLength, + _In_ BOOL fReplace +) +{ + return pHttpContext->GetResponse()->SetHeader( dwHeaderId, pszHeaderValue, usHeaderValueLength, fReplace ); +} + // End of export \ No newline at end of file From b9ed1e073af826432a6145a5233d89aca4f8eb09 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 7 Nov 2017 13:56:01 -0800 Subject: [PATCH 091/107] Require Microsoft.VisualStudio.Component.VC.Tools.x86.x64 to be installed before compiling ANCM (#238) --- build/repo.targets | 26 +++++++++----------------- korebuild.json | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 korebuild.json diff --git a/build/repo.targets b/build/repo.targets index 0faa63a2b7..43afd316c8 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -4,26 +4,18 @@ $(VerifyDependsOn);PublishPackage - - - - - - - - - %(MSBuild15ExePaths.FullPath) - - - - + - + + + + @@ -48,4 +40,4 @@ ApiKey="$(APIKey)" /> -
    +
    \ No newline at end of file diff --git a/korebuild.json b/korebuild.json new file mode 100644 index 0000000000..80e6e41c09 --- /dev/null +++ b/korebuild.json @@ -0,0 +1,14 @@ +{ + "$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 From 89f6f16ba5e15f55e2bbb1b2be6edba1c7e7972b Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 7 Nov 2017 14:25:06 -0800 Subject: [PATCH 092/107] Disable tests for continuous integration. (#234) --- .gitignore | 1 - korebuild-lock.txt | 2 + .../Framework/InitializeTestMachine.cs | 2 +- test/AspNetCoreModule.Test/FunctionalTest.cs | 58 ++++++++++--------- .../FunctionalTestHelper.cs | 2 +- 5 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 korebuild-lock.txt diff --git a/.gitignore b/.gitignore index fa7a3504ea..c356cf3416 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,4 @@ src/AspNetCore/version.h *.VC.*db global.json -korebuild-lock.txt diff --git a/korebuild-lock.txt b/korebuild-lock.txt new file mode 100644 index 0000000000..7c709d9092 --- /dev/null +++ b/korebuild-lock.txt @@ -0,0 +1,2 @@ +version:2.1.0-preview1-15552 +commithash:4af2f444ec8cba0faa866dd867370e6aa6df69ea diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 9fc4d086c2..fa8f3a11c6 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -27,7 +27,7 @@ namespace AspNetCoreModule.Test.Framework public static bool Enabled(string flagValue) { - return InitializeTestMachine.GlobalTestFlags.Contains(flagValue.ToLower()); + return InitializeTestMachine.GlobalTestFlags.IndexOf(flagValue, StringComparison.OrdinalIgnoreCase) > -1; } } diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 7f5c754a5b..2505b9f039 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -11,8 +11,10 @@ namespace AspNetCoreModule.Test { public class FunctionalTest : FunctionalTestHelper, IClassFixture { + private const string ANCMTestCondition = TestFlags.SkipTest; + [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange)] @@ -23,7 +25,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5)] @@ -36,7 +38,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19, false)] @@ -57,7 +59,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -70,7 +72,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -81,7 +83,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -92,7 +94,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -103,7 +105,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -114,7 +116,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -125,7 +127,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("ANCMTestBar", "bar", "bar", IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -142,7 +144,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -153,7 +155,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -164,7 +166,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789")] @@ -175,7 +177,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -186,7 +188,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 10)] @@ -197,7 +199,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "00:02:00")] @@ -210,7 +212,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -221,7 +223,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "dotnet.exe", "./")] @@ -234,7 +236,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -247,7 +249,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true, true)] @@ -260,7 +262,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -271,7 +273,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -282,7 +284,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE", "f")] @@ -297,7 +299,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, true)] @@ -309,7 +311,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.noChange, false, DoAppVerifierTest_StartUpMode.UseGracefulShutdown, DoAppVerifierTest_ShutDownMode.RecycleAppPool, 1)] @@ -325,7 +327,7 @@ namespace AspNetCoreModule.Test ////////////////////////////////////////////////////////// [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit)] @@ -339,7 +341,7 @@ namespace AspNetCoreModule.Test // NOTE: below test scenarios are not valid for Win7 OS ////////////////////////////////////////////////////////// [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [ANCMTestFlags(ANCMTestCondition)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "IIS does not support Websocket on Win7")] @@ -353,7 +355,7 @@ namespace AspNetCoreModule.Test } [ConditionalTheory] - [ANCMTestFlags(TestFlags.RequireRunAsAdministrator)] + [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")] diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 45200ac380..b35d93e745 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -35,7 +35,7 @@ namespace AspNetCoreModule.Test { get { - if (TestFlags.Enabled(TestFlags.SkipTest)) + if (_attributeValue == TestFlags.SkipTest) { AdditionalInfo = TestFlags.SkipTest + " is set"; return false; From bd18430428beba00f6dcebdd0a18e822a0214dd8 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 7 Nov 2017 16:43:23 -0800 Subject: [PATCH 093/107] Added StartupWithIStartupFilter (#236) Added StartupWithIStartupFilter --- .../FunctionalTestHelper.cs | 1 + .../Program.cs | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index b35d93e745..d7381be4d1 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -718,6 +718,7 @@ namespace AspNetCoreModule.Test 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"; } diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index f7e3a9483a..c7c10424c9 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -80,14 +80,9 @@ namespace AspnetCoreModule.TestSites.Standard .UseIISIntegration() .UseStartup(); } - else + else if (startUpClassString == "StartupWithShutdownDisabled") { - throw new System.Exception("Invalid startup class name : " + startUpClassString); - } - } - else - { - builder = new WebHostBuilder() + builder = new WebHostBuilder() .ConfigureServices(services => { const string PairingToken = "TOKEN"; @@ -98,6 +93,18 @@ namespace AspnetCoreModule.TestSites.Standard }) .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"); From 618d3dabee0830a3fa073cc524be3b867b3ca348 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 8 Nov 2017 16:02:52 -0800 Subject: [PATCH 094/107] Modifies ANCM DLL location based on folder rather than name. (#240) --- nuget/AspNetCore.nuspec | 4 ++-- nuget/Microsoft.AspNetCore.AspNetCoreModule.props | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index 77232892b1..9ae12a82a0 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -15,8 +15,8 @@ Microsoft.AspNetCore.AspNetCoreModule - - + + diff --git a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props index 6e7a680584..5e0df1fdab 100644 --- a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props +++ b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -1,8 +1,8 @@ - $(MSBuildThisFileDirectory)..\runtimes\win7\native\aspnetcore_x64.dll - $(MSBuildThisFileDirectory)..\runtimes\win7\native\aspnetcore_x86.dll + $(MSBuildThisFileDirectory)..\runtimes\win7\native\x64\aspnetcore.dll + $(MSBuildThisFileDirectory)..\runtimes\win7\native\x86\aspnetcore.dll From 82e096c9a5060ca4a7f4d6768aa9505e1238904b Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 10 Nov 2017 17:07:56 -0800 Subject: [PATCH 095/107] Adds windows auth support (#241) --- src/AspNetCore/Src/managedexports.cxx | 45 ++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/AspNetCore/Src/managedexports.cxx b/src/AspNetCore/Src/managedexports.cxx index a44980f476..d05df89705 100644 --- a/src/AspNetCore/Src/managedexports.cxx +++ b/src/AspNetCore/Src/managedexports.cxx @@ -135,22 +135,38 @@ http_get_completion_info( // 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_paths( - _Out_ BSTR* pwzFullPath, - _Out_ BSTR* pwzVirtualPath +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; } - // These should be provided to the in process application as arguments? - *pwzFullPath = SysAllocString(pApplication->QueryConfig()->QueryApplicationFullPath()->QueryStr()); - *pwzVirtualPath = SysAllocString(pApplication->QueryConfig()->QueryApplicationVirtualPath()->QueryStr()); + + pConfiguration = pApplication->QueryConfig(); + + pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationFullPath()->QueryStr()); + pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr()); + pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled(); + pIISCofigurationData->fBasicAuthEnabled = pConfiguration->QueryBasicAuthEnabled(); + pIISCofigurationData->fAnonymousAuthEnable = pConfiguration->QueryAnonymousAuthEnabled(); + return S_OK; } @@ -381,4 +397,19 @@ http_response_set_known_header( return pHttpContext->GetResponse()->SetHeader( dwHeaderId, pszHeaderValue, usHeaderValueLength, fReplace ); } +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_get_authentication_information( + _In_ IHttpContext* pHttpContext, + _Out_ BSTR* pstrAuthType, + _Out_ VOID** pvToken +) +{ + *pstrAuthType = SysAllocString(pHttpContext->GetUser()->GetAuthenticationType()); + *pvToken = pHttpContext->GetUser()->GetPrimaryToken(); + + return S_OK; +} + + // End of export \ No newline at end of file From c12c938d4a69b08f7ea7a133f5139882c4f8caca Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 13 Nov 2017 15:29:07 -0800 Subject: [PATCH 096/107] Copy aspnetcore.dll to contentfiles and re-add old nuget locations. (#245) --- nuget/AspNetCore.nuspec | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index 9ae12a82a0..a91aeee733 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -1,5 +1,5 @@  - + Microsoft.AspNetCore.AspNetCoreModule Microsoft ASP.NET Core Module @@ -13,10 +13,13 @@ ASP.NET Core Module en-US Microsoft.AspNetCore.AspNetCoreModule + + + - - + + From 9e345ad43fb60ec99c1d3e95ab5f6e9d5e348241 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Wed, 15 Nov 2017 21:59:54 -0800 Subject: [PATCH 097/107] fix build issue (#248) --- AspNetCoreModule.sln | 26 +++++++++++++------------- src/AspNetCore/AspNetCore.vcxproj | 11 ++++++----- src/AspNetCore/Src/dllmain.cpp | 7 ------- src/IISLib/IISLib.vcxproj | 11 ++++++----- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index ff2f980680..fa80b87d35 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26815.3 +VisualStudioVersion = 15.0.27107.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" ProjectSection(ProjectDependencies) = postProject @@ -36,25 +36,25 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.ActiveCfg = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = 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}.Release|Any CPU.ActiveCfg = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.ActiveCfg = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.Build.0 = Release|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.ActiveCfg = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = 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}.Release|Any CPU.ActiveCfg = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.ActiveCfg = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.Build.0 = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|x64 {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Win32.ActiveCfg = Debug|Any CPU diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index ba93deb7e2..005f0c5d5a 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -96,13 +96,14 @@ NotUsing - Level3 + Level4 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) precomp.hxx $(IntDir)$(TargetName).pch ..\IISLib;.\Inc ProgramDatabase + MultiThreadedDebug Windows @@ -113,7 +114,7 @@ - Level3 + Level4 NotUsing MaxSpeed true @@ -121,7 +122,7 @@ WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) ..\IISLib;inc precomp.hxx - MultiThreadedDLL + MultiThreaded Windows @@ -134,7 +135,7 @@ - Level3 + Level4 NotUsing MaxSpeed true @@ -142,7 +143,7 @@ WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) precomp.hxx ..\IISLib;inc - MultiThreadedDLL + MultiThreaded Windows diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index 1dc7317042..e25cc49623 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -4,13 +4,6 @@ #include "precomp.hxx" #include -#ifdef DEBUG - DECLARE_DEBUG_PRINTS_OBJECT(); - DECLARE_DEBUG_VARIABLE(); - DECLARE_PLATFORM_TYPE(); -#endif // DEBUG - - HTTP_MODULE_ID g_pModuleId = NULL; IHttpServer * g_pHttpServer = NULL; BOOL g_fAsyncDisconnectAvailable = FALSE; diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj index 296e723b3f..4f9f36678a 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -88,11 +88,12 @@ - Level3 + Level4 Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase + MultiThreadedDebug Windows @@ -101,7 +102,7 @@ - Level3 + Level4 MaxSpeed @@ -109,7 +110,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - MultiThreadedDLL + MultiThreaded Windows @@ -120,7 +121,7 @@ - Level3 + Level4 MaxSpeed @@ -128,7 +129,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - MultiThreadedDLL + MultiThreaded Windows From 85ea220c4e383e906ab1478ce1617908851a741e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 16 Nov 2017 15:05:32 -0800 Subject: [PATCH 098/107] Fixes dotnet.exe string as runtime now reads it (#246) --- src/AspNetCore/Src/inprocessapplication.cxx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/AspNetCore/Src/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx index f57518b7e4..73430009e7 100644 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ b/src/AspNetCore/Src/inprocessapplication.cxx @@ -579,12 +579,6 @@ IN_PROCESS_APPLICATION::ExecuteApplication( } // The first argument is mostly ignored - hr = strDotnetExeLocation.Append(pszDotnetExeString); - if (FAILED(hr)) - { - goto Finished; - } - argv[0] = strDotnetExeLocation.QueryStr(); PATH::ConvertPathToFullPath(m_pConfiguration->QueryArguments()->QueryStr(), m_pConfiguration->QueryApplicationFullPath()->QueryStr(), From 34e3f3debc139aecc22283e73ee3116752cfa0a2 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 16 Nov 2017 16:00:12 -0800 Subject: [PATCH 099/107] Update build configuration (#237) --- AspNetCoreModule.sln | 25 ++++------------- Directory.Build.props | 13 +++++++++ build/Build.Settings | 1 - build/Version.props | 9 ------ build/common.props | 23 --------------- build/dependencies.props | 24 ++++++++++++---- build/repo.props | 10 +++++-- korebuild-lock.txt | 4 +-- .../AspNetCoreModule.Test.csproj | 28 +++++++++---------- ...AspNetCoreModule.TestSites.Standard.csproj | 2 +- test/Directory.Build.props | 7 +++++ .../WebSocketClientEXE.csproj | 11 -------- version.props | 12 ++++++++ version.xml | 6 ---- 14 files changed, 79 insertions(+), 96 deletions(-) create mode 100644 Directory.Build.props delete mode 100644 build/Version.props delete mode 100644 build/common.props create mode 100644 test/Directory.Build.props create mode 100644 version.props delete mode 100644 version.xml diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln index fa80b87d35..a6d854e59b 100644 --- a/AspNetCoreModule.sln +++ b/AspNetCoreModule.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27107.1 +VisualStudioVersion = 15.0.27110.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" ProjectSection(ProjectDependencies) = postProject @@ -23,8 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution NuGet.Config = NuGet.Config EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketClientEXE", "test\WebSocketClientEXE\WebSocketClientEXE.csproj", "{4062EA94-75F5-4691-86DC-C8594BA896DE}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,8 +34,8 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Debug|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = Release|x64 {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}.Release|Any CPU.ActiveCfg = Release|Win32 @@ -46,8 +44,8 @@ Global {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Debug|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = Release|x64 {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}.Release|Any CPU.ActiveCfg = Release|Win32 @@ -79,18 +77,6 @@ Global {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Win32.Build.0 = Release|Any CPU {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.ActiveCfg = Release|Any CPU {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.Build.0 = Release|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Win32.ActiveCfg = Debug|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|Win32.Build.0 = Debug|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|x64.ActiveCfg = Debug|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Debug|x64.Build.0 = Debug|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Any CPU.Build.0 = Release|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Win32.ActiveCfg = Release|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|Win32.Build.0 = Release|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|x64.ActiveCfg = Release|Any CPU - {4062EA94-75F5-4691-86DC-C8594BA896DE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -100,7 +86,6 @@ Global {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} {4DDA7560-AA29-4161-A5EA-A7E8F3997321} = {02F461DC-5166-4E88-AAD5-CF110016A647} {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {02F461DC-5166-4E88-AAD5-CF110016A647} - {4062EA94-75F5-4691-86DC-C8594BA896DE} = {02F461DC-5166-4E88-AAD5-CF110016A647} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0967E9B4-FEE7-40D7-860A-23E340E65840} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000000..84b1730184 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + + + + Microsoft ASP.NET Core + https://github.com/aspnet/AspNetCoreModule + git + $(MSBuildThisFileDirectory)build\Key.snk + true + true + + \ No newline at end of file diff --git a/build/Build.Settings b/build/Build.Settings index ae2912343a..371ec56452 100644 --- a/build/Build.Settings +++ b/build/Build.Settings @@ -1,6 +1,5 @@ - $(MSBuildThisFileDirectory)..\ Debug diff --git a/build/Version.props b/build/Version.props deleted file mode 100644 index f5ae3f4b01..0000000000 --- a/build/Version.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - 7 - 1 - 1987 - -RTM - - diff --git a/build/common.props b/build/common.props deleted file mode 100644 index 940c1db088..0000000000 --- a/build/common.props +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Microsoft ASP.NET Core - https://github.com/aspnet/AspNetCoreModule - git - $(MSBuildThisFileDirectory)Key.snk - true - true - $(VersionSuffix)-$(BuildNumber) - - - - RunConfiguration.TargetPlatform=x64 - - - - - - - diff --git a/build/dependencies.props b/build/dependencies.props index 37991cfb7d..3524f8edcb 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,9 +1,21 @@ - + - 1.2.0-* - 2.1.1-* - 15.3.0 - 0.6.1 - 2.3.0-beta4-build3742 + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + 2.1.0-preview1-15549 + 2.0.0 + 2.1.0-preview1-27424 + 2.0.0 + 2.0.0 + 2.0.0 + 1.1.0 + 2.0.0 + 15.3.0 + 7.0.0 + 10.0.10586 + 4.4.0 + 2.3.1 + 2.3.1 \ No newline at end of file diff --git a/build/repo.props b/build/repo.props index f38e6b5141..9df3355343 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,11 +1,17 @@ - + - RunConfiguration.TargetPlatform=x64 + x64 + + + + + Internal.AspNetCore.Universe.Lineup + https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 7c709d9092..5f7f568497 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview1-15552 -commithash:4af2f444ec8cba0faa866dd867370e6aa6df69ea +version:2.1.0-preview1-15555 +commithash:97b60e251fce2680a08866ab977aa7e3b4f92b86 diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj index eba88b8832..924806ae08 100644 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj @@ -1,8 +1,6 @@  - - - net46 + net461 true true AspNetCoreModule.Test @@ -19,18 +17,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj index ad4431d629..3cac8887c4 100644 --- a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -3,6 +3,6 @@ netcoreapp2.0 - + diff --git a/test/Directory.Build.props b/test/Directory.Build.props new file mode 100644 index 0000000000..e9a0d8089f --- /dev/null +++ b/test/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/WebSocketClientEXE/WebSocketClientEXE.csproj b/test/WebSocketClientEXE/WebSocketClientEXE.csproj index 67a5fa5efe..486d2188af 100644 --- a/test/WebSocketClientEXE/WebSocketClientEXE.csproj +++ b/test/WebSocketClientEXE/WebSocketClientEXE.csproj @@ -1,17 +1,6 @@  - - Debug - AnyCPU - {4062EA94-75F5-4691-86DC-C8594BA896DE} - Exe - WebSocketClientEXE - WebSocketClientEXE - v4.6 - 512 - true - AnyCPU true diff --git a/version.props b/version.props new file mode 100644 index 0000000000..3b7fe5c4ce --- /dev/null +++ b/version.props @@ -0,0 +1,12 @@ + + + 7 + 1 + 1987 + -RTM + $(VersionPrefix) + $(VersionPrefix)-$(VersionSuffix)-final + t000 + $(VersionSuffix)-$(BuildNumber) + + \ No newline at end of file diff --git a/version.xml b/version.xml deleted file mode 100644 index ebc4d03121..0000000000 --- a/version.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - dev - - From 2e14ec8f9bdf12897c8c7a3e27e6c610e99e6e15 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 30 Nov 2017 10:20:46 -0800 Subject: [PATCH 100/107] Update AspNetCoreModuleX64Location and X86. (#264) --- nuget/Microsoft.AspNetCore.AspNetCoreModule.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props index 5e0df1fdab..7a761813b4 100644 --- a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props +++ b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -1,8 +1,8 @@ - $(MSBuildThisFileDirectory)..\runtimes\win7\native\x64\aspnetcore.dll - $(MSBuildThisFileDirectory)..\runtimes\win7\native\x86\aspnetcore.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcore.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcore.dll From c94685e470001d56d9fa42cdd463140c5a83487c Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 4 Dec 2017 15:08:56 -0800 Subject: [PATCH 101/107] Use aspnetcore-dev --- Directory.Build.props | 3 ++- NuGet.config | 6 ++---- build/dependencies.props | 2 +- build/repo.props | 2 +- build/sources.props | 16 ++++++++++++++++ korebuild-lock.txt | 4 ++-- 6 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 build/sources.props diff --git a/Directory.Build.props b/Directory.Build.props index 84b1730184..d696002cb8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,7 @@ + Microsoft ASP.NET Core @@ -10,4 +11,4 @@ true true - \ No newline at end of file + diff --git a/NuGet.config b/NuGet.config index f495268808..e32bddfd51 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,8 +2,6 @@ - - - + - \ No newline at end of file + diff --git a/build/dependencies.props b/build/dependencies.props index 3524f8edcb..0fc2c94e6c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,7 +5,7 @@ 2.1.0-preview1-15549 2.0.0 - 2.1.0-preview1-27424 + 2.1.0-preview1-27644 2.0.0 2.0.0 2.0.0 diff --git a/build/repo.props b/build/repo.props index 9df3355343..0969ca492d 100644 --- a/build/repo.props +++ b/build/repo.props @@ -12,6 +12,6 @@ Internal.AspNetCore.Universe.Lineup - https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json diff --git a/build/sources.props b/build/sources.props new file mode 100644 index 0000000000..05a4b7dc2e --- /dev/null +++ b/build/sources.props @@ -0,0 +1,16 @@ + + + + + $(DotNetRestoreSources) + + $(RestoreSources); + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; + https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; + + + $(RestoreSources); + https://api.nuget.org/v3/index.json; + + + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 5f7f568497..fe4a961da3 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview1-15555 -commithash:97b60e251fce2680a08866ab977aa7e3b4f92b86 +version:2.1.0-preview1-15620 +commithash:6432b49a2c00310416df39b6fe548ef4af9c6011 From 9a1cc4ba1ca64be83f2981785ae256091ef0fec4 Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Tue, 12 Dec 2017 16:53:36 -0800 Subject: [PATCH 102/107] clean up test framework utility functions (#273) --- .../Framework/IISConfigUtility.cs | 5 + .../Framework/TestWebSite.cs | 11 +- test/AspNetCoreModule.Test/FunctionalTest.cs | 1 + .../FunctionalTestHelper.cs | 377 +++++++++--------- 4 files changed, 202 insertions(+), 192 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs index 79557281ed..2286c63b4d 100644 --- a/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/IISConfigUtility.cs @@ -685,6 +685,11 @@ namespace AspNetCoreModule.Test.Framework 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; diff --git a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs index af7098f697..21e1c9cfb3 100644 --- a/test/AspNetCoreModule.Test/Framework/TestWebSite.cs +++ b/test/AspNetCoreModule.Test/Framework/TestWebSite.cs @@ -292,7 +292,7 @@ namespace AspNetCoreModule.Test.Framework string appPoolName = null; if (IisServerType == ServerType.IIS) { - appPoolName = siteName; + appPoolName = "AspNetCoreModuleTestAppPool"; } else if (IisServerType == ServerType.IISExpress) { @@ -461,7 +461,14 @@ namespace AspNetCoreModule.Test.Framework } else { - debuggerCmdline = Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramFiles%"), "Debugging Tools for Windows (x64)", "windbg.exe"); + 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."); diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 2505b9f039..0268367dd0 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -12,6 +12,7 @@ namespace AspNetCoreModule.Test public class FunctionalTest : FunctionalTestHelper, IClassFixture { private const string ANCMTestCondition = TestFlags.SkipTest; + //private const string ANCMTestCondition = TestFlags.RunAsAdministrator; [ConditionalTheory] [ANCMTestFlags(ANCMTestCondition)] diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index d7381be4d1..4094e3f477 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -69,15 +69,7 @@ namespace AspNetCoreModule.Test } private const int _repeatCount = 3; - - public enum ReturnValueType - { - ResponseBody, - ResponseBodyAndHeaders, - ResponseStatus, - None - } - + public static async Task DoBasicTest(IISConfigUtility.AppPoolBitness appPoolBitness) { using (var testSite = new TestWebSite(appPoolBitness, "DoBasicTest")) @@ -87,7 +79,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(3000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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", "")); @@ -120,7 +112,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(1000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -155,7 +147,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(1000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -194,7 +186,7 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(1000); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; @@ -231,7 +223,7 @@ namespace AspNetCoreModule.Test Thread.Sleep(1100); string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; - string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.RootAppContext.GetUri(urlForUrlRewrite))).ResponseBody; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; @@ -264,7 +256,7 @@ namespace AspNetCoreModule.Test Thread.Sleep(1000); string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; - string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.RootAppContext.GetUri(urlForUrlRewrite))).ResponseBody; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; @@ -293,8 +285,8 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; Thread.Sleep(500); - string totalNumber = await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK); - Assert.True(totalNumber == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK))); + string totalNumber = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody; + Assert.True(totalNumber == (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody); iisConfig.SetANCMConfig( testSite.SiteName, @@ -309,8 +301,8 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); int expectedValue = Convert.ToInt32(totalNumber) + 1; - string totalResult = (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK)); - Assert.True(expectedValue.ToString() == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK))); + string totalResult = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody; + Assert.True(expectedValue.ToString() == (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody); bool setEnvironmentVariableConfiguration = true; @@ -357,13 +349,13 @@ namespace AspNetCoreModule.Test // check JitDebugger before continuing TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); - totalResult = (await GetResponse(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"), HttpStatusCode.OK)); + totalResult = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetEnvironmentVariables"))).ResponseBody; Assert.True(expectedValue.ToString() == totalResult); - Assert.True("foo" == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariablesANCMTestFoo"), HttpStatusCode.OK))); - Assert.True(expectedEnvironmentVariableValue == (await GetResponse(testSite.AspNetCoreApp.GetUri("ExpandEnvironmentVariables" + environmentVariableName), HttpStatusCode.OK))); + 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 GetResponse(testSite.AspNetCoreApp.GetUri("DumpEnvironmentVariables"), HttpStatusCode.OK)); + 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); @@ -400,7 +392,7 @@ namespace AspNetCoreModule.Test Thread.Sleep(1100); // verify 503 - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); + 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); @@ -409,7 +401,7 @@ namespace AspNetCoreModule.Test // rename app_offline.htm to _app_offline.htm and verify 200 testSite.AspNetCoreApp.MoveFile("App_Offline.Htm", "_App_Offline.Htm"); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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); @@ -442,7 +434,7 @@ namespace AspNetCoreModule.Test // verify 503 string urlForUrlRewrite = testSite.URLRewriteApp.URL + "/Rewrite2/" + testSite.AspNetCoreApp.URL + "/GetProcessId"; - await VerifyResponseBody(testSite.RootAppContext.GetUri(urlForUrlRewrite), fileContent + "\r\n", HttpStatusCode.ServiceUnavailable); + 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); @@ -451,7 +443,7 @@ namespace AspNetCoreModule.Test // delete app_offline.htm and verify 200 testSite.AspNetCoreApp.DeleteFile("App_Offline.Htm"); - string backendProcessId = await GetResponse(testSite.RootAppContext.GetUri(urlForUrlRewrite), HttpStatusCode.OK); + 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); @@ -470,12 +462,12 @@ namespace AspNetCoreModule.Test { var postFormData = new[] { - new KeyValuePair("FirstName", "Mickey"), - new KeyValuePair("LastName", "Mouse"), - new KeyValuePair("TestData", testData), - }; + new KeyValuePair("FirstName", "Mickey"), + new KeyValuePair("LastName", "Mouse"), + new KeyValuePair("TestData", testData), + }; var expectedResponseBody = "FirstName=Mickey&LastName=Mouse&TestData=" + testData; - await VerifyPostResponseBody(testSite.AspNetCoreApp.GetUri("EchoPostData"), postFormData, expectedResponseBody, HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri("EchoPostData"), postData: postFormData, expectedResponseBody: expectedResponseBody); } } @@ -501,7 +493,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "disableStartUpErrorPage", true); iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "processPath", errorMessageContainThis); - var responseBody = await GetResponse(testSite.AspNetCoreApp.GetUri(), HttpStatusCode.BadGateway); + var responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseStatus:HttpStatusCode.BadGateway)).ResponseBody; responseBody = responseBody.Replace("\r", "").Replace("\n", "").Trim(); Assert.True(responseBody == curstomErrorMessage); @@ -518,7 +510,7 @@ namespace AspNetCoreModule.Test // check JitDebugger before continuing TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); - responseBody = await GetResponse(testSite.AspNetCoreApp.GetUri(), HttpStatusCode.BadGateway); + responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseStatus:HttpStatusCode.BadGateway)).ResponseBody; Assert.Contains("808681", responseBody); // verify event error log @@ -551,7 +543,9 @@ namespace AspNetCoreModule.Test DateTime startTimeInsideLooping = DateTime.Now; Thread.Sleep(50); - var statusCode = await GetResponseStatusCode(testSite.AspNetCoreApp.GetUri("GetProcessId")); + 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()); @@ -560,7 +554,7 @@ namespace AspNetCoreModule.Test break; } - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.NotEqual(backendProcessId_old, backendProcessId); backendProcessId_old = backendProcessId; var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); @@ -598,7 +592,7 @@ namespace AspNetCoreModule.Test for (int i = 0; i < 20; i++) { - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; int id = Convert.ToInt32(backendProcessId); if (!processIDs.Contains(id)) { @@ -631,7 +625,7 @@ namespace AspNetCoreModule.Test for (int i = 0; i < 20; i++) { - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; int id = Convert.ToInt32(backendProcessId); if (!processIDs.Contains(id)) { @@ -665,11 +659,11 @@ namespace AspNetCoreModule.Test Thread.Sleep(500); if (startupTimeLimit < startupDelay) { - await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep3000"), HttpStatusCode.BadGateway); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep3000"), HttpStatusCode.BadGateway); } else { - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep3000"), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep3000"), expectedResponseBody: "Running"); } } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -687,11 +681,11 @@ namespace AspNetCoreModule.Test if (requestTimeout.ToString() == "00:02:00") { - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep65000"), "Running", HttpStatusCode.OK, timeout: 70); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep65000"), expectedResponseBody: "Running", timeout: 70); } else if (requestTimeout.ToString() == "00:01:00") { - await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, 70); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, timeout: 70); } else { @@ -722,10 +716,10 @@ namespace AspNetCoreModule.Test expectedGracefulShutdownResponseStatusCode = "200"; } - string response = await GetResponse(testSite.AspNetCoreApp.GetUri(""), HttpStatusCode.OK); + string response = (await SendReceive(testSite.AspNetCoreApp.GetUri(""))).ResponseBody; Assert.True(response == "Running"); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + 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 @@ -736,9 +730,9 @@ namespace AspNetCoreModule.Test var difference = endTime - startTime2; Assert.True(difference.Seconds >= expectedClosingTime); Assert.True(difference.Seconds < expectedClosingTime + 3); - string newBackendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string newBackendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.True(backendProcessId != newBackendProcessId); - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + 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) @@ -768,7 +762,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogFile", @".\logs\stdout"); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; string logPath = testSite.AspNetCoreApp.GetDirectoryPathWith("logs"); Assert.False(Directory.Exists(logPath)); @@ -779,7 +773,7 @@ namespace AspNetCoreModule.Test // verify the log file is not created because backend process is not recycled Assert.True(Directory.GetFiles(logPath).Length == 0); - Assert.True(backendProcessId == (await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK))); + 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); @@ -794,7 +788,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "stdoutLogEnabled", true); - Assert.True(backendProcessId != (await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK))); + 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)); @@ -811,7 +805,7 @@ namespace AspNetCoreModule.Test using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { string arguments = argumentsPrefix + testSite.AspNetCoreApp.GetArgumentFileName(); - string tempProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string tempProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; var tempBackendProcess = Process.GetProcessById(Convert.ToInt32(tempProcessId)); // replace $env with the actual test value @@ -833,7 +827,7 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); Thread.Sleep(500); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMStartEvent(arg1, arg2), startTime, backendProcessId)); } @@ -847,9 +841,9 @@ namespace AspNetCoreModule.Test { using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { - string result = string.Empty; + string responseBody = string.Empty; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "forwardWindowsAuthToken", enabledForwardWindowsAuthToken); - string requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); + 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); @@ -859,23 +853,23 @@ namespace AspNetCoreModule.Test TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); Thread.Sleep(500); - requestHeaders = await GetResponse(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), HttpStatusCode.OK); + requestHeaders = (await SendReceive(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"))).ResponseBody; if (enabledForwardWindowsAuthToken) { Assert.Contains("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders.ToUpper()); - result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); + responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"))).ResponseBody; bool compare = false; string expectedValue1 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERDOMAIN%") + "\\" + Environment.ExpandEnvironmentVariables("%USERNAME%"); - if (result.ToLower().Contains(expectedValue1.ToLower())) + if (responseBody.ToLower().Contains(expectedValue1.ToLower())) { compare = true; } string expectedValue2 = "ImpersonateMiddleware-UserName = " + Environment.ExpandEnvironmentVariables("%USERNAME%"); - if (result.ToLower().Contains(expectedValue2.ToLower())) + if (responseBody.ToLower().Contains(expectedValue2.ToLower())) { compare = true; } @@ -886,8 +880,8 @@ namespace AspNetCoreModule.Test { Assert.DoesNotContain("MS-ASPNETCORE-WINAUTHTOKEN", requestHeaders, StringComparison.InvariantCultureIgnoreCase); - result = await GetResponse(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"), HttpStatusCode.OK); - Assert.Contains("ImpersonateMiddleware-UserName = NoAuthentication", result); + responseBody = (await SendReceive(testSite.AspNetCoreApp.GetUri("ImpersonateMiddleware"))).ResponseBody; + Assert.Contains("ImpersonateMiddleware-UserName = NoAuthentication", responseBody); } } @@ -909,10 +903,10 @@ namespace AspNetCoreModule.Test { // allocating 1024,000 KB - await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("MemoryLeak1024000"), HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri("MemoryLeak1024000")); // get backend process id - string pocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string pocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; // get process id of IIS worker process (w3wp.exe) string userName = testSite.SiteName; @@ -946,7 +940,7 @@ namespace AspNetCoreModule.Test iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "rapidFailsPerMinute", 100); Thread.Sleep(3000); - await VerifyResponseStatus(testSite.RootAppContext.GetUri("small.htm"), HttpStatusCode.OK); + await SendReceive(testSite.RootAppContext.GetUri("small.htm")); Thread.Sleep(1000); int x = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); @@ -957,14 +951,14 @@ namespace AspNetCoreModule.Test // check JitDebugger before continuing foundVSJit = TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); - await VerifyResponseStatus(testSite.RootAppContext.GetUri("small.htm"), HttpStatusCode.OK); + 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 GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backupPocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; string newPocessIdBackendProcess = backupPocessIdBackendProcess; // Verify IIS recycling happens while there is memory leak @@ -974,9 +968,9 @@ namespace AspNetCoreModule.Test foundVSJit = TestUtility.ResetHelper(ResetHelperMode.KillVSJitDebugger); // allocating 2048,000 KB - await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("MemoryLeak2048000"), HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri("MemoryLeak2048000")); - newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + newPocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; if (foundVSJit || backupPocessIdBackendProcess != newPocessIdBackendProcess) { // worker process is recycled expectedly and backend process is recycled together @@ -1003,7 +997,7 @@ namespace AspNetCoreModule.Test z = Convert.ToInt32(TestUtility.GetProcessWMIAttributeValue("w3wp.exe", "Handle", userName)); Assert.True(x != z, "worker process is recycled"); - newPocessIdBackendProcess = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + newPocessIdBackendProcess = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.True(backupPocessIdBackendProcess != newPocessIdBackendProcess, "backend process is recycled"); } testSite.AspNetCoreApp.RestoreFile("web.config"); @@ -1042,34 +1036,34 @@ namespace AspNetCoreModule.Test testSite.AspNetCoreApp.CreateFile(new string[] { "barhtm" }, @"wwwroot\pdir\bar.htm"); testSite.AspNetCoreApp.CreateFile(new string[] { "defaulthtm" }, @"wwwroot\default.htm"); - string result = string.Empty; + SendReceiveContext result = null; if (!useCompressionMiddleWare && !enableIISCompression) { - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.False(result.Contains("Content-Encoding"), "verify response header"); + 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 GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("barhtm"), "verify response body"); - Assert.False(result.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 GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("defaulthtm"), "verify response body"); - Assert.False(result.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 GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + 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 GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("pdir/bar.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("barhtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "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 GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("defaulthtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "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"); @@ -1104,23 +1098,22 @@ namespace AspNetCoreModule.Test testSite.AspNetCoreApp.CreateFile(new string[] { "barhtm" }, @"wwwroot\pdir\bar.htm"); testSite.AspNetCoreApp.CreateFile(new string[] { "defaulthtm" }, @"wwwroot\default.htm"); - string result = string.Empty; - const int retryCount = 3; string headerValue = string.Empty; string headerValue2 = string.Empty; + SendReceiveContext result = null; for (int i = 0; i < retryCount; i++) { - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - headerValue = GetHeaderValue(result, "MyCustomHeader"); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + 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 GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - headerValue2 = GetHeaderValue(result, "MyCustomHeader"); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); + 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; @@ -1129,10 +1122,10 @@ namespace AspNetCoreModule.Test Assert.Equal(headerValue, headerValue2); Thread.Sleep(12000); - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("foo.htm"), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("foohtm"), "verify response body"); - Assert.Equal("gzip", GetHeaderValue(result, "Content-Encoding")); - string headerValue3 = GetHeaderValue(result, "MyCustomHeader"); + 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"); @@ -1167,14 +1160,13 @@ namespace AspNetCoreModule.Test testSite.StartIISExpress(); // Verify http request - string result = string.Empty; - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("Running"), "verify response body"); + 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 GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("Running"), "verify response body"); + 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); @@ -1217,15 +1209,14 @@ namespace AspNetCoreModule.Test testSite.StartIISExpress(); // Verify http request - string requestHeaders = string.Empty; - requestHeaders = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); - requestHeaders = requestHeaders.Replace(" ", ""); + 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"); - requestHeader = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); - requestHeaders = requestHeaders.Replace(" ", ""); + 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 @@ -1349,13 +1340,12 @@ namespace AspNetCoreModule.Test Assert.Contains(publicKey, outputRawContent); // Verify non-https request returns 403.4 error - string result = string.Empty; - result = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri(), new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.Forbidden); - Assert.Contains("403.4", result); + 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 GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.Forbidden); - Assert.Contains("403.7", result); + 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); @@ -1390,16 +1380,16 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); // Get Process ID - string backendProcessId_old = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId_old = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; // Verify WebSocket without setting subprotocol - await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server + 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 VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // echoSubProtocol.aspx has hard coded path for the websocket server + 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()) @@ -1419,10 +1409,10 @@ namespace AspNetCoreModule.Test } // send a simple request and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); Thread.Sleep(500); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.Equal(backendProcessId_old, backendProcessId); // Verify server side websocket disconnection @@ -1453,10 +1443,10 @@ namespace AspNetCoreModule.Test } // send a simple request and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); Thread.Sleep(500); - backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.Equal(backendProcessId_old, backendProcessId); // Verify websocket with app_offline.htm @@ -1546,7 +1536,7 @@ namespace AspNetCoreModule.Test */ // send a simple request and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); } } @@ -1574,7 +1564,7 @@ namespace AspNetCoreModule.Test } // send a simple request again and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); // roback configuration IISConfigUtility.RestoreAppHostConfig("DoWebSocketErrorhandlingTest", true); @@ -1656,7 +1646,7 @@ namespace AspNetCoreModule.Test 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 it + // starting IISExpress was deffered after creating test applications and now it is ready to start. testSite.StartIISExpress(); if (verifyTimeout) @@ -1664,12 +1654,12 @@ namespace AspNetCoreModule.Test Thread.Sleep(500); // initial request which requires more than startup timeout should fails - await VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep5000"), HttpStatusCode.BadGateway, timeout: 10); - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep5000"), "Running", HttpStatusCode.OK, timeout: 10); + 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 VerifyResponseStatus(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, timeout: 70); - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri("DoSleep50000"), "Running", HttpStatusCode.OK, timeout: 70); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep65000"), HttpStatusCode.BadGateway, timeout: 70); + await SendReceive(testSite.AspNetCoreApp.GetUri("DoSleep50000"), expectedResponseBody: "Running", timeout: 70); } /////////////////////////////////// @@ -1701,16 +1691,16 @@ namespace AspNetCoreModule.Test DateTime startTime = DateTime.Now; // Verify http request - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); // Get Process ID - string backendProcessId_old = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId_old = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; // Verify WebSocket without setting subprotocol - await VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echo.aspx"), new string[] { "Socket Open" }, HttpStatusCode.OK); // echo.aspx has hard coded path for the websocket server + 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 VerifyResponseBodyContain(testSite.WebSocketApp.GetUri("echoSubProtocol.aspx"), new string[] { "Socket Open", "mywebsocketsubprotocol" }, HttpStatusCode.OK); // echoSubProtocol.aspx has hard coded path for the websocket server + 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"; @@ -1732,10 +1722,10 @@ namespace AspNetCoreModule.Test } // send a simple request and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); Thread.Sleep(500); - string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + string backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.Equal(backendProcessId_old, backendProcessId); // Verify server side websocket disconnection @@ -1763,10 +1753,10 @@ namespace AspNetCoreModule.Test } // send a simple request and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); Thread.Sleep(500); - backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + backendProcessId = (await SendReceive(testSite.AspNetCoreApp.GetUri("GetProcessId"))).ResponseBody; Assert.Equal(backendProcessId_old, backendProcessId); if (startUpMode != DoAppVerifierTest_StartUpMode.DontUseGracefulShutdown) @@ -1824,12 +1814,12 @@ namespace AspNetCoreModule.Test } // send a simple request and verify the response body - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + await SendReceive(testSite.AspNetCoreApp.GetUri(), expectedResponseBody: "Running"); // Verify https request Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); - var result = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip" }, HttpStatusCode.OK); - Assert.True(result.Contains("Running"), "verify response body"); + var result = await SendReceive(targetHttpsUri, requestHeaders: new string[] { "Accept-Encoding", "gzip" }); + Assert.True(result.ResponseBody.Contains("Running"), "verify response body"); switch (shutDownMode) { @@ -1865,7 +1855,7 @@ namespace AspNetCoreModule.Test { case DoAppVerifierTest_ShutDownMode.CreateAppOfflineHtm: // verify app_offline.htm file works - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "test" + "\r\n", HttpStatusCode.ServiceUnavailable); + 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"); @@ -2093,41 +2083,50 @@ namespace AspNetCoreModule.Test return findEvent; } - private static async Task VerifyResponseStatus(Uri uri, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) + public class SendReceiveContext : IDisposable { - await SendReceive(uri, null, null, null, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData: null, timeout: timeout); + 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 VerifyResponseBody(Uri uri, string expectedResponseBody, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) + 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) { - await SendReceive(uri, null, expectedResponseBody, null, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData:null, timeout:timeout); + 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 VerifyPostResponseBody(Uri uri, KeyValuePair[] postData, string expectedResponseBody, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) - { - await SendReceive(uri, null, expectedResponseBody, null, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData, timeout); - } - - private static async Task VerifyResponseBodyContain(Uri uri, string[] expectedStrings, HttpStatusCode expectedResponseStatus, int timeout = 5, int numberOfRetryCount = 2, bool verifyResponseFlag = true) - { - await SendReceive(uri, null, null, expectedStrings, expectedResponseStatus, ReturnValueType.None, numberOfRetryCount, verifyResponseFlag, postData: null, timeout: timeout); - } - - private static async Task GetResponse(Uri uri, HttpStatusCode expectedResponseStatus, ReturnValueType returnValueType = ReturnValueType.ResponseBody, int timeout = 5, int numberOfRetryCount = 1, bool verifyResponseFlag = true) - { - return await SendReceive(uri, null, null, null, expectedResponseStatus, returnValueType, numberOfRetryCount, verifyResponseFlag, postData:null, timeout:timeout); - } - - private static async Task GetResponseAndHeaders(Uri uri, string[] requestHeaders, HttpStatusCode expectedResponseStatus, ReturnValueType returnValueType = ReturnValueType.ResponseBodyAndHeaders, int timeout = 5, int numberOfRetryCount = 1, bool verifyResponseFlag = true) - { - return await SendReceive(uri, requestHeaders, null, null, expectedResponseStatus, returnValueType, numberOfRetryCount, verifyResponseFlag, postData: null, timeout: timeout); - } - - private static async Task GetResponseStatusCode(Uri uri) - { - return await SendReceive(uri, null, null, null, HttpStatusCode.OK, ReturnValueType.ResponseStatus, numberOfRetryCount:1, verifyResponseFlag:false, postData:null, timeout:5); - } - private static async Task ReadContent(HttpResponseMessage response) { bool unZipContent = false; @@ -2183,9 +2182,18 @@ namespace AspNetCoreModule.Test return result; } - private static async Task SendReceive(Uri uri, string[] requestHeaders, string expectedResponseBody, string[] expectedStringsInResponseBody, HttpStatusCode expectedResponseStatus, ReturnValueType returnValueType, int numberOfRetryCount, bool verifyResponseFlag, KeyValuePair < string, string>[] postData, int timeout) + private static async Task SendReceive(SendReceiveContext context) { - string result = null; + 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"; @@ -2273,24 +2281,13 @@ namespace AspNetCoreModule.Test Assert.Equal(expectedResponseStatus, response.StatusCode); } - switch (returnValueType) + if (responseText == "NotInitialized") { - case ReturnValueType.ResponseBody: - case ReturnValueType.ResponseBodyAndHeaders: - if (responseText == "NotInitialized") - { - responseText = await ReadContent(response); - } - result = responseText; - if (returnValueType == ReturnValueType.ResponseBodyAndHeaders) - { - result += ", " + response.ToString(); - } - break; - case ReturnValueType.ResponseStatus: - result = response.StatusCode.ToString(); - break; + responseText = await ReadContent(response); } + context.ResponseBody = responseText; + context.ResponseHeader = response.ToString(); + context.ResponseStatus = response.StatusCode.ToString(); } } catch (XunitException) @@ -2302,7 +2299,7 @@ namespace AspNetCoreModule.Test TestUtility.LogInformation(responseText); TestUtility.LogInformation(responseStatus); } - return result; + return context; } } From 30bbd87d81e707bfde41895a13a7880c1cb3c934 Mon Sep 17 00:00:00 2001 From: pan-wang Date: Mon, 18 Dec 2017 14:19:15 -0800 Subject: [PATCH 103/107] Panwang/refactor (#277) * Init check in for refactoring * clean up to make app_offline work * update loadassembly and build * add configpath to aspnetcore_config to make recycle work * Adds in process component to refactor (#249) * outprocess first checkin (still missing marjor components) * Adds In-Process support for shimmed module. (#257) * Init check in for refactoring * clean up to make app_offline work * update loadassembly and build * add configpath to aspnetcore_config to make recycle work * Adds in process component to refactor (#249) * outprocess first checkin (still missing marjor components) * Adds In-Process support for shimmed module. (#257) * load from bin start and catch unhandled exception * Fixes request handler vcxproj * Adds request handler to nuget package * build issues * outofprocess refactoring * adding logging support * enforce Warning As Error for build and enable process recycle for outofprocess * fix AV for win32 build and update build flags * Fixed m_srwLock lock issue * remove dealock in loadmanagedapp and remove UseMFC * Readd lost exception catching * nuget package issue and status code * fixing warnings * Adds Headers * removing web sockets exe for now * remove flags * nuspec stuff * spelling * only look in inetsvr for now (or same folder) * rename method * terminte thread before closing the handle to it * couple changes related with AV * null check and Kill thread for in process if dotnet timed out * fix recursive lock issue reported by appverifier * client disconnect support AV fix * flow 502.5 process start failure error page * Feedback from inperson code review --- .gitignore | 11 +- AspNetCoreModule.sln | 93 - Directory.Build.props | 17 +- IISIntegration.sln | 56 + LICENSE.txt | 6 +- README.md | 5 +- build.cmd | 4 - build/build.msbuild | 1 + build/repo.props | 3 +- build/repo.targets | 2 + build/sources.props | 4 - korebuild-lock.txt | 5 - korebuild.json | 8 +- nuget/AspNetCore.nuspec | 2 + ...icrosoft.AspNetCore.AspNetCoreModule.props | 2 + run.cmd | 4 - run.ps1 | 36 +- src/AspNetCore/AspNetCore.vcxproj | 118 +- src/AspNetCore/Inc/application.h | 286 -- src/AspNetCore/Inc/applicationinfo.h | 218 ++ src/AspNetCore/Inc/applicationmanager.h | 83 +- src/AspNetCore/Inc/appoffline.h | 101 + src/AspNetCore/Inc/filewatcher.h | 6 +- src/AspNetCore/Inc/forwardinghandler.h | 446 --- src/AspNetCore/Inc/globalmodule.h | 37 + src/AspNetCore/Inc/inprocessstoredcontext.h | 90 - src/AspNetCore/Inc/outprocessapplication.h | 40 - src/AspNetCore/Inc/proxymodule.h | 17 +- src/AspNetCore/Inc/resource.h | 1 + src/AspNetCore/Src/application.cxx | 78 - src/AspNetCore/Src/applicationinfo.cpp | 332 ++ src/AspNetCore/Src/applicationmanager.cxx | 217 +- src/AspNetCore/Src/dllmain.cpp | 231 +- src/AspNetCore/Src/filewatcher.cxx | 36 +- src/AspNetCore/Src/fx_ver.cxx | 195 - src/AspNetCore/Src/globalmodule.cpp | 59 + src/AspNetCore/Src/inprocessapplication.cxx | 686 ---- src/AspNetCore/Src/inprocessstoredcontext.cxx | 113 - src/AspNetCore/Src/outprocessapplication.cxx | 121 - src/AspNetCore/Src/precomp.hxx | 53 +- src/AspNetCore/Src/processmanager.cxx | 294 -- src/AspNetCore/Src/proxymodule.cxx | 129 +- src/CommonLib/CommonLib.vcxproj | 204 + src/CommonLib/application.cpp | 50 + src/CommonLib/application.h | 48 + .../Src => CommonLib}/aspnetcoreconfig.cxx | 133 +- .../Inc => CommonLib}/aspnetcoreconfig.h | 81 +- src/{AspNetCore/Inc => CommonLib}/debugutil.h | 1 - .../environmentvariablehash.h | 0 src/CommonLib/fx_ver.cxx | 192 + src/CommonLib/fx_ver.h | 46 + src/CommonLib/hostfxr_utility.cpp | 259 ++ src/CommonLib/hostfxr_utility.h | 33 + src/CommonLib/requesthandler.cxx | 44 + src/CommonLib/requesthandler.h | 59 + src/CommonLib/stdafx.cpp | Bin 0 -> 350 bytes src/CommonLib/stdafx.h | Bin 0 -> 1628 bytes src/CommonLib/targetver.h | Bin 0 -> 630 bytes .../Src/path.cxx => CommonLib/utility.cxx} | 199 +- .../Inc/path.h => CommonLib/utility.h} | 48 +- src/IISLib/IISLib.vcxproj | 7 +- src/IISLib/precomp.h | 4 + src/RequestHandler/RequestHandler.rc | Bin 0 -> 2664 bytes src/RequestHandler/RequestHandler.vcxproj | 287 ++ .../RequestHandler.vcxproj.filters | 109 + src/RequestHandler/Resource.rc | Bin 0 -> 4360 bytes src/RequestHandler/Source.cpp | 0 src/RequestHandler/Source.def | 6 + .../Inc => RequestHandler}/aspnetcore_event.h | 0 src/RequestHandler/aspnetcore_msg.h | 165 + .../Src => RequestHandler}/aspnetcore_msg.mc | 0 src/RequestHandler/aspnetcore_msg.rc | 2 + src/RequestHandler/disconnectcontext.h | 78 + src/RequestHandler/dllmain.cxx | 340 ++ .../inprocess/inprocessapplication.cpp | 889 +++++ .../inprocess}/inprocessapplication.h | 75 +- .../inprocess/inprocesshandler.cpp | 146 + .../inprocess/inprocesshandler.h | 72 + .../Src => RequestHandler}/managedexports.cxx | 118 +- .../outofprocess}/forwarderconnection.cxx | 4 +- .../outofprocess}/forwarderconnection.h | 0 .../outofprocess/forwardinghandler.cpp} | 3429 +++++++---------- .../outofprocess/forwardinghandler.h | 199 + .../outofprocess/outprocessapplication.cpp | 66 + .../outofprocess/outprocessapplication.h | 25 + .../outofprocess/processmanager.cxx | 296 ++ .../outofprocess}/processmanager.h | 4 +- .../outofprocess}/protocolconfig.cxx | 2 +- .../outofprocess}/protocolconfig.h | 2 - .../outofprocess}/responseheaderhash.cxx | 4 +- .../outofprocess}/responseheaderhash.h | 2 - .../outofprocess}/serverprocess.cxx | 805 ++-- .../outofprocess}/serverprocess.h | 91 +- .../outofprocess}/websockethandler.cxx | 35 +- .../outofprocess}/websockethandler.h | 1 + .../outofprocess}/winhttphelper.cxx | 2 +- .../outofprocess}/winhttphelper.h | 0 src/RequestHandler/precomp.hxx | 119 + src/RequestHandler/resource.h | 24 + .../Inc => RequestHandler}/sttimer.h | 37 + src/RequestHandler/version.h | 8 + test/Directory.Build.props | 7 - version.props | 11 - 103 files changed, 7317 insertions(+), 5797 deletions(-) delete mode 100644 AspNetCoreModule.sln delete mode 100644 src/AspNetCore/Inc/application.h create mode 100644 src/AspNetCore/Inc/applicationinfo.h create mode 100644 src/AspNetCore/Inc/appoffline.h delete mode 100644 src/AspNetCore/Inc/forwardinghandler.h create mode 100644 src/AspNetCore/Inc/globalmodule.h delete mode 100644 src/AspNetCore/Inc/inprocessstoredcontext.h delete mode 100644 src/AspNetCore/Inc/outprocessapplication.h delete mode 100644 src/AspNetCore/Src/application.cxx create mode 100644 src/AspNetCore/Src/applicationinfo.cpp delete mode 100644 src/AspNetCore/Src/fx_ver.cxx create mode 100644 src/AspNetCore/Src/globalmodule.cpp delete mode 100644 src/AspNetCore/Src/inprocessapplication.cxx delete mode 100644 src/AspNetCore/Src/inprocessstoredcontext.cxx delete mode 100644 src/AspNetCore/Src/outprocessapplication.cxx delete mode 100644 src/AspNetCore/Src/processmanager.cxx create mode 100644 src/CommonLib/CommonLib.vcxproj create mode 100644 src/CommonLib/application.cpp create mode 100644 src/CommonLib/application.h rename src/{AspNetCore/Src => CommonLib}/aspnetcoreconfig.cxx (81%) rename src/{AspNetCore/Inc => CommonLib}/aspnetcoreconfig.h (81%) rename src/{AspNetCore/Inc => CommonLib}/debugutil.h (99%) rename src/{AspNetCore/Inc => CommonLib}/environmentvariablehash.h (100%) create mode 100644 src/CommonLib/fx_ver.cxx create mode 100644 src/CommonLib/fx_ver.h create mode 100644 src/CommonLib/hostfxr_utility.cpp create mode 100644 src/CommonLib/hostfxr_utility.h create mode 100644 src/CommonLib/requesthandler.cxx create mode 100644 src/CommonLib/requesthandler.h create mode 100644 src/CommonLib/stdafx.cpp create mode 100644 src/CommonLib/stdafx.h create mode 100644 src/CommonLib/targetver.h rename src/{AspNetCore/Src/path.cxx => CommonLib/utility.cxx} (71%) rename src/{AspNetCore/Inc/path.h => CommonLib/utility.h} (69%) create mode 100644 src/RequestHandler/RequestHandler.rc create mode 100644 src/RequestHandler/RequestHandler.vcxproj create mode 100644 src/RequestHandler/RequestHandler.vcxproj.filters create mode 100644 src/RequestHandler/Resource.rc create mode 100644 src/RequestHandler/Source.cpp create mode 100644 src/RequestHandler/Source.def rename src/{AspNetCore/Inc => RequestHandler}/aspnetcore_event.h (100%) create mode 100644 src/RequestHandler/aspnetcore_msg.h rename src/{AspNetCore/Src => RequestHandler}/aspnetcore_msg.mc (100%) create mode 100644 src/RequestHandler/aspnetcore_msg.rc create mode 100644 src/RequestHandler/disconnectcontext.h create mode 100644 src/RequestHandler/dllmain.cxx create mode 100644 src/RequestHandler/inprocess/inprocessapplication.cpp rename src/{AspNetCore/Inc => RequestHandler/inprocess}/inprocessapplication.h (68%) create mode 100644 src/RequestHandler/inprocess/inprocesshandler.cpp create mode 100644 src/RequestHandler/inprocess/inprocesshandler.h rename src/{AspNetCore/Src => RequestHandler}/managedexports.cxx (68%) rename src/{AspNetCore/Src => RequestHandler/outofprocess}/forwarderconnection.cxx (93%) rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/forwarderconnection.h (100%) rename src/{AspNetCore/Src/forwardinghandler.cxx => RequestHandler/outofprocess/forwardinghandler.cpp} (60%) create mode 100644 src/RequestHandler/outofprocess/forwardinghandler.h create mode 100644 src/RequestHandler/outofprocess/outprocessapplication.cpp create mode 100644 src/RequestHandler/outofprocess/outprocessapplication.h create mode 100644 src/RequestHandler/outofprocess/processmanager.cxx rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/processmanager.h (97%) rename src/{AspNetCore/Src => RequestHandler/outofprocess}/protocolconfig.cxx (97%) rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/protocolconfig.h (98%) rename src/{AspNetCore/Src => RequestHandler/outofprocess}/responseheaderhash.cxx (97%) rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/responseheaderhash.h (97%) rename src/{AspNetCore/Src => RequestHandler/outofprocess}/serverprocess.cxx (80%) rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/serverprocess.h (83%) rename src/{AspNetCore/Src => RequestHandler/outofprocess}/websockethandler.cxx (99%) rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/websockethandler.h (98%) rename src/{AspNetCore/Src => RequestHandler/outofprocess}/winhttphelper.cxx (99%) rename src/{AspNetCore/Inc => RequestHandler/outofprocess}/winhttphelper.h (100%) create mode 100644 src/RequestHandler/precomp.hxx create mode 100644 src/RequestHandler/resource.h rename src/{AspNetCore/Inc => RequestHandler}/sttimer.h (83%) create mode 100644 src/RequestHandler/version.h diff --git a/.gitignore b/.gitignore index ddefb684fa..5004d81d9d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,15 +25,9 @@ project.lock.json *.ncrunchsolution *.*sdf *.ipch -<<<<<<< HEAD -*.vs/ .vscode/ -.testPublish/ -.build/ *.nuget.props *.nuget.targets -global.json -======= *.bin *.vs/ .testPublish/ @@ -41,12 +35,15 @@ global.json *.obj *.tlog *.CppClean.log +*msbuild.log src/*/Debug/ src/*/x64/Debug/ src/*/Release/ src/*/x64/Release/ +x64/ +*vcxproj.filters *.aps *.pdb *.lib @@ -59,5 +56,3 @@ src/AspNetCore/version.h *.VC.*db global.json - ->>>>>>> ANCM/dev diff --git a/AspNetCoreModule.sln b/AspNetCoreModule.sln deleted file mode 100644 index a6d854e59b..0000000000 --- a/AspNetCoreModule.sln +++ /dev/null @@ -1,93 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27110.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" - ProjectSection(ProjectDependencies) = postProject - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{02F461DC-5166-4E88-AAD5-CF110016A647}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FDD2EDF8-1B62-4978-9815-9D95260B8B91}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreModule.Test", "test\AspNetCoreModule.Test\AspNetCoreModule.Test.csproj", "{4DDA7560-AA29-4161-A5EA-A7E8F3997321}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreModule.TestSites.Standard", "test\AspNetCoreModule.TestSites.Standard\AspNetCoreModule.TestSites.Standard.csproj", "{030225D8-4EE8-47E5-B692-2A96B3B51A38}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0EF45656-B25D-40D8-959C-726EAF185E60}" - ProjectSection(SolutionItems) = preProject - NuGet.Config = NuGet.Config - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.ActiveCfg = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Win32.Build.0 = Release|x64 - {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}.Release|Any CPU.ActiveCfg = Release|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.ActiveCfg = Release|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Win32.Build.0 = Release|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.ActiveCfg = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Win32.Build.0 = Release|x64 - {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}.Release|Any CPU.ActiveCfg = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.ActiveCfg = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Win32.Build.0 = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|x64 - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Win32.ActiveCfg = Debug|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|Win32.Build.0 = Debug|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|x64.ActiveCfg = Debug|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Debug|x64.Build.0 = Debug|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Any CPU.Build.0 = Release|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Win32.ActiveCfg = Release|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|Win32.Build.0 = Release|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|x64.ActiveCfg = Release|Any CPU - {4DDA7560-AA29-4161-A5EA-A7E8F3997321}.Release|x64.Build.0 = Release|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.Build.0 = Debug|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Win32.ActiveCfg = Debug|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Win32.Build.0 = Debug|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|x64.ActiveCfg = Debug|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|x64.Build.0 = Debug|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.ActiveCfg = Release|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.Build.0 = Release|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Win32.ActiveCfg = Release|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Win32.Build.0 = Release|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.ActiveCfg = Release|Any CPU - {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {439824F9-1455-4CC4-BD79-B44FA0A16552} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} - {4DDA7560-AA29-4161-A5EA-A7E8F3997321} = {02F461DC-5166-4E88-AAD5-CF110016A647} - {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {02F461DC-5166-4E88-AAD5-CF110016A647} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0967E9B4-FEE7-40D7-860A-23E340E65840} - EndGlobalSection -EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props index 2a4a55f689..a03bf421cb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,17 +1,12 @@ -<<<<<<< HEAD - + -======= - ->>>>>>> ANCM/dev -<<<<<<< HEAD Microsoft ASP.NET Core https://github.com/aspnet/IISIntegration @@ -28,14 +23,4 @@ -======= - - Microsoft ASP.NET Core - https://github.com/aspnet/AspNetCoreModule - git - $(MSBuildThisFileDirectory)build\Key.snk - true - true - ->>>>>>> ANCM/dev diff --git a/IISIntegration.sln b/IISIntegration.sln index 75d59c5d46..12bb8b1bc8 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -45,6 +45,14 @@ 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}") = "RequestHandler", "src\RequestHandler\RequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -150,6 +158,50 @@ 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 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Win32.ActiveCfg = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Win32.Build.0 = Debug|x64 + {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}.Release|Any CPU.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Win32.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Win32.Build.0 = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Win32.ActiveCfg = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Win32.Build.0 = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Win32.Deploy.0 = Debug|x64 + {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|x64.Deploy.0 = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Win32.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Win32.Build.0 = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Win32.Deploy.0 = 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|x64.Deploy.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -163,6 +215,10 @@ 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} + {D57EA297-6DC2-4BC0-8C91-334863327863} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/LICENSE.txt b/LICENSE.txt index 64abc3099c..d50cef4a3f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,3 @@ -<<<<<<< HEAD Copyright (c) .NET Foundation and Contributors All rights reserved. @@ -13,7 +12,7 @@ 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 @@ -36,5 +35,4 @@ 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. ->>>>>>> ANCM/dev +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 683efab329..94f5fa5df9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ -<<<<<<< HEAD -ASP.NET Core IIS Integration -======== - This repo hosts the ASP.NET Core middleware for IIS integration. 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. + ======= # ASP.NET Core Module diff --git a/build.cmd b/build.cmd index d3bb975df1..c0050bda12 100644 --- a/build.cmd +++ b/build.cmd @@ -1,6 +1,2 @@ @ECHO OFF -<<<<<<< HEAD PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" -======= -PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" ->>>>>>> ANCM/dev diff --git a/build/build.msbuild b/build/build.msbuild index 59c022e99c..c731cd9ff8 100644 --- a/build/build.msbuild +++ b/build/build.msbuild @@ -3,6 +3,7 @@ + diff --git a/build/repo.props b/build/repo.props index 4d9fa51c7f..810db16cd7 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,5 +1,4 @@ -<<<<<<< HEAD - + diff --git a/build/repo.targets b/build/repo.targets index 43afd316c8..97c447ab01 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -16,6 +16,8 @@ + diff --git a/build/sources.props b/build/sources.props index c91e42df09..05a4b7dc2e 100644 --- a/build/sources.props +++ b/build/sources.props @@ -1,8 +1,4 @@ -<<<<<<< HEAD - -======= ->>>>>>> ANCM/dev diff --git a/korebuild-lock.txt b/korebuild-lock.txt index a1256be6f8..fe4a961da3 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,7 +1,2 @@ -<<<<<<< HEAD -version:2.1.0-preview1-15576 -commithash:2f3856d2ba4f659fcb9253215b83946a06794a27 -======= version:2.1.0-preview1-15620 commithash:6432b49a2c00310416df39b6fe548ef4af9c6011 ->>>>>>> ANCM/dev diff --git a/korebuild.json b/korebuild.json index 79c0242c7a..80e6e41c09 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,9 +1,4 @@ { -<<<<<<< HEAD - "$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": { @@ -16,5 +11,4 @@ ] } } - } ->>>>>>> ANCM/dev + } \ No newline at end of file diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCore.nuspec index a91aeee733..bb3c627b3e 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCore.nuspec @@ -20,6 +20,8 @@ + + diff --git a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props index 7a761813b4..5b01ee63a4 100644 --- a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props +++ b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -3,6 +3,8 @@ $(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.cmd b/run.cmd index 47b40d746e..d52d5c7e68 100644 --- a/run.cmd +++ b/run.cmd @@ -1,6 +1,2 @@ @ECHO OFF -<<<<<<< HEAD PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" -======= -PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" ->>>>>>> ANCM/dev diff --git a/run.ps1 b/run.ps1 index 4b3425ac8a..b7de403018 100644 --- a/run.ps1 +++ b/run.ps1 @@ -29,12 +29,9 @@ Updates KoreBuild to the latest version even if a lock file is present. .PARAMETER ConfigFile The path to the configuration file that stores values. Defaults to korebuild.json. -<<<<<<< HEAD .PARAMETER ToolsSourceSuffix The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. -======= ->>>>>>> ANCM/dev .PARAMETER Arguments Arguments to be passed to the command @@ -57,11 +54,7 @@ Example config file: #> [CmdletBinding(PositionalBinding = $false)] param( -<<<<<<< HEAD [Parameter(Mandatory = $true, Position = 0)] -======= - [Parameter(Mandatory=$true, Position = 0)] ->>>>>>> ANCM/dev [string]$Command, [string]$Path = $PSScriptRoot, [Alias('c')] @@ -73,10 +66,7 @@ param( [Alias('u')] [switch]$Update, [string]$ConfigFile, -<<<<<<< HEAD [string]$ToolsSourceSuffix, -======= ->>>>>>> ANCM/dev [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments ) @@ -93,11 +83,7 @@ function Get-KoreBuild { $lockFile = Join-Path $Path 'korebuild-lock.txt' if (!(Test-Path $lockFile) -or $Update) { -<<<<<<< HEAD Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix -======= - Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile ->>>>>>> ANCM/dev } $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 @@ -114,11 +100,7 @@ function Get-KoreBuild { try { $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" -<<<<<<< HEAD Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix -======= - Get-RemoteFile $remotePath $tmpfile ->>>>>>> ANCM/dev if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath @@ -146,11 +128,7 @@ function Join-Paths([string]$path, [string[]]$childPaths) { return $path } -<<<<<<< HEAD function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { -======= -function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) { ->>>>>>> ANCM/dev if ($RemotePath -notlike 'http*') { Copy-Item $RemotePath $LocalPath return @@ -160,11 +138,7 @@ function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) { while ($retries -gt 0) { $retries -= 1 try { -<<<<<<< HEAD Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath -======= - Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath ->>>>>>> ANCM/dev return } catch { @@ -191,12 +165,8 @@ if (Test-Path $ConfigFile) { if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} } -<<<<<<< HEAD } catch { -======= - } catch { ->>>>>>> ANCM/dev Write-Warning "$ConfigFile could not be read. Its settings will be ignored." Write-Warning $Error[0] } @@ -223,8 +193,4 @@ try { } finally { Remove-Module 'KoreBuild' -ErrorAction Ignore -<<<<<<< HEAD -} -======= -} ->>>>>>> ANCM/dev +} \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 005f0c5d5a..1237d765a8 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -40,7 +40,6 @@ true v141 Unicode - false DynamicLibrary @@ -75,16 +74,34 @@ $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + + + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + NotUsing - Level3 + 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 @@ -104,6 +121,17 @@ ..\IISLib;.\Inc ProgramDatabase MultiThreadedDebug + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true Windows @@ -123,6 +151,17 @@ ..\IISLib;inc precomp.hxx MultiThreaded + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true Windows @@ -144,6 +183,17 @@ precomp.hxx ..\IISLib;inc MultiThreaded + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true Windows @@ -155,52 +205,22 @@ - + + + - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - + - - - - @@ -221,29 +241,16 @@ - - - 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} + false @@ -254,6 +261,9 @@ PreserveNewest + + + diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h deleted file mode 100644 index 3bed6712b8..0000000000 --- a/src/AspNetCore/Inc/application.h +++ /dev/null @@ -1,286 +0,0 @@ -// 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 APPLICATION_KEY -{ -public: - - APPLICATION_KEY( - VOID - ) : INLINE_STRU_INIT(m_struKey) - { - } - - HRESULT - Initialize( - _In_ LPCWSTR pszKey - ) - { - return m_struKey.Copy(pszKey); - } - - BOOL - GetIsEqual( - const APPLICATION_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 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; -}; - -class APPLICATION_MANAGER; - -class APPLICATION -{ -public: - - APPLICATION() : m_pApplicationManager(NULL), m_cRefs(1), - m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), - m_pFileWatcherEntry(NULL), m_pConfiguration(NULL) - { - InitializeSRWLock(&m_srwLock); - } - - APPLICATION_KEY * - QueryApplicationKey() - { - return &m_applicationKey; - } - - virtual - ~APPLICATION(); - - virtual - HRESULT - Initialize( - _In_ APPLICATION_MANAGER *pApplicationManager, - _In_ ASPNETCORE_CONFIG *pConfiguration) = 0; - - VOID - ReferenceApplication() const - { - InterlockedIncrement(&m_cRefs); - } - - VOID - DereferenceApplication() const - { - if (InterlockedDecrement(&m_cRefs) == 0) - { - delete this; - } - } - - APP_OFFLINE_HTM* QueryAppOfflineHtm() - { - return m_pAppOfflineHtm; - } - - BOOL - AppOfflineFound() - { - return m_fAppOfflineFound; - } - - virtual - VOID - OnAppOfflineHandleChange() = 0; - - VOID - UpdateAppOfflineFileHandle(); - - HRESULT - StartMonitoringAppOffline(); - - ASPNETCORE_CONFIG* - QueryConfig() - { - return m_pConfiguration; - } - - virtual - REQUEST_NOTIFICATION_STATUS - ExecuteRequest( - _In_ IHttpContext* pHttpContext - ) = 0; - -protected: - - mutable LONG m_cRefs; - APPLICATION_KEY m_applicationKey; - APPLICATION_MANAGER *m_pApplicationManager; - BOOL m_fAppOfflineFound; - APP_OFFLINE_HTM *m_pAppOfflineHtm; - FILE_WATCHER_ENTRY *m_pFileWatcherEntry; - ASPNETCORE_CONFIG *m_pConfiguration; - SRWLOCK m_srwLock; - -}; - -class APPLICATION_HASH : - public HASH_TABLE -{ - -public: - - APPLICATION_HASH() - {} - - APPLICATION_KEY * - ExtractKey( - APPLICATION *pApplication - ) - { - return pApplication->QueryApplicationKey(); - } - - DWORD - CalcKeyHash( - APPLICATION_KEY *key - ) - { - return key->CalcKeyHash(); - } - - BOOL - EqualKeys( - APPLICATION_KEY *key1, - APPLICATION_KEY *key2 - ) - { - return key1->GetIsEqual(key2); - } - - VOID - ReferenceRecord( - APPLICATION *pApplication - ) - { - pApplication->ReferenceApplication(); - } - - VOID - DereferenceRecord( - APPLICATION *pApplication - ) - { - pApplication->DereferenceApplication(); - } - -private: - - APPLICATION_HASH(const APPLICATION_HASH &); - void operator=(const APPLICATION_HASH &); -}; \ 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 index 9b413341bd..9e9b062ba2 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -5,6 +5,11 @@ #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: @@ -15,12 +20,12 @@ public: VOID ) { - if( sm_pApplicationManager == NULL ) + if ( sm_pApplicationManager == NULL ) { sm_pApplicationManager = new APPLICATION_MANAGER(); } - return sm_pApplicationManager; + return sm_pApplicationManager; } static @@ -37,29 +42,27 @@ public: } HRESULT - GetApplication( - _In_ IHttpContext* pContext, + GetApplicationInfo( + _In_ IHttpServer* pServer, _In_ ASPNETCORE_CONFIG* pConfig, - _Out_ APPLICATION ** ppApplication + _Out_ APPLICATION_INFO ** ppApplicationInfo ); HRESULT - RecycleApplication( - _In_ LPCWSTR pszApplication + RecycleApplication( + _In_ LPCWSTR pszApplicationId ); - HRESULT - Get502ErrorPage( - _Out_ HTTP_DATA_CHUNK** ppErrorPage - ); + VOID + ShutDown(); ~APPLICATION_MANAGER() { - if(m_pApplicationHash != NULL) + if(m_pApplicationInfoHash != NULL) { - m_pApplicationHash->Clear(); - delete m_pApplicationHash; - m_pApplicationHash = NULL; + m_pApplicationInfoHash->Clear(); + delete m_pApplicationInfoHash; + m_pApplicationInfoHash = NULL; } if( m_pFileWatcher!= NULL ) @@ -67,13 +70,6 @@ public: delete m_pFileWatcher; m_pFileWatcher = NULL; } - - if(m_pHttp502ErrorPage != NULL) - { - delete m_pHttp502ErrorPage; - m_pHttp502ErrorPage = NULL; - } - } FILE_WATCHER* @@ -86,16 +82,16 @@ public: { HRESULT hr = S_OK; - if(m_pApplicationHash == NULL) + if(m_pApplicationInfoHash == NULL) { - m_pApplicationHash = new APPLICATION_HASH(); - if(m_pApplicationHash == NULL) + m_pApplicationInfoHash = new APPLICATION_INFO_HASH(); + if(m_pApplicationInfoHash == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - hr = m_pApplicationHash->Initialize(DEFAULT_HASH_BUCKETS); + hr = m_pApplicationInfoHash->Initialize(DEFAULT_HASH_BUCKETS); if(FAILED(hr)) { goto Finished; @@ -122,41 +118,18 @@ private: // // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload // - APPLICATION_MANAGER() : m_pApplicationHash(NULL), m_pFileWatcher(NULL), - m_pHttp502ErrorPage(NULL), m_hostingModel(HOSTING_UNKNOWN), - m_pstrErrorInfo( - " \ - \ - \ - \ - 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

    \ -
    \ -
    \ -
    ") + APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL), + m_pFileWatcher(NULL), + m_hostingModel(HOSTING_UNKNOWN), + m_fInShutdown(FALSE) { InitializeSRWLock(&m_srwLock); } FILE_WATCHER *m_pFileWatcher; - APPLICATION_HASH *m_pApplicationHash; + APPLICATION_INFO_HASH *m_pApplicationInfoHash; static APPLICATION_MANAGER *sm_pApplicationManager; SRWLOCK m_srwLock; - HTTP_DATA_CHUNK *m_pHttp502ErrorPage; - LPSTR m_pstrErrorInfo; 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/filewatcher.h b/src/AspNetCore/Inc/filewatcher.h index 6ae853708e..c5bff0df0d 100644 --- a/src/AspNetCore/Inc/filewatcher.h +++ b/src/AspNetCore/Inc/filewatcher.h @@ -19,7 +19,7 @@ #define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') #define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') -class APPLICATION; +class APPLICATION_INFO; class FILE_WATCHER{ public: @@ -67,7 +67,7 @@ public: Create( _In_ PCWSTR pszDirectoryToMonitor, _In_ PCWSTR pszFileNameToMonitor, - _In_ APPLICATION* pApplication, + _In_ APPLICATION_INFO* pApplicationInfo, _In_ HANDLE hImpersonationToken ); @@ -116,7 +116,7 @@ private: HANDLE _hImpersonationToken; HANDLE _hDirectory; FILE_WATCHER* _pFileMonitor; - APPLICATION* _pApplication; + APPLICATION_INFO* _pApplicationInfo; STRU _strFileName; STRU _strDirectoryName; LONG _lStopMonitorCalled; diff --git a/src/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCore/Inc/forwardinghandler.h deleted file mode 100644 index 4a6ecbe451..0000000000 --- a/src/AspNetCore/Inc/forwardinghandler.h +++ /dev/null @@ -1,446 +0,0 @@ -// 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 "forwarderconnection.h" -#include "protocolconfig.h" -#include "serverprocess.h" -#include "application.h" -#include "tracelog.h" -#include "websockethandler.h" - -enum FORWARDING_REQUEST_STATUS -{ - FORWARDER_START, - FORWARDER_SENDING_REQUEST, - FORWARDER_RECEIVING_RESPONSE, - FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, - FORWARDER_RESET_CONNECTION, - FORWARDER_DONE -}; - -extern HTTP_MODULE_ID g_pModuleId; -extern IHttpServer * g_pHttpServer; -extern BOOL g_fAsyncDisconnectAvailable; -extern PCWSTR g_pszModuleName; -extern HMODULE g_hModule; -extern HMODULE g_hWinHttpModule; -extern DWORD g_dwTlsIndex; -extern DWORD g_OptionalWinHttpFlags; - -enum MULTI_PART_POSITION -{ - MULTI_PART_IN_BOUNDARY, - MULTI_PART_IN_HEADER, - MULTI_PART_IN_CHUNK, - MULTI_PART_IN_CHUNK_END -}; - -class ASYNC_DISCONNECT_CONTEXT; - -#define FORWARDING_HANDLER_SIGNATURE ((DWORD)'FHLR') -#define FORWARDING_HANDLER_SIGNATURE_FREE ((DWORD)'fhlr') - -class FORWARDING_HANDLER -{ -public: - - FORWARDING_HANDLER( - __in IHttpContext * pW3Context, - __in APPLICATION * pApplication - ); - - static void * operator new(size_t size); - - static void operator delete(void * pMemory); - - VOID - ReferenceForwardingHandler( - VOID - ) const; - - VOID - DereferenceForwardingHandler( - VOID - ) const; - - REQUEST_NOTIFICATION_STATUS - OnExecuteRequestHandler(); - - REQUEST_NOTIFICATION_STATUS - OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus - ); - - IHttpTraceContext * - QueryTraceContext() - { - return m_pW3Context->GetTraceContext(); - } - - IHttpContext * - QueryHttpContext( - VOID - ) - { - return m_pW3Context; - } - - static - VOID - CALLBACK - 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); - } - - static - HRESULT - StaticInitialize( - BOOL fEnableReferenceCountTracing - ); - - static - VOID - StaticTerminate(); - - static - PCWSTR - QueryErrorFormat() - { - return sm_strErrorFormat.QueryStr(); - } - - static - HANDLE - QueryEventLog() - { - return sm_hEventLog; - } - - VOID - TerminateRequest( - bool fClientInitiated - ); - - static HINTERNET sm_hSession; - - HRESULT - SetStatusAndHeaders( - PCSTR pszHeaders, - DWORD cchHeaders - ); - - HRESULT - OnSharedRequestEntity( - ULONGLONG ulOffset, - LPCBYTE pvBuffer, - DWORD cbBuffer - ); - - VOID - SetStatus( - FORWARDING_REQUEST_STATUS status - ) - { - m_RequestStatus = status; - } - -private: - - virtual - ~FORWARDING_HANDLER( - VOID - ); - - // - // Begin OnMapRequestHandler phases. - // - - HRESULT - CreateWinHttpRequest( - __in const IHttpRequest * pRequest, - __in const PROTOCOL_CONFIG * pProtocol, - __in HINTERNET hConnect, - __inout STRU * pstrUrl, - ASPNETCORE_CONFIG* pAspNetCoreConfig, - SERVER_PROCESS* pServerProcess - ); - - // - // End OnMapRequestHandler phases. - // - - VOID - RemoveRequest(); - - HRESULT - GetHeaders( - const PROTOCOL_CONFIG * pProtocol, - PCWSTR * ppszHeaders, - DWORD * pcchHeaders, - ASPNETCORE_CONFIG* pAspNetCoreConfig, - SERVER_PROCESS* pServerProcess - ); - - HRESULT - DoReverseRewrite( - __in IHttpResponse *pResponse - ); - - BYTE * - GetNewResponseBuffer( - DWORD dwBufferSize - ); - - VOID - FreeResponseBuffers(); - - VOID - OnWinHttpCompletionInternal( - HINTERNET hRequest, - DWORD dwInternetStatus, - LPVOID lpvStatusInformation, - 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(); - - HRESULT - OnWebSocketWinHttpSendComplete( - HINTERNET hRequest, - LPVOID pvStatus, - DWORD hrCompletion, - DWORD cbCompletion, - bool * pfAnotherCompletionExpected - ); - - HRESULT - OnWebSocketWinHttpReceiveComplete( - HINTERNET hRequest, - LPVOID pvStatus, - DWORD hrCompletion, - DWORD cbCompletion, - bool * pfAnotherCompletionExpected - ); - - HRESULT - OnWebSocketIisSendComplete( - DWORD hrCompletion, - DWORD cbCompletion - ); - - HRESULT - OnWebSocketIisReceiveComplete( - DWORD hrCompletion, - DWORD cbCompletion - ); - - HRESULT - DoIisWebSocketReceive( - VOID - ); - - VOID - TerminateWebsocket( - VOID - ); - - HRESULT - SetHttpSysDisconnectCallback( - VOID - ); - - DWORD m_Signature; - mutable LONG m_cRefs; - - IHttpContext * m_pW3Context; - IHttpContext * m_pChildRequestContext; - - // - // WinHTTP request handle is protected using a read-write lock. - // - SRWLOCK m_RequestLock; - HINTERNET m_hRequest; - - APP_OFFLINE_HTM *m_pAppOfflineHtm; - APPLICATION *m_pApplication; - - bool m_fWebSocketEnabled; - bool m_fHandleClosedDueToClient; - bool m_fResponseHeadersReceivedAndSet; - BOOL m_fDoReverseRewriteHeaders; - BOOL m_fErrorHandled; - BOOL m_fWebSocketUpgrade; - BOOL m_fFinishRequest; - BOOL m_fClientDisconnected; - BOOL m_fHasError; - DWORD m_msStartTime; - DWORD m_BytesToReceive; - DWORD m_BytesToSend; - DWORD m_cchLastSend; - DWORD m_cEntityBuffers; - DWORD m_cBytesBuffered; - DWORD m_cMinBufferLimit; - - BYTE * m_pEntityBuffer; - static const SIZE_T INLINE_ENTITY_BUFFERS = 8; - BUFFER_T m_buffEntityBuffers; - - PCSTR m_pszOriginalHostHeader; - - FORWARDING_REQUEST_STATUS m_RequestStatus; - - ASYNC_DISCONNECT_CONTEXT * m_pDisconnect; - - PCWSTR m_pszHeaders; - DWORD m_cchHeaders; - - STRU m_strFullUri; - - ULONGLONG m_cContentLength; - - WEBSOCKET_HANDLER * m_pWebSocket; - - static PROTOCOL_CONFIG sm_ProtocolConfig; - - static STRU sm_strErrorFormat; - - static HANDLE sm_hEventLog; - - static ALLOC_CACHE_HANDLER * sm_pAlloc; - - // - // Reference cout tracing for debugging purposes. - // - static TRACE_LOG * sm_pTraceLog; -}; - -class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext -{ - public: - ASYNC_DISCONNECT_CONTEXT() - { - m_pHandler = NULL; - } - - VOID - CleanupStoredContext() - { - DBG_ASSERT(m_pHandler == NULL); - delete this; - } - - VOID - NotifyDisconnect() - { - FORWARDING_HANDLER *pInitialValue = (FORWARDING_HANDLER*) - InterlockedExchangePointer((PVOID*) &m_pHandler, NULL); - - if (pInitialValue != NULL) - { - pInitialValue->TerminateRequest(TRUE); - pInitialValue->DereferenceForwardingHandler(); - } - } - - VOID - SetHandler( - FORWARDING_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->ReferenceForwardingHandler(); - InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler); - } - - VOID - ResetHandler( - VOID - ) - { - FORWARDING_HANDLER *pInitialValue = (FORWARDING_HANDLER*) - InterlockedExchangePointer( (PVOID*)&m_pHandler, NULL); - - if (pInitialValue != NULL) - { - pInitialValue->DereferenceForwardingHandler(); - } - } - - private: - ~ASYNC_DISCONNECT_CONTEXT() - {} - - FORWARDING_HANDLER * m_pHandler; -}; \ 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/inprocessstoredcontext.h b/src/AspNetCore/Inc/inprocessstoredcontext.h deleted file mode 100644 index 64244692d4..0000000000 --- a/src/AspNetCore/Inc/inprocessstoredcontext.h +++ /dev/null @@ -1,90 +0,0 @@ -// 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 IN_PROCESS_STORED_CONTEXT : public IHttpStoredContext -{ -public: - IN_PROCESS_STORED_CONTEXT( - IHttpContext* pHttpContext, - PVOID pvManagedContext - ); - - ~IN_PROCESS_STORED_CONTEXT(); - - virtual - VOID - CleanupStoredContext( - VOID - ) - { - delete this; - } - - virtual - VOID - OnClientDisconnected( - VOID - ) - { - } - - virtual - VOID - OnListenerEvicted( - VOID - ) - { - } - - PVOID - QueryManagedHttpContext( - VOID - ); - - IHttpContext* - QueryHttpContext( - VOID - ); - - BOOL - QueryIsManagedRequestComplete( - VOID - ); - - VOID - IndicateManagedRequestComplete( - VOID - ); - - REQUEST_NOTIFICATION_STATUS - QueryAsyncCompletionStatus( - VOID - ); - - VOID - SetAsyncCompletionStatus( - REQUEST_NOTIFICATION_STATUS requestNotificationStatus - ); - - static - HRESULT - GetInProcessStoredContext( - IHttpContext* pHttpContext, - IN_PROCESS_STORED_CONTEXT** ppInProcessStoredContext - ); - - static - HRESULT - SetInProcessStoredContext( - IHttpContext* pHttpContext, - IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext - ); - -private: - PVOID m_pManagedHttpContext; - IHttpContext* m_pHttpContext; - BOOL m_fManagedRequestComplete; - REQUEST_NOTIFICATION_STATUS m_requestNotificationStatus; -}; - diff --git a/src/AspNetCore/Inc/outprocessapplication.h b/src/AspNetCore/Inc/outprocessapplication.h deleted file mode 100644 index 57e6022c0c..0000000000 --- a/src/AspNetCore/Inc/outprocessapplication.h +++ /dev/null @@ -1,40 +0,0 @@ -// 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 "application.h" - -class OUT_OF_PROCESS_APPLICATION : public APPLICATION -{ -public: - OUT_OF_PROCESS_APPLICATION(); - - ~OUT_OF_PROCESS_APPLICATION(); - - __override - HRESULT Initialize(_In_ APPLICATION_MANAGER* pApplicationManager, - _In_ ASPNETCORE_CONFIG* pConfiguration); - - __override - VOID OnAppOfflineHandleChange(); - - __override - REQUEST_NOTIFICATION_STATUS - ExecuteRequest( - _In_ IHttpContext* pHttpContext - ); - - HRESULT - GetProcess( - _In_ IHttpContext *context, - _Out_ SERVER_PROCESS **ppServerProcess - ) - { - return m_pProcessManager->GetProcess(context, m_pConfiguration, ppServerProcess); - } - -private: - - PROCESS_MANAGER* m_pProcessManager; -}; diff --git a/src/AspNetCore/Inc/proxymodule.h b/src/AspNetCore/Inc/proxymodule.h index f05438c5c1..7e5f30a8eb 100644 --- a/src/AspNetCore/Inc/proxymodule.h +++ b/src/AspNetCore/Inc/proxymodule.h @@ -1,18 +1,19 @@ - // 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 "forwardinghandler.h" +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer *g_pHttpServer; +extern HMODULE g_hAspnetCoreRH; -class CProxyModule : public CHttpModule +class ASPNET_CORE_PROXY_MODULE : public CHttpModule { public: - CProxyModule(); + ASPNET_CORE_PROXY_MODULE(); - ~CProxyModule(); + ~ASPNET_CORE_PROXY_MODULE(); void * operator new(size_t size, IModuleAllocator * pPlacement) { @@ -44,10 +45,12 @@ class CProxyModule : public CHttpModule private: - FORWARDING_HANDLER * m_pHandler; + APPLICATION_INFO *m_pApplicationInfo; + APPLICATION *m_pApplication; + REQUEST_HANDLER *m_pHandler; }; -class CProxyModuleFactory : public IHttpModuleFactory +class ASPNET_CORE_PROXY_MODULE_FACTORY : public IHttpModuleFactory { public: HRESULT diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index be91685c10..45cd8f1ef5 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -6,6 +6,7 @@ #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." diff --git a/src/AspNetCore/Src/application.cxx b/src/AspNetCore/Src/application.cxx deleted file mode 100644 index 2bd5b688b8..0000000000 --- a/src/AspNetCore/Src/application.cxx +++ /dev/null @@ -1,78 +0,0 @@ -// 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::~APPLICATION() -{ - 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; - } -} - -HRESULT -APPLICATION::StartMonitoringAppOffline() -{ - HRESULT hr = S_OK; - if (m_pFileWatcherEntry != NULL) - { - hr = m_pFileWatcherEntry->Create(m_pConfiguration->QueryApplicationFullPath()->QueryStr(), L"app_offline.htm", this, NULL); - } - return hr; -} - -VOID -APPLICATION::UpdateAppOfflineFileHandle() -{ - STRU strFilePath; - PATH::ConvertPathToFullPath(L".\\app_offline.htm", m_pConfiguration->QueryApplicationFullPath()->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; - } - } - - OnAppOfflineHandleChange(); - } -} \ No newline at end of file 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 index f2640a55a5..094f1d5e1c 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCore/Src/applicationmanager.cxx @@ -6,28 +6,28 @@ APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; HRESULT -APPLICATION_MANAGER::GetApplication( - _In_ IHttpContext* pContext, +APPLICATION_MANAGER::GetApplicationInfo( + _In_ IHttpServer* pServer, _In_ ASPNETCORE_CONFIG* pConfig, - _Out_ APPLICATION ** ppApplication + _Out_ APPLICATION_INFO ** ppApplicationInfo ) { - HRESULT hr = S_OK; - APPLICATION *pApplication = NULL; - APPLICATION_KEY key; - BOOL fExclusiveLock = FALSE; - BOOL fMixedHostingModelError = FALSE; - BOOL fDuplicatedInProcessApp = FALSE; - PCWSTR pszApplicationId = NULL; - LPCWSTR apsz[1]; + 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 ); - *ppApplication = NULL; - - DBG_ASSERT(pContext != NULL); - DBG_ASSERT(pContext->GetApplication() != NULL); + *ppApplicationInfo = NULL; - pszApplicationId = pContext->GetApplication()->GetApplicationId(); + DBG_ASSERT(pServer != NULL); + DBG_ASSERT(pConfig != NULL); + + pszApplicationId = pConfig->QueryConfigPath()->QueryStr(); hr = key.Initialize(pszApplicationId); if (FAILED(hr)) @@ -35,33 +35,39 @@ APPLICATION_MANAGER::GetApplication( goto Finished; } - m_pApplicationHash->FindKey(&key, ppApplication); + 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 (*ppApplication == NULL) + if (*ppApplicationInfo == NULL) { switch (pConfig->QueryHostingModel()) { case HOSTING_IN_PROCESS: - if (m_pApplicationHash->Count() > 0) + 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; } - pApplication = new IN_PROCESS_APPLICATION(); break; case HOSTING_OUT_PROCESS: - pApplication = new OUT_OF_PROCESS_APPLICATION(); break; default: hr = E_UNEXPECTED; goto Finished; } - - if (pApplication == NULL) + pApplicationInfo = new APPLICATION_INFO(pServer); + if (pApplicationInfo == NULL) { hr = E_OUTOFMEMORY; goto Finished; @@ -69,13 +75,19 @@ APPLICATION_MANAGER::GetApplication( AcquireSRWLockExclusive(&m_srwLock); fExclusiveLock = TRUE; - m_pApplicationHash->FindKey(&key, ppApplication); + 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 (*ppApplication != NULL) + if (*ppApplicationInfo != NULL) { // someone else created the application - delete pApplication; - pApplication = NULL; + delete pApplicationInfo; + pApplicationInfo = NULL; goto Finished; } @@ -92,13 +104,13 @@ APPLICATION_MANAGER::GetApplication( } } - hr = pApplication->Initialize(this, pConfig); + hr = pApplicationInfo->Initialize(pConfig, m_pFileWatcher); if (FAILED(hr)) { goto Finished; } - hr = m_pApplicationHash->InsertRecord( pApplication ); + hr = m_pApplicationInfoHash->InsertRecord( pApplicationInfo ); if (FAILED(hr)) { goto Finished; @@ -112,13 +124,12 @@ APPLICATION_MANAGER::GetApplication( m_hostingModel = pConfig->QueryHostingModel(); } + *ppApplicationInfo = pApplicationInfo; ReleaseSRWLockExclusive(&m_srwLock); fExclusiveLock = FALSE; - pApplication->StartMonitoringAppOffline(); - - *ppApplication = pApplication; - pApplication = NULL; + pApplicationInfo->StartMonitoringAppOffline(); + pApplicationInfo = NULL; } Finished: @@ -128,21 +139,21 @@ Finished: ReleaseSRWLockExclusive(&m_srwLock); } + if (pApplicationInfo != NULL) + { + pApplicationInfo->DereferenceApplicationInfo(); + pApplicationInfo = NULL; + } + if (FAILED(hr)) { - if (pApplication != NULL) - { - pApplication->DereferenceApplication(); - pApplication = NULL; - } - if (fDuplicatedInProcessApp) { if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG, pszApplicationId))) { - apsz[0] = strEventMsg.QueryStr(); + /*apsz[0] = strEventMsg.QueryStr(); if (FORWARDING_HANDLER::QueryEventLog() != NULL) { ReportEventW(FORWARDING_HANDLER::QueryEventLog(), @@ -154,30 +165,30 @@ Finished: 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); - } - } + //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 { @@ -187,7 +198,7 @@ Finished: hr))) { apsz[0] = strEventMsg.QueryStr(); - if (FORWARDING_HANDLER::QueryEventLog() != NULL) + /*if (FORWARDING_HANDLER::QueryEventLog() != NULL) { ReportEventW(FORWARDING_HANDLER::QueryEventLog(), EVENTLOG_ERROR_TYPE, @@ -198,7 +209,7 @@ Finished: 0, apsz, NULL); - } + }*/ } } } @@ -208,23 +219,33 @@ Finished: HRESULT APPLICATION_MANAGER::RecycleApplication( - _In_ LPCWSTR pszApplication + _In_ LPCWSTR pszApplicationId ) { HRESULT hr = S_OK; - APPLICATION_KEY key; + APPLICATION_INFO_KEY key; - hr = key.Initialize(pszApplication); + hr = key.Initialize(pszApplicationId); if (FAILED(hr)) { goto Finished; } AcquireSRWLockExclusive(&m_srwLock); - m_pApplicationHash->DeleteKey(&key); - if (m_pApplicationHash->Count() == 0) + 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: @@ -232,65 +253,17 @@ Finished: return hr; } -HRESULT -APPLICATION_MANAGER::Get502ErrorPage( - _Out_ HTTP_DATA_CHUNK** ppErrorPage -) +VOID +APPLICATION_MANAGER::ShutDown() { - HRESULT hr = S_OK; - BOOL fExclusiveLock = FALSE; - HTTP_DATA_CHUNK *pHttp502ErrorPage = NULL; - - DBG_ASSERT(ppErrorPage != NULL); - - //on-demand create the error page - if (m_pHttp502ErrorPage != NULL) - { - *ppErrorPage = m_pHttp502ErrorPage; - } - else + m_fInShutdown = TRUE; + if (m_pApplicationInfoHash != NULL) { AcquireSRWLockExclusive(&m_srwLock); - fExclusiveLock = TRUE; - if (m_pHttp502ErrorPage != NULL) - { - *ppErrorPage = m_pHttp502ErrorPage; - } - else - { - size_t maxsize = 5000; - pHttp502ErrorPage = new HTTP_DATA_CHUNK(); - if (pHttp502ErrorPage == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); - goto Finished; - } - pHttp502ErrorPage->DataChunkType = HttpDataChunkFromMemory; - pHttp502ErrorPage->FromMemory.pBuffer = (PVOID)m_pstrErrorInfo; - pHttp502ErrorPage->FromMemory.BufferLength = (ULONG)strnlen(m_pstrErrorInfo, maxsize); //(ULONG)(wcslen(m_pstrErrorInfo)); // *sizeof(WCHAR); - if(m_pHttp502ErrorPage != NULL) - { - delete m_pHttp502ErrorPage; - } - m_pHttp502ErrorPage = pHttp502ErrorPage; - *ppErrorPage = m_pHttp502ErrorPage; - } - } - -Finished: - if (fExclusiveLock) - { + // clean up the hash table so that the application will be informed on shutdown + m_pApplicationInfoHash->Clear(); ReleaseSRWLockExclusive(&m_srwLock); } - if (FAILED(hr)) - { - if (pHttp502ErrorPage != NULL) - { - delete pHttp502ErrorPage; - } - } - - return hr; -} \ No newline at end of file +} diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index e25cc49623..d61a729361 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -6,35 +6,42 @@ HTTP_MODULE_ID g_pModuleId = NULL; IHttpServer * g_pHttpServer = NULL; -BOOL g_fAsyncDisconnectAvailable = FALSE; -BOOL g_fWinHttpNonBlockingCallbackAvailable = FALSE; BOOL g_fRecycleProcessCalled = FALSE; PCWSTR g_pszModuleName = NULL; HINSTANCE g_hModule; -HINSTANCE g_hWinHttpModule; -BOOL g_fWebSocketSupported = FALSE; - -DWORD g_dwTlsIndex = TLS_OUT_OF_INDEXES; -BOOL g_fEnableReferenceCountTracing = FALSE; +HMODULE g_hAspnetCoreRH = NULL; +BOOL g_fAspnetcoreRHAssemblyLoaded = FALSE; +BOOL g_fAspnetcoreRHLoadedError = FALSE; DWORD g_dwAspNetCoreDebugFlags = 0; -BOOL g_fNsiApiNotSupported = FALSE; DWORD g_dwActiveServerProcesses = 0; -DWORD g_OptionalWinHttpFlags = 0; //specify additional WinHTTP options when using WinHttpOpenRequest API. - +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; } @@ -42,75 +49,6 @@ BOOL WINAPI DllMain(HMODULE hModule, return TRUE; } -VOID -LoadGlobalConfiguration( -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; - } -} - HRESULT __stdcall RegisterModule( @@ -138,8 +76,14 @@ HRESULT --*/ { - HRESULT hr = S_OK; - CProxyModuleFactory * pFactory = NULL; + 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"); @@ -148,63 +92,50 @@ HRESULT CREATE_DEBUG_PRINT_OBJECT; - LoadGlobalConfiguration(); + //LoadGlobalConfiguration(); - // - // 7.0 is 0,7 - // - if (dwServerVersion > MAKELONG(0, 7)) - { - g_fAsyncDisconnectAvailable = TRUE; - } - - // - // 8.0 is 0,8 - // - if (dwServerVersion >= MAKELONG(0, 8)) - { - // IISOOB:36641 Enable back WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS for Win8. - // g_fWinHttpNonBlockingCallbackAvailable = TRUE; - g_fWebSocketSupported = TRUE; - } - - hr = WINHTTP_HELPER::StaticInitialize(); - if (FAILED(hr)) - { - if (hr == HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND)) - { - g_fWebSocketSupported = FALSE; - } - else - { - goto Finished; - } - } + InitializeSRWLock(&g_srwLock); g_pModuleId = pModuleInfo->GetId(); g_pszModuleName = pModuleInfo->GetName(); g_pHttpServer = pHttpServer; - g_hWinHttpModule = GetModuleHandle(TEXT("winhttp.dll")); - // - // WinHTTP does not create enough threads, ask it to create more. - // Starting in Windows 7, this setting is ignored because WinHTTP - // uses a thread pool. - // - SYSTEM_INFO si; - GetSystemInfo(&si); - DWORD dwThreadCount = (si.dwNumberOfProcessors * 3 + 1) / 2; - WinHttpSetOption(NULL, - WINHTTP_OPTION_WORKER_THREAD_COUNT, - &dwThreadCount, - sizeof(dwThreadCount)); + // 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 CProxyModuleFactory::Terminate method will clean any + // The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any // static object initialized. // - pFactory = new CProxyModuleFactory; + pFactory = new ASPNET_CORE_PROXY_MODULE_FACTORY; if (pFactory == NULL) { @@ -213,28 +144,45 @@ HRESULT } hr = pModuleInfo->SetRequestNotifications( - pFactory, - RQ_EXECUTE_REQUEST_HANDLER, - 0); + 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; - g_pResponseHeaderHash = new RESPONSE_HEADER_HASH; - if (g_pResponseHeaderHash == NULL) + pGlobalModule = new ASPNET_CORE_GLOBAL_MODULE(pApplicationManager); + if (pGlobalModule == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - hr = g_pResponseHeaderHash->Initialize(); + 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)) @@ -242,19 +190,12 @@ HRESULT 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 (pGlobalModule != NULL) + { + delete pGlobalModule; + pGlobalModule = NULL; + } if (pFactory != NULL) { diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCore/Src/filewatcher.cxx index 9bd5d6c2da..0765b1c39e 100644 --- a/src/AspNetCore/Src/filewatcher.cxx +++ b/src/AspNetCore/Src/filewatcher.cxx @@ -13,9 +13,17 @@ 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 @@ -97,13 +105,13 @@ Win32 error &pOverlapped, INFINITE); - DBG_ASSERT(fSuccess); + DBG_ASSERT(fSuccess); DebugPrint(1, "FILE_WATCHER::ChangeNotificationThread"); dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) { - continue; + break; } DBG_ASSERT(pOverlapped != NULL); @@ -117,6 +125,8 @@ Win32 error pOverlapped = NULL; cbCompletion = 0; } + + return 0; } VOID @@ -173,7 +183,7 @@ FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : _pFileMonitor(pFileMonitor), _hDirectory(INVALID_HANDLE_VALUE), _hImpersonationToken(NULL), - _pApplication(NULL), + _pApplicationInfo(NULL), _lStopMonitorCalled(0), _cRefs(1), _fIsValid(TRUE) @@ -253,7 +263,7 @@ HRESULT // Othersie we have to cache the file info // if (cbCompletion == 0) - { + { fFileChanged = TRUE; } else @@ -266,9 +276,9 @@ HRESULT // // check whether the monitored file got changed // - if (_wcsnicmp(pNotificationInfo->FileName, - _strFileName.QueryStr(), - pNotificationInfo->FileNameLength/sizeof(WCHAR)) == 0) + if (_wcsnicmp(pNotificationInfo->FileName, + _strFileName.QueryStr(), + pNotificationInfo->FileNameLength / sizeof(WCHAR)) == 0) { fFileChanged = TRUE; break; @@ -284,7 +294,7 @@ HRESULT { pNotificationInfo = (FILE_NOTIFY_INFORMATION*) ((PBYTE)pNotificationInfo + - pNotificationInfo->NextEntryOffset); + pNotificationInfo->NextEntryOffset); } } } @@ -294,7 +304,7 @@ HRESULT // // so far we only monitoring app_offline // - _pApplication->UpdateAppOfflineFileHandle(); + _pApplicationInfo->UpdateAppOfflineFileHandle(); } Finished: @@ -314,7 +324,7 @@ FILE_WATCHER_ENTRY::Monitor(VOID) ReferenceFileWatcherEntry(); ZeroMemory(&_overlapped, sizeof(_overlapped)); - if(!ReadDirectoryChangesW(_hDirectory, + if (!ReadDirectoryChangesW(_hDirectory, _buffDirectoryChanges.QueryPtr(), _buffDirectoryChanges.QuerySize(), FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline @@ -355,7 +365,7 @@ HRESULT FILE_WATCHER_ENTRY::Create( _In_ PCWSTR pszDirectoryToMonitor, _In_ PCWSTR pszFileNameToMonitor, - _In_ APPLICATION* pApplication, + _In_ APPLICATION_INFO* pApplicationInfo, _In_ HANDLE hImpersonationToken ) { @@ -364,7 +374,7 @@ FILE_WATCHER_ENTRY::Create( if (pszDirectoryToMonitor == NULL || pszFileNameToMonitor == NULL || - pApplication == NULL) + pApplicationInfo == NULL) { DBG_ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); @@ -374,7 +384,7 @@ FILE_WATCHER_ENTRY::Create( // //remember the application // - _pApplication = pApplication; + _pApplicationInfo = pApplicationInfo; if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) { diff --git a/src/AspNetCore/Src/fx_ver.cxx b/src/AspNetCore/Src/fx_ver.cxx deleted file mode 100644 index 1c844d3113..0000000000 --- a/src/AspNetCore/Src/fx_ver.cxx +++ /dev/null @@ -1,195 +0,0 @@ -// 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 -#include -#include "fx_ver.h" -#include "precomp.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/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/inprocessapplication.cxx b/src/AspNetCore/Src/inprocessapplication.cxx deleted file mode 100644 index 73430009e7..0000000000 --- a/src/AspNetCore/Src/inprocessapplication.cxx +++ /dev/null @@ -1,686 +0,0 @@ -// 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" -#include - -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() : - m_ProcessExitCode ( 0 ), - m_fManagedAppLoaded ( FALSE ), - m_fLoadManagedAppError ( FALSE ), - m_fInitialized ( FALSE ) -{ -} - -IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() -{ - Recycle(); -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_APPLICATION::OnAsyncCompletion( - IHttpContext* pHttpContext, - DWORD cbCompletion, - HRESULT hrCompletionStatus -) -{ - HRESULT hr; - IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = NULL; - REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; - - hr = IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext(pHttpContext, &pInProcessStoredContext); - if (FAILED(hr)) - { - // Finish the request as we couldn't get the callback - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 19, hr); - return RQ_NOTIFICATION_FINISH_REQUEST; - } - else if (pInProcessStoredContext->QueryIsManagedRequestComplete()) - { - // means PostCompletion has been called and this is the associated callback. - dwRequestNotificationStatus = pInProcessStoredContext->QueryAsyncCompletionStatus(); - // TODO cleanup whatever disconnect listener there is - return dwRequestNotificationStatus; - } - else - { - // Call the managed handler for async completion. - return m_AsyncCompletionHandler(pInProcessStoredContext->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); - } -} - -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_RequstHandlerContext = pvRequstHandlerContext; - m_ShutdownHandler = shutdown_handler; - m_ShutdownHandlerContext = pvShutdownHandlerContext; - m_AsyncCompletionHandler = async_completion_handler; - - // Initialization complete - SetEvent(m_pInitalizeEvent); -} - -// -// Initialize is guarded by a lock inside APPLICATION_MANAGER::GetApplication -// It ensures only one application will be initialized and singleton -// Error wuill happen if you call Initialized outside APPLICATION_MANAGER::GetApplication -// -__override -HRESULT -IN_PROCESS_APPLICATION::Initialize( - _In_ APPLICATION_MANAGER* pApplicationManager, - _In_ ASPNETCORE_CONFIG* pConfiguration -) -{ - HRESULT hr = S_OK; - DBG_ASSERT(pApplicationManager != NULL); - DBG_ASSERT(pConfiguration != NULL); - - m_pConfiguration = pConfiguration; - m_pApplicationManager = pApplicationManager; - hr = m_applicationKey.Initialize(pConfiguration->QueryApplicationPath()->QueryStr()); - if (FAILED(hr)) - { - goto Finished; - } - - // check app_offline - UpdateAppOfflineFileHandle(); - - if (m_pFileWatcherEntry == NULL) - { - m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(m_pApplicationManager->GetFileWatcher()); - if (m_pFileWatcherEntry == NULL) - { - hr = E_OUTOFMEMORY; - 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()); - goto Finished; - } - m_fInitialized = TRUE; - -Finished: - return hr; -} - -HRESULT -IN_PROCESS_APPLICATION::LoadManagedApplication() -{ - 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; - } - - 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; - } - - // If the debugger is attached, never timeout - if (IsDebuggerPresent()) - { - dwTimeout = INFINITE; - } - else - { - dwTimeout = m_pConfiguration->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) - { - // do we need kill the backend thread - 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 ? - - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, - m_pConfiguration->QueryApplicationPath()->QueryStr(), - m_pConfiguration->QueryApplicationFullPath()->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; -} - -VOID -IN_PROCESS_APPLICATION::Recycle( - VOID -) -{ - if (m_fInitialized) - { - DWORD dwThreadStatus = 0; - DWORD dwTimeout = m_pConfiguration->QueryShutdownTimeLimitInMS(); - - AcquireSRWLockExclusive(&m_srwLock); - - if (!g_pHttpServer->IsCommandLineLaunch() && - !g_fRecycleProcessCalled && - (g_pHttpServer->GetAdminManager() != NULL)) - { - // IIS scenario. - // notify IIS first so that new request will be routed to new worker process - g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand"); - } - - g_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 (g_pHttpServer && g_pHttpServer->IsCommandLineLaunch()) - { - // IISExpress scenario - // Can only call exit to terminate current process - exit(0); - } - } -} - -VOID -IN_PROCESS_APPLICATION::OnAppOfflineHandleChange() -{ - // only recycle the worker process after managed app was loaded - // app_offline scenario managed application has not been loaded yet - if (m_fManagedAppLoaded || m_fLoadManagedAppError) - { - Recycle(); - - } -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_APPLICATION::ExecuteRequest( - _In_ IHttpContext* pHttpContext -) -{ - if (m_RequestHandler != NULL) - { - return m_RequestHandler(pHttpContext, m_RequstHandlerContext); - } - - // - // 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, - E_APPLICATION_ACTIVATION_EXEC_FAILURE); - } - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); - return RQ_NOTIFICATION_FINISH_REQUEST; -} - -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 = 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(); - PATH::ConvertPathToFullPath(m_pConfiguration->QueryArguments()->QueryStr(), - m_pConfiguration->QueryApplicationFullPath()->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; - - m_ProcessExitCode = pProc(2, argv); - if (m_ProcessExitCode != 0) - { - - } - -Finished: - // - // this method is called by the background thread and should never exit unless shutdown - // - if (!g_fRecycleProcessCalled) - { - STRU strEventMsg; - LPCWSTR apsz[1]; - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, - m_pConfiguration->QueryApplicationPath()->QueryStr(), - m_pConfiguration->QueryApplicationFullPath()->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; -} - - -// 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::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)) - { - max_ver = std::max(max_ver, fx_ver); - } - } - - hr = pstrResult->Copy(max_ver.as_str().c_str()); - - // we check FAILED(hr) outside of function - return hr; -} diff --git a/src/AspNetCore/Src/inprocessstoredcontext.cxx b/src/AspNetCore/Src/inprocessstoredcontext.cxx deleted file mode 100644 index c0b2d8ff16..0000000000 --- a/src/AspNetCore/Src/inprocessstoredcontext.cxx +++ /dev/null @@ -1,113 +0,0 @@ -// 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" - -IN_PROCESS_STORED_CONTEXT::IN_PROCESS_STORED_CONTEXT( - IHttpContext* pHttpContext, - PVOID pMangedHttpContext -) -{ - // TODO if we want to go by IIS patterns, we should have these in a separate initialize function - m_pManagedHttpContext = pMangedHttpContext; - m_pHttpContext = pHttpContext; - m_fManagedRequestComplete = FALSE; -} - -IN_PROCESS_STORED_CONTEXT::~IN_PROCESS_STORED_CONTEXT() -{ -} - -PVOID -IN_PROCESS_STORED_CONTEXT::QueryManagedHttpContext( - VOID -) -{ - return m_pManagedHttpContext; -} - -IHttpContext* -IN_PROCESS_STORED_CONTEXT::QueryHttpContext( - VOID -) -{ - return m_pHttpContext; -} - -BOOL -IN_PROCESS_STORED_CONTEXT::QueryIsManagedRequestComplete( - VOID -) -{ - return m_fManagedRequestComplete; -} - -VOID -IN_PROCESS_STORED_CONTEXT::IndicateManagedRequestComplete( - VOID -) -{ - m_fManagedRequestComplete = TRUE; -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_STORED_CONTEXT::QueryAsyncCompletionStatus( - VOID -) -{ - return m_requestNotificationStatus; -} - -VOID -IN_PROCESS_STORED_CONTEXT::SetAsyncCompletionStatus( - REQUEST_NOTIFICATION_STATUS requestNotificationStatus -) -{ - m_requestNotificationStatus = requestNotificationStatus; -} - -HRESULT -IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext( - IHttpContext* pHttpContext, - IN_PROCESS_STORED_CONTEXT** ppInProcessStoredContext -) -{ - if (pHttpContext == NULL) - { - return E_FAIL; - } - - if (ppInProcessStoredContext == NULL) - { - return E_FAIL; - } - - *ppInProcessStoredContext = (IN_PROCESS_STORED_CONTEXT*)pHttpContext->GetModuleContextContainer()->GetModuleContext(g_pModuleId); - if (*ppInProcessStoredContext == NULL) - { - return E_FAIL; - } - - return S_OK; -} - -HRESULT -IN_PROCESS_STORED_CONTEXT::SetInProcessStoredContext( - IHttpContext* pHttpContext, - IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext -) -{ - if (pHttpContext == NULL) - { - return E_FAIL; - } - if (pInProcessStoredContext == NULL) - { - return E_FAIL; - } - - return pHttpContext->GetModuleContextContainer()->SetModuleContext( - pInProcessStoredContext, - g_pModuleId - ); -} \ No newline at end of file diff --git a/src/AspNetCore/Src/outprocessapplication.cxx b/src/AspNetCore/Src/outprocessapplication.cxx deleted file mode 100644 index ce76d3be14..0000000000 --- a/src/AspNetCore/Src/outprocessapplication.cxx +++ /dev/null @@ -1,121 +0,0 @@ -// 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" - -OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION() - : m_pProcessManager(NULL) -{ -} - -OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() -{ - if (m_pProcessManager != NULL) - { - m_pProcessManager->ShutdownAllProcesses(); - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } -} - - -// -// Initialize is guarded by a lock inside APPLICATION_MANAGER::GetApplication -// It ensures only one application will be initialized and singleton -// Error will happen if you call Initialized outside APPLICATION_MANAGER::GetApplication -// -__override -HRESULT -OUT_OF_PROCESS_APPLICATION::Initialize( - _In_ APPLICATION_MANAGER* pApplicationManager, - _In_ ASPNETCORE_CONFIG* pConfiguration -) -{ - HRESULT hr = S_OK; - - DBG_ASSERT(pApplicationManager != NULL); - DBG_ASSERT(pConfiguration != NULL); - - m_pApplicationManager = pApplicationManager; - m_pConfiguration = pConfiguration; - - hr = m_applicationKey.Initialize(pConfiguration->QueryApplicationPath()->QueryStr()); - if (FAILED(hr)) - { - goto Finished; - } - - 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; - } - } - - if (m_pFileWatcherEntry == NULL) - { - m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pApplicationManager->GetFileWatcher()); - if (m_pFileWatcherEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - } - - UpdateAppOfflineFileHandle(); - -Finished: - - if (FAILED(hr)) - { - if (m_pFileWatcherEntry != NULL) - { - m_pFileWatcherEntry->DereferenceFileWatcherEntry(); - m_pFileWatcherEntry = NULL; - } - - if (m_pProcessManager != NULL) - { - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } - } - - return hr; -} - -__override -VOID -OUT_OF_PROCESS_APPLICATION::OnAppOfflineHandleChange() -{ - // - // Sending signal to backend process for shutdown - // - if (m_pProcessManager != NULL) - { - m_pProcessManager->SendShutdownSignal(); - } -} - -__override -REQUEST_NOTIFICATION_STATUS -OUT_OF_PROCESS_APPLICATION::ExecuteRequest( - _In_ IHttpContext* pHttpContext -) -{ - // - // TODO: - // Ideally we should wrap the fowaring logic inside FORWARDING_HANDLER inside this function - // To achieve better abstraction. It is too risky to do it now - // - return RQ_NOTIFICATION_FINISH_REQUEST; -} \ No newline at end of file diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index 403d949256..b0c2c5cc4e 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -54,13 +54,6 @@ #define WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS 111 #endif -#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module" -#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module" - -#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))) - #ifdef max #undef max template inline T max(T a, T b) @@ -97,43 +90,33 @@ inline bool IsSpace(char ch) #include #include "stringa.h" #include "stringu.h" -//#include "treehash.h" - #include "dbgutil.h" #include "ahutil.h" #include "multisz.h" #include "multisza.h" #include "base64.h" -#include "sttimer.h" #include #include #include #include #include -#include "environmentvariablehash.h" -#include "..\aspnetcore_msg.h" -#include "aspnetcore_event.h" -#include "aspnetcoreconfig.h" -#include "serverprocess.h" -#include "processmanager.h" +#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 "application.h" +#include "applicationinfo.h" #include "applicationmanager.h" -#include "inprocessstoredcontext.h" -#include "inprocessapplication.h" -#include "outprocessapplication.h" +#include "globalmodule.h" #include "resource.h" -#include "path.h" -#include "debugutil.h" -#include "protocolconfig.h" -#include "responseheaderhash.h" -#include "forwarderconnection.h" -#include "winhttphelper.h" -#include "websockethandler.h" -#include "forwardinghandler.h" #include "proxymodule.h" -#include "fx_ver.h" + FORCEINLINE DWORD @@ -158,11 +141,15 @@ HRESULT_FROM_GETLASTERROR() : E_FAIL; } -extern BOOL g_fAsyncDisconnectAvailable; -extern BOOL g_fWinHttpNonBlockingCallbackAvailable; extern PVOID g_pModuleId; -extern BOOL g_fWebSocketSupported; +extern BOOL g_fAspnetcoreRHAssemblyLoaded; +extern BOOL g_fAspnetcoreRHLoadedError; extern BOOL g_fEnableReferenceCountTracing; extern DWORD g_dwActiveServerProcesses; -extern DWORD g_OptionalWinHttpFlags; +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/processmanager.cxx b/src/AspNetCore/Src/processmanager.cxx deleted file mode 100644 index 39a9a1811d..0000000000 --- a/src/AspNetCore/Src/processmanager.cxx +++ /dev/null @@ -1,294 +0,0 @@ -// 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_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_ IHttpContext *context, - _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 **ppSelectedServerProcess = 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;iIsReady() ) - { - m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); - *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. - // - - 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 ) - { - m_ppServerProcessList[dwProcessIndex] = new SERVER_PROCESS(); - if( m_ppServerProcessList[dwProcessIndex] == NULL ) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = m_ppServerProcessList[dwProcessIndex]->Initialize( - this, - pConfig->QueryProcessPath(), - pConfig->QueryArguments(), - pConfig->QueryStartupTimeLimitInMS(), - pConfig->QueryShutdownTimeLimitInMS(), - pConfig->QueryWindowsAuthEnabled(), - pConfig->QueryBasicAuthEnabled(), - pConfig->QueryAnonymousAuthEnabled(), - pConfig->QueryEnvironmentVariables(), - pConfig->QueryStdoutLogEnabled(), - pConfig->QueryStdoutLogFile() - ); - if( FAILED( hr ) ) - { - goto Finished; - } - - hr = m_ppServerProcessList[dwProcessIndex]->StartProcess(context); - if( FAILED( hr ) ) - { - goto Finished; - } - } - - if( !m_ppServerProcessList[dwProcessIndex]->IsReady() ) - { - hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); - goto Finished; - } - - m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); - *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; - } - -Finished: - - if( FAILED(hr) ) - { - if(m_ppServerProcessList[dwProcessIndex] != NULL ) - { - m_ppServerProcessList[dwProcessIndex]->DereferenceServerProcess(); - m_ppServerProcessList[dwProcessIndex] = NULL; - } - } - - if( fSharedLock ) - { - ReleaseSRWLockShared( &m_srwLock ); - fSharedLock = FALSE; - } - - if( fExclusiveLock ) - { - ReleaseSRWLockExclusive( &m_srwLock ); - fExclusiveLock = FALSE; - } - - return hr; -} \ No newline at end of file diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index 45f16a68e5..6f9b6e247a 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -5,12 +5,12 @@ __override HRESULT -CProxyModuleFactory::GetHttpModule( +ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule( CHttpModule ** ppModule, IModuleAllocator * pAllocator ) { - CProxyModule *pModule = new (pAllocator) CProxyModule(); + ASPNET_CORE_PROXY_MODULE *pModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(); if (pModule == NULL) { return E_OUTOFMEMORY; @@ -22,7 +22,7 @@ CProxyModuleFactory::GetHttpModule( __override VOID -CProxyModuleFactory::Terminate( +ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate( VOID ) /*++ @@ -41,39 +41,38 @@ Return value: --*/ { - FORWARDING_HANDLER::StaticTerminate(); + /* FORWARDING_HANDLER::StaticTerminate(); - WEBSOCKET_HANDLER::StaticTerminate(); - - if (g_pResponseHeaderHash != NULL) - { - g_pResponseHeaderHash->Clear(); - delete g_pResponseHeaderHash; - g_pResponseHeaderHash = NULL; - } + WEBSOCKET_HANDLER::StaticTerminate();*/ ALLOC_CACHE_HANDLER::StaticTerminate(); delete this; } -CProxyModule::CProxyModule( -) : m_pHandler(NULL) +ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE( +) : m_pApplicationInfo(NULL), m_pHandler(NULL) { } -CProxyModule::~CProxyModule() +ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE() { + if (m_pApplicationInfo != NULL) + { + m_pApplicationInfo->DereferenceApplicationInfo(); + m_pApplicationInfo = NULL; + } + if (m_pHandler != NULL) { - m_pHandler->DereferenceForwardingHandler(); + m_pHandler->DereferenceRequestHandler(); m_pHandler = NULL; } } __override REQUEST_NOTIFICATION_STATUS -CProxyModule::OnExecuteRequestHandler( +ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( IHttpContext * pHttpContext, IHttpEventProvider * ) @@ -81,47 +80,105 @@ CProxyModule::OnExecuteRequestHandler( HRESULT hr = S_OK; ASPNETCORE_CONFIG *pConfig = NULL; APPLICATION_MANAGER *pApplicationManager = NULL; - APPLICATION *pApplication = NULL; - hr = ASPNETCORE_CONFIG::GetConfig(pHttpContext, &pConfig); + 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 Failed; + goto Finished; } pApplicationManager = APPLICATION_MANAGER::GetInstance(); if (pApplicationManager == NULL) { hr = E_OUTOFMEMORY; - goto Failed; + goto Finished; } - hr = pApplicationManager->GetApplication( - pHttpContext, - pConfig, - &pApplication); + hr = pApplicationManager->GetApplicationInfo( + g_pHttpServer, + pConfig, + &m_pApplicationInfo); if (FAILED(hr)) { - goto Failed; + goto Finished; } - m_pHandler = new FORWARDING_HANDLER(pHttpContext, pApplication); - - if (m_pHandler == NULL) + // app_offline check to avoid loading aspnetcorerh.dll unnecessarily + if (m_pApplicationInfo->AppOfflineFound()) { - hr = E_OUTOFMEMORY; - goto Failed; + // 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; } - return m_pHandler->OnExecuteRequestHandler(); + // make sure assmebly is loaded and application is created -Failed: - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); - return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; + 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 -CProxyModule::OnAsyncCompletion( +ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion( IHttpContext *, DWORD, BOOL, 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/AspNetCore/Src/aspnetcoreconfig.cxx b/src/CommonLib/aspnetcoreconfig.cxx similarity index 81% rename from src/AspNetCore/Src/aspnetcoreconfig.cxx rename to src/CommonLib/aspnetcoreconfig.cxx index 99ccac1c3e..ea0799e87e 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/CommonLib/aspnetcoreconfig.cxx @@ -1,56 +1,47 @@ // 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 "stdafx.h" +#include "aspnetcoreconfig.h" ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() { - if (QueryHostingModel() == HOSTING_IN_PROCESS && - !g_fRecycleProcessCalled && - (g_pHttpServer->GetAdminManager() != NULL)) - { - // There is a bug in IHttpServer::RecycleProcess. It will hit AV when worker process - // has already been in recycling state. - // To workaround, do null check on GetAdminManager(). If it is NULL, worker process is in recycling - // Do not call RecycleProcess again - - // RecycleProcess can olny be called once - // In case of configuration change for in-process app - // We want notify IIS first to let new request routed to new worker process - - g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Configuration Change"); - } - - // It's safe for us to set this g_fRecycleProcessCalled - // as in_process scenario will always recycle the worker process for configuration change - g_fRecycleProcessCalled = TRUE; - - m_struApplicationFullPath.Reset(); if (m_pEnvironmentVariables != NULL) { m_pEnvironmentVariables->Clear(); delete m_pEnvironmentVariables; m_pEnvironmentVariables = NULL; } +} - if (!m_struApplication.IsEmpty()) - { - APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); - } +VOID +ASPNETCORE_CONFIG::ReferenceConfiguration( + VOID +) const +{ + InterlockedIncrement(&m_cRefs); +} - if (QueryHostingModel() == HOSTING_IN_PROCESS && - g_pHttpServer->IsCommandLineLaunch()) + +VOID +ASPNETCORE_CONFIG::DereferenceConfiguration( + VOID +) const +{ + DBG_ASSERT(m_cRefs != 0); + LONG cRefs = 0; + if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) { - // IISExpress scenario, only option is to call exit in case configuration change - // as CLR or application may change - exit(0); + delete this; } } HRESULT ASPNETCORE_CONFIG::GetConfig( + _In_ IHttpServer *pHttpServer, + _In_ HTTP_MODULE_ID pModuleId, _In_ IHttpContext *pHttpContext, - _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig ) { HRESULT hr = S_OK; @@ -67,7 +58,7 @@ ASPNETCORE_CONFIG::GetConfig( // potential bug if user sepcific config at virtual dir level pAspNetCoreConfig = (ASPNETCORE_CONFIG*) - pHttpApplication->GetModuleContextContainer()->GetModuleContext(g_pModuleId); + pHttpApplication->GetModuleContextContainer()->GetModuleContext(pModuleId); if (pAspNetCoreConfig != NULL) { @@ -83,14 +74,14 @@ ASPNETCORE_CONFIG::GetConfig( goto Finished; } - hr = pAspNetCoreConfig->Populate(pHttpContext); + hr = pAspNetCoreConfig->Populate(pHttpServer, pHttpContext); if (FAILED(hr)) { goto Finished; } hr = pHttpApplication->GetModuleContextContainer()-> - SetModuleContext(pAspNetCoreConfig, g_pModuleId); + SetModuleContext(pAspNetCoreConfig, pModuleId); if (FAILED(hr)) { if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) @@ -99,7 +90,7 @@ ASPNETCORE_CONFIG::GetConfig( pAspNetCoreConfig = (ASPNETCORE_CONFIG*)pHttpApplication-> GetModuleContextContainer()-> - GetModuleContext(g_pModuleId); + GetModuleContext(pModuleId); _ASSERT(pAspNetCoreConfig != NULL); @@ -137,11 +128,11 @@ Finished: HRESULT ASPNETCORE_CONFIG::Populate( + IHttpServer *pHttpServer, IHttpContext *pHttpContext ) { HRESULT hr = S_OK; - STACK_STRU(strSiteConfigPath, 256); STRU strEnvName; STRU strEnvValue; STRU strExpandedEnvValue; @@ -160,6 +151,10 @@ ASPNETCORE_CONFIG::Populate( 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) @@ -174,20 +169,20 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } - pAdminManager = g_pHttpServer->GetAdminManager(); - hr = strSiteConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); + pAdminManager = pHttpServer->GetAdminManager(); + hr = m_struConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); if (FAILED(hr)) { goto Finished; } - hr = m_struApplicationFullPath.Copy(pHttpContext->GetApplication()->GetApplicationPhysicalPath()); + hr = m_struApplicationPhysicalPath.Copy(pHttpContext->GetApplication()->GetApplicationPhysicalPath()); if (FAILED(hr)) { goto Finished; } - pszPath = strSiteConfigPath.QueryStr(); + pszPath = m_struConfigPath.QueryStr(); while (pszPath[dwPosition] != NULL) { if (pszPath[dwPosition] == '/') @@ -214,8 +209,15 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } - hr = pAdminManager->GetAdminSection(CS_WINDOWS_AUTHENTICATION_SECTION, - strSiteConfigPath.QueryStr(), + 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)) { @@ -235,8 +237,14 @@ ASPNETCORE_CONFIG::Populate( } } - hr = pAdminManager->GetAdminSection(CS_BASIC_AUTHENTICATION_SECTION, - strSiteConfigPath.QueryStr(), + 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)) { @@ -252,9 +260,14 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } } - - hr = pAdminManager->GetAdminSection(CS_ANONYMOUS_AUTHENTICATION_SECTION, - strSiteConfigPath.QueryStr(), + 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)) { @@ -271,8 +284,14 @@ ASPNETCORE_CONFIG::Populate( } } - hr = pAdminManager->GetAdminSection(CS_ASPNETCORE_SECTION, - strSiteConfigPath.QueryStr(), + bstrAspNetCoreSection = SysAllocString(CS_ASPNETCORE_SECTION); + if (bstrAspNetCoreSection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + hr = pAdminManager->GetAdminSection(bstrAspNetCoreSection, + m_struConfigPath.QueryStr(), &pAspNetCoreElement); if (FAILED(hr)) { @@ -402,13 +421,13 @@ ASPNETCORE_CONFIG::Populate( { goto Finished; } - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_STDOUT_LOG_FILE, - &m_struStdoutLogFile); - 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, diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/CommonLib/aspnetcoreconfig.h similarity index 81% rename from src/AspNetCore/Inc/aspnetcoreconfig.h rename to src/CommonLib/aspnetcoreconfig.h index b3fa4d7d8e..ffb4928cf9 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/CommonLib/aspnetcoreconfig.h @@ -34,11 +34,13 @@ #define MIN_PORT 1025 #define MAX_PORT 48000 -#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) +#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))) -extern HTTP_MODULE_ID g_pModuleId; -extern IHttpServer * g_pHttpServer; -extern BOOL g_fRecycleProcessCalled; +//#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) + +#include "stdafx.h" enum APP_HOSTING_MODEL { @@ -57,14 +59,16 @@ public: VOID CleanupStoredContext() { - delete this; + DereferenceConfiguration(); } static HRESULT GetConfig( - _In_ IHttpContext *pHttpContext, - _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + _In_ IHttpServer *pHttpServer, + _In_ HTTP_MODULE_ID pModuleId, + _In_ IHttpContext *pHttpContext, + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig ); ENVIRONMENT_VAR_HASH* @@ -132,11 +136,11 @@ public: } STRU* - QueryApplicationFullPath( + QueryApplicationPhysicalPath( VOID ) { - return &m_struApplicationFullPath; + return &m_struApplicationPhysicalPath; } STRU* @@ -149,30 +153,22 @@ public: STRU* QueryProcessPath( - VOID - ) + VOID + ) { return &m_struProcessPath; } APP_HOSTING_MODEL QueryHostingModel( - VOID + VOID ) { return m_hostingModel; } - STRU* - QueryHostingModelStr( - VOID - ) - { - return &m_strHostingModel; - } - BOOL - QueryStdoutLogEnabled() + QueryStdoutLogEnabled() { return m_fStdoutLogEnabled; } @@ -213,6 +209,36 @@ public: 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: // @@ -221,33 +247,40 @@ private: 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_struApplication; STRU m_struArguments; STRU m_struProcessPath; STRU m_struStdoutLogFile; - STRU m_struApplicationFullPath; - STRU m_strHostingModel; + 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/AspNetCore/Inc/debugutil.h b/src/CommonLib/debugutil.h similarity index 99% rename from src/AspNetCore/Inc/debugutil.h rename to src/CommonLib/debugutil.h index aee17b4fba..16fce88edd 100644 --- a/src/AspNetCore/Inc/debugutil.h +++ b/src/CommonLib/debugutil.h @@ -2,7 +2,6 @@ // 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 diff --git a/src/AspNetCore/Inc/environmentvariablehash.h b/src/CommonLib/environmentvariablehash.h similarity index 100% rename from src/AspNetCore/Inc/environmentvariablehash.h rename to src/CommonLib/environmentvariablehash.h 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 0000000000000000000000000000000000000000..18b5b37fe16d22894639e83cb6a90679348178ba GIT binary patch literal 350 zcmZ9IK@NgY5JTVE#5?@pMmKNB^QH+lgBC>R7HVD$28=d(}@f&XXPX{Wqj5fP3B z6*W(8CjEo<*PWSiq&xH0~bmWSUxm-1JqmTJXC6R}6Fs{HMw(;SPz jK2B9hk6i~@ErGog&xkeGDVc2&C`s%8%tpnGtpl+ObV27ji+Q)Lo$(8LTkL99 z`W0(0iQLCo5_O>;c%)HlX%WA2-p*KU)f13vZFE;#SdGB{W7ulY%w>HbN*(d^dPhi! zy9T){JtAL%&m3tL8EIgo;Jo*-Wgc_e;~VIwdZX960Otgjr+oSOk@xxCNx*Xaw_$n$ zBf~mebwi$tZoi^BcgXy=RJA61&UcQ*0%WeiI5MOgEJEx{5)n1XedW7o-|l!C>AO4T z9@=`|RxsL4%YN${@DvfnemhT{`|S=tp1Y06%}4^`$Lx;i)rr2-kuzq`Q+J8vDq5cI z>?MrO1|2<)%ng|zk73?@>uM%r{h$8H0sdEHt*D^{$?pUFGmyJ-N2m_)PvGxx@2&$m z{PnkKjC=2zYijgl*L2#l@1bu)dEdV--u_r!y(JNDpUCS8S(3}QK8>^JsdNp#>GLPO zmw0cs({Sf@dt(f#XZa_bdi#2lCgjTEbPhMpm}kzexOlLe#w-gEIJ!L=e8w302XnUz AfB*mh literal 0 HcmV?d00001 diff --git a/src/CommonLib/targetver.h b/src/CommonLib/targetver.h new file mode 100644 index 0000000000000000000000000000000000000000..567cd346efccbe2d1f43a4056bdcb58a2b93e1a8 GIT binary patch literal 630 zcmaiyOKZYV5QWcL=zj>fEfw0WxN;+co0fK2Vs4B9U*sm0{`t1wOo&Foy0~*EXI^K{ z&F{}p2USW{Xp2p>*G`#oJ!s%(q!H-M(Ty4fmG}kNtEQTB%)V1m=}BwwfWPvrT#@e@ zH0NG}74Ao{glS)#QXA|NYdIfY7hrMp+Ji@H`t9kzWx_SD6;~Ri;cvXH)6CO{-I1p_IJPQ#X=r zigZeSqQguJz35q;zt9^Q_C^^>*mmuXUCp&pw^fPoGX+dho4RCrySs7j@9_UipWkA5 OQDt4mH~x-^Z~X_?9czaG literal 0 HcmV?d00001 diff --git a/src/AspNetCore/Src/path.cxx b/src/CommonLib/utility.cxx similarity index 71% rename from src/AspNetCore/Src/path.cxx rename to src/CommonLib/utility.cxx index a4ef464539..10876c104e 100644 --- a/src/AspNetCore/Src/path.cxx +++ b/src/CommonLib/utility.cxx @@ -1,11 +1,11 @@ // 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"stdafx.h" // static HRESULT -PATH::SplitUrl( +UTILITY::SplitUrl( PCWSTR pszDestinationUrl, BOOL *pfSecure, STRU *pstrDestination, @@ -95,7 +95,7 @@ Return Value: // static HRESULT -PATH::UnEscapeUrl( +UTILITY::UnEscapeUrl( PCWSTR pszUrl, DWORD cchUrl, bool fCopyQuery, @@ -151,7 +151,7 @@ PATH::UnEscapeUrl( // static HRESULT -PATH::UnEscapeUrl( +UTILITY::UnEscapeUrl( PCWSTR pszUrl, DWORD cchUrl, STRU * pstrResult @@ -231,7 +231,7 @@ PATH::UnEscapeUrl( } HRESULT -PATH::EscapeAbsPath( +UTILITY::EscapeAbsPath( IHttpRequest * pRequest, STRU * strEscapedUrl ) @@ -269,7 +269,7 @@ Finished: // static bool -PATH::IsValidAttributeNameChar( +UTILITY::IsValidAttributeNameChar( WCHAR ch ) { @@ -282,7 +282,7 @@ PATH::IsValidAttributeNameChar( // static bool -PATH::FindInMultiString( +UTILITY::FindInMultiString( PCWSTR pszMultiString, PCWSTR pszStringToFind ) @@ -301,7 +301,7 @@ PATH::FindInMultiString( // static bool -PATH::IsValidQueryStringName( +UTILITY::IsValidQueryStringName( PCWSTR pszName ) { @@ -322,7 +322,7 @@ PATH::IsValidQueryStringName( // static bool -PATH::IsValidHeaderName( +UTILITY::IsValidHeaderName( PCWSTR pszName ) { @@ -342,7 +342,7 @@ PATH::IsValidHeaderName( } HRESULT -PATH::IsPathUnc( +UTILITY::IsPathUnc( __in LPCWSTR pszPath, __out BOOL * pfIsUnc ) @@ -373,7 +373,7 @@ Finished: } HRESULT -PATH::ConvertPathToFullPath( +UTILITY::ConvertPathToFullPath( _In_ LPCWSTR pszPath, _In_ LPCWSTR pszRootPath, _Out_ STRU* pStruFullPath @@ -384,7 +384,7 @@ PATH::ConvertPathToFullPath( LPWSTR pszFullPath = NULL; // if relative path, prefix with root path and then convert to absolute path. - if( pszPath[0] == L'.' ) + if ( pszPath[0] == L'.' ) { hr = strFileFullPath.Copy(pszRootPath); if(FAILED(hr)) @@ -403,13 +403,13 @@ PATH::ConvertPathToFullPath( } hr = strFileFullPath.Append( pszPath ); - if(FAILED(hr)) + if (FAILED(hr)) { goto Finished; } pszFullPath = new WCHAR[ strFileFullPath.QueryCCH() + 1]; - if( pszFullPath == NULL ) + if ( pszFullPath == NULL ) { hr = E_OUTOFMEMORY; goto Finished; @@ -425,18 +425,185 @@ PATH::ConvertPathToFullPath( // convert to canonical path hr = MakePathCanonicalizationProof( pszFullPath, pStruFullPath ); - if(FAILED(hr)) + if (FAILED(hr)) { goto Finished; } Finished: - if( pszFullPath != NULL ) + 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/AspNetCore/Inc/path.h b/src/CommonLib/utility.h similarity index 69% rename from src/AspNetCore/Inc/path.h rename to src/CommonLib/utility.h index a553ccfe05..7ad0119445 100644 --- a/src/AspNetCore/Inc/path.h +++ b/src/CommonLib/utility.h @@ -3,7 +3,7 @@ #pragma once -class PATH +class UTILITY { public: @@ -79,17 +79,41 @@ public: _Out_ STRU* pStrFullPath ); -private: - - PATH() {} - ~PATH() {} + static + HRESULT + EnsureDirectoryPathExist( + _In_ LPCWSTR pszPath + ); static - CHAR - ToHexDigit( - UINT nDigit - ) - { - return static_cast(nDigit > 9 ? nDigit - 10 + 'A' : nDigit + '0'); - } + 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 index 4f9f36678a..05a4e90bf5 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -23,7 +23,7 @@ Win32Proj IISLib IISLib - 10.0.15063.0 + 8.1
    @@ -78,6 +78,8 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase + MultiThreadedDebug + false Windows @@ -94,6 +96,7 @@ true ProgramDatabase MultiThreadedDebug + false Windows @@ -111,6 +114,7 @@ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true MultiThreaded + false Windows @@ -130,6 +134,7 @@ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true MultiThreaded + false Windows diff --git a/src/IISLib/precomp.h b/src/IISLib/precomp.h index 0dc0cd7b52..9cccea4045 100644 --- a/src/IISLib/precomp.h +++ b/src/IISLib/precomp.h @@ -4,6 +4,9 @@ #include #include #pragma warning( disable:4127 ) +#include +#include +#include #include #include #include @@ -16,3 +19,4 @@ #include "ahutil.h" #include "acache.h" //#include "base64.hxx" + diff --git a/src/RequestHandler/RequestHandler.rc b/src/RequestHandler/RequestHandler.rc new file mode 100644 index 0000000000000000000000000000000000000000..6d285329154a138f9216537b866148988d06f675 GIT binary patch literal 2664 zcmdUxTTAOe5Xb+|g5M#wFN$KlJo%`t7CE&_FQQN=VvQDRJTxu7_}M+@HyamYlPF#u zBFlC*)0x?s|6G!P&o$K)=tN^(YpMsu>`*g!=kQ|9b)^YUb*-}k-RedWdkTLB9l@JI zO>fTWnsdODSsUvwGMie~61UVGt-_7?cY^fD$yPG@o4QlQNtyg zL&rMRP#qn@ZE$X@rAs(neou4&r^VUdZ$6Z7dG9<8)C8ABj6+a*&^__bK*wX?I-RZ>XYU#ks|yti8}>b(odZbbi0 z$W%-X1X?BM5l_AkyPT4)jJ1|i1#zmU#tyHQH@!8&;=Ycks-&m7^iQAes&n&@>T0pf z?h`90)a+rSLk$nnYNNmAZf(JB!|g+xu1@!~ftBqApNcbSb$*UVf6#9Hx}MJOR-ap= zwzol~Sn`0#o37tYHh5Y2^K+oBmlPO~3yZ_E?q+hqO25VR5}je2>_7V)mVz&HI09JUAu* literal 0 HcmV?d00001 diff --git a/src/RequestHandler/RequestHandler.vcxproj b/src/RequestHandler/RequestHandler.vcxproj new file mode 100644 index 0000000000..68424230df --- /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 0000000000000000000000000000000000000000..0241f12cce13bee9be7e89ef038c380c39e9cf45 GIT binary patch literal 4360 zcmdUyS#Q%o5Xa{kiSJ<27b;Mj9zyD4(~GKU5;ccFs#KL6B~nO34i1UW4*dSR*~E^W z0D*@f%U&R<)IO^{cDLN#T#A3k-dk?z?oV(H(a=R> zAL(JQv}`$<9j8M$j;&%xKq>MbunO)Jf zBfG#Fi~i_fpGW(^`NvpCcBpacH#{#n^$F*cJ5;ue%H9H|<(^cWH$Zd+_vwF^)7jY?e5+CS9u~>o8L^@r1x|nAa$Yl0b!FGvTgy*t zt~}vwCN5Qo^|WMF`kbX}N6v1kLAOMwulaMF%P<|Rjp zll_)U_%|84S@kE5MyJ;ur)}1IKo>^!Jn`Nl&^79A&>!6KJfmh+T|ZG`F2A2sYs4L2 z33PFuv#ZEzaz_NcOO0+rU({PcN0~gVQ&DRvzKZ+8x9Fjo)bTWT(07hj)zB&(&N0s` zQ0DE69T3k&cP3?NXZB50f9)`~-0E<8vTPj5*z@`W z#^jo}w)~!yB#Wyn88Yf$W6>`6U>dO2W><5He@IWW1O6UYmX(U^esI!s;sMr{pC8~F z&+MIEQI~Y$x|V(Xxo593WtXS26gR>fPODkmpX=aG=Jlc_e$Sg(_DIT2`o;Tf)sDI6 z=DorEEtyjdtT3F}WK}jLqo4+t@?wNV)tAQOSryCYn(qSToxH0ca}@J8MVM}1*;e)Y x=`Z$XeV^iJbRx~e__u$4>Y-Nu`2{=1QEb+`_p<(fvpTdsnXg)w{?AMM{U`Y~CA9zm literal 0 HcmV?d00001 diff --git a/src/RequestHandler/Source.cpp b/src/RequestHandler/Source.cpp new file mode 100644 index 0000000000..e69de29bb2 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/AspNetCore/Inc/aspnetcore_event.h b/src/RequestHandler/aspnetcore_event.h similarity index 100% rename from src/AspNetCore/Inc/aspnetcore_event.h rename to src/RequestHandler/aspnetcore_event.h 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/AspNetCore/Src/aspnetcore_msg.mc b/src/RequestHandler/aspnetcore_msg.mc similarity index 100% rename from src/AspNetCore/Src/aspnetcore_msg.mc rename to src/RequestHandler/aspnetcore_msg.mc 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/AspNetCore/Inc/inprocessapplication.h b/src/RequestHandler/inprocess/inprocessapplication.h similarity index 68% rename from src/AspNetCore/Inc/inprocessapplication.h rename to src/RequestHandler/inprocess/inprocessapplication.h index 88aeb1c2fd..faa2d374b2 100644 --- a/src/AspNetCore/Inc/inprocessapplication.h +++ b/src/RequestHandler/inprocess/inprocessapplication.h @@ -4,37 +4,21 @@ #pragma once typedef void(*request_handler_cb) (int error, IHttpContext* pHttpContext, void* pvCompletionContext); -typedef REQUEST_NOTIFICATION_STATUS(*PFN_REQUEST_HANDLER) (IHttpContext* pHttpContext, void* pvRequstHandlerContext); +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); - -#include "application.h" +typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); class IN_PROCESS_APPLICATION : public APPLICATION { public: - IN_PROCESS_APPLICATION(); + IN_PROCESS_APPLICATION(IHttpServer* pHttpServer, ASPNETCORE_CONFIG *pConfig); ~IN_PROCESS_APPLICATION(); __override - HRESULT - Initialize(_In_ APPLICATION_MANAGER* pApplicationManager, - _In_ ASPNETCORE_CONFIG* pConfiguration); - VOID - Recycle( - VOID - ); - - __override - VOID OnAppOfflineHandleChange(); - - __override - REQUEST_NOTIFICATION_STATUS - ExecuteRequest( - _In_ IHttpContext* pHttpContext - ); + ShutDown(); VOID SetCallbackHandles( @@ -45,6 +29,11 @@ public: _In_ VOID* pvShutdownHandlerContext ); + VOID + Recycle( + VOID + ); + // Executes the .NET Core process HRESULT ExecuteApplication( @@ -53,14 +42,31 @@ public: HRESULT LoadManagedApplication( - VOID - ); + VOID + ); REQUEST_NOTIFICATION_STATUS OnAsyncCompletion( - IHttpContext* pHttpContext, DWORD cbCompletion, - HRESULT hrCompletionStatus + 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 @@ -72,14 +78,13 @@ public: 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_RequstHandlerContext; + VOID* m_RequestHandlerContext; // The shutdown handler callback from managed code PFN_SHUTDOWN_HANDLER m_ShutdownHandler; @@ -90,6 +95,10 @@ private: // 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; @@ -97,9 +106,20 @@ private: 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( @@ -117,7 +137,7 @@ private: static BOOL DirectoryExists( - _In_ STRU *pstrPath //todo: this does not need to be stru, can be PCWSTR + _In_ STRU *pstrPath //todo: this does not need to be stru, can be PCWSTR ); static BOOL @@ -131,5 +151,4 @@ private: 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/AspNetCore/Src/managedexports.cxx b/src/RequestHandler/managedexports.cxx similarity index 68% rename from src/AspNetCore/Src/managedexports.cxx rename to src/RequestHandler/managedexports.cxx index d05df89705..464f31e2ee 100644 --- a/src/AspNetCore/Src/managedexports.cxx +++ b/src/RequestHandler/managedexports.cxx @@ -28,83 +28,64 @@ register_callbacks( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HTTP_REQUEST* http_get_raw_request( - _In_ IHttpContext* pHttpContext + _In_ IN_PROCESS_HANDLER* pInProcessHandler ) { - return pHttpContext->GetRequest()->GetRawHttpRequest(); + return pInProcessHandler->QueryHttpContext()->GetRequest()->GetRawHttpRequest(); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HTTP_RESPONSE* http_get_raw_response( - _In_ IHttpContext* pHttpContext + _In_ IN_PROCESS_HANDLER* pInProcessHandler ) { - return pHttpContext->GetResponse()->GetRawHttpResponse(); + return pInProcessHandler->QueryHttpContext()->GetResponse()->GetRawHttpResponse(); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ USHORT statusCode, _In_ PCSTR pszReason ) { - pHttpContext->GetResponse()->SetStatus(statusCode, pszReason); + pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(statusCode, pszReason); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_post_completion( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, DWORD cbBytes ) { - return pHttpContext->PostCompletion(cbBytes); + return pInProcessHandler->QueryHttpContext()->PostCompletion(cbBytes); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_set_completion_status( - _In_ IHttpContext* pHttpContext, - REQUEST_NOTIFICATION_STATUS requestNotificationStatus + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ REQUEST_NOTIFICATION_STATUS requestNotificationStatus ) { HRESULT hr = S_OK; - IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = NULL; - hr = IN_PROCESS_STORED_CONTEXT::GetInProcessStoredContext( - pHttpContext, - &pInProcessStoredContext - ); - - if (FAILED(hr)) - { - return hr; - } - pInProcessStoredContext->IndicateManagedRequestComplete(); - pInProcessStoredContext->SetAsyncCompletionStatus(requestNotificationStatus); + pInProcessHandler->IndicateManagedRequestComplete(); + pInProcessHandler->SetAsyncCompletionStatus(requestNotificationStatus); return hr; } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_set_managed_context( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ PVOID pvManagedContext ) { - HRESULT hr; - IN_PROCESS_STORED_CONTEXT* pInProcessStoredContext = new IN_PROCESS_STORED_CONTEXT(pHttpContext, pvManagedContext); - if (pInProcessStoredContext == NULL) - { - return E_OUTOFMEMORY; - } - - hr = IN_PROCESS_STORED_CONTEXT::SetInProcessStoredContext(pHttpContext, pInProcessStoredContext); - if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) - { - hr = S_OK; - } + // todo: should we consider changing the signature + HRESULT hr = S_OK; + pInProcessHandler->SetManangedHttpContext(pvManagedContext); return hr; } @@ -112,11 +93,11 @@ http_set_managed_context( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_indicate_completion( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ REQUEST_NOTIFICATION_STATUS notificationStatus ) { - pHttpContext->IndicateCompletion(notificationStatus); + pInProcessHandler->QueryHttpContext()->IndicateCompletion(notificationStatus); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT @@ -153,7 +134,7 @@ http_get_application_properties( { ASPNETCORE_CONFIG* pConfiguration = NULL; IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); - + if (pApplication == NULL) { return E_FAIL; @@ -161,7 +142,7 @@ http_get_application_properties( pConfiguration = pApplication->QueryConfig(); - pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationFullPath()->QueryStr()); + pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationPhysicalPath()->QueryStr()); pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr()); pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled(); pIISCofigurationData->fBasicAuthEnabled = pConfiguration->QueryBasicAuthEnabled(); @@ -173,7 +154,7 @@ http_get_application_properties( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_read_request_bytes( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _Out_ CHAR* pvBuffer, _In_ DWORD dwCbBuffer, _Out_ DWORD* pdwBytesReceived, @@ -182,7 +163,7 @@ http_read_request_bytes( { HRESULT hr; - if (pHttpContext == NULL) + if (pInProcessHandler == NULL) { return E_FAIL; } @@ -190,7 +171,7 @@ http_read_request_bytes( { return E_FAIL; } - IHttpRequest *pHttpRequest = (IHttpRequest*)pHttpContext->GetRequest(); + IHttpRequest *pHttpRequest = (IHttpRequest*)pInProcessHandler->QueryHttpContext()->GetRequest(); BOOL fAsync = TRUE; @@ -213,13 +194,13 @@ http_read_request_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_write_response_bytes( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ HTTP_DATA_CHUNK* pDataChunks, _In_ DWORD dwChunks, _In_ BOOL* pfCompletionExpected ) { - IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->GetResponse(); + IHttpResponse *pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse(); BOOL fAsync = TRUE; BOOL fMoreData = TRUE; DWORD dwBytesSent = 0; @@ -238,11 +219,11 @@ http_write_response_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_flush_response_bytes( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _Out_ BOOL* pfCompletionExpected ) { - IHttpResponse *pHttpResponse = (IHttpResponse*)pHttpContext->GetResponse(); + IHttpResponse *pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse(); BOOL fAsync = TRUE; BOOL fMoreData = TRUE; @@ -259,7 +240,7 @@ http_flush_response_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_websockets_read_bytes( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ CHAR* pvBuffer, _In_ DWORD cbBuffer, _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, @@ -268,7 +249,7 @@ http_websockets_read_bytes( _In_ BOOL* pfCompletionPending ) { - IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pHttpContext->GetRequest(); + IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pInProcessHandler->QueryHttpContext()->GetRequest(); BOOL fAsync = TRUE; @@ -293,7 +274,7 @@ http_websockets_read_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_websockets_write_bytes( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ HTTP_DATA_CHUNK* pDataChunks, _In_ DWORD dwChunks, _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, @@ -301,7 +282,7 @@ http_websockets_write_bytes( _In_ BOOL* pfCompletionExpected ) { - IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->GetResponse(); + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse(); BOOL fAsync = TRUE; BOOL fMoreData = TRUE; @@ -323,13 +304,13 @@ http_websockets_write_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_websockets_flush_bytes( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, _In_ VOID* pvCompletionContext, _In_ BOOL* pfCompletionExpected ) { - IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->GetResponse(); + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse(); BOOL fAsync = TRUE; BOOL fMoreData = TRUE; @@ -348,16 +329,16 @@ http_websockets_flush_bytes( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_enable_websockets( - _In_ IHttpContext* pHttpContext + _In_ IN_PROCESS_HANDLER* pInProcessHandler ) { - if (!g_fWebSocketSupported) - { - return E_FAIL; - } + //if (!g_fWebSocketSupported) + //{ + // return E_FAIL; + //} - ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); - ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); + ((IHttpContext3*)pInProcessHandler->QueryHttpContext())->EnableFullDuplex(); + ((IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse())->DisableBuffering(); return S_OK; } @@ -365,51 +346,50 @@ http_enable_websockets( EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_cancel_io( - _In_ IHttpContext* pHttpContext + _In_ IN_PROCESS_HANDLER* pInProcessHandler ) { - return pHttpContext->CancelIo(); + return pInProcessHandler->QueryHttpContext()->CancelIo(); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_response_set_unknown_header( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ PCSTR pszHeaderName, _In_ PCSTR pszHeaderValue, _In_ USHORT usHeaderValueLength, _In_ BOOL fReplace ) { - return pHttpContext->GetResponse()->SetHeader( pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace ); + return pInProcessHandler->QueryHttpContext()->GetResponse()->SetHeader(pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_response_set_known_header( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _In_ HTTP_HEADER_ID dwHeaderId, _In_ PCSTR pszHeaderValue, _In_ USHORT usHeaderValueLength, _In_ BOOL fReplace ) { - return pHttpContext->GetResponse()->SetHeader( dwHeaderId, pszHeaderValue, usHeaderValueLength, fReplace ); + return pInProcessHandler->QueryHttpContext()->GetResponse()->SetHeader(dwHeaderId, pszHeaderValue, usHeaderValueLength, fReplace); } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_get_authentication_information( - _In_ IHttpContext* pHttpContext, + _In_ IN_PROCESS_HANDLER* pInProcessHandler, _Out_ BSTR* pstrAuthType, _Out_ VOID** pvToken ) { - *pstrAuthType = SysAllocString(pHttpContext->GetUser()->GetAuthenticationType()); - *pvToken = pHttpContext->GetUser()->GetPrimaryToken(); + *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/AspNetCore/Src/forwarderconnection.cxx b/src/RequestHandler/outofprocess/forwarderconnection.cxx similarity index 93% rename from src/AspNetCore/Src/forwarderconnection.cxx rename to src/RequestHandler/outofprocess/forwarderconnection.cxx index 9dd853992d..99990f938c 100644 --- a/src/AspNetCore/Src/forwarderconnection.cxx +++ b/src/RequestHandler/outofprocess/forwarderconnection.cxx @@ -1,7 +1,7 @@ // 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 "..\precomp.hxx" FORWARDER_CONNECTION::FORWARDER_CONNECTION( VOID @@ -23,7 +23,7 @@ FORWARDER_CONNECTION::Initialize( goto Finished; } - m_hConnection = WinHttpConnect(FORWARDING_HANDLER::sm_hSession, + m_hConnection = WinHttpConnect(g_hWinhttpSession, L"127.0.0.1", (USHORT) dwPort, 0); diff --git a/src/AspNetCore/Inc/forwarderconnection.h b/src/RequestHandler/outofprocess/forwarderconnection.h similarity index 100% rename from src/AspNetCore/Inc/forwarderconnection.h rename to src/RequestHandler/outofprocess/forwarderconnection.h diff --git a/src/AspNetCore/Src/forwardinghandler.cxx b/src/RequestHandler/outofprocess/forwardinghandler.cpp similarity index 60% rename from src/AspNetCore/Src/forwardinghandler.cxx rename to src/RequestHandler/outofprocess/forwardinghandler.cpp index 6eee4af412..293c56bfd5 100644 --- a/src/AspNetCore/Src/forwardinghandler.cxx +++ b/src/RequestHandler/outofprocess/forwardinghandler.cpp @@ -1,85 +1,55 @@ -// 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 - +#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 STR_ANCM_CHILDREQUEST "ANCM_WasCreateProcessFailure" -HINTERNET FORWARDING_HANDLER::sm_hSession = NULL; -STRU FORWARDING_HANDLER::sm_strErrorFormat; -HANDLE FORWARDING_HANDLER::sm_hEventLog = NULL; +#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 APPLICATION * pApplication -) : m_Signature(FORWARDING_HANDLER_SIGNATURE), -m_cRefs(1), -m_pW3Context(pW3Context), -m_pApplication(pApplication), -m_pChildRequestContext(NULL), -m_hRequest(NULL), -m_fHandleClosedDueToClient(FALSE), -m_fResponseHeadersReceivedAndSet(FALSE), -m_fDoReverseRewriteHeaders(FALSE), -m_msStartTime(0), -m_BytesToReceive(0), -m_BytesToSend(0), -m_pEntityBuffer(NULL), -m_cchLastSend(0), -m_cEntityBuffers(0), -m_cBytesBuffered(0), -m_cMinBufferLimit(0), -m_pszOriginalHostHeader(NULL), -m_RequestStatus(FORWARDER_START), -m_pDisconnect(NULL), -m_pszHeaders(NULL), -m_cchHeaders(0), -m_fWebSocketEnabled(FALSE), -m_cContentLength(0), -m_pWebSocket(NULL), -m_pAppOfflineHtm(NULL), -m_fErrorHandled(FALSE), -m_fWebSocketUpgrade(FALSE), -m_fFinishRequest(FALSE), -m_fClientDisconnected(FALSE), -m_fHasError(FALSE) + _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) { -#ifdef DEBUG - DBGPRINTF((DBG_CONTEXT, - "FORWARDING_HANDLER::FORWARDING_HANDLER \n")); -#endif // DEBUG - - InitializeSRWLock(&m_RequestLock); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::FORWARDING_HANDLER"); } FORWARDING_HANDLER::~FORWARDING_HANDLER( - VOID ) { // // Destructor has started. // -#ifdef DEBUG - DBGPRINTF((DBG_CONTEXT, - "FORWARDING_HANDLER::~FORWARDING_HANDLER \n")); -#endif // DEBUG - 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. @@ -91,6 +61,12 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( // DBG_ASSERT(m_pDisconnect == NULL); + if (m_pDisconnect != NULL) + { + m_pDisconnect->ResetHandler(); + m_pDisconnect = NULL; + } + FreeResponseBuffers(); if (m_pWebSocket) @@ -98,542 +74,693 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( m_pWebSocket->Terminate(); m_pWebSocket = NULL; } +} - if (m_pChildRequestContext != 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()) { - m_pChildRequestContext->ReleaseClonedContext(); - m_pChildRequestContext = NULL; + 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; } // - // The m_pDisconnect must have happened by now the m_pServer - // is the only cleanup left. + // Begins normal request handling. Send request to server. // + m_RequestStatus = FORWARDER_SENDING_REQUEST; - RemoveRequest(); + // + // 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) { - // m_hRequest should have already been closed and set to NULL - // if not, we cannot close it as it may callback and cause AV - // let's do our best job here - WinHttpSetStatusCallback(m_hRequest, NULL, NULL, NULL); - WinHttpCloseHandle(m_hRequest); - 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; - if (m_pApplication != NULL) +Finished: + if (fLocked) { - m_pApplication->DereferenceApplication(); - m_pApplication = NULL; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); } - if (m_pAppOfflineHtm != NULL) - { - m_pAppOfflineHtm->DereferenceAppOfflineHtm(); - m_pAppOfflineHtm = NULL; - } - - m_pW3Context = NULL; + DereferenceRequestHandler(); + // + // No code after this point, as the handle might be gone + // + return retVal; } // static -void * FORWARDING_HANDLER::operator new(size_t) +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 + +--*/ { - DBG_ASSERT(sm_pAlloc != NULL); + HRESULT hr = S_OK; + + sm_pAlloc = new ALLOC_CACHE_HANDLER; if (sm_pAlloc == NULL) { - return NULL; + hr = E_OUTOFMEMORY; + goto Finished; } - return sm_pAlloc->Alloc(); + + 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::operator delete(void * pMemory) +//static +VOID +FORWARDING_HANDLER::StaticTerminate() { - DBG_ASSERT(sm_pAlloc != NULL); + 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) { - sm_pAlloc->Free(pMemory); + delete sm_pAlloc; + sm_pAlloc = NULL; } } -VOID -FORWARDING_HANDLER::ReferenceForwardingHandler( - VOID -) const -{ - LONG cRefs = InterlockedIncrement(&m_cRefs); - if (sm_pTraceLog != NULL) - { - WriteRefTraceLog(sm_pTraceLog, - cRefs, - this); - } -} - -VOID -FORWARDING_HANDLER::DereferenceForwardingHandler( - VOID -) const -{ - DBG_ASSERT(m_cRefs != 0); - - LONG cRefs = 0; - if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) - { - delete this; - } - - if (sm_pTraceLog != NULL) - { - WriteRefTraceLog(sm_pTraceLog, - cRefs, - this); - } -} - -HRESULT -FORWARDING_HANDLER::SetStatusAndHeaders( - PCSTR pszHeaders, - DWORD -) -{ - HRESULT hr; - IHttpResponse * pResponse = m_pW3Context->GetResponse(); - 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 = g_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; -} - HRESULT FORWARDING_HANDLER::GetHeaders( - const PROTOCOL_CONFIG * pProtocol, - PCWSTR * ppszHeaders, - DWORD * pcchHeaders, - ASPNETCORE_CONFIG* pAspNetCoreConfig, - SERVER_PROCESS* pServerProcess + _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; @@ -656,7 +783,7 @@ FORWARDING_HANDLER::GetHeaders( // if (!pProtocol->QueryPreserveHostHeader()) { - if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + if (FAILED(hr = UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, &fSecure, &struDestination, &struUrl)) || @@ -708,7 +835,7 @@ FORWARDING_HANDLER::GetHeaders( } } - if (pAspNetCoreConfig->QueryForwardWindowsAuthToken() && + if (fForwardWindowsAuthToken && (_wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"negotiate") == 0 || _wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"ntlm") == 0)) { @@ -901,12 +1028,12 @@ FORWARDING_HANDLER::GetHeaders( HRESULT FORWARDING_HANDLER::CreateWinHttpRequest( - __in const IHttpRequest * pRequest, - __in const PROTOCOL_CONFIG * pProtocol, - __in HINTERNET hConnect, - __inout STRU * pstrUrl, - ASPNETCORE_CONFIG* pAspNetCoreConfig, - SERVER_PROCESS* pServerProcess + _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; @@ -1011,10 +1138,10 @@ FORWARDING_HANDLER::CreateWinHttpRequest( } hr = GetHeaders(pProtocol, - &m_pszHeaders, - &m_cchHeaders, - pAspNetCoreConfig, - pServerProcess); + m_pApplication->QueryConfig()->QueryForwardWindowsAuthToken(), + pServerProcess, + &m_pszHeaders, + &m_cchHeaders); if (FAILED(hr)) { goto Finished; @@ -1025,1067 +1152,34 @@ Finished: return hr; } -REQUEST_NOTIFICATION_STATUS -FORWARDING_HANDLER::OnExecuteRequestHandler( - VOID -) -{ - REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; - HRESULT hr = S_OK; - bool fRequestLocked = FALSE; - ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; - FORWARDER_CONNECTION *pConnection = NULL; - STACK_STRU(strDestination, 32); - STACK_STRU(strUrl, 2048); - STACK_STRU(struEscapedUrl, 2048); - STACK_STRU(strDescription, 128); - HINTERNET hConnect = NULL; - IHttpRequest *pRequest = m_pW3Context->GetRequest(); - IHttpResponse *pResponse = m_pW3Context->GetResponse(); - PROTOCOL_CONFIG *pProtocol = &sm_ProtocolConfig; - SERVER_PROCESS *pServerProcess = NULL; - USHORT cchHostName = 0; - BOOL fSecure = FALSE; - BOOL fProcessStartFailure = FALSE; - BOOL fInternalError = FALSE; - HTTP_DATA_CHUNK *pDataChunk = NULL; - - DBG_ASSERT(m_RequestStatus == FORWARDER_START); - DBG_ASSERT(m_pApplication); - - // - // Take a reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - - // get application configuation - pAspNetCoreConfig = m_pApplication->QueryConfig(); - - - // check connection - IHttpConnection * pClientConnection = m_pW3Context->GetConnection(); - if (pClientConnection == NULL || - !pClientConnection->IsConnected()) - { - hr = HRESULT_FROM_WIN32(WSAECONNRESET); - goto Failure; - } - - // check offline - m_pAppOfflineHtm = m_pApplication->QueryAppOfflineHtm(); - if (m_pAppOfflineHtm != NULL) - { - m_pAppOfflineHtm->ReferenceAppOfflineHtm(); - } - - if (m_pApplication->AppOfflineFound() && m_pAppOfflineHtm != NULL) - { - HTTP_DATA_CHUNK DataChunk; - PCSTR pszANCMHeader; - DWORD cchANCMHeader; - BOOL fCompletionExpected = FALSE; - - if (FAILED(m_pW3Context->GetServerVariable(STR_ANCM_CHILDREQUEST, - &pszANCMHeader, - &cchANCMHeader))) // first time failure - { - if (SUCCEEDED(hr = m_pW3Context->CloneContext( - CLONE_FLAG_BASICS | CLONE_FLAG_HEADERS | CLONE_FLAG_ENTITY, - &m_pChildRequestContext - )) && - SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( - STR_ANCM_CHILDREQUEST, - L"1")) && - SUCCEEDED(hr = m_pW3Context->ExecuteRequest( - TRUE, // fAsync - m_pChildRequestContext, - EXECUTE_FLAG_DISABLE_CUSTOM_ERROR, // by pass Custom Error module - NULL, // pHttpUser - &fCompletionExpected))) - { - if (!fCompletionExpected) - { - retVal = RQ_NOTIFICATION_CONTINUE; - } - else - { - retVal = RQ_NOTIFICATION_PENDING; - } - goto Finished; - } - // - // fail to create child request, fall back to default 502 error - // - } - - DataChunk.DataChunkType = HttpDataChunkFromMemory; - DataChunk.FromMemory.pBuffer = (PVOID)m_pAppOfflineHtm->m_Contents.QueryStr(); - DataChunk.FromMemory.BufferLength = m_pAppOfflineHtm->m_Contents.QueryCB(); - - if (FAILED(hr = pResponse->WriteEntityChunkByReference(&DataChunk))) - { - goto Finished; - } - pResponse->SetStatus(503, "Service Unavailable", 0, hr); - pResponse->SetHeader("Content-Type", - "text/html", - (USHORT)strlen("text/html"), - FALSE - ); // no need to check return hresult - - m_fHasError = TRUE; - goto Finished; - } - - switch (pAspNetCoreConfig->QueryHostingModel()) - { - case HOSTING_IN_PROCESS: - { - - hr = ((IN_PROCESS_APPLICATION*)m_pApplication)->LoadManagedApplication(); - if (FAILED(hr)) - { - _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; - goto Failure; - } - - // 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 m_pApplication->ExecuteRequest(m_pW3Context); - } - case HOSTING_OUT_PROCESS: - { - m_pszOriginalHostHeader = pRequest->GetHeader(HttpHeaderHost, &cchHostName); - - // override Protocol related config from aspNetCore config - pProtocol->OverrideConfig(pAspNetCoreConfig); - - // - // parse original url - // - if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, - &fSecure, - &strDestination, - &strUrl))) - { - goto Failure; - } - - if (FAILED(hr = PATH::EscapeAbsPath(pRequest, &struEscapedUrl))) - { - goto Failure; - } - - m_fDoReverseRewriteHeaders = pProtocol->QueryReverseRewriteHeaders(); - m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); - hr = ((OUT_OF_PROCESS_APPLICATION*)m_pApplication)->GetProcess( - m_pW3Context, - &pServerProcess); - if (FAILED(hr)) - { - fProcessStartFailure = TRUE; - goto Failure; - } - - if (pServerProcess == NULL) - { - 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(); - - // - // 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, - pAspNetCoreConfig, - pServerProcess); - - if (FAILED(hr)) - { - goto Failure; - } - - AcquireSRWLockShared(&m_RequestLock); - fRequestLocked = TRUE; - - 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; - - // - // 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); - - // - // WinHttpSendRequest can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - - //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"); - if (ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) - { - ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::RaiseEvent( - m_pW3Context->GetTraceContext(), - NULL, - hr); - } - - DereferenceForwardingHandler(); - 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; - - } - - default: - hr = E_UNEXPECTED; - fInternalError = TRUE; - goto Failure; - } - -Failure: - // - // Reset status for consistency. - // - m_RequestStatus = FORWARDER_DONE; - m_fHasError = TRUE; - - pResponse->DisableKernelCache(); - pResponse->GetRawHttpResponse()->EntityChunkCount = 0; - // - // Finish the request on failure. - // - retVal = RQ_NOTIFICATION_FINISH_REQUEST; - - if (hr == HRESULT_FROM_WIN32(WSAECONNRESET)) - { - pResponse->SetStatus(400, "Bad Request", 0, hr); - goto Finished; - } - else if (fInternalError) - { - // set a special sub error code indicating loading application error - pResponse->SetStatus(500, "Internal Server Error", 19, hr); - goto Finished; - } - else if (fProcessStartFailure && !pAspNetCoreConfig->QueryDisableStartUpErrorPage()) - { - PCSTR pszANCMHeader; - DWORD cchANCMHeader; - BOOL fCompletionExpected = FALSE; - - if (FAILED(m_pW3Context->GetServerVariable(STR_ANCM_CHILDREQUEST, - &pszANCMHeader, - &cchANCMHeader))) // first time failure - { - if (SUCCEEDED(hr = m_pW3Context->CloneContext( - CLONE_FLAG_BASICS | - CLONE_FLAG_HEADERS | - CLONE_FLAG_ENTITY | - CLONE_FLAG_SERVER_VARIABLE, - &m_pChildRequestContext)) && - SUCCEEDED(hr = m_pChildRequestContext->SetServerVariable( - STR_ANCM_CHILDREQUEST, - L"1")) && - SUCCEEDED(hr = m_pW3Context->ExecuteRequest( - TRUE, // fAsync - m_pChildRequestContext, - EXECUTE_FLAG_DISABLE_CUSTOM_ERROR, // by pass Custom Error module - NULL, // pHttpUser - &fCompletionExpected))) - { - if (!fCompletionExpected) - { - m_pW3Context->SetRequestHandled(); - retVal = RQ_NOTIFICATION_CONTINUE; - } - else - { - retVal = RQ_NOTIFICATION_PENDING; - } - goto Finished; - } - // - // fail to create child request, fall back to default 502 error - // - } - else - { - if (SUCCEEDED(APPLICATION_MANAGER::GetInstance()->Get502ErrorPage(&pDataChunk))) - { - if (FAILED(hr = pResponse->WriteEntityChunkByReference(pDataChunk))) - { - goto Finished; - } - pResponse->SetStatus(502, "Bad Gateway", 5, hr); - pResponse->SetHeader("Content-Type", - "text/html", - (USHORT)strlen("text/html"), - FALSE - ); - goto Finished; - } - } - } - // - // default error behavior - // - 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); - } - -Finished: - if (pConnection != NULL) - { - pConnection->DereferenceForwarderConnection(); - pConnection = NULL; - } - - if (pServerProcess != NULL) - { - pServerProcess->DereferenceServerProcess(); - pServerProcess = NULL; - } - - if (fRequestLocked) - { - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); - TlsSetValue(g_dwTlsIndex, NULL); - ReleaseSRWLockShared(&m_RequestLock); - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); - } - - if (retVal != RQ_NOTIFICATION_PENDING) - { - // - // Remove request so that load-balancing algorithms like minCurrentRequests/minAverageResponseTime - // get the correct time when we received the last byte of response, rather than when we received - // ack from client about last byte of response - which could be much later. - // - RemoveRequest(); - } - - DereferenceForwardingHandler(); - // - // Do not use this object after dereferencing it, it may be gone. - // - return retVal; -} - VOID -FORWARDING_HANDLER::RemoveRequest() -{ - if (m_pDisconnect != NULL) - { - m_pDisconnect->ResetHandler(); - m_pDisconnect = NULL; - } -} - -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_CONTINUE; - BOOL fLocked = FALSE; - bool fClientError = FALSE; - BOOL fClosed = FALSE; - - 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 (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_IN_PROCESS) - { - 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(m_pW3Context, cbCompletion, hrCompletionStatus); - } - } - else if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_OUT_PROCESS) - { - // - // Take a reference so that object does not go away as a result of - // async completion. - // - // Read lock on the WinHTTP handle to protect from server closing - // the handle while it is in use. - // - ReferenceForwardingHandler(); - - DBG_ASSERT(m_pW3Context != NULL); - __analysis_assume(m_pW3Context != NULL); - - // - // OnAsyncCompletion can be called on a Winhttp io completion thread. - // Hence we need to check the TLS before we acquire the shared lock. - // - - 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) - { - if (m_RequestStatus == FORWARDER_DONE && m_fFinishRequest) - { - if (m_fHasError) - { - retVal = RQ_NOTIFICATION_FINISH_REQUEST; - } - goto Finished; - } - - fClientError = m_fHandleClosedDueToClient; - goto Failure; - } - else if (m_RequestStatus == 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 - // - WinHttpSetStatusCallback(m_hRequest, - FORWARDING_HANDLER::OnWinHttpCompletion, - WINHTTP_CALLBACK_FLAG_HANDLES, - NULL); - fClosed = WinHttpCloseHandle(m_hRequest); - DBG_ASSERT(fClosed); - if (fClosed) - { - m_fWebSocketUpgrade = TRUE; - m_hRequest = NULL; - } - else - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; - } - retVal = RQ_NOTIFICATION_PENDING; - goto Finished; - } - else if (m_RequestStatus == FORWARDER_RESET_CONNECTION) - { - hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); - 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_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: - - // - // Reset status for consistency. - // - m_RequestStatus = FORWARDER_DONE; - m_fHasError = TRUE; - // - // Do the right thing based on where the error originated from. - // - IHttpResponse *pResponse = m_pW3Context->GetResponse(); - pResponse->DisableKernelCache(); - pResponse->GetRawHttpResponse()->EntityChunkCount = 0; - - // double check to set right status code - if (!m_pW3Context->GetConnection()->IsConnected()) - { - fClientError = TRUE; - } - - 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); - } - - if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) - { - pResponse->ResetConnection(); - goto Finished; - } - } - - // - // 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; - } - } - 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); - } - - if (retVal != RQ_NOTIFICATION_PENDING) - { - // - // Remove request so that load-balancing algorithms like minCurrentRequests/minAverageResponseTime - // get the correct time when we received the last byte of response, rather than when we received - // ack from client about last byte of response - which could be much later. - // - RemoveRequest(); - } - - DereferenceForwardingHandler(); - - // - // Do not use this object after dereferencing it, it may be gone. - // - - return retVal; -} - -HRESULT -FORWARDING_HANDLER::OnSendingRequest( - DWORD cbCompletion, - HRESULT hrCompletionStatus, - __out bool * pfClientError +FORWARDING_HANDLER::OnWinHttpCompletion( + HINTERNET hRequest, + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength ) { - 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)) + FORWARDING_HANDLER * pThis = static_cast(reinterpret_cast(dwContext)); + if (pThis == NULL) { - DBG_ASSERT(m_BytesToReceive == 0 || m_BytesToReceive == INFINITE); - if (m_BytesToReceive == INFINITE) - { - m_BytesToReceive = 0; - m_cchLastSend = 5; // "0\r\n\r\n" - - // - // 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 Failure; - } - } - else - { - m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; - - // - // WinHttpReceiveResponse can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - if (!WinHttpReceiveResponse(m_hRequest, NULL)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); - goto Failure; - } - } + //error happened, nothing can be done here + return; } - 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; - - // - // WinHttpWriteData can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - if (!WinHttpWriteData(m_hRequest, - m_pEntityBuffer + cbOffset, - cbCompletion, - NULL)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); - 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. - // - // WinHttpQueryDataAvailable can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - if (!WinHttpQueryDataAvailable(m_hRequest, NULL)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); - 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; - } - } - - // - // WinHttpReadData can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); - if (!WinHttpReadData(m_hRequest, - m_pEntityBuffer, - min(m_BytesToSend, BUFFER_SIZE), - NULL)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); - goto Failure; - } - } - -Failure: - - return hr; + DBG_ASSERT(pThis->m_Signature == FORWARDING_HANDLER_SIGNATURE); + pThis->OnWinHttpCompletionInternal(hRequest, + dwInternetStatus, + lpvStatusInformation, + dwStatusInformationLength); } VOID FORWARDING_HANDLER::OnWinHttpCompletionInternal( - HINTERNET hRequest, - DWORD dwInternetStatus, - LPVOID lpvStatusInformation, - DWORD dwStatusInformationLength + _In_ HINTERNET hRequest, + _In_ DWORD dwInternetStatus, + _In_ LPVOID lpvStatusInformation, + _In_ DWORD dwStatusInformationLength ) /*++ @@ -2107,14 +1201,18 @@ None --*/ { HRESULT hr = S_OK; - bool fIsCompletionThread = FALSE; + 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(); - BOOL fDerefForwardingHandler = TRUE; - BOOL fEndRequest = FALSE; + + // Reference the request handler to prevent it from being released prematurely + ReferenceRequestHandler(); UNREFERENCED_PARAMETER(dwStatusInformationLength); @@ -2136,6 +1234,7 @@ None NULL, dwInternetStatus); } + // // ReadLock on the winhttp handle to protect from a client disconnect/ // server stop closing the handle while we are using it. @@ -2144,10 +1243,6 @@ None // we have to account for that and not try to take the lock again, // otherwise, we could end up in a deadlock. // - // Take a reference so that object does not go away as a result of - // async completion - release one reference when async operation is - // initiated, two references if async operation is not initiated - // if (TlsGetValue(g_dwTlsIndex) != this) { @@ -2155,51 +1250,37 @@ None AcquireSRWLockShared(&m_RequestLock); TlsSetValue(g_dwTlsIndex, this); - fIsCompletionThread = TRUE; + fLockAcquired = TRUE; DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); } #ifdef DEBUG - DBGPRINTF((DBG_CONTEXT, - "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x -- %d --%p\n", dwInternetStatus, m_fWebSocketUpgrade, m_pW3Context)); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x -- %d --%p\n", dwInternetStatus, GetCurrentThreadId(), m_pW3Context); #endif // DEBUG - fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); if (!fEndRequest) { if (!m_pW3Context->GetConnection()->IsConnected()) { - m_fClientDisconnected = TRUE; - if (m_hRequest != NULL) - { - WinHttpSetStatusCallback(m_hRequest, - FORWARDING_HANDLER::OnWinHttpCompletion, - WINHTTP_CALLBACK_FLAG_HANDLES, - NULL); - if (WinHttpCloseHandle(m_hRequest)) - { - m_hRequest = NULL; - } - else - { - // if WinHttpCloseHandle failed, we are already in failure path - // nothing can can be done here - } - } hr = ERROR_CONNECTION_ABORTED; fClientError = m_fHandleClosedDueToClient = TRUE; - fDerefForwardingHandler = FALSE; - // wait for HANDLE_CLOSING callback to clean up - fAnotherCompletionExpected = !fEndRequest; + 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) { - fDerefForwardingHandler = FALSE; fAnotherCompletionExpected = TRUE; - if (m_pWebSocket == NULL) { goto Finished; @@ -2232,8 +1313,6 @@ None goto Finished; } - - switch (dwInternetStatus) { case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: @@ -2270,7 +1349,6 @@ None // This is a notification, not a completion. This notifiation happens // during the Send Request operation. // - fDerefForwardingHandler = FALSE; fAnotherCompletionExpected = TRUE; break; @@ -2280,7 +1358,6 @@ None // for WINHTTP_CALLBACK_STATUS_SENDING_REQUEST (which we actually need). // hr = S_OK; - fDerefForwardingHandler = FALSE; fAnotherCompletionExpected = TRUE; break; @@ -2291,18 +1368,15 @@ None m_pW3Context->GetTraceContext(), NULL); } - if (m_RequestStatus != FORWARDER_DONE) + if (m_RequestStatus != FORWARDER_DONE || m_fHandleClosedDueToClient) { hr = ERROR_CONNECTION_ABORTED; fClientError = m_fHandleClosedDueToClient; } - else - { - fDerefForwardingHandler = m_fClientDisconnected && !m_fWebSocketUpgrade; - } m_hRequest = NULL; fAnotherCompletionExpected = FALSE; break; + case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: hr = ERROR_CONNECTION_ABORTED; break; @@ -2339,43 +1413,41 @@ None goto Finished; Failure: + m_RequestStatus = FORWARDER_DONE; + m_fHasError = TRUE; - if (!m_fErrorHandled) + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) { - m_fErrorHandled = TRUE; - if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) - { - m_RequestStatus = FORWARDER_RESET_CONNECTION; - goto Finished; - } + m_fResetConnection = TRUE; + } - pResponse->DisableKernelCache(); - pResponse->GetRawHttpResponse()->EntityChunkCount = 0; - fClientError = !m_pW3Context->GetConnection()->IsConnected(); - if (fClientError) + if (fClientError || m_fHandleClosedDueToClient) + { + if (!m_fResponseHeadersReceivedAndSet) { - 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. - // - } + pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); } else { - STACK_STRU(strDescription, 128); + // + // 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)) || + 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, @@ -2399,9 +1471,9 @@ Failure: strDescription.QueryStr(), strDescription.QueryCCH(), FALSE); - } - } + }*/ } + //} // FREB log if (ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) @@ -2414,7 +1486,7 @@ Failure: Finished: - if (fIsCompletionThread) + if (fLockAcquired) { DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); TlsSetValue(g_dwTlsIndex, NULL); @@ -2422,22 +1494,15 @@ Finished: DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); } - if (fDerefForwardingHandler) - { - DereferenceForwardingHandler(); - } - // - // Do not use this object after dereferencing it, it may be gone. - // - - // - // 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. - // - 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, @@ -2448,6 +1513,13 @@ Finished: { m_hRequest = NULL; } + else + { + // unexpected WinHttp error, log it + /*DebugBreak(); + m_RequestStatus = FORWARDER_FINISH_REQUEST; + fDoPostCompletion = TRUE;*/ + } } // @@ -2465,22 +1537,27 @@ Finished: // in case of websocket, m_hRequest has already been closed after upgrade // websocket will handle completion m_fFinishRequest = TRUE; - m_pW3Context->PostCompletion(0); - + 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); - - // - // No code executed after posting the completion. - // } } @@ -2549,14 +1626,14 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( // Take reference so that object does not go away as a result of // async completion. // - ReferenceForwardingHandler(); + //ReferenceForwardingHandler(); if (!WinHttpWriteData(m_hRequest, "0\r\n\r\n", 5, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); + //DereferenceForwardingHandler(); goto Finished; } *pfAnotherCompletionExpected = TRUE; @@ -2582,17 +1659,9 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; - // - // WinHttpReceiveResponse can operate asynchronously. - // - // Take reference so that object does not go away as a result of - // async completion. - // - ReferenceForwardingHandler(); if (!WinHttpReceiveResponse(hRequest, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); goto Finished; } *pfAnotherCompletionExpected = TRUE; @@ -2719,7 +1788,7 @@ HRESULT FORWARDING_HANDLER::OnWinHttpCompletionStatusDataAvailable( HINTERNET hRequest, DWORD dwBytes, - __out bool * pfAnotherCompletionExpected + _Out_ bool * pfAnotherCompletionExpected ) { HRESULT hr = S_OK; @@ -2760,14 +1829,14 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusDataAvailable( // Take reference so that object does not go away as a result of // async completion. // - ReferenceForwardingHandler(); + //ReferenceForwardingHandler(); if (!WinHttpReadData(hRequest, m_pEntityBuffer, min(m_BytesToSend, BUFFER_SIZE), NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceForwardingHandler(); + //DereferenceForwardingHandler(); goto Finished; } *pfAnotherCompletionExpected = TRUE; @@ -2862,331 +1931,190 @@ Finished: return hr; } -// static HRESULT -FORWARDING_HANDLER::StaticInitialize( - BOOL fEnableReferenceCountTracing +FORWARDING_HANDLER::OnSendingRequest( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + __out bool * pfClientError ) -/*++ - -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 Failure; - } - - hr = sm_pAlloc->Initialize(sizeof(FORWARDING_HANDLER), - 64); // nThreshold - if (FAILED(hr)) - { - goto Failure; - } - + HRESULT hr = S_OK; // - // Open the session handle, specify random user-agent that will be - // overwritten by the client + // 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 // - sm_hSession = WinHttpOpen(L"", - WINHTTP_ACCESS_TYPE_NO_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - WINHTTP_FLAG_ASYNC); - if (sm_hSession == NULL) + if (hrCompletionStatus == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; + 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; + } + } } - - // - // 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(sm_hSession, - FORWARDING_HANDLER::OnWinHttpCompletion, - (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | - WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), - NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + else if (SUCCEEDED(hrCompletionStatus)) { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; - } + DWORD cbOffset; - // - // Make sure we see the redirects (rather than winhttp doing it - // automatically) - // - DWORD dwRedirectOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; - if (!WinHttpSetOption(sm_hSession, - WINHTTP_OPTION_REDIRECT_POLICY, - &dwRedirectOption, - sizeof(dwRedirectOption))) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; - } + 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'; - // Initialize Application Manager - APPLICATION_MANAGER *pApplicationManager = APPLICATION_MANAGER::GetInstance(); - if (pApplicationManager == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } + m_pEntityBuffer[cbCompletion + 6] = '\r'; + m_pEntityBuffer[cbCompletion + 7] = '\n'; - hr = pApplicationManager->Initialize(); - if (FAILED(hr)) - { - goto Failure; - } + 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); - // Initialize PROTOCOL_CONFIG - sm_ProtocolConfig.Initialize(); + 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 (FAILED(hr = sm_strErrorFormat.Resize(256))) - { - goto Failure; - } - - if (LoadString(g_hModule, - IDS_INVALID_PROPERTY, - sm_strErrorFormat.QueryStr(), - sm_strErrorFormat.QuerySizeCCH()) == 0) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; - } - sm_strErrorFormat.SyncWithBuffer(); - - // If RegisterEventSource failed, we cannot do any thing about it - // No need to check whether the returned handle is valid - - if (g_pHttpServer->IsCommandLineLaunch()) - { - sm_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); + if (!WinHttpWriteData(m_hRequest, + m_pEntityBuffer + cbOffset, + cbCompletion, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } } else { - sm_hEventLog = RegisterEventSource(NULL, ASPNETCORE_EVENT_PROVIDER); - } - - g_dwTlsIndex = TlsAlloc(); - if (g_dwTlsIndex == TLS_OUT_OF_INDEXES) - { - hr = HRESULT_FROM_WIN32(GetLastError()); + hr = hrCompletionStatus; + *pfClientError = TRUE; goto Failure; } - if (fEnableReferenceCountTracing) - { - sm_pTraceLog = CreateRefTraceLog(10000, 0); - } - - return S_OK; - Failure: - StaticTerminate(); - return hr; } -// static -VOID -FORWARDING_HANDLER::StaticTerminate( - VOID +HRESULT +FORWARDING_HANDLER::OnReceivingResponse( ) -/*++ - -Routine Description: - -Global termination routine for FORWARDING_HANDLERs - -Arguments: - -None - -Return Value: - -None - ---*/ { - // - // Delete all the statics - // + HRESULT hr = S_OK; - APPLICATION_MANAGER::Cleanup(); - - // - // wait for all server processes to go away - // for a max of 10 seconds. - // - - DWORD tickCount = GetTickCount(); - - while (g_dwActiveServerProcesses > 0) + if (m_cBytesBuffered >= m_cMinBufferLimit) { - if ((GetTickCount() - tickCount) > 10000) + 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) { - break; + // + // Disable buffering. + // + m_BytesToSend = 0; } - Sleep(250); } - if (sm_hSession != NULL) - { - WinHttpCloseHandle(sm_hSession); - sm_hSession = NULL; - } - - if (sm_hEventLog != NULL) - { - DeregisterEventSource(sm_hEventLog); - sm_hEventLog = NULL; - } - - if (g_dwTlsIndex != TLS_OUT_OF_INDEXES) - { - DBG_REQUIRE(TlsFree(g_dwTlsIndex)); - g_dwTlsIndex = TLS_OUT_OF_INDEXES; - } - - sm_strErrorFormat.Reset(); - - if (sm_pTraceLog != NULL) - { - DestroyRefTraceLog(sm_pTraceLog); - sm_pTraceLog = NULL; - } - - if (sm_pAlloc != NULL) - { - delete sm_pAlloc; - sm_pAlloc = NULL; - } -} - -VOID -CopyMultiSzToOutput( - IGlobalRSCAQueryProvider * pProvider, - PCWSTR pszList, - DWORD * pcbData -) -{ - PBYTE pvData; - DWORD cbData = 0; - PCWSTR pszListCopy = pszList; - while (*pszList != L'\0') - { - cbData += (static_cast(wcslen(pszList)) + 1) * sizeof(WCHAR); - pszList += wcslen(pszList) + 1; - } - cbData += sizeof(WCHAR); - if (FAILED(pProvider->GetOutputBuffer(cbData, - &pvData))) - { - return; - } - memcpy(pvData, - pszListCopy, - cbData); - *pcbData = cbData; -} - -struct AFFINITY_LOOKUP_CONTEXT -{ - DWORD timeout; - PCWSTR pszServer; - BUFFER * pHostNames; - DWORD cbData; -}; - -struct CACHE_CONTEXT -{ - PCSTR pszHostName; - IGlobalRSCAQueryProvider *pProvider; - __field_bcount_part(cbBuffer, cbData) - PBYTE pvData; - DWORD cbData; - DWORD cbBuffer; -}; - -VOID -FORWARDING_HANDLER::TerminateRequest( - bool fClientInitiated -) -{ - if (m_pApplication->QueryConfig()->QueryHostingModel() == HOSTING_IN_PROCESS) + if (m_BytesToSend == 0) { // - // Todo: need inform managed layer to abort the request + // No buffering enabled. // + if (!WinHttpQueryDataAvailable(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } } else { - bool fAcquiredLock = FALSE; - - if (TlsGetValue(g_dwTlsIndex) != this) + // + // Buffering enabled. + // + if (m_pEntityBuffer == NULL) { - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); - AcquireSRWLockShared(&m_RequestLock); - TlsSetValue(g_dwTlsIndex, this); - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); - fAcquiredLock = TRUE; - } - - if (m_hRequest != NULL) - { - WinHttpSetStatusCallback(m_hRequest, - FORWARDING_HANDLER::OnWinHttpCompletion, - WINHTTP_CALLBACK_FLAG_HANDLES, - NULL); - if (WinHttpCloseHandle(m_hRequest)) + m_pEntityBuffer = GetNewResponseBuffer(min(m_BytesToSend, BUFFER_SIZE)); + if (m_pEntityBuffer == NULL) { - m_hRequest = NULL; + hr = E_OUTOFMEMORY; + goto Failure; } - m_fHandleClosedDueToClient = fClientInitiated; } - // - // If the request is a websocket request, initiate cleanup. - // - - if (m_pWebSocket != NULL) + if (!WinHttpReadData(m_hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) { - m_pWebSocket->TerminateRequest(); - } - - if (fAcquiredLock) - { - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); - TlsSetValue(g_dwTlsIndex, NULL); - ReleaseSRWLockShared(&m_RequestLock); - DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; } } + +Failure: + return hr; } BYTE * @@ -3232,43 +2160,468 @@ FORWARDING_HANDLER::FreeResponseBuffers() } HRESULT -FORWARDING_HANDLER::SetHttpSysDisconnectCallback() +FORWARDING_HANDLER::SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD +) { - HRESULT hr = S_OK; - IHttpConnection * pClientConnection = m_pW3Context->GetConnection(); + HRESULT hr; + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + IHttpRequest * pRequest = m_pW3Context->GetRequest(); + STACK_STRA(strHeaderName, 128); + STACK_STRA(strHeaderValue, 2048); + DWORD index = 0; + PSTR pchNewline; + PCSTR pchEndofHeaderValue; + BOOL fServerHeaderPresent = FALSE; - if (g_fAsyncDisconnectAvailable) + _ASSERT(pszHeaders != NULL); + + // + // The first line is the status line + // + PSTR pchStatus = const_cast(strchr(pszHeaders, ' ')); + if (pchStatus == NULL) { - m_pDisconnect = static_cast( - pClientConnection->GetModuleContextContainer()-> - GetConnectionModuleContext(g_pModuleId)); - if (m_pDisconnect == NULL) - { - m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT; - if (m_pDisconnect == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + USHORT uStatus = static_cast(atoi(pchStatus)); - hr = pClientConnection->GetModuleContextContainer()-> - SetConnectionModuleContext(m_pDisconnect, - g_pModuleId); - DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); - if (FAILED(hr)) - { - goto Finished; - } + 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--) + { } // - // Issue: There is a window of opportunity to miss on the disconnect - // notification if it happens before the SetHandler() call is made. - // It is suboptimal for performance, but should functionally be OK. + // Copy the status description // - m_pDisconnect->SetHandler(this); - - Finished: - return hr; + 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/AspNetCore/Inc/processmanager.h b/src/RequestHandler/outofprocess/processmanager.h similarity index 97% rename from src/AspNetCore/Inc/processmanager.h rename to src/RequestHandler/outofprocess/processmanager.h index 1cc8a6ab54..9523e8a819 100644 --- a/src/AspNetCore/Inc/processmanager.h +++ b/src/RequestHandler/outofprocess/processmanager.h @@ -4,6 +4,7 @@ #pragma once #define ONE_MINUTE_IN_MILLISECONDS 60000 +class SERVER_PROCESS; class PROCESS_MANAGER { @@ -29,7 +30,6 @@ public: HRESULT GetProcess( - _In_ IHttpContext *context, _In_ ASPNETCORE_CONFIG *pConfig, _Out_ SERVER_PROCESS **ppServerProcess ); @@ -104,6 +104,8 @@ public: m_fServerProcessListReady(FALSE), m_cRefs( 1 ) { + m_ppServerProcessList = NULL; + m_fServerProcessListReady = FALSE; InitializeSRWLock( &m_srwLock ); } diff --git a/src/AspNetCore/Src/protocolconfig.cxx b/src/RequestHandler/outofprocess/protocolconfig.cxx similarity index 97% rename from src/AspNetCore/Src/protocolconfig.cxx rename to src/RequestHandler/outofprocess/protocolconfig.cxx index 85fd86aa61..9faebab82a 100644 --- a/src/AspNetCore/Src/protocolconfig.cxx +++ b/src/RequestHandler/outofprocess/protocolconfig.cxx @@ -1,7 +1,7 @@ // 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 "..\precomp.hxx" HRESULT PROTOCOL_CONFIG::Initialize() diff --git a/src/AspNetCore/Inc/protocolconfig.h b/src/RequestHandler/outofprocess/protocolconfig.h similarity index 98% rename from src/AspNetCore/Inc/protocolconfig.h rename to src/RequestHandler/outofprocess/protocolconfig.h index f7d915d6a9..0bb34aa53d 100644 --- a/src/AspNetCore/Inc/protocolconfig.h +++ b/src/RequestHandler/outofprocess/protocolconfig.h @@ -3,8 +3,6 @@ #pragma once -#include "aspnetcoreconfig.h" - class PROTOCOL_CONFIG { public: diff --git a/src/AspNetCore/Src/responseheaderhash.cxx b/src/RequestHandler/outofprocess/responseheaderhash.cxx similarity index 97% rename from src/AspNetCore/Src/responseheaderhash.cxx rename to src/RequestHandler/outofprocess/responseheaderhash.cxx index 02653c29b7..f2fae274d5 100644 --- a/src/AspNetCore/Src/responseheaderhash.cxx +++ b/src/RequestHandler/outofprocess/responseheaderhash.cxx @@ -1,9 +1,7 @@ // 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" - -RESPONSE_HEADER_HASH * g_pResponseHeaderHash = NULL; +#include "..\precomp.hxx" HEADER_RECORD RESPONSE_HEADER_HASH::sm_rgHeaders[] = { diff --git a/src/AspNetCore/Inc/responseheaderhash.h b/src/RequestHandler/outofprocess/responseheaderhash.h similarity index 97% rename from src/AspNetCore/Inc/responseheaderhash.h rename to src/RequestHandler/outofprocess/responseheaderhash.h index b7781e45b9..54f9c82954 100644 --- a/src/AspNetCore/Inc/responseheaderhash.h +++ b/src/RequestHandler/outofprocess/responseheaderhash.h @@ -106,5 +106,3 @@ private: RESPONSE_HEADER_HASH(const RESPONSE_HEADER_HASH &); void operator=(const RESPONSE_HEADER_HASH &); }; - -extern RESPONSE_HEADER_HASH * g_pResponseHeaderHash; \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/RequestHandler/outofprocess/serverprocess.cxx similarity index 80% rename from src/AspNetCore/Src/serverprocess.cxx rename to src/RequestHandler/outofprocess/serverprocess.cxx index b9f8010902..4c94331de9 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/RequestHandler/outofprocess/serverprocess.cxx @@ -1,14 +1,15 @@ // 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 "..\precomp.hxx" #include -#include +//#include -extern BOOL g_fNsiApiNotSupported; +//extern BOOL g_fNsiApiNotSupported; #define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000 + HRESULT SERVER_PROCESS::Initialize( PROCESS_MANAGER *pProcessManager, @@ -21,7 +22,10 @@ SERVER_PROCESS::Initialize( BOOL fAnonymousAuthEnabled, ENVIRONMENT_VAR_HASH *pEnvironmentVariables, BOOL fStdoutLogEnabled, - STRU *pstruStdoutLogFile + STRU *pstruStdoutLogFile, + STRU *pszAppPhysicalPath, + STRU *pszAppPath, + STRU *pszAppVirtualPath ) { HRESULT hr = S_OK; @@ -38,6 +42,9 @@ SERVER_PROCESS::Initialize( 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; @@ -188,14 +195,10 @@ Finished: HRESULT SERVER_PROCESS::SetupAppPath( - IHttpContext* pContext, ENVIRONMENT_VAR_HASH* pEnvironmentVarTable ) { HRESULT hr = S_OK; - DWORD dwCounter = 0; - DWORD dwPosition = 0; - WCHAR* pszPath = NULL; ENVIRONMENT_VAR_ENTRY* pEntry = NULL; pEnvironmentVarTable->FindKey(ASPNETCORE_APP_PATH_ENV_STR, &pEntry); @@ -207,51 +210,14 @@ SERVER_PROCESS::SetupAppPath( pEntry = NULL; } - if (m_struAppPath.IsEmpty()) - { - if (FAILED(hr = m_pszRootApplicationPath.Copy(pContext->GetApplication()->GetApplicationPhysicalPath())) || - FAILED(hr = m_struAppFullPath.Copy(pContext->GetApplication()->GetAppConfigPath()))) - { - goto Finished; - } - } - - // let's find the app path. IIS does not support nested sites - // we can seek for the fourth '/' if it exits - // MACHINE/WEBROOT/APPHOST//. - pszPath = m_struAppFullPath.QueryStr(); - while (pszPath[dwPosition] != NULL) - { - if (pszPath[dwPosition] == '/') - { - dwCounter++; - if (dwCounter == 4) - break; - } - dwPosition++; - } - - if (dwCounter == 4) - { - hr = m_struAppPath.Copy(pszPath + dwPosition); - } - else - { - hr = m_struAppPath.Copy(L"/"); - } - - if (FAILED(hr)) - { - goto Finished; - } - pEntry = new ENVIRONMENT_VAR_ENTRY(); if (pEntry == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - if (FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppPath.QueryStr())) || + + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppVirtualPath.QueryStr())) || FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) { goto Finished; @@ -614,7 +580,7 @@ SERVER_PROCESS::SetupCommandLine( if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) { // let's check whether it is a relative path - if (FAILED(hr = strRelativePath.Copy(m_pszRootApplicationPath.QueryStr())) || + if (FAILED(hr = strRelativePath.Copy(m_struPhysicalPath.QueryStr())) || FAILED(hr = strRelativePath.Append(L"\\")) || FAILED(hr = strRelativePath.Append(pszPath))) { @@ -658,11 +624,10 @@ Finished: return hr; } - HRESULT SERVER_PROCESS::PostStartCheck( - const STRU* const pStruCommandline, - STRU* pStruErrorMessage) + VOID +) { HRESULT hr = S_OK; @@ -690,13 +655,13 @@ SERVER_PROCESS::PostStartCheck( if (processStatus != STILL_ACTIVE) { hr = E_FAIL; - pStruErrorMessage->SafeSnwprintf( + /*pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, m_struAppFullPath.QueryStr(), m_pszRootApplicationPath.QueryStr(), - pStruCommandline->QueryStr(), + m_pStruCommandline->QueryStr(), hr, - processStatus); + processStatus);*/ goto Finished; } } @@ -785,13 +750,13 @@ SERVER_PROCESS::PostStartCheck( // on the port we specified. // fReady = FALSE; - pStruErrorMessage->SafeSnwprintf( - ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, - m_struAppFullPath.QueryStr(), - m_pszRootApplicationPath.QueryStr(), - pStruCommandline->QueryStr(), - m_dwPort, - hr); + //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; } @@ -805,13 +770,13 @@ SERVER_PROCESS::PostStartCheck( if (dwTimeDifference >= m_dwStartupTimeLimitInMS) { hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); - pStruErrorMessage->SafeSnwprintf( + /*pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, m_struAppFullPath.QueryStr(), m_pszRootApplicationPath.QueryStr(), pStruCommandline->QueryStr(), m_dwPort, - hr); + hr);*/ } goto Finished; } @@ -829,13 +794,13 @@ SERVER_PROCESS::PostStartCheck( 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); + //pStruErrorMessage->SafeSnwprintf( + // ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + // m_struAppFullPath.QueryStr(), + // m_pszRootApplicationPath.QueryStr(), + // pStruCommandline->QueryStr(), + // m_dwPort, + // hr); goto Finished; } } @@ -844,12 +809,6 @@ SERVER_PROCESS::PostStartCheck( // ready to mark the server process ready but before this, // create and initialize the FORWARDER_CONNECTION // - if (m_pForwarderConnection != NULL) - { - m_pForwarderConnection->DereferenceForwarderConnection(); - m_pForwarderConnection = NULL; - } - if (m_pForwarderConnection == NULL) { m_pForwarderConnection = new FORWARDER_CONNECTION(); @@ -879,244 +838,257 @@ SERVER_PROCESS::PostStartCheck( m_fReady = TRUE; Finished: + if (FAILED(hr)) + { + if (m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + } return hr; } HRESULT SERVER_PROCESS::StartProcess( - IHttpContext *context + VOID ) { HRESULT hr = S_OK; PROCESS_INFORMATION processInformation = {0}; STARTUPINFOW startupInfo = {0}; - BOOL fDonePrepareCommandLine = FALSE; +// 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; - STRU struCommandLine; - - LPCWSTR apsz[1]; - +// 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(context, &startupInfo); - - 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(context, 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; - } + SetupStdHandles(&startupInfo); // // generate process command line. // - if (FAILED(hr = SetupCommandLine(&struCommandLine))) + if (FAILED(hr = SetupCommandLine(&m_struCommandLine))) { goto Finished; } - fDonePrepareCommandLine = TRUE; + while (dwRetryCount > 0) + { + dwRetryCount--; + if (FAILED(hr = InitEnvironmentVariablesTable(&pHashTable))) + { + goto Finished; + } - dwCreationFlags = CREATE_NO_WINDOW | - CREATE_UNICODE_ENVIRONMENT | - CREATE_SUSPENDED | - CREATE_NEW_PROCESS_GROUP; + // + // setup the the port that the backend process will listen on + // + if (FAILED(hr = SetupListenPort(pHashTable))) + { + goto Finished; + } - if (!CreateProcessW( - NULL, // applicationName - struCommandLine.QueryStr(), + // + // 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_pszRootApplicationPath.QueryStr(), // currentDir + 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(&struCommandLine, &strEventMsg))) - { - goto Finished; - } - - - 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); + // 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; } - // FREB log - if (ANCMEvents::ANCM_START_APPLICATION_SUCCESS::IsEnabled(context->GetTraceContext())) + m_hProcessHandle = processInformation.hProcess; + m_dwProcessId = processInformation.dwProcessId; + + if (m_hJobObject != NULL) { - 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) + if (!AssignProcessToJobObject(m_hJobObject, m_hProcessHandle)) { - 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); + hr = HRESULT_FROM_WIN32(GetLastError()); + if (hr != HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)) + { + goto Finished; + } } } - apsz[0] = strEventMsg.QueryStr(); - - // not checking return code because if ReportEvent - // fails, we cannot do anything. - // - if (FORWARDING_HANDLER::QueryEventLog() != NULL) + if (ResumeThread(processInformation.hThread) == -1) { - ReportEventW(FORWARDING_HANDLER::QueryEventLog(), - EVENTLOG_ERROR_TYPE, - 0, - ASPNETCORE_EVENT_PROCESS_START_ERROR, - NULL, - 1, - 0, - apsz, - NULL); + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; } - // FREB log - if (ANCMEvents::ANCM_START_APPLICATION_FAIL::IsEnabled(context->GetTraceContext())) + // + // need to make sure the server is up and listening on the port specified. + // + if (FAILED(hr = PostStartCheck())) { - ANCMEvents::ANCM_START_APPLICATION_FAIL::RaiseEvent( - context->GetTraceContext(), - NULL, - strEventMsg.QueryStr()); + 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) @@ -1173,7 +1145,7 @@ SERVER_PROCESS::SetWindowsAuthToken( FALSE, DUPLICATE_SAME_ACCESS )) { - hr = HRESULT_FROM_GETLASTERROR(); + hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; } } @@ -1185,160 +1157,134 @@ Finished: HRESULT SERVER_PROCESS::SetupStdHandles( - IHttpContext *context, LPSTARTUPINFOW pStartupInfo ) { - SECURITY_ATTRIBUTES saAttr = {0}; HRESULT hr = S_OK; SYSTEMTIME systemTime; - STRU struLogFileName; - BOOL fStdoutLoggingFailed = FALSE; - STRU strEventMsg; - LPCWSTR apsz[1]; - STRU struAbsLogFilePath; + SECURITY_ATTRIBUTES saAttr = { 0 }; + + STRU struPath; + //STRU strEventMsg; + //LPCWSTR apsz[1]; DBG_ASSERT(pStartupInfo); - if (m_fStdoutLogEnabled) + if (m_hStdoutHandle != NULL && m_hStdoutHandle != INVALID_HANDLE_VALUE) { - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - if (m_hStdoutHandle != NULL) - { - if (!CloseHandle(m_hStdoutHandle)) - { - hr = HRESULT_FROM_GETLASTERROR(); - goto Finished; - } - - m_hStdoutHandle = NULL; - } - - hr = PATH::ConvertPathToFullPath(m_struLogFile.QueryStr(), - context->GetApplication()->GetApplicationPhysicalPath(), - &struAbsLogFilePath ); - if (FAILED(hr)) + if (!CloseHandle(m_hStdoutHandle)) { + hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; } - - GetSystemTime(&systemTime); - hr = struLogFileName.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", - struAbsLogFilePath.QueryStr(), - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond, - GetCurrentProcessId() ); - if (FAILED(hr)) - { - goto Finished; - } - - m_hStdoutHandle = CreateFileW(struLogFileName.QueryStr(), - FILE_WRITE_DATA, - FILE_SHARE_READ, - &saAttr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (m_hStdoutHandle == INVALID_HANDLE_VALUE) - { - fStdoutLoggingFailed = TRUE; - m_hStdoutHandle = NULL; - - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, - struLogFileName.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); - } - } - } - - if (!fStdoutLoggingFailed) - { - pStartupInfo->dwFlags = STARTF_USESTDHANDLES; - pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; - pStartupInfo->hStdError = m_hStdoutHandle; - pStartupInfo->hStdOutput = m_hStdoutHandle; - - m_struFullLogFile.Copy(struLogFileName); - - // start timer to open and close handles regularly. - m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000); - } + m_hStdoutHandle = NULL; } - if ((!m_fStdoutLogEnabled || fStdoutLoggingFailed) && - m_pProcessManager->QueryNULHandle() != NULL && - m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE) + 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_pProcessManager->QueryNULHandle(); - pStartupInfo->hStdOutput = m_pProcessManager->QueryNULHandle(); + 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: - - return hr; -} - -VOID -CALLBACK -SERVER_PROCESS::TimerCallback( - IN PTP_CALLBACK_INSTANCE Instance, - IN PVOID Context, - IN PTP_TIMER Timer -) -{ - Instance; - Timer; - SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*) 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(pServerProcess->QueryFullLogPath(), - FILE_READ_DATA, - FILE_SHARE_WRITE, - &saAttr, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (hStdoutHandle == INVALID_HANDLE_VALUE) + if (FAILED(hr)) { - hr = HRESULT_FROM_GETLASTERROR(); - } + // 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; - CloseHandle(hStdoutHandle); + 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 @@ -1510,7 +1456,9 @@ SERVER_PROCESS::SendSignal( hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); goto Finished; } - + // thread should already exit + CloseHandle(hThread); + hThread = NULL; Finished: if (hThread != NULL) @@ -1539,11 +1487,11 @@ Finished: 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 @@ -1882,7 +1830,7 @@ SERVER_PROCESS::StopAllProcessesInJobObject( { if (!TerminateProcess(hProcess, 1)) { - hr = HRESULT_FROM_GETLASTERROR(); + hr = HRESULT_FROM_WIN32(GetLastError()); } else { @@ -1924,7 +1872,7 @@ SERVER_PROCESS::SERVER_PROCESS() : m_hListeningProcessHandle(NULL), m_hShutdownHandle(NULL) { - InterlockedIncrement(&g_dwActiveServerProcesses); + //InterlockedIncrement(&g_dwActiveServerProcesses); srand(GetTickCount()); for (INT i=0; i 1) + 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_struAppPath); + strUrl.Copy(m_struAppVirtualPath); } strUrl.Append(L"/iisintegration"); @@ -2217,7 +2182,7 @@ SERVER_PROCESS::SendShutdownHttpMessage() } // log - if (SUCCEEDED(strEventMsg.SafeSnwprintf( + /*if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG, m_dwProcessId, dwStatusCode))) @@ -2235,7 +2200,7 @@ SERVER_PROCESS::SendShutdownHttpMessage() apsz, NULL); } - } + }*/ Finished: if (hRequest) @@ -2295,7 +2260,8 @@ SERVER_PROCESS::SendShutDownSignalInternal( if (AttachConsole(m_dwProcessId)) { - // call ctrl-break instead of ctrl-c as child process may ignore ctrl-c + // 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 @@ -2325,8 +2291,8 @@ SERVER_PROCESS::TerminateBackendProcess( VOID ) { - LPCWSTR apsz[1]; - STACK_STRU(strEventMsg, 256); + //LPCWSTR apsz[1]; + //STACK_STRU(strEventMsg, 256); if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { @@ -2334,6 +2300,11 @@ SERVER_PROCESS::TerminateBackendProcess( 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; } @@ -2344,12 +2315,8 @@ SERVER_PROCESS::TerminateBackendProcess( m_hProcessHandle = NULL; } - // as we skipped process exit callback (ProcessHandleCallback), - // need to dereference the object otherwise memory leak - DereferenceServerProcess(); - // log a warning for ungraceful shutdown - if (SUCCEEDED(strEventMsg.SafeSnwprintf( + /*if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, m_dwProcessId))) { @@ -2366,6 +2333,6 @@ SERVER_PROCESS::TerminateBackendProcess( apsz, NULL); } - } + }*/ } } \ No newline at end of file diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/RequestHandler/outofprocess/serverprocess.h similarity index 83% rename from src/AspNetCore/Inc/serverprocess.h rename to src/RequestHandler/outofprocess/serverprocess.h index 16a7fd33e6..04579fc372 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/RequestHandler/outofprocess/serverprocess.h @@ -23,7 +23,6 @@ #define ASPNETCORE_IIS_AUTH_NONE L"none" class PROCESS_MANAGER; -class FORWARDER_CONNECTION; class SERVER_PROCESS { @@ -42,14 +41,14 @@ public: _In_ BOOL fAnonymousAuthEnabled, _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables, _In_ BOOL fStdoutLogEnabled, - _In_ STRU *pstruStdoutLogFile + _In_ STRU *pstruStdoutLogFile, + _In_ STRU *pszAppPhysicalPath, + _In_ STRU *pszAppPath, + _In_ STRU *pszAppVirtualPath ); - HRESULT - StartProcess( - _In_ IHttpContext *context - ); + StartProcess( VOID ); HRESULT SetWindowsAuthToken( @@ -70,7 +69,7 @@ public: VOID ); - DWORD + DWORD GetPort() { return m_dwPort; @@ -90,7 +89,6 @@ public: ) { _ASSERT(m_cRefs != 0 ); - if (InterlockedDecrement(&m_cRefs) == 0) { delete this; @@ -100,7 +98,15 @@ public: virtual ~SERVER_PROCESS(); - HRESULT + static + VOID + CALLBACK + ProcessHandleCallback( + _In_ PVOID pContext, + _In_ BOOL + ); + + HRESULT HandleProcessExit( VOID ); @@ -113,38 +119,11 @@ public: return m_pForwarderConnection; } - static - VOID - CALLBACK - TimerCallback( - _In_ PTP_CALLBACK_INSTANCE Instance, - _In_ PVOID Context, - _In_ PTP_TIMER Timer - ); - - LPCWSTR - QueryPortStr() - { - return m_struPort.QueryStr(); - } - - LPCWSTR - QueryFullLogPath() - { - return m_struFullLogFile.QueryStr(); - } - LPCSTR QueryGuid() { return m_straGuid.QueryStr(); - } - - DWORD - QueryProcessGroupId() - { - return m_dwProcessId; - } + }; VOID SendSignal( @@ -165,8 +144,7 @@ private: HRESULT SetupStdHandles( - _In_ IHttpContext *context, - _In_ LPSTARTUPINFOW pStartupInfo + _Inout_ LPSTARTUPINFOW pStartupInfo ); HRESULT @@ -184,6 +162,7 @@ private: HRESULT GetChildProcessHandles( + VOID ); HRESULT @@ -193,7 +172,6 @@ private: HRESULT SetupAppPath( - IHttpContext* pContext, ENVIRONMENT_VAR_HASH* pEnvironmentVarTable ); @@ -220,8 +198,7 @@ private: HRESULT PostStartCheck( - const STRU* const pStruCommandline, - STRU* pStruErrorMessage + VOID ); HRESULT @@ -230,28 +207,6 @@ private: DWORD dwExcludedPort ); - DWORD - GetNumberOfDigits( - _In_ DWORD dwNumber - ) - { - DWORD digits = 0; - - if( dwNumber == 0 ) - { - digits = 1; - goto Finished; - } - - while( dwNumber > 0) - { - dwNumber = dwNumber / 10; - digits ++; - } - Finished: - return digits; - } - static VOID SendShutDownSignal( @@ -286,10 +241,12 @@ private: STRU m_struFullLogFile; STRU m_ProcessPath; STRU m_Arguments; - STRU m_struAppPath; - STRU m_struAppFullPath; + 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_pszRootApplicationPath; + STRU m_struCommandLine; + volatile LONG m_lStopping; volatile BOOL m_fReady; mutable LONG m_cRefs; diff --git a/src/AspNetCore/Src/websockethandler.cxx b/src/RequestHandler/outofprocess/websockethandler.cxx similarity index 99% rename from src/AspNetCore/Src/websockethandler.cxx rename to src/RequestHandler/outofprocess/websockethandler.cxx index 1958c6a9c4..c64bbe4adb 100644 --- a/src/AspNetCore/Src/websockethandler.cxx +++ b/src/RequestHandler/outofprocess/websockethandler.cxx @@ -27,7 +27,7 @@ This prevents the need for data buffering at the Asp.Net Core Module level. --*/ -#include "precomp.hxx" +#include "..\precomp.hxx" SRWLOCK WEBSOCKET_HANDLER::sm_RequestsListLock; @@ -48,7 +48,6 @@ WEBSOCKET_HANDLER::WEBSOCKET_HANDLER() : DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::WEBSOCKET_HANDLER"); InitializeCriticalSectionAndSpinCount(&_RequestLock, 1000); - InsertRequest(); } @@ -103,7 +102,6 @@ WEBSOCKET_HANDLER::StaticInitialize( // If tracing is enabled, keep track of all websocket requests // for debugging purposes. // - InitializeListHead (&sm_RequestsListHead); sm_pTraceLog = CreateRefTraceLog( 10000, 0 ); } @@ -137,9 +135,7 @@ WEBSOCKET_HANDLER::InsertRequest( if (g_fEnableReferenceCountTracing) { AcquireSRWLockExclusive(&sm_RequestsListLock); - InsertTailList(&sm_RequestsListHead, &_listEntry); - ReleaseSRWLockExclusive( &sm_RequestsListLock); } } @@ -153,9 +149,7 @@ WEBSOCKET_HANDLER::RemoveRequest( if (g_fEnableReferenceCountTracing) { AcquireSRWLockExclusive(&sm_RequestsListLock); - RemoveEntryList(&_listEntry); - ReleaseSRWLockExclusive( &sm_RequestsListLock); } } @@ -166,7 +160,6 @@ WEBSOCKET_HANDLER::IncrementOutstandingIo( ) { InterlockedIncrement(&_dwOutstandingIo); - if (sm_pTraceLog) { WriteRefTraceLog(sm_pTraceLog, _dwOutstandingIo, this); @@ -213,8 +206,8 @@ WEBSOCKET_HANDLER::IndicateCompletionToIIS( --*/ { - DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::IndicateCompletionToIIS"); + /*DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::IndicateCompletionToIIS");*/ _pHandler->SetStatus(FORWARDER_DONE); @@ -254,14 +247,12 @@ Routine Description: _pHandler = pHandler; EnterCriticalSection(&_RequestLock); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::ProcessRequest"); // // Cache the points to IHttpContext3 // - hr = HttpGetExtendedInterface(g_pHttpServer, pHttpContext, &_pHttpContext); @@ -285,7 +276,6 @@ Routine Description: // // Get Handle to Winhttp's websocket context. // - _hWebSocketRequest = WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade( hRequest, (DWORD_PTR) pHandler); @@ -331,7 +321,6 @@ Routine Description: // // Initiate Read on IIS // - hr = DoIisWebSocketReceive(); if (FAILED(hr)) { @@ -374,7 +363,6 @@ Routine Description: --*/ { HRESULT hr = S_OK; - DWORD dwBufferSize = RECEIVE_BUFFER_SIZE; BOOL fUtf8Encoded; BOOL fFinalFragment; @@ -398,10 +386,8 @@ Routine Description: if (FAILED(hr)) { DecrementOutstandingIo(); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); - } return hr; @@ -438,12 +424,9 @@ Routine Description: if (dwError != NO_ERROR) { DecrementOutstandingIo(); - hr = HRESULT_FROM_WIN32(dwError); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive failed with %08x", hr); - } return hr; @@ -463,7 +446,6 @@ Routine Description: --*/ { HRESULT hr = S_OK; - BOOL fUtf8Encoded = FALSE; BOOL fFinalFragment = FALSE; BOOL fClose = FALSE; @@ -498,7 +480,6 @@ Routine Description: // // Convert close reason to WCHAR // - hr = strCloseReason.CopyA((PCSTR)&_WinHttpReceiveBuffer, dwReceived); if (FAILED(hr)) @@ -517,7 +498,6 @@ Routine Description: // // Send close to IIS. // - hr = _pWebSocketContext->SendConnectionClose( TRUE, uStatus, @@ -542,7 +522,6 @@ Routine Description: // // Do the Send. // - hr = _pWebSocketContext->WriteFragment( &_WinHttpReceiveBuffer, &cbData, @@ -552,7 +531,6 @@ Routine Description: OnWriteIoCompletion, this, NULL); - } if (FAILED(hr)) @@ -598,7 +576,6 @@ Routine Description: // // Get Close status from IIS. // - hr = _pWebSocketContext->GetCloseStatus(&uStatus, &pszReason); @@ -610,7 +587,6 @@ Routine Description: // // Convert status to UTF8 // - hr = strCloseReason.CopyWToUTF8Unescaped(pszReason); if (FAILED(hr)) { @@ -622,7 +598,6 @@ Routine Description: // // Send Close. // - dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown( _hWebSocketRequest, uStatus, @@ -635,7 +610,6 @@ Routine Description: // Call will complete asynchronously, return. // ignore error. // - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING"); @@ -648,7 +622,6 @@ Routine Description: // // Call completed synchronously. // - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful."); } @@ -810,7 +783,6 @@ Finished: // The handler object can be gone after this call. // do not reference it after this statement. // - DecrementOutstandingIo(); return hr; @@ -841,7 +813,6 @@ WEBSOCKET_HANDLER::OnWinHttpIoError( hr, pCompletionStatus->AsyncResult.dwResult); Cleanup(ServerDisconnect); - DecrementOutstandingIo(); return hr; diff --git a/src/AspNetCore/Inc/websockethandler.h b/src/RequestHandler/outofprocess/websockethandler.h similarity index 98% rename from src/AspNetCore/Inc/websockethandler.h rename to src/RequestHandler/outofprocess/websockethandler.h index 845452760d..f29c268658 100644 --- a/src/AspNetCore/Inc/websockethandler.h +++ b/src/RequestHandler/outofprocess/websockethandler.h @@ -3,6 +3,7 @@ #pragma once +extern IHttpServer * g_pHttpServer; class FORWARDING_HANDLER; class WEBSOCKET_HANDLER diff --git a/src/AspNetCore/Src/winhttphelper.cxx b/src/RequestHandler/outofprocess/winhttphelper.cxx similarity index 99% rename from src/AspNetCore/Src/winhttphelper.cxx rename to src/RequestHandler/outofprocess/winhttphelper.cxx index 6985c6aed1..ce4256a710 100644 --- a/src/AspNetCore/Src/winhttphelper.cxx +++ b/src/RequestHandler/outofprocess/winhttphelper.cxx @@ -1,7 +1,7 @@ // 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 "..\precomp.hxx" PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade; diff --git a/src/AspNetCore/Inc/winhttphelper.h b/src/RequestHandler/outofprocess/winhttphelper.h similarity index 100% rename from src/AspNetCore/Inc/winhttphelper.h rename to src/RequestHandler/outofprocess/winhttphelper.h 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/AspNetCore/Inc/sttimer.h b/src/RequestHandler/sttimer.h similarity index 83% rename from src/AspNetCore/Inc/sttimer.h rename to src/RequestHandler/sttimer.h index ebed8510a1..1bd4b67543 100644 --- a/src/AspNetCore/Inc/sttimer.h +++ b/src/RequestHandler/sttimer.h @@ -1,6 +1,8 @@ // 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 @@ -111,6 +113,41 @@ public: 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 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/Directory.Build.props b/test/Directory.Build.props index 1a9321c4b3..3a74fe4d2a 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,7 +1,6 @@ -<<<<<<< HEAD netcoreapp2.1 $(DeveloperBuildTestTfms) @@ -13,9 +12,3 @@ -======= - - - -
    ->>>>>>> ANCM/dev diff --git a/version.props b/version.props index cc08cc8495..5c4a7c32d1 100644 --- a/version.props +++ b/version.props @@ -1,21 +1,10 @@ -<<<<<<< HEAD 2.1.0 preview1 -======= - 7 - 1 - 1987 - -RTM ->>>>>>> ANCM/dev $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000 $(VersionSuffix)-$(BuildNumber) -<<<<<<< HEAD -======= - ->>>>>>> ANCM/dev From 869e3d37c8537a6ba07a1684e08c06487d78a08b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 10 Dec 2017 12:58:52 -0800 Subject: [PATCH 104/107] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 6 ++---- korebuild-lock.txt | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b64a798b78..756d48feb4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -29,10 +29,10 @@ 1.1.0 2.1.0-preview1-27644 2.0.0 - 2.1.0-preview1-25907-02 + 2.1.0-preview1-25915-01 2.6.0-beta2-62211-02 15.3.0 - 4.4.0 + 4.5.0-preview1-25914-04 0.1.0-alpha-002 10.0.10586 4.5.0-preview1-25902-08 @@ -42,10 +42,8 @@ 0.1.0-alpha-002 2.3.0 2.3.0 - 7.0.0 2.0.0 - diff --git a/korebuild-lock.txt b/korebuild-lock.txt index fe4a961da3..bd5633da7c 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,7 @@ +<<<<<<< HEAD version:2.1.0-preview1-15620 commithash:6432b49a2c00310416df39b6fe548ef4af9c6011 +======= +version:2.1.0-preview1-15618 +commithash:00ce1383114015fe89b221146036e59e6bc11219 +>>>>>>> 0dae9f5... Update dependencies.props From 2cf020103a273b4df99fd2df7d3d62e3611020eb Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Mon, 11 Dec 2017 09:02:11 -0800 Subject: [PATCH 105/107] Update to new corefx packages (#486) --- Directory.Build.props | 5 ----- build/dependencies.props | 3 +-- korebuild-lock.txt | 5 ----- .../Server/IISHttpContext.cs | 11 +++++------ .../Server/IISHttpContextOfT.cs | 5 ++--- .../Server/IISHttpServer.cs | 14 +++++++------- test/IISIntegration.FunctionalTests/HttpsTest.cs | 2 +- 7 files changed, 16 insertions(+), 29 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a03bf421cb..46a1159f78 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,9 +18,4 @@ true - - - - - diff --git a/build/dependencies.props b/build/dependencies.props index 756d48feb4..51bc469024 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -29,8 +29,7 @@ 1.1.0 2.1.0-preview1-27644 2.0.0 - 2.1.0-preview1-25915-01 - 2.6.0-beta2-62211-02 + 2.1.0-preview1-26008-01 15.3.0 4.5.0-preview1-25914-04 0.1.0-alpha-002 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index bd5633da7c..fe4a961da3 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,7 +1,2 @@ -<<<<<<< HEAD version:2.1.0-preview1-15620 commithash:6432b49a2c00310416df39b6fe548ef4af9c6011 -======= -version:2.1.0-preview1-15618 -commithash:00ce1383114015fe89b221146036e59e6bc11219 ->>>>>>> 0dae9f5... Update dependencies.props diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs index 4b7f3c12d7..608aae0626 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs @@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.IISIntegration { @@ -41,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration protected Stack, object>> _onCompleted; protected Exception _applicationException; - private readonly BufferPool _bufferPool; + private readonly MemoryPool _memoryPool; private GCHandle _thisHandle; private MemoryHandle _inputHandle; @@ -64,12 +63,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private const string NegotiateString = "Negotiate"; private const string BasicString = "Basic"; - internal unsafe IISHttpContext(BufferPool bufferPool, IntPtr pHttpContext, IISOptions options) + internal unsafe IISHttpContext(MemoryPool memoryPool, IntPtr pHttpContext, IISOptions options) : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pHttpContext)) { _thisHandle = GCHandle.Alloc(this); - _bufferPool = bufferPool; + _memoryPool = memoryPool; _pHttpContext = pHttpContext; NativeMethods.http_set_managed_context(_pHttpContext, (IntPtr)_thisHandle); @@ -142,8 +141,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration RequestBody = new IISHttpRequestBody(this); ResponseBody = new IISHttpResponseBody(this); - Input = new Pipe(new PipeOptions(_bufferPool, readerScheduler: TaskRunScheduler.Default)); - var pipe = new Pipe(new PipeOptions(_bufferPool, readerScheduler: TaskRunScheduler.Default)); + Input = new Pipe(new PipeOptions(_memoryPool, readerScheduler: TaskRunScheduler.Default)); + var pipe = new Pipe(new PipeOptions(_memoryPool, readerScheduler: TaskRunScheduler.Default)); Output = new OutputProducer(pipe); } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs index 5d326b27b1..483ea3c163 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Threading.Tasks; using System.Threading; -using System.IO.Pipelines; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; @@ -15,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { private readonly IHttpApplication _application; - public IISHttpContextOfT(BufferPool bufferPool, IHttpApplication application, IntPtr pHttpContext, IISOptions options) - : base(bufferPool, pHttpContext, options) + public IISHttpContextOfT(MemoryPool memoryPool, IHttpApplication application, IntPtr pHttpContext, IISOptions options) + : base(memoryPool, pHttpContext, options) { _application = application; } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs index a02e1fac1c..efd548c527 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion; private IISContextFactory _iisContextFactory; - private readonly BufferPool _bufferPool = new MemoryPool(); + private readonly MemoryPool _memoryPool = new MemoryPool(); private GCHandle _httpServerHandle; private readonly IApplicationLifetime _applicationLifetime; private readonly IAuthenticationSchemeProvider _authentication; @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { _httpServerHandle = GCHandle.Alloc(this); - _iisContextFactory = new IISContextFactory(_bufferPool, application, _options); + _iisContextFactory = new IISContextFactory(_memoryPool, application, _options); // Start the server by registering the callback NativeMethods.register_callbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle); @@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration _httpServerHandle.Free(); } - _bufferPool.Dispose(); + _memoryPool.Dispose(); } private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pHttpContext, IntPtr pvRequestContext) @@ -126,19 +126,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private class IISContextFactory : IISContextFactory { private readonly IHttpApplication _application; - private readonly BufferPool _bufferPool; + private readonly MemoryPool _memoryPool; private readonly IISOptions _options; - public IISContextFactory(BufferPool bufferPool, IHttpApplication application, IISOptions options) + public IISContextFactory(MemoryPool memoryPool, IHttpApplication application, IISOptions options) { _application = application; - _bufferPool = bufferPool; + _memoryPool = memoryPool; _options = options; } public IISHttpContext CreateHttpContext(IntPtr pHttpContext) { - return new IISHttpContextOfT(_bufferPool, _application, pHttpContext, _options); + return new IISHttpContextOfT(_memoryPool, _application, pHttpContext, _options); } } } diff --git a/test/IISIntegration.FunctionalTests/HttpsTest.cs b/test/IISIntegration.FunctionalTests/HttpsTest.cs index cbc1b137f6..fa15c4b6cd 100644 --- a/test/IISIntegration.FunctionalTests/HttpsTest.cs +++ b/test/IISIntegration.FunctionalTests/HttpsTest.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [Fact] public Task Https_HelloWorld_NoClientCert_CoreCLR_X64_Portable() { - return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable , port: 44398, sendClientCert: false); + return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable , port: 44397, sendClientCert: false); } [Fact] From 2f785cdd41c011b0404c5f75926aa9d6f204cf91 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Mon, 11 Dec 2017 12:18:39 -0800 Subject: [PATCH 106/107] Upgrade deps (#488) --- build/dependencies.props | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/build/dependencies.props b/build/dependencies.props index 51bc469024..850919796a 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,6 +3,7 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) +<<<<<<< HEAD 2.1.0-preview1-15576 2.0.0 1.0.0-pre-10202 @@ -43,6 +44,42 @@ 2.3.0 7.0.0 2.0.0 +======= + 2.1.0-preview1-15620 + 1.0.0-pre-10223 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 0.5.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.1.0-preview1-27776 + 2.0.0 + 2.1.0-preview1-26008-01 + 15.3.0 + 4.5.0-preview1-26006-06 + 0.1.0-e171206-2 + 4.5.0-preview1-26006-06 + 4.5.0-preview1-26006-06 + 4.5.0-preview1-26006-06 + 4.5.0-preview1-26006-06 + 0.1.0-e171206-2 + 2.3.1 + 2.3.1 +>>>>>>> 471bd8a... Upgrade deps (#488) From fa9d7a7e8cbcee70741d95b2ec5622be22ed9340 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 18 Dec 2017 17:13:30 -0800 Subject: [PATCH 107/107] Update dependencies.props [auto-updated: dependencies] --- .gitattributes | 1 + IISIntegration.sln | 81 ++++++++----- NuGetPackageVerifier.json | 6 + README.md | 82 +------------ build/dependencies.props | 109 ++++++------------ build/repo.props | 6 +- build/repo.targets | 21 +--- korebuild-lock.txt | 4 +- src/AspNetCore/AspNetCore.vcxproj | 9 +- src/AspNetCore/aspnetcoremodule.rc | Bin 6094 -> 3044 bytes src/AspNetCore/resource.h | Bin 1106 -> 533 bytes src/CommonLib/stdafx.cpp | Bin 350 -> 176 bytes src/CommonLib/stdafx.h | Bin 1628 -> 815 bytes src/CommonLib/targetver.h | Bin 630 -> 312 bytes src/RequestHandler/RequestHandler.vcxproj | 8 +- src/RequestHandler/Resource.rc | Bin 4360 -> 2181 bytes src/RequestHandler/Source.cpp | 0 .../AspNetCoreModule.Test.csproj | 2 +- .../Framework/TestUtility.cs | 4 +- ...AspNetCoreModule.TestSites.Standard.csproj | 2 +- 20 files changed, 113 insertions(+), 222 deletions(-) delete mode 100644 src/RequestHandler/Source.cpp 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/IISIntegration.sln b/IISIntegration.sln index 12bb8b1bc8..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}" @@ -49,9 +50,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCor 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("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" +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 @@ -178,30 +183,50 @@ Global {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 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Win32.ActiveCfg = Debug|x64 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Win32.Build.0 = Debug|x64 - {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}.Release|Any CPU.ActiveCfg = Release|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Win32.ActiveCfg = Release|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Win32.Build.0 = Release|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Win32.ActiveCfg = Debug|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Win32.Build.0 = Debug|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Win32.Deploy.0 = Debug|x64 - {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|x64.Deploy.0 = Debug|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Win32.ActiveCfg = Release|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Win32.Build.0 = Release|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Win32.Deploy.0 = 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|x64.Deploy.0 = Release|x64 + {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 @@ -217,8 +242,10 @@ Global {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} - {D57EA297-6DC2-4BC0-8C91-334863327863} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} - {55494E58-E061-4C4C-A0A8-837008E72F85} = {FDD2EDF8-1B62-4978-9815-9D95260B8B91} + {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/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 94f5fa5df9..f9310e5d9e 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,3 @@ -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. - -======= -# ASP.NET Core Module - -The ASP.NET Core Module is an IIS Module which is responsible for process -management of ASP.NET Core http listeners and to proxy requests to the process -that it manages. - -## Installing the latest ASP.NET Core Module -The ASP.NET Core Module for IIS can be installed on servers without installing the .NET Core runtime. You can download the [Windows (Server Hosting) installer](https://go.microsoft.com/fwlink/?linkid=832756) and run the following command from an Administrator command prompt: -``DotNetCore.1.1.0.Preview1-WindowsHosting.exe OPT_INSTALL_LTS_REDIST=0 OPT_INSTALL_FTS_REDIST=0`` - -## Pre-requisites for building - -### Windows 8.1+ or Windows Server 2012 R2+ - -### Visual C++ Build Tools - -[Download](http://download.microsoft.com/download/D/2/3/D23F4D0F-BA2D-4600-8725-6CCECEA05196/vs_community_ENU.exe) -and install Visual Studio 2015. In Visual Studio 2015 C++ tooling is no longer -installed by default, you must chose "Custom" install and select Visual C++. - -![Visual C++](https://cloud.githubusercontent.com/assets/4734691/18014419/b06e589a-6b77-11e6-9393-4eed32186ca3.png) - -Optionally, if you don't want to install Visual Studio you can just install the -[Visual C++ build tools](http://landinghub.visualstudio.com/visual-cpp-build-tools). - -### MSBuild - -If you have installed Visual Studio, you should already have MSBuild. If you -installed the Visual C++ build tools, you will need to download and install -[Microsoft Build Tools 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48159) - -Once you have installed MSBuild, you can add it your path. The default location -for MSBuild is `%ProgramFiles(x86)%\MSBuild\14.0\Bin` - -### Windows Software Development Kit for Windows 8.1 - -[Download](http://download.microsoft.com/download/B/0/C/B0C80BA3-8AD6-4958-810B-6882485230B5/standalonesdk/sdksetup.exe) -and install the Windows SDK for Windows 8.1. From the Feature list presented, -ensure you select *Windows Software Development Kit*. - -If chose to install from the command prompt, you can run the following command. -```` -.\sdksetup.exe /features OptionId.WindowsDesktopSoftwareDevelopmentKit -```` - -## How to build - - -```powershell - -# Clean -.\build.cmd /target:clean - -# Build -.\build.cmd - -# Build 64-bit -.\build.cmd /property:platform=x64 - -# Build in Release Configuration -.\build.cmd /property:configuration=release -``` - -## Contributions - -Check out the [contributing](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) -page to see the best places to log issues and start discussions. - -This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/) -to clarify expected behavior in our community. -For more information see the [.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). - -### .NET Foundation - -This project is supported by the [.NET Foundation](http://www.dotnetfoundation.org). - - ->>>>>>> ANCM/dev diff --git a/build/dependencies.props b/build/dependencies.props index 850919796a..2dca7d0862 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,85 +1,48 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) -<<<<<<< HEAD - 2.1.0-preview1-15576 - 2.0.0 - 1.0.0-pre-10202 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 0.5.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 2.1.0-preview1-27644 - 1.1.0 - 2.1.0-preview1-27644 - 2.0.0 - 2.1.0-preview1-26008-01 - 15.3.0 - 4.5.0-preview1-25914-04 - 0.1.0-alpha-002 - 10.0.10586 - 4.5.0-preview1-25902-08 - 4.5.0-preview1-25902-08 - 4.5.0-preview1-25902-08 - 4.4.0 - 0.1.0-alpha-002 - 2.3.0 - 2.3.0 - 7.0.0 - 2.0.0 -======= - 2.1.0-preview1-15620 + 2.1.0-preview1-15638 + 2.1.0-preview1-27867 1.0.0-pre-10223 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 0.5.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 - 2.1.0-preview1-27776 + 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-26008-01 + 2.1.0-preview1-26016-05 + 2.1.0-preview1-27867 15.3.0 - 4.5.0-preview1-26006-06 - 0.1.0-e171206-2 - 4.5.0-preview1-26006-06 - 4.5.0-preview1-26006-06 - 4.5.0-preview1-26006-06 - 4.5.0-preview1-26006-06 - 0.1.0-e171206-2 + 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 + 4.5.0-preview1-26016-05 + 0.1.0-e171215-1 2.3.1 2.3.1 ->>>>>>> 471bd8a... Upgrade deps (#488) diff --git a/build/repo.props b/build/repo.props index 810db16cd7..7421f4691d 100644 --- a/build/repo.props +++ b/build/repo.props @@ -6,15 +6,11 @@ - + - - x64 - - Internal.AspNetCore.Universe.Lineup diff --git a/build/repo.targets b/build/repo.targets index 97c447ab01..04c26d53da 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -1,8 +1,4 @@ - - https://dotnet.myget.org/F/aspnetcoremodule/api/v2/package - $(VerifyDependsOn);PublishPackage - @@ -21,25 +17,10 @@ - - 1.0.0-pre-$(BuildNumber) - - - - - - - - - - - + \ No newline at end of file diff --git a/korebuild-lock.txt b/korebuild-lock.txt index fe4a961da3..2e540bdffd 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview1-15620 -commithash:6432b49a2c00310416df39b6fe548ef4af9c6011 +version:2.1.0-preview1-15638 +commithash:1d3a0c725dc6b8ae6b0e47800fd6b4d8f8b8d545 diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 1237d765a8..4af4211f2e 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -72,13 +72,13 @@ - $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform)\ - $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform)\ - $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\$(ProjectName)\bin\$(Configuration)\$(Platform)\ @@ -241,9 +241,6 @@
    - - - {55494e58-e061-4c4c-a0a8-837008e72f85} diff --git a/src/AspNetCore/aspnetcoremodule.rc b/src/AspNetCore/aspnetcoremodule.rc index 3a6973bab1ac1a7ecd6b13d4a64ba0af27c0d173..bca79c6a18ef1bd18d7f4924d0281dca204e63cd 100644 GIT binary patch literal 3044 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 WTCAViSXt%7zUV&RRhF~=J^loSeVa4@ diff --git a/src/CommonLib/stdafx.cpp b/src/CommonLib/stdafx.cpp index 18b5b37fe16d22894639e83cb6a90679348178ba..6850f4cb6e22e6dc2673c7bcc331178bf420245f 100644 GIT binary patch literal 176 zcmX|)K?=e!6h!xd++nC2-6UraQ4rLPy+BERZ6qe;=U3|O4K2Eyd5;-uameF`!($L^ z-45nUxSrczZEz?02bl#SEzZdK jNlw5y3;N86>rUl0z1CN)wAzH^;^YZU5#RN*85*s=RTetq literal 350 zcmZ9IK@NgY5JTVE#5?@pMmKNB^QH+lgBC>R7HVD$28=d(}@f&XXPX{Wqj5fP3B z6*W(8CjEo<*PWSiq&xH0~bmWSUxm-1JqmTJXC6R}6Fs{HMw(;SPz jK2B9hk6i~@ErGog&xkeGDVc2&C`s%8%t3!P?mf#>PN{JrH`N3e%|H8xCQn6SN*v2wo&$ zg)pD4&T<4x#LI#A4B9rHg4`D-4CEYxbOh#DC-88}k6rt{e(#|mk>vuMO|Us@o0u|1 z7_guWxk8H6pdl$rZ4uVf#p_Evn~WFnc<~X>!w4$ozs-*YJ?NOC!> zwN2g5gF+Ez@;7ZqCm5->5bhjiq?n8PSvvAAQ6l8Ku~uPjM>mr9OepfUHi)9lbh@1B z?M4Z&SZob8gI;>J9O1gQS@{nacX?hK*8b-%Pq$gg+T7(r{fX-Vr0v{BTWNAON6VEU zUC&qhIoOCx9KV> L=UMJ4ZU^}VdaMbW literal 1628 zcmbu9&2H0B5QWbgiFdeaH?W|UV2h9t1gR3u1|&8}m21bbk?RCIX(pnGtpl+ObV27ji+Q)Lo$(8LTkL99 z`W0(0iQLCo5_O>;c%)HlX%WA2-p*KU)f13vZFE;#SdGB{W7ulY%w>HbN*(d^dPhi! zy9T){JtAL%&m3tL8EIgo;Jo*-Wgc_e;~VIwdZX960Otgjr+oSOk@xxCNx*Xaw_$n$ zBf~mebwi$tZoi^BcgXy=RJA61&UcQ*0%WeiI5MOgEJEx{5)n1XedW7o-|l!C>AO4T z9@=`|RxsL4%YN${@DvfnemhT{`|S=tp1Y06%}4^`$Lx;i)rr2-kuzq`Q+J8vDq5cI z>?MrO1|2<)%ng|zk73?@>uM%r{h$8H0sdEHt*D^{$?pUFGmyJ-N2m_)PvGxx@2&$m z{PnkKjC=2zYijgl*L2#l@1bu)dEdV--u_r!y(JNDpUCS8S(3}QK8>^JsdNp#>GLPO zmw0cs({Sf@dt(f#XZa_bdi#2lCgjTEbPhMpm}kzexOlLe#w-gEIJ!L=e8w302XnUz AfB*mh diff --git a/src/CommonLib/targetver.h b/src/CommonLib/targetver.h index 567cd346efccbe2d1f43a4056bdcb58a2b93e1a8..617620b37267acaa10b2cb1aca6c7f506d4b2385 100644 GIT binary patch literal 312 zcmZXQyK2Kg5Jh`~|KWmLV?)vgS89yGjYC|9k#?jVSnVwPu*u&?T3e7uGsVE%bM9s#O5FF)R z0j6uQk5!Za(W1nH&r)ru2P@Z rKw|rhjR3 literal 630 zcmaiyOKZYV5QWcL=zj>fEfw0WxN;+co0fK2Vs4B9U*sm0{`t1wOo&Foy0~*EXI^K{ z&F{}p2USW{Xp2p>*G`#oJ!s%(q!H-M(Ty4fmG}kNtEQTB%)V1m=}BwwfWPvrT#@e@ zH0NG}74Ao{glS)#QXA|NYdIfY7hrMp+Ji@H`t9kzWx_SD6;~Ri;cvXH)6CO{-I1p_IJPQ#X=r zigZeSqQguJz35q;zt9^Q_C^^>*mmuXUCp&pw^fPoGX+dho4RCrySs7j@9_UipWkA5 OQDt4mH~x-^Z~X_?9czaG diff --git a/src/RequestHandler/RequestHandler.vcxproj b/src/RequestHandler/RequestHandler.vcxproj index 68424230df..d3ffaac1cc 100644 --- a/src/RequestHandler/RequestHandler.vcxproj +++ b/src/RequestHandler/RequestHandler.vcxproj @@ -75,22 +75,22 @@ true aspnetcorerh - $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ true aspnetcorerh - $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ false aspnetcorerh - $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ false aspnetcorerh - $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform) + $(SolutionDir)artifacts\build\AspNetCore\bin\$(Configuration)\$(Platform)\ diff --git a/src/RequestHandler/Resource.rc b/src/RequestHandler/Resource.rc index 0241f12cce13bee9be7e89ef038c380c39e9cf45..caf820af68d69e5d579d959011008f9d66e1cf4d 100644 GIT binary patch literal 2181 zcmcIlU2md56n!V*Kis7+HEk5F*`$vMBTI^493XAdgs?Iy2_4)4x7(yYerI4rNI!NT zEDy%HA7}2p=WrQ~V3Dm^Q5KsDqO3e5Iha>huqBkRq#`LWQWgicCQzN)@1!>EUlj2f%1WTHjOWs2Ozr5vvLQQ zAki=c6}ryASlKc1!hx!|hG&O{ zjo|hg#uBO5q~k^Y9kRZlGXdG9wP9aX#$6+Ts?3n>c&Y;Y>FD-R=gLJMuS``YpQW5c zueIvX9@!)1`88%2>E<(}kCHA3O*sMl;T^RvOFKDEPdDH{2tBiKzlyx5*%n9QA{<9( zg&p6E9dGV~Jc$3QGIv}g4KUAg(&jE#z7?9m(YItpdRSMjTjb4M!^R5(-+}(;X5-d! z7b1-_TClNOOF6*zN! z^A0pzv5am-6As-JTt$7iKGkpa=}#d~>4*$BM4*|)(>|duUh+h=YtY&a&I4tHz_duS zviTYoQI3Pbz{{bB0x%0kV8Q2;6IhX72U1q>BcW+dSRX@9kLPzr&C6~ta%e>uP4Z(c zNx7$_T62818>V@Ff^*3>$<1Q_!m{nI5*4)vxHJba(Z}CU(LRsLIIg&s%055FmRui; zc&E)1a$gs`j=rI8uHFXqk7O!J*jt+9tOJ(NGEb^z&O!`g1x|EbZWXy&YO(eAe*0pd f`85^an16r#ZraT+KYn5Xp1G3VzM@6-?o#^;RY4=q literal 4360 zcmdUyS#Q%o5Xa{kiSJ<27b;Mj9zyD4(~GKU5;ccFs#KL6B~nO34i1UW4*dSR*~E^W z0D*@f%U&R<)IO^{cDLN#T#A3k-dk?z?oV(H(a=R> zAL(JQv}`$<9j8M$j;&%xKq>MbunO)Jf zBfG#Fi~i_fpGW(^`NvpCcBpacH#{#n^$F*cJ5;ue%H9H|<(^cWH$Zd+_vwF^)7jY?e5+CS9u~>o8L^@r1x|nAa$Yl0b!FGvTgy*t zt~}vwCN5Qo^|WMF`kbX}N6v1kLAOMwulaMF%P<|Rjp zll_)U_%|84S@kE5MyJ;ur)}1IKo>^!Jn`Nl&^79A&>!6KJfmh+T|ZG`F2A2sYs4L2 z33PFuv#ZEzaz_NcOO0+rU({PcN0~gVQ&DRvzKZ+8x9Fjo)bTWT(07hj)zB&(&N0s` zQ0DE69T3k&cP3?NXZB50f9)`~-0E<8vTPj5*z@`W z#^jo}w)~!yB#Wyn88Yf$W6>`6U>dO2W><5He@IWW1O6UYmX(U^esI!s;sMr{pC8~F z&+MIEQI~Y$x|V(Xxo593WtXS26gR>fPODkmpX=aG=Jlc_e$Sg(_DIT2`o;Tf)sDI6 z=DorEEtyjdtT3F}WK}jLqo4+t@?wNV)tAQOSryCYn(qSToxH0ca}@J8MVM}1*;e)Y x=`Z$XeV^iJbRx~e__u$4>Y-Nu`2{=1QEb+`_p<(fvpTdsnXg)w{?AMM{U`Y~CA9zm diff --git a/src/RequestHandler/Source.cpp b/src/RequestHandler/Source.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj index 924806ae08..882330ad6f 100644 --- a/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj +++ b/test/AspNetCoreModule.Test/AspNetCoreModule.Test.csproj @@ -28,7 +28,7 @@ - + diff --git a/test/AspNetCoreModule.Test/Framework/TestUtility.cs b/test/AspNetCoreModule.Test/Framework/TestUtility.cs index 8238933fa7..dd2bd67c5b 100644 --- a/test/AspNetCoreModule.Test/Framework/TestUtility.cs +++ b/test/AspNetCoreModule.Test/Framework/TestUtility.cs @@ -13,12 +13,12 @@ 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; -using System.Management.Automation.Runspaces; namespace AspNetCoreModule.Test.Framework { @@ -963,4 +963,4 @@ namespace AspNetCoreModule.Test.Framework return uri.DnsSafeHost; } } -} \ No newline at end of file +} diff --git a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj index 3cac8887c4..ef1ceb86c1 100644 --- a/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj +++ b/test/AspNetCoreModule.TestSites.Standard/AspNetCoreModule.TestSites.Standard.csproj @@ -3,6 +3,6 @@ netcoreapp2.0 - +