From dff0db80ca2092f1f6e6dbcd327d4e8d83075729 Mon Sep 17 00:00:00 2001 From: Sourabh Shirhatti Date: Thu, 25 Aug 2016 12:23:56 -0700 Subject: [PATCH 001/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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 zcmbTdRa{&_voDGVhhV|o-Q67qcXtgixCeKa5Foe{+!@^6-G;&4JrDx9eEaNu?tM7F z^Kj>7*6JzguBu*L>t8i7YASMQ$b`sHP*7+J^3obmPze4|P;m8#FdrjbE_N6nUj!a9 zdLEk2HXh#QZq`r|R?ZgIWf=2pJ0^ zadP?KhJq3n^Kmh^bg=dyx3IRg2Z~T$clJ_}+gpiH>hdVFE4xTp+u6(exmj!asc2dH zIamr;DY1QIh``iHCy-<^P(Lp0XOb zl(U;PIS(s4izPcRJ2@{0D+d=hA0HnxIVU>@I~zME8wV!~2cIApryx5!`G0>XKeV}7 z*$8S#%l@~nk2?`cI}Z;RK{hsTZ*Nv_E>>qZTQ&{>0fGO>;N)capkQ(L1$vnKumIhu z{zrnewY#O8y^DvvGm!j063s1~Jv~GyKW6&Bmf+-~to*+P1Kt1IP#-2^^D%c}<6vcH zb8`C6y8esW-9y9r|DDGFk=k9$*TtGm!`j{1)6Me3A2w9~!~9{p|GT395Pr-?P}R-; z!zt#D($1EiPS!vV1!)n=k6&1=?5zZ4WhFVecx5>xWqBoJ*f}^PB)BCcqy#vmIJu+* z1URMsN5=n&S4vWfn^TsDQ-E7Snu9}zU6P$cLYh;GkDZ%cmV=j%=YMz=fbJgVKuhcY z(QE&q_rG~L{;#}(Qf}7f9?ouB&d!ei69H;=&K}P0cFr#3QkvZ4y7oXTXK(laIOl(@ zRodFk-pkrb*3H?8{J*R#X#aoEF3B$~%_+do&dtlm|6i--mgJG)mX#LZ;pgY%;%BG) zZ(ghaKNe&Am<-#0LgW7vD*rR|5r6-={NI89ar3{ojy3ROzqoyD44!4;7brNp5d~=p zEuW2x?w7oM{#u@2QVL$o`pYdI5Z`~GEWXTtSA5=qAa^9g0r|VK2o(9yv;#y>4;`De z)HWz`A4FoUf4~3Tiw*A;g?bjgl3i1RrI@$r7QZlOXz-_5?*>6fT)-!7|B3Is7l<(c zzCRQfT7xS}TgexT19LY1{T3r*shh17=c~DWqb%ZD-+DHv5R~^})~$$DtY|IO6hc+# zGs?a`iIkxzrI$wY-cb&SqW|k#4}x#CrD!Dc*<74{ZD8#WgxMQe@a(Lyjm}n(j)pDj z&^DqTR)yX;KEdEcVGe&#qQkCsqaYKax?>xDc^Fxn!78WXWL#0 z_Y&)YZ?+tew~8>`i;j9b{Z72PE>m@61JyggNcZT-x+ss6pO>zfoxK>k3fPnt~ zg8BQ|b;fL+M_9{{X&Ne5~oU6P4)*!K8FksboShJsqU z-*VD5ce<}^qk@4HGq!@7d4}C*8Ufr+BYep5UL{St9NPV_SVJ|-X~qMsjJ*!6liaV( z_A-14_^}pz*~|?{(iO2Nl*JO;$wod`?u7*3+(L?PvfF%=*NemFcBY=|;Hw^a9!}b? zi%EQ`N!Bu84QbJosv$1RH1g^oq%&`w^DDoaU`-X+B<)pxv)4Hy&+LcPhVo0V9+q-0 zq)0;)kbr5KU9&?0T@iNyqPV-=OR{~I%3~ne zRD>`$?dIt}pIF|4?yg~vUy_C z7Z;j-Mh#n)|J`+{UoHEvs_Ek9b2IWvK?}oNOT4HU_Us%em+!O`2iD_o6^Al=$ zyJ2DkiEeV?rDsu;)+1yys3I)fg!1$-=|e<*dUJ}Fdcb#5)SdTmy(on4u;X{5{CNue zWm0*kR>^pakmmeVo-cwPYv#4qy4Vy3CcwB_1nMS$pbR|UIbM|4{E5`*Q7)Hve+@I>AO9yPy-OGeB7zi3(S>Oh*fo(}d*WI|8HZ;=bR8`tcI)`PTAWHO zjR}(Yb(yU^m^5k?(u3=Z-^Pbd9%W$M3m`7I-N(*9%a0cdagj3G_u% z0r)lHofI|7$$7Ue1A$o8nKJ6apLfI8mYx1~PXB8NEO0JeLf{U@#kG(mOPaj4{yo1C z*sxb{iw;TDNS|?W1z~yCRB|#hM}`ufj1IT2k%m4dlJF<`J|N8JeuW{7Juu)1Z{?PD zj7>d#sKlAPox&}Pr;O%?$wKl5=Pla2d2p_MHFGDLEL{yIW_p~x9^Ht|O#M|sABB$* zNAm~|Vbufs#wHAc>)@M}#hxK%Hmv!k^0j5$BG z%cL^+54|g1X)bB2TK#YnI7Z4vd+%h>1~R0WOPkMRmAj0{14l)Q{)WRrBb50lyw1+( zJesshf^4$~K=g@L?qWB9Y9_smv(ogq02D@<5)8SCh|Y-MsbnMWouB@s5htUiITR13a?<8%uwbOZ|k zsw8(qlQW;0iICiWf!x{8I#FrYv8R@pnzxbfUjNerR!C1RA*{dwE}1|)6<%2q3v^NO6LU!*~|aS9t1h;SkrmHfvt$=yY2 zd&=^HKlpVfR0>p>B2MQw#BJ+A4%0B?{_V?~b6+-}#TRrDHjS8Ct}>ArcqZ4ySLUq~ z=rasdB)d{HFKe7lhlwE>HTDpWJEA%g7{J5J86BMI0ySRD7;>Bt+!*y5*nIu_k8y%8l0!!n^ zJ>B80OHf`Id^!%*(cZv&X*^#57QT4#O6Z;ME~s0Ok?9*dNCsfUIKOcdRQ(D17;&W zl3hsOVJk@L#k7~C<<_>}97vmc9j`jfZ~5Yz_l2Ftv>MG_jsn5MKZkibm^ZiDOW6@c zITjm07z6T7P4ossJgc=jpbXb@r?)72>t}1oEMp+?qp_`toia!!+F8i?^RkJ*MMgKM z+VVJ8UuiWZ^~>(K5u0F(+ATw86BG7axBmU4p9>cU9ix_Of~FQhQjcCS0q!eyQqg{D zSsaN|@%!5V-+jo2MXxz9;->;{`yMi5WiDGP6$Wg(Vee-tBQB$7($(=odbISkAF8Bz zQ%cOZG}o);rn#ZJt@ha3TkUlD9#YqGzpOX>G(K5twt*2QcIKh{o-&Ds(dCaf|C;vp zGIQohsxcxQ)1Y_=wOl4Gw$Ck7pb@Q@%_THTH~|>&?WO#YyqJg&kH_;#6?H*Pgyqf=iIS2yfUP6|GNN6)&^(omow?@gXep4juG?Q z`gSeOdUIu_clmrd@^O^##gLS*H$P3AoX}V&9aOK%&IF$}H_~Ix&0D?dpm)5Hu_JXt zc9jXQTMw%+@F-N+$eC{zKVLt+Dz#0&&>U9n5D9!*6t$?WGQ9;>I5O#Xe~!NS?fLz~ z{bbu^&A_2_2{w#2RW|Fjyv3jIy<}u$<sHvHKA7tIRT0^$#i(0~{;b)u*uh6wdVcYmr`<&B5k;de#o?4>BxgD;q8cEt{`(i$ z>3EO}gW-Odpd}q(s{@-PhYG-QNww znZC(l(Ml>cgX|uL52JQdS@UfGo{_ylOL`~jvsiw1D@9LpN=pnA5NFtxkAAk`)A)=d zbWDk|QoP2l*4*UQUyiwsguw(uJ{sHTTF<)rVBnHh?5bC#H}FKPcP6wHLs%T-{~i!d z(W|4?hQ_gQ+gYPaL~*>V@Op~^oa~*wO_ext@R%Q}8+fk9Ms)-6-#%%wkxnWQ7aH0^ z{YZ6s`YHtVmag>AZxIO&wJ@zd+wR=Q`~{QmYQ%RI55gBdBSH|`Qu=_5Z6Z4Irh3P_l^2{>HUz2LxLA%cJ65qWxL>CmiTA)X3 zqU#`rs428D+us$uiVYMk0z7!am?aZoC|y-Dyt_yzDu@2P%#Qj(fg_1?sH2}~1f3&K z)D5{WiLqbbt9(W1M-Z0T#hYEe;1DRFmR#COKQn3$bBLC%M!kJTR znrmDCICe1`C}8FCmt$OXBY+Lph?`|}xxI~rp@`SI_hm%N2;HdI=~_qHa(pKzdo;$$oqWZM%W8Q_j8nDHo#lX&g^eS@AMG}mP` zC_eYe{0kO)A4yZA(_dW*b|&LSfEj6*8HV5}!Q5o}#GcUaHp2wq{W7=od}xNE83$Fg zkH=R}^fqN+=HK#=Kgl?B!(>>fCWA^}bF*%(Rc_l(iDG6j`Loof9LM0W5{|70$bswQ zPufai|A=J<>9FmHYYU8kLh#_h|TB(x&mVH0ms-u5T`eo?e(7MA2- zKfnr7+*WqVB_ti0EBcb+be)Ugac+$Yl)BQK8uLSTOtB6H!#5h#84b6-P|AIar6*Ga zYt1R6W^Cn}olr}%(K{$ykQsD|_3HIYo9xw#-t?@Iaan{$j!Yh}SBY&CjKf0K z$*ukH;iYhT71=;Or~NzkP{Jzl$>derwO5K=ZgnH*nWZfAh+lseysR(3?pLx9@BQkx#j`>JFb28p(KQpvxp#G&mQ zS}i!}*unL$8+{u$0mPCWc_~;!t6~y!x}jxISzOo~&;CY$jDVO5?pn79+Wc1Z4E7%g zk4`AET%Y_g6qO+0WqjT@@?IHsRV8Iox^Id(Wm0@Kl ztC1HbYBt$}&1ERQa*)d4T3CE$*NIbKf#v`HM(*fgXK66#UI-&eC~q0ZK0?#LJ)t!y zRne)N3#N6MF=C)o9e4>~LYLm+yb&fr3KZk40)NNSqMP>yj&rCy)1pb=B>ppxws%t%vF{i4P9=s^>JGQ~W zx)Z}v!GEnu0Yr0ree%&sL&%mOjGWB{GgZ!`uf>&ObtWM0498apJ{5mJWd3i-Xh$tK9HWWKPNz8v}7G!l6cCvdq-6B zvjZQ4;Fj`*9doo5+RUT+@4uOj9E*)IIpyNN9VWi!KDCV|Nf_JJq$uq=4F)*as$QLd z!41MI)&Z9B5dhvV)NUX9{N_SRoDaJ+;rldhd7Ey;VS1>*F8XV0DW{@@{}+zZW>t*} zwbfs1LP}$Zr{9o}2eFTr`3cVRa_l(>a$ZbzqBv}H7l(%1Zjai*UisNg&h#QaN2hl! z$5Na0+3iS=D7BW>X*by4K5Ph-rR+hV=aV`s{*Btcn5$ z!F)>qq}nee=NOx^@vv3Dp?JS`ta!yxwHKdrj)+7fErDVA3#3fw4+7FE4&zCfYq&SZ zwvV-&9K?=s!#ZhV1>zj|;D+W%ym@YVPUk?5$k_Pn0wY}X?Sf)C$Go#u3qO&CL(0lq z@#zJ-)jVrAtRwqE^%T411?stkMfJOg$}MVQ@zu_#PD$Amxcz2>+bUqbq8yord9iqR zfrUx+pfy9tPeMsKj zS=@KZT7#`sn%$^K8rU&Dh1Q&suH#U80_pj2|K@cSb#@im9C{yK+FM{oE)7g7A-)Td z2Kuw#RnxDs5vw>~@xYt&L4@DahISfIS4Rns6o#E)UTug3X?u9Dyi(g2H2WS?G*pyT z%#F|j4p1XphP`$IsmXbE6(s};_@TCNU@mca8xFyQw$x4rX(fzILzNn0NkVU_oC69w zW4yX$x-0&8(BPg4I_?MfD4?AAw7fP}V<6OKvWR-3Ca}bHn$vWLv-8L4Y zr^4+5HJRx7c~t8&RuFcGsdR3gvz=xeh7)9TNrt|<$} zh4AHn+^AvT%|Nhp)5VykhJ{=H9XB4p`%9pR{O&UnZ7;S%wjjgU^`w?<7BkrdipqimUbGin56@SI;C_0{KB*-+{b2}>1gm7ODZ3j2)m7hVTF6R z0Zma0B}5O6m@fTmZbH4@V_D~Brz55Nusge;$IEpSB~V|4r#}}?gD(e*42t!ST2@~r zqHIi5EUy}b4fVc?m7|?@(UeG&V+2_z zr6%AjEPYX^)RPc03dbnBZ0CEp^igwWTb`5%>p6TPhCw{rhaB;GX@GWofOxNKu}3Vm zm0FL%o$v{*Z?>vbiMc8@Eg?1fRSY-_UuTrhEtx6zAzn#s3{(R4o=h55qy@pmfIpG+;8ML=&{y5b8WSCBy5<^!sFBJO`Y$K6P zTK|XdlF+9w-W_^?Kda|NsR~#xIMCI&^!YH3pFNmTcY)CR;%V=PJ>HTJ!ApbHRE!pYB?Ne{dbJr~b)*qXH_bBrp`>%KyPJXsU^H>cjU4J3u1RzdpaX5bJ$x z28QZ^-xz<8ovfVR1qJ5H5R!*^$%vVaxjBhxUkvP;peuai!BP|F6g_6zs(BWs!X)J% zIY_+9Sw&{FY--qUu-Q_n`hlSAPltuN@rU*2tf=S3XZ4~l!U?{kn^DdCz%?6C*uBzE zJ^1~0l{kBe?LFKP*Y7#7!_LOsNOu|ksLdq4hXPwi&<=ni?S&6QG_B@0mpBW}U%hXE zO|hkfs<@jPc$Y5LI`JYUit9p9b5G1=@#VO&QZ>BZw2k5gm^IacPwcFdjAeDNs}9B) z^8J6VNotjbe`Bbmx4j}mK`1i_7q`X@)ORbA^HgI0&}3gP*Vc#q-Zc!2RQ{Q!%k|n( zZbR{{HJ6M$hB_pu{E5MO-P6EH6}rnBNpcZ4t}~^nk0vJ0)Cvq4x@E-=Nww>IE$oS= z!1qv}B4z)?GOu)%!jm|AElYIwN-=dUyA|4(?|}V>x7OCLu;z|4d&OfFO!m1S&g4MZ z|1p5wu!j7GUYgqiW6+**_dJeqsq)AXoV=_f*_qArP4hRdjvYy;tn=g|m~{ecV9Cj$ zZlhHsKYJHB>k z#ojxC!o(o#=ok%yCsGNareY9rPmT3=J7X7Wr!GT&fVQIL;SYt}R^#MO@9QHHS$GOV zJ^Y_~di5L0!F(jtZhe$30qT{%n20fa{2A&_z_~%95?$FiCnLT}U{Oo*lPss5Ax^;q z3S(Ove&z(VP?buimRt{h44)j~vw13653i6;Pvpxb`@=JB`ppsk=15g=YdGX4JvNz; zfBXl-xDRHWisv#~_!R@+3)8MXag6X+-j;4slZI9fgonWD_m|6G2kwin9Tz>SZ@yoH zazW@Fz9bimG~;Q}-dOEiQ$>ckLVO;@LOz^77X5iQ(V){=geBv<3FOR7lj~pfX^CV? zlhvVDngno=t(AP6af_z~O-6Gr96DK#)31e=) zyvK6k`O|RAoL74)y5bc)di|spAx*1Ve@C6YUjVvmL2(@bki8widmdK}yiR$R#+Cp6 zbeDT#la&O2W-#x+y*qLxocA)!_X?L@L7bEMv4IWZ}@`jQ9myx3^$gOq(zz6Uy&+OU#& zf1HBhurKM4)kq?L%0B8Ku{}Qmjuh-O6aE^g(|f89VdKqE#O7whfrB2m0uXMPLmQd$3~ z2<-0MiL$dV5gMJ?E z1N)(Yyx8a>069X1*Z?n1Nj|40ijNFJ#GADPhravXy)xA28va{0J?MZaI#+&XHOX7X zA|I{x)OD5VbE$C5DXv9}8B2z5?N_Bhkj3;8I5_;EUoo#c!4R z;f0o&&tQCDP5G``+6ivN=6I z`%6T`&hM(#V-5wtp;e|$AA-;L_DhkUzp;iUhOgS8FUPX1R6TQ4J6VJ_g({M(+R5r} zlk1-!{lAw>y;xe5tvE@ajkum*`@p#xGx!$usj?lc3$OEy_e%L669ZZ;j8}NpR15n+ zB4*>)AHvygqFow~Vz!J;rK>e6inlm!sp+vvyg8wm`)%;OM6;#-j;`%HKpWDxqjj=QCt5}`-@SasN{;>Hs>feh4V%ZxPgxo1^r!mO;u#E zYS45~O{y~{Y{%+2muiB?&%wB?xmBI`caHz#wcDixZ}v)Y8ukaD+LtW6 zg_Q3zKdXlR@q;b=NtoN1Lxi^Sq-UhxDg8L>JZeev6Ki?>?8D{f2i`F-WhMoNt>4}O zn`{E+8wo6Wxe5X*jPs%dnks~w2|f>47hU!A_xwU8+guCS8a$N8SkP$~JU%G!@t^NJ z@xNsIXA)s@uY4~ccJzRy4eOLJ!W4-W8Su^;F69>BM7vBc7Efu^1*>VfJxn4%A53%Q zB5xEol6a)bKZU!Gy2yA=*-ys@8mr#wKf$#*N8LTD^=Mg(6gY1 z6|aAyn#F<*FCQQkxDX3>nEY?XADFW8t`7?Z_+oTCDwHAddF}zfqsKyYGf9O#nK}%6 zOv`urs^}=hkuQ9)oSH*zIjnObQ{@YY_Yu*TAdcV#?BXrYTwk&4!X495&{)K zCk#e>U=QX?{f613DgspOfxP}awe`zF3ogK{%Yz?PJU?izSQO*uhqPJbYkQ-G`ZlAx zSNoZ|+GPrNIGk8u9h#c^-QM1H{=Il5V;Aq_U3DFs z5|8Xhlk@}kTs^N)gM=|i>QQ=`*`iI4hqJ=;eU&}2eQbu<>~Y>9y^&~1KkkneJ-H|& zJt%a0_yS4}^a-{=`FXD8MdfdX>oViPI}|D(5CmbBw+OzWICm#y{;0 zMp@(&Y~|54_<|uD@5$hanGM|gi*KH|yq*Tq1WYe1Mx%2r=75ld8zXKCArh0HbE_1` zo9S^AjB@`CCd_)JzCT@U+I^tOnJqLB(pf`vkwY`v?;8f{5s z5!77_vj8+&c+^@7!_lkg#(a=VHOuTCsO7EEZu%oHn>8|36=A{Mx zQ$c1a?J3UWhys-7^*N zrgba6y@jz1&?ZgOsLomGYkkt9W3c=-kP}q)jk#J``lY)M3MAjuO}AOogr%L-S%IPe zietoqX)op0&t?{zxTTElj9n|STyUnJ7ICaQBW)0*8glKs=v)-iVzib}V_SDtSCAnG zqX!eetZ2+rr?p|5OhY{c+vkU@Svn~L`Uy`0CL>lZf3F&R<(^Rc&q4U_oAj-aY#4An zB5wmA6M@DuTWW!DTCk?kgxw~My2;{zMvdGq##Fbdh_P)V6FtMk@=1~p+00AkO558L z!o84pbzvYtZI>_9shsHj@SQOQ!o#v;f)f?vVJb}700=2+gZqIz~V+61ivUZxV2BA=&ngaSB zUO+8{sm?!r0N|q}kYzgveB% zGGoT2;ni7rCo#Q09TsZvO{#re`U&DTlaG!aM} z6fG&h%+RsEC!%6ZYv$h~cJ8L(PfZ(j`g0#Lc7o_;zRwodA9PDsZ5xaBTM-8Vl4sf! z0?3@A<2X4oSTvK}ruX31L3auCqN@qb?RBmEKG>nkqpW^TZr|{ejyF8u27$K6 z0{qmh#g}95j;5o2N8ht{ucblJX%MsvT-b=u>$(VSQQ+mZ;iv)peY?ET$e^4w<3G{C z5mUXab9eA-Y+@uJ^2m;75iq5#*~mTL*985v5=cq{eMJ)eZSj(X|07{l+5+UY|DKFq zDWOJm3Cn6`0-FxQ=#@Qm#uWhwN~08x;Bn!L9PjM!j8@Z&LtD+0HX~c7CDowsZ1vd% zBL0XE)}^vXMKXrkeYol)(x}pW2|C(Y6>CIeS*vAkj=& zA2urMDJ#M#stj5P7#Fr9>?PQtpsM`A)5s3{g)Y8L7l!Gz%?4P`(ARY1@(=oK5f)sS ze_-3Q5y>j)^|P#%CDINT86j?udcAlnZfh_)rlGfz?$?kAMyFF2k?9u2%U>bl|~+xr9Yl+WVQveaOHsj7=BL zOQAltsA0sV8r*#dz4mV&Pa@&%d;op#wiZ?-xf4wW_h6ZW#PF`kWI8vojAh12==ywQhH+UCOI)3iK$F+} zOB+!tWByn$++hqzCaEQNMz>zMf;u5#yY`!q&c``5ve;q17K*SP2t0Wm6?n!3qjt2{ z^D+djN|pY4bPp|Z0TmjA;xnYE{wX-RAG;7Axzw9i@#z*1dq`DHAW^Pp*UvjcWsw;m z1rL5TivNX(*@NmnEU(4jKc){mP@61DHo6m^;Jd3Y79+Hc8Ca?l5k^X&B;eGW$UpA8#ZTwb4w z#)x9*oLK6e`H%rgoob!VO=082{0T)FDIZ?Jq z?)y!7qN5ESlAlLaUX_qK#asa?svYia17k%6uM0KOXXZi#@lM2aggBoqisZHn)>DNu z?ARxI<`Sv1dF#B|E@;tP_L4iT;{?&9tbb1mK$zSNb2@6=vPtP>Ug@bGLvRPwr)P35W)}%v6gt$H${szGm=_@s#Ae6M#P1Itdgh+GKW=WZH zw)$2;e*Xv0h_Qi+<`Nizv)J!{qL3-Bo+uf5!Fyge&V?*{K(p@jivcA*K^|lxwe$w$ zvHZd|2*pY^6PJRaap*d0Ndp&VuN38Kpgw{5rw~C*?6k4l{RmU4k`v_A0z6O@dEj|f z0-l5<$Rok{PxOSHPN0YEN-Kob5jhL<$Tec8O$b)q>9Lw%v!JasbIJRmhV>HyYrOHf zD=||mPhFw_nicBepNIx)KZ=_Kp1(>icmX5Y>A%jS>?krj@z5y+{t@h^b2N~ZZ0UY? zx*HG7nZXNW8sj0HZ`F>eNZavR3^5hgq4JLEq)GKnO@~EUdCFA&5x!|W0l5gjOacvF z*F~)z(+_#1MSf)OWoV>SXmCjFv7-yRRRB(0=z777$>s^l8PV`9U@=o1bw`ZEseSP+ zkZ5ZQ2WkD;V!H9dh$GrdkWCJ7Qs(p}f6l4OV)wW9ljGdtX;POPd6-GLI149cq}q&3 zZYr9-30cHbh2CrRIQG5cw*Lnc0i9JB7000Qyp?m16s(ScyX0PCa>HHAvFkPEn)B zebXoqGg9S1+)h0TahK*&;-zZs?{80p#A1HWEAOA?}THXWo9GHL^?Wh;3>4f zoH`P{*>V-OO1GJ>Dlt9sD|LfL1IgujQ5YkrA%_L=tZc`K#T4Y@qaRARtTwrXuHqg2 z#r_1l2@06Xv_44J4}#O<=dp@cpqV6KSr~Tz+TAsu&+56LChdKsD_0U5v)^dj>i6u7&G}lsLjcbR|;pNQ?(v(HP5rN|X!4 zL_%n%P}knr-ah+;oJro7_=$cmd!-mq_E!KNTv1MWzq@+sj-B}6t*)WmY4acFgnNU1 zj@|Hd)(f&Pnrz-!;+ylVP97gMaXd>qbaQ3{rl8g=;WP>P1Z4ZUOAtZ9hM#K6T)>{1 zkhjsa4e+TCkpN7VJLewYvYF#E3cat5bn5Q>5P}QK>4GZW^^q3>miWJ|n7ESu4biOD zSAr<|c6n7A^(;An0aniY$MfeLq-Oxq7;b!f7i(i{w^zcwSVk9yZCJiGk*<3N{Z!Rl^iWm!GNZr|vudaf6 zvdsC+I2+#S1c93m|Ev(#(x8SVzR5rzTa>VyzdA|!fx_Y|oa@Mh{pVFF$FUlSK3Aix z$HFe7)L2>svp(ZRo3AS+2@N3Z_C$9Dv2Ew2ZKL(Ch~j%wAt=Hzuz0>4P;>lBBD5Mf z$}l4GaNwb8sEkd0<4;q}B#s=C3AY6i6O&GHDZZ@>B!DuU zH9uOu2lb2D5n7TBTZ@=Ao5OuHh+QV?XWWYV3OfDw9L7zVUR+SIP$30T1hP_t{FVK7N<5dq#fDNX@WtMqSVtA?kQYXxgfhu|_~S5sNLTiG zO}v%yA!ij$_3DWMms)~v=4(@=n94knBt|0_i*Bw`os91O5KFu>&v)0^N0=6M|2gMTNz-rw81sjpuSYK%o>IX) z&ftzr+l1>3E2SBDTVUuN@NYQ>OkI%e-y7vwEsRF+isyBv|C$ug0+$XPy0M&Y*f zg5ptZi*BI%nz(6v^ zvzfWJ7cR2uw;FcH6ic;Q4+%K3$wsym(zmD8Bj8<+5LfEvPL}AnHDIAhdEsk>LKZNY ztZX{y=&mBW+K8N$CU+kF`*WaYkCIcSVLngac006}hlR*|n&Q{=zqh#mM8oWp9*ep2 zCS}1moROH)L=oC)o#wMdBed5=!Z2&S&VtjZzR)X6*KI6k3rlek9EC` z26)A{*mkL|BDw}+zNY)bP(7`y1jrq$(Y%zp=6=yy4X{UF@lmhlP!Obg z;ix7B3h|}CfR>hS@%voW8ddR;*LuDupc)XL0K5#AH*K_xA%aiO0a|0D!; zuLO0G3_PV82g#f@zYDiORD(@?OEbRWnclv|mrp$uWDATpYt$oPl?mb%w~} z$-$F}P^LU8!I~%+Tl`MbnY$y)mn~PObnYSoY|LSIM6^tPPbCV)M*7)~?IIivlckg| zP)nEcJehd<5bjKp$ot;i3>r5;eaaB5<|ZSccOHoNEop$&lF?J-d)VBpD-BRMVVl@yBO^(-+bZZa@t&eJo0J7IeK6wiZA4|IUrdyAqIr9!{Q$(BJ zTbX(B!d_`w;B)b9xv(A3hA$vO7kPyxPHmyxc0Ps!k)c&1%X7`OJj87tpB`q&Yu8SP zyl#eD=Ok zLpW|-jly%_({DC;BYYd2^p0~0FCCDcuhs^0yQE}arXsnb4&=BP>%D)aFWWYFGG!*4xbtitH+3XjX$c8TU3&q*oqdM zQr_2ej=c2icW*P z+>pyDe1PJO{Zww>w;{@kk4lh5Fw0$%>r9h01hO)XpN=x&(y?qjTeu0{HvMP-m9m5U z1tbs&g2RCKMR+?28vG~enR5xM%p0DCT8Wxp6l+I`stgWgf6yRu4uWi| zeH8Ee-{l!Iho6)0@YMn8h*!*)wgA^+|V!q zpqH|pAa|l871QG2wBlP%uizeu-ER z-KLma7ns({e}y%5EeIZef5w%>V!QUlkcRkG_OtS9YVSAQq)zSA0n$~FqlqpCNT1y0 zM1>lGm+xPKWbF%NYzJJ3&h8bDsZl-~(>Yz=N;$G#^y{;}Y~aWse2u9E$uvRxcpZ~= zOz$=ZoAxPqMiJfP)%U9yegXIbvy?{R&)SrOAK!&{LDRX^>T9}1Uz$8geAQ&0%Jn0e zq>Y#69`OFlOLP9-)@4Lz$oir%anu1We5P}Xrllm`>URi5rwyxEox8m95vK+B0ilgV zvv6ljGxcUNKzgRcI~aSUkf7?a088D_T$Fu8FY%N6wT&nr6Q~Tr8_^x*0pxdfpZr$osFRCTb5bQ=_ zK}wL+LH+Y7Bktx-;)B@qq{iK(pG(n@90$0dxY4GVZl@b8&1IBast zUq1Gt(An5^SWUvVBj&{c?ZQ^3fy|E4(?|)PTDZ4P(zUk5G0lEkP`gH$oA!=A^R(>I zw{OS#^-vwkvV__~)AoO5kiYO@RTtc?RH>V_&2_&Wt}w@{-}f0n3c)Zp1kJVDp3{Y8 zBQ%aVIU)H?;BK^G+^xsjngMI$mQ=F2sToEl4+R$ zcSBY!^t;-&JvVLS6SSRDjOpM~xxqt>>Cu4vCXm;O2cIQufCog>X%}mX9PT#9+PvCI z=?6s@jjQ#0$U_6;+KRw#htax8gKlSE08Jcpqm(A2huh##olCGAYj;l}I!K)8+tmz} zbuX&r1K*16^Qy!O-{(@oW(fl#BAL4}&5p0@yv)v5C~D4$eRLe&s;htO`w^6(858BV z=S$v_$^;2|wQEaJ2XMRZrG?;<@0|6qNOQSn46ePbuMpRsh0tLd#MNX zExy=g>LR|yr2fe?tNXxNt!V;z5@_VQi#qhp4Yx_J9^Say1#9yw zwaf9vy|)0onK3MbCgya`)b^;iz?LjTBRU!W*X!Z;*MkS*RzMJRqoIIl;~D7}2)aQ= zD4YvP?`o9F>tydji%aAdT_)}(kN}uE*>nCnC+?O8bc^kmE6Ft7phW;wP6(IcSoC9V z59pS6CY>hmo-U_0<9n;#jRfD65ygORQ43$d>Nzf>o*){C_#&JLbldPHW)yg%2S4rD ztM~bJ?M%3LM#i^NyxVf~pDfJHE4S3|>UrM=^zGYA{eGwp^@W1phryqAJiq>1vpnsC zRBP+HFRGBPS*s?_)A^E)6~Mf^xSEFaUtD!zRXpj67Qx8_{Ay}x&%YxeRc>F9M5JUN z$HLvDu(sQdfl~E;Ix_(wwDzfOC|Z^gr4jFkKEsqB8{C6#N>W(9f3&A2H|UlSI~sh8 zK)q?F$;lK3rZ{vkidZO}UxFg2o~ev?fw&;u>&>PRg*jq~_(hg@W5SGg(;jsOL?H!F zmnUXN_QYK`vM~L(3A3xGG>pzDKc6Ef#Fud!J01b2UDLTH3d)7XReMC;k63hlg#b6> zZ;i+HLFNp$*Z}6Qo!SAip62HYbHjG3SO!gzDLOA)=d~d>N#k(H&@qO8m4ZxfDVExx zr-b2e8F4olhJBd|H2X?;1etfG%)-vJWAc!@g|&};J5+Cn>c~Xj;3-00VQ!CgG?M2x zJ^o`~YrN*quug_`wkLP1Ratt3N?H>I5kSrRg6oqU>=cF{kWq-Gur`?v5FdAga)i10zhku3aS#?UCBs`I z4wd@153O4#|4NiJ^XIAkbyx{MLTmY89W-g&0F+Y#JQS|QIAsa@VA z@or;ahgNI0KSLZu_D*Z@yZM=Q17_F#vgTPj=nZ-foPxL-Ker9Vf>MnPZRdboqyclY z_$vFYQYm51D1W=KtI_?s)*HqE;igZawcKaJ=q1u~C{nkTn4oV}RVkzlwp)LGw{xX1 z=QBxAeq#e#e(sZxrXFGRL-jdGSaWKX?`^@JqFKjaH4{lUgOuRhcP*Kna!aMa z+G0uEd=kJeCF3k=0k+ zjY6gwE*D=1Bih#+_#>axO~SR2X6zlep_s`a?K}hhMw+y%& z7LLO3mwpRnC^a2T>voMcThL$;-*5Bg^t$aSdi1TXz8&hfLv`qR-%x0B1K1+(73Kyk z*3m8EZp=CHv87-rpx=`!)swp^z*7X0*z&M2w;pb5#-$F%Er3UJ7T-EIWKul=tHI@O zn{MaBEQcKK2B%fBNMj1zZQtw})Kd?nz}key-H-dbvm?aYOEFv862Cj}bnbnay9Qdf zKi;^sZVJMQJDnx^&&SzILinJYgjU*p%%ZNKAVzLZK2UN*yqhbT(`DR*aW^+>Y@ACJ z#Tw;rj7%vPQqEj*x;@DaxbyZ1%+5jeH@)4@wsvm63RP=2afKYJFl(blF^M!XjyP{h zhtI~v#vp^9Zp%-K0Xh=`Q#+3Mc*aPT73uaUNxU0Asn$k4J7xW_^(UlTa1W(=ouY5Z zN)@mN>j$akL0opa|5lY6$lb68G?Z|#DqxTy(Z!_ZO9(92XrtKi7i6m zOa>YYXG2g7mc7_3E;H)D5amYBUWW$J!&65Q8MbeE*&Tg*KUVLz%u_JAeFB0`fsJp( zp+A_WV1eQpy^xr>e(rj5HxbF~VYDv0#gRF0qx)XRQg@PvNsc*i zdx}n<)1YIeHNY*6o>J;Wi4=>_7;EG2%&GAHRP#WYaW_8Yjz=xKgP?af&~$#hBLsx8 z+g|?HH!iJP6433VX}mzU*64c(#kD!EQYk(QUVs#Ooo5ouoEQVg^oh@WJC%dqEzTv% zy*)|^K^5uBuoiX*cl>P4yoYug2?z96R)n7+Gx}vU9I~ej(_0tDaqjM!Cs%96M_@Yx zzC{cwxum3yf%SEopGOufeOxPYjI#2(?M7uYcC2lS=e|gXN?bBeZ|GZDiRaqvOK`z7 z2dlWnblV@{P(y^=EyKEaZrWBnf^_=4#`OG^;@p&8wmp4%|HBg2Mh?nbN=YW{KNao< zX^GcjZBjdP^7<;p4}<0@xU(JTx)Y!@tzUN{iySfvm;nF&c4X4JRZ5zAg^C{^jaLCv z3+NW45t>>#Mr)-jPAYCVTX8Up$L|)Ps*&35$Pctf^6^xj7W5z!#L@S37@RN6QF8>f zTz^t$r*X3F$~XsquBb5zH#SzTzCc5!OWf6a6JA2924J0YFM>D8!BaGvC(f6b{vI)0 zzPks)D}i~{BEjToD~TRV1(*}YPzU?Vh(v{lt4Rg~BI3nH# z*INhlP2JJAmjQiK+N=ek;KqB5C(`3?9<^KN!G*!{2gV|XP}xDHR9*KoL>^B>%)AAR zLXWn!$!FITL8p+;UKA$Hz@u^ki=W6z^e0iMdh`r6&Q#8BhsAL>go-j5kRv&D42tx9>&?dPfehy&e%xb*>_@c8aKhf*QS z*65mKDn8I;3>=G5E?;$+Nu*1Rqoc+$@0`GUy^OPzYa76Pra$f$nLfK%{SkWmuz?e=pO@loERHvNV_9f`*wGUD*^jmNtMJ5^p-n{#K*&n=}?opa3IEX-}z zosclM*5`=W6=lJU^>KddwJ!g9JKixR%Smrl%1hd>lV1VT8R+H{rGzoo#uwg0rBq%c znIv=b`cU~gO`O(R_~Rl$4We*Wm$xnaie5s4KUP)pd@>5@(aFp22A{U_A-`%-8zmr2 zkG1y?EwE)1h5OFj+HSB^(Xc~O@>u@s4>fsBQIFDesgyWB+8TA<2f z>=eXOkvD?7#kZsh#oCrTbMhDPHITF!{@og6wY=ljbTr3`Z+|4|^ef-+Lza0UGCMc)xzaq@BhFhuMR(_kzAv<=e6P zcC23O0eur^%i4mWK#>2vnjeX7=QI>mK$tuQkTzRpiTvO;fdz;Rt`pXva#NPJl67s+ z9#7HIO{BnMAO%Ss+dJ;o7R9;kBsCOkvsOIdhPlbB371ExuNeTb!Wj#($>^Z`>|WJoXcA>(CsV$y3NOq71U-TckC8!4uM@d^O+>cG^IB+TldK`Vk2tXHhMLt zzyvYpJ4kirm>v8ge=tTtgQyW4M~DEhjPa~%l@8msuBxK}0iZ?;ds0HTO= z0hfYL`)02QW#Q$ydLR&<%h+;zDZMXTU~ z+URn^WdJwwn@|;|Y+lkI22?3dO34~_I5X}Ri?!L6=GG2pW0IZjh%ON9VQpi$pBaf& z3=q@yt>0eif4OMg0H_3@+fcyt%r9VSDWnjZKsp(xZV--@T0@u|FxC!s0luax&f>y3 zR_xlp!MIxyQT4=|j2xajkaU$&SJ{EkWUeDQcVTn`3QKS+bDx_b>`Uvt+2WZa>Tc7J z!D8{pC?!D(g^uhK#k=J@8;{}1OdEwtLDlY;H{*!}h23%z@3imzBp@)@_;e*`Sxc!| zhOj&7W@O~3W9^B)?RQ0a_&ZL{i!0FEhG>mn4TrM+zj;_My&ZqjJkgnR(p+2_65 z23sKJ_#L6>DmSh)$SZCN(5<)^vv!raf@uhupk4j~92?lH zYg180a5v0Ll}=-?@bJ)37)r0qZi7xWBqAbqSI?Am<)%z_23<82Gu6}b>k&W-8X`P)bir_2gpqih z3X^XHK!8edF6sTc2<`?KiCeS{g;;l!;BKu1Ut0jqvJ^@@nv~A4u{~8UTWm`upJ=1c z(lRgV9G0}jMCa;(*3Aw%r3BsDQ{xM|p@<4+9h*tfbn$nzHpPCGKVy^2u;v z3QaMl&j)u~1j9$xX>v|9t0Y56&Y1pltSt$gz?KB6(U}3~K{~Cj9bP3Q;*AN?jd#eH zA;Wkl<9FW$T0<#WcpUFd7(}`TzuU$FiG-7DnEcy?X*X%XaxEi@&cpZ(3=imU)QHIxNzN8T50gRb2sz%N$+)d2ZTur%MZ_$Wpn1VG2~d zvS~~X6?R^5Cs4%#5jJDp1#!1ku(p|H(C-w}4mC^el_%nB}T89=V%~Qy(eKOpQI0XhYbju#dLd5j= z1dfd7JSE zSMU(V^~X+<>>o~IP3)x**+hEl4X@9=E3{FFlpvMj3a@&L)>=<5$jC~^4UNc-yCH*H z8zn%pxZ5o1?KB6hE~BOw7aJI1nWmNixGqKJ(oCmqS)@-tRK7O5uNg zoJ_o}r9xxoQ;WI!ItoOub;(hvt_{N~i3pjyUUkW;mJlvX61UUvf9&U2&M3_kayN5` zX8dl2D{la<;8d7yntqINjtqG$6xs%GT5h_XoS=oa*{0heXOWqMLMb0^Glq{#siGu! zlu`~J681!Mp5uE9Q7da5nNe$E7Ef9 zJLyIP);I>p3JULI$GSt~{hIMa85=-0#RaJ?NX_?pn1-1^NM4fYaiZZwgr|VXc1GP} zZko337XALm9wfC?BxKX+FwX$OzMRIEayRhZ{!szLP-_M+av-JQwjgJRu1ACRqE#8L zYHiy!1*-4>R?<25iLDR$o|SG3Yty>Xn^`Xbgab_%?yGRq`l2;@G2q)3?|J54Te+%j0G3Ah_Sk-~tC7HAPI=81wP z2pIcu5Ryz+YzfZGd|7JIa2iCz4ET#MCoh~rq(yoO(1g!-%^fBrAh<( zMvf&si4g`bVFK=ggj#5eP-ts8Mc4M4+m5&+H>M+9H84 z7JgC6!*{!`^{Enb=L$|y;_3$DCYfz53Ap8t@>?ocOI+>9uqpNmtQM<7nizM>MHcU# zABAXWW7L&4s9@YO_%=A(6+T&I-G=k8x5JB>*3F;v%)`C-abguPB}2lzhY%z?JAE;+m`c~z11Otq9@Ol#x)Y7z~m zBcsw0TMv6ew^-Bbvxx~)YQz;TfX8?5|1xF(nU%zXjIBkA$10I&9TYZd2JcblxO_+| znC?Mi$qphC&kzK8kbaY51=*+kCZ4k^=q!JGgdU!on z$4llZZgJt12AhZ&8aSten1pzUZPFwY+~}Wn`|GOe$=yWed|<~SNo_}H!uch!v}l(i z0a3Ab_I8~>5|eyfh@fJb;EBu0onG&Z>b?uCZQ2c@6-`%~nWTtP-xRqm85ui1eHK(t z>-PKW!LlPwG%-Es_R+LSK)1<~b0|yX6(*RMMsGFw0^K~vFw!HU2M1!|C~N}1SG!wo ztrm%xUOP81PA+q^cN+=$rC6Fhw6*gH_5MM^`x2AbMcf0`@zXK`moDxiS6zEQ$KQ6?-4(+e`g^tPY2=Dir|z zuqBl|fnxwlRv*Sug?5u~ylt6(K&$N!xK+99(^}3x$g`-UWVRO103O-pr!|VZIB7uspa|LU%7A zq?lH|d9v-*;o9R{$o?>mcZ-g{LX1qtt$Lkx(w#uVAQd4q2KXwle+0Paz)6*}@Q4qz zhJImSp*fW0%b|Kd*6)Wt_N_0vrVuc{97-z|eE{60craf+E*?gb@|kwQflhSFA=&7* z-S%r#pa0v?L|hOZrzW!BZzy;*6lv)OV%({dM(F-E^GOTca?EGACi;=>T`MWYyl;~@0rCxe0MMW9%KuJd| zvtLG1tVtpFdEjno@THwh7{Ax??FtTa3)cEAR~23F4KlbJd>@barDeOGk0MV;}o=oW;H+waOy(8|8D!$S{YjlfZ8D>V5=02Cr$`o>Dv; zn&z{?SQc@(;G)@tdjp6Y%^D;>OO%*m$qBc$1)X}VjoKaVz{$|tc-#%48NiZZZa3iw zwZm9rMd};dS;4>F4sW_DqC>Imgs=Z~YJZ-DK)0J+F8ku7Dye!Rmbk{-QJ~wF@v~iP z0{#m4z{*Z}6PWWNZNAgr;W1Lg6 z-P&?w3iII*0%zRtxixyto;YQpih~pyPF}_icJaHPMT0}!0LkKrGEd@Zhat?(*)|W| z1Bz6BtM{;zDOz7X|jCe1e0-Ug4`sIMwzcutC~uN`i?V{JrEYqQbI z9yc9{yO|Xj4)Z1#4AszF5_iI#Kv2c+Z+cp{ZhTZqnX+|tZ) zB4v7g>b& z)a$g`kSB1-08Q);$>MIX8e#+1ws|Lvqy4kme-e470kINNA`xfq)&=P3?a*hKF7q)} zV$ki}(D&1s4Z2P1j!k1Jj#asF9e{-X1REcSRTxO?02u3aBSf#H9A>ba?h7Ho9H22_ zA@NM4a$t6z{BB4fClpZ`%n|oQ+=upcwMql0ita=*%$0{h8Snqllz8>rUWV4o#F#WA zE@TkCcF#dU2#Ez6CgMd6?q+hCVed+4T!N9!Ie9)OcV=Tq)=Yimq!JpJEyJ@Q&{v!2 zViCdy?Lfh&dXkQh4YN_Rh?b?+>9o7_Hf&|&Au!4UPxM9VyT@&5P{}LbF11E_%lR&=9nod+>P`zTAUg> zH%ts_>$n?vjA{t(2LElqWxFL@vvf5;M&PPT{L|CAX+XE65O&)Z-#^aIxO%#r1VT%x z2u-`6?xJYiEs`%e{`UQ{(#0c{keK(Fd*S_YR7HsJk_#eB8gVDDCv;Oul+@R_&lG$$ z<5GB(P#E1ER>UHYO@QIm{=AG^Tn%>9F0ve@FxH)9wtS7HX%7ow7TgVzOUuUH40Fp` zGWm26wF%gO6v`I?rB#K$li>Ou`*sLH-}Z4K@H6e#%d#&NTdE1|!x(PcULPmI(P;9n zkVi7rrldk4tDuYEZex?VsJN)uzqWvc9^YkVs?&Ln=B_OMVjyf>KH}?&CVDI5_J&z_q`DL(178qB23#K|V_3IHM^KD3_C+7tg|J?epvp*^U06u2 z$mXIDtA*sF&~n2e=JGAWd4g%5?y*P+-C>huKp;|Yl$#fWY~33Az9NZQK~*rq;+jlBf-G!`fO(d9;-OqR?HmT%IW`7?Yd~JV{;2f4$ZJTF|B`|;77TnYRa&~2=3Rf$UN=`;y)I`uFW|4qie_heLO!nSK1oETo z{bkBYsUR+eQ6fsK3W|NrcH|@Ly`j}@w0!|{P8K$;*;|iyLtLZNg9J|>BEPaEkR5ju zjR40EbVJj!OPHJPLrB|%^^LK(DT}NWA2~F7-ZvBr^bMIqf)Y%fus-$VZl&6tL$4`! z;+k2hE~+%Tx2u&CQ3Kjd-MQ&0+XkD2$f~C9n{9bjm zZe@C4>*uNcV1RD-dcFOD(r)70gy)z-aW}{2ha5CmNNH>I5jNwrq6O2oxS87`N`hQ4 z3~lAvrW06>wblmKk<;x{w&)Zap$;2&dLs{G|UIQLR_+H@Ke_|iP z;azx_orgFTKw;d?_XzjOZl5kfnt7~cbvVH=45@T$U3LOv#HsXe!j7~df&53bu z4CYu{k^$MkLNh_3A{m-ExSNx+Vonl`kT5rWBoL9%uy4XygDU=dKfE8R@$WK#Zl`nm zpO3~lcIBSqvyno_Mz%#h&vg2Y$gBVlGHiCq$Y?Vc#}5RVxm!GR6zuewU_~+vgU$TL z)kwXV6cs%66gk(AC*55FzC==yebFHP7xnROCT2B~ZRCu32E6Dl%-G!!X;~mjJT2n# z3DH}5ycp(>fyqzmB^P?>L#!gee=%WJEP?1qn3@ND5!HLy)0l=}xgSW;N16n$>O=A) zL>~-lznGS{ZIEWUey=maU{4wxc016YvT$ok#=8XsAR|Z`bTr5A(ya^htt@8*<6gBi z;cGmCMjCb*;Yv%>w9TBq8&#$1YO%XZ3uEXkf@`>`oeKPaC!&7Ewsjvc+h5p-;-c|Hv>sD0-v&+n#Fp#`?KBOUaNn-)}u&32&b3o z%ulBq-#;2lDTS)+%R8Lg9gl5N(5-OcZiqC((F|51m|Ze!`E~8!B2Th5j+|7yvt~-& zv8StLc;E7^FubG5CQlxTuXs1v7aMX7_HVNz>&twOYkoGd95iH_j_sD2v+ZsW!<<_< zsxxn)ZmIlqMOKQB85;6u(zv2;FV$}^hu1?@SAmCsA$@>XqbZ(}xoOC>$LrSUuJ6@y ztGO|~ZP(a=Duf7V5|X*uAyp2Li98g~fVHvgj)cZVm=_`; zq1oF|{MPG1{q^3nTi&gq4CrP(0U1EIv$l*#pYDH+hM--8ZW(-j3-0Em6LKG`CS#7L z!p#B|z}v`FItrF&PpLO*fHoB`J72&t*bOI=&ovC8NSB(m@R{uB{r#rm@U?7i z_IjzjkWk5DZwbOTxbbd5y8v;uYF&W|z5{0V?63h_#iyu?!rjtu^k<#HY_fbq?dnp! zRF~Md!|QQC->jt7WLIiVx8OC>v)E@^Ife0ljT1}5LV(FE zVW#m_{>qUg*;Ogz_u{x6Y5O{)I;^b-ss>ylDVb4Cn?Hk3=_^-xmc;OEoD@x@*u4apT8Nt~B9poQ2X6uuz2K zZg6XN6l5<9`R!6tzApbApAVti1kaFg?TG}p3ENuUZR)i+!lGmiCJL^XwBLxgFMc;a zfu(rd&C7T{#X(9Gp~M%z+h|k6sWB1V>?J%*dyvVknlh`pM>e`r>XLc-`|IKTrRx4( zl`4sTdNtY><_5cjpIs&L9y`7g$69I7#yo-m2e30|s!h*zR9U7~2ky=_oHM7KxKKT< z6HP(`kVp|>o029x3(}@I(Sfp;6c3~}Qr`0IKUA0Doyj_EF$eQcI(2RunegM5?9~|d z%AgHZ{P|x0@2}>6f1a=Erl@T_C)a=f`SaAjqaGHRfpb*N^senex1v(JL?sZ1!kTW^ zO7g`B0U-@CNiAl)nGh0sx?zg91q}!sJ?E>*>L+CimXOYPcpL;@N?QgsU?$>} zQk7qL`n&lESTzxy2+I&-dCfk=I;wra~49K8mvh@ODdSdYa29@?#jaX(K3O; z=_q*OEMeD~sA89SDR^OSq+e-hv6Q)K!^n36DC8T^wJ-lRN}JDw$R0Ca4Gr{KLX}l< z82;Y0?YZq^4jLwaMZ+__nr`dx=y7Jh7Sl=WdUQHlp6`%T;Gmmt?)lTP6M#03e~bew>bIwdFsqs@mj08DwnE_ z&jGr@^LCSj&2Tp_33E%W2<~PGcr0`dG1z?h!D5(b)j-m(e_as^>m|4J z1we#(3~?NVi5wA3bEHiCP=<#i&e)iuIl!SA9vLK$3DT9~OS)blqZjNa zDHogRN1+RSNxUMY*1WH3$L5)C)?<$B4q_B z8mHnwkjLFf$gl;x`G&iBfNq#4CI{#3dm=ylbos4ZRf)ge4le_(TW8h*-(98m)vX?# zPW||3ELG0h-GXii*qBR**B!tLz&Iw=e&c)!6n-XbhPy#-KvLZMRX%m6S7+ABfEcEz z0JohF8AWry$7xmju;$D02?hM?+Rt24bXlIr=80EAlnL?pQcd2Av0kIg z0eyQv*1x~?yl=<4EK50tF&9j8+()j_4yu~t=yd}C_v<80Tdf(Dw{Ur<3$ zH~T1y9Uw90-Q7t_V11^(@+F>ghIi(4HnxVlMLh>1lVWGk!*YuiII0xi@tyf{P{kkb z^|zO*`|AXBgFh*e>ucR=aXGH(wCnpv)0n^i1fW}3Twu(Rnm%w0LQ+et6BNMv?Y1yI z$5>rY_GEE4VvBaXs(+$gAJ%+Tsq|bb&IalgicQ^x1^t%eYvA+@X~)iNw&2$mvX$^k zv`N;nx`A6w_`c|qbslXa6K@j`WloY?6pVt}FqHwT6^AZv4>8u8Z$goOyyAP6pG|xg zB&MwgyjggaG=+%upL>=x#k9zFzyP#yC-H6=2wsP!m;o#viwgIvh&FD?6~H6~iOzAo z&>QG|7*3o={c@-(8~O(Bh-*__%!lxo=r-9Zw`Hjg2c-|U3Y?FS`oqmE z#}%mr@6x9#c)bu2vdcr%dZail_5AT|F^{Oq47be&WOaTwif@WkLNtpp{^1T1d5`Mj zEK&TSqx;#Uq+xhiSKEh9?ujn~ZKY_dMA6C}Oau}cY#f|My4MmapX}-h-HL087=WjGDC`OFiMHEMa>37g@Z);U zv1*zn#ZXFY8}q*2u+erq4C|49lJTNwpZRNPkrsup5sjUVAvH2nvX>Ts?>1P$;Bb@Qthmw~hNs0xu2+ z?jK6Lk>D*}B#%8<(tQ?j`$kK$v$w|j0oL3dYxVcHV_m8gg$tDGcf8Bd`To(I#-VQ0 zMt1tx(%0@aNTuD$qT!+hJC|ofAdVNm8})o7m$E{T>6@XT6-Rr&(v)CHYTm}CvBLFC z*dk_FE(hc#L6_%~!D|%@=s||*oJPGMzzLdEbtPw*n>Ij}rUC|LAfOCRWK9&H&?>1e zt0|HgAZw7uL2YnTF~K{OXzdPf&Va9#XbqQ8V)Gy%%}cLhk+VUeeF=+qYuy<~FwHer z5D+5ZGyWaq5;1H^H91ZltbIwrMFt+QQldDPcso@8I@aHg)oWeW>azXyXUM#)(gwF} z47X7lU__y>s#jNr*gX>CQ8qR6vfuRDJOu<a2ox5T?y3+lbBhq!xL~?PmkP0Y0z!g zwrsKQ^dSkM3~F!k0`>&etS@YzGxsi8g`sR>e&}58uT2ais;7>$u&jX_RRB4C-3OAg@czZyE2X? zr9L3M5782u@eLdfS{u@BNdNU(*QNTOA15g$wa)6>E!VH68)JH@%4_qc-@O!jQutiS z74T%wgRrqNHfbJ(VR=0yD2usdD^cQsX>6ObMzVjyX-w5c=E-@sL?Uc!qu6i$G!-@9+R-(sHGn9s(Ps}9pv4Sx#+i0>@ zIHE2>Nj$yRsEn0ZZR6^tCIQVBm2f?+4k8l8=KIPlt`5qw2jGfp;6iaeH|HMr@ZMOQ znmNT;aSj>Ae}w(Xx01(0}dv*GgQl$sxm<-M){}2yC7a zYa>qn|N5(N@Ucv&h~Lcux^b?~k9AeehCT)jj%HJ+Siyn~$jnEQPz!lUp-kev%GIN9DC$FX z@kaW@zJ`as(NIRRU9IJPc2wE5mf~PuHIa~{^i_u2W0#u^ zm$xgS54yGGnSwh4SMY9mgFWHRhnx(Vrcltm^K+;;vSS_2w1+ber6z72+d`NWD~bya zBfjX_@WqejHuFp^g9X_Zq)^P&^GqRJ$rP!(Me_okvv*(h+EX)$%~?n+A_JHlb@yR6 zJS2EB+KV!;aEh6ngNOYY6$!0b>-{L|60e8qw`2YLOZ|4}Q<_X=J$kgQu}wZ$)-SSgh zRA!>36xar>;C=Wc#+R!2=eOhgu?o~^RaBSgTy_^4qVFH)FVHPUU6NXBFzcC88huaJ zH}@f49Dn7w)P(QY1a~^#gpeTB|CaQnXO=WO9M=-nyuLs~@k6$8SZ;AVTQBd`A{q%I zUbdcjBf1km0Oz}9({1d1CIN+SCqeIJ)ZK$YOPawQiVjNEwnOS84-BdQg*1w+J zF|UUBVQqd6(?)SO$rA%AWCtyiUAxyRLb$@vsHu9OC$+DWxmcCqEq}h(f4m=jrD8Zp zQ2~N96fixX42J#-blWDDlqpIm2ss|bqo59#q6&h70L?ZtgJe|Pxk}Rp z6;`B~Y;|6lwGm?a>q`TidOIu;{$ET8HUquqki$}v4(2Bd)iHlS=~lcYA=yDj)w2(X z?k(SXTDRkW|L0fRvLw2P+xm3WaIEv^xhaaiO7!y6Psr(p&oDX3mQ@!tZ9OSi$H8Qtt~EBL)~9mdEanLdJj$J5z@H3RZ}0bZKW>aSm6N z7>xy{z#hS)$s1Jw?-CZ2MZ!bd-|gRC4rMWT+D$`eGLxyLo6qTLb$71%Vy2zv*0jd< z7^z-aRZ;pf6Q!}%S%5-8rO7lPOzsvjT(L=tg@rT2 z5VIK1Cjy_lfJ6cloSKGU_hUwh9=-fLCd{p1g}LRMYwh|(I~KEDD!_)AE_?jsL0*2L zQG2P&Z!gtvuhrY3Jk(HX<>A6mRH3eo40PUOI(^I-%R`#*4z;3;M0V-kknkK*2}}ND zxEo32y%?!Nkz)`Z(nYboVXcvQ${iGIMA(!yGc@99kbOAm4%1Q$0|LGvnij*XhY8p1nvK&y=mxOgMUL0e|IgmL zus4qDSb#c|om8c&`*!#I|NpolbnQohY1Tsc$0W&j60l^AOCmI%WNnX{Bfd;X#p0L9 zlW0*+oU4bQI~U!^2WS8LfUpCm>mi}dV>3%4>)vCau0nI+Z5(I9azHm)Re` zde&MaN#aQ;)63h5d7h)*9!JugGq5%nz25O$l%>UYSoS1cgr`S`Zd@yn#yuO*x7{3l zJF^F29iwt8s;~R+2rRDuB--fBz0{5W``E5a3H-OKa)LB17ChAP5jY=Bfa3pkJlyD>F z6rKL@ZMQf0zxRXlhN&5flNy%0%6pD>0NuX7I6%BFb-h&OM4TQw?|`EY<*4OQY^8(a zBB3@bnh^eXAs&|UilCXhkQnU_tpSH2nAdM-9qQZrT~`bsUF-W+OtG+Td)ynj^Z$JA zdE4UV97u)j%ZV9s$@&-mhVQ>kYdk$*X$w}J~gj1qb?G>AqCJQ zAt?A9Cve+mcYrB5$S{->cM$eX+w6zZg54?Ask8QIt4}PtaDIEJk#vVSbK51x`&W+>qd}(D20;Sr*PG4Mj+yh@OO(4BOfoAV=;^?jh%+%kK`$-OYXJ(P= zoUc>V|E0LY(i(#VUX92e5*Zdr<`ne=p^BW>jsJ1EG*(PQW7nwHQtGesU<)(4xO*sD zYzk0&^RK>taWT`gl*FoDklc8Xhd#S3I%PC1jXq?puLb>nQGPe(*F$id(}fxb@}tL` za^Jqcn77y6f8KUKUU%PiX5UuQ{5ZKAdn@l~e6bWNOF5Y+*~s*4g(pQN)Jh*^JFqqs z7Ri)t^8EJzoj_v0`{Q@BrR^3)Qr`4IH>VOxa5riiYYWeN<~~(rPcbLP@&5Vi?d8YI zB{z1rGu-Ud#QL|*zaQP<==k!HT|wtjH(EabMFI~!>#%W5wNidD1d_%ZXYPK=V*k>s z7gMrmLptN-DGh@%biWAhHu|&wyTf00#>Ddzn$R9X6z-aXl85r(7#WKo<~MCog&Qug z90&;sy!m(zvL*LWh+{M$F*t>&TCognkWk)p#?*H2$=~oxvBtrYhv^Fb)%jcy`piaa zt|^3YDW-1$iQpS&EQRg*!<7$imKle+V&I-&2l(e7Ij!X4ZU|TlhyJoGF}dSHOk&4t zDleo)OR{$$=2RsYLW7*=wQOBuidIyuVbg@G8R|6RlNnvC?v#?2G8BrGi`uo#>x=pE zy8r9@{^x+cwL6nuT$|&Xp2^+%|KN2mc)l0il5FyK(lJogxB7AHc0!Tiy@B!J<-`Pd zO@>mn{%dUS z&H}u57x5;&9Gi^jgEb`-<$c&jxV}B#Uj4hi(GI01ZO3W=%PItPVM}S;ga~8wW7pyY zx0&-xPgO2YPd8gh%+~VR^*+MgwNMmSI@M%2c7v12L)@S**d`A5(%GYR zV{My?9VxugsVRE?aKk+zs@)S`%bZ*BTrwaabO(`vyY(An+n`>YOxbv3GO>Boq7CFn zu$JClcW>Wz|NHA8V;@f)*ZWXKhQ2M*lph0h(<&dyU+}P)a1R^DBwP2h26^-sYG8ynt06Tf+Qs-<1>!j(~`7-@E!%*;LF#6gY#FB2tZW1V5S&v*B z>J@hbZcQL8_>=cxV#sj)<+u_ZZWbm1GTD;J;L|A2g@|;zFg^LeEKN+lt<&k6R$ASR zq!-5b4(4Qm&zzXC2#RA&@iHQeZJPZ;!C{8xz9SCYHnPDk(2W^OjQ_qf7xe7}LUGYI z=`lHAUUmb>)pZm@m>gUA>6)$4a-(|3@@Ttj|91lM*Awg+!0#!gJRluZ^l)5PzRUMi zw=!O!Jg9Vew|s07LEOfkqkjvuwlOH#S5igCGGgZF(_*-*d!lb^{{Hd$zyE%`^9$8v zfNqCw*d&$(Oedh*9cCO*MaAE$?R?y)sFl=RKO`M!u={m$o9f_h&QRLZ^sdFBN#=&6 z3M+|Vw)|JkT1`J+$}Z`?z)+rRDh&TJI@e1qOR-2?{;N7!qqco=5K4z|Mz;rw$A9=kO-#7&)*u; z2dT%Qs)BG%2eL(LPBr^j=z&ki`{1;XPN&7WjCi#ZWMuJ06XXN6)SN5Mj)qDKWH8E3 zvL__g?rF7XpOU79iUpEd-8j*0F!RDGz{;aJJd#fVnoX?DsVCn)VXxkN476^qyE@|9 zZxz2j90v5l=~j{lqLB76=|76Ti;|_i@LA;aRZyYDyy`b30h#a*lS#Q0L8Dd0DB0{r z>1L7IuhQip!h7`M*`JFdL$=72Cd&+db@zv2{3(Iou9%0K`)Y_+8`-ME$~zPBV!|n1 z5GS}@X~iB8EpcbGfoF)Y_SLdpsrprMQBNqL-6YxP_Rx6E^9tgoANc z9P`hqop+(RlwyHj!3pdNfbLiWX_}Hbs{}#x!6VaG=zIAMi`)dF5cdm8a?P9*8ppRW z@=lt@P~EYc9Y!KPOYV-4XFB2eHhIeG4$#|vsJigyxBbs=`)~VpKh1DSW+~mZ?S6Mg z&v=4>3kD?(6Xr2#XAg(Yj)T?HD@-MkpH<-UjKhVsN%n{B>>qd7(+95pXheGB!riR; z>{OG0jB0DnXWzDG6M6ma&)3~wZ!bnf^_co}rvj!+(CuV2?nWSqk<fK1j< zjRIE@#(Sb-P{f0Z5v`|qoEhz6HFq|ufguSk%0>LNqbZ%nG>jeKw&X#Bw}^S#(V}_X zoob#ARTmuSn^m%?XH{xR4;J1B2}e)AlWb<3u#G+n~%j>Sy?)4^1uk#%=0LI;N#^C0N^7 zLbipwQN{#rMbU|&19$7(G4e{C6|pM0IL~JrmYj-PgZkZ0%q5x@g%987Yz<`Rsf813ms9d&UCfar z-1Yp?J!!N05TQThWl)&jRj3H4|L>@?(2tuoNh{4*;P26U8TqSG2mM;4mvV47Y*kDi zGvDoS=){S?JW&eea_G(FC;Ha@=lkxDZ~H#dd}*cV8`=}@#z6zFDX7dFa((Fe@ew7B zYj*X8<2u-BZG;?qvI%BV65In{x2KWVfym{V(w6#~uvs8?Y6!Vw-U`2UQnj^HwFF1^ zcLvv5Grea2`EB2S?Eiip)#qaQ@!yATZ}B=RV0s4KT4S(z;Y3L_B5eT)4WOx!<~FXv zmT%~QY@PLH7Hx(^JyMriD5i?$EI32V0z`5NnoLQ6t+V&X4oX+l=fw#j?CxwO!4N<` z%Uk(oj?CEHrMLoX`c8LXM;-OpPd3qc zGI1Ev6DYD0i3RsUU&T086KZnMnxQbl8NTZ5fFm8cq2{S&<;4tqx>*aJK_vEy2MJ-skP^@jmLyp9%0_!RhVMzOKDg8}5WP+!r>wKX-&^3V6; zt{FqtX3P|L?yaT3LaC2^b2PHr=<~ zt?ttVbldh})dsP)I->V{YFl;`6OlAIj_PK~%_TYk1L)S-n!3hOyX%yM5yOJ6J+>jh zdEcKzv%$u<>EPh=O!R?VGr_(Ce~FhBUGJQ|GAC=G>_R^%d|Pl>Bz|=tsgF64%XOQy zKsRw4QQYk-N=mg(4_6(#?wr`fTF;2Rah4+umx7OLM~>$*pl=P%^x}Pd-w*X*?QpX* z7=QNhp=nzSJCK5VKG?TlVpkrHUH>its$k-?Ps-9@iTW;%!9{8@7lqC& z)y4|QjTX`a-7`h@@@N$kK>!n`|F(NfJ;R$MCOmiF?ujR2N(XC^1l6R|*Q(#9Uoo1ocvZM>}{C>wc2F%$F zf>+Wge1n5{Wpbge1}pCM#M&?7-#;^3VVbWV;j9-9+OPd7l}UX~lFFQIWY0-#^h(Ki zqh~~xfV&G9y1Wqt3pMzt;V^A#{2%ca6A26Lum3uubsMLEO%j8*@8Kh^fJ^Noy_dsT z*~ZfonqL_rzO6{$8Cc#&MvmKpuw=P;-u(AW!JpoGeifqXl~=B`+%P_E;*~k~INWq3 zz8J@}o}xoYyQ{An@OmP5+YRY*u8(vS%Ax9K^?jf= zk|pu*$gMOi6P>z9mVAkPX}H@`vE#SuSBTG6Y>9~_EnZC$*X z^Scdz2B0$5~li2wFBN+7fg-)4KifeaC-e@+nqt`+gmU+}Uy4frQ~F zkRr7|?tQzksk*Dn2oJ<@pRR`zaTH}8SKrd3mhXB~gI%wbmcOIOlv7T|8dgG)1ocVX zv|B+*^!J6)8I(5?HwXD*xGXSM zGAO}Wl4sp-%I6!1E%}DrcP| z$}!^B2bmv-&XVkkERBNVvf_Ql+H8=t&*jUeoX|q5UR&KYRtV0C+Xsp^vf)|JPjj=j zh>%IM2|S@Ky`XOcAKY%&UdLfS=Md*=q=&BSvif4m zS(xqsYWV4Cii^nmPv}#r9sK{RTC0m zC$AIHx9In|?lF{5r zz8hFpOB0O=(1Qk>?#Sj#2H4*tXFo-e3EXo?JdW0dDMM|&3a@lhcSza|xMh}4spq2@ z>w-~Fc-$1k3W%l+pwgmcDyB<*hupROuKE6A{y3p;Z!hiZ&g^#sx!X`G5RLu3#&GQH z;iyo;epet&I`Or6$RHDJN>m+*Yf-ly@=7vIk|zX`$aYeIB&jDHTUt5kH2YrZaIv%< zdF+%lmVjMjHbOBO$eh)En+V6Zz;Rx8et`D>=f~^+{yzNr=#~nuyZRqPDlon7+iUwr zYj?&pkNZ@v%UVrm$6U^vd!EHy*OiqB3v~SU2g^{yUE?}H;X%Ipg~6N@l$lWSQF^<& zmC!WG+-j$iG6s2P=BE+xr454QeMi#VP+q0g)i3+>o>(-1aejul)duoH;tcPux)CkY zHB2)aXjaQ;B!wtBa%^zI_08M~%OFVO6U-U63p$F+Nr4v0`?a2Ft^AnSo`+^}hyPCU zI%%lX*_BYN>ztfHH;=4lFCvw)bYc1JNAt3~V3_>?4;}S!Itq{?c3J|h)d*xfFR z@^0s;cmC(M{h#0W&g&eecF)E}ofgVePyA2GQ?)eyAi@KB;GjT!S}`U?=(c zSj^BJh)Sid7C28%^9&LS8t#zdxdcnZ35HF=NfU6@P^uJxWoB~G6fl|# zrg$K4{IwIR5ODi+t0jcOR1fI(ec%4`Xl=Q5ojdHF$qkcvOQ&ijW-s;F=`!d{WhuE#jtqg04 z|9ESjuBcqio+?lIuD%ooxkNw$%nfRn5ebgH>z9Q{+H0o(=?>qZqxauQVIO{nB{1ZcVW zX>LsOB(0VZ3R68E^xl|%e!jj;Z>^Sa)lQ(>4QG3J&<#04tf&v_UVh1VZyTolD?KH1 zBNf;*J)hmRrgXokSo0M7wpN_5EiqeRIFumW@`@C`Eb(AvTZ)uXTi5PmrOShqAsSo2 z-B6)x(4NyA^`O#1`xx=}hdp+78Ri3McSA|jw{N>Y-(KEcci;A{h_EQjiL^Yd?Q=tgY$d6LGOZWg zmNzlW7Kr&Zwt?1+0I2wc1X{^lJ-f(oK+Fv%%9SJEaT&#yoMQ8FyHsdWswD(`Xdigj z+nv9AgY~j2t)@c()5Gyv!1M{a1-=r=Z9xr%#U+;?^eE-fp5UENTtEe8%71G#Go&{o zS`11YvGDW-v2$37{z-RaaAtY~5)K8EgxG~q=~SjP^T|F63CnnnCPjj2!FoodNvNL%MJc=dvjguRWzNitP@Hls9nQBTmlJSU&|qr51s3HL1``L}T%V`j?MT;zIel zf(g2i!u8lIKnq`d$hoAW0hY%;?=?M$D@oPtc8PnJZTS5x#DBstIGM$#KTRb&ays zC$#H^cVsj^T9&&pVp%Rz0ohw~IuYcwzuSebW{U1;t!d4!A+NUDU20d+H}|o1Yfvb0 z>>icAU{D;j`FAm8bcWm3l9NDjCha}-GlC}l<-$;nvl3Y zcGPwA^bR8(Z)b-mIlWeQC%JxXltWaWgc&tV3~BTBBs+`n`l(%ON*p-%Z3fV-@B8+zw;Qcnxp4dUp?g1c*H=7nrW@p+ zWNnP3r}C`6J;T8bi;nKARCb!io5mJ0Yly4H%&%%11p^+}Gvug2gqR^h+sfwX0ag}3 zuwy5m>{8qCrnztu@7xq*W5pCU;=prg%C|!S|tb<+=h(y zm`)jrnwwZsF;w&2tQ{!Z4&5a*r|fO452ass?T^>?&$s>e*WJrYYd&+|41e}JYrE82 z<`ELNK!z)cWDivCB4TwDuXQ&w&s5~#PT4S?&&nYo z6>C}P+3!rbE6>o>#1!0(3CG;4cHbKhRGy`O?6s`DSa{fxC4wokiOCl;+_7GZsM9W_ zH6;hNsM#P`gcS`&kz^DVL~xZI-~_jkP3lYb2RV2m6}=Cbjnnio{9(98dZCFz?7K$Z zwS9{|w*B$i{&7Oz4Cl)^Slj2?fVpl8c&y$*Rg7UUx&WS#5{xHWU@-;RSwU)BokK?=L@2v~ESa(Qi+`K5j+WJNvMqhVObK zct%ibpX3*EK_qLtwquv}CAv?+$UP4gzP-q+kV-TN*+kK)yi7THgP(J!M2wxYBeT4M zYkgAoZc9>;0&5*JbGwASq*`uch(7|8v?B%FO^a*sE>hl{>{m15FSsgrZ+RDhf3edG zDg(<2nr3o+5|9n~DQJ?DcS|$svG=Ehqiq>wGm$@jld&iTPFN}FYYCKgD6E(_F@%yN z6mqnDr2{e7PtJ2eicn+1Gt?zj(>Xk3892?5+IFOA;sxeqS`yFm^1PI%XBi+U`bw>%qj5H)K~{k zUyVU)P(hsAAn4XVKVNr$eSa~9eer(P{{6VzukR=RfkC%SLuCI;d#PY(JYzN9Rb@@ z_D82xshB2LN~wrQdZ>a|#WK_dn3jX4gq~6*&o6b(yztNBTq$|tZ=f5HUSQ?(VkwF8 zgM4QnB?>S_Kj^BKP;L{Af^M%nGtjyfM&0^l8w!}(b5hd7f^Jd59V|Y)y01})zKtqU zy=+`0R_`vh5VR93kqjQ|ajNQUKI`g6$PiNcg+hyyK}Z8RR>Sj8kvdyY`D8HRppZeX zV|8vxMi5Fu2JUvtc3Px1ZaJ?FF&T(Dcu=BG+K49(mDz-Qb4MuCPD$-EV*|!ohKSfC zefPb@F1?l;=QTG^Q0n7BQHJ8v1$FTyN|gi@@#c^;ZP6EZ1K#FL7pZM~;2F!KDbADM^0XRPg18$oUQmVTGBjyaKEBq{7LAsTMw)fOvk;?v#? zg`U#dp^x}<-Q@@6O#BtwNo&NqWjVv_X-N^{LWSXyc?(n$=kwDd;wS8l`TNJ~OJQ2K zUh;o^bf?-h1L$V$^W?rNe)P;UJ(D&JMx_U@e7hB;ys5D>x zKU>?pAG(jOyQa30(7^QH%Zy9S2s_Zn+CF=a2%8TBn+()!<;U1W<>ju9)zOe%+Kfj) zc{d?$>zg!%K)PvIl2)<`O<+|i`fPc(GtqnaB|5T;)jmJdy1kt%Eth zS=~vLB1sn|V(oN;n7Os68wu0+Ja*4 zFOAJtQs-zjhmLOfBf-a7d*Q_4?ErOndl4g`O99<@dDdVkUav;S&b|-#nrQY35;>G9 zvw~S{TM%ozyw*6lxu~GZ$8Z^ix(bC6lQ2RFot_eHD?E(f%_J8{kU@8FZfVBIL$U%Z zL}{l`C@)39SL|A2*tz}cxZ94A9`f^L_xIaNX=i=7&wnTey5asVnG}vam8FZGsTR*i zQ3_+SZl|&~`1ZhNwPW4p?_M$a_t$j2N3GeH4QE~o9#5O_xTbD3zjH;Cv6*}~Np#WB zwuu$7y?(`aatZ-0DwPHc$taOMc6Lo?J#JE{so1~BTA%)GJ9@Jkc#{45ld7 zb{-M5RGQQVF2~)dfchl88A&~*HPQRzIYa7F!$E3l~+_DH1V#T7%hbQdGy? zIAm@6o%!d->#i-kjr6wo-}iy3RugoC;Ec4slY|5jLOsP#_ki#qa+~ zuZT*KO=gnRjI$kcrMY<+YvKu974P`RtHiVGGKIZ1%1|5%TcfNpX^7C3P%h|((Q8m% z!RNUhOwet7ECg{W_-jVn25H*h--of#R0Q^Iq;T`zi^=tvCYsUzK&5riG{NbXFMRzk z*JP-BvG52}OxU8K4fAektalKkY1frZVVNT6-aSmKY>Rmw0yX}exthSi0Jj}28gVuU z%F6*K2bPjeN37%I$9dk5_Wjs}vx~@cK@OSWgzGW1%n{PH<_I~F8KwdGy}jeQ3TcbA z$h6$-Bb&1ff{2S!8zi?tc7rn#QlV3EQse$$Mcl173vtkmhR^)<_WEt#7M(yt zxDj$eH#{c1fsH9XRkyXz=lNW>&6$j~ZM!M$*ye_w{h3R?o0)i^9mYR(X!r{yy~oW0`B| z!rk21RSuP#DJ^B-)rsQXo+N#lV&iU9PO+8WOb_C=lg3AhVE_SWY4Gx(a<` z3uQ8)2=+h~6r3e1i26O3KTs{^aP?6yb0c&P!uafTi$7iOo}YhkTty!x3Zi>scLgOJ(zwp2(#$ub{GS$7Ef~hW22BG52niSC(Jg zCtsY+0J^b0`Mo@?8xj2)F8q@KbYuGR&&(?gF{hGR@?5d!6Md|QoMDI$4c66;nqS}M zKAy9wZhpnmU5dGL3t6Y7WaC?Su)Ve4{?e?o%m0)*fu!l-7^51U$u3 z3Br)Op)ztN_&X_y`ZJ!?*|-sHq0+_D9Njz(;sm!vhLchyHcy;PAFCSH)rI}e{LjyC zMQPoL=rcIJwJdGZXqDyx_29$E7S0nT-c++}8 zS^>Rlb#sBknX(hh*Pi91g>iLc({&Z-mT&_1zL%2=K{M7S4Sx=wyl5m!FoAniP^Jz- zT;TQhgAfDX)o#x$A!f4??sbd^Xua!f&i_(^i)H@W^*75ptYhxF<j7dsA{ zTP=Nq*F#is479gSBv!WNrp|*`6xi`< zhhx`SD5?B_yMe}Jla13hPx~`KL*_)^sFi~)OjuqXhG$)8+hsjuLC2Fh~=e2PD<5`<_MYr=#on*)t zn{j=NG{p>eFT>FZwVRCD(hfxFz7)XKYbq~e1Kf7BBwF|61L}%?0OxKgBy)DEkkFmx zN`E=d3F6Dc0#@?QqH!abak(){JAok%H9#TDsQ-p++^rac!P5bnO@j1R(6|N4+zwGz z%1+f11oyj>3rQqvP2W8Oe+$!J{rP?0|5sEz4xRmV=sG(}bb}CUyAvIgl3*!PGDQ>I zlC2hzcujKtdQXen)T=*RY*Yx9WGXRFBqgDfIis(?Jj z-ryQ;!i~_>ovSbq^L*y%U8!C(c8q?atM1@KicA_YPZ9d4`wpT-)b!ig_$YbYXxktD(qosTs;9TmY|t9wieE4G-sHt5O)BFXM&!9B}D7Nsdt}bs87MV55yBW`q9O&r{ zxtG%wEO86j+ULvOPkX~H^yB)UpRWaJ-OzN+P{4GkF`axV z@vZ{z`6np#>!VLP57w?cENf8$v&h<#1Us52SD8T_+lav{3>Hd_AWyRgUP^XX$V(Zc zr_1w?!7*o7eNRtwn>UIi{c$=$H})nK%Z8FFwqATsjxkP%moOv1lIw+R@-paXusEba z+5*lJN5|Q1&IC6Jjhds`@Z0%R!pm-qQ(-3`#Q0~dRP8jix&aBSi<|W8!taJc8E*;q z-ocV>{+lt`EFLFzBd!_H^AD1mjDl{>B6rKb+rW**Ouu;SsIxXOSF?ew=)IZFKygyNXJ`Tu~-rST%R+o+uO^oT=;IAQvuT`==Q}< z^&yzT?V7im)gDPzUhXtq;*P@u$uU*R?SZv5m+I>th1`B7O$z@)%RQKt@@y&fhbtTt zwz3v0$o~C%{p*daXhzRw^4&Dk+_vPh-YdU_;*5TNc?q{YvRfrj;Oj%N_m87hoFXJ? z2V&_0%i{qZztDS^4Z0g&KJG>ux=x&m>G{^KWIfYoid#rcOyd0BY)6jWRo!`7Cwbc% z>Gy(@!ZYaRYr)mZ&u_awzwdQ`MR!>o`uA{rA7~Z65`d9Z;E2svLdgjiD_gng2&aNg zQsFPa7~`M+DUDTW$Peng*?y`sl$ZUJ^8tRhTZ7WtnS7`TPr3*xp9t&tL5RvB!dees zb^|no#LF&MITSu0{(BcOMP5YJKS`KsQP?K}_tm~+x8={!d(TUIJn_4H5MkS~x$ibItkg}8YU`MgKvHt}Nqz$DFWnTHjZe*aMW z7{dp*g|9G*vUnm3SDhh=dBE1kU+)LjHBwoE8!@i1y3H_#sGs=to5qNnHiERY`?L+* zO~vOD^1EgDn@@DCwrxf7)K7G;L@+IROpD8fOIjQxtdxGwC%N|3RrP^x-}dd_Z~OEp zml0e1K6Jl6j+VZFhGtv^`kD_f4}Du^9(k`u_Q`^f00nC=@q|=`N9eX)5k)->NHI9U zdSA@WWBnOlX-YS=Dc#3X-LYbB*a0br!ESk2#h-x`XiKDZsBJB_pqRlz5vhz@ouy-E zlU1g4_3O11>&!lL9IlbhDMHF;fG_h;V-#=boTA@vo07W02{r7zYy4|NH zLGPmwPvIj6T9mibshA2;d5TQld6g8Q{82-LS2ZfbM6#Uy!Ml5L`;5pnsBnr6z^R#(PmX_HIdH6$(pYasJHHMly_oG$_; zVD~8e`PpnEGCHo5tV-+Vv$7h-3|_gqWye(!+6V%JyRFO7rs-YOtihp6^r~4TU*PbuJ?K#Fv>48HnUi@Mn?B1V(ZOyXG>O` zl86y$Pb4-hzG-Q0oOFIV<#6RsI6Z`T-@NGI8mT=TyBfWryx50sAOJP)D`jYk@%s+o zUrfr@tgE11tg4>1d)_mx6AC7SZbUk3mvIC?R7(gZyIgR$(wkd8?glth)+pG-&u_c` zyuEl=Fgq&ii2nP2=pHy%6cyfpjr&*|j<7Yq3+=DxtZC@g6k%b_Ow*&V(>Z#(bZ2Ny zkqi@Le~&z^C}lf}7X;3Kx^OfSkv~Ca_E2m|bToS-xEmsQwqc7QJRE{cA$4t%mq5%3 z@P@ffmmFVkeXLPEbeR=1-OjcKtr$O+M7h3$fiV4CTBaMVXl%y)RzJK!1@oFT=@c?U z(w+2^$N1!29FAT7Ar(|E@k3ws)26lS>~Gx)2?hKrpGCDdREown%H)|k?_+?l-WIrr z&c@x8;kFWnuOP1Wyi_U^V4)brr?+sf;;uE|U8P}OKCd9l0jklVyo+HBf7-ZqxmkixlIDb*av531b` ze79Hhj969kaw$~4ib#uk2SefoAx2m5nK*zjW(qYO_uiCY5W1J%P{(hEWWIGM!;n?l z0K$v#^Eh`_<$y^XPZ4Ow2c^NUKr>K<5Bou1)5oFSEi&9dMTPL72%7Ie4ZRexNCdCj zNpDW7o%kX+Kcx!|lUpOP4T)qgKO8NM3M>ZVkp2V5A%`o^xys;$2o<8L7T-+8%vyRs zbcfE?PK~0BW?hD6OrU-?zornjGf()Zd75Rph>ke%z5Wv38SeIcG-3(21X``NjU7cS z+h|+3u){5(M7hIDV|G`I4shn%@fbY@4`^H|jkc1I=9YuI zK|_+F`Ef{^XLnv3%GeI^a#ABiDreKPNzl!u?~DQh&iK>4J``U!r0A7bO!74y5<5Vi zJ#UWvGpwt6{Ek99r{m)8yqJgYO0Ujl&*wOZ-JE1)YEsyuG7>&VGMZ?C;n-MI90l!y z6JCGR=N&;J0dS_dHPbYw1qg8%K}%AZ%;S7SWFDbUy%lxfI!nI?wjLYqNsSs`$PnOD zl!c}5F*tx!2yug0Z&!)$Q+oo{Ues*GJ^?p@7^f~32^|Uw-bLkI2kU#=X5U@fa=zP8 zx)dpI`SH5@&-a%Sml@A3{(tY?@1uR{i`IN@VrzKBCt9nd+?0{-4OvP5b0EL-cWw5L zaDG~YyAzlM&CT?>kS7YX3G;F{YEKzOZ_Aos+LCqGnu$?qF?%gD6-jCf!$EuJ}Jat#t^yF3< zO^ehn=pTw8ZU$a936wWRNbu3>?C!Lx>_49bSxSnr!WT=)Z*(a28aYnY5@IYaV4A|M z;cnNiwhJl};=Sc{Xa4!|vQuSK8e7@lhwhhvR$+w|Z4Pwh zJlL!mM*i2>tn>kScA45vzQB#OEU6hrf28S6iY-^E4m=^ zfuZ4`NQH^>)}~|^A0Tb@h=K31T%06#wS-tC&sbI@8I~1y^FaS2xo@Z0yZTaa+S^)L zZzwIlK!Z(+xJIw6i;H<$mI~@?;U1JAe>(DFUMS=y)ucK(P(r_u7L#`DELqa*%oJaT z?(mD%cRnTh+4sY-7i6%p!ZeT++A?vGQXn*8S5F6p5I1?+e4Zez$Y_`*bWlv8w92uYyZ=|lY&|9X4a!2D5PKXJbg>i5fGfX+lrLnoGu5IXk1rxEqxrjZZs9NiGIoM z>$2`-e@e1G!%<2om2n-}*%-VCLd+5|dXjHph-7YqX9~r$_DqmVljnrQoS_Wp6dEGG z2oXm@{2p%WDNgE<4}q(E>Zhu5=39VMePx@JE7gDT&)5ClnE!h}6!4z=&0{!m{Qj~t z8<1Oatu#2_Q4go4TSK&5Y4Xd*$MaVWa=W8ma(rye_#|yN>;#_m=ZZ!Jaowb3;k? zXL!3uTqPGc2cDE7E!utxjmJGKgn~4t5)H@{LOvDidcnw4QnEdn_E_bz1VxP1Jdj%6 zp=RzYXdEXri9_(O98PRy_IwEn@pA?ey>wclIOaWK94NR)x(rh@++>>m{=}$+(Xi#w%JO)+sVT8Lyka}f#>_a{Yyye#!ix_Lr1^fkM^l(tHviN zErO5rP&P+fazs>t-!0#fQ0K}qt$ASW+m`BmVy1MD7J&*UT~P71HOn%@x}9|!>kB`M ziAvfs_@LXd_OPtwT*AcG2ZbVyOA9C!wYVM8(RS6O44Yi*uCwolE+Dk46sgbh=-6~N zrt-TB;cgHv7_k|k6njdp?lM6JodHQpeLbIyz3OaGD0tSz5*t5s2h;eAlLq8Nbhos+ zL1Vn+47w#hFD;AKEvcXt9qdrR^tg775@h-iP+wKzYZFq3d^IE2SAr%P$H_a&CUCdjgpEfp?NFH3Ei+H*Xq(@M z= z;QmBq6>~$JQ1VHPd^d8UqI`AN8@V#fe+t~I$jd9+q?3_j{Ih#a$F573qZKQ~>$FZy z9}vACW&Cwp;o}4U?-8B_qdUpUH%fT(otr~c4tHZy;6u{_c>2Pt{<6MjdFR3Z%*g7R z1{!4w&8j8HJu4I-6x}9m={`|I|t+h(Tc(g;g>wbf|C+WqV>D}#6+VxQ* zq!f3)Jpp%vhLV)N;IP>kn?}|2nhfkLfw{?p*_}vm2(lA)pRkTTX;7|;-U4Kj(8lo- zJve?~fQpI2(-asI2T&)6It920=6z18_N%&tF7e2`0f_1UBD4U7s&>MWN0?Gt(N`!c zC%5G*JGWk~pJj2BOm|~%36OPCsX;pD37uB;JMY2r};zs_7NHD}I zxSK)_QH6XrWw;Ib1B!-=&*xKNOv0Q*=@pXk`_rYQET>0w6^pdROv^?L*Guz!zi+$t z@3$9R_F2tN_dD#N>wayifaxQ}38Kq`$soz60H)--sT^Q@O{N_)+1Or)L&iYd0VL^KR+mNL-F$$E!BcT9cJYk7e(Z^f3oSDUu3B5BvRS4;myp()FUc;*UTDgR#q}I}rvbHKOfMw{C9%ZRwXtdm{9!3k#1O5goW|U2U7C^D8}mOuUt9g?cL3cwYkwWO`Whr-r zPv{1fB|?)>7gfbPBfuaBU*Z_WI}N8vfOojlyf9|%S2PW$tbGYP>CSG`Rb4cUH7ROI zvc`y*6e4U=XB!E!rT@OX*~N%wwGqKE$QV6q8)+r+8BGUqj94M7N{h0mY~8f62?4uw zCsc{)QS=M9enDE_mV&HP3Ef%jKI9{HPH+fWUx$Bp^nMpyA{H>VKf3tJM;ob-5O}-y zA_5eySE?KPg|;j$=*7)5THZQo3_=}uOBz32qGwAm3$MH$(mgB1-qntv*ejgM- znVe_da*|+n#BhY3k3@ST)>~&ruin@r%vkoF;(|RB_WYR?)ogVJ5-i!Ob7Udm zCLL79o6o(UWJ#^9;Dm40k5J8b)1mdC<`~O76!luSI%j64XyEI;7@ktwR!&}DRZQXR zhf!1`RGZE1s5~bI!isZwjT~|n`x&!2^EaatqDyExR7)`1toWRWM`-o6T^zM04Cl|+ z{U6`<+W7?o=+<}TSFHl3*kXZ8WR6Jv>x(Q09)VPIsZlh@Ii=sF%VDN)oli#ErXUvt zQ=)1J)u{N>3Stb$zPYGK1>Yv0+*<>cDhiMh|E%H`L06f*2jlPvS9DfpYl%v7hnz%U znOsbVmY>2yyaARNU0BS(1l_)B@dN~g1(FG0HPbEBHSnR6kv_ox$8?cVQm`n`rX`j# zitLg~xB%U{r9z`1rX@vos{H`Bac}zaXEUOha*iq|zYrMG=)7Lh$2uRU3JLODlTdOZ z*ga3!o)o>;=?^2X@e^q?icz<&&0QCFQ>cv8Cw}8T3KAwn3xVq2 zeX5qAZb>prC^%YK0e6D~bW4!cZ@c!dx4nTn=r+v9`=P4?x;>v%lM2z-i5qb0e?3SS z-aPZ&s!LPWj2b+PCKf__$~d2EIP_cA1X@)HWPD6dRjZ{Po9Y_L<=`Gmm7kCfdJtK_ z3jLZf?IaKHXvM^Oq*8UN;&*5E|48=Z&~vo?A)i(6y|#%p+-LvM!>Ode%H~L zYL&h#UJ2cFG79BQprB4^m0U=y;y@87g&^XQMT(L&9WqX zXVfpsmm$gPyUr%ZeNhMrl3-udaXMuNha}vSW7!8dzq{^mB+}-?a&P+(6cAUhbINJ$ z^J2vCe`D5lqa|}i4Qx%&9&7ehL?^U~S&H8 zfvbY7_HF$2_VRtd)18Mi=yvSv?@Z7QN+#fo#Sb|j2Ui438PW{QF2Zf_%y-*}!_pe_ zoV#QRFXSvy_6MGJbwxROCbVtQ(yiS!Vh9NCR$DB&%~IjTdQ5(|1PkAL`1yHWR1qr5 zoUntX_x}H^&W5;A9#|!vH@Ut{MN2~Ei5Q^RSMgZHFDK&S*;H7u^C=kb#dBq{TrY@| zvIci5PP!EflZFda!`;xJH9E5T^L3|7>vjd*x^5_7nxZNUtc{yuO0Y`?Qq>XhZ2)OVd_w-q$#m=Ax+?{n=Lo~H{1RQq<# zV3gf<{s9Yeqq<(~T<=Ble-;};21({h($*3A)PvxB2XHr1>uIG%Htw1x zfXg;Re|Hh|hzhG($}3~655;je4(P_5yPYxrd3$+jTRnSl0o};buVKrg&^#`&HmEM| zLtuG;s(dSb&nf4S?r}-|h+%f_b1Ta0TbA`EsvDAsdX%SeCm)Q#nRPkfZB1K2IGYvI z2~~F~&nTu0hobzZLjDJ?MuAGDwgu-(+0sv$o@T2-bwvS=M7M_$_)V!EpVN!!DnnFM zv`Aoq@Qz8I!E^J#I8_z3of4m838^YzrsH=J$+;82FYBdJKydMNmb%XJ=QIH_VS%^E zgLnopOq*NO-&UZt=rq}fB{V@pO81mQ*n#}j3ypLS>o_&354@KkT#N8;n zU`hpBi|iJvJtiri(5vs#b1Kmu6|9s!Sz3lXi!3A{dfBN;|DqtGk zLY_fGY5Dg10jMkIa%+sR2^G=&Q>w~5Ktc}{m8P)4c@nzmEE73PEfk6ti7hO2dbVTQ zO1zxbcns z2R_Z+Y-us+Ygl*Fx^@fV!l5x*&@_v>W1m{tHRchB=mRG3?i zmCi0?$@Hq!S&6_aUwCD08_`;w)7>a*#-}+p@N}%g0%JmzrWClqpUnt|xXe~TF*W=v zKL}>VbLyF5Y};dItya$c+$JD+QiWI-p=VMQo$_FsTcyA!Fl?xrAmI+nhGD>fAczj5 zVrT*?*3^1laUt>$dN$lNH!R)AZmVgFC^jWpQg-Zy)VAX|wT+6(0ci^sK9Y;?aDGLY6Ml*MTws$p|&hBhm%w-VKvaL$>Ms?h&^}{(W;uKPg*yfyxt4w zhV!m=DqxzC6beo}S&_J{H0XAvPDscUNe-6a$sZx1oMX|ZNY^m_CW-YFNhUU@hLnfU z9<%6+{8w2?ruSL4lV7G3JQW~Dle4B;_uW!>`Gk0q4eiu6gp&sm`H*{NT=ZiQ(n$Fl zMY51kiq6i=Ab{m!$@V^fmQAU0v6!#sX;b|qDXa$MxRU7j%9!a1IT0l=)B&!qGq?!1 z{)i|7skxOey;**s5VVsJiCyG+Ivnqox3FvsQ&$#$nJ>nv>w%(e@C?fxaceqrG-Jl& zP8t;TSh`Z)-RtN&DHolI`t?8|@FU8ZNc3NSKiUCnOJ*4HzcjX4*I?IY`b}W_qGoE@ zXO}ljQ9-%erR@jh<9RbVw4+6k1$pG-3d0E3+JLUqKc|wSAT4Sx?&7^<2^WPPA?>KVNrBlXfd_)VoK&jvZy*#Qqvj{=Ng@hoR~bP@cL_#`t1K zqb%I51kV5^ex1~dUCS}tfWs0^bn+a&VIXuPdd#YevIuTFhK_ZgCwc7mxZ3mTZ1FTV za3W&IziI**%sRx;zI~~34bkv*!@^A-plPz@Ni$q(-H;7s7}|`@j8v?5!Em8saO+}R zKX%=t)P*_z|8o8ZINbUfWe`CA;DFbfP6%(j*BJ=)TPQS-J(FNW5=lg#5mlCa8Khn;Q zKsR~KZDO9)j6YO_x%+x@jqQhy#AeL%-Md(xhp(fvNyk$Z%{_|YZdsccTp(~dpRkubCW4H}zymTlo{7AWe)Q2G-hweD71yK?F3Sd_L{y^o`GP(2AS{WxW zPPPt{-^X%SO6P-t(ri+xCCS~^g}56~KtJn7yle5hL8TEGX)DX)rddyRZGN=`MQK%u zigOqM6LB}^`Wo#*6#Lfv^W#N@)~y$eE9ln$zuzCnW4B_l#Cvn`3rxa1!iBc$l&`ZH zJV{u=m^2G7B;PnhdJc~@m|tsA+cn`61@LRhOmh~@!02|xc|ai9+_p|>;3UZeb5acxi z->qzaD*<@1H7O-f@4_)qVgOX&xH;X@Qig*1_Wpd`tI)cQLAT}vy1g^2AYmg~d}k5I zI2R*1b7x9~4nEX~q_Uy%MR%X*VkKjeofJ=^VW0Dy0ts=;zfq=*Q5(-~!p*QZWKjgw zvk)xSXjw`NayyWoN5IV^ym0Z6NO|BZ=HR%>PR$zB?8y@k=Qw7jXOy%VIKHeuSPoSs zmYv2YJtwB0X&j{|_}PnanQr(eyxEv|AgY0kS_%P`uH)l6^9{*&4YguHVfb>D&Ka_w zbJR^CBG(<15IKc2-3Uc$v=BJJ4is%wti5Sw*>d?qI&ysaZY+SY>`U81Wrpr?EC= zt0iR43brI60^H4MqcOb=oEXh7p6~nJ-){=EZZpuW-vZwc_IY%^0hB=S_4Rp$RTvqR zRb(AI79)LgU~-n$l_tjsdVhSp*F5dPMy-g}1qJtq)~>5ih$Ple;nQq8j|r4FTTe!a z&G@(#UX4||M%Hm4nh9#t}xoqhO5_R+TAKFav$|BtrFW1~V(w1O+}S;Tu$IU%>* zb7!**>BT?Zto_~ z(axBEek#zq%|SP7e;qrPEUvePs4F3v_0$W%DOj8Yqb%;mDP~@(%n{}2N$Q)QTypCEaQT20YAe8qKMIj)XU^GeW*$~M)t&6+y2%#Kn=V#&>liv9% z`fL_Y0Zrv^%H36>+i*>wFTrhfzb%ljr?R`x#=Z)_n^#U2#usHf6Jf{d9m@dwJGiPp zHF(9w#8RN#?aftX$Dc|=w)e~}dz#WqaqVJ0BNeCt=6@%k;B)@=s54FyaOou#{5qA&D#2(YnQzS|r{Dp0ktQtY3Z+Q$3) zwPGky(ekl5&!qMXeI-TuipC?l_uYZxoz^(kcyrdEiFY|%zVdj|!);7LH~Z>n*T{x8 z> z`Ocso#F!Rq9dknML(1V?G;sz=FAWlH`E%eP9~3-Bjp8oeMs{)cq#=@U1y|1Ic^5$% zfVGC(1mB;h!>2%vOUB&#D8qbwR4AyIxuU{KAidJMjZ)rxw$DiwS5!_Ay&r?cr%R@U zUF(M;$RXDeA(*o1Tp3#C@K2@y>nWeI(ekCa^`>@Ha9d(x-{*JwvHZSrww?8h$YF6R z?enT$1xnxM;dbZ*49pFZ$yf5N05g=aR;1?V7sA~je+?y!`f;D4{BE)sXsFeFF>kGA zancI2f(@^42zT@KyMg>?>4|~$L=wlB5UdQu{$!U=pr}jpVlQP2rlf;c#C)ur^Ss;dFexq zsvWtt;YFPDm4Vit)y)V9G8!>*Rw*JfrfCiCO>MC4_4VRz@Cd#yWkgV&!80Yh*8KhB zWuJFtcm}#Po>Xw!#uC*@b6Y2SmlQsT@hKJM(2txX9jW@7Wzs7$(^Qq_bAbXi*+ur$ z20S?fu2_=B{a9}0IH z!QD8A;#zc(j5B$+65Vl(e5E31n<7-<=LADF$}1)U|C}xS$e#?PY&rp!uqZM;41u9O}Wp5UkF~$;`S0)Jufs99k)dmL0?wXqm%aw7ZQyy<&6*McKXW(~V9&id z2_~gbkS>28(%Gw*499H{YU1B!8-!?rhSJF#v@|mU7*g1og#XKl5GcfbhXL7J2?_)t z8ic$%&5NR7MT5T23u-|)WE0uWnn#(v9rtPnx3HQse3;$t2}(QC8MN6>0Nfq8o~vN? zM-mten*9t%BTyPwd{?-RggjjgtO$*TXKGz3F6T4I;;OAg47)*7L86e9q<|9SZJeen zrY?$J<@BqlQ}qygDUoJrYNpm>p>Y)6=OL?CgmD1;`3|}YCE-R1bs9d!=c$4J9@+V2 z7tt-?dV*oD&a@^ZC;;Jo14c7*I^LeyCJJ|qoEuzx`J9dtH6A2Rq&vsXb|Z&jSnuig zY5s3lC1$5J!(=ZI1tSQCX2jYxZwlnF(;>%bLXn9inG9d5GUPB;yQ%3B;BMGB_~M*M zN&?&pV`fYQLC^*{lHNleqA0)XQBp}&AjDgtDuc!ne?M%u4_Dv)`p$7T_>=qtpj%`* z7!&sM+iu_Dzwbwz>f4pEG<(6I3K}|#aCqmR@N-!+y$fM!A^p|`aR@JE!T6Ov;k52EOl&;OSVZ=>jx6D0+Gp@8YJ`@U)p)7yGpN|V-)V$p{9u+YqJ=D>7rAkhAHGe(9Ff0;%b2ahI{~o^GG=| z7|K_uG%(JK zr7IMBIgPtXeiSJKoQMEA@Y-A5eF$>W4T^}qE65~wS_vyagSZ0XbU~ach|s6Tidn9oIYu{UQPCKXfwzBZUJulFy$I?c2)|l z`(JNav~EL=+*D?)rGGz;XGSjyI8G>~XCo-Hk~{z+f4h#Ma)=gqffmn_k)O<3s*x){id0UFyN9#(!-rgODbd(i!4Ke4=34NIFf0@M*5Z1L@}Ri~ z3_(g$U|-gv8%r?#$;HDt<(C|O1^D_)qw=Jf%_P|F}$rm4!PFoK77i z#Bu&>ys~NuTJb?Rjuvbj+S%`tOmvvBG@?h-=;^KfeT`Yw76R8jpr|zdNgJd{wbri$C+k1shT_mR)im(g5ZQRXMjwS@EQl7+R z6~Ege?v{kOg(=ptn<6@v?pkkfpB8`u7YI3^TPE)I_Oko)`-@4np+|djm98nd@5k=g zQM1wtRZeiz^66D0S5}4GAhHuncB2H=Yl?{hV&6vQRJ;8k(m9CEGT-h*BBJo$8LWvC z2Q1`iFrW4lbfa)jkhf|jYzneeINS(S%}z9;p9T!x@|z^kTIrW5bEbVT2RT2%c+mvD zp+;0AZw#a~jBJb?G{lLfH5Fh=O1ygK!1c0jCq`b4ZMb^=#6Y+;Ru#QUUVZTyhHA_b za4JYC{GfzwI&Z>x$=8X^4{)3HnT-iMQvm^=`~Z2<*RivGEWqck@j7CFXvT96QjzH4 zg2PA5lmi7yN=I{8Qs)}}_Au_ZxpOM7@ETARH%<#=TDIV3fDdBrx!s`5R-ig1>-hx! zsJZ{k@KHLlsNQ zB0K*!Jo)6`0N0XXp(Q{pP~{^Q+`geK91kC4RL|h}#0oiEH|dxc{&a9k z6>?y#$Y+VcKMn5^AThdU(JZG;-`Y}$-4CWen{NXV(xNMRmkWNhnkTh1iG?|x1>85|MqcL z_?tU-2}896_a$ZGnwG8NZg>D2Ky$MQ13l$+ePY*|Ki^(ncP33$?1Iy+|Lt+$Zg5oZ zsFRXh8g%m)<|?$QbNu~Cke`q@44;cknKj`E(nvak#8kk@BcZN`^QMew$^5P~C7BN4 z-{zgCJmv~qHl5qPLs8;(f4UZh>vf3n4ph`is9PImq6|gu>`=HSi)>=pX>O2~jt%~e zg1}Wu_N}Kq-_1(sQ6-VkEC{B|gS@&%xQv+HUKNsO6{(YgkO(a|yj6JLLVCOW8G@q( zGq&Fq^BW=!$r9*L2;~s`3fnC{lzS~{ffcaR4YO+wBj>RYXSN!=sh1Tud%=mig z=WlT&`gOFl9_UtBxpJ*a*4=l=%A8ZitGvl>61DS!Ib6_%Vib6?lI`*H%n#)yUzxhRpTff58 zhUDay5;sU1AwTsJFd!ecv4m=x-~j*ywQ&jj=hF)zLxKaf6;}7LMIHm2=xw>Ag?wC= zrIJ@q;a(J)zPKaOU09kNBcYMVQ5x>X{!pm--kW9+vye)+71P{~*8Vo-etYuoWH#l?v?k#uUdhr8hcRDW4lp%sE|Y3lvy-YM>mn636)xt_`ey z)zmdML2aCy9Qo~oB`R=GK|!mboAUvg#5kI_4V~e(u=t0|a*~8f6KrXPcio!z;|V2% zg7#6AB=K6OW}~rqd;*AZf~-jBHk!Fx-%LWgMpP92-ooyU++|FkRq`STTnQyfH+V(j zB{zox>Oy6vOdS*$DHzb2NWF>$PiOInXdPtYJ`BJ_Ym5+cqol>#@66vnUPQER^yx)) zwz*Kc(PT6JbvV^GrORV`X@k&b}Iha8gtXk6(#cSsHjmhM)tt6-6y(e^*I! z(>?H@0N0?)EGXP7ghNUTm6yd7Z@EFMaFrl+cLnpx4i1{jrCkWj6TEzpaOJ6vGNY*R zmflpjY#lgAg#(6jmI2*Lob6L}VReP|M}e7_7t_zE4Zo(jW#27@w_bGmkTq(LvzqTc z=##H3cy%;Qk2s!RhM_)o^pV2Z7L0td7Q2*EDCPq~ovelTnAy+bSGqd>sJL5GMY>NktS9>3CW_m?5|&Q+Nx z=@D{K745ke{r2Grh6c6GlKLa zW;;1Am!vt*$O^=e_KEPD##Iz_ca89B6t79@D4KXPgeh5=xD_%hZ`~C<6^tHDd;($Z zKvwtz!Og>jB2i8#Yd);e&ANq4@59BKINcT&27m$qV*rdpY~MiaE1}&3O`ec*Bf||j zu42$lp6^z)t^(MfKoalO(pC>?ZpemgBIk8TUM5na)|&O@lq_~4VGH%M4hiO#N1h&9yzdW43P(JfsYf&C6(FW#9-fl0?mTN&k-c+O&GOv6X6 zD2>eXw7TD4b~|JK_xE9p!XkQFe*)cx!lw(RdOwo2_H}P&ab}Kee=2b$xg%ON!HpuM zXqn!I=sL!!YLTD((YISsN-riBhZ9hdpYSNh%?jWBUvzHODh34%~aaR%Kfk`>QD1dt=jp;gj zE=G!?;z+?$#F^CyDwQH38gUm_viSa;g;GPgX{DWMiPGWn^5qBFW6~=T-8b$=C3B`k zAEroGqr_|F@0s$(9Gq1!CyLH}p_Iv`Vym{_nO$r6vu6F&O6nr?eA1rQlz+!b0%zPk!8IV~A2QwTm@5F+nf%RDr-j#$ z>G<<2v{GKZyg-+|yM;z+ENbNUYWRibQnKX}i;CVrHxyNXOb>vcni0rj#GP6V|9Xy* zks@dy9Q_%k48q-6%45C|pft{CbN+cAuI`89B&FYHf=WqJh+wuGf_gFInv3a9ym;(* z>zJ1cDa5UXO=w?}kgEyYHr(x*j*QfF$7e6BV7yE>ITe+zkF4;X!v$^=G*2!8nYihs zu&lEW^{eu6JRYW3Ur`!*V<<&6y@o2An|g5Vv?{)Tn)K(Chqm+i6n%{0%5^3lZ5sUbsnwSk?x`4-F(TD^dLD{LWyI@)L7-t zT2d)wPiKQhk`hWfkeb+pXU|!za{&$p?yjb;P!0njxHV_W=_4EL5EocjEg1CQdzN&a zF63a~E@=kth8$Cw4=fz#a2t}EPvPg}9~<^j;i2o^M}GUYaVjcLI*x4pevya+LGfe@ zp51)ue8>L5Cz(|+GuLP_oLS3(DN|p`UPt!3PwkRW9&$<- zhvTm2Q$dCeo4g@EkMxh%{U6_U-mZ8+jWg)Bl#t$?`t^}gmTG)`8_8@wIn!Q{wkT`+ zM=9PDz8%nx2cQR#rS4CxGVhcK={X@vqG}N!WnM%kUcSS~SgGkI*sreQL~zTK32B<@ zM)l2wo~@KAU1YK$jNVA?5qXCS7xh2H5;uD@k6|si{bH8PCsA7W%_cZA26Y69=hP}w z2BTl^I@Y3B`^abD6XB?`=*IIAbn6H43I zgBvk>17|@BTB7S(AbR zRJ!&=QI6B|ZG(wadVA5_Qv&ra}QQLXIQ&U2MowA?j|QX&Oa^ zudM|$+B`r=cu^=MIm?^z89~=Pe`|J;U!*8wztDGsHY`CyCy_+IY$cNnSSI{ z?5Q+W#kKWDV!w8`kyKQNu>p{?1}1{FdjoGmSE%kba^RslH6E zar0bK}l+yuLwPQSOl2Ajd%H2h0yR_AwH z&MFZ1ru_Fz$v-VbEG9<&5ekOvwz)zX1LDmZ1^Li9-FRYil{}uwePJ2zL&CE&=AR$0 zJ0rkjCsryusw6Z}aIR8HU;*;?BWHn-INCyv5Tx>WeJr_( z8y1fi$Mqm(i--7Rf96>;FPDNjbqJSRBPY?M3p*kCGC^WA>W9W)V#^cmAC%RPPk?Gs z+c2-Rqz7(;!W6O^3uch`<9f`sPQi3PqJy*4Luv}(Zg4MADG6gLB^UNR0(kdN$)CnYDZD?8k8gd*ajmc0pF**s?h828gcO zBde0uq#gBut^&kyE`cCCr#JbWxth?-RwA%%;BG|CMplLz1@wJ3tCZ;YT>3&3`a21S zA}DQGSL z%#?O?*YB#p2U>V=5HE5G!AbpeTmiY{DpYyl3G(Xhk&DK&-x!l4YNE?Or7u~7sRT*R zbPdzs?+}&5-99g5QkHTuFlk9H1GAifx4jV`3f#$Cpa#i;U(idg?5BxLJDTOyKQtp= zcrV+CSn8Ms-pAII*vxa)-FX%~WXuI`-0;(2OV*^DaM4c&32`U#8_K=*l+0C7X!A$)Bo zqkcrUv3$2}t_&s2s)ClqaJ9TMj{f(%7S39hCkAW7e97f>NrJmQt0=7X7zgW#WAPNV z=;!P1k8gW^Hg_0w>$mpb2TSo{Z2+G_Ut1iZku{;#DJl~#B~=9sE|^w*&y64DN1syaILCB@@gEK&+sjG_p~Jdx}+>5Rd5fRvX%RX_d(pNO5&Ins+)8!;%& zijX*o%NfbCa?ldfoVAL?U^$g(YR5h)?3}K8ub){tUbu;p$k(g76^T8fuxgh?->A`< zBF;<`Szj49WzY?ABbo-iEqBu!+=eLAPa3%{L9!=KkL&4_+C~W^%d@jWT3W@@#tt&2 zuc*ru$(J3uAblK^pu6JKA|M@cQ}hp>xLXCIeAg?Za#9)QccVszyNx^4qS^WH?ifB) zeMLuvxLewkJW1j*A7h-<5A%6doaC3C`SbhU7{L+;K)3!+zmHv1YvTKY$R}E_V&H}? zt#Y#hW;Z#(1_}%u>TiwQOcxNX@EQuQ^OO1^J`jm7HaH%x#O`G$Fv4sPgk_k6O{HS_duukzpRAe~@1C0-Q zlU8huCCsiu2D{Y9_`yg?&`m~`pJ7U@rTM7kwQX74YKooqccAxwk28WgNQ6^8exh6H z6JD1z51v~;vn-Kxs`e`LnQ%rc!03*tAGfmli z_$ThR%{XxDD?`$cdl%(_uDHUbqj-TLjlu6N1i>t306zE`q_nYl(23MGhd~>Pa~li@@UD*yuWaA=`9#~ z*aRDeys09EuV^C5fixYWcrtN?N93!5eNsIE~^?=iD=; zfgH{K68MvmxUZWp2!GAV()*$7`^H&&+!#NJ;~7x~e*g(_x7l$XP44^ad6FaD(`0jl zj`3aQ;y9e|agJO3r3PU&d*W^@<8EbNw))DT>Tr0tTi>Uby1ncqiAU7C z`k-65XuG1ze|2yF^L_tq*Q$VS{jYz0%+J{9RBPV1!7rUjtgFox+-;5C`zmWfql54| z|Hxd3a;$Bv0A_4$`^#)6z1~C%EmJP0=NowaZUv;pYNyleahL! zJW9az35~>X+ceB62AfvniM#Q=Ox?HC8O^FI zgVMtx;c(B{ZP6JSGNG81$v$9&q*rK*Y~;_!`Lrf1oayr#@O1`Sx7R(p-e?GP>!0_7 zJ=%EyKPQAL&lJqdWmw;+--TFSy)wgDw%osO#imPNNUXGG+$(41w2Xky7ervN(C&`Q zUCya{S=J))liDh-r--u5E#5B7dxobVYtF>sO@6?WY_TDMnWF#YNLN+d4JvlUF`DH) zU!EQ@`j61+tg^j|1A3)7mx+qJLWQNd!O@y%2rijs`3&6+ZcAts$fR0i={?jrz?iL ztq}*TxH4E_IG(@nTNrTfW*Q!m&R~%v7v&#$b?^6yfo>5R!8D0~-I>3>?*klyvt+1CfBiBgXA(n|!y0IwR7M^P2%)8!z?J&2=X#5e!6Y|Hkq=RM_E1Xo1a%o`EP&$f2 z=)rA*D1Di+<@?_{@J%Dl{LT09P-wm9w@tZ*6708b<^%-V!tpj4PWN!FV?wPDW+C8WQg zODb6UWiy635CcZ=P-ZrsxLZmfS7kJhh0Mrsx1G6FOW@qiY)!+d`ADPk37He0e7oTx zSGU4rSga2++>K&!Obhd$?=N8{!EtLHxk0ycuMS7*|8u&wN=4{HU&x^^UBzCcdy>e@ z>S0gnJf{Jx(&5x_*kuw*;5-`b0((-`NYJHcx6YaHgwpJSaw3(=6L*`A zr9i&hdXXxvaJs+Za87?WLxP);HUVZX5;d%|B_7jUiH-dE`1V%=X1bXGcOyrjI$yg# zU-xe>Enm8({1KB(nd7y9=`;|pQ~Z0Ymq{I@;mB#o6YA(zeDz5S@HO<2k*~!&#Ro@F zG2Y)B_q*#{4v4CJ3JRr-l=GSJ6mm4{r`Q-^8HF&d%~{+FI*d;gTsNJ3hGdXr2ZwiMpazL5N*W z!TAix7K5~n$IzQAO17axH&jT72kFuKoH;4~)%k-k9PdyOd!vB62?J(Hr;VfT+@*7d zX&f5L6}>0!whVpqVX_f1`??cTd1bKTaC&uErWAqEL~Ho3C#{Jm4XlKkdrCf9GstIy zZp|lw6myZIYxK6?{rP<#W64Kz0o~?_ZsSz&-pd+5H>#p_uo#zQecj|Z-;WA2}ah+A-zCZQz>OB0dEuB@;rbB|b3;b#cv z2C)qieh|YtZ=wgU6GKQN#>V==+3UM*j1xvGI*{`mX#P>8IYqF?!-DLiEJl8ogQF*gQNkzuUew1}QP{xd~&q+iD7FX*TZ0sU-Ep-J*|) zgKl6h4GBqh&a`gd_N{m48)v!=6t3hYj-}@Jp&NS)x_2IzUnrNlq8pZHCBiUhlaPcC zr17&M5U8&oj+NeaiSb%Th%OS;GptTR(qh-5015NrH~PlFUADmERHR>{Mrew0`3Mv} zB7I!s9zYSAMWzt=j1k{}9b*U!ATU#MOWFP!t|~kh9P>KD;jcdiDmP7NbKN#oiFu~v zv+HK3%jzl!HHSYpNTuRkrn!N)7WpM&JZ*U!w)T{)z|v}Bt=a9Pg3Fhqn*jCyv-d6vZsWMNs1|KkD(Up}x&Qy2d!|!m5!Y`^;tK#l zO0LAacTYODC=vufU=feOt68BAcf;?`WSDdLjxb6?`)(v;tF!s@UXZjtW&SCFB)f3t= zi|bR}t~Sau#UI>_&oCkA#xnURxcyLkz8`k~Ov<`FeA+AIyJ(^`+r{90zmER&PgBV} zPswdy&(ck?Xls+Y@0B6whN0X~c_H!~4N%i*zMAWxIyd7XhDJJ5yhz8D|o^$LgqNz{olk~KV6f12KL^h^k; z(f)g)duUr^C#8fSl!A^kepzOnMosyItKov*l(lJ@G7uMvBquODvSPqLm37(8+$s{* z72S>M?XhD2-xGIx1<2#&cg6va=A@OmnHOXMopx4*yFn{;{x<#g8Xc&n9 z5Qb%3)r3@JxRfHJndSJeVu+CD%_N`ovZByqzaFEGkn2z0v+3v8fT zf7;V*>*P0Bot$$XWkHrEKFc*HIcgRCwYFP=G_2Ccy)Rk1Zx!xVNbt`F=t106H05Hs zSfzu?+=fC{awNH~$0?J}FF`SlbS=-kJOR0iiKK*$Nk?tluVz~(0eMs;CrG$?jUms<*9eYS?qAPu?8)|P6*mBxpx5}lAp zO;5D1gu9ha=oeZm4cm09)1SivA*1qQP)1=m`)&%i3T0R4c2LFke$|FuJJ*rF1MJ~k z*6n@&^ZihKv2CKntABz+6XOARx+WU`bSPx)MZ zHtBta5NzbQQhei?_g+TCNNQT{-bEYihMQC@(cLUuk?M?yz}p0qL)Yzlh_GuP>fvb? zSj~%tqv|Ej95c*<>ejFjsDNuA9V!+6<&x0%CIYA|k{I1AUfw#z6k}KtZX1U(;!~|w zD640P4@R-KwaioE#nf#FN`D1m6e}2fOZPthq`(55JxM_90iZ)3-#VL`0U(3jf4cOAAH-p|NPh&e_RCJ z$e$aSj^17}iJFLV9jt;y@%QcMr*;qkmW~ai#b zBvDi*7y~EaHqMUEUMy)!gd6VmIi9Pwg#>{C1I%l_*_tjywgpH1N! zq2M5aO(@*}iMcs5Tp%#F9v1|zponV_!hA#VS4MX_YvIgd=(RD$tjCIR|URy*rn6D`qqo6Gu;9|9cz_8<<9H z$n8~CL9>x-z#ZH7r<}S=)*)mgLZ;tIcQy#pW|rO~@yu+I55`K3kA)ab-$s?QNdcsc zWkB4QRa}k$ocyIUCR2u#YWpzOK{{{FbJ@Y@z4TM(psoy7Hh!tM+*GGq@VbPNUfui@hB0 z&sf{&y|oHhypU!sNXyP9nsV<`kPQm=LnmqFH>1#NHR(a|7X_IGHJ$fK8N1R%P)h`o zg?#?L=#FS}zf`>|tHl_z)wElJ`C?9z$q6YiPArwnu6p3-xgo^eU~1NHNS1L1-SSkY zx#>(y&~4y`lAq@A&yT~m{VvOL!$`jPqF-aYE0a_l@>NRE)9;NLjHnZ@_3d}Q`CfLn zpo=CbR0M|#H_J7LN>(18`-+9j^~z17Nyyu)*ecf@=7VU67RE*At!fJTkd$m5en;N582HD-kDs?emX=`yJMB7jdDSZHI8N^EB zUw>-XhPfqv?7bI6(m+5`5vzfj%$YNNI6sG38s(3}!w##0i+}T|W1} znkeFmG$uM5eOb_ra_1oHMpLIg>%qi~8*}ayk9-|BurYK2?zZl6`$A9^r_$mV0wzp$ z#;(i(&iSNj!;<4}F_Ui~y9c)#8mm5K7aFNwraIbRZVb0||C5}WDyH7596c3=K_ z-~IKz&szpb&}}l6kMUeGw#xTP8w3d27_JicMr+5pSZO-s_kdZ-3U}BI*)hSol^xi% z|D%O&ku$bY^iX_+GRQpwX-|dc%)=ULU|=kd>{yiEj*{8UXP0YkX|bj!qQQ4r&4%i_ zZcOh%<65L%5$jAAmmktRu1u`Jw zD`;zO<|lVAU`r`}rA%F2c~mWF-8z9KfF)!k!`+m!%I%5>sJ^>=lfx98rXO*Wex@&W zWOj6@h`tyuK_>Ng>pyS1UA=Xm&&`BmIU&B!9;*ot0d43j;%K6eyMu1vxaf-Q2J{xK_QvlQLjcVq^{uO%2qyk=OB{e;(p{xoTF@jiESVCuA`xZ_V9`)yrg$@bT7(r{qAhW2yO$zyRFp!sC?Du3E=d6% z$T`_T41ye4+z>aTM1LOrE32xr;f@~YPCE^|4n?-|S$Sv9(QPoB2Qhq`{PS0*YrD7` zGx&l8FZrrrZo>q0%cy*k3b))h+dKEqkGD+TbP{x%AA#Nvzs4EpCMw2UD3hqF;#6S>!`Hl2SPQjDMBt049+DAf$fK8tE2 z!;H|eqYQ1VSW_irh<_lC=ywz72fs>W%Q8L!=Gu}^pI-S=*!xfTR<;eMHo zOe~`u?-ovg@{iWUev*+!fshqA-_sCsBJWZOGUJII?3j;ngSDJ44c{#QGp`$WLtkkz zmOE2*-s~I}oL|szb>NFAdzc!rO&czXBsh}bC11FUfHgYmMdOJ)Kc50sbRI6DZNGwexaij69|*mULH39cU?#pSqN4@<0P z)K_81AZ~YKr7dWp#@)!;tN8;}eXYePb%q1>iKw3@L(`U{{z`0w0cq3oq`iYG-j#>6DvrNSu4b!ixiKaN1us^XD%3Rc9~z_EqRv3+MOz= zOH~>L{0gT+Rn&(mN1%=QdDzQ ze0I#*;bgYA5KY_;-Os$W(9Q6R8oJI!$Oc12$KJcQJ^uNAaEKqkH_$D(eWHLxf8|ST z+@T%}!b%3HU-zqu1bW=sD5NfK0@x?9y#9 zn=bFq!yR^RfNHHZlVXOs5gAe)(#IsG}9`#%U8u zk6LGCgsx8oBjeiL;Ae=~apMlq4vM7gGmjopKzUH}SUdL{(Z+db!D`a}z44zwNb_!S zZDCJLX29B?&yr12xvNym?fk9m>p%br$ft^V8r7M#qI{x*th@lpQu?ls&v7YUIWsxv zb}lG9N@hyRjAcL2$LKTf%3>7ejXDa(Pbmb81Q!o=xEoYsFHAD9Wr?JWFlSn`vI-Qn zKMHrX=@_?G+QS}qTW6xZK9p|zC>n)2)B)aHd4BjOmuc)^s;Ry&4T0!^3jdiGTYEn+&Ui!f7}24e!#qED()P3 z1>GL!R%(yVMQ6`;5~CUULPy1ZMNHO$Wl`iBw}oFZr0W@nLQyP{pO7g`>|hc0Li1Dw zyM|!I4GqkDeu%(y7E}Z^kC_t1ON?odn)#b$U6=_)LxIIuWfr)4*cC9BS@%UzqPa!V~d%A80cAzT^I zE$-^MPA8lacWc(;X6yd{bQ4pot{u;*!&ys*AS>+fE z3mSoGIrQ^kEN3Fkh^W8Xs+U=06?T;OQ9aqwN}p z%1U=$ksc0hp6T74ln2RitTj59RHHV|>UGJd(Lay=I8I&y*>+#OX_OE)7&1JT=Pw3L z;Zkwd2+S0d^6aQ=&ozHFwY`(d`FbnsJT>mtwH5D{TzAqYK7m2i*Cbi?HPPC8{!k;2jhyT2CoL&2tkeMUurZ-HIQx-!x zD4WwFto)dd%?$cAW-SVoE09s))hNMjNT5MMw)`>NI;P{X25WnB(w*nu=xl#(sXHFe}5bio!mp^JZLf^@73qQ#pqC)6ZjRqvud zM)t>M$Cbu2-aOpaG}|=T6-+YKEI>DGBGoi$FefO>Ebbg`P0%c>3Z8jT>F6&>5^b1( zF5`b5N1twpAV_$+<8FD-4Thi_@l6G?0d;s76{r?>dmig~Bb9{u6!AMh-Uw9YVk|CX z;$d!1g2QbHck2(g$$^5_^fG{no8WFR!=cPr2+5`{Z}aA)(wUMG4mAwR{t3}BykuMw z)|j*;JaVsq>aAqKk{iU`p7qO^Cg%N)hLB0*VpD~#=-qQ9nh4FgtlME1690TDn2%?f zsF;t>d#D>MShua|dfNl4XEjszpiVE6guOJ&%@|`Ri`hY7HiN4Izzy}6C=#T~JQFV& zRXQoCn?=ZJSj|ap4kaRc(Vj2bNLf*)#f?JBD2IVKS?Ur@ZZZ1Uq->PUN=pf14@)_r z+qem%tbf^dAGOdY#RLd-+qDf_2uyUyavP#RiCK*~C?Zr>GajV9FP^~+#O-;;e8uOZ z5HKwjeLd+;=Ih6CJdTvR(?m9WI?<&tUk8KP%(tDLiC)DiMAeyC5CkO|n)sC#^bRsO z=LeQz9x;Mk)S6jOQL}KjZg3lAXZfOarjVfO+I!phHH$7gm!G`tcX@26=X#?_Sc@^d z7n_ILZU@!tZ9`bv%pivL5S~212Hl`oOD6mri;``R&-b_ef8Gya{M`d|OWXU;sh#Z` z@A^w#%Sutp!;lZ4HXRXIFpYtJBWB`)14q`U(zap4TmrhJ@po9GjpdE3E^0G zu}ig9(d3%)(n-3N*Z@zN4y06lKZKzUlPQA>dNI#w6K!(nswXmOi(b@j2*ub3>@z!C z*8{N-JbUY<4|vo%5OQ*GT!Klvlr~%$cbfu=s8rQqm|IT879+36OS5~L+yld`!{oUe z>E|(wY$Ngge*#bg#RR)nL9NO_s`)~~pdkc-0cA^E`l1-)@iv|^HEJgs7DZ3O#XSEl z?3i&s(?$-@T82W4ms(O4tCOt!n#0b9)6KfAVMxu0rg`XOPjwhFRVh1nnSDlWc9%h% z)p56YFB`P;Sdu9yK`Gg6EUNkBHu!B%SBYM zOVO5r)UL#5{A$W=1w#uCxguV+qtr3LI>5-1Gq5W&~j!q@>YBgK1WK{TZR2jQ3PO z?JL|CJrbXFEaj6t*{C0*KaPG{{0oc&xDc3M$Qwm8bR8|#6Th~mzOMpuh(Hiz zUU4JqEt9qn3ZBvJ5sP=`Np-M!n!45IxZ8@&n@mJcbm!HUsxXWHGGig!e1`qJd|TyA z$cRNa4gNe)G+)y9}0@5Ek4su8d3qyt|emCr~0xl%?AuW-}gTb z`^69Epc|FUGXLx7JJ4EY@e5n8M|bljJ{y^5G}ukphfTMnXzN@mo5GwbNp;agD0X8= zw;(_p@tM4L=7&!XsRYDW3jHDt%OM2ExAs)MRkEk` zHCCrEw;GB~N=?#3uuxVTO!ldT@!NrJjqZ~2E`H&q6^O{nM>F)b(jK7;Y2So3o+t9e znxsfZj-&q^eR1dvQzw~?7@*tS4Mv@!$k)bG9Uhdxi83{2S<|fq5bg!UGS{=DSJGKK zij*C#l22n&L%@aKZL&r8pLjRfYi!D0Sx}XRZQ*Vwcj2&e<}``hmC_rv0hxPxvi;(71?_c?B)EI??_ zNDa@87Slk#C?f3y&l!qQ(}fJ9Z)U~(K+m=%;98*FB`i8*KqjP; zL>q{m80S_7q^ZO-6bp`K@-`5x}^`J1tW!sn_HtzsnywS>vdDRiFTp~ zt9n=YB^6W!zrK4flI?fc#O9?NJQR;}Dupnq3lY9>V|j3S6&nyn`$246T5r&en?>H&M0pf% zWlL$GBmn(lH{MW(^yFPG^xv5r@P>1mxwETgeb3E=z{{s%L9ur9qc|h+??i%Q8Vu&J z07XoE5tYKtAI(U+D25JQyYmEX-m1&YC5dl2!|krMy-&MlfAiLF3|hMITi z$dECAEH_i~(fF*aWd)_9DxzBHHh*@sz=8@42ddm8VIFjR`_`>`k~KMrtZ=uUM`qRB zO65^ZLcY~hg{aDFW3=P2cibvMq$zuC!Xi5qVDEf@kfQC@qJ{{qA~hCS%uHUzdcr$Q zO5+cV%l{kbMpYY=YJ><8?dQqa|M~v*ZNFQvr3iHUIL1M@^#C9Y+Dcr5Q7xyYF32{I z_Gx7|8SaK;1-I1WM4UZnP~WW?&%$&-QNHf9i9#GPMA;;m?n@%N31Zl(ixEn7i8BE_ z3$<23JGFyy(IXMRW{&1ZRb4i8*cEicKWzPokMMb z4KgZiMvdfowaxy^{A6x{od_~ebZd#9qd7rYlQ;H4vq?GMxP`miG&r(`cVb^e(c{M-0+@wuQX`V%#-E)nVCYs8&E_ijHR`*;o_5MX zSE=t7HL^R$yE$Ozuqv-|-*WDs;EMedTH!=BDb=VX7}-nOb}Brom*~#@G}4JT7O#eu zSHJDuKi_9%-B^)slOU{dq-igO?I5=rC<)muH<<)Zl z#Gl6v#O17Se>qYcL_- z1wVQgnnreAbcNd{ECl2xh-e?<`xhUh|2TSLZC2c2LHP{f2MR_TF30|&z8{ckn*gz{ zQ}$FRxiVthK~ZR1ka%jdy&#tTkV+WhEaD^@E+t}raJS7mYh+C?rG0gyDsiTo4!JbI z-53sjtXo2pip{mVbEPV)V`($ct!5#p6mDuFZ&3|^l_w?m+(5T#l{vj6LcL{pnaa+c z%eqbFD+#(KYM*#dVFOe2COE2C0##Fmp(WMJOwYVU(Y0aXxSTsGI4q4HQq2dGD+FK1 z=-~S*jDthm7nK@ti)045CJM2n88r)DYzkyr1{PDEI#l7zghir^ha|xu4$!9FeQy1t ziZZfEp%%k!IpIN|@(>WGajS`8r9J`ccpSw8=ZTn>2O~`@MkeFJkYMfYEoq%=rF!D@ct({7s z8#?umEDn7y(8kj;3&ag8U{z>9gn{dIzue(RDlRY5OFdGGsucVIsE~|0**d4K!DxFd z5I_#Y?24dU`xrsec3$45Wg7BoH`}y6XV49ULlsr=G>M;F3X8R!2KoE_@P61OO|H_N znkV}A=&5tnVMHqDGuc{S>14>l6R-Bcz}Bn~j1YD*4J||P6cH9Oqit?b+lYvshzxk| ziwatky^H;czqT+gaaq|)So@zvBdlO+BYMwX&>=bS2NE|L)ze0;#w<((tr|Ey>$`;n zBZHj&&L&bfccI=O6^;Zp*Ufo^(%;qIy@6wZSnVI0Zg|hq5F$t>Vrx^bu-54)8^=i0 z02nt>cpk^+ng$<7|9K1$KGGR;9OX!Ht2S{K6b(#@8VO}~Me!T3MT57)CEP9FT8FF{ zcxif5f|_8NZ8^=>qRF1)yuco-qIN1|L%3U$21XQcxe>jf-$=6U60812^!vl!*%xkY ziLPH~l$5Do->4)}HQ$zkr{h&JnkEz>hHkz->Nij+QL* z*`mjhKF2ZDseyk0FtQtRjY*!WuME0H?sy}-a*qLAqYQnK5EXqDNa%#H1?Xn}0BHq9 z+;Wu=Pk{!oy3AfJrO6j4ik>l6)sUacxZBHy76gH~=nIIXXH|@MgGpNu3EFj-c1!4v zMNv&|=2VZzM)5;e9+oz#sT!nx8s~#o)ro`<1732qN6VAoj6pPZENedBcJA+Qhus5o zBa$5%H2nG;r=@SX%_VB%%(TCy+P9L4P2J}P-|pDw&-JvdbAU2xHPWEHqCnYmgJGAB z0@0{M1-_3F`BPO{F#Y}bvY8x`mat0HsZUt|280PND7QQTU@L3ld`oPh>LA7Ta}m#> z+-oGLwt&wH*yE&LS(I|A+}C9)EaKbLI7_y*XP1qCEHsodjCbpmXZnIISdB%UQaKCW z`xNlP+ZUXk|M>LdvujVL)@w#1V0y506blsk6FvM3s@*KU{m6d~b-3I0dZLlRL_@T% z?Z7I@6C)*A?D+KGPxsBaftxZ%ucDEXbH&{2ej$xZjkm>MhFPk@>UKKyi-}!!t_S4M ze5;z_Zae4lqxkM=()_icau71ob_`xc-5dBbHQB8bQ#kJQ7Uw!B8G>#0**o{2?{BxV zZZsT8%%tZufsqBeRVSZStAV-*ELi1v(S|DuLBFgg&*NUM32Hr&t_G#ObtPL)0lywz zl{Fx0>?sf*#?=-{{E#8q`%ug2^=)T}MIdn3g0?efMNmP?ku&6?Kh94vsvsuNh0Ov3 zR!iYONb_$EqZQ{iWNXx6&?*Z%CqxDAOcy|0hu`Au%@`O`A%_(W8>}KAli-RvW|oYj zuW?&WV}6b&`*3{JRl*d@k#sg#tOgZfZqLUPVQ!S)KQzP; z7VF?F!)J>$EaD80ubtAmpt`1ptD*#p;DQ#oTkp_tG<2brv!is65<^=vy>~9!Eur0p zS>tYqMy||9@hf@S{qjt^0Li5G+S!#)ZPA<%bW7BIqDdn`T>q2Ay#N&x$KT)XW!+Su z+sUZ<7`@6$VUrzEP>8p@b^w(Ec3J5I;hLbO$HO9Y5C_jbe(!Ce986h9^01T;NYoDF zWTzHw$5N4hL3#5lLFS;FX4BE8dWy=nqDX~qKqo>)iz(=~z>Avdj30;n-|v^QZYt32 zIMTnLev$bLRm>Xal1#=a<_SLP+<;h;%&R7}FjkLy`wx!v&31`o(i_|1L`7moYzxbd zO_ki|RHWl!EAtdmk$iAj_=>qD`)MFcQ&Dh89qv|jfS|4jSA#nLakpL%yCB-sVGD|d zLgjuhqjqhZD~!L3VsaT+D?~LTD04H(KR>~~BH5RY2VxCKuVUsCWzRyQ>Z3^Bh=5PL zLRBO@ftYRW2E{BHvZg9MxqrXInstpzbLDf$$$=R`nf%$)QRG%F;Y1}Pl&nNX*Wg3S zX^g!?=?zpweE2g@$Duekcwbx%rPO-|Epc$}@At!he;tpWj)UJ1GFCnD_OGM=ws(NC zt1zN_R0lbtnu415hfJ36ocdNIY2reZ8!|vvf)~?5&P4;3q9)O-PcXn$AF{osxV2YF zyDWb|qIDueBRJXJiG)PU{mQL4epq9WQLK`+@<$kWuq61pX)wq+V-?~jZe4K96&qz@ z$GU$aa(m{euc8S~QnzG!IN{V%7n>O(BY4e3$o{---H&yS9H zrHP{9WY+AwxGR`XhLkP7(`d-KOBvn%-R_j6G+txavK)3ox}58rK%6jVchQ^beY0W1!pWH*U|3#H9GZE5T91vXoO&#mIbwUZ6_cP_tgG!H+r6bc5fk}A( z1uLk&Yr@6PKi5C2bcH)`8EY-48SSzt#pr1Za#si*u4SD%Jp<#tV>UfB$h=3P9+Pv$ z0T7mN>*+h~aR%Mq_UDO?^4j$$L+gLX(OU$ww_RC6ZL3=2~iYFwK}xo_EOOO@P10Xw6`-t1z9JBTP2h%GlzBWTnyI z5*svWJcihQMK&&ydxL~bBHub{_e257nA9bhiI|h;%bXU8T~chgI-~8D+FN$JtHj8WUwxQzjXg0 zE1r2xl>YSgpWhBY4m-M3;vo4h{Cgbrs9SO~R%*SjHs}%JM_6J0T*I~Xt8cV)6WA@) z$&=>c1rTchsOCq{um(Az6Hk$q`&ftqCNZoQY8@5vH`=C9nHx70jS)Z;&p>JfB@!TR zb1{?wD1nrsW<%73UxZmPip0&4D7yTk&6}mo6{J%lAV`#4kOn7mf*_%5hGp27ER3hE z0o{tC*Zc^3ay3cJIU^-qW6%j&T%V4bBQLsbx*O!_`1Jv>^8;P(ynQyFG0jPQgn5ArJNj<)JJt?O}ITx4GTzUYTK6#R)g|!2m9U0&?qYx z+wz}_JA67}{JHcA@#%9qmX4RkSz!$&WJ-Oe{tHG2DpK#s^*o2LNgz~QT51s>7DUrJ zCER#7R?Zt(g2ib-OIpkib-Kb0=?h8J9ayw^+%35lG+9f{8GpI-CiXNM!N+_Hms)L2 z*2LdOyCtal(5-?HrM}N6mM#&DP2z6GxuuHrqB$qFY_gZ<`~@rk+K4wXH+N3r`JeA^ zI|mKuHVhxfTm#dLGj5DfdiHsRV@<1@5=tE(TqoNat z!m?ooiChZ06#!ILf`|%5;Xd#>Ey8HDfG-873@0Es$3M;~`+$wz@DSx-h z2i=f5vlu*z*n1M?zMe@FdfsQ`Wko6c_>y=reC^6DWpi;geHCgwNzQCJeHBpDLxQu~ zWn^2OL`v3JRab~0eI?WyccT(>ET)0n^Rx(licN!RRfeNprd8Ru7`P>OQM--%ie~d+xoJM=sfZFx5L|hH+rw2{WzX`DGI zIyRc~*`mJNEf=#c!ZWYI+Dk`j7UscjFnJa%E$e4>dnpgPoxc7Y{inDtbzvIFNo+yx zK@micB5%c_QpwEgfpTv+1#QD%G#GhY(sNLfL95cD+-4N4#R$!xNacow^Tj8DD_+~0 z6mO$fiQz`4*gY!MZVP>IicJIcThq$UAUlGThdS&W>gnFo%M=RLE?Xp@XjL4_+M*@( zAnoe-#@<8SLa+zWjg#>>z6NXe@>O*+R915VBFet@}xvsiN{k z8+#sF^0K25Qa z&|QhW&?gTm=64c{xK^@fGemft1RTXPfbfXRr6v@nq?D9aO}c3eU)^S3 zn=@#1Mar}y3#YW{z1Q`!aIM`taG-1`X!`v1>%!l#$FVRPsxf@mSZl>EBy%>=e>?dZD6@y8f z)348fK_u_qJNNVZK}&Y}IF2K!r=YyF21i+UJn{M@Qg6`dEZ)E3-p7>K&X+F#LlM8f z=>M*XtbwwLIs5nVelZ}?uy~x&&rM+`fT2UQC^E1rs?;M@PJqRxr9$j8gbf|UFk&Es zGSF70Ha22C8nji2kRr;TkC2Q=mLcPrWDVMJKAd+=#dnB*+s^@=cLV{ug?-bt6f@E< za2rJx6wTAJE}?B>{EoP$b2hVDA@NSVus|VFGftKhApTaw{wtHB4Mht@`6(&d6%v$m zq^7X4+JuP5yCurWN1Wtd* zlTUTnxrVH|8PM%4tAnI}!mEg$gf>beul;&M1r2#xbtc<}!!VB!czx(sCBfvcJsm$k z-?D$%_{7x*F$ZObDCB zmKsafnz!^iCF~zyF;lp066rJ<-rupdbN6}A8=Sccu$o!23Z^dVb^(jTb)+Vcr6<^5 zpfD|@5iw5uxZrXT;Va5D-GJ=8?T#kS%4nf|*d}JafY?*-IYGFVYgrib)o{0*ZAE1Z z%5*v48G58N3JXrEUEJ5yBjzeFome^-fT%s#kgCWJ)M?T#Gq_z zSPrZ$bzV51xUReKR*>}j+y2{Or+fC}=#L|b*H*^?&*OO|<f#gfZCh@q5o$j(10T5+M&V&%q$_@=x zJr0U5DxElhhSR}Fy$~6HTwX~ufmsA>mI1F2GaFd>`1li^w)qeA&88>lTV0PWM390e zjI)LX4Tu$Gvn>m-l^NWYIF$UQL&61XV`wpTtLdUX5#esP18#=9k(7*ul;jjp_Ef4o z%bW|kDLBu@qd;q?6q)jZiX-!hyTvn+b5shK*>jj!y zKtCs0{_(cgjdS$=^C)S;fGTTQRQ;z>tu1QL##?gYN+3j8hg@6(++erda~Gel1~}tL zO|GtL26sbwO!1t#L56_%Mk-bl$LJ)Q`kjJiNQhN~CYWE8A{zM^#P!sLMMnF6R!UUx z3$XTTzUsuu1OaA-Sa#{3&u{XMpH$q;d;cx|4HsET4}mRjS;{_I@99whr>*aKB8+Rko9-6do6_AYjiAk%S;kP0!CB)v8^?r9PYT1(R4g6+xypWYv@SQ{@2L%{=c)XH$z7xx=T;chUXC+5Us z#kYkwNhHpl6`Z`>#UY&zsP#E&liE-|19Q_Nm8CIRlIwf0Z5VeWsXpq5yA9Mb`GH4* zd}%f+JWqz(dxhJw>ChGh8@uEoD$ec9!mGfQ-#pLM)DEJ_jx>q9j97e_HhQ|V$}O&> z74FtRD1N-{4|`2ctS9;yeXhNC&=g7+#}wu}wA2j@d$XB4!MANYS@!b%^1P^JK+#kNRdO}7f{-rR2F}K_ zDw?4z3t(y`q+jN^yl+ppU}HkhLS~aHyBQjSiXD<;-KHLS~9)-nCWCo$8r3e z+Gh9L=oaCtvo^#GwI&&w@=2krK5eKF0NE*(yIGuiD5^LOR_&bXyUp5)^Xjx@zAFTw z*t<1^^cC_BfpAk=TrbN7Pg_w;_ux zJ-XIX5GIKhyK_LkoTbG_Q8>uq3DFwkZfLdF@B7`4H%0$pcpCmaayp1$5mgBGwnz&M zH({{Q@h~jSzWVa`FCdQf7cbg(5(?)8R<}u=PbI7XLCKh|jb-C-TYUVde}%Y>%0z{v zy8xu#la7QS4JGo)wP=S;SNg2-y@1&NA`9OssmK4|ZokC`x4zCwQJUnLwmZFgrkBRs z1yqAr(nrsg+k7%h$zM7oL`*W4qe7@%0`jO2G()p+H%;PVNfGBGenQ^kWb&ZArq!Ll z+(kcQ;^KUA63=Yz?PYb8Ta}a>^O&0k2)$alx^>@?HlVTFL9%a@O-JLcmfF;!)P6FV zcO%h@?0+Y8U`5=m#ZZ(kF*jr(!I z#jq%CwQb1b1tnqUEJNo6%`u*cm{@$8?^>)CkK<=YL>DTLRn zZIW0!AISo2g2*TG3Qm7}9DSzL2hUTP+&8IHUTrf3@R%4EOF*lQD!b9Lc6y9>H^^nu zLL4YxdrGPQflB_5AJWJhKO1+MH*=QSfNJvPP%FyzC%vpWLoC1qhrNS_N=A96tvQVC zv1A7ER(Qyk%vh@9^FB+VUQ>ycQN7uI2qU-x&~325-KZs5uk-);zTfW@<`R7#=}0V= zA;m$r23!=1PXD)MNe!G%BH(dt_qzdJx z49`jnjE*5tx%_9PS}v_m!ZvR>DVu|4jFif8|Pwtrrv4#7uI4^TNK>U)QF*c1Nu7e*PJc6AJ0#?rkxuZP1onyNPoE8t)WliCn2Dl=Fi&XbJO>El%JUWB?KNOTiI>zWa*Nl*NDyzNof9FU^t zY!azYI@?Qz<-N+w>f9qCCOP_!(t`3r%)vu@rTwGLimaEfd;T+XVZwQm-~= zhE0tsirujw_Giotvxgd={^8F@#5x$3kk*y3nw>;gC7TSGq*t(8pd&HV28D5T@D**t zJVW$!hMp_75jkE3N0gUyJGDrz6)>@Ji^VRZ(-P$a~mdboA zA*|EUc?MkvZvcC68huR7+#T5Mo)O{tic1bCuo>CApm`SQ;@qqgma-|gI4LBee29B( zpaQ1STd(;Qzu;h8Sw9N1nZt5!il3V*05jYT-4s%4JA+(Z1SKRFK^4(3D9(~Y%x>}3 z7lZJk8DyEx!!i|4ca3XEH{8=#CoW#QtqHWp90{(Dy^kLI-beh8(bF{!0C{`iF{RTO zjw`(P(X_Z5u+09A9l0bYO76i)(mP5q<}}kTm>TC3ZBm>dT{sOy3#6eSTu~3dfV=T% z(8hi&+|~s%r%->>R5-P>y*L+?_Y!b!_UJB!gS%&yk}$P6PRp;9XUgNO;yb9*iklA- z?Gd_nZs*WwYp^LsW4;oDOB2Kok!!mzlV@tuMYd)su$l?gNY%_sr;?($etB-vn5=HC z$up()9O8)%yWQXKhb6O}9 zTb}U)An*v6pPj?uu)8@g)EjY=^bCIm=YNpnZX`JvNI#R;K4zzDIdN;DoRiVkY@5*{ z&S4s)Xt|c{;cn=@6j;%NYDSsW?PXSa%2!Y_>8oJQAt(Fp#O*)d5Br_VtPhE2e;vu! znVU_Z5cUArM2FN`lfA$9<&{_Za4{$dY*Fq;CX>jfzy(OUJR%tQ4h9vf!Z0i7fv4Y) z`Yly)BN*#!z~Xq2C+etMnM5$;DAVR+DwD>P~KR3N0*OzC$s|NWo`y%XNyS zpfYc{rm_iZZik>YXu^us>csq>oXp6>-t8_v%R{yFMBHGnf?he^4a{=KuSybwRb7I; zaoC~t)?ib(8#5|CttDu31=Z^4-({>_RF|*Q7HLM0^E(daEQOu z4;O@txalM0VZlmZRPKw z`x)i4;~wJ~Yb5WqA#;4vo}V$FgP{8>$8 zTRUrt#7+PCk{?uQ({JJ2xlYq0iRf1zTM?=Q5xPi!T_*9_|F7)OF7h02vvb=qoXIV2!;Eq7@UguXdxZwwUF6=AzcQK z@fjy@8+ke&{pbhGl07*Q#;h}IdD9ik59HJpiQATj%3m(n$WEE;UW;r~UK&Kvg*ojm ztG3!!%NKFCm|aLzJQ4WYa9ZDfzjLv4oYQ+8^;2SvJ@?na-2k>r{d>1D+Rv+=w+=)q zq6}LLbIUk`OmN{V;cmS=g@R?A8${F&i{0{jHY)qX2KO9cWnNp6H5a)_!+24Mhn@TR ze%1|K&*}abAEQ5xe%s!JDy5V*+AslzZC!;vkRd(Jpc|02vFZqe23w|ys7V+75K@zb z1VZcv0P<^=BZJMyD8+Ri!v#s&yuNdUD8Nz9TGAb%Wg{; zgWXIvi@QNHSNdHJ_X+ztw|B|f)$MS%Vsy{eHX_qMVUbsbK0?!hS$j1wV`L(0PbUaN zhq6;vm`OVGH6!UfHP60!&EJv8hZwF*=60nG&~5`rWaOgv``0alGlN-1J@BsWo* ze7Y%=(3#|#5}8nvOnJ87;}fQx{wB$%{G$6c$`kEck_-dn^O?TaU+O}i45o4PqtB;Z z=o>WIHy-J)BINWs0%rl~dVR{pw+{$R%n08&z4pE!3&*quI( z6N|WydT6lJS5;exnm1FwU1hBWRmkZsph1ZcXzBO2*r#m%Kn79?EZQp*1-AE*neui zM9N*cBmj6ohrcEe8Mz&VEp1jHSv2nspE-F?CyQn01nm&^Ej?Kq&%Ls1>sF8|a3(VQ zN!0ZG^0?c%TSB&4HG&A_p#ko;l@CJzPBNeB7DU?iSZ3`i8j;l;c@-{&3Mg&ssXR~P z`gLDHHxjR4O`*9!%s0?&zr#3-ph%33& z!=@~!R-eB3JdX3Yw#y|#prY|SG@h2^VsCX zst9)?UsT!LJ~x4Wmm|^b@*!h+iqFi_7|VtA@SSojR#FF3YF|U!AfU$Osr>9nZ|bji zY&C4nXtU?cB4UE3$zSMm*lAJ_Z1YT!l+}8lw*|u|2^RW<{AM&gIo?3byXTpTQbJAj zB5tQKRlz*7pF8!;W^|{aAA6nbzz2Ju4!@so`!^@6G)@=LZS+m}2FiV%;T=ownrv~y9Ql5hc=$2Z{bkn%iYUYvb89&li5#|{r z_SP$q^;(OfBqaBLQ|w;$)2M9Oh%Tn1) zBKr6o1MBEsq7!BPK$!qF2CG%HT_U|csp+MlFQ`e=tlGulNsIoPGp?{Ls*udEZ)k5~ zm0;*>%ifa0Iv4eIZntwBV`=dl$KM1J@G^RBSNZ|!=={r7= z_;}~gsz;>38NA&vw_N{(+Xad^(5lG-zXUr!+8%h3`mwpOB+20*Ta~NKpA_wGO2C91 za;@q6_aU!;M3TJ z!qTSA1iPVaDJ?s^2E3n=z;T0eAu}ARW8*Ukk4 zP-_^dzWWQz=3?XaPCGDcguj+kwmT+jr$;N0fQUYi{_{Ad7s%Bpr1I3&J>j7qCIYt2 zyBLTXXMo2o^f>75!NN2nX}q4n6nBFp(k;!biCSx6m6!C9}QQ*>L-=-Ha{UwY9W-0g}PJ(`n}MHWCClC1=~G2WC4aeV+1R7gbR zzII#vOq)dm70GgavkxNS#ny*-os?upsv`xal^pcsQLj~(KDhwhPJ;0FxBVWB{#Eqc zm(kOwk7Q5`lKz+yNFiM^muC>Q^1im2TP69eZ}P4}g3Q?(%f)9SVb0gByH(9R*=VFr zu}}CsxV5>g(&pkEQeiTyFarmJ#0ja|yHa0_n&Y<5QDM>21tBh_iKfx9_SFMVwYR*Rj^g+;iV5?w079 zKt@uwHJ1v9ARWxMG}~9Oys{sc*lr&jtE9cVn8E@Z!rfLi*T7r~Lse%7p=DB1viwG1 zu!wH^F5x=$JClQV`K}au9t+Uzw2GhK_HX$XLIILB`f~$QN_l)nvGG`BwI+}m#FoEV zh0buCYGY0K5Y?^Y5Nx#t%K^EO@X8mx6}J$aMFHu$19ixLaieG6Y?4 z=441)*Hfya%lb#6<0XrfVx^x`k#6%eVX~@PLBc@{qCpW|Z?X)7OW_~q@IyH8k_Q|5#Uy92wsNhuhold=;*6sjFXF_!^+ZgVa zjuZKU@otA5bOc+W55Q(~OOebir34_N&Mk!@BQ|1ZYDebe@Rbz+TTxXwA>0|M749ab z5pNy~(Czg3@wWSGzZ)dy?zviUs(OjI90tp6@58__{@v29X;s}7HMDI)CPqOPJa0oq z3+U-j#{H-6uZKZ2beq0lUYi3GSrTex{o;X# zZh5(AcN0AW1ulR*m}%oa^Af;NbsGe>X=JMRG*Tvs2zAA1( z&FnKxpOB`18^5F6+q4@1m+t!@YtVsZk8!AzOlPfBo2l?|^ykpF%bsT7Ho4xJ=G<$h z>y_sg4C+l~PEl0kc-Nffi5u{Vzkr%TOvtJkQfYvYPK#i-A=QSpxrNPIhr_Vj&E~rZ z5(LB-*{7%WMpg<94eoGARh(^LRsKcI5SA`GYP^$fb8Yq6pPTHW!)i@^PFiG}p_`vu zDCiA0P*?)KltfBJUl~0aQQcvX(?GO(Phq3G&`rsb7w}i^Ky0iL2EbdgVlc3gCC_2!TAqp}@ zKS^jnuTYK);^PGKAglE(HpX$6Ix{I2AczH<;8{ru3>xPC9yj-zFf%f59@gXU`IlG} z)sXTYjMUKYh_KeQ$!Uk^0tiV^6c%DW`8Xaa{Z1MH4$TCg(UbSjtI#v_0V>XPh-bFZ zbiXF5n((9sNsUrMyyts(^E@XZroGBZsz)%ZH$tHxflYpCMVAMaDE1aiQ~3}dogtTq z1=S(DoEV3)Gys$IL=$;Olk^Uf9Q4zGgYw}v z1wK0mKHQdBEmny4;SR)VK(+27r(5@m@Wmj5i1op3)kS{jhtLU z)ao@<=ov`mn!nL7(IX8JC_Vp$Xce{CDyjd4JR3pMv8M7+BL=h^B6^4?sL=wI!`x>d zCfEma+Ua~`8jS0VfmArcv~&?6E>pWC(*E8298g6uFd>f^JAL7zLmkX3L1F98%~bAP(45 zdfGMa?ZUD>ndZ?@$_MUd?#@%{7C2l7&bRe$+BP<^-8+b#n(MRn?u1Pa(PT8GKW5Sc zbfr8~QkF@5r99JfbJ87*2sWrolC}Jx$=2i#d&Fa1M{a7AnFe|=|CXi=|{zQTe#s@@WHLhsf>K8b^hj*p&&iH|GDRb`=(O5r_d zB5>73Jgb_awu9S-VyV@d671?l!?KAKh8QYG6INS%TV2nCa0hB&!-131|*;G^hf0BPbz*0)w$~L#aUiSjGavq?%kg#BnRB z=98*ZqFIx;hEj|OBC$gZ5yQZjjxz4i>|`^;-73eSNHC<3R-JJ-!{u)Hvv=Ut?&q$y zJLpCXx@K()MAS0FvH}CT zap_ISHBZJRUOccaknqg zzji}Zy~J80-Q`+mo;BW_>#Vfb}?-VRUw60+TBY=gomLir$TGjqHqwORE)bPi2MPbUR(Dx zKw?h>2s`Gbt;u9oP&Nk9LotOxTJlC za-b5$V4~5}@i;SsF!2ZksfWnDaSmdz%Rrjn_C@O5*M+%7Ig8}0fQkSVSy$;XqzYh! zo6V|@s6kaJl>xSBUAnHRdz!$`F(5FCy4Yn>L=QdJZNXOYZY?cj_F4I2HPbfqIVVdy zd?Bh5gGZodBi%2iO^v638C5Nlh6>PaShv0#wcP1uO9?{(Xf3LyKsSVAD9_PGv3ovI zrR4qK{(9T<<5*6gk2B~-71bG=ycUlI8OYn3XBmgRU;ft*D8*QJ~(_?s64Z zny89}7D|!hAytt0eeL9L=@=uoXhE?(sxgO`0ksWt05SjfcC9;m(^GLw#6dSoY+(4$ z&~yt8>e@+WimEB2qmU1RR8d(DVw7HN(2Xu>r2c$S7-7I^bQ@P43HxrrsPcuUO8!7q zW7lXAl62f|eMfLaOM4pib-JGQK;D7_>mpjH0V2w5HPG#hLhSfmKGTKnM1c|16LEWZ z{nYW`Mdf7Iz}@KUf_Wz&>I-~=th^DVFlL4uHaRjTgq?Th;$6j z3Q|qA;!9ra+U0*%RRWCnn;_m>hPy$0+{~-<%yUm_AmrS|eP;{DCFsCRIS9=$xfcN6=o*Kpz zAGzH^Dc9p|gyo`X7)Lrr8%W(StjN9)uV@;qC!Ee=RH@*3Lj#*kkxAJ@Y-IQA&`cu9 z#8GnyWcw-z9a1tXbiQ{>y~r&Ri{~IBQaonvR?!hW+>I(vd(=J7+GW_2=Bs{ic5%^F zwmOlSc&m|xphEka>t`2p8cg~wQr3#=E&kKfF_xoY$rBwwH)!0#xD{($tzQ4|wYUaq-vsu`gwXBBu%5Oba>7kiiqlVx{fN!^WyCE;a^hguFbgA(|`99UTuFW^2OG#}mvT&PJ8zlF?rWxvB-qb+Q?ILP1GD0k2zq{sWk^Zoj zpD5Y<$jA5I)92`KHDqG92bZr1?C{x8eZYd&BG66UyDGSxqUg7xFzq7NB8?40cX06Y6MN>UPXjAc4uP?Rc*NIRLg$FL+)ODu3rjBv0BiP>FrbN;VEnBe%<>9>>?lgNy4%ZhlZ**sI!YgQa=|gFS<2&Z z0fZZ8LRCt*pqJSMjFqmCKJZedpxqyS&fquDc#;1FJwN#aL z7o-g%(YhpJF_yRthRx<57!;8i7eyvJ{Woi+k~L<3ds^3#F4>~dHqV!3dtM}Sr0Uju zQ?trA#)t6ry1b#F8z>q&PwZUUm+ZXe`kiRL|@%NI|eVKcH$S4R<$4 zh{C-Vk;q+JU#ZBtCgOHi^&-+y%l2z>kGRbXzTRi=oHA#d8j>2l2g4(tmyf%tXS8!T zj!TB}o_5Z#WY*G(QYfN^xSNH+Cib;MT@J-k2+8(DJ*bL%dP3-q;#|`Wp15z|{EjL> zH|_yIGy=QCm){P%-Ol~@<4C-M>m!jr4sUxvF&j=b7l_;07J)YKz&P%gR-tW7L^*Zw zit_0R#(=pr3vgFiSupx^L^@uo#RB)lGp;OI=S-12OEFUON)h3aP!=!#Nb_qYIpbU$ zITYRa;vgoDfe7Cc)Q;iR211FB8lNWfH`{>Nuq>p}!pWlzEx6shGSSY&h7ytk5oVx9 zPGDp2Hv`?C!Msnuy8%-U+`@$uqDmb9#IU2kf^F&70zSwy&A*spn@U{!wCp{q*> z)qE=9@SX-I0>cm;4aE^Cqq*VCGSQP*9u7gS6TDZl<}Vf{6Lr#egb23KOd4njGQU57$hnduWGCERatVWbg3z zxBc!`o1JqHOdm%MJTku=i8pO~G^v%yWhz?nBAfzf9k*=lPTm7z#To@bm@p zy;{jqPDT7gc{^K8dWWhs(uQywjAWX!wTe){OT-z3;H8eP{RtZip}D+n(k$;VFPJo) zepE-q14Lk2jO`MX0UO%rriVCzHC-h<(Wm#GD8Fg9+HJ{vd_ixUM{G5t&q^$lr9NvQb_jGF~`ZX3oH!NJ}b{mhZ zvqG$3nd}aVDugxT>_iBon4N;!=ZKThZYfL;?TWi8D$>EU&Lbp}8BIV(ey+7Xv%c3^ zkLWfFRF^7dSkYEtbDwB~g1#=_Z;ZPwlvf?h`GDTo?jD8><8D@d2UP4ldO!Ly6^Z82 zZ4d{;k|wYc96E!87@3&jX1Lp|IYWLpebjLpE`e;tnuA`{S^?V-#w9N72DcM*EEhK- zQW2pa;wETsQ&z-eOulVsX!>&TZ6|);?>249(F%i!Q7~xPIhxArJ+-7KQ9jn9n+;hv z3Tm5*TPjPF0(_)xG`G}IG&#}~YM(5{kd@jP7$E0rSrm^VbQpdd_U{MBR{8YnT&P&E z`sVH}><(c(3#!~;|yu2H*yt?W_n;M{zQzRuG_7A>6dGOZh)vm z0*a?YTUJP#!U$`c%l5iQ!$_O+cS|#!cLPo}B|w_ajFtiG*-S#kSl1;(67Pn=;Dt$N z&LrM#;50B!Y!`UiWnspDjyYTYX}%J92HeKc%A_e!jN)sNR-s_~$by#hV$&iQ_ytm7 zsGNcW;1#vug|;)>-aLL6rnYoY!7#eubWYriYM57u%MY&RvQ1=_QnM3;RHfX9l`gYh z32@`h^{}hz9oC05rfjBfILXX2mCiZT4xXBG8d-?1CGJ*nSq5qQCA4B!G8&{R&gJ^} z!|!mK`cR?utlJ-CZMEQUX5aa{FB^T=x83fqw>?Tm-s5w!eUF}jD%Iqlbisb&aAiw( z&MGyG-w2hy9#o6$1g>}GzBXvwrRyUJQXauNHrDlxD^Yd#qHFP=7Gsy zlQeGlu6~c6MrMG<0&XiU8$5lYL`j&f?kjBI%I2Cpv=NaFrG!0UTlJk?I|GTSYRsya ziUhicwpGO_aC_IvU(gA0gO-ufD<@;}m0m!@pM2858_AOOuycTEbApBlQPuFOro|A= z1lq|);rL$Blv~2xda@hT&!8bgqd3G}YyCBln*NLis?(obLAR8Nxp(+^F6+j9 z`O*8MCnnj9*@FF`B*RWbcC}^G9#DEbu4ToV(K{OUkj;20;EH- z09d1W>2)p?B@sBdn;0Id2ZP!jJqyPnc8o6v8$BH_9T0eM$r)}-8)H@Q)HBJH8qh5r z=H}BErA*CSQ>>EmnVr>!by;qZV3nrzH)p|(4U!2g1kGLVCxM#t3XIKsMzN5X(p_WR z$YQ(Ia(9=cECwkRa>cIrnM+rA?W63pk64@fU*9vj{;q>YBj38Qe|C`lJ{W z>bXw43}NFN6JBykqY|305UdyGBi&9K?C0Czjhz>N^z`v8>sHgofK4w{7aB`tH(bLw zsO?T#1@MvTt-1Kanmm*?%GD(@C@1xQsfp-hexvdkXr4?U@lcQ&C|@d8Wr+xD79!41 zHR2JV0A&vgMe*4{%t8~C4Ou(}Eg&k?ocC2L&7`J6c>~<)@D=WHwzHzBYNB4^S~)Jr zI}WLYO+1YDa8N*Rz*HmsX zCaLFYTjt1&swh>UAMOp{u?2BYP%%2%IM6P8o$~!{zaRboK1a@5Pm=X9(p%Up#&$2e za0c3GTo{i--txV$4sk^-$cm?HgJD_)8;E;Af|`4Gl|}3Xl+Y+5-ZH73V#mqeCND^z zQfkNbZV|y$kC?jylqsf(-V314S_LUdDsDCA$N7-1s3cBQrVeYznj0ObKj!4jJp`R@ zHp~tH+9ZOYz!lSAH=3~9(Ua(!{0z4R+7RWw(Rz-}QfxU)>c`*(ulajwMbyy71sej= zMSiKwFdRO8o_Z8}B)*q1W~c1i^WYhkH+WIat%9#FIS&at-%wm0BqdJh9st)|!O^qO zGa~wpy?ED#Vr^I6v%Z@c(k10QsJ;=4Y{8_aDy40Ax~Rg#?&Z^Osai4d27zcMx=`k5 z1#Y4hOwhblP^0Iu)@~KL~@4s)D$#>j%T@7^+qu~kF7k}e9q;oC1<615o3m|$1YiG zyP7W;IMv!_$DBA^rEvL03e8*@;8v^@PnCs@+8?E<{+2dx*0oeXr~xO_BuqL#rCy-F zkE1%X1G){^$ftb^j7;@B*-lZF-ma`^FfBgp&($it)UiypRLEtR0<7hbfZH>}NDI0l zTNrfNiGqO?S_RnN&W6+^DcUWotO#?LJd`F&yojpIm`jBjVP0~BOgUd8M`uIGIoB^G zC~(&pj6VPD*K>>v77x=w%ovC?ARYClTGhqpEc$QW250YF!&o%x%BNB`!>ok@GprKK z-n&E0d7jWC_nwoJwy3mvI+EQ^c0EgGH>R73ceAFj&-<%krahghkSsQ!97H-bvFInLscO@rUzT-NQspU06`tnKsY_q)3>AYZtZAx|}y zB^vZ6)jpq}%K|Z_Qm`m^+@v-M>O$koaM$T?b`F=y0wk5@%(1krVMnpmpiYShnE|pG zqH}nk(pxTjfaQg)QpY_S<zpF!N0}~BNatrc-sZ4THnAfF1A>nAIOih>M=n4yjyI0u7hd9M4i2WvcX|U z3dU7@uJ;~^h&;$JXG8#|8*%7%#hXbjdPdS^BS0YuBTG*EI+@gYe4hqr`r5}IFK3oW z)6guyDjLRykaCb?`Y2r9g|cR*AF^pRv+hZ?NeQ?|MN(cyXmPDa@i4c{WH!;Ow3qjh zWST{+pQXl|jzBtn0^?ca8e)2e_j4NUzc ze_>lUT2-}F>TdsGF=gl%hbFT#83BW&_b+b2Zh}sg;J>x<29m8r(hJZuI)`b+T}T#I zU$PC_q5=lDG*m#-8U-&<Bfkp1#gVeLXIVk{nuajmzCvY& zNcA>6F`sn3dRX1viO-eT@tO$Zvsaj@u^cW{;!SlkDwGKX$K|UcZjI`yG&Xu-?%y0_R zQtae%)>}gyX-Uve1QB>fUE=q{?)!lo$o@I{qu-t`tZKHPjhREr=&WWBO3zu>SwbF@ zxO4&0v{V%)rY~Hor9x4fi;{wYk6E|i#C7i;M5qLeUnqpz8Yq2E3Sf;rfJ*+K{(+F< z?!*I@c*>SlIH%PN;wZp+9U%mG8mh~|4}AoU|ENEANLX4T6m3@(Yl$Z9iU#3oBe@Hu zfw>$Y)q)76(xKt^!gG`eYe72OE0imn5CL>_nt}>BoTy)WJPrx;&SukP>%jt z#ac~0c?sAMhQ(FO)w6nIsSQr5)2K8d0D^tUxq(;iM*y@9b@jZyzy}Pj3gAfVe1^70 zaSz-r+tt%%H7DgfdOv!fdN98kC(Ff?zUOXI#pHjVXxKZw1R@4u`Q$_X=nw5bhK9nL zcip9%`RBqVLEn+M4~T3q@J`s&yv>P;CBo1EbYl$2FDenub$va=t#?%gwR2BaudBsk z%4OmtxLcIvPlB-D{YFL%yUaXkIl_Lj2*TVR0qgZ?vtJc=1D_mn6et6%a!=Jk z?8HVEiU|;8AQ8Ecrw8>yYW$wani^Z7Amr`gl4GrSw{XVbOC>Bvl?tFzLy{+d94QqL zp9&_ORCc4vYdw7flE}<1CkC`>&1o+qV{|KvQm=cCX_A z4(N88&yTm=8?XJyX|!&jTP9_9wX{n$&kAZFC!V!RcVPwAc&OMs#@Q)qXy=G)cSZaFV)> z(Cu;gMm_?%VX*46s(KAwx?vR`{eIZ(cm97Lo-^T((ZB6n z2X_%IMkJ~*IAwE7CJ(@9wb8E9fdFRSfR=Qx>$G_;bV3(BACG!~YzJW7uaQ+bh?tKg ze}joXmg#3F-&PEkVVfwPB9PXlMpejql=-Q2`axMD33&|U53<(HtI9AUX7O&q8i+=HK;Wo9hxwYIyHu4TdUzLLM;ZYd@iB@tf=qGw4 zP=Teve+?o>SgLR8^O;M_(%i^*wnNYs)g(^d^l7fGpG2!L?2r~rv`k@59CIw&9r5nE zw-H|MU}*fTT}Qovuf@BnOWO#MT{4e_d(*KA2%$~9n<}>yO;@vXk!o%VlAseeca3+0 zqJSgSE0yPK!Ab((iqLS#kgq(^IINPM|Ia4qEFy+y$sSy!tXpbAzeYd$hWi51w($gk z*zRvltS4(lVF^@m)y3yEZ^mBNXg?^(B2480Hx(0S+znR)Q6J!@<~K-xFV75Q)c}*t zF8gcCOArjQF(VN#RHFq71mqV;h-D*kQu~rWndY?qf!ns}Vd0*;zs|iN6v5U_e80-# zUqm$ednzc1UzciL0rt0J{!n+6a$zCxCOqj++=k(XMxDv0PXqGhJ8h_r46`hA;Dy7wY0!@>F-{ zT88BzwwqgO-Fj0|X1?>*xnN2eRDqW^y8%Wt9YRo_z&bOE9+{=|dfk3ge%kd|Cem*W zl$(2l8%V+dvkT-)z6M7G_6|ShW!*BLPk*l1vVDy>g!OwN_dU>7V-T<2VpzwShEg#S zBc&%T+-^9fljV9wf)g+X7!DT(%fLMdbqA`6<}fR`hIEu9c*Llu2dsXqR+@6J+UTGw zVHAJhw%7b%t+%4d9MN@Z;O=ZSOeV<&o&vCe#gD zZ#Nu4adX_jXe}&H4oxGOi@-2QiQQ5(I}YlJ$=^?Ry)vE~k;usq7h2+nkY(wBgW2bAsQhrWG$8AC26fC-I<~Hz{q8kaq&X>M>%vDl z{GQIsFe8E`KbuUNO>s@|r*;r(eKdRlJ0fsrMa@YYhU^$fR}(%9##je*3um%JczD}a zqNfe(fS3l5H!Gm}xe&{4qgPTADQ3{^mG&d?TI9x|w?) zWC45S$O94F(^9Xo^$ao?5V-3yNufe|W6+Xn7jxFJUxpWB2(SKU-jPlPK?J&C`9F4>kQ2zN7m>1gK{c(M<$+e(OB339a_&kYB)fy;{86JJW@C^`Sao97t zpi24{ETF76intpNu^dm4$gdf{_6H+urgH0OZV$T6EQfPwTO>*zg6rL>#yGc1Ac}nn zd*TrXy(*K7q)QGjrlMhPQ_wH^<8pc_C0AW!0HmxC8j^hzN{2oTg4|MyJWSiwLm{az zb!Hu^XNrfd($;T+YMIk#-IsLjnAKd%i zao+qnRy7=JR!I%Rye*wfE-1IB1#gTC0FZi#qN@Dprczp~|A!bl6q@19c90?wVAjqZ zGH0YD1On+JF9Qc5=B2f4l0Xwjbe%I)9C<`(%mg#{U)OL$;oiT}hhkaMzwu{Gc0;!^d-n1fsO|09Z3FA7Z~0w16UD-Ba;OE}_T|T3;b17v zlozpSwqOWnZZi)AS;R(p;OuU^ge!vdqqDQ<& zlPjtjsAob6S4lMrDG;L9QqB`{IU2kvn*}J0n56$ddvBuU$gL{@W>BhX`_BCTr_Ooq zDrJmPYc5;>5M*Yxt>br}-7RHu0YD%KAh3E3lc3wx7dnaEl+Sax8qELiYb?(0>%xyP zw92k;VS1orODtrsY~uvDlMbQ70zviz*z-4CeubO+cvVU+^Cj{>V{%^`30qVXE8Ymi z6S@IUQPH?agv%nLk0xX#9%jY-H=$4efo_TQV23x+J{;9JZ&=&Iv|{54&niKo8uklr zdnb0i-+;9P;}!bR&u=L>{Jyf8|I}mjEQI(mdZU*(KU!rDwSSMg1R^!_z0S7L2+;@rCPo?nl zGunjX|9R77m!DLrd$ZlD+p%%HGbAt?dT*2Wg&vEd_rx;aqIS)YtJsU$(3Cjb7J;uj z(U@C|?Ws`2I_EFHExXdE6CfzmO1q)?y1@@UE1h}YId8JRM5KvX4TymM{wItfv zMZ{zCqHq|r!}Fw|gJ|`@-6-JjY>I}c%i& ze$go4p%aO!`?==o@$E4f?XO}$-u*5OGKRn6K;&2(A}zmZo2n9J$6&}%6g{@=Vew{tqKf3Z zU`F{8bW^VEY#Ks1*?Pds$0=W4rtrZ9J+7ge4-z;}bxOtIZSuNl;FAO1 zR$;E{-(PDs6UsS(Zlqj!ack>#{ipcN%Q#h-b_{c(eu-Ef4HbXziOon!LQ^Nl&T%gs zBTfk5w%5N-SI=4?9FjkPdaX=w8mEd?x2eF;Npm%G_JlCdVZ5DC9xXTVmUFXs zej#lmx6gqyE_9G(uCiUb_0UW|ECmaWBQH;|O)FZnJJ`ri$8^XP>!Q|VS>ALzNsouy z(ZDneU|mY3?6^XUtEdIt6tQRlbQ@%#+aTqme)eizha6m?k@kkAh2n!P_KI6-n=Pi% z@OmeLaY$qD#9rP6TMZV*c!Yx`{{P9i8+;z)^a$!+LV59UDoQcU8`Ur*_R#Ma|=1=+L{6(ctCfKYt6eo;K>Q(3= z+`jV(|NAV1ZdVWc_jmbD8#-r1{tzqRfoz+MQv3wL*3X|&+-RizZHXtX&2EEj+A_c2 z;o4z#LycrObayNBwV%j%`oi_fwqVx5&9>&FSX?)uKD!O#w$Vr`oj^SV(gekK8y@9y ze5yZ=lUaI6s#yv} z(vwC??2p6*Rc_q-Et$LMQkA|f)3%fgmU7mVpqu;*TNODJz%XP`m{u`qd={z58DvicT>Kk zo}N|pge2VNmO}{VHS1Yh>a7u-wQ7~-#%7Qkt*hR2K~$E3Qugia0}`B=@Lk`ZW1N9O zuD&oSspx^!Dd&rheC}sUR{d&C|KDGylc4>^lZWFNIIy8}*9Qu|_skzKj+a7-iySw`w`LlT%bvYJNQ=9Q93QmrHM<>X^q8;aMtnRaJi5dhLlX)3DRfh$1f$I5^I%CJ3zMdV z4hYe4KCSCap?m}@R<-aILBgnH>+XdnZ5DJMsX{f~9?%S|v)vPSv-~JP8pL+cK6ErQ z#!NMM^D49e-S%apP&$GEkk~U}N{Xs3&cbiZrAx@Qdq+W+;s5UWkoO}6> zO@k$7d$>3$E}W+W{XKkH9^1E)5j2@H?ZTZ;3`hk`a1}9ZHM}u0BzO(Pj}&z5wT1&Y zgkB>HyNEKvDsPDmijd6m_^BD?DJU@C=)y}Rb_Ctn?xi%CkH`B*4&RqVe>I4TUJ9Yr z)3wl&D+1p0&bxE*^e+1W4J+_7pL>de+i-B9yd2qlH!PFN=qbl6Ng6Wyj@bP_WRg=l zJEtnrAg<SeRaME?AEf0Vt1vudZWj0DPw1t*4nBuzB%3IhNt$#|_xvXq=aB6YA`X3%t>LKF8-)ar zkvr95Q(a+Ox39}|iyjT~4+y#~kcC#ayK4hqN~@t|Eozembbac9$~7rY&GNj*9d#*! zgLv7YsMV#Pz(hpk5jxjx-YdW>I?1s8jZ%XeF@q#D!p|u|1vYHDDJjt zsAo8K&+;WLm&emXm3xKm9wX&Srn%EtnejOiq&X}-;VNApr!Yu1-B4j&8Y`-I545yw zZnJlg>bdlqW{kMw1qEH~+gGn{9?Ta%zwEEi0vy(3+Q-O3jnzI?=)snyv_^j5a+I}{PzY-4} zIgVpF&w+zzaH;Et3C(lXC(7ohcJQ*ofmO2E>;P9C`j!~s`sx|ogWiZJ*W!6Z@}+;A zF9;Kg#;ou-Swtl4&1`H^OScZx=i?7U(2@tWh45)@fyxNQ}e>n_`k^GDr=!8Tz5qIBzj{0v@9W!P|9Cp5EE@FA)DZA<1<-PO?XWt*bd$QaJQ&}?K#$zfioq8oR<9#*>5^fLNm;F z!*keRcudnt7zG5cmVr#>a`v6EOJOOEWjH>{tM2BxR)?x3>KB~!XQQYaZLp41(!Py; ziyFazp545ofTsegq{2r%|N(WV-+v_hM?za<*4rFEg{-E0ns0n*T4)>Y{+I8z z(^x5xywonAfm2Wht5p8YO8YCCN5HZohw+w7V>Mvz#c$6u8p$7~cZP?=FaarOUSE#N zrpJM>&+svgGvYpfz9}LObGnWt&@Hvonp#2 z5W+@{a55VG%v1WWC=@kpjV+Yo!fFb4-}-%D9T~^*^5zYP>de)WI2YR}+dwxozKR1% z;mVVfp6}J^gjd|qM}_f+qr|_Ff^XMFU?vyqDp{vf|Erh+WV~tj z?$BJ(aMu{s?;x2?ZK2YCxLf2K4frgn9!D(UVp1V@XQ-4U%_1p+4OFAc2IGjrsGUl_ zwsamxjmMZO8YpkxLqaB5Rgg@n9qq0s)Nze75WfgY@R{3%1m0`;bkHB*t#;KvPWby* zp}>?AmjW-0VN$XdJV^dAJBXVlaKdWX@^te4`9cs%czr5IDaL9OK0RH3p_`qoNJD$0 z1Ls-Sz+EVE;2Q1N5 zB3eAUfpKPCDR;`Va2_cf*qOGUn~%=oShs(F!tnR!DSzkjAWGwG__D52rIu12XXh~sgjVJ7lOyy|t z&`}g%7y(k_W$=)yAw3w-hBTBEZ$JiLdp2a=fLI5%tw}|CQqfW0gJWZzr4-eRM%Zz+ z!S1R`2l?{Rb$&gHzXA|@ne0Yn(i-= z%}k<;j(31q3cIJ8N;1tYbmdQIs&@jSk@7w#aks7@kCU*W*V22Ps7R&4Z#{GDC}~O1 zjn718r8S${x_yMtQ=ziw8Vr1oI&;O2*}Ap_sFi9kk$bUjEFFt(8`Ys29m|fS6hH)Y zYqYbtof;5^ZRNC?>hl)0Pk=|Up-YQB^i)X^(AN8GKuWozxKiWM$-9*m^W8FrVO~Km zVO;P__})Aog*VJ`w`8Ry?Bnt!O;QbqBQGQM9a+mQ9a4XKeb>k#1!MZc8Lczj(2)wg zKo;~UXpMGr+nwlz3l@b~J=ZJ(ehWgEFEE9^$;u(_qQPj#XM2%;a*# zi1r{C88_*RC4fS->-!me$TtX{mkBAbDD60*&&sT3JRgr*V9LoY(ye&mZp!jh!5A~% z8+>Fe_EKKPQ2@8)Rz`I2VaX}O63rVt%>_z1UiAc~NM_X6mHFw76MI=)`AVBW%?s?{>9V7tP_m>T#4At_#!XEE;(| zs)-d;B(r88SDXCn^v~DjGA7GLC(w;k1GwTkw@z>a6s88U9g6>4(CY?5yFr(lVwB2O zIvPwaa%5PyeX8xhx}>+lC^INghuQgor+34ZT0~SzO2@Dym*!b(nw!8$IRId*uzp#r zD+e2X?w+svh(;@(I+?J&7-u{#@Q@gpqj%iYB`pRyOrd(L1hfpT;MHP2^vK=Q1OYWk5>} z-}rOzp!aFPL)w$Rb&=_&v1Y@qt=nH8r;ieq&DSMKan=Xhg*y*G8bDz>31Hi9v2a)K zmVM@)UX`3$eiu+(R;k=~EY2ZJz?*3h6>wXFNd^;|!=n4&Ybs^#(F4G35Z+(8B5Q-9 z$K|}F4@*0T(p2C+4anB8u8|!}05ggaMZSlf%|eo;?unJo;ncP5jkdYCE|4&Ga2S)V zArRr0`3dPMne*u7)1juBelb5Qq0HlGedECb!TkMdrdMEwJ0U)=d#d&+*=;HkclTPC z6b&n6XgoFdtszv|fyJW^6jdJ{cXO*_;(^f2t16vO!E+eYFCbLB3PwzV!WA(vy!Ho> z)?40|qlmBQx1KE}g=S>;O~xUE5Ta4KiBY^&<- z!`W0W4imAjvL1-$qebk0krbuvj<1G{G~Shw8l>MAzT!6#8w}B$^Psna8HKqG3o?L$gJT zm6uXt92BAA#E=jsxG8T;79!f0gj~{|KC?#s1Kb{n8#Fp`SlrDq!uejr6WYbwTB(0EGcYg zr8KQnA(rXp&_7l=U3Esr>=Osw1}X0LdB)$Lr`14KuIdE3B~UlEG(f^m%+N8YCRcRy zZsJz^U9_zegvS;%!DrH>X;ZFS5GGQ=1z>T(XRv0O^ovK8Ej#G74iV^PPbWJABTcNt z@Y_pyRJ;_z((fdBazcZLn9%vy4~T+KjTj!l%(928-=RDONwK9z-`Nj zr8dDWpIY;{T9-6$9;VJOw~(L>{Rv!qw?%W4;8c|D!X;4}Jah>got*4)a`IA|2v1rR zMARKKMo=Lzg^qhK&yuqoO8V;7O$1*JNFW7rs8H!BvH)pA*6X(}<2aUXZ}jEYNlR)F%)eoY{Nb&`V0v38%S`K&3cRNsR zsk4~92ov}RrMXeNAdJNW%5~+Vg1nOjKzbbh(#xzj+~$11dp|nKrI7>90J;ez;5zkR zpQrN~8Ta(UDcMaKop*Ant;>t|d+2FRSu^e96zIW4rDaF+DWHnBfz>M>h>bm!^xIp>%e1JhyomCVL|#|h9h0BMsvkPyrRWkZ&1kz#GAnK z_iBWmDW26Kcg&3=FBgSl3qKm%`*_A>@AoRb^6O*ZQ(?-)X$gm=UOk(VHg{M%#uOyU z!=#qkdD$~nGq@YmJVn_=5vKNKN^9F@;pk6W)^o(5>ZS@Jszkwp-1dGKbFtr*DimhhDC_b5qPm(*P07tEP@v`Wb0d*ZUf+9-tWc*YNiMkr z-58zeVzMG4%wbV-!O}Tms9$Du81HNV3@O}ADHV@K5NQKGP%T`{S}uARL?#}|f8dvQxfWized_-;K?un_(-xMr`4fOB#URNK)Mj^o5Ux^eW>s^}yh{0a2ynsf}O6~etu6LwLlc{oU6eY@i5)%`bG%IolIP7ZWjRX3LE z`xSRJOU%bP{QAf(zy!Lz8q}Z^jT}-Li%x+tEHUj*YQUpLqAMYU84V-Mu5-w)b^X;3JsEUT~JbtFLqb>&(B)-MuB=@uG zvP|JKO7eRdc^Q3#92rayxv7vAYlg&oiWe7fm>RtD-4MB(KRdemX_SivYa3W5t)1g zQ&vrET+_AjqZkulo|N46e?TWVM^Z zbXqT!S|mC3=C5#AhzA$7|JB z`EF<=Kt4>Q;;;NEb*5a0^jLT&9rO+jUqmHo^0tLDbh_`hYKBKB90#w9KDF;yH6my_ zX+_;&S&O|2ej~?ijb;_x(2Ku+ozAD+I{dznV|FzfUXE3rp3wbLB@Ap=QtFaI2RQG} zno!URf}lBYEKscqgZ7~kOuR9w`>wWt~;5N+~A^!qz0YZs{ z1xk0i(4bb|7m&TiW~hH(IF4_z96Ug`QpD0E)rZu*ArdQcu@3nN(=fs&6|2a64c+QVqcB-b z)shr-Yk;-ExHX6lgmuqF#T*(NTF$%Q3>Cb*5bU5>vA`H`HM^TgZkWDS-Ec8WUF zHU44fW1aVS`9JIP{?Ge_ewd->MQ+9Ka+R5 z)7NFZa6B49w3mEK=AwdZSz_?)@Hq=PGD;pzi+s1VPg#c$biv(lAZA@pfq#k1x^%7S zY~lYRV`mp`7DVL&%+k7{1b6D~YA(&qar-i1eWV}&hPi5k2}E3ET5{Ph`- zPF%WNSjb{W{ZyJC@@Z&8>}H{EUafOcI&=#gEa?$|y^4`c&lJeFk8xjir0PipU?PgK z3DqNUN&abl1%`^52(h+o`>;6oWLJ+%2oplIj!f()x+UyYs2Oa&35J?R%*gb2izk%v za`jn9mY?iWKaJc?35Q`+_O@3kBXsKOZwZWi?vAJ7vu6<ZGvYmco=Xy~1!&sXw5*}2plMT?l_YvMnn<3a8Z-u>_jz8yiC z)7860+EYV}52XXWgYhYF7sSf1R24+>pt2gCbcfA>o5ps89>TS%A(mBBU?U;@aJNEj zsUrtSYcrA9Qcj>7DlG*xW2NOCjK0aXLa8#>Q^4P!;e=!>ig6siFC(qVA?Ss*WyVI= z9#gt`b*pMDzI|T`Rg*Aq(?a{+8~Z+&5EAKb@TW@fn0N%7W6v`Y5gBgto@J86wgnYL znQ6u0Z4V-d#gC0QJMy1fR1DcUCst|Ow!)ySXfo#lH4$QqQKbOl$_5GQGo5lHBWWB)y;`PF&3SD zBiugQB!9DGW$TCFxVW*#PVgE%R0|`sd|{QKI8m3*_F(sXw?zxHFCg{2)Jl?JJmmR7G&KL@gOn{K!pK%%bP9GXKvh*{$VhgxPIH#0QA$4#g(Ks89(C0yJ5xL_reD8Evo*?B;1= z6Ha2aFK99Ss4_>dqc$iTw$2}-raM09NI$+g>NPayG|QU6g3(E1zIuMbTxEk44w+Kh zE@Q5CCNVCrwr(rXZNhX$<8`m(rjQ!}Y;Vue?kQWd17Xa{&yBwNN zYc1f<_-Fb&`{`M zXkos!3N`KQ?!BH?QC?4?Vbx(#Mxjvp+gCq62wsr2IA~=go^VvDb0@OpZe{7nT|*&S zJ^B9A@y?JS1J7FGZkS)Ij&IExr1ajpN5~FE3DDWw1HGB`+zJ z=$a!d2uv16>KF*SndxII=%>>Pbd$x9lkDJNRJ0bT%Dvwx zl*9<|*8FE2h}Zuj%kQc7A`lbDRX#h{i7>BqDXRbdn_LF8$nGjvF&p9_8@Ev1?3bJ| z=5B||T1VM(WMkvrsC~6Xans8_MzDfg9$AcwbbWG2Cnfo5(Ky_*+hW^=@+IqX5g@V? z@84kY|3s;Vb!EUCMJVWr`1`Z%xG$+p(>32+-ZjU5i7nhzHv1GQ-TiE3Wpj6|Uc|Y@ zz!1dudF#nXvvnCe2&jKNOVJ9l;YkX~&~B8(ECswq%hW-L89N@b7G}*{dH>t*%XlMs zX&C-fW50aCsa-x87LC|jELrp`rx2iQ{7A>m^Aizj)6`q8YUV3AsPNhM?#3UjarQ1GZ)fE2Ug8?tX&z-{?|uiAS=Vy)L*MdFgf^0quv1hRXIl6}C+{2@Dn%^%kj zdq3k#sA98C8lQ$|I__dW{+ZZFi>w6s)U`Iw>c~X`Buoia{j`AfL!d4L3#%)lT(kBz1bhKl%Lr7n4(yF^7(Ed zHNfBB7dB?rEUGH6aLiVLvBE34o?z<{I#Arr0mrUlk6q-$@aJRQR zzHN~If4{$5gKk4NKx{wU4eldlxwG>ftUL$neFWm~fnX$Tw0CHe=GGuyuFBcwfhP0% z+>>R-UGxrp72H&}gGC#f+Bx;)Gd;xT=_A%({%-r7QKMUmR{AwNBxs9i4tL+rzWv+h z2Y!7f+uU6){E8;C`|<*KdT1SwZ8V=cJR18zZ(&>_0r*W5=~)CgG7WgHQa&1YJ?Cu3PydHJcolzL(X9ix~Y6M?fuCcY#Yb1oZ1#uS5v-Q z@ptnFtf>+Z{Wo#LM6(o%ndqxq_*OL3PDQU|bUTW>w|bbonqe%oHLK0FMd_&Zx?2iE zu8uUtt03PfUy~>Ve3`gocN+`}u1orqEY!Dc7d+(O_|Mm+&uJ`%TT4hVNtu)I8uIyOx0X=q2<$Mvf|I4&%r?zkH~ zIHS!-P$wb5I=sT#fY>h0;3X=;`Hr-_BbsD;0{;34sng&ZA$+OBm&QhEI|(kDfD|(s zO;_FipiCtWwH$yVc@s6}4YI-&+3SGHSKXor*m1W^&|FV;0Y|tq7E3g!aKg60y*;#) z6c+od^*>9cerK6TkNAOdC5YVIpnqUwO`+J*ry$ZGzt?kMBK~Q5T4f4}iruuCXeEnI z2rl2w`a;!gE{a9p|liM5>RFXKr@E!^>PH<%))76Bn`#8GGf`IjkSGK z=p-}q2gcDWiuTvif&*G1-IxrBg6yzZvh77a`TP6Q6OHQ85UV5ZhM6rkNU9lk`#^wU zS<`T0dT-`dmB(+^J8PO~eMOJvozzQ+=&_EtTMZ{S z2S>u-8X+lNVB&tN9D0+!Wa1(i$I*jOrH;=u^%*&|y10URHr?;Ypsc$t4)jn8xVNVIk1jllE;x^WxQwYNn=7Z$~u?0U0e;wexi2%x@I zz3?)>__a-RjBIgZeebpk+#iFrDmi%VcskWI3z-`TV>2ywScSrEJKwU!8?PFnM!6zbc~k!9Ve z<@e=s4YyA>XT8U1RLjeyKPS4R`@R&Mu|Ibh>7AwSlud8*AQ+=&z8$yDQZ^Skmf$8t zm^Yv3!>`v;Bd*cT^-LOh6|icxF+9Ll6{&hrGpQ9wh~yz0Jfw(~0Ti29O-9>9V*8&E zP60laO5`1|ba25cC3N+YKh6lJ@cS#~Ti!sok724mMQIVC47=o6R?+WjTwy2xsnSD! z1Wo$NwNOMr(9eyv>Q>z_2zN5Jj7Su%lO6I){4zkAk=h+;!A^qOEJcsf_aGaU{b8`0 zTz4a7phKkh1eV`q)yt_G;rs^=9E3SZwvP=mN&`l+eW05QpYb^zuu*?}Y%jB>$eIB=CR7iH4g}(74L><(*cqXI|(V{f! z96P6pqRyj0onw;Irfkw5k}fP$MOMih@6~qwo?5-IGu$>*W)Ns%*$$PEF2tMpk)Fa- z%YBe|fL)%=Sj$YUtT`nj70PX>de1ItZrRD40tO9D^8R`!e-Ubzow5qG`oc_lteQR1 z&^^6ND5y}ZM z5OxR`WK#nsyzeb08YL4hsMlAt*`4Q;3Uq8~jc%2%S?SezCJ`3|2P%oI&Pns&$CU10 zhS2lB&nmf%nWJ`NB&tO~%V0Tu#_`t1gicQvzBTRnFxKje9tSi{mFU9CXewZ8f72&# z=tc(vFnb+z7iafXT3zJC@)_;TK!s^;k{q?`cW$XQnHj<~Fi|%N)J`o0IO@}I9u^C~Uu8kjeGO`Z~gdKW%A(kTy7#g_21KXlnTY#-tcB4Ib2 zsoErNMtVls7#1Y= zv^!vd8n-Mu6yILa*HQlZIGtA&rq5A+U-&&IFjev=io)G^m111|UVccv04GJ5pwnpx zopjix9JxPJcT<8g%0Z@-HkW4s8LosG&5ob(PLQ-teWH`|dGy|fPd#I?5E#Q-`7Kb? z@=n}pRFSIUv;b{Fo41+DHs*!=rP%Z*ll1{alBBe)8`YE#x2fNU%w*E`2=*5aau9Dy zZM&#*n;dYrBA&aw4;yLR2CO}Y;sqg>lcWT6dy>XbtCB|04Vl&javQM4p;UXdyqsla zs#Fnlb8J0P%;}p>y&2Y)5tcHs52Tt9ZN6p=w33d5C>^1mmME`_$QevX2uasJT>O0* zpn3VbS2_5!mLH0{v0g$2BR!oC2e)wt32~^o^SHB$&s~=w#=SG$Hp960ULVg*Q%+Y5*91c50u8>(UKg`3v97Y#PEw-%;LYk{0><8ttZuGH2HdI|^r98I`o zyA7ovtuvgV54vlNDpGeA>YRUQQHc0ds&_FU0Vn&@YW^>3L%TMiN1;>>FHK+GNWQEp=#U|6+K7l z(&DmbCi}?jhJr-X7r~HK=|Lzkc6EfOHm820k~+$`Cxd*&?-liJsY~}pAsW4=19#`g zIUF;2p532Kc|hLSzY)|CA4lS4ler}hu-#ax3)9tKq|j}LjIe|%Z^N zJC9zOYp=OOqv6GC1`6`KkRvwe`w=IG!|yfML+9euzP<{Ig{aNJNba>Kb9O=0EdpF` znv9^E;M71B*$@VCI%<8LQ@vXdN$a~^EoG8aVQXEB;&J=fRxFEuWp&s;6@Os%G?%jv zZtL5Eb|7Eh`J!9k$Y<*A$`qG10ZdzD=3diC%CPgN7AV2`@4FuFOf0MN%-BOnyI?$WPgxA*PSNd!DMRd?>&CPD3MET}; zXXxG2xS=-?(B#p>Sw&=N3++`{&Z7#M|0bYXNq)ythxpIerSkS$H10|ig08~DriQ8N zbI!6Q947W?_iVas9Xc)on@D^oV|Gi4!N|sHlYanQCwGj9o)zDp;kk`*H?ap(yZyo{ zLtW*Su{J;q?&XS$eXegBfNs|j|GjDJ_H2PNaVgwI3f^Xi+f9h|`JueGUKL$dOSov0 z7wOoIWHEPR5*(myz6{OSoTr+`0e~RLg@APkklLA~Fc11PZph?ou%)&+ecSkIcuB?% zA0V>&?|*&%8xyMojqQO*Bo449CTC-TwM9`F58?*LUoJ)qA4q^pYYe6LS~Ua{A9@m5 zhDe?bUl66a%`VELUIB!YqD45|6*D}$CJ3<~#Cj9^geQ!M9FpU~+zQsazBM;Xas+_O znIcpfx@aPWvGdpOuN!x1HSeNJ*v?}H#9clUIqAQMEkPv$sFV=$ICI%W;xosQie zYm_3%vuOdA@cKfvx|JzOE`o~klMezd%|V0}pioRaiR7PV@UyHDbI2%k*|j)NhDAKC zQRFy|FI6aGy6*HV`d`}nn`U2uh-WGw9hN19EhHz=wcdP6FoC<}m&RtG6`ZHTYVwHg59CRIu!0K7^Sj(FgfrV-mB?PgG!$z?f_ZH`xl|KQz zjQ4vu*+AP&2gyx@yi}7#M~|uR-P!$cjJO~sy{t!q_?+i<4d5p2(#=M-l;L3oITsbF zD_GE2ktDbzi}CIs1HMXi3i$g2|MwMX)jGA32soY4qP88S^9rW&Oat5|_bO}XMB)8| zJrCYK#xH?9Wi}#A{YKZf!dh|zYh#KVSu2)(I%AK^^otSxnMDpgXe`iD4(B`DLB1w* zl#xGq{qM?PkP-4eNzylR{%LH&OhS;T6W1o2N7Z2X>`sL2lbX9pnqNNBOdwl?V)(rs z8J6ONM?WlzG>38n<+(U0_anCKYVtSGk9l$RM96C6qS4>hRv6>df<=O{H7(UEypkVO zmW3hWkJBR*L`oQ(#A+LCoYIabinVnqx5O?sBfp+33bs-gzh2g=uN>H|RE}t>1Hv9J zZUjNOJ;+{X+H*O74rxLno}qT2J*Gp$Ek1=3PaDY6dcJw-b;yXi&6DV*---5n(CZaq z+xTihBZu2$g%a>ziu|UK?n-sl>;HY8qA5b(*AxFj2YH7=J;&!bYTA; zj|z!K@R{mvl=Du#aC3dzn3FAI@yt62<<>ff{@|@i^i`^RLEPV;>$Yx>NNEDy#+p27 z4YmCvID@sMR6D}f#h$WeO>}!9hQ`a*+!jAD5gB1=TBIMin;_>g(sx^x$-5bUwhmv! z1S7gXDs%=xC!Wr?~uxjt1AL zDumP}K&o>R{6cmVP?~55j+%rxx|ETbexBMadfEAeA+Bu!_WeCxmMjV_T05)5cKWw@ zt_f7#A%>3=`jI8@=f0?b!XF5k{du_C)K+q&`TBxCN?=n7)Hp&(X)(=h_DvJ+hDZlD z`t6Ib0~^gt6K^*I*PsuqPvg_d;vE1b-E71Rarku(zs{%as$5U|@2SzXLU5Sz@KKCk zWC^IW$5bk~t--D5He4GV%2sMR&cHZRD+I6molRFmiNn-DgIqidkZL@X=zfJN*_8H% zYrmo8A3I*ylm~)j!rHt()RoAwnd_111c&4bW7&RdCjXw>vkC z5Td&bK{{vQ=gVOsJ4{H2m2x2>LT|f&LIQVNmPbOm6iR;Qq15AS+YkkknNTgOhN?+C zh_=IeoQS|_U}2`IbHs&8HC0s0*ii)83QEA)vb5+JzVWQrPTqp~*v8H59h&a{{=RIH z;1urW91K&GSJ{p-PhzUw5tCCGLfG3iIC^cV;-OKki|=83=;R0CZa|JgSv=66kt23P zBi3^|PJ$aXs#%%u#!b82K6g}b(W`~q<#o0`p2NQK_}+T5)<(qAZO$kB{c+k7HjU%( zb&^R;8VlERAy?I#L$^hkVyUkMk0? zUzfvI0VFiCxBIE(3O(QVa{_&iO-scm{uqT?zijv;PH7{w~; z*%%&<+J~$fI2Iyqu7A`NpTab^;t{<4J4x?Fd72xRWPM`IU6+xvXrbgKTKX+TY2`9q zN4era;i4H#KD%?@BGWAbP%I&os{is!!DYPve0@_k74%uKjXgF36>u1uFJkgwOJH}A z{9)lvtq|=6x(z=EcLT~0gFrQ%r_-|Y?z-p*%FL!cg8j_ui*Ywbp1e9@(N^GH^$Kd& zO~uO1LA$6P=9#pwDw2<#Doj64r`QG0cM)`(Avl?M7lkE~)np!uV+I~<36&$XW_+;g z!49FiIbJ)Llid_|0Fb0P<0a@KHiU|-GPvzZHQUaPZpqKb(Y*Aj#QNP~udZi=Hnq(@ z=N4q9aW6?@Xw#U!e!QZ@EXP7}IPQ|R!W4PYb{ZN>;Vl*RR-G6aP;L$d7<^4suS(`& zkgVvtU-G*NG>&>|3|6oT%B2imgZ=00yEql}S}?kVcJ*)@{bS6JS9_f!pe|^$j|bAa z1CN9|4R(pY+-u4qpUaxJ2EJ?Guw3GW4@{}uk3b8D0iJ36l>m3cdU?IH2GNJWXL7kq zAWc^fK?p~iQEUymiMB=%G5q>CJPVM|w?3knR#DouW1xDCgd2n$HIu{nFg zZ8~+cRXHWN3Z7L0Rq?roCN$A9$+4AWw8_q%J{^-+HE`0IV#Bq6?}uqx(}Wdu_nLU? z`#F-8*VXEQITcgi4kRO%Q#Z^_(h2gH~qqEQ>M`H z+w)RB;_x+XRBSkN9!yK!U){9RRfQ!IUVYUMcgrIZRmM7%Fm9x{39hvM*XKt#1^n}U z;gU1R9t|+u{X~j1$&k zf3AL=e9(GK)Y}o#V-*wcU;L3^d?-DTL#0qG8j~fw&#N4~u1vULf+BUBjQjlyuysUJ z$&^g%G}B8TyUtHen1Y9SH3E12QvRmYzd`ju>X=ZIIe|9HQGyv`#0PCesC^DMbGiGZme}PJWwyn0WRU*VU_+d>s*lWGps-?MzDj7qJGP5k^X_hN^0t;s>4~^qKVO%t zu^mk!y$@nSStMf}0IRfox&7sOZ+7YB&g#fsymmB+d%G1Nn`t@^hP#QU6awheyVhq> z(B!L8r9PsYU$2@A+|8-#N#gfzPyJP&0NNyhqUAzeh8Fy`tny`&n-}wFugd<{laK?7^VZkAMdAlKd7!EADq}!Yw~{$tAT- zIvyHF=!FTsw05Ds=pIz$wiynZ(SOtlk+2Estz{b-)k+9u4sJqg_n{wf8)}wPx$J_U z%^T6zEn-u%GP4+lYECFeN3vq#$aS%0#P7$FctpNn7@lRM*$Jb(sI6Rpk`!@k+t=SK z7-1~3ys2przv)o`54nd=b@L$aIuqOt*6*(kb*ZKCUz7N=j6$IMy9G;fdX?>nBjz5F zJ?(<-h#__MYnrUBe=D=T)C+J?6}XI*Jw|?LXWC_NAGjN{r*5OgVthKP8bBdFC80qC z#P4cj8=zcMF7ZjEI^!8@MeytM^l`?T-jox8De0ymY!?&&eYCwC6(Net_?u!J9suZ6 z(3A#A;PKrx8pDxv%BUx#W3qDmYxQ4gD0J^=~7CN%TE0)LdrV^#*i*}|)Yq7E0D2WS9=9K9^xybT) zA~r}Dy=W4{B@sYz&R14KxoOrEud9jXLKX#u>J`J#{>rmmj@x}oU*8vsx9LE2rtofM z?GZ9R$Vv6U&J|g;_yqQvn!J7YnpQfr7MQ^mch1(chC)sHXxn`{No&co!42{ym2YGojYVX zcrzuqwJlLD$_8HHlXF>RB^_Q5Fx?xV(S_WlcDK0WZUvYWs&^%9+#y_GmlylOe?Cuh zm>6axOk2oI3x^?LypVwHwvyBmeM9+8 z>@b~@Ql^*k#uQ5kF{?(fdONHds@SUJT-g}t_Bc>Nz}8h#>dU;5T(xEaPI{W-IAXLL zT%z(M`sGvsas{{n*W=cZ-rZYsfg5(==By5tsx?atcxvHJr$=-5<~T=`vmwOqv>4)kly6&bJAEP)_G0noJd zyUUE8If_H!`n_1|oA|=%gkR@%vy17z?UIYWUe0529wU8(XY0Yq#TbKYttD0*V{4gq zgxjEEd(bzwM-ft#g}i2j#V|pk)xM4=!~@O(7$?L=ZckAYKI7eZZV!(aqQhcdMrs)6 zJd15!Mhck5#%-pr{GUUe?-|IivJ5n>ELe6tOsvbRYny85k%v|fS#1yy?~y+%cY?{_ zKrt{aUGaKHs2fW5`Rk$9+nV{wk}V%ajC&K}HA`iXLHZB~r#tVMC8#{Om60O7?&(~x zJm^*llMV9Z1zDs}Fv3eu9>soe!@T)v8aPX}m4|c%GK(-d0Mw|uB)Y#n@a%l7JfC*; zy1y^OIZIudiJ5mh)ci)`a@T)8dtCUj+M9R8OtjC0NJ!8fvF*C5MkZ3~Fz+Ez>!9iT z2D>=iZ84aovWbor8)&I9*C}VoLu|-|PB+Hu`Zu$Id1kED3)c5|Q0qk97{gF(n*0E5 z=$ht+1~v*cV=VnHgyFA`)9>$b#hftZxCfk?T<0y~bIPrhzQ3}WpS0u`qee5~Br{mv z%)?VIqFdsb(ASt80(YN#x>;kE#MwNu0&85lNUE8OOj9Zh*>GHdR+>{2`%~E^%#|BAvWK&E{{ZT8px)-Luy#7@f8L z;F-^n67o~GO#Vl3(dzRxj+e`5_~8!uZihUP&!;IH)saGoJnm$*CQ~9LJ<|Jpvj{6% znI*o$KDt+*8NPBTR5h`8yD0!l1ZzXhK0)Swu!3LxaOz0*HP0CWZ&rwVe&<4SjZuo8 zRT4(CArzwg7=C?(&-41*-y^>+Rd!Vv_({i0U8hzvw;syhVN`C=ow>tyKcO!yYEVCG znyjiE^md>ih+Dg<+b4`1|9uQiXBg)Yi>vf!w_| zG-4BkZF)v4(kMFb>^60xCB@?`F_eS7PNFhPytyj2P`F-dEX>+&!R{MwYi|aZNzEa) zZF%ay?z81#u*xeIP3=ZbsckFR0s1nGI4J1K7y(TShVqF6o6^fbH#L_6UdFAl9KB1# z>FA%ZZogz<$in~(-i|ku9iwlVZP_kqZMaC4#GT1*wg!aQ+7v=_r^)#8AQqN*`F87# z6dX*FcZSvq`_I=my+{Q5AU7WSRXOIXi)Ua<_+vj}meqiWtz&tE9Jz*6b~vN4OYfJkt%HvU;yD zeSj0sU|_s+DQF!u7VfOH;bWnZy?2@8^MHnM!bLg%6>pueTm~gisiBWXR zm9?#YUg_t9Tyx|{KkM(H#a1`-O@gI>m<>?Ne#{hE8JSg1b>H ztIe?;d6_7^4&{hZma#%F^4+T3vD|eY9HPpffV+vwEUN2FQM(?CZSbk5zZ&9e*LA&biWz#G3)$o|AZB20*2=EG5io4f%pHhNy0OF&LkR!YksIp7OA)e>yb!xt1hYO>C(zyjW5~W!SLrf#P{{z z47$u~%xQOwX|H z_1P12qs#ZyaWS?g!Z0;dfb+t^QT*lA*MbmrrFutP5x4aGzQ^(Vd#tuD9UalyxXt+E zk8L{RL3)+#{_$;Da(LVgaL~J=YvH{`EM``>Ff%A31bT5&Bk!;hK=sGnnz;l~+>OdN z@tv{Q7Vyfq+n@_$^`L;b;3y!8XN{HkyX>dudt0~V?{1(Q1_8H#xw#@Ly7Z%Zg)M=H zIBCceV{nJt8l7vlm-zN?Mm(Z(?D_FR9NOT#eqJ^blwQVTOU)RaMxaVyc-mvKySiljHJ^o-*$`a^?+QiishTx4t0hJq_dp~7Z zjG?52ayJI@b1H+PD$cS3S4wjLInG23uImZOCd6*i{$*S%GA8%`lt) z+1cN)CCug5IEKp*PQm!hUBD)j=@)PerxX~u`r+Zf+ER~Gm%Uf@;jy54wADJ&e7aZ3 z4}(WW9bZY>%(8?B!;Dq*=8*@mNoTR+DLAn2WbhJdYG01>I3QCyF8%~9mP3dJ>DH7v z&@G@shHs}^v3UDdVS4)KdmLxp?)AR{vBcscK49hm&2jnfkHzyy)&~v7ZWVaC!P#G{x+Gf_ z3^IDn`;gbth4=53*v}Iayn8Ae(+%&%PnQ&c!lq?hYbY`!ct!woK#aevoC)7X(%DG9 zYTR4u&z-sI;E^@*nDafYQ)DNWPft#q?W#&+xZ>}8c5;66D11qS!%z7dE7g*KEumj! z-yuIP$r)%YMyUaq(#+`jZT6V1Q#ZFvZK;0iVij@9nn@7lF8-)KVvf<@E#jb9q|TO` zwO(hrODZ2nxqOV#DYC`h7A{=jEFgF^xuE&ygDeB|Y6t%MIGvYwNLLSF(bg?`tg8&A zo`nzD#fD@r;Lx(fF}qFCUx@1Mc1dtUX})lA*&$uA?fgtMZOA7%K-JeAbwZFGK>d;I z^^n5qu4qQ9eThtsXS5o0_zCyg#gf&OlUusUm>zI7zYjK2iceoemRhC=i%G1BCWkg9 zK61DXiK5x9Y)Lf*+wxM#I9ri#EQ|NeoFoTRMV&q@5`9Y=9+)#4$%`Q`_&dI<#IM49IEaFV0b(Wvb z^qd=)fNetE-`^J*cU@IL)dbI>GA~9cO6OBoa?)aX^B=^iC;;7#io3PD-)%JA@qI<$ z=wBwYR5<%b0-M!1Y0onX%{Ods-MioIK;DR@G#RSaxzd`UlNIsWTQm|7mJCrnJB^}1*jUh%|BQd|79QOA>}LUx~0Lu(~uAub)9Sk zcY~>4k{8gm3xB7u)=~<$IYCjwzos$-F(3%yEV0KIZdlto8;BN@k!h#|tx!?lA1-(H zaWL5ymG(BudM>g#LHj5hen?*X$wD9)u-<@^0t` z3+^U$BU=>E))WkFYlUH*^%``mu{GI1Gq6ouk(%GtnoppcZR3g|M280P^|4NDisw@} zrdx10+>J-rUk5}(A**m)A2y%v{oZ8oOmj=kWOWfJ+FUSQsOls`_}RMWkXXP3!qe;k zZUIan`Nwp@-Fn!biL(FON!6t2cr; zo`OXI->T)qRs~)eMo4qFiBwpO6QPJ%+ z7xGICx*@)KS!`nAY&L(GYo9n=S(Z+Aun(*#R)zP*)X0&6(HODQN{G9e3cRNuoo*GTbJUFq z(2W$=5~iF;EFC52Z{uOU`R^D5K|d)k*g}l8nSFYLXKyGbh3j60;gRfCV;WF*zm7_Y zs=+d1cnB0PrAqJror!EN2lCpyy~;?7+m`8!0I5K7kw}u#f_ra6L^9hLyKWVV=*YoE zL8wqawAoE$&I9P*YpigTvOL=yC=txWUrH0m8|oJ z)tM4MC*KW2FV%GTjJ)+c^GpIB+}dR2=$^9+FM zje&3t;V(km*q^0@dm+)-LDql5@ntuCe_#UlcfJO1(0=#rs zLLV+soqLSIj|dO3g5xk1nbxJk?#qy{x!zY*K+pSy!7@-vB>O*n%?rCBd5NCKnRLs1 zX9S2gJWG8jD6rNyugb%1@VdINwD?5M@IszFr@lc@HLuJsxdq@eCj|?$gdj??%1J%S zxdZ8*fQ;}fDN1sh_~a=0C(d^S^mW>FA#pWX*|)4VYR z9JQP@DRPI1{d=Ym9Rzo4SzGEpT{`h63|Y_(^_wej@#j~vai}QYjnh}`xvPG$Rxsz= z>WI6cLLq2zP*B5XAcFri?Bt49$9haqP;0J(V7Q=8n}0sf;n$K(;c^)-o6K+rpS~x^ zqzU30-><=_Aysp-EFo&nI*zRw9A;Vo-MFB~=1k{$AN#r^d7xh+DFEi;&Jp{$iBsdf{w-#BN4@ zl7MR0U$9Gr$0Ox{<-o}7{Q))Y$4Z5zg?Ddiy=|dJ>R@#7t4JPA$;KX_vMx4K9QGlxcUNviFm8315P?>**)`ELIWp? zrm)jE5BD;38wH*M-eg9Gsfab&`1axE3GfR&77YU9W|VTS>DlNQBy2cv6fS4 zvoS=Q4v*Q9vLoSZ0DgU(PP4a+!|*lMw%O51OI>}TVS(UMuiDRPC|rKl20sZi;9LK`%(0sO~$BXJ+iQhn=#J|Ydjul72 zRKzpGgJdED(`wn-?fpAZEJD1L6wvp0ZRrAG4jI+m_(Vl9NVlqy^&k0lxfrkvnl`2b zhA8};&*2AM+#P^spcSaJot_zYgSRZPvR08k%STZIcWxZJ;pEWpD&lx-aUszbQ$C95 zO%W{;g7zvvPmzmDn)NMOE3Iz{=iMaq}THw>zGY_m_WDB z5C%Tul3RmU=^3g70iXejikNvRcg)&Py3v$f-UkQw-11xa)_Xb}+xn_c6nga{vb@Qh z({#zU*MS1Z)mWfxil1&eUH*r=IqdByHn3|m34vA$aW}lrZgn9VkUN>M9yjNn8@#QK z)cJUq;mONO*CeJ1f3k1~f!c~i#W$Zit}@tkJzONyNevN?yAfGl_(s%6fqmi=eyZs> zR%HQTe|t?nio_Y(6p=XiyY(%W`3BxhjHR1D*}tms`@*kR_hflwZ!P&a0?swDB^>WC zK2q#|hy^j&7dS2ET;a(>NN{#RB9;9kBdri>vgRI-wJigc$&3+038RGI2*O%$2q9_9 z!J!G2QRMqg6A3JpFo47~_B1jZQvw2mi1cY?foyCo+e%<@2@A)~`S zKhFY5o{Wgc;GR2eloqVk{tt69dww3NZ50PcvZ=p3FCOWwm7mwId^Z&Q1#au?OnnLA zs8yXiy_RG;7>G*GaEFCk;ZPw?Bnyy**(I&5Q8T#EWw z0+{A_-l`Y|F6(bPA~$3a<*#KL+!cV%thHC%siYA9*qBvoyS*D@T!@U3S@7Lm+vn?> z7mliFEY_FZM_dut9hNPD6%;2s<n*TP=1{j5@FLEwn;>1L!eJa8CL?#a@1Wah7OjnA)*r}KA}1&)*-S`>_wBn! zq-1qhtE7i|Acj86SU^{*7b8Omf`*z{6nNok0-Nqya30*N?lX-T$rk_h2pz_ZG`6#M z6Jhh(GCG@OwisfBo^a;Em{Qx=czKu}vz*UV*hMZSbsIj1H^k^6C_YyX8>j|lR1!mq zd7)fkiYTT6$txuexbnbo@?Mi2*;XUNv+X?+Er5V%*0Jcm2MJN9N^>f@%p*Q!%e^9% z8yIVDCg%O_=CyM{is)hLSo7o8=P2mU)ja;bjFyxe@s4(AsZ?~!&!?j-xUDrCEHd9k zyV@^`9CvfCEnR5}>6qr`i0!P$G~WLH1o%k_hXsKQyrv&RGb4v;-b4DixEpQRt%6Ep znYuM^6xqQ-U~o$j>`?$x4%O-`fU+aUb@e{aVNTvXj+A%=jyZ0UbgF3WN$1UK;K?ZZ zCV7rRhPBxtrc_@c<({=5I-HW-Wcg}9!-Dh#+}7EPUZ|I(iw0V3O%(jC0o`aF*==6Y1IVZZz9vCRN)4Zl$4T3nBuWk71V+*#w3Fnd$XbN2`^Go7h9 zA_`Cq26`b5$_%>-10jy65^f?q@4zLE&gCymOGwh9K%+omf1qmi8OQPWWvp5K9cPhG zDLqTe#SizvVdZmE*|o zRZ};2$~oD9Q6yFaYMc)kYcXPZ2Aw*t=^n$n=j&42wuOzb?1(cmfyf_R&&H5!4U`^C zL%1z|V4%3*?WL_j+2t;x;60?7Xqg!jD#LFGlSv)>5k$a_v;(>2UPsrxTJ)GgN}HLl z+mdj>LpW-y@4%MH}b_V)fhR#3KV z6yc6VDO4z$8hGP;GRIqfn2PV= z2e(yR-e)~X(F5K+p z;BFODu1Q$Dosme_a_8`NK-B82cmo5sZB7pxFqOt5F zCbqUrTs!%k0_uGgKntVewT2h zLCXtNzZV^aDTzR~8w5}$QXE(A22oy`P%d6 zDKNa`h$0nx+(>KPsUxQuG6uTI3UhsMw-%0$!HB7*6aD5n;Pd=$<8~R}s2lorA}c+R zamqbeISK9cq&=e;H|=N|>L1a|*VM627qn??%?!Fl3vKIm4UKm6DEr7kl}x{nqsFOa z+pAx@@Z3Zfk;isNAzlhCw+S^1+8=adRuGW6iC|v&3~QyDnDs)FUc~9!f91R^q67!N zj2^O{hJJgLBkPwz`1;!vFZqWoam7Mp7)yRmv{1`T1~fW5$pqKej;uncOQvpKZd)p5KGdO z;D$M9k{bpNEdn)Cl5V;H<=%@oyq38`Fq!^`w~cg(N6B}KsNtY@;6&3&=gM5|#;^0~ zl_7hNE`?E^#H8t>m3RlYM1w9DY0R~-qgI_HEq+|$w_Igtc5!Nx0;;9 z+%Kry3O8Jm;|z^DRSSMlz|^IUaU*~6mD;9RJ1C{mD~K(e^qC;FG(?g`PFAQuKzECQ zg@d{M2?6HJAXh$VoYNP15^b1WH53dq66!2(=PV{E=yEnP-ptY=1tmJ;_vNp z3UcJvH$Oh08wvKAOEjqXn3}g8H?tBJMfk}dtpuPd^L9br%pFQXj#K}84U%sqGvUI~ zyy`DLQ^~ZFCHEstkD)53#eDeH@Ha z^F|t=`WtpmWNcbVs{I7q`ZF*d-Et>P-pi@@VLqo|+8N5$!e$xP^c{Ia#Of;B3r;OK z{TaVJDr}H{ zn%nSZEOl_Sx!_OChUp3pY#N3^7SF~)_2%w+_bxRm^9ZJiB+_f2^O#_dzEJS*IB%c4 zubDwDa_5lmW)U+g{S2t5L$Pmt|9nhs-A*riaw+lg`49*sB-ZvAdwa>!^Nm>**cJj7Lf()x6U1;Z%ec{ThiA7`nZ8Z!Kr?SA1{ytt*yo$EE@~OFNShf4^qga4ZckCn6daTUr4z zN5aA^?oM}+V+mn_W|Gn8OreU0DnQ~KZu70kq{U8q3r1rCg%T*-QMQx!O1BY=<-IX6-B9M~JR72vkR73i(IYp8AQkHK!-5iUef+xY z0lF zo){})^1)X5q%bc4%Fi%*6t$DWQ(gm9%Rdy;n#8C|0(6s}bSPP%vU9j8)LHGAvFnPg zgh9J;6g1SEFfG-SQF|7vQrrwrpn*A|e|-2gdDgUyq!;csuA_CF!VyVekGa1`pRHdH z6!PD@xpy520m6|;jOXpBiCu;&vfI+HcBSTI>`OSH-igM+ME_8&x-_wca+;^~Ksxo? zSMH3I0MyX}AT7<63o-W;<nix4J^G?wrhlldVNFF zXc=v`*)NdCwq!yODx$dnD|?3>$R~**U#o1j$X>!>*w8N;*V+9Q0-bDen@Qr`FY7I_ z&|LQS5D1z;G111_6@ecXwyW(~q`|_f$flctZn&3`rXcXFzL9=`K*DL$9^!UAX>^P= z{JJQ1@--*9fd!}@Wk?|p72Dg-=6-!$GQHp%;chf82tH0fzA@T=xLc!W=_tGk-P7F0 zXN!suq(may94y4zKQWS?_PpRRW%aP5sO^|%nv(%ZU1lO%kxFSotz-$^9y&i%JyJLc zkbAzH2_&E;PYH?XNWIc0{nqFCbT=csfo@~;*^bS<>}dfWh=5W=VUlvvznftXzy!5V zaN9-rWj`jKd(Yv30K0+5VZKeavH_)e6r^`D=4L(-N>VXVW2f`_p4vv|XNtCGbA${#xm}8Y8B7{u}IyxYT|gv3*j;Yv|W=L&I>*0R^$Q#)Nrt_<;+m!0E|@@ z0{RxmtDb4t`uwyAt9GOkDiPFTH2S@RGD|W4+*J8{<`Zfbxm$cVa`^oo02i+f~6ZAvmK1hBe|A*c*zQcZZFx_@pEEP3bE@x0Nq|#Cmi*XjY$dz{lyq zSvrAk9J}ji+aNBVr;AvI?BN1Tv|5(_+tF=!f+sz2xA}bD?ZEcf$|#ty+xZ1q=9JP2 z$-j`^#%7$Sw%NP^=%)?&vzv8!%RW-vy%Y9#Iu)wJHbBmLjG-NWG8`UKhpelwi14;` z2%1MBnU6=|CRXq!jTC*`Ey_mRJ6p_`5@Ov-Q>GJtBqHNAqLLjsYHojFl3(hU@9gWE zHg#n#m+^EB*H58#p-N-r#&sw?4YvSb2na1E-e^@1lA8wou$#xo%$bCrvcJ_D;k?o> zPbp{^=wXwC2YogTqQtHHMIX^*{JI6mWoEUK5TIT{R*OUCIm-```M}{R7D(($k%hb~Q~R*~Y$67GoPN zYJzI_jH!V?h7#O%KZdkd$VfSQaFAo@kqxZsAyDxyYJP$D+Z5fcQFyWhNK&-?m^YtS z{TP$2gsGHm1_mKN*;G5!tK5beaVmA9qSvqYh`If9t_ZJYkIz(GlF3tr30Gt&akOA5 z-$*PmroF4Pgn~H+n5~!MJgr5DI`<*^{k`s$T-j>S>zq2_`cG&y>qe{US~FL>fJa!6 zey`yk)7g7jU;X1IS`pL9RrD7!p-OQ(L!FxKkBz94;5q{?6PkVbNL*zMGv2VCX6T zvB9*1+uEoNUf1Wl!)=?>0xcez{l*P+o08o^sxA-GQw_Q-Whc0yW`(x@xIQl`IcP8T zQyD3&2#}iiPy)(nq!hr@XN~#fTkZli_r*N#T|BJ2!5=D{< zdCKBFwQaGU;=9WUZUdND3TUe2An>m9&RLcu6BIAq}f%nybxO)jeT-%CQS3-aP+qaoLDGY(XIsSvI)z4LI|t9^3rnlN=J zJrK9#q>PrK>4tnCjHL_-_UU>Ef1XcxFe8twpHlq4V5B%h2_uGm zzl`X{kLth**XXxVP~G(P`7ce$Zeey&fqghDe2}ayFy@iygQH6QgCT*fmB;T{x;(bp zA(+87PY%<*pc|B^ejz~7GnE?YKUI%`bQ6JAMA{JeUhwe5>uL~87SI87gQHYSC=00e z)w$Y@oU4fS+0x%{Xo$GALl8#hSQOfgl`&q#32y5ix4E$89tw10g)w30bw~3#|EYDR z9NrZZ2>zfO;{=YgR-%kPqLOXVZRa6g_d`iYi3EWT=Np&1%a)1vUGalfE`26f6%7(P z?IcH-#BH%vIFa?M`Eg zedHJ|vj`0%IbarBDij}i#L^Qfg*==M!RnEu1#)}k#Vm_V>jV(eWuuc^Yx$#d0g4cG zGj~sDpze5NrAc8sVXmC3@rpp5shq5N?mZ+EIz$Kecl6Y5%U7Nc6LzvJI2xYEb&0Vt zAf;-vLLYTX6;m; zGWXo_j=Q*CA7T1TW0~knO8>yzc1s}uQ;7fHdDd8BP-xDorqI`-7yjNm7bkvwK&9S zc?E)g%b09tyk}QRw0LdD(+@&^#x6@{deQw_T-|YLBV_C>xpO|F6dTA zGx=oPQG7^&4VSC$ZofEAmKj|GSR^|tXM{A>DIbU=)FbLTr&VPINn<_C0v|D1p+JdG zkU9D>`imKpt>EpQa@{5_E1p5~Sg$*D4j2yu`q0Wha7-A=&}Q|=qi-Fw zrq=nb<0ahqnn9Mw%Z%BEe9aK^=OXfh?wV5?=+L^(RLMqC-BW|g!9p=%%7-BI`71Op ze%%dS(wQo}ILdiMkyNncrag&i_IpZb5C;;wd5Xd)hdHm{$H8s))V5x$oUWmhwb4|AWQ8|BNSh?9 z6oo}}w4=`h7_jRd^vTiK#3nk{>0Ar9x*}fTTOqg!OEK3&KZN30k!@kAA~Lbkp{h!2&3IZy{_ppt zM+MOG4R^SWM?pi3Q8#woyTfBlN7RH}*Mv)F$o`;LsaG)`Sb9wx7(=~gYm@%scWZL> zOS{0Ed$qFEd^D9Dj=o|>I=zQ$C*O<0bmJz`ZKRYzD!QSo3h&BGW~7!|Z?SY7Y3L*F z55E?3jaJXZ`}!{*=McgKx?L_7uCOBn^KfEBfaj3P@!AjgXhtc;3H)A&a;PRWNY$J2L1+#FTn_t%B` zWqO5DT8Y5DecORu0(!(tUSI+n&*e`@b7QOhZS6i9eaD&Bg%H#hg9e`~wPue$C(W$) zt&|rP`_FXq(ae@X@fzqvICsTNhdQppGaz(^iehWP-6E?B1+d~SYv^c2SZ-BF!8i-{ z64{P9A0eDhS0lrVGS$s3EiUV)=dYk^L7rzg4Zoy21KhSJA#i>026sl|a)Jx$pS0YO z33v0$2dM(1R~^?>52Cbq=YXG)N5a~Q`yw%g5V2wp+4C-H;)KmcwCY>~2ilOysyGq3 zM(*OtPQykQntH#YaLEDHz3RZtya5nrqvb zql-*awv|n0b)z(RAQKRT2_6*;v*Hqz_i8u9&GRH1`SpFVHxXB68J)uuZ{u;%kmHO5 z9wU*n|NJY`;&$6#YC)XjgWiRu;yKQoeY%Yw#<^hk%~j)Ww$L&C8U?j`d>g2kj;N}Na;D)s+qdiFMKbG6JNinTWYEx#3 z1yRtx=vhXJ zRzHzoE;dgTxuY47DizwVl*9I@?z^f_qe6OAvT7AN-P}H>08q_gvMt)SF_abU$x)|C zg_@7L+n7}rXAYUFX_dld`XLOfQ#b)uRO$2GC29%QaPLFbq%l%@BsuY`yqXSSB0s9clm#%-Zf$zNh#E{s91-`~JG`Xd1{6oO>stkVT$>(eDm|8f8|u)CAK;Z8yo}UKqp)bx zBa+V;#q9MM9iNm+FnM|u>BC#1$k<=(=e^T1+0ySaJCbCvjQz!uj}fKkkfmb-3&TL! zu4%VD_196lQY0GfMH0O<TyLy3=EEI!vj4etpAm3Kry@F@lf1EW0n? zp}E&Wi+r~qR$IzF_P5J>gpMX>u1`MAtww!VhyV%~O@+A#i*$aPe#?`LpzgU5Lu%xP zlU03xnytI23`W?J10HBh1&+cYnUtn}C15{x)mw+M;!izT1J0+@jlcBZ5iV+eEDczG zXprCLf!Jv>5T**l^0E|7&ZABFM(Z$R8HP27$fD9nF3yjG+xn)q9Zyw-b*8Hoi!qp^ zoe_u-nCc#5ODWW5aHoDL>a&3v+(Xwb7JfZi6}jP{VO3cuV*gLFIB2Q_#oZNhIVI+0 zIt-W@j%fXol`bh8oo!cl$|>8G7vRj`&UycblI#Qr&{rK(S4sr)@5p9t=m=7e@)9WLF%r z1StY{3Ip{|aNE3uN&<;|nIwAHA9l4OT=;wvm=Y~i=7iznbmElk#wHf0{l=*Z5TVx? z!&h;j<*|$pt;Pc>+~ztCq~5W3A406G3r&59|DU}#QEyz=l}1&vd*A>4-}bDIRX3hB zaKJ=LPRA$TKD`o)B0&%YivUVi9Hdztz2>{`l_6moem{znB<|iI``ZW)=r+VuUiUv) zi*qVu{vzm2kfz51N+?G#L|Wx51l13l+LEIci;L!cLib z9m41g>b)Cn*7C5IHxv1Pe<$H^DHD8VRxs~a+rR00YD>V*zaoe?211Ph!t6B+n%~~AD3+ccbVJ6c zD$`reElhaDr$SY(O58Bu6mFGQq3}0hH8+F;k11gWU4dfL}P>J+H329j_%bX$XC zH3yu5OTEiy;`I0HlAK)IVd!+qM}V2E=b>=7MPE#)G8?hleXLXuU}Q%PgnG{6EaqKE zxus66Gt{PxBK);;iBaz*=)FAVXD~3H@MUS~HtV^NM>G)U%Tttg8QcD$(EU zY6qUVXXNsXTmJQ^k@IX>R4n=jZG~UcG+u_&=~VWIMA977(Kn=5Do2uPfCi{arnZv_ zH+aoI{^?7qFsOJHzJ0Cn`TY^YgoMw^CVU>ecLY|-n)O&FsyI8kF<3dD)?CcC_ZByZl^o)UcXxM52{T`HTpQ{7IzkfO^4pUyRCqUjgq++ zpHYk%NEmPTo%;JF+!$TqgtPm(RSf_hR`HeoJ*hw`1r^inR^z!P8S7peydN|vD#e`W z$l{C$sR@vvSXs@^rpSF<-jP*gXb4Ng8G-Y?0$!d6`}QB{IAfYSfuuq`&q`Z2v)ZJB zAtb*d*bE7v42qq9&T^wIKb0K;>GP zVmkivu8;=2ob$rzdtNQluUn}e$Z;};Ka1HRs!D4hYxflylWDHE7V%dLc zZnQtM&0REesPU6P?-)R-K@b=#O`W9PqJQ9SfYpUU^YngG-k|4V@a6E%^r!z*;Vs%yUt890b1!2=1qfuC#I|_+eHXqFdCzzp z*=%yn3Cjm@>h96MOoj0H_ZCR3H3ePtbzn00JI8kPx&ch%ZAf8^0`Mr(3AcbmT|u|l zWyv_Zg!gskJ+7AYc6w{I0!37Te6dceNa2j2Pa~_Bmv$lC+rP1XB3)Ta4D%_=!z}1j zpTTY`dRCgJQ7VBK8!8KYQX{-e2ittXOlQw6j_ytULiy*vznA~_Ka^QeO4P|WwuJl@ zn*+ITusuh5xb8SKNvH}VH0|R*=(;@8B5jg4Uc=p9f1d3&WNNr974C?uE9}4)XSc6z%QIZ&=ggb-IhUQ)w8ri^V)(@BrT%w@ANSd=d z&tntv%G2~nwf-yqoK9cUHNnYHMwj7*X?#ovRH96s3WiHtgxgLII_ z)hPtsq*t^^>^xMqQTaCAoy#L=#E|LX_bL~9?c#)bcJ9;+MtekyTYKD>J2zp{uXp4OqDVPi0ChrE^N>8qMVaVz zIx{%M3_IFRp9d-3RsH;TE$CK{Ku^*LoKBD_cUV#H163l6NL&A{?{>`3S|DgAwOW0* zdk)L&b4q1)HLY|XM%;O?$iP{PP@kIG>FT|Eh#NFTI})7drh27{2Wnfqb(M600pS4E zZ_MkvA^Hs>_v>Dr3zGZ*DAsmp85!(Ej(NUTbsH`hbtqC4+=ko7J}?-Um~FJNRb-G^ ztu3be6R+_Q3)~3b7&RNu3UNT~XkYj_k_R=-03$5_w{6Ggl6)HT^}@%S=@oF1>r~aR zUOFw)0n)3mE&1)E*H?R@WNDo=*d&Iz!#qfKG1*KZ)P7kmH6W^y$@1j7uL((wGi9Y) zfPU8B{~vOTQKE?BuqfXN9fcN@<3oy( zor>vK4&sd;g%CM#Hz?op4@oszqwE4{kGoy}Hd~-u{)A@EHrqKhD7+}$( z7uDr4kCO`1 z&yea7pqH&c(4iYy*-uM5__}aFTjhpL4<&7L9jC2cVeLS-&TC@3@36({E%2EeuoLR4 ztQlE5H=%j4pqq89h@F|_@UjU-ZP@-)C818h=`@n$k+uqmW!tA8o;bWFr2$t`K$DbA z;{+j+8>P?5YJC3Or$vOjWe^|Evv?ZeYjV`gqh8;iiJd1xq8qHu#T>DPMjAGTigLW$ zWHGG9O09874Qwi@+J_Ld4no8Mf!56c3T#5#Hn}trk#4({D2<@AEW1g%Ft<<*$(^{8 z?x|kE_#`Dk#dm~zUFu8fE~?J{n!3`U>m3lH z!dX=fiRp2-UA&eb7DX8I67Z5?9sw3RT|*c4e21RF`6T`}T<8&(#SSs#TSbvVLJ1uP2{0s=wH6 zs*nC+6c4@OmT6r6Bu*wb+-*&)lZJ1Yj1Hqm5W6Ove%pa-jmP78jiQ=UNCR*fL}E01 z?8$~nI<^_~U!l5qZ}%SN*uMMOL#+Pa0&0VxYY5YO>z5Zek2rb06arOad#Wx~X32;{ z#gWlc1g*b(1}ICUu2h}bP+Dk|iXRa|1$BiR$B>mBdq`<<()sUlk!+YFmgJ-{HTKyJf<(C-xa5VtRhP1|DDGi z^bp*gdVtG0h%NKjSSStlt{n#Bx;|w2w&`f`u9k!F`hoi-uCrogGB+m~a~9PdTm@2K zLd+!8(!!NU{2TgEDYIB8uVn(8Awpu$5+%?+b7^e3rBqE>y2~hZE5G^X;AR8pCSo7h zq^pbP27@?6tb{qqHXd=`pi?tO%8Q%V2 z-{~%46T_^wmtpU@pR%{5C?Vd|s5>L>=Wua2GS(KLGjnB_#~m-@kz*WBa(v2ivO7O6 zcS%h{8PoyHYgaXJ__kRg!b7}Xsqm^p12s;v(j#n93d%hr6f!K*q@}rE%t5!+dWSXv z4C3!IF)XHG+x+xFubs?(2PuS+#udN>T3d8mp=)mW7}s_?$K;Y!D0f=@~UH*i=qQ5|H+zHF~VcZNC z=Aq1ov))Z{jHyF2Taw*4O!01ttrm(gGIQEF-0GDR6}b*Wl+dIHox9MkL|8t1f|-pL z9QLVZSAg|L;53c_>_3$wlcUgA2nr>u03UBK@xiPK_>eZAntY2;TOI0PpSQ6XKZ326 zM&+4yu=I~K(tnBN9`7i{MI=jVzF-jO?6F0aK(!}^z0HXmO>bmoH5bbA1b}ofD^ORa z^m}Pfnxu$8m(~I)WJom{g;=rS_2lsJ5dWrhWPr1sN|X^q^i=GT!1aQ+=c^XRdS9t& z{n#MG_a^%Hk_O$*rz5$3+z)pPcLQa(oBtqp95p+FhJ?&D2X$$8^LSSEbQjC5dj1f+4j+Fjbq2a-W}f9i!A}JxKLA@*3bCF+P3fVQxuJx6XWt+ULVx~oi6ep z!`hOnn4G&i?gpjSFne^KXEdYnTH&D#-~m84`);+#wpF(9!rd$(;@^*=Snv!s-tHj{ zLuyIZV3qkJq=()bfUfdn4BnN@(nyj`Y(Y0>6im|mE0_ZaSc;{XhNZ>R)+*a=+YEWh zdfX71{3 zOh;?uk1r20^X>|5fik0TU5?FhGpPj(ZmZNqhtK~XI8@M>zD<8FwKS?atgAC)0t zuMaXM(jK@QWHK#i&+`;&ig!aE(5ZhLCT+K@^IYjVXkTiUN+IG*GyDy3A5nZBwjsYE zJ7IYWXcVZhiTWblk_xa%$DwdWa?Mw#1R0Y zgI|hiSP%mS9soZCcl&#}%mZppcstW!kQC!)uQToz!du$7uN^$1U)-(pOIB#wZFf%} zC{?%{@|ytg>Jz zkNZ*lp0Y~K70aO}=*gLm20uUH1EkQm@+hfr80J*DO3)3dqW<;m1AUe%iuFX$?M}c2 zy1gQ?p%Y8l{L?!qma4f)+zA9tx-o(h_C@2@P?(+XV(1!Xf2O+qlG_L7F9&H9);J25 z0K!B+X7$%ozUA=e|Np+e?l)Q2gpre#Teqhdc7!}r9Aol2&cnZpyFq#M4;cX$$Mbcb zH`dz{=~Y`EZ0bD6TFRNSU0USL5|{6843%*3gw0*mPG+K%a3FRXybiu|teGNi8H%p< zEU}ywf)@E_7>)wEHI29TnRXX@8$<= z2KJ8f8tL}G^Z9%l8!X%BL?U)INCynZ3aPwB&9j3BllQ^LfCDcH#a!Pr48!F=-Q2AA zMt=))n^*u%K(fEtwuix90=D0lrXl!E0%wp-S0mdEt)6kU!(x@-$>5n{(H$ci(2A}b z?Lq;=N7&$U-WA8?7@0Ex%Lt#-aS$M>XuFzz+(5TSsVDeX7Bp(D5(0*ZuXH}-wdoO> zH?$MnHs;$h)K|(?QA9GRA@#<4BlpXJ93QdRX1nA)<74cG$fbOl&a-v@?eFnA^dZ$?8&%^O>u zQQmtiYn~?zuRw93vedRj-|oh9M8zB|IB=Jpvoqo%T}3F#EQ^vq$CTeD$uT0T49V+1 z;&GMBwSq0H;jH^R-bK~2KzVk*PWkrMB7S|{INPoJH1YfY4vE@42L$7Q8CZ$D_2f9- z@>^}A@!a)(2{-QVNp{k%#=G?kbF1DRS>wZ0;n>`8p9k+71r?aVFj%{5$K6DXQ{AHu zRbB27%^waSGwQUPO@s-pqG40(Y($;#0L{!v%q!5g_l6iAG!j$m6^b6hnS1Tyvg&-0 zNPt!Uuq-Ss3(vZTf+iz?5k&CP$lnN9O2}VCK&u@e6%e=V<2pTeJ1p_4xW~JV?wQGz zVPgynrS5tIxvK9mUOBy12lp&eq$H=^q^sPrRMfab#3F6)@9Un*KCEYx0+7gd zuwZ3dUwo0V+^^i6wK_UQgRZV#gF<$ulFp=i4>-)ha~$er-OyYMnnOG|Kqk9^u!Xbd{H>#eb{?FVW&pz~GQ7onB8ttt<~X za5o5yiBy`U@`(B!QBud(I9A%Kjz4q5(QPF*HMvZsgvutmgbmT{8cB50d1C3XX9*FA zQ274cQAQ1~(c0lGo?LwTU{9n{J@8g=dy2ayVFUss3h3`Pypn zcS%kWxGQ2*03s`bSlxWvV(4_0(yyTLswj?_F+%F)%t5V1>r6Mk({py;48BKmMw%N$ z7CMLBW&zD5Gi;%Dyh=c|(!kyxC^`l2X0f*Tv16E#R66T^j5r)DL@p|fC4R8{Hx`tg z>EC1&Asq*ej7ziX!63=oKOFkTGKd1#@7YdDN{tR zKER+Ui+w!KFdSxUtt|%3$w~iysrbOVgKh}Gs`_Mz#>sb2?$!hj!62>v6%DmG^BXKgI z11+uCE0{7lu7&6`z4Bud)@I2OO#*W$tfu`FQAH=ILbj>cT*%jjP|P%)?J`S9vHlZj zd+-kkL#_J%=7AS(Y=3QW1d^~0*)x{&fLFGV>~R~M!MQP{!`vu!f)L8a3&+Ar{;9ZC zG0T2jB=F!mgzdQsGL`jE_1dDH$T6&?Ccb?W@9BJ61#t8KzAnebJBB&Rd>GsfjV02n zzT3YQ8W&%BE&e6bG6C>fr+hS~Hg9n^eEtMhwKkpRNY~WR@<@)aAbp1by&5qGxQFeZ z0=QCmJIe}a5UQE^ zin;Y(0xo9o3LTb>?hTFMHaVc6DZ-wDbJErVv0@ze#og{$+mOoQME}YF3Frw`WlMHa z6#dc(dZQr=t_-w63cX467S(OOiaCiw^jQbpQ1g7E6b!=6GLoW_u71M2jxv9m5qB*E z#`Zl6K$faLahUR7s=6{kb@<`)r?Y$}I&iwCXmI%cC=XJ6{A6t)=a|c^;~ivkJ^C{b zZ=3BE|M&Iv|ITNb)=-nrB;x>8v_#Qmu!bYlLTeJUE9mRS>lx#P%w9xP!asEfM+L2Z2>WE=oS@FcQm3UZW) z^l3|WPe;ug;dX|DT{@BxQtZ2x+B}|%|B5;;?r)=WRNj4klGAGq4e2YKU#sW(q}W&no>GxGncN zCE>QaR3`rSHHmGUqvmQK8qq%SZebV#8xPmHdj(F+VOEc}uV^p6{H}lU87GLfC`Mbj z|I}6YTDlJavym2ZT{M+MsA%&U~uk%TrSAa9HcA?>4)bpbvC+e z*4midj8S)!xJ|BXT9`|A!Fe1tqX3u_>|fLefkQi)U~VskveR?}AQ6~Z#QedGRVgdk z`idglS1;YeG}id+YfyO_>l~&ntwMm!4$ke#2NN6?ZtDpUj%_DqewaW9MGy@cx*%&q zi8AuIdWC#7v)b`5EtdKO)^k>2HaToj6d^Z?T5{(Gca!c32x0`%fo_O4P}^^A;B^3K zGEZLf0Ts5R0^N+`fi@#SRW}*J%~D>iTpn16D)B+^=8#&71#TYccJ&na19yXXo*~V{ z-Nf8lTz9DV`h5@K{(hdvdO5VeUzceTmT8XI&jaF&VsN*QL4eq^W;R1ca(wiSPLVNQ z3)b)qGKW~pDWaqz^7xo+A%WQ7AIE|j5zq*%N+2llXWH~WJ29<@b#yjqYjZclVGpnVThgjpXkB>v^#dm}cuI1Z=hs^&j7b+H1 zgrou%$8Wrw@GGjiiU=8r8I;OI(Xhe#5Nkl=+|pU?6-x0VSXZf4<8i!IlkCQ|ng#+_ zomBp;Y^D@BV+E&`p5CRme|lsOQ;|=F=nwk@kr70?0O6#&2YT=C9+){Xq zQH1@rEW|a4$t+pA*$>r!pT@*oc>2)UYJU3?f0h}hQ77=DIB5MN)zEe{t?LrngW4of z8YQrBNSEV51upHQNPs+%DIah~vKSzP=vfxpgB1W51;*v#^2&lU>3#ZX_FGSz#=fnp zcp(sLZfsc&DHo7v*1zT8i=c$7w4i5kY70MD#RWZmh<5 zB7I!?W^k6IH$`UA5_*>&ApljVTsIo$Q94`5J=iGJh=z-&aOLLSQ+R|na#j}OXz9k~GX1?wyLoK^aYp}GeYaiJ>=V1; z>MJ7Oko#;IjAMTb5NM5a?|`X2S)kU(?30C| zc`LOQQq{DKl_-Xiaj#Xhh|n^mzLATX*{$oWb@TY1ad5`r*o7c?@(GL@YNH`gYhY?P z5$_h8^qKV$;2Oq0JZkbZQQr$jHHM*(jD-lC>*9Rg% z+(*q0qV4vQ;>n8U& z(8Nv#q6CodR-V~u0dyct2x3GB$_ zB>Q>YF!nVFbj#Y|6pS_AtESQL2KBm=Z*Q9uLddMzF)IorsqI0Un>CkJ%EaSlA3R%G zgC!p>+;zgrcSUjgu`s#EnF&jPpVu0t{Qeh@fO4!%Du$T%MiO~NYxOwUC{`M9qZ{Kn zU*rODDWxzqZGhWsS*(yW4mkwVxU`#ogNY#_#gZyPhqXKd3-E>^;O~Z!#a4|J-M=>? z|NJv(7QMN_59IL~go`R$X<t)q;U>!H645Q)HnxRB$)7WfB<$t_3y70{5lH zX%#Yol|W302D1A}GW@=To)1|ztwr<`6KNUNyN>MA!xn&K4zer@XImrO0JG-H&gB}J zC_@h+CEDp`J!f;xZEZZ%YMn!CXse9`F<+`=&>+*{0Jz(?e9os)`$+1{&;R~@@d&`O zJkF>A?)Cw`3-?mhe;#*>XfpTN)(U+ey#iK*pl__x4Bfv&cHhB@r~O7#Q%wMbNdV;< z4HTDbrey-Gxuuevxj?$i=%okoYsg?lg5>+kND5*3l?3l?1LK>3Hq;VvWvsU*6zH6Q#k!+H zam~YJbGc3v#}R*UD7F=g%xv{`5Z;gdB(kATZ17$~0%7*p93&WcaUN|FDBfhG_s}+W znfonLhD@=zWZYQpmxHw|n|%|P$G!>ogk+SQ4|hWg+)WY~i`C*qaAsMa%N~&vUZtw- ze`=X446EBm}B3GkUCJtjN z!zi)oWU7@moEt7dH`)+zglj+_{UH)QciGa>L!BB|{7U$u3Yl7~e+Zp(prXY;{KsZ!n<2dLwR1csnVn!NG z*Y{#1+o}vUTkRw(8t$6_E;SFKQx$S#fR%?qbdJv6kTH>*2vG|vu2Owjh}z~16bMN% zuQBk}0KBWPQl>7o^^%hqggmMAudzG{X1V8NwW~~|1Y&uK1>tmwWCM*RWjFbyVgeh5 zH~A0ivq@f{1w|@5k2liNb*?`%9fvz#Fxe7~lQL&)RYiYdSb37xOWen2<`3-J!M{kfHO^8?QPoi=o95yPRP0ljf;5a=#sLPnCiR8j@rYxv~fvlRGWa;jbn zAWql+z14gQA6X$6@qqW9VKwU@D_e$_?WI@^OMmK^?W#wOi{9?@sQ3lfI< zPPt8=Q&L$$T16#7sjrbI>%xoFm#%^oA4CW=2 z4lZGhqba3&Bcv;$Rm?E+mr8+K!slp!yr&Gnk2W{I^w(jgVpE{Zug{~g_?S}P z=B6sW9|=HIV(1 zu>5?YcH5e5WZ-K1zZb=Kt(TC`ldZmj8cHSKKs=k zt+p=Q4FB{w;kL5<;f+h-+xa_><6bihb6tMT`*%GW(yz)Z9@2oDb|~=&C$A&pbqY|R zY$b}!)NgM~Bfpy{VtWu#M(y~X6f%R=spNV#xx8J1T}e}_(pdGYU0Hq!#dVnsqqe10 zVC$s{zP;!SfH$2dyaG{zN-|pmy(vQRK&J|LLx8b^yM;mI^(Nye>+DkEqlXsg$PWos z?V?6ZdVLJ#Gc7&S>d-YRH)gic!GNRPX@r3wkID!BeqFe!GDBDX_s8u0Y^#bb9PD4* z?I083vLkCgY$PpL?nUe|n5o4AXDaoMTnPRYd5og z1QS!1zP?Uy5}YS1mA1=($dK@L$saW@0|v4JWY2X78Jk;BfM_YH9)kcad(y}(TrImSY7-;UaMo=;)@6>F{Z4ry z!{ph%%R6H6HVTe15vnyR?Z>e;tyfP^BQSa!k<8zfMd5Nu9J4+t@6tZCp;%UbHQBX^ zgN&qqk25=Ny%y}#4sxq|ch6%y%=$p|+`zo01k9KLbb~1}FW78Gg!-B?=V0g_ohsMQ zOu#N&Caz-3>ASJmiwATAyxIpbR(!uy_;b3JbyJStMfZT_E6lFuyZf3ez-IKXA;HlT zI)q_is<8{OWSKt9%FQi${e6affZCt&YB1k;#94uRICYoQpT$CMXAw}o3w$6JLxqFOlV#vwQ$Km#qk7HJ2N=rsh2 zgZ}H+C4gSc8y$d0(dwOk9y}xYOwX0q_vGQReolZtCqq8mY2p{A^Q~eW(~VGnTs;Pm z@?&lR6EpD3XS)AfNPCX;SILBl{$BWB#ptr;%LR{$&Q0dVVHMEBN+7q=3c5NcutJsB zDdBFVHb^wK93t_e0i|=ILBM4utl(Kz7pq<^@3~=O{!pgVqUZtLV5>mJytD;aq4o&$ zNH0C%G7T3}7XRQ%f{FV+ipWYhUgI2E;gw9H>4@q|lZo7qzh(9>QXup4Ah`)<7xx2o zZk3i|(_L17fOF4kPX1y?r+gP;z9r^1aqFzkW;$HyvCb zjnd8^^bR1iJgSc=NUT7uRv|m(Aj&vb&f-^&O?Tx#ZF-t|470!RQS2UCOTKD&tY?w- z*($6r8YAE~Z7;qq(`90@k)NJh+U2Qz^z1|ETYqenVXD>&Z%o0fk|L5SU`?<7$UI#U z54HkZbpYD=U)Mv?KuGj5!&*Jz)i?T00%Mn+5D;FjEG>$>z}HzlY2}Y{!pTN$-BA!w ztJ{;Ay%F`y+;j`?IgR);j;-Od4o&Z!Ols)s`px6f;kF%wmJFIgAt6>^;(22c*-20P zO6_Cf<1|=8-W8K4_d;Xy)nAju?JwiFlH1-YmJBV8(b|ngJ#ZAw(0a(?`qRDwwE9%& zE2As}D#>k}!BkXuFp_~84n+AgNdB-ReGQ70&pLRsC}=AL7ka54Lr8hgdO^3U^eS2SpshT=MCl5Fj*>c@dw~&<6km?Sb{5P_ zozD+p$Ac?5>Dcg0T~*Tm`+KS8y2R!Hf9}Ty?l+&pGN%2DyEW^pVF%zn#(TNm#^$Kn z-gASa4r+D-)~+fy@}~mw5b5~6mR=0*26j+E&Y40v=`3?9SqLElW25r438}&}bG(CY zS1yHD_QQTg0y10B^M$TwS$)Q3&>+IvN{{Hr+Xrr|4LF4l*LoHb+O~b+*nl-;i-xVR z#N2m~ZznnVc%8xb2#E8}MlslJ*oa8PCaR+}wJg)kc^s8eZAep>+60#C!;6kobsMC; zX-3TS2HR@JM3ouOiP%6#5bWnb%-a*4sU()pGf0qc!V;WiE)%O~?=^+FIb9HLCY;iN zsj!{b_r~KM-D`13=iLw69RB|OqUejlx%tUqZa64HRO`zx?$)NSWYH)eHQfUgYbu}0~Q3m;a7Vf5L!(4utt8hd#u&ebm z*18Gz?R!pVd^5AoNF^`R^_Snq?bLuE2FmXtqOsN=OBr{_0VX8{gdA)8$WI2c5NFX+ z9NMPMX3wEj<{!xrq3<~ap;AaPX*5%tNbnPoFU8Q1H%JQ0inK|<35qtWd3&_=Ru~_*ZgAzlwu^3nNj5mWJB04apUsac-ZT>kA8G#8i76meVGc=pNYfQ)5kiR87;+pA5&Dlx~mLOQe-u*gg+-HQiJ7C3nm--zLi@u;D^XrManyNdKt9woCT2=fxO9C$i!PLGo~UujZH zHyF`t)4$a#WVHc#hGhw^`Y22@2|}gi5M33H5nD=b$C7KDIONUPn68`!a_pel-WcZL zC8uIRyiMSAt>Zf$41#wBx@fX!>MBI#HJNvf6A#s@9eRKWK4bAu!hXYo?Ytg3!@PLn6v0{$>5%=|)SULh5Lc`ODEt83yqwoXf1_$t#q~F3`@?i6nw20Rn zYKhmGW=+nsHs7W+6huwMjORq~9IR>s*c#jR=@ui%Z{v0?>jr#HF>$*)@ACVsol>+O zJblcoI*u}SkJO+_?tUD)^80QPYSYOHvTxr(H-3I)7c7sp+kY%Xp&Tz~O`G>Ox8ekGO6zEW~0vbLn_#;G_bZ5R(}UB z4;u3lDwb*8s<*5auGS`NhWbfbiX87I4Tj;ZH7d$kKKXDr#NN3GGXZq0yO1Gk= zu@=@EHl_e?#0N$v|G{UPdh(UoGfF0&2k{l2H`RYe^m$uJAhXZqYno;UepZ`EH*+vV zz_fG!hz}Fnx^n7%L*B8hVJni&N~&`^04 zp7KrLZ>`23kSZrzp7tE4;=PUW{JYwt#%Z@ z#wLoj4Q&=XR0BeYRdMZLkW>Xx^1;g_<4q*=Jzt7hIfXebfmXwRW0~m+|8$7Y0a8uyrk*Da9cs)dy`AXn67S7;s=7qS)vo}R=doH zW7X2}>VSC<^$+OaQ?||B-f?fD5i@Q=(cqhz2|(ad-M-9||AHj0(4hCP^75w^N*JVuE^xf1Dr`9?mWUK^hO2XU;a5YoAqY|9mGTjk@XnFvy~>Vu3x zX_)4HqzcBN4d-MUXDh;itSWgV357bX4#kvVeV(atGLb{=i{%=jjn)jclc;9KJ1-h) z2VE3=W>=o5eX+nOcz%Q#mlVdVXG|ikBV4YBMmu^)E;Nk0K-}&926ubetB*q5Zg5** zDVxZH&Xq;s5_(=b_Tv6mV&7`?e!sYzsV5cXp7PN}WO6uT`bgb8u{4IfDIzqBn`B$< zN@elX^xSDZAwc!}I)B)oS*Wm8OkdOpYqtz%j+BDAjTpwukE|?0NvUG zG}*?&Vb(%=BcJg>c$9UsbO1`u1n5^POs_`^xGi7DOAtOIC@)|x%HCd%VK9-#KIf$u|>AZ$;;h6jBvb!_+@<5vMd+48*PZ&UPt%Sh-`*!mLOD0f<6i_i2v)dt~j`GH$~}X1`txb3guXR9)!pQwG@Czpa;gJAJNI5Gtlkk z3$@NC4fX)orL#550MYMJ2TRHa`ojlAJb*TBdZy+TmsDsQFiTy;Zi7GU?1TewSy0cg zf?cFzR&-L$83xG0#nUj4JWG zT||Ffrn8IIVLq5WDHJ}b^uAoQ7=ot8sE7YJuY#x~B~zuyusC@lRg>x4L|iVf2)G`! z3HPF#vx@9p<_1!hQdmRAwl#KN^MdXxlZtcl(6o_*tSi||YTqhloWMRD1Z#zGWfgi_ zU7ibD5!e3~V!Kf(3^yu;E9f?n7X~e}jcqZU%g|3Zf)A&Po}=?Bh-|nUmC)^kL1b|I zqcC)#P}N&bqHaByItCKcJk|C06LVsCnVpzAp;l$gy$S8)DDRi!~$Ky zkO&d-&?zWpg|a^Hcb5F8K9rR6M~OJyCO1B)PfCoXFx|m#HxkmQq&mNnVt>V|XHpA5 zX9dIS(`To9-{&nwQP8H9K<=t)0Igb~8s_ zq)8G>Z|;D*tpTh$kL27;NrioUlnQxtk()~jnqdy{*2=Q&rEDw`m7$D@bu&QNX8C16 zAz}K!^f({UZq%?AXi+M%1t9TFnc2BnpfJmdGHW+P@rT))0H<3kjK*S>cQgH~klh7? zFKVnQUXl1*t}bxE$Zts`alV`WZWhAn*DDY-TDA+s1>cJJpik$KxGMDD`dT<(-tEM^ zHci8UaKE`hyyVLTn26UUgtnBy<~5F}^<=s$^b?oloo}Vm`=2bCF3!>3Oz8F;U$P1v z&iZh7>Rh$N(88|f8hO>m8t27r_tQ9A@&|%IlPZ{0NCrcdbwMT(s7U3M@^_F? zlOPN3&5`mHQqO}W zCa8`29@gXC0yXqBzV9D)#x|6%|ND1h+p>)Q|1zr@2#YwD3-+%0tHl+*{! zudQ($x)rS;cbywO=vJ8DQY;wp3-SJ0e(d>Kry|~j+IuW(PpTg9wBW4aU+EYwF)!>qr* z{(HaBdKh&!`N-(&jdReWWmyRndGTkfvw3Ap*aMFYIahr*@^!iFYCU3(EHL*(Oz#Qo zGT_0pj=4i$cf)r2$y-*ffcY+-fIgfFpF1}+6ibf5G(7HN<_zHg-R4c0!Beyp$e|31 zro8k(_pGY`KEJlRUI!_ys(B^l&Y{yR%YGcQ8K$ZdWPL0?nzX7jCa4{lbuWmH(^O`7 z*|E0r@d9bPd;Lt0cyrxy$AQU0kIpe`m0%hvf2j=WD+4y->2k_0Q(z5Xo*LwFH*qsY z(CuB|7SHvVNhOU9H%3IEZEp{e|3z_U(d@aD{soCjhug~icK-o^8)fui>rSpz2y;W0 zF-CKlRteKXglcVHiHzeW_g<6<yh{_5wjb@W*f zZQ{ZBT;=4%y4Mqj(N~**T<-#jJl(uM$&67b!F2hvTVC38@l+ggar9B3> ze|bf^@E(X+fZ?MNc{7BlrAZT$x zHaD!?v_nk{q6v+IQxl4|Xqw(#I*;Q#)-8f#6P6?k#EWA6_&c&o5$ds?GgZkpWL79# z!!Z?$a|N}TyUZHg25grFwo>opaO8jP(eT)OhGBUBQ(Mq(_fIMYoE_9rXeu;;B08}=p#TvvKE5YMzJ!U$CdGx5{Ey}@GqFF`N?Huupc-0(>Wyh9e`SS&+ z=2L!I%u__={^b!QX14lyqdhOA=1&nE3PxJ6G~RyEynJ)2=t1@zqUXU%z-?SENT&`# z+ysj>)|EfxA0A~hCzQe55%P5@wT794x%iCwbF>j&pDrNWK zc0&{b1A-ox(9BrXc2#1bJ!p5I9jc!bT^h4e;9jJwb}qEVKLghVQs-wC%K*AhGMwt$ z!VJ92hzyjnaQRUzi@qg<$);wn)V;uL<{Rkt`2A^wp{(1%D-yh1))?+04R_*aL1o2cM!xte+6trx!H zZjxr?^`o9k6512?>aL4EqbL4a92CZ5?ItMJC<@!$Ri$fm-p(4pGTgkrIyYq*Ev1_F z(odR-FheibZ40d2^Wf8xcPeNSxgg$IO0Uk+mx@$(3Ia8$3K`dT+MI~kEfZOAvBpG@LaP< z-h^)5wXYXg#EIgUSbD_3()Fn|!184zhQkPT_B*1fu`2W5LzYU63dC~JVFz%P$t zM?&GRX1=~IL*q}B3E%-*ID*ZqzeQpB_Qu_gaqx;g4qOV1rOgJ+A3~3zRkJtCnaVh0 zw#T`d))pmLnr!*hu`_oj7~$snPQAoL>dW$tWp(~3FqqiS1gY#I+f68qYEYK^9CW*; z3nVWesCt*_xT{T1Rm#v-Diu}ddOuZ^U`GC(RVV-FA#Ki(P(#@SBSj67cgJUK!8K1E z;CB)#GSJ@=)tbL`G5$ij8dJmf?`ssnPv#9d;L$>c+cwYCyN=2)4+wS}@-Aul9SE`& zgQFgW<-MgKsD8H9Q+l1Li)6?qo?&tnJZ-y2XBs+EUh*vxp&6=7DzC5*6TsQVQU-A( z{yb9c(>%=Q!(&TbQ9}eHmgJ;H+zr0PiWVm0+hzId2i4obWKMl*h!&?2$=q~Y3Cj~%3dm6okY6@m>*hL9YsY2 zs2~qhY#wMb2nfCDZc^z^8T$2VW2o?I;ACI&!Pa|=PU9UQw0lEs-*1mk$ zy=2?>j~Hlspi+2NcR*R{(e$OOh_?8-SoJoaBl#A@@XiFD3kBPzn97MV3rFAs7|T&1 zpnz=sw~Jp^%Cviot~lHamiNA9VJwSdz0&Csxqyr!EVs1Ar4?SyZ7ZQH6UAC;$q@br zSIWw<$B--_Y&{Rxa!bRF)$534IE*9RH>}P`HNh2=H&gFqdJ}-&m;e* zbCcZ~%BBCD*V71_?TDi9WJJ9+HqwKe3-y$7G|~XAL@eS6=&Pj( zG@P!muPHk@k-2h3-|K9`p5xvBURcG07HliYm1)+PbNxnON<6!G9&vR)EdEVofSZ4`NMkm# zM%;02XsnQm!!X)+o0y(K5fB-0h;fbU+KJ^%L@)aF1=}Nn^m+E*R83?=L23VOUu{1s*eQ-nlTxNd&rC<-lkMCH;{auA0Se$pZpa5tdF*c zZ-5{;?a5^U6*jfi-XtYg8lWg@{=Cda7DmVp3R{p@`x%cCu8}7veaph0XDVF44?x(7 zyA`?-z@osIo_HF8t*0Nuc%XcXG4SsL_)KHF(W0zAnQt9vmoxt{AG)i;V^cj;ka!8P z2aigQSFc1wYBZ4|BJkE{Vmc3*kgs0KT@4<@RN3N^gLJ3%NdNo8Dmu7 z4|+Wn0=F%`2;=Mfz-@sEq=S_L^`2=pHD0uSRYW>atHX^j!UO2G1dB*5p`~Za?B6cZZv|<7%E#xhPibJJ(wHi}`L_Qb-Q!vGY+&GyT{5e~y`JQp3txiAWy#HtyjJuU~aOK{x&u zxEpg0Ydj3`H-x%$4_t;(DUl2{Cp(_*v~Ot^0rFY#Zh_OCKOF#V7{>TnVQ%JrBkL0b zmA9;RAU`x6mA@&E&yq&+y7!19Be+0Gji_9ZH9e$3a^0v8R+hyzvFxW8eAK*uycV|% z(-WLk}IXvzda<&Y^$#oFf63r|;L$HOF(uew(C9bh>g zM&oWdS%>3p->rRJrbt=F9l9ZI1kRpPRv5rJxvZCjW4LNJl*ng*LAmjtr0k*QpH`?46t3-+s~sMJFm--H z&}>S-1|f;SssvT*35JB%OaA9?Z+lvRq_+&ycWwJ_NP*$YuK2q&+(zv*q1n0?<&d_q z8>?4Ox6n3uFJ#D9P`p+)8^}U8wArsPuc!2zqs?jC64g zORifF;S`pra@3!15BP%V)Q(S4pz|TT3cGj3S^gVQfqO@qoP!K!Rm2qKpbtlPr-5T8 z!{|vvbe#DeK10h8_aY=_m`el}5)cTT1hXwvjJE3z+Y*oAV=duC`BJ*~Le;7fQz($l zp{gBAsbXpbz$-ge&=354yBy;z==b|yr)x}8VJFvDN(zl`^J8ro9LJ-$F~N*92{ccO zvLzH_6O@V>?@i7shC1FQ76lYUxTf{#{=w!zJrgFDr}frXp^5SW2w@s14VtP4sD2C6 z?JV^X*hcq3{lfH9SRf5mYe~agV;tU*rNxKGVG*emNTDF~2f8gO#(G&;6gJ6U0n@P1 zJo0*gZW>M1=KIf|m}VKu^2uky6ibV=&(45Mttc`xnOVKXAfWnqn1n5k_sRH%`*CjYZy52I(IhtI8nMX;{QDeVfJen2Z~;hNi?VPZv;Dg4=e*WS*xW_(?96 zlazcZH*j0MSXlM%J~=I8$nJg4a0;Lb((kd`-$3nTD9S1!GzTKclO{(s+8)z?DX}4(l?h+W-FO7)q$>Y31b6dNmqzq~$EiwA zb&O<`uA;j@x=qe;aJQ+Oc5!iVxC3rD;{5J57ZnxZAUz#e6ECF|Oxs0Ph?fkyuRDXT zHc5?#+#y2FqZ4L%pv%gHea~N?Zs~lzW!C6lh{+rW?QRa!#Dsw2sW^<^sh8 zBJa`Nlg)2A4(9;AX~vKsfq}Tjj^cqEYO9d6p!`seN4lGxpNqzNzW73dO6Ukfd%&$ zs#18nv~LG`$n5Uv<+xr&R*aj)8;WI!6)$tSD(*>mUWb1Dh2U=g?ZKjOSX?IC6-1Av zPkpzT#qx7-x2ah-rgb(O(-lMysPMf{19=d{e54j{L=+X@1h@Mu$^{U8YY(lKjM#}7 zRzW$zcK{R?gtd$X>q}G`bcTVWrRbdtHPd>{K`wg~ODU8Uf>)s1w=Z@pa8c(%Y&1YD zBY=!2K5;9O9>X`S8>mHD^$enBtB^tRjo-%LLJrO1t*r8zMPWr_1bu!sXKQ2Ms!Fw3 zs8o|1%ryfIMBU)qnPt6bgpa+*T}bQ&iCAe&VF--V!+g%#;WShq27Z zdT7&Dw(n54@s_=w!`=QJ=uMfH##UOtqVi6Er5Nw#_ozEAnMxx}n_h?;sc=v5R}j}yLGi#i|}A@%xB|n+c>mAp4KAL+r&sAD4F?N-_0eFWWBX2}M}|DU3IRT4W)p3Tlpu$c^u{>>|@8UMq{s!JSSRLt|d#-*HW- zKa(`#aU=o|;D_6^ac#N3UNKg3&xkg7H~M@;*l}5rfL7$jLS{n=7T+S;5w5IJmglut z+|5#qEE(Th4t6W_<>}SJM%}Q9bjuDyn_dcNY>168yODVfZMd-8!+e2jE@s=A=$H{c zwQ*Zp44Ud?DtvQdd^*pjPQ_>}D{^+ahLC)3!778(n-AGaJ7VieYMXgI7?ay+MUSV6 z6L=b}s8aOpqiZmYP9d;!!pQkdf%Jj49cFG#tsXv<&y*>yD+Dxq08>D$zdcQ0e$}~T z$^2eiWulT?Pw>xDoT}#L$ql3}H2R>aL&;5SeyC#CB5_Kg7QZbZzMxEnW1_3D-C*7C zl8X$+zH<|h0Qcmfi-rtpI{S_5y#xftZzp8pq+$tnpTJ~_QV9Lx;72taqX~#>a|yN1 zAYeOWp<(P|&!ai__G7P{ghTA@5smUJ-TLss@n-@M+;GI=t1!0w7zl<9HzG`BqOml{ z5*pJbM=hw7o;+U4Fk!t7?@bkXGR;ZtJPmJMG-@E zh1+0kq-Y(T+i7qZg$V%tx=fR~AHiWDha?i{o@d(i2)>I%`VKdHkII0Oh~J(~V)@J& z!6B`4x!s)jFNVND%_D?mL?NY$FRYr+cB=~&k77q-mMjz@E}`!rom9}(gM;=Fy=Wb# zDqj>UYm_MQXDTb_xR!a5%1^#dxQ|jJ$|0Xh3d+JrLo`;3fvEIDcM}1F-BC|gZ9z+S|D3!Pul7Z z#rk(?fkS9z@7V&Vit$~?Eu>Vjq+0R`FlCTbBn{uBwc3jHV*UD>j+)>0bE;iYLaoBm zozlEw_!w!|{2sUQN6hQL*UMo>rmg~`nI>RQ&#aae?` zN06G5d>W8H2G$zjmg$zGc9#-sD}SFiHqtIgk{T_GfY>_01EGC0Rn=^>{nR;N@z{a` z=SYXUiA9kiRX~pF;9h0#c^21&@Jc|pNECidgD0yYj{d&=7Mo79bWbO0nhr>smMhSxG!k7J^&l!ic+EjbJ6Ww%n%WF9ojm!(7L1kvae zyiScneKj}7-O6xV@6D8{h|(7Cr|#zezpu$Yq920H?2j>_e{Xj7zS_=i-M8i<9?eut z6w3oNw&EF~V-=sV=Df}@!$0+*QbklYo@vi%oe~+PsEk}d7>;8iR^`DNce_G3y|&i!F%S=+Q#itFM^bv|f{^@lpIFkiHbgGj(d(5Tv;J(S#pD#n@9R z)MY;YsdZqR1~5Y_h^>D1>Fgp)_3MVZC1nChv|gw{OU+;oP=(E%yue&GEh1x2(qyKq zVh7kn8`hw=DnVqK!xT;vk%@HMeoGbXCi19TE(FQ#lmc|4+9DgvIy-VB>vdXHQuiSj zbd<7B)MY#B@%+9Kjw2MWeq411FM$MeLL=O597Xp!k-YS}zBb;BDR+E_zn7`?TYQe^ zKNIiPGt7<7^wMK=#MwW4nDHu5Ia?}X)ZZ@OPA`wkmQSqdMxzOURz+F_E&*YRYTH~| zPdGw~g55zk#3eKi{!?57$(5Z_1>6SJBGDG&VYC_r^1T#G0&+_L_3gQhWB3FL^c1xd z7(B-`c|X(|h0sYU^fmWX+cZ**kqN_S7%$BpjumT00i_ZEx@>oZvB^kqiryh_nxL9m z$adzPP#3Gza+talRLrH&YU0pjsYQl_!?G}Q8;6}gp=HNo-@wUr=9E#UCaoSMLhcY2 zgM!6?!jGkX9S5Jf7bKIol9M(%5yfE))M{8GFk_nd<>7JQZ@gQ;NRko8{AYw3-Vss3 zfrwo1X}MCd{Xg%+R& z#o}A$bBp8M*qliG*UzWI`M&Q)>>P5-;_P>%XB{&w2x%FosF|a8OxZr%#PB}#*1yL zyoJCn2pw0d^Y@q!%Z01z*xwLzOQ{otNBWihmx>WMK&E#5Rjwd7nq@qQIS8_j+rBI` zyb4jSKfE%l!Gl2=%L1sm5=>gU7Q{v@^-{MjR%I?f*gHl zTPZ$pxlF)#0_#>#?y^~}n<7x+*tpQ)!c)}k}8CHq|5OhbPTDjX)s#iUBGXN}RNG_H^ka8^bwa6Ap zVI?Z)ClC?VY}=lV8k_>hnqjy1h}Ap68o4dORY`FS=My{^fvBMntO8evUVIwSN)Sg2 zy{wf5c6&nb$F;2lOvEVF9_Mh|%8mi5bdD=upc{klerzEq`8HCNpbMR0d(pkslF|mb zwfHRK5+#;M7T>b@P3nCdj533uH@DB6<}?qK^gboAh0Sm1 zy{h=O%hkQ5cV0dx-p$Yi`V4ok5SR{g2PsD+BVJXHnacm7+SAu!VR>V};yI(i#>@+S zFhc6Pe6aVM3TnU%5*M`?a|O0iXV+drQU=wd0c)h3Zc4@lSieN4#?nB0o7JV1+?`#y zJWhr$CVGdfI31-D6K@2jE}p8Ki=2Vm5J8I&AePvKDsz}l?h}$mYM?kqFLGEkt^~*S zI+h&{<_S#Mz?%R{HMih;W}61uGN_^P>{9RtyI}|n1VZ_SYpgwekWWbJm?%xL&DO9| z7x@-R^Ixg-CySZLj1>+`MKB~DOJ%{*GFgR#TRHSz9_A^W^--hM>iM|AZ9%tq?T`)b zhp(_iulDyrUHXf=>6C{*iCUEHtDp@%aJPE>ks4hf#ND6(m!Qi^>|!jA?DeEBz-@5` zDCz+!HdB2VNcyZ5e3v^K08lXz2$^;iE?c){i{aKadXK;=QzeMV%X^Tl-ikGf$?CA* zC1IS?sLRf%dbF%Yx7B$PI-5p&KV+5I6h1RKgs_o91IidK&&=phH$?9UJ1~YPktxt{ zb;HXviDFZ&XKltCLhWl1u!w6NOPAWnfo{rvtW$X_Xuueu_vmu%QSMv)%^@KhU93qF z7?MRl%Pv2+zf7tNw_c0~m&db=Pm=4MueqX8s<2TBUk>?jgEfX&J)xexW;Q@#I8BDt_t2P3Nt&{p&CcnbLC&AR;Sy2x?^?+T(60yrx=BvZ$yq1$`!Lel|=O)&-NXjoL^z z)tlq1xiN65teS<}AnodN3u_%f=|Q73mAP^eHd-&B=#(tW;R0gqSOI2S)}twRaYW9~ zLGu~atVi>y`y?YLJpe*f&SvXqKvLtKuGFOpxaA+j-8vb_BLF0g<__w&P(e)dtj|?# zP7t`X?dkp0@hXkHdWKmqWW6a=1Cqn>pZ=hmIGR}JDyIY5QeqWcR8q76^E*1V5x#I+tvrcuGU1MY>+N|H>bA8S zKDZ5S;kH`IL3wDKC6d&7DtzAYkRfOr>bqMD+$0!7>f!>j@9U7h&ufpq8J>d{wr*e_ zk$$5HbVIXVB{qs#h?^O7+YArQKsP@Amwx3(k$@WRb&VDl!G?uGU4DVD|7Aq^b?U~# zRTWU`!M3H-mh&f*U>sEUqEH+j7Y}U`OPM;RcxuB*ve(Zotl^bW9zn&9S=ANS3S&eg zLtG=<&ouEe;)Kv{P^;H_qDsX>0TfuCys9$ zb(y4LL%DkxEW4zY*?vL#1qs&tFK85wp}l1>aI5HaQg$$A>U7)2mGiAs*3be;wF`|` zh_W{USz4(PHZmHc4HpQ?N3{sZrqopzwGC6wAf!qGkA9KUkT^q}Modf>?1%Q%$V;|6 z9g^JtH)d^{PTKFQ9Wq1$F)8No08jg9?oSGIS9NDQ0#^LbGkN@Lk{`@nXpQxFk|o$V6190&g{hNy%$uWvnfTQgI7U zDYA4Re7Y*Me7adA20-e3#Kl6AQ7AC=3sm9=wyXtr%CdKi6tp^z6)H>7ISunnC5o9f zh_!iI#ky@>(fP<&TVFZ3#(-nt7Vt_=is)j;wJ+K4mziwDE?@6Wck1D$ zDJ)*T3C9BES1l6u^8!LTlm_|CJjvSilmWajEQ%sJC2< zX!9#@)a-IW`0`(w3OgWa*!7N5VwzqKl;NlkkU7@J6K->#?$jK-lP8T7%>s1|z)HZB^t>p_>voRhotkS(9#hpu|!c*@L&c+Cmbj zZ81Tsl&TFiLbj>%!_3nkGjPvlJuz18Szm*$&(imTc};r06jgVBw?;B73SVEBhTZsl z+zk!=pW`})9CFb+`az(ThE%8yeS8mFLrPgyO+aM*-X)Av2hK9qASA=?@Tnew_DuX? zK=vtv_6P}|AP;3p^3}7b?2+v@m9k+`*^QznK>>#BodPsCG44my5$3}u7wf%luZmU_ z-okJ8Z3Fc=g^k((0R{KA*3lZ;mZ6etp)gL2=Le*n1`dWrS424Nn`v?1FzA~p^tL$> zt(Z~48E0nb$YORpHO!Enpn}-PoTjnUYN!@gJ>oKkb=?>1(UuCK3Q0=^Sq`c^26|Ej zQ3=W~3|NZbu~OFeL9QLut;!@q&Mq0fttBrpSw zJbG($zATrlE4o=Bf!Y2wiRNgk40dRnoAIk90C8Ehz@HT|(ycTyNns ziRx@NH5b;_-uGx4kN9tXBu#3=MZc?CC zaA`3nn=Q^Mq9y^}NkoS=7Bui}CS&|Tpc-bv4YwOokzUU~FV0N5WJ<_;2uTxkND1@9 z+ZyB&2A9&R2<5MaA#Nk2YzaLPB35qQ`yF*d&LI;AeMvWqR6oUq5=xbyFuBoPiOwxO zfZ%eV8&V}KC|KZ8Hv|&l9W5vNSbc%D=morsUqJ=k!=h87bj$K9Z9iF%O)paDhD4gM*tmvtNTJ%DAUkNSaySAcA9;fwQ0 zvEv@3gC%!^kuP80>~L!nGMgTk)Z~bqmj;d*s&2)4{OB=^H`>X>2+p(Sbdp{fgYyZE zmr|Q%1x4KtWn(TEj^H?Y1C@p!vhdKpx408u+Je1D4f0ONqE z7D;`l(fR%X>hgf>7OS(8K>25czCe{`McdoUBwDOTws3RsXKhiKO$y*yTe1gc0i@jA zpp==LwD0qhCmz5P*lGZri4*+u~U^G^>3A0yRis!+~raH!sihpHtf%xls&} zthV&w(k%aeJ^fi8QK~!oE3M!Rex_8-wU*;b?$%{O*Hk=rEan7I9ZHqy<&L$zaJLZQ zbyE`P;kM>yg^(VwNuOWy!sHF{n zkxYT^Z#OnEt4O!S+m;1JFywvTcb*#B-EP*C$c_QGaaObqLqL8l+-!U47Jm6gkMT)X zcH7Px)k`GU)p&QwQReV9SYU|F;SdMA!5aA{T3aU%qAU9=tX_Pk@lE&drVny^E zG;-!EsQ~t}YAmgPq;pGOA^a0A>QH{0d>Df;~V zdx_F;it?^L=2JLhlKnK{Ft{6cfvdSOnh0NIkEb!_GvtJcrKowduNb#-nA-uBq>~c5 z3Rkgh2Vf^vsnvEtVO#HH0VHKNxZpHf=`i5Z)p`|V`equ*ZeX`%L-FD@vR(y~tkJoK zP^sDXN`W z;i``DB|#9Z$#UNTV?PsEtq3_f?f+?vsfBc8EQ6Nn%FTuF-``6wsUq|}J+J{D!%aM7 zeYZFr7FQpz2B6o6!df8TG}T{F$kCo-Z4^UCIFgcR+6Q(>DYbr|vU9c7c&Ft?okNiJ z!x|VOSVGxkhmF}^DE54j>6Tm@;aBN!KnLZZB?euAg{>DizlSV?mYJhIuBH{a`Z|j$ z?5+0ZTH!V|Qy@gB0#umC_a-yV5=lid8bvBbIJEoUuF;0g&*yZ#2EQVn55TfWFvr~> zn~XwJ26|v*!(2;|ut@J}utvJ2-<7@BMP)xjzEOxG-C}pk60cpmU`lM4f-{~Cs{Z6a zH`PlPjHRfj;H28a>R5#8;bp!{bC@DZg%lz}{kD~_D?F`HL#YGuJ1hz{e;ENZ+PpP9 z^xg-w)Z6?o zDxd^=D2XqmAK?qcz+^{rFdyl%1+fGL<`1(lIhe{S=9LkuhDk%p#KelW!&gPTO zSZ7P%f%fr@^e0_$F2Zdw6g^#G=E$K5oF;~Yo{VhiaE=@U8K(98ia5fc1G@4A9*Dum zCjj@9@Y#ucRvoKuGcer` z%b0dzB9##WtJIk>sO>f4OoykDq%Xr1Z(8wT1>D9~YSA1%bAreWsvg~WfAb_pVWVK% zSep^|TleHMZEOBO0Jr%fjG!T7cvzZ=Vz{kA4(X@mOhyKj9cduBBYOIK5JfDd7oboX z*mO-o^c_JiUBYN1F2WNM`w$lkIIs03xS)Z!8eZ8_Zw~dc&DF_l@3Q)cuD@2-NqC?G z?04dO?6#WR(si2160{4Va~k|8%w`E98lP!)_R+foJiwquT@giZXIo)8b|&8ohK*1NljJkW99e|u zt|SU8$H6V~9WEW~&!GL{+}yiSH8mwV6F<9veYgk+^VuI=TJ}JL(22*Nss+Y=i^L>h zhE7)D^f(IHw_)zQMFCbTWrJbyOO)(cjH^{1~NigGFRyiWt@j5gY= z%7Tr}f#W-HK4`xJM46F%kYfoJplS<)rlWMJvH#nl1l($3FX-JuIeZgWo!_UU_x)iL zqW+G*e(KU1*w3Tf*7rb}equM+pev}h!UCd^@oT6Uk;tT9rn&^MiP{Yb9pxTMf>g{P zD>kNoeXPYnfV)dM8TJ=J*@U7C9kc8t~>Dxhu5KOJ*kPQ>bjzYfYO(RVgdnzFY8PA|rK{!Pz==CCmp&V}S| z)9J{Y=^n%1P+QRphCXO&mUh711}{5s~SJeB*2vb2h`2MXij{KHl3&$ zBU!102;L3BjXm0}Am=$`b}Rm-Im+0gM3~!DD{aMRn{=soFEnA#izi%}6Esl@rvW2E z%KO@ZXEgqP=i$hQabG8el8Rqn)A@V~S()>y4xex}dW@MvTsL?yi{U$o8u$dn^FnCK zSyb%X_`>Egz2Eht)zxjsnb}r{839GVm5IYzg{0JA5V*W!$;m~~m?iuatgZ3{xIQ(U zMGmHGdad;IbQv&Bk#g}yEJie0Q`q1bg)#G9NM$qlQBpOr(mwW+jgleZePBb+(P9V- zH$|WV_4ie*!%{hWJgn3hI!hB~{wI zyBq5oT*;{W*YB4`wpzVDRT%>s(~+R3W=_`x15WS%rI7FJD`f2Rly?oH;XqS28O7(9 zGyf(sDRg5?cfzu}iK1`#L<4xsEd?sWUNA90#)pRGSjYmpk=5nvs8OUGK%v{xlDicA zrEKT$_I%{t?~UmFH5UQamj*^y|2yce;ubX=^&m?HfbP_#Q8Wr4RKSfPiERO8co?8s z6zIUmeQ2$n7pR5P#H#obwJDEBw@atrc`pSEuVz&P+-h*!;gEdP-~)+(V#{7oKOqsA zk`PC*kJwWJRy-VaVICZ0@uKWx#Z zv8kL>H=yHsusWD|G0T!Et5NpR0p)ZnrLM(o(DY(O1$whcc{$MMgxgaa@~N!FYV>OE z5tBWicR>+AdLCT~VE$wE&bh4JUd8hLOX(|Lm&u3ve6Cm24tIm#x!KYR#N8Sl>kj~J zs765OMXohZVmD`ucJW?w6_y5Q6v~kZ|W}nlN1)PDjkIEva(p{-2Mwx zj#~Qc{YNMmS$8pEI+0@~CT87AWnj6;>x!rwtFT6tBW`D)F`6a98R!!agKn7^F&s++ zs4zSZcNT?z6m%Qf$Gb@-=D^*aCvN;4me}ppo1vZw-MO}s_|I{a)D=BS1Zd7kXkv&! z{ZsfE)}on>@aV^z@jT)xDk{JYnG9vQ$Exd9>*%u>)>Sr9A}r9V)_Zua8_XQdpjG%B zdiWgXcthP}9EI>G47_lg^Z~MhwpDR4Z%~^9Yjc<~s7{stsj-ifbjDvfd}-ov#i{?xmm1>5SDU~*fz@i}==3Ag1`cl7}CSMB4? zw}*pl1GP@JB}sVWqoOp@LZ9F-Rcs<5NKT}H@LG=){i8u|G)zh(7;Ztr4yA55G#07| zH9>A*zhE~JHj$5X^Wbhw`lv}cD5H>(MoiFUu~rea>Mc%Vm(TGG+uDg-orhA{b$g2J z9seR_0c*v*k8+77wvRp;;baBu8>H7sH`fAoAQRVbr0_-r;DI^XiAK?iJnBsyQDcu#Ov>w*$}O zl91ApRCIzo?bW3FK@Eicg2EVmuW3laC+_!fqGL8%(Y{>qdqwL|MRR z9ZO^F$}yl2jL<7fMdm?b(~wZ1z>??@R_0v2yc@@KKihA%_4inM!aX_~BDQ(<7t_JS zudhtx|H^K!%~U+j$=;WmYhjZun#Cy<76L=15D02S!QR zzl_Z02hArFslZc0c^;UJ?B;5iTbo!eRI-%KyBaC$F&PR_4o0PdtUMylW2vxVm(Y%& zc(->&Tkh7;5V5helIAG@mub38!3Etu_FA)rw$j=QbBH;IplFw)c%43Ff}S>c;yPF~ znKd(x_3T$9uq$%EDWOE&-H}Hz763|)?e;=7r?ctI3`)?PG#XKS+qlg>fL6L1bPsJN zV^7{@?DJ-%f76TQF#nHAZ&B1UY&lyJUmkNQQwnp^#a)r5k-ocufHYD#7!n=PK)q=Y z{@Ic!a7>JUZmQ(LsytJHCGhJ#LpKqGX~t)|)X%TWg}zYGSyJd!c>J+AjmhuB@{>! z65A$-ju2*+qTH)}opo8&JYAVm%3N<$ry=MVdBihboBD2I=_G^)Ymi^qv$#FJ!pv%d23l9&>l@tdh;|SvO)<=K>k_Q}Sz7k| z#s0^_MU}wWgLwq+y*L}gQ)7Y+fSNnlQLd>e1NEU2<8}fpSNrHFkM@{KV`xVeoM$}R zg|H}SnKz@NyZ4-&W2i{IjZXmHf}HupW(+-31(~RcxV@2DB;rwhHrqGeEh&=}w6@-a zDV)uqksT*yHm>ZLae>p;+aNQdmJHiSI=b_^qh_(`*6gV*nx^~hippv-b_U4YimPQG zE5U9+M?D)>C(>7Fn*gtNvynC_)>fZR?7zH!KaP2~oUQRJvWlb5brhq5b#%7M3GJ{0 zNGy$npAi-PQ3S|i2g4z|I80DsBD$w&GZFw4ri1u&x0L;=KX6*O^6qb8q9K)R$M;j3 zk@Nj7M6T0kml7S7kdtnugahT79>E0&!QH5DU_RoB!{4t<%y3vKnDSAdqv388t=Hp@ zq|#+J=(i2hnl$_g^wD1&4R@2=3sT;q8|2`T%t4bw&rZe+N)JU#h-@#6p>+4C`?_&o zYp51VR0u=cH>UN2;J30@W)y@L6iF*63t*zvPC+#wrg%G55iI zXQ{h!G^PE;8H`)-cNTYBsbx4?be{@RH{CGE8HmxS8Hc#dsSZ3B+?zUY^$z2B0-P#x z&>fq{t!^GOpGerjiP!Yf^XX(#SSHUW_u!)PQRS1%`uop`*c$X=Q6Pv;Is7nL1I-YP zQhZsl_7jI`an-=rR<$7);v~?IYI#@o zefs6kLLYwzZRiz53M%3GFc_olAL(R`bGTvJ@@?xU%~cj|1(|bCk4T4d1FhC73Xvj$ zA{|IljX0*Gm$m9`<@j#_d+1+>G5LGyS3;c@WcDA18%kq2^7Lbvxzw4zGhpWMdIw7n zWw{izudc~x?uB9<)ApoQ$Z{)e-iP-tPqX~6rUGaAizVuYl#hKkwBjfbX6(;eV~S7R z?o;Sq@$i#=u+VNsCh{ucSg!gxFUOt42}9}xRaVj`@d5>jNa;|!9_nU_|)3-jibk$oPE2zsTy}Q@JxRkDA6P1QGo-e*BVxAZDGNan{ zL91)|HIioZ)m==1S33CE&NRlc z){wUE$Rvi6@J2+e2Xx9r49#H29acjN2Hhl{Xb5LH>hsY$%C}ACVA4QI-nu_QS}Uik zwyAY(D-1=RYCZb?KA@k!C-e-%LH#;-VkH;$&TjSW`cQM5i#5$%ml_3d);_AQtLzMEvQC^zcG{sD9xNtwvZR!oW6qy;dIKs$P)wuwP>CC;`GA7kW zQF=`w!ZfbPNGo+0YdV#7FdEPeRlzN)IgYktjj}>@wKYRhc!>hnD!g9zHcEZm5VA^w z)7>8^2XTW)K(}HHE2d{=cqj)9kIy9a3?v?!UF?Y66Fz@7=f?~Q`)wK$aQRUO8{QN8 z=}xX22`&qPqYWa^L zn3NwI<53ec5ag_ZPvrc^xyql`Zj=Q~iyaD!Rtk$8ZClRO_q0~Y+^Kxo zyb3b^b-`azvtLUxh6Ef*Zq46>!LeU0M=~)C%99sM0muw(gz{%JoIC{vjthREXY(v|nFK zZ0XW}V7Vgw-54?oYd|-pM|QUb=T@>hR>7lXa$$uaLN~W`WN;9$qXeMqi`Th_{3WDq zE`7~Ggfsy^zjs-tk|R6alz2?KR}BJGDIK}^?^i5|=9GfgL_+F#W%%ZdCh;ECBiNcT z0`~S6q~Ypng%#TkZaxTx+t{4kFVmTk{tVc&h?f}<*?8goQ#;lIg(2{ELvLX(7m3IS znmA9l>fdA)vwd!{kdl9rCZI!OoM=riI*T7has3!=!IBKv5}Wz5b!y*2sSjMUoIY^m zQm=F@7LCh^*xk(h$kbx&8ddr^Lq26HNzyOFvT3KC6c%Dq?PtJ1FPCK@!%2K*Nq3+@ z6ikpRxLe)gZF1&{2evVM2i~ChT+cp|h4z5AV2FralE$}pO`(n>kg`94yUkgDu+Rg+FsWbq8sO6~S?QN_UV+VRm==N7d;Ycp(R}z&}pi%UV%XqI34^grHg1%jl8$~KcF(ZCu z>j8Q+pHgv3JJ%yACk3GZ!)cfO4%D(Db$K2jT?zn+WK@7`{afiUDBCZQ<{@8TU#ND? z$&~Ea<;oOsL{#pM;oCLYMj-qWQCmnPt{|xX4skWcEY`O0b^Bs0gP?)i`dN0r!>=uu za1FldyQ1z=9>)RYS+W`DD*xSqBGoS{{#7;Dt(-y+`aK&V$5F)?yi{#MS@7p{L;Ei* zNqanfDq-gvh|^QVM|kIZLEXy_8IVox(gml8QINArKeuWj&%t;gZ1C}8#h`CgR9dG0_jMUY zcm6hbk3B)NcbkcdsVkmfqM@h*ON(x9q>v!!b5F;(r9mXY@@NaoHn)?}y=c)zot6}p(z7$elsxUeW4zHDL35}_

    Q^ab?B?668y%-)L8k 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

    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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] =?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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] [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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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